diff options
author | Lucas Almeida Rocha <lucasr@src.gnome.org> | 2008-10-10 21:37:39 +0000 |
---|---|---|
committer | Lucas Almeida Rocha <lucasr@src.gnome.org> | 2008-10-10 21:37:39 +0000 |
commit | c94cbf18983fa5cb47fcfd118b8ff94d71b52361 (patch) | |
tree | af4241562bc8fe99588ef932f1c820e560b98867 /gi | |
download | gjs-c94cbf18983fa5cb47fcfd118b8ff94d71b52361.tar.gz |
Initial import.
svn path=/trunk/; revision=2
Diffstat (limited to 'gi')
-rw-r--r-- | gi/arg.c | 996 | ||||
-rw-r--r-- | gi/arg.h | 54 | ||||
-rw-r--r-- | gi/boxed.c | 582 | ||||
-rw-r--r-- | gi/boxed.h | 57 | ||||
-rw-r--r-- | gi/closure.c | 287 | ||||
-rw-r--r-- | gi/closure.h | 45 | ||||
-rw-r--r-- | gi/enumeration.c | 161 | ||||
-rw-r--r-- | gi/enumeration.h | 44 | ||||
-rw-r--r-- | gi/function.c | 554 | ||||
-rw-r--r-- | gi/function.h | 48 | ||||
-rw-r--r-- | gi/keep-alive.c | 440 | ||||
-rw-r--r-- | gi/keep-alive.h | 83 | ||||
-rw-r--r-- | gi/native.c | 183 | ||||
-rw-r--r-- | gi/native.h | 91 | ||||
-rw-r--r-- | gi/ns.c | 321 | ||||
-rw-r--r-- | gi/ns.h | 42 | ||||
-rw-r--r-- | gi/object.c | 1469 | ||||
-rw-r--r-- | gi/object.h | 57 | ||||
-rw-r--r-- | gi/param.c | 416 | ||||
-rw-r--r-- | gi/param.h | 47 | ||||
-rw-r--r-- | gi/repo.c | 607 | ||||
-rw-r--r-- | gi/repo.h | 60 | ||||
-rw-r--r-- | gi/value.c | 512 | ||||
-rw-r--r-- | gi/value.h | 45 |
24 files changed, 7201 insertions, 0 deletions
diff --git a/gi/arg.c b/gi/arg.c new file mode 100644 index 00000000..8e3c26a5 --- /dev/null +++ b/gi/arg.c @@ -0,0 +1,996 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include "arg.h" +#include "object.h" +#include "boxed.h" +#include "value.h" +#include <gjs/jsapi-util.h> + +#include <util/log.h> + +static JSBool gjs_value_to_g_arg_with_type_info(JSContext *context, + jsval value, + GITypeInfo *type_info, + const char *arg_name, + gboolean is_return_value, + gboolean may_be_null, + GArgument *arg); + +static const char* +type_tag_name(GITypeTag tag) +{ + switch (tag) { + case GI_TYPE_TAG_VOID: + return "VOID"; + case GI_TYPE_TAG_BOOLEAN: + return "BOOLEAN"; + case GI_TYPE_TAG_INT8: + return "INT8"; + case GI_TYPE_TAG_UINT8: + return "UINT8"; + case GI_TYPE_TAG_INT16: + return "INT16"; + case GI_TYPE_TAG_UINT16: + return "UINT16"; + case GI_TYPE_TAG_INT32: + return "INT32"; + case GI_TYPE_TAG_UINT32: + return "UINT32"; + case GI_TYPE_TAG_INT64: + return "INT64"; + case GI_TYPE_TAG_UINT64: + return "UINT64"; + case GI_TYPE_TAG_INT: + return "INT"; + case GI_TYPE_TAG_UINT: + return "UINT"; + case GI_TYPE_TAG_LONG: + return "LONG"; + case GI_TYPE_TAG_ULONG: + return "ULONG"; + case GI_TYPE_TAG_SSIZE: + return "SSIZE"; + case GI_TYPE_TAG_SIZE: + return "SIZE"; + case GI_TYPE_TAG_FLOAT: + return "FLOAT"; + case GI_TYPE_TAG_DOUBLE: + return "DOUBLE"; + case GI_TYPE_TAG_UTF8: + return "UTF8"; + case GI_TYPE_TAG_FILENAME: + return "FILENAME"; + case GI_TYPE_TAG_ARRAY: + return "ARRAY"; + case GI_TYPE_TAG_INTERFACE: + return "INTERFACE"; + case GI_TYPE_TAG_GLIST: + return "GLIST"; + case GI_TYPE_TAG_GSLIST: + return "GSLIST"; + case GI_TYPE_TAG_GHASH: + return "GHASH"; + case GI_TYPE_TAG_ERROR: + return "GERROR"; + } + + return "???"; +} + +JSBool +_gjs_flags_value_is_valid(JSContext *context, + GFlagsClass *klass, + guint value) +{ + GFlagsValue *v; + guint32 tmpval; + + /* check all bits are defined for flags.. not necessarily desired */ + tmpval = value; + while (tmpval) { + v = g_flags_get_first_value(klass, tmpval); + if (!v) { + gjs_throw(context, + "0x%x is not a valid value for flags %s", + value, g_type_name(G_TYPE_FROM_CLASS(klass))); + return JS_FALSE; + } + + tmpval &= ~v->value; + } + + return JS_TRUE; +} + +static JSBool +gjs_array_to_list(JSContext *context, + jsval array_value, + unsigned int length, + GITypeInfo *param_info, + GITypeTag list_type, + GList **list_p, + GSList **slist_p) +{ + guint32 i; + GList *list; + GSList *slist; + jsval elem; + GITypeTag param_tag; + + param_tag = g_type_info_get_tag(param_info); + + list = NULL; + slist = NULL; + + for (i = 0; i < length; ++i) { + GArgument elem_arg; + + elem = JSVAL_VOID; + if (!JS_GetElement(context, JSVAL_TO_OBJECT(array_value), + i, &elem)) { + gjs_throw(context, + "Missing array element %u", + i); + return JS_FALSE; + } + + /* FIXME we don't know if the list elements can be NULL. + * gobject-introspection needs to tell us this. + * Always say they can't for now. + */ + if (!gjs_value_to_g_arg_with_type_info(context, + elem, + param_info, + "list element", + FALSE, + FALSE, + &elem_arg)) { + return JS_FALSE; + } + + if (list_type == GI_TYPE_TAG_GLIST) { + /* GList */ + list = g_list_prepend(list, elem_arg.v_pointer); + } else { + /* GSList */ + slist = g_slist_prepend(slist, elem_arg.v_pointer); + } + } + + list = g_list_reverse(list); + slist = g_slist_reverse(slist); + + *list_p = list; + *slist_p = slist; + + return JS_TRUE; +} + +static JSBool +gjs_value_to_g_arg_with_type_info(JSContext *context, + jsval value, + GITypeInfo *type_info, + const char *arg_name, + gboolean is_return_value, + gboolean may_be_null, + GArgument *arg) +{ + GITypeTag type_tag; + gboolean wrong; + gboolean report_type_mismatch; + gboolean nullable_type; + + type_tag = g_type_info_get_tag( (GITypeInfo*) type_info); + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting jsval to GArgument %s", + type_tag_name(type_tag)); + + nullable_type = FALSE; + wrong = FALSE; /* return JS_FALSE */ + report_type_mismatch = FALSE; /* wrong=TRUE, and still need to gjs_throw a type problem */ + + switch (type_tag) { + case GI_TYPE_TAG_VOID: + nullable_type = TRUE; + arg->v_pointer = NULL; /* just so it isn't uninitialized */ + break; + + case GI_TYPE_TAG_INT8: { + gint32 i; + if (!JS_ValueToInt32(context, value, &i)) + wrong = TRUE; + if (i > G_MAXINT8 || i < G_MININT8) + wrong = TRUE; + arg->v_int8 = (gint8)i; + break; + } + case GI_TYPE_TAG_UINT8: { + guint16 i; + if (!JS_ValueToUint16(context, value, &i)) + wrong = TRUE; + if (i > G_MAXUINT8) + wrong = TRUE; + arg->v_uint8 = (guint8)i; + break; + } + case GI_TYPE_TAG_INT16: { + gint32 i; + if (!JS_ValueToInt32(context, value, &i)) + wrong = TRUE; + if (i > G_MAXINT16 || i < G_MININT16) + wrong = TRUE; + arg->v_int16 = (gint16)i; + break; + } + + case GI_TYPE_TAG_UINT16: + if (!JS_ValueToUint16(context, value, &arg->v_uint16)) + wrong = TRUE; + break; + +#if (GLIB_SIZEOF_LONG == 4) + case GI_TYPE_TAG_LONG: + case GI_TYPE_TAG_SSIZE: +#endif + case GI_TYPE_TAG_INT: + case GI_TYPE_TAG_INT32: + if (!JS_ValueToInt32(context, value, &arg->v_int)) + wrong = TRUE; + break; + +#if (GLIB_SIZEOF_LONG == 4) + case GI_TYPE_TAG_ULONG: + case GI_TYPE_TAG_SIZE: +#endif + case GI_TYPE_TAG_UINT: + case GI_TYPE_TAG_UINT32: + if (!JS_ValueToECMAUint32(context, value, &arg->v_uint)) + wrong = TRUE; + break; + +#if (GLIB_SIZEOF_LONG == 8) + case GI_TYPE_TAG_LONG: + case GI_TYPE_TAG_SSIZE: +#endif + case GI_TYPE_TAG_INT64: { + double v; + if (!JS_ValueToNumber(context, value, &v)) + wrong = TRUE; + arg->v_int64 = v; + } + break; + +#if (GLIB_SIZEOF_LONG == 8) + case GI_TYPE_TAG_ULONG: + case GI_TYPE_TAG_SIZE: +#endif + case GI_TYPE_TAG_UINT64: { + double v; + if (!JS_ValueToNumber(context, value, &v)) + wrong = TRUE; + arg->v_uint64 = v; + } + break; + + case GI_TYPE_TAG_BOOLEAN: + if (!JS_ValueToBoolean(context, value, &arg->v_boolean)) + wrong = TRUE; + break; + + case GI_TYPE_TAG_DOUBLE: + if (!JS_ValueToNumber(context, value, &arg->v_double)) + wrong = TRUE; + break; + + case GI_TYPE_TAG_UTF8: + nullable_type = TRUE; + if (JSVAL_IS_NULL(value)) { + arg->v_pointer = NULL; + } else if (JSVAL_IS_STRING(value)) { + if (!gjs_string_to_utf8(context, value, (char **)&arg->v_pointer)) + wrong = TRUE; + } else { + wrong = TRUE; + report_type_mismatch = TRUE; + } + break; + + case GI_TYPE_TAG_INTERFACE: + nullable_type = TRUE; + { + GIBaseInfo* symbol_info; + GType gtype; + + symbol_info = g_type_info_get_interface(type_info); + g_assert(symbol_info != NULL); + + gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)symbol_info); + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "gtype of SYMBOL is %s", g_type_name(gtype)); + + if (gtype == G_TYPE_VALUE) { + GValue *gvalue; + + gvalue = g_slice_new0(GValue); + if (!gjs_value_to_g_value(context, value, gvalue)) { + g_slice_free(GValue, gvalue); + arg->v_pointer = NULL; + wrong = TRUE; + } + + arg->v_pointer = gvalue; + + } else if (JSVAL_IS_NULL(value)) { + arg->v_pointer = NULL; + } else if (JSVAL_IS_OBJECT(value)) { + if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { + arg->v_pointer = gjs_g_object_from_object(context, + JSVAL_TO_OBJECT(value)); + if (arg->v_pointer != NULL) { + if (!g_type_is_a(G_TYPE_FROM_INSTANCE(arg->v_pointer), + gtype)) { + gjs_throw(context, + "Expected type '%s' but got '%s'", + g_type_name(gtype), + g_type_name(G_TYPE_FROM_INSTANCE(arg->v_pointer))); + arg->v_pointer = NULL; + wrong = TRUE; + } + } + } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { + if (g_type_is_a(gtype, G_TYPE_CLOSURE)) { + arg->v_pointer = gjs_closure_new_marshaled(context, + JSVAL_TO_OBJECT(value), + "boxed"); + } else { + arg->v_pointer = gjs_g_boxed_from_boxed(context, + JSVAL_TO_OBJECT(value)); + } + } else { + gjs_throw(context, "Unhandled GType %s unpacking SYMBOL GArgument from Object", + g_type_name(gtype)); + } + + if (arg->v_pointer == NULL) { + gjs_debug(GJS_DEBUG_GFUNCTION, + "conversion of JSObject %p type %s to gtype %s failed", + JSVAL_TO_OBJECT(value), + JS_GetTypeName(context, + JS_TypeOfValue(context, value)), + g_type_name(gtype)); + + /* bis_js_throw should have been called already */ + wrong = TRUE; + } + + } else if (JSVAL_IS_NUMBER(value)) { + nullable_type = FALSE; + if (g_type_is_a(gtype, G_TYPE_ENUM)) { + if (!JS_ValueToInt32(context, value, &arg->v_int)) { + wrong = TRUE; + } else { + GEnumValue *v; + void *klass; + + klass = g_type_class_ref(gtype); + + v = g_enum_get_value(G_ENUM_CLASS(klass), + arg->v_int); + + g_type_class_unref(klass); + + if (v == NULL) { + gjs_throw(context, + "%d is not a valid value for enumeration %s", + arg->v_int, g_type_name(gtype)); + wrong = TRUE; + } + } + } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) { + if (!JS_ValueToInt32(context, value, &arg->v_int)) { + wrong = TRUE; + } else { + void *klass; + + klass = g_type_class_ref(gtype); + if (!_gjs_flags_value_is_valid(context, klass, arg->v_int)) + wrong = TRUE; + g_type_class_unref(klass); + } + } else { + gjs_throw(context, "Unhandled GType %s unpacking SYMBOL GArgument from Number", + g_type_name(gtype)); + } + + } else { + gjs_debug(GJS_DEBUG_GFUNCTION, + "JSObject type '%s' is neither null nor an object", + JS_GetTypeName(context, + JS_TypeOfValue(context, value))); + wrong = TRUE; + report_type_mismatch = TRUE; + } + g_base_info_unref( (GIBaseInfo*) symbol_info); + } + break; + + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + /* nullable_type=FALSE; while a list can be NULL in C, that + * means empty array in JavaScript, it doesn't mean null in + * JavaScript. + */ + if (!JSVAL_IS_NULL(value) && + JSVAL_IS_OBJECT(value) && + gjs_object_has_property(context, + JSVAL_TO_OBJECT(value), + "length")) { + jsval length_value; + guint32 length; + + if (!gjs_object_require_property(context, + JSVAL_TO_OBJECT(value), + "length", + &length_value) || + !JS_ValueToECMAUint32(context, length_value, &length)) { + wrong = TRUE; + } else { + GList *list; + GSList *slist; + GITypeInfo *param_info; + + param_info = g_type_info_get_param_type(type_info, 0); + g_assert(param_info != NULL); + + list = NULL; + slist = NULL; + + if (!gjs_array_to_list(context, + value, + length, + param_info, + type_tag, + &list, &slist)) { + wrong = TRUE; + } + + if (type_tag == GI_TYPE_TAG_GLIST) { + arg->v_pointer = list; + } else { + arg->v_pointer = slist; + } + + g_base_info_unref((GIBaseInfo*) param_info); + } + } else { + wrong = TRUE; + report_type_mismatch = TRUE; + } + break; + + case GI_TYPE_TAG_ARRAY: + if (JSVAL_IS_NULL(value)) { + arg->v_pointer = NULL; + } else { + gjs_throw(context, "FIXME: Only supporting null ARRAYs"); + wrong = TRUE; + } + break; + + default: + gjs_debug(GJS_DEBUG_ERROR, + "Unhandled type %s for JavaScript to GArgument conversion", + type_tag_name(type_tag)); + wrong = TRUE; + report_type_mismatch = TRUE; + break; + } + + if (G_UNLIKELY(wrong)) { + if (report_type_mismatch) { + gjs_throw(context, "Expected type %s for %s '%s' but got type '%s' %p", + type_tag_name(type_tag), + is_return_value ? "return value" : "argument", + arg_name, + JS_GetTypeName(context, + JS_TypeOfValue(context, value)), + JSVAL_IS_OBJECT(value) ? JSVAL_TO_OBJECT(value) : NULL); + } + return JS_FALSE; + } else if (nullable_type && + arg->v_pointer == NULL && + !may_be_null) { + gjs_throw(context, + "%s '%s' (type %s) may not be null", + is_return_value ? "Return value" : "Argument", + arg_name, + type_tag_name(type_tag)); + return JS_FALSE; + } else { + return JS_TRUE; + } +} + +JSBool +gjs_value_to_g_arg(JSContext *context, + jsval value, + GIArgInfo *arg_info, + GArgument *arg) +{ + GITypeInfo *type_info; + gboolean result; + + type_info = g_arg_info_get_type(arg_info); + + result = + gjs_value_to_g_arg_with_type_info(context, value, + type_info, + g_base_info_get_name( (GIBaseInfo*) arg_info), + g_arg_info_is_return_value(arg_info), + g_arg_info_may_be_null(arg_info), + arg); + + g_base_info_unref((GIBaseInfo*) type_info); + + return result; +} + +static JSBool +gjs_array_from_g_list (JSContext *context, + jsval *value_p, + GITypeTag list_tag, + GITypeInfo *param_info, + GList *list, + GSList *slist) +{ + JSObject *obj; + unsigned int i; + jsval elem; + GArgument arg; + JSBool result; + GITypeTag param_tag; + + param_tag = g_type_info_get_tag(param_info); + + obj = JS_NewArrayObject(context, 0, JSVAL_NULL); + if (obj == NULL) + return JS_FALSE; + + *value_p = OBJECT_TO_JSVAL(obj); + + elem = JSVAL_VOID; + JS_AddRoot(context, &elem); + + result = JS_FALSE; + + i = 0; + if (list_tag == GI_TYPE_TAG_GLIST) { + for ( ; list != NULL; list = list->next) { + arg.v_pointer = list->data; + + if (!gjs_value_from_g_arg(context, &elem, + param_info, &arg)) + goto out; + + if (!JS_DefineElement(context, obj, + i, elem, + NULL, NULL, JSPROP_ENUMERATE)) { + goto out; + } + ++i; + } + } else { + for ( ; slist != NULL; slist = slist->next) { + arg.v_pointer = slist->data; + + if (!gjs_value_from_g_arg(context, &elem, + param_info, &arg)) + goto out; + + if (!JS_DefineElement(context, obj, + i, elem, + NULL, NULL, JSPROP_ENUMERATE)) { + goto out; + } + ++i; + } + } + + result = JS_TRUE; + + out: + JS_RemoveRoot(context, &elem); + + return result; +} + +JSBool +gjs_value_from_g_arg (JSContext *context, + jsval *value_p, + GITypeInfo *type_info, + GArgument *arg) +{ + GITypeTag type_tag; + + type_tag = g_type_info_get_tag( (GITypeInfo*) type_info); + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Converting GArgument %s to jsval", + type_tag_name(type_tag)); + + *value_p = JSVAL_NULL; + + switch (type_tag) { + case GI_TYPE_TAG_VOID: + *value_p = JSVAL_VOID; /* or JSVAL_NULL ? */ + break; + + case GI_TYPE_TAG_BOOLEAN: + *value_p = BOOLEAN_TO_JSVAL(arg->v_int); + break; + +#if (GLIB_SIZEOF_LONG == 4) + case GI_TYPE_TAG_LONG: + case GI_TYPE_TAG_SSIZE: +#endif + case GI_TYPE_TAG_INT: + case GI_TYPE_TAG_INT32: + return JS_NewNumberValue(context, arg->v_int, value_p); + +#if (GLIB_SIZEOF_LONG == 4) + case GI_TYPE_TAG_ULONG: + case GI_TYPE_TAG_SIZE: +#endif + case GI_TYPE_TAG_UINT: + case GI_TYPE_TAG_UINT32: + return JS_NewNumberValue(context, arg->v_uint, value_p); + +#if (GLIB_SIZEOF_LONG == 8) + case GI_TYPE_TAG_LONG: + case GI_TYPE_TAG_SSIZE: +#endif + case GI_TYPE_TAG_INT64: + return JS_NewNumberValue(context, arg->v_int64, value_p); + +#if (GLIB_SIZEOF_LONG == 8) + case GI_TYPE_TAG_ULONG: + case GI_TYPE_TAG_SIZE: +#endif + case GI_TYPE_TAG_UINT64: + return JS_NewNumberValue(context, arg->v_uint64, value_p); + + case GI_TYPE_TAG_UINT16: + return JS_NewNumberValue(context, arg->v_uint16, value_p); + + case GI_TYPE_TAG_INT16: + return JS_NewNumberValue(context, arg->v_int16, value_p); + + case GI_TYPE_TAG_UINT8: + return JS_NewNumberValue(context, arg->v_uint8, value_p); + + case GI_TYPE_TAG_INT8: + return JS_NewNumberValue(context, arg->v_int8, value_p); + + case GI_TYPE_TAG_DOUBLE: + return JS_NewDoubleValue(context, arg->v_double, value_p); + + case GI_TYPE_TAG_UTF8: + if (arg->v_pointer) + return gjs_string_from_utf8(context, arg->v_pointer, -1, value_p); + else { + /* For NULL we'll return JSVAL_NULL, which is already set + * in *value_p + */ + return JS_TRUE; + } + + case GI_TYPE_TAG_INTERFACE: + { + if (arg->v_pointer == NULL) { + /* OK, but no conversion to do */ + } else { + jsval value; + GIBaseInfo* symbol_info; + GType gtype; + + symbol_info = g_type_info_get_interface(type_info); + g_assert(symbol_info != NULL); + + if (g_base_info_get_type(symbol_info) == GI_INFO_TYPE_UNRESOLVED) { + gjs_throw(context, + "Unable to resolve arg type '%s'", + g_base_info_get_name(symbol_info)); + g_base_info_unref( (GIBaseInfo*) symbol_info); + return JS_FALSE; + } + + gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)symbol_info); + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "gtype of SYMBOL is %s", g_type_name(gtype)); + + value = JSVAL_VOID; + + if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { + JSObject *obj; + obj = gjs_object_from_g_object(context, G_OBJECT(arg->v_pointer)); + if (obj) + value = OBJECT_TO_JSVAL(obj); + } else if (g_type_is_a(gtype, G_TYPE_VALUE)) { + if (!gjs_value_from_g_value(context, &value, arg->v_pointer)) { + g_base_info_unref( (GIBaseInfo*) symbol_info); + return JS_FALSE; + } + } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { + JSObject *obj; + obj = gjs_boxed_from_g_boxed(context, gtype, arg->v_pointer); + if (obj) + value = OBJECT_TO_JSVAL(obj); + } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { + GEnumValue *v; + void *klass; + + klass = g_type_class_ref(gtype); + + v = g_enum_get_value(G_ENUM_CLASS(klass), + arg->v_int); + + g_type_class_unref(klass); + + if (v == NULL) { + gjs_throw(context, + "%d is not a valid value for enumeration %s", + arg->v_int, g_type_name(gtype)); + } else { + value = INT_TO_JSVAL(arg->v_int); + } + } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) { + void *klass; + + klass = g_type_class_ref(gtype); + + if (_gjs_flags_value_is_valid(context, G_FLAGS_CLASS(klass), arg->v_int)) + value = INT_TO_JSVAL(arg->v_int); + + g_type_class_unref(klass); + } else { + gjs_throw(context, "Unhandled GType %s packing SYMBOL GArgument into jsval", + g_type_name(gtype)); + } + + g_base_info_unref( (GIBaseInfo*) symbol_info); + + if (JSVAL_IS_VOID(value)) + return JS_FALSE; + + *value_p = value; + } + } + break; + + case GI_TYPE_TAG_ARRAY: + if (arg->v_pointer == NULL) { + /* OK, but no conversion to do */ + } else { + gjs_throw(context, "FIXME: Only supporting null ARRAYs"); + return JS_FALSE; + } + break; + + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + { + GITypeInfo *param_info; + gboolean result; + + param_info = g_type_info_get_param_type(type_info, 0); + g_assert(param_info != NULL); + + result = gjs_array_from_g_list(context, + value_p, + type_tag, + param_info, + type_tag == GI_TYPE_TAG_GLIST ? + arg->v_pointer : NULL, + type_tag == GI_TYPE_TAG_GSLIST ? + arg->v_pointer : NULL); + + g_base_info_unref((GIBaseInfo*) param_info); + + return result; + } + break; + + default: + gjs_debug(GJS_DEBUG_ERROR, + "Unhandled type %s converting GArgument to JavaScript", + type_tag_name(type_tag)); + return JS_FALSE; + } + + return JS_TRUE; +} + +JSBool +gjs_g_arg_release(JSContext *context, + GITransfer transfer, + GITypeInfo *type_info, + GArgument *arg) +{ + GITypeTag type_tag; + + if (transfer == GI_TRANSFER_NOTHING) + return JS_TRUE; + + type_tag = g_type_info_get_tag( (GITypeInfo*) type_info); + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "Releasing GArgument %s out param or return value", + type_tag_name(type_tag)); + + switch (type_tag) { + case GI_TYPE_TAG_VOID: + case GI_TYPE_TAG_BOOLEAN: + case GI_TYPE_TAG_INT8: + case GI_TYPE_TAG_UINT8: + case GI_TYPE_TAG_INT16: + case GI_TYPE_TAG_UINT16: + case GI_TYPE_TAG_INT: + case GI_TYPE_TAG_INT32: + case GI_TYPE_TAG_UINT: + case GI_TYPE_TAG_UINT32: + case GI_TYPE_TAG_INT64: + case GI_TYPE_TAG_UINT64: + case GI_TYPE_TAG_LONG: + case GI_TYPE_TAG_ULONG: + case GI_TYPE_TAG_DOUBLE: + break; + + case GI_TYPE_TAG_UTF8: + g_free(arg->v_pointer); + break; + + case GI_TYPE_TAG_INTERFACE: + { + if (arg->v_pointer == NULL) { + /* nothing to do */ + } else { + jsval value; + GIBaseInfo* symbol_info; + GType gtype; + + symbol_info = g_type_info_get_interface(type_info); + g_assert(symbol_info != NULL); + + gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)symbol_info); + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, + "gtype of SYMBOL is %s", g_type_name(gtype)); + + value = JSVAL_NULL; + + if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { + g_object_unref(G_OBJECT(arg->v_pointer)); + } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { + g_boxed_free(gtype, arg->v_pointer); + } else if (g_type_is_a(gtype, G_TYPE_VALUE)) { + GValue *value = arg->v_pointer; + g_value_unset(value); + g_slice_free(GValue, value); + } else if (g_type_is_a(gtype, G_TYPE_ENUM) || g_type_is_a(gtype, G_TYPE_FLAGS)) { + /* nothing to do */ + } else { + gjs_throw(context, "Unhandled GType %s releasing SYMBOL GArgument", + g_type_name(gtype)); + } + + g_base_info_unref( (GIBaseInfo*) symbol_info); + } + } + break; + + case GI_TYPE_TAG_GLIST: + if (transfer == GI_TRANSFER_EVERYTHING) { + GITypeInfo *param_info; + GList *list; + + param_info = g_type_info_get_param_type(type_info, 0); + g_assert(param_info != NULL); + + for (list = arg->v_pointer; + list != NULL; + list = list->next) { + GArgument elem; + elem.v_pointer = list->data; + + if (!gjs_g_arg_release(context, + GI_TRANSFER_EVERYTHING, + param_info, + &elem)) { + /* no way to recover here, and errors should + * not be possible. + */ + g_error("Failed to release list element"); + } + } + + g_base_info_unref((GIBaseInfo*) param_info); + } + + g_list_free(arg->v_pointer); + break; + + case GI_TYPE_TAG_ARRAY: + if (arg->v_pointer == NULL) { + /* OK */ + } else { + gjs_throw(context, "FIXME: Only supporting null ARRAYs"); + return JS_FALSE; + } + break; + + case GI_TYPE_TAG_GSLIST: + if (transfer == GI_TRANSFER_EVERYTHING) { + GITypeInfo *param_info; + GSList *slist; + + param_info = g_type_info_get_param_type(type_info, 0); + g_assert(param_info != NULL); + + for (slist = arg->v_pointer; + slist != NULL; + slist = slist->next) { + GArgument elem; + elem.v_pointer = slist->data; + + if (!gjs_g_arg_release(context, + GI_TRANSFER_EVERYTHING, + param_info, + &elem)) { + /* no way to recover here, and errors should + * not be possible. + */ + g_error("Failed to release slist element"); + } + } + + g_base_info_unref((GIBaseInfo*) param_info); + } + + g_slist_free(arg->v_pointer); + break; + + default: + gjs_debug(GJS_DEBUG_ERROR, + "Unhandled type %s releasing GArgument", + type_tag_name(type_tag)); + return JS_FALSE; + } + + return JS_TRUE; +} diff --git a/gi/arg.h b/gi/arg.h new file mode 100644 index 00000000..bf86e86d --- /dev/null +++ b/gi/arg.h @@ -0,0 +1,54 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_ARG_H__ +#define __GJS_ARG_H__ + +#include <glib.h> + +#include <jsapi.h> + +#include <girepository.h> + +G_BEGIN_DECLS + +JSBool gjs_value_to_g_arg (JSContext *context, + jsval value, + GIArgInfo *arg_info, + GArgument *arg); +JSBool gjs_value_from_g_arg (JSContext *context, + jsval *value_p, + GITypeInfo *type_info, + GArgument *arg); +JSBool gjs_g_arg_release (JSContext *context, + GITransfer transfer, + GITypeInfo *type_info, + GArgument *arg); + +JSBool _gjs_flags_value_is_valid (JSContext *context, + GFlagsClass *klass, + guint value); + +G_END_DECLS + +#endif /* __GJS_ARG_H__ */ diff --git a/gi/boxed.c b/gi/boxed.c new file mode 100644 index 00000000..48608025 --- /dev/null +++ b/gi/boxed.c @@ -0,0 +1,582 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include <string.h> + +#include "boxed.h" +#include "arg.h" +#include "object.h" +#include <gjs/mem.h> +#include <gjs/jsapi-util.h> +#include "repo.h" +#include "function.h" + +#include <util/log.h> + +#include <jsapi.h> + +#include <girepository.h> + +typedef struct { + GIBoxedInfo *info; + void *gboxed; /* NULL if we are the prototype and not an instance */ +} Boxed; + +static Boxed unthreadsafe_template_for_constructor = { NULL, NULL }; + +static struct JSClass gjs_boxed_class; + +GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(Boxed, gjs_boxed_class) + +/* + * Like JSResolveOp, but flags provide contextual information as follows: + * + * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id + * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment + * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence + * JSRESOLVE_DECLARING var, const, or boxed prolog declaration opcode + * JSRESOLVE_CLASSNAME class name used when constructing + * + * The *objp out parameter, on success, should be null to indicate that id + * was not resolved; and non-null, referring to obj or one of its prototypes, + * if id was resolved. + */ +static JSBool +boxed_new_resolve(JSContext *context, + JSObject *obj, + jsval id, + uintN flags, + JSObject **objp) +{ + Boxed *priv; + const char *name; + + *objp = NULL; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not resolved, but no error */ + + priv = priv_from_js(context, obj); + gjs_debug_jsprop(GJS_DEBUG_GBOXED, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv); + + if (priv == NULL) + return JS_FALSE; /* wrong class */ + + if (priv->gboxed == NULL) { + /* We are the prototype, so look for methods and other class properties */ + GIFunctionInfo *method_info; + + method_info = g_struct_info_find_method((GIStructInfo*) priv->info, + name); + + if (method_info != NULL) { + JSObject *boxed_proto; + const char *method_name; + +#if GJS_VERBOSE_ENABLE_GI_USAGE + _gjs_log_info_usage((GIBaseInfo*) method_info); +#endif + + method_name = g_base_info_get_name( (GIBaseInfo*) method_info); + + gjs_debug(GJS_DEBUG_GBOXED, + "Defining method %s in prototype for %s.%s", + method_name, + g_base_info_get_namespace( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) priv->info)); + + boxed_proto = obj; + + if (gjs_define_function(context, boxed_proto, method_info) == NULL) { + g_base_info_unref( (GIBaseInfo*) method_info); + return JS_FALSE; + } + + *objp = boxed_proto; /* we defined the prop in object_proto */ + + g_base_info_unref( (GIBaseInfo*) method_info); + } + } else { + /* We are an instance, not a prototype, so look for + * per-instance props that we want to define on the + * JSObject. Generally we do not want to cache these in JS, we + * want to always pull them from the C object, or JS would not + * see any changes made from C. So we use the get/set prop + * hooks, not this resolve hook. + */ + } + + return JS_TRUE; +} + +static void* +boxed_new(JSContext *context, + JSObject *obj, /* "this" for constructor */ + GIBoxedInfo *info) +{ + int n_methods; + int i; + + /* Find a zero-args constructor and call it */ + + n_methods = g_struct_info_get_n_methods(info); + + for (i = 0; i < n_methods; ++i) { + GIFunctionInfo *func_info; + GIFunctionInfoFlags flags; + + func_info = g_struct_info_get_method(info, i); + + flags = g_function_info_get_flags(func_info); + if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0 && + g_callable_info_get_n_args((GICallableInfo*) func_info) == 0) { + + jsval rval; + + rval = JSVAL_NULL; + gjs_invoke_c_function(context, func_info, obj, + 0, NULL, &rval); + + g_base_info_unref((GIBaseInfo*) func_info); + + /* We are somewhat wasteful here; invoke_c_function() above + * creates a JSObject wrapper for the boxed that we immediately + * discard. + */ + if (JSVAL_IS_NULL(rval)) + return NULL; + else + return gjs_g_boxed_from_boxed(context, JSVAL_TO_OBJECT(rval)); + } + + g_base_info_unref((GIBaseInfo*) func_info); + } + + gjs_throw(context, "Unable to construct boxed type %s since it has no zero-args <constructor>, can only wrap an existing one", + g_base_info_get_name((GIBaseInfo*) info)); + + return NULL; +} + +/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on + * the prototype in addition to on each instance. When called on the + * prototype, "obj" is the prototype, and "retval" is the prototype + * also, but can be replaced with another object to use instead as the + * prototype. If we don't set JSCLASS_CONSTRUCT_PROTOTYPE we can + * identify the prototype as an object of our class with NULL private + * data. + */ +static JSBool +boxed_constructor(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + Boxed *priv; + Boxed *proto_priv; + JSClass *obj_class; + JSClass *proto_class; + JSObject *proto; + gboolean is_proto; + + priv = g_slice_new0(Boxed); + + GJS_INC_COUNTER(boxed); + + g_assert(priv_from_js(context, obj) == NULL); + JS_SetPrivate(context, obj, priv); + + gjs_debug_lifecycle(GJS_DEBUG_GBOXED, + "boxed constructor, obj %p priv %p", + obj, priv); + + proto = JS_GetPrototype(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GBOXED, "boxed instance __proto__ is %p", proto); + + /* If we're constructing the prototype, its __proto__ is not the same + * class as us, but if we're constructing an instance, the prototype + * has the same class. + */ + obj_class = JS_GetClass(context, obj); + proto_class = JS_GetClass(context, proto); + + is_proto = (obj_class != proto_class); + + gjs_debug_lifecycle(GJS_DEBUG_GBOXED, + "boxed instance constructing proto %d, obj class %s proto class %s", + is_proto, obj_class->name, proto_class->name); + + if (!is_proto) { + GType gtype; + + /* If we're the prototype, then post-construct we'll fill in priv->info. + * If we are not the prototype, though, then we'll get ->info from the + * prototype and then create a GObject if we don't have one already. + */ + proto_priv = priv_from_js(context, proto); + if (proto_priv == NULL) { + gjs_debug(GJS_DEBUG_GBOXED, + "Bad prototype set on boxed? Must match JSClass of object. JS error should have been reported."); + return JS_FALSE; + } + + priv->info = proto_priv->info; + g_base_info_ref( (GIBaseInfo*) priv->info); + + gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) priv->info); + + /* Since gobject-introspection is always creating new info + * objects, == is not meaningful on them, only comparison of + * their names. We prefer to use the info that is already ref'd + * by the prototype for the class. + */ + g_assert(unthreadsafe_template_for_constructor.info == NULL || + strcmp(g_base_info_get_name( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) unthreadsafe_template_for_constructor.info)) + == 0); + unthreadsafe_template_for_constructor.info = NULL; + + if (unthreadsafe_template_for_constructor.gboxed == NULL) { + void *gboxed; + + /* boxed_new happens to be implemented by calling + * gjs_invoke_c_function(), which returns a jsval. + * The returned "gboxed" here is owned by that jsval, + * not by us. + */ + gboxed = boxed_new(context, obj, priv->info); + + if (gboxed == NULL) { + return JS_FALSE; + } + + /* Because "gboxed" is owned by a jsval and will + * be garbage colleced, we make a copy here to be + * owned by us. + */ + priv->gboxed = g_boxed_copy(gtype, gboxed); + } else { + priv->gboxed = g_boxed_copy(gtype, unthreadsafe_template_for_constructor.gboxed); + unthreadsafe_template_for_constructor.gboxed = NULL; + } + + gjs_debug_lifecycle(GJS_DEBUG_GBOXED, + "JSObject created with boxed instance %p type %s", + priv->gboxed, g_type_name(gtype)); + } + + return JS_TRUE; +} + +static void +boxed_finalize(JSContext *context, + JSObject *obj) +{ + Boxed *priv; + + priv = priv_from_js(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GBOXED, + "finalize, obj %p priv %p", obj, priv); + if (priv == NULL) + return; /* wrong class? */ + + if (priv->gboxed) { + g_boxed_free(g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) priv->info), + priv->gboxed); + priv->gboxed = NULL; + } + + if (priv->info) { + g_base_info_unref( (GIBaseInfo*) priv->info); + priv->info = NULL; + } + + GJS_DEC_COUNTER(boxed); + g_slice_free(Boxed, priv); +} + +/* The bizarre thing about this vtable is that it applies to both + * instances of the object, and to the prototype that instances of the + * class have. + * + * Also, there's a constructor field in here, but as far as I can + * tell, it would only be used if no constructor were provided to + * JS_InitClass. The constructor from JS_InitClass is not applied to + * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags. + */ +static struct JSClass gjs_boxed_class = { + NULL, /* dynamic class, no name here */ + JSCLASS_HAS_PRIVATE | + JSCLASS_NEW_RESOLVE | + JSCLASS_NEW_RESOLVE_GETS_START | + JSCLASS_CONSTRUCT_PROTOTYPE, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_EnumerateStub, + (JSResolveOp) boxed_new_resolve, /* needs cast since it's the new resolve signature */ + JS_ConvertStub, + boxed_finalize, + NULL, + NULL, + NULL, + NULL, NULL, NULL, NULL, NULL +}; + +static JSPropertySpec gjs_boxed_proto_props[] = { + { NULL } +}; + +static JSFunctionSpec gjs_boxed_proto_funcs[] = { + { NULL } +}; + +JSObject* +gjs_lookup_boxed_constructor(JSContext *context, + GIBoxedInfo *info) +{ + JSObject *ns; + JSObject *constructor; + + ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); + + if (ns == NULL) + return NULL; + + constructor = NULL; + if (gjs_define_boxed_class(context, ns, info, + &constructor, NULL)) + return constructor; + else + return NULL; +} + +JSObject* +gjs_lookup_boxed_prototype(JSContext *context, + GIBoxedInfo *info) +{ + JSObject *ns; + JSObject *proto; + + ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); + + if (ns == NULL) + return NULL; + + proto = NULL; + if (gjs_define_boxed_class(context, ns, info, NULL, &proto)) + return proto; + else + return NULL; +} + +JSClass* +gjs_lookup_boxed_class(JSContext *context, + GIBoxedInfo *info) +{ + JSObject *prototype; + + prototype = gjs_lookup_boxed_prototype(context, info); + + return JS_GetClass(context, prototype); +} + +JSBool +gjs_define_boxed_class(JSContext *context, + JSObject *in_object, + GIBoxedInfo *info, + JSObject **constructor_p, + JSObject **prototype_p) +{ + const char *constructor_name; + JSObject *prototype; + jsval value; + Boxed *priv; + + /* See the comment in gjs_define_object_class() for an + * explanation of how this all works; Boxed is pretty much the + * same as Object. + */ + + constructor_name = g_base_info_get_name( (GIBaseInfo*) info); + + if (gjs_object_get_property(context, in_object, constructor_name, &value)) { + JSObject *constructor; + + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "Existing property '%s' does not look like a constructor", + constructor_name); + return JS_FALSE; + } + + constructor = JSVAL_TO_OBJECT(value); + + gjs_object_get_property(context, constructor, "prototype", &value); + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "boxed %s prototype property does not appear to exist or has wrong type", constructor_name); + return JS_FALSE; + } else { + if (prototype_p) + *prototype_p = JSVAL_TO_OBJECT(value); + if (constructor_p) + *constructor_p = constructor; + + return JS_TRUE; + } + } + + prototype = gjs_init_class_dynamic(context, in_object, + /* parent prototype JSObject* for + * prototype; NULL for + * Object.prototype + */ + NULL, + g_base_info_get_namespace( (GIBaseInfo*) info), + constructor_name, + &gjs_boxed_class, + /* constructor for instances (NULL for + * none - just name the prototype like + * Math - rarely correct) + */ + boxed_constructor, + /* number of constructor args */ + 0, + /* props of prototype */ + &gjs_boxed_proto_props[0], + /* funcs of prototype */ + &gjs_boxed_proto_funcs[0], + /* props of constructor, MyConstructor.myprop */ + NULL, + /* funcs of constructor, MyConstructor.myfunc() */ + NULL); + if (prototype == NULL) + gjs_fatal("Can't init class %s", constructor_name); + + g_assert(gjs_object_has_property(context, in_object, constructor_name)); + + /* Put the info in the prototype */ + priv = priv_from_js(context, prototype); + g_assert(priv != NULL); + g_assert(priv->info == NULL); + priv->info = info; + g_base_info_ref( (GIBaseInfo*) priv->info); + + gjs_debug(GJS_DEBUG_GBOXED, "Defined class %s prototype is %p class %p in object %p", + constructor_name, prototype, JS_GetClass(context, prototype), in_object); + + if (constructor_p) { + *constructor_p = NULL; + gjs_object_get_property(context, in_object, constructor_name, &value); + if (value != JSVAL_VOID) { + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "Property '%s' does not look like a constructor", + constructor_name); + return JS_FALSE; + } + } + + *constructor_p = JSVAL_TO_OBJECT(value); + } + + if (prototype_p) + *prototype_p = prototype; + + return JS_TRUE; +} + +JSObject* +gjs_boxed_from_g_boxed(JSContext *context, + GType gtype, + void *gboxed) +{ + JSObject *obj; + JSObject *proto; + GIBaseInfo *info; + + if (gboxed == NULL) + return NULL; + + gjs_debug_marshal(GJS_DEBUG_GBOXED, + "Wrapping boxed %s %p with JSObject", + g_type_name(gtype), gboxed); + + info = g_irepository_find_by_gtype(g_irepository_get_default(), + gtype); + + if (info == NULL) { + gjs_throw(context, + "Unknown boxed type %s", + g_type_name(gtype)); + return NULL; + } + + if (g_base_info_get_type( (GIBaseInfo*) info) != GI_INFO_TYPE_BOXED) { + gjs_throw(context, + "GType %s doesn't map to boxed in g-i?", + g_base_info_get_name( (GIBaseInfo*) info)); + g_base_info_unref( (GIBaseInfo*) info); + return NULL; + } + + proto = gjs_lookup_boxed_prototype(context, (GIBoxedInfo*) info); + + /* can't come up with a better approach... */ + unthreadsafe_template_for_constructor.info = (GIBoxedInfo*) info; + unthreadsafe_template_for_constructor.gboxed = gboxed; + + obj = gjs_construct_object_dynamic(context, proto, + 0, NULL); + + g_base_info_unref( (GIBaseInfo*) info); + + return obj; +} + +void* +gjs_g_boxed_from_boxed(JSContext *context, + JSObject *obj) +{ + Boxed *priv; + + if (obj == NULL) + return NULL; + + priv = priv_from_js(context, obj); + + if (priv == NULL) + return NULL; + + if (priv->gboxed == NULL) { + gjs_throw(context, + "Object is %s.%s.prototype, not an object instance - cannot convert to a boxed instance", + g_base_info_get_namespace( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) priv->info)); + return NULL; + } + + return priv->gboxed; +} diff --git a/gi/boxed.h b/gi/boxed.h new file mode 100644 index 00000000..9ebd52e1 --- /dev/null +++ b/gi/boxed.h @@ -0,0 +1,57 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_BOXED_H__ +#define __GJS_BOXED_H__ + +#include <glib.h> + +#include <jsapi.h> + +#include <girepository.h> + +G_BEGIN_DECLS + +/* Hack for now... why doesn't gobject-introspection have this? */ +typedef GIStructInfo GIBoxedInfo; + +JSBool gjs_define_boxed_class (JSContext *context, + JSObject *in_object, + GIBoxedInfo *info, + JSObject **constructor_p, + JSObject **prototype_p); +JSObject* gjs_lookup_boxed_constructor (JSContext *context, + GIBoxedInfo *info); +JSObject* gjs_lookup_boxed_prototype (JSContext *context, + GIBoxedInfo *info); +JSClass* gjs_lookup_boxed_class (JSContext *context, + GIBoxedInfo *info); +void* gjs_g_boxed_from_boxed (JSContext *context, + JSObject *obj); +JSObject* gjs_boxed_from_g_boxed (JSContext *context, + GType gtype, + void *gboxed); + +G_END_DECLS + +#endif /* __GJS_BOXED_H__ */ diff --git a/gi/closure.c b/gi/closure.c new file mode 100644 index 00000000..774ddaba --- /dev/null +++ b/gi/closure.c @@ -0,0 +1,287 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include <limits.h> +#include <util/log.h> + +#include "closure.h" +#include "keep-alive.h" +#include <gjs/mem.h> +#include <gjs/jsapi-util.h> + +typedef struct { + GClosure base; + JSRuntime *runtime; + JSContext *context; + JSObject *obj; +} Closure; + +/* + * Memory management of closures is "interesting" because we're keeping around + * a JSContext* and then trying to use it spontaneously from the main loop. + * I don't think that's really quite kosher, and perhaps the problem is that + * (in xulrunner) we just need to save a different context. + * + * Or maybe the right fix is to create our own context just for this? + * + * But for the moment, we save the context that was used to create the closure. + * + * Here's the problem: this context can be destroyed. AFTER the + * context is destroyed, or at least potentially after, the objects in + * the context's global object may be garbage collected. Remember that + * JSObject* belong to a runtime, not a context. + * + * There is apparently no robust way to track context destruction in + * SpiderMonkey, because the context can be destroyed without running + * the garbage collector, and xulrunner takes over the JS_SetContextCallback() + * callback. So there's no callback for us. + * + * So, when we go to use our context, we iterate the contexts in the runtime + * and see if ours is still in the valid list, and decide to invalidate + * the closure if it isn't. + * + * The closure can thus be destroyed in several cases: + * - invalidation by say signal disconnection; we get invalidate callback + * - invalidation because we were invoked while the context was dead + * - invalidation through finalization (we were garbage collected) + * + * These don't have to happen in the same order; garbage collection can + * be either before, or after, context destruction. + * + */ + +static void +invalidate_js_pointers(Closure *c) +{ + if (c->obj == NULL) + return; + + c->obj = NULL; + c->context = NULL; + c->runtime = NULL; + + /* disconnects from signals, for example... + * potentially a dangerous re-entrancy but + * we'll have to risk it. + */ + g_closure_invalidate(&c->base); +} + +static void +global_context_finalized(JSObject *obj, + void *data) +{ + Closure *c; + + c = data; + + gjs_debug(GJS_DEBUG_GCLOSURE, + "Context destroy notifier on closure %p which calls object %p", + c, c->obj); + + if (c->obj != NULL) { + g_assert(c->obj == obj); + + invalidate_js_pointers(c); + } + + /* The "Keep Alive" (garbage collector) owns one reference. */ + g_closure_unref(&c->base); +} + + +static void +check_context_valid(Closure *c) +{ + JSContext *a_context; + JSContext *iter; + + if (c->runtime == NULL) + return; + + iter = NULL; + while ((a_context = JS_ContextIterator(c->runtime, + &iter)) != NULL) { + if (a_context == c->context) { + return; + } + } + + gjs_debug(GJS_DEBUG_GCLOSURE, + "Context %p no longer exists, invalidating closure %p which calls object %p", + c->context, c, c->obj); + + /* Did not find the context. */ + invalidate_js_pointers(c); +} + +/* Invalidation is like "dispose" - it happens on signal disconnect, + * is guaranteed to happen at finalize, but may happen before finalize + */ +static void +closure_invalidated(gpointer data, + GClosure *closure) +{ + Closure *c; + + c = (Closure*) closure; + + gjs_debug(GJS_DEBUG_GCLOSURE, + "Invalidating closure %p which calls object %p", + closure, c->obj); + + if (c->obj) { + gjs_keep_alive_remove_global_child(c->context, + global_context_finalized, + c->obj, + c); + + c->obj = NULL; + c->context = NULL; + c->runtime = NULL; + + /* The "Keep Alive" (garbage collector) owns one reference, + * since we removed ourselves from the keep-alive we'll + * never be collected so drop the ref here + */ + g_closure_unref(&c->base); + } +} + +static void +closure_finalized(gpointer data, + GClosure *closure) +{ + GJS_DEC_COUNTER(closure); +} + +void +gjs_closure_invoke(GClosure *closure, + int argc, + jsval *argv, + jsval *retval) +{ + Closure *c; + JSContext *context; + + c = (Closure*) closure; + + check_context_valid(c); + context = c->context; + + if (c->obj == NULL) { + /* We were destroyed; become a no-op */ + c->context = NULL; + return; + } + + if (JS_IsExceptionPending(context)) { + gjs_debug(GJS_DEBUG_GCLOSURE, + "Exception was pending before invoking callback??? Not expected"); + gjs_log_exception(c->context, NULL); + } + + if (!gjs_call_function_value(context, + NULL, /* "this" object; NULL is some kind of default presumably */ + OBJECT_TO_JSVAL(c->obj), + argc, + argv, + retval)) { + /* Exception thrown... */ + gjs_debug(GJS_DEBUG_GCLOSURE, + "Closure invocation failed (exception should have been thrown) closure %p callable %p", + closure, c->obj); + if (!gjs_log_exception(context, NULL)) + gjs_debug(GJS_DEBUG_ERROR, + "Closure invocation failed but no exception was set?"); + return; + } + + if (gjs_log_exception(context, NULL)) { + gjs_debug(GJS_DEBUG_ERROR, + "Closure invocation succeeded but an exception was set"); + } +} + +JSContext* +gjs_closure_get_context(GClosure *closure) +{ + Closure *c; + + c = (Closure*) closure; + + check_context_valid(c); + + return c->context; +} + +JSObject* +gjs_closure_get_callable(GClosure *closure) +{ + Closure *c; + + c = (Closure*) closure; + + return c->obj; +} + +GClosure* +gjs_closure_new(JSContext *context, + JSObject *callable, + const char *description) +{ + Closure *c; + + c = (Closure*) g_closure_new_simple(sizeof(Closure), NULL); + c->runtime = JS_GetRuntime(context); + /* Closure are executed in our special "load-context" (one per runtime). + * This ensures that the context is still alive when the closure + * is invoked (as long as the runtime lives) + */ + c->context = gjs_runtime_get_load_context(c->runtime); + c->obj = callable; + + GJS_INC_COUNTER(closure); + /* the finalize notifier right now is purely to track the counter + * of how many closures are alive. + */ + g_closure_add_finalize_notifier(&c->base, NULL, closure_finalized); + + gjs_keep_alive_add_global_child(c->context, + global_context_finalized, + c->obj, + c); + + /* The "Keep Alive" (garbage collector) owns one reference. */ + g_closure_ref(&c->base); + + g_closure_add_invalidate_notifier(&c->base, NULL, closure_invalidated); + + gjs_debug(GJS_DEBUG_GCLOSURE, + "Create closure %p which calls object %p '%s'", + c, c->obj, description); + + return &c->base; +} diff --git a/gi/closure.h b/gi/closure.h new file mode 100644 index 00000000..d8855c19 --- /dev/null +++ b/gi/closure.h @@ -0,0 +1,45 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_CLOSURE_H__ +#define __GJS_CLOSURE_H__ + +#include <glib-object.h> + +#include <jsapi.h> + +G_BEGIN_DECLS + +GClosure* gjs_closure_new (JSContext *context, + JSObject *callable, + const char *description); +void gjs_closure_invoke (GClosure *closure, + int argc, + jsval *argv, + jsval *retval); +JSContext* gjs_closure_get_context (GClosure *closure); +JSObject* gjs_closure_get_callable (GClosure *closure); + +G_END_DECLS + +#endif /* __GJS_CLOSURE_H__ */ diff --git a/gi/enumeration.c b/gi/enumeration.c new file mode 100644 index 00000000..a107cc0a --- /dev/null +++ b/gi/enumeration.c @@ -0,0 +1,161 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include <string.h> + +#include <gjs/jsapi-util.h> +#include "repo.h" + +#include <util/log.h> + +#include <jsapi.h> + +#include <girepository.h> + +#include "enumeration.h" + +JSObject* +gjs_lookup_enumeration(JSContext *context, + GIEnumInfo *info) +{ + JSObject *ns; + JSObject *enum_obj; + + ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); + + if (ns == NULL) + return NULL; + + if (gjs_define_enumeration(context, ns, info, + &enum_obj)) + return enum_obj; + else + return NULL; +} + +static JSBool +gjs_define_enum_value(JSContext *context, + JSObject *in_object, + GIValueInfo *info) +{ + const char *value_name; + int value_val; + + value_name = g_base_info_get_name( (GIBaseInfo*) info); + value_val = (int) g_value_info_get_value(info); + + gjs_debug(GJS_DEBUG_GENUM, + "Defining enum value %s %d", + value_name, value_val); + + if (!JS_DefineProperty(context, in_object, + value_name, INT_TO_JSVAL(value_val), + NULL, NULL, + GJS_MODULE_PROP_FLAGS)) { + gjs_throw(context, "Unable to define enumeration value %s %d (no memory most likely)", + value_name, value_val); + return JS_FALSE; + } + + return JS_TRUE; +} + +JSBool +gjs_define_enumeration(JSContext *context, + JSObject *in_object, + GIEnumInfo *info, + JSObject **enumeration_p) +{ + const char *enum_name; + JSObject *enum_obj; + jsval value; + int i; + int n_values; + + /* An enumeration is simply an object containing integer attributes for + * each enum value. It does not have a special JSClass. + * + * We could make this more typesafe and also print enum values as strings + * if we created a class for each enum and made the enum values instances + * of that class. However, it would have a lot more overhead and just + * be more complicated in general. I think this is fine. + */ + + enum_name = g_base_info_get_name( (GIBaseInfo*) info); + + if (gjs_object_get_property(context, in_object, enum_name, &value)) { + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "Existing property '%s' does not look like an enum object", + enum_name); + return JS_FALSE; + } + + enum_obj = JSVAL_TO_OBJECT(value); + + if (enumeration_p) + *enumeration_p = enum_obj; + + return JS_TRUE; + } + + enum_obj = JS_ConstructObject(context, NULL, NULL, NULL); + if (enum_obj == NULL) + return JS_FALSE; + + /* Fill in enum values first, so we don't define the enum itself until we're + * sure we can finish successfully. + */ + n_values = g_enum_info_get_n_values(info); + for (i = 0; i < n_values; ++i) { + GIValueInfo *value_info = g_enum_info_get_value(info, i); + gboolean failed; + + failed = !gjs_define_enum_value(context, enum_obj, value_info); + + g_base_info_unref( (GIBaseInfo*) value_info); + + if (failed) { + return JS_FALSE; + } + } + + gjs_debug(GJS_DEBUG_GENUM, + "Defining %s.%s as %p", + g_base_info_get_namespace( (GIBaseInfo*) info), + enum_name, enum_obj); + + if (!JS_DefineProperty(context, in_object, + enum_name, OBJECT_TO_JSVAL(enum_obj), + NULL, NULL, + GJS_MODULE_PROP_FLAGS)) { + gjs_throw(context, "Unable to define enumeration property (no memory most likely)"); + return JS_FALSE; + } + + if (enumeration_p) + *enumeration_p = enum_obj; + + return JS_TRUE; +} diff --git a/gi/enumeration.h b/gi/enumeration.h new file mode 100644 index 00000000..1e2c579d --- /dev/null +++ b/gi/enumeration.h @@ -0,0 +1,44 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_ENUMERATION_H__ +#define __GJS_ENUMERATION_H__ + +#include <glib.h> + +#include <jsapi.h> + +#include <girepository.h> + +G_BEGIN_DECLS + +JSBool gjs_define_enumeration (JSContext *context, + JSObject *in_object, + GIEnumInfo *info, + JSObject **enumeration_p); +JSObject* gjs_lookup_enumeration (JSContext *context, + GIEnumInfo *info); + +G_END_DECLS + +#endif /* __GJS_ENUMERATION_H__ */ diff --git a/gi/function.c b/gi/function.c new file mode 100644 index 00000000..80d6d175 --- /dev/null +++ b/gi/function.c @@ -0,0 +1,554 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include "function.h" +#include "arg.h" +#include "object.h" +#include <gjs/jsapi-util.h> +#include <gjs/mem.h> + +#include <util/log.h> + +#include <jsapi.h> + +#include <girepository.h> + +typedef struct { + GIFunctionInfo *info; + +} Function; + +static struct JSClass gjs_function_class; + +GJS_DEFINE_PRIV_FROM_JS(Function, gjs_function_class) + +/* + * Like JSResolveOp, but flags provide contextual information as follows: + * + * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id + * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment + * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence + * JSRESOLVE_DECLARING var, const, or function prolog declaration opcode + * JSRESOLVE_CLASSNAME class name used when constructing + * + * The *objp out parameter, on success, should be null to indicate that id + * was not resolved; and non-null, referring to obj or one of its prototypes, + * if id was resolved. + */ +static JSBool +function_new_resolve(JSContext *context, + JSObject *obj, + jsval id, + uintN flags, + JSObject **objp) +{ + Function *priv; + const char *name; + + *objp = NULL; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not resolved, but no error */ + + priv = priv_from_js(context, obj); + + gjs_debug_jsprop(GJS_DEBUG_GFUNCTION, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv); + + if (priv == NULL) + return JS_TRUE; /* we are the prototype, or have the wrong class */ + + return JS_TRUE; +} + +JSBool +gjs_invoke_c_function(JSContext *context, + GIFunctionInfo *info, + JSObject *obj, /* "this" object */ + uintN argc, + jsval *argv, + jsval *rval) +{ + GArgument *in_args; + GArgument *out_args; + GArgument *out_values; + GArgument return_arg; + int n_args; + int expected_in_argc; + int expected_out_argc; + int i; + int argv_pos; + int in_args_pos; + int out_args_pos; + GError *error; + gboolean failed; + GIFunctionInfoFlags flags; + gboolean is_method; + + flags = g_function_info_get_flags(info); + is_method = (flags & GI_FUNCTION_IS_METHOD) != 0; + + expected_in_argc = 0; + expected_out_argc = 0; + + n_args = g_callable_info_get_n_args( (GICallableInfo*) info); + for (i = 0; i < n_args; i++) { + GIDirection direction; + GIArgInfo *arg_info; + + arg_info = g_callable_info_get_arg( (GICallableInfo*) info, i); + direction = g_arg_info_get_direction(arg_info); + if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) + expected_in_argc += 1; + if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) + expected_out_argc += 1; + g_base_info_unref( (GIBaseInfo*) arg_info); + } + + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call is to %s %s.%s with argc %d, expected: %d in args, %d out args, %d total args", + is_method ? "method" : "function", + g_base_info_get_namespace( (GIBaseInfo*) info), + g_base_info_get_name( (GIBaseInfo*) info), + argc, + expected_in_argc, + expected_out_argc, + n_args); + + /* We allow too many args; convenient for re-using a function as a callback. + * But we don't allow too few args, since that would break. + */ + if (argc < (unsigned) expected_in_argc) { + gjs_throw(context, "Too few arguments to %s %s.%s expected %d got %d", + is_method ? "method" : "function", + g_base_info_get_namespace( (GIBaseInfo*) info), + g_base_info_get_name( (GIBaseInfo*) info), + expected_in_argc, + argc); + return JS_FALSE; + } + + if (is_method) + expected_in_argc += 1; + + in_args = g_newa(GArgument, expected_in_argc); + out_args = g_newa(GArgument, expected_out_argc); + /* each out arg is a pointer, they point to these values */ + out_values = g_newa(GArgument, expected_out_argc); + + failed = FALSE; + in_args_pos = 0; /* index into in_args */ + out_args_pos = 0; /* into out_args */ + argv_pos = 0; /* index into argv */ + + if (is_method) { + in_args[0].v_pointer = gjs_g_object_from_object(context, obj); + ++in_args_pos; + } + + for (i = 0; i < n_args; i++) { + GIDirection direction; + GIArgInfo *arg_info; + GArgument *out_value; + + /* gjs_debug(GJS_DEBUG_GFUNCTION, "i: %d in_args_pos: %d argv_pos: %d", i, in_args_pos, argv_pos); */ + + arg_info = g_callable_info_get_arg( (GICallableInfo*) info, i); + direction = g_arg_info_get_direction(arg_info); + + out_value = NULL; + if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) { + g_assert(out_args_pos < expected_out_argc); + + out_value = &out_values[out_args_pos]; + out_args[out_args_pos].v_pointer = out_value; + ++out_args_pos; + } + + if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) { + GArgument in_value; + + if (!gjs_value_to_g_arg(context, argv[argv_pos], arg_info, + &in_value)) { + failed = TRUE; + } + + ++argv_pos; + + if (direction == GI_DIRECTION_IN) { + in_args[in_args_pos] = in_value; + } else { + /* INOUT means we pass a pointer */ + g_assert(out_value != NULL); + *out_value = in_value; + in_args[in_args_pos].v_pointer = out_value; + } + + ++in_args_pos; + } + + g_base_info_unref( (GIBaseInfo*) arg_info); + + if (failed) + break; + } + + /* gjs_value_to_g_arg should have reported a JS error if we failed, return FALSE */ + if (failed) + return JS_FALSE; + + g_assert(in_args_pos == expected_in_argc); + g_assert(out_args_pos == expected_out_argc); + + error = NULL; + if (g_function_info_invoke( (GIFunctionInfo*) info, + in_args, expected_in_argc, + out_args, expected_out_argc, + &return_arg, + &error)) { + GITypeInfo *return_info; + GITypeTag return_tag; + int n_return_values; + + failed = FALSE; + + return_info = g_callable_info_get_return_type( (GICallableInfo*) info); + g_assert(return_info != NULL); + + return_tag = g_type_info_get_tag(return_info); + + *rval = JSVAL_VOID; + + n_return_values = expected_out_argc; + if (return_tag != GI_TYPE_TAG_VOID) + n_return_values += 1; + + if (n_return_values > 0) { + jsval *return_values; + int next_rval; + + return_values = g_newa(jsval, n_return_values); + gjs_set_values(context, return_values, n_return_values, JSVAL_VOID); + gjs_root_value_locations(context, return_values, n_return_values); + + next_rval = 0; /* index into return_values */ + + if (return_tag != GI_TYPE_TAG_VOID) { + if (!gjs_value_from_g_arg(context, &return_values[next_rval], + return_info, &return_arg)) { + failed = TRUE; + } + + /* Free GArgument, the jsval should have ref'd or copied it */ + if (!gjs_g_arg_release(context, + g_callable_info_get_caller_owns((GICallableInfo*) info), + return_info, + &return_arg)) + failed = TRUE; + + ++next_rval; + } + + if (expected_out_argc > 0) { + /* We walk over all args (not just out args) and skip + * the non-out args + */ + out_args_pos = 0; + + for (i = 0; i < n_args; i++) { + GIDirection direction; + GIArgInfo *arg_info; + GITypeInfo *arg_type_info; + + arg_info = g_callable_info_get_arg( (GICallableInfo*) info, i); + direction = g_arg_info_get_direction(arg_info); + if (direction == GI_DIRECTION_IN) { + g_base_info_unref( (GIBaseInfo*) arg_info); + continue; + } + + /* INOUT or OUT */ + arg_type_info = g_arg_info_get_type(arg_info); + + g_assert(next_rval < n_return_values); + g_assert(out_args_pos < expected_out_argc); + + if (!gjs_value_from_g_arg(context, + &return_values[next_rval], + arg_type_info, + out_args[out_args_pos].v_pointer)) { + failed = TRUE; + } + + /* Free GArgument, the jsval should have ref'd or copied it */ + if (!gjs_g_arg_release(context, + g_arg_info_get_ownership_transfer(arg_info), + arg_type_info, + out_args[out_args_pos].v_pointer)) + failed = TRUE; + + ++out_args_pos; + + g_base_info_unref( (GIBaseInfo*) arg_type_info); + g_base_info_unref( (GIBaseInfo*) arg_info); + + ++next_rval; + } + } + + /* if we have 1 return value or out arg, return that item + * on its own, otherwise return a JavaScript array with + * [return value, out arg 1, out arg 2, ...] + */ + if (n_return_values == 1) { + *rval = return_values[0]; + } else { + JSObject *array; + array = JS_NewArrayObject(context, + n_return_values, + return_values); + if (array == NULL) { + failed = TRUE; + } else { + *rval = OBJECT_TO_JSVAL(array); + } + } + + gjs_unroot_value_locations(context, return_values, n_return_values); + } + + g_base_info_unref( (GIBaseInfo*) return_info); + + return failed ? JS_FALSE : JS_TRUE; + } else { + g_assert(error != NULL); + gjs_throw(context, "Error invoking %s.%s: %s", + g_base_info_get_namespace( (GIBaseInfo*) info), + g_base_info_get_name( (GIBaseInfo*) info), + error->message); + g_error_free(error); + return JS_FALSE; + } +} + +/* this macro was introduced with JSFastNative in 2007 */ +#ifndef JS_ARGV_CALLEE +#define JS_ARGV_CALLEE(argv) ((argv)[-2]) +#endif + +static JSBool +function_call(JSContext *context, + JSObject *obj, /* "this" object, not the function object */ + uintN argc, + jsval *argv, + jsval *rval) +{ + Function *priv; + JSObject *callee; + + callee = JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv)); /* Callee is the Function object being called */ + + priv = priv_from_js(context, callee); + gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call callee %p priv %p this obj %p %s", callee, priv, + obj, JS_GetTypeName(context, + JS_TypeOfValue(context, OBJECT_TO_JSVAL(obj)))); + + if (priv == NULL) + return JS_TRUE; /* we are the prototype, or have the wrong class */ + + return gjs_invoke_c_function(context, priv->info, obj, argc, argv, rval); +} + +/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on + * the prototype in addition to on each instance. When called on the + * prototype, "obj" is the prototype, and "retval" is the prototype + * also, but can be replaced with another object to use instead as the + * prototype. If we don't set JSCLASS_CONSTRUCT_PROTOTYPE we can + * identify the prototype as an object of our class with NULL private + * data. + */ +static JSBool +function_constructor(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + Function *priv; + + priv = g_slice_new0(Function); + + GJS_INC_COUNTER(function); + + g_assert(priv_from_js(context, obj) == NULL); + JS_SetPrivate(context, obj, priv); + + gjs_debug_lifecycle(GJS_DEBUG_GFUNCTION, + "function constructor, obj %p priv %p", obj, priv); + + return JS_TRUE; +} + +static void +function_finalize(JSContext *context, + JSObject *obj) +{ + Function *priv; + + priv = priv_from_js(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GFUNCTION, + "finalize, obj %p priv %p", obj, priv); + if (priv == NULL) + return; /* we are the prototype, not a real instance, so constructor never called */ + + if (priv->info) + g_base_info_unref( (GIBaseInfo*) priv->info); + + GJS_DEC_COUNTER(function); + g_slice_free(Function, priv); +} + +/* The bizarre thing about this vtable is that it applies to both + * instances of the object, and to the prototype that instances of the + * class have. + * + * Also, there's a constructor field in here, but as far as I can + * tell, it would only be used if no constructor were provided to + * JS_InitClass. The constructor from JS_InitClass is not applied to + * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags. + */ +static struct JSClass gjs_function_class = { + "GIRepositoryFunction", /* means "new GIRepositoryFunction()" works */ + JSCLASS_HAS_PRIVATE | + JSCLASS_NEW_RESOLVE | + JSCLASS_NEW_RESOLVE_GETS_START, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_EnumerateStub, + (JSResolveOp) function_new_resolve, /* needs cast since it's the new resolve signature */ + JS_ConvertStub, + function_finalize, + NULL, + NULL, + function_call, + NULL, NULL, NULL, NULL, NULL +}; + +static JSPropertySpec gjs_function_proto_props[] = { + { NULL } +}; + +static JSFunctionSpec gjs_function_proto_funcs[] = { + { NULL } +}; + +static JSObject* +function_new(JSContext *context, + GIFunctionInfo *info) +{ + JSObject *function; + JSObject *global; + Function *priv; + + /* put constructor for GIRepositoryFunction() in the global namespace */ + global = JS_GetGlobalObject(context); + + if (!gjs_object_has_property(context, global, gjs_function_class.name)) { + JSObject *prototype; + JSObject *parent_proto; + + parent_proto = NULL; + + prototype = JS_InitClass(context, global, + /* parent prototype JSObject* for + * prototype; NULL for + * Object.prototype + */ + parent_proto, + &gjs_function_class, + /* constructor for instances (NULL for + * none - just name the prototype like + * Math - rarely correct) + */ + function_constructor, + /* number of constructor args */ + 0, + /* props of prototype */ + &gjs_function_proto_props[0], + /* funcs of prototype */ + &gjs_function_proto_funcs[0], + /* props of constructor, MyConstructor.myprop */ + NULL, + /* funcs of constructor, MyConstructor.myfunc() */ + NULL); + if (prototype == NULL) + gjs_fatal("Can't init class %s", gjs_function_class.name); + + g_assert(gjs_object_has_property(context, global, gjs_function_class.name)); + + gjs_debug(GJS_DEBUG_GFUNCTION, "Initialized class %s prototype %p", + gjs_function_class.name, prototype); + } + + function = JS_ConstructObject(context, &gjs_function_class, NULL, NULL); + if (function == NULL) { + gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to construct function"); + return NULL; + } + + priv = priv_from_js(context, function); + priv->info = info; + g_base_info_ref( (GIBaseInfo*) info ); + + return function; +} + +JSObject* +gjs_define_function(JSContext *context, + JSObject *in_object, + GIFunctionInfo *info) +{ + JSObject *function; + JSContext *load_context; + + load_context = gjs_runtime_get_load_context(JS_GetRuntime(context)); + + function = function_new(load_context, info); + if (function == NULL) { + gjs_move_exception(load_context, context); + return NULL; + } + + if (!JS_DefineProperty(context, in_object, + g_base_info_get_name( (GIBaseInfo*) info), + OBJECT_TO_JSVAL(function), + NULL, NULL, + GJS_MODULE_PROP_FLAGS)) { + gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to define function"); + return NULL; + } + + return function; +} diff --git a/gi/function.h b/gi/function.h new file mode 100644 index 00000000..cb8ff886 --- /dev/null +++ b/gi/function.h @@ -0,0 +1,48 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_FUNCTION_H__ +#define __GJS_FUNCTION_H__ + +#include <glib.h> + +#include <jsapi.h> + +#include <girepository.h> + +G_BEGIN_DECLS + +JSObject* gjs_define_function (JSContext *context, + JSObject *in_object, + GIFunctionInfo *info); +JSBool gjs_invoke_c_function (JSContext *context, + GIFunctionInfo *info, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *rval); + + +G_END_DECLS + +#endif /* __GJS_FUNCTION_H__ */ diff --git a/gi/keep-alive.c b/gi/keep-alive.c new file mode 100644 index 00000000..f2c62719 --- /dev/null +++ b/gi/keep-alive.c @@ -0,0 +1,440 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include "keep-alive.h" + +#include <gjs/jsapi-util.h> + +#include <util/log.h> +#include <util/glib.h> + +#include <jsapi.h> + +typedef struct { + GjsUnrootedFunc notify; + JSObject *child; + void *data; +} Child; + +typedef struct { + GHashTable *children; + unsigned int inside_finalize : 1; + unsigned int inside_trace : 1; +} KeepAlive; + +static struct JSClass gjs_keep_alive_class; + +GJS_DEFINE_PRIV_FROM_JS(KeepAlive, gjs_keep_alive_class) + +static guint +child_hash(gconstpointer v) +{ + const Child *child = v; + + return + GPOINTER_TO_UINT(child->notify) ^ + GPOINTER_TO_UINT(child->child) ^ + GPOINTER_TO_UINT(child->data); +} + +static gboolean +child_equal (gconstpointer v1, + gconstpointer v2) +{ + const Child *child1 = v1; + const Child *child2 = v2; + + /* notify is most likely to be equal, so check it last */ + return child1->data == child2->data && + child1->child == child2->child && + child1->notify == child2->notify; +} + +static void +child_free(void *data) +{ + Child *child = data; + g_slice_free(Child, child); +} + +/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on + * the prototype in addition to on each instance. When called on the + * prototype, "obj" is the prototype, and "retval" is the prototype + * also, but can be replaced with another object to use instead as the + * prototype. If we don't set JSCLASS_CONSTRUCT_PROTOTYPE we can + * identify the prototype as an object of our class with NULL private + * data. + */ +static JSBool +keep_alive_constructor(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + KeepAlive *priv; + + priv = g_slice_new0(KeepAlive); + priv->children = g_hash_table_new_full(child_hash, child_equal, NULL, child_free); + + g_assert(priv_from_js(context, obj) == NULL); + JS_SetPrivate(context, obj, priv); + + gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE, + "keep_alive constructor, obj %p priv %p", obj, priv); + + return JS_TRUE; +} + +static void +keep_alive_finalize(JSContext *context, + JSObject *obj) +{ + KeepAlive *priv; + void *key; + void *value; + + priv = priv_from_js(context, obj); + + gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE, + "keep_alive finalizing, obj %p priv %p", obj, priv); + + if (priv == NULL) + return; /* we are the prototype, not a real instance, so constructor never called */ + + priv->inside_finalize = TRUE; + + while (gjs_g_hash_table_steal_one(priv->children, + &key, &value)) { + Child *child = value; + if (child->notify) + (* child->notify) (child->child, child->data); + + child_free(child); + } + + g_hash_table_destroy(priv->children); + g_slice_free(KeepAlive, priv); +} + +static void +trace_foreach(void *key, + void *value, + void *data) +{ + Child *child = value; + JSTracer *tracer = data; + + if (child->child != NULL) { + JS_CallTracer(tracer, child->child, JSTRACE_OBJECT); + } +} + +static void +keep_alive_trace(JSTracer *tracer, + JSObject *obj) +{ + KeepAlive *priv; + + priv = priv_from_js(tracer->context, obj); + + if (priv == NULL) /* prototype */ + return; + + g_assert(!priv->inside_trace); + priv->inside_trace = TRUE; + g_hash_table_foreach(priv->children, trace_foreach, tracer); + priv->inside_trace = FALSE; +} + +/* The bizarre thing about this vtable is that it applies to both + * instances of the object, and to the prototype that instances of the + * class have. + * + * Also, there's a constructor field in here, but as far as I can + * tell, it would only be used if no constructor were provided to + * JS_InitClass. The constructor from JS_InitClass is not applied to + * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags. + */ +static struct JSClass gjs_keep_alive_class = { + "__private_GjsKeepAlive", /* means "new __private_GjsKeepAlive()" works */ + JSCLASS_HAS_PRIVATE | + JSCLASS_MARK_IS_TRACE, /* TraceOp not MarkOp */ + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + keep_alive_finalize, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + JS_CLASS_TRACE(keep_alive_trace), + NULL +}; + +static JSPropertySpec gjs_keep_alive_proto_props[] = { + { NULL } +}; + +static JSFunctionSpec gjs_keep_alive_proto_funcs[] = { + { NULL } +}; + +JSObject* +gjs_keep_alive_new(JSContext *context) +{ + JSObject *keep_alive; + JSObject *global; + + g_assert(context != NULL); + + /* put constructor in the global namespace */ + global = JS_GetGlobalObject(context); + + g_assert(global != NULL); + + if (!gjs_object_has_property(context, global, gjs_keep_alive_class.name)) { + JSObject *prototype; + + gjs_debug(GJS_DEBUG_KEEP_ALIVE, + "Initializing keep-alive class in context %p global %p", + context, global); + + prototype = JS_InitClass(context, global, + /* parent prototype JSObject* for + * prototype; NULL for + * Object.prototype + */ + NULL, + &gjs_keep_alive_class, + /* constructor for instances (NULL for + * none - just name the prototype like + * Math - rarely correct) + */ + keep_alive_constructor, + /* number of constructor args */ + 0, + /* props of prototype */ + &gjs_keep_alive_proto_props[0], + /* funcs of prototype */ + &gjs_keep_alive_proto_funcs[0], + /* props of constructor, MyConstructor.myprop */ + NULL, + /* funcs of constructor, MyConstructor.myfunc() */ + NULL); + if (prototype == NULL) + gjs_fatal("Can't init class %s", gjs_keep_alive_class.name); + + g_assert(gjs_object_has_property(context, global, gjs_keep_alive_class.name)); + + gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Initialized class %s prototype %p", + gjs_keep_alive_class.name, prototype); + } + + gjs_debug(GJS_DEBUG_KEEP_ALIVE, + "Creating new keep-alive object for context %p global %p", + context, global); + + /* Without the "global" parent object, this craters inside of + * xulrunner because in jsobj.c:js_ConstructObject it looks up + * VOID as the constructor. Exploring in gdb, it is walking up + * the scope chain in a way that involves scary xpconnect-looking + * stuff. Having "global" as parent seems to fix it. But, it would + * not hurt to understand this better. + */ + keep_alive = JS_ConstructObject(context, &gjs_keep_alive_class, NULL, global); + if (keep_alive == NULL) { + gjs_log_exception(context, NULL); + gjs_fatal("Failed to create keep_alive object"); + } + + return keep_alive; +} + +void +gjs_keep_alive_add_child(JSContext *context, + JSObject *keep_alive, + GjsUnrootedFunc notify, + JSObject *obj, + void *data) +{ + KeepAlive *priv; + Child *child; + + g_assert(keep_alive != NULL); + + priv = priv_from_js(context, keep_alive); + + g_assert(priv != NULL); + + g_return_if_fail(!priv->inside_trace); + g_return_if_fail(!priv->inside_finalize); + + child = g_slice_new0(Child); + child->notify = notify; + child->child = obj; + child->data = data; + + /* this is sort of an expensive check, probably */ + g_return_if_fail(g_hash_table_lookup(priv->children, child) == NULL); + + /* this overwrites any identical-by-value previous child, + * but there should not be one. + */ + g_hash_table_replace(priv->children, child, child); +} + +void +gjs_keep_alive_remove_child(JSContext *context, + JSObject *keep_alive, + GjsUnrootedFunc notify, + JSObject *obj, + void *data) +{ + KeepAlive *priv; + Child child; + + priv = priv_from_js(context, keep_alive); + + g_assert(priv != NULL); + + g_return_if_fail(!priv->inside_trace); + g_return_if_fail(!priv->inside_finalize); + + child.notify = notify; + child.child = obj; + child.data = data; + + g_hash_table_remove(priv->children, + &child); +} + +#define GLOBAL_KEEP_ALIVE_NAME "__gc_this_on_context_destroy" + +JSObject* +gjs_keep_alive_get_global(JSContext *context) +{ + jsval value; + JSObject *global; + + global = JS_GetGlobalObject(context); + + gjs_object_get_property(context, global, GLOBAL_KEEP_ALIVE_NAME, &value); + + if (JSVAL_IS_OBJECT(value)) + return JSVAL_TO_OBJECT(value); + + return NULL; +} + +static JSObject* +gjs_keep_alive_create_in_global(JSContext *context) +{ + JSObject *keep_alive; + JSObject *global; + + global = JS_GetGlobalObject(context); + + keep_alive = gjs_keep_alive_new(context); + + if (!JS_DefineProperty(context, global, + GLOBAL_KEEP_ALIVE_NAME, + OBJECT_TO_JSVAL(keep_alive), + NULL, NULL, + /* No ENUMERATE since this is a hidden + * implementation detail kind of property + */ + JSPROP_READONLY | JSPROP_PERMANENT)) + gjs_fatal("no memory to define keep_alive property"); + + return keep_alive; +} + +void +gjs_keep_alive_add_global_child(JSContext *context, + GjsUnrootedFunc notify, + JSObject *child, + void *data) +{ + JSObject *keep_alive; + + keep_alive = gjs_keep_alive_get_global(context); + + if (!keep_alive) + keep_alive = gjs_keep_alive_create_in_global(context); + + if (!keep_alive) + gjs_fatal("could not create keep_alive on global object, no memory?"); + + gjs_keep_alive_add_child(context, + keep_alive, + notify, child, data); +} + +void +gjs_keep_alive_remove_global_child(JSContext *context, + GjsUnrootedFunc notify, + JSObject *child, + void *data) +{ + JSObject *keep_alive; + + keep_alive = gjs_keep_alive_get_global(context); + + if (!keep_alive) + gjs_fatal("no keep_alive property on the global object, have you " + "previously added this child?"); + + gjs_keep_alive_remove_child(context, + gjs_keep_alive_get_global(context), + notify, child, data); +} + +JSObject* +gjs_keep_alive_get_for_load_context(JSRuntime *runtime) +{ + JSContext *context; + JSObject *keep_alive; + + context = gjs_runtime_get_load_context(runtime); + + g_assert(context != NULL); + + keep_alive = gjs_keep_alive_get_global(context); + + if (!keep_alive) + keep_alive = gjs_keep_alive_create_in_global(context); + + if (!keep_alive) + gjs_fatal("could not create keep_alive on global object, no memory?"); + + return keep_alive; +} diff --git a/gi/keep-alive.h b/gi/keep-alive.h new file mode 100644 index 00000000..ab2a5a99 --- /dev/null +++ b/gi/keep-alive.h @@ -0,0 +1,83 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_KEEP_ALIVE_H__ +#define __GJS_KEEP_ALIVE_H__ + +#include <glib.h> +#include <jsapi.h> + +G_BEGIN_DECLS + +/* This is an alternative to JS_AddRoot(). + * + * This "keep alive" object holds a collection of child objects and + * traces them when GC occurs. If the keep alive object is collected, + * it calls a notification function on all the child objects. + * + * The "global keep alive" is stuck on the global object as a property, + * so its children only get notified when the entire JSContext is + * blown away (or its global object replaced, I suppose, but that + * won't happen afaik). + * + * The problem with JS_AddRoot() is that it has no notification when the + * JSContext is destroyed. Also, it can be annoying to wrap a C type purely + * to put a finalizer on it, this lets you avoid that. + * + * All three fields (notify, child, and data) are optional, so you can have + * no JSObject - just notification+data - and you can have no notifier, + * only the keep-alive capability. + */ + +typedef void (* GjsUnrootedFunc) (JSObject *obj, + void *data); + + +JSObject* gjs_keep_alive_new (JSContext *context); +void gjs_keep_alive_add_child (JSContext *context, + JSObject *keep_alive, + GjsUnrootedFunc notify, + JSObject *child, + void *data); +void gjs_keep_alive_remove_child (JSContext *context, + JSObject *keep_alive, + GjsUnrootedFunc notify, + JSObject *child, + void *data); +JSObject* gjs_keep_alive_get_global (JSContext *context); +void gjs_keep_alive_add_global_child (JSContext *context, + GjsUnrootedFunc notify, + JSObject *child, + void *data); +void gjs_keep_alive_remove_global_child (JSContext *context, + GjsUnrootedFunc notify, + JSObject *child, + void *data); +JSObject* gjs_keep_alive_get_for_load_context (JSRuntime *runtime); + + + + +G_END_DECLS + +#endif /* __GJS_KEEP_ALIVE_H__ */ diff --git a/gi/native.c b/gi/native.c new file mode 100644 index 00000000..36efb1fd --- /dev/null +++ b/gi/native.c @@ -0,0 +1,183 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include <gmodule.h> + +#include <util/log.h> + +#include "native.h" +#include <gjs/jsapi-util.h> + +typedef struct { + GjsDefineModuleFunc func; + GjsNativeFlags flags; +} GjsNativeModule; + +static GHashTable *modules = NULL; + +static void +native_module_free(void *data) +{ + g_slice_free(GjsNativeModule, data); +} + +void +gjs_register_native_module (const char *module_id, + GjsDefineModuleFunc func, + GjsNativeFlags flags) +{ + GjsNativeModule *module; + + if (modules == NULL) { + modules = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, native_module_free); + } + + if (g_hash_table_lookup(modules, module_id) != NULL) { + g_warning("A second native module tried to register the same id '%s'", + module_id); + return; + } + + module = g_slice_new(GjsNativeModule); + module->func = func; + module->flags = flags; + + g_hash_table_replace(modules, + g_strdup(module_id), + module); + + gjs_debug(GJS_DEBUG_NATIVE, + "Registered native JS module '%s'", + module_id); +} + +static JSObject* +module_get_parent(JSContext *context, + JSObject *module_obj) +{ + jsval value; + + if (gjs_object_get_property(context, module_obj, "__parentModule__", &value) && + value != JSVAL_NULL && + JSVAL_IS_OBJECT(value)) { + return JSVAL_TO_OBJECT(value); + } else { + return NULL; + } +} + +JSBool +gjs_import_native_module(JSContext *context, + JSObject *module_obj, + const char *filename, + GjsNativeFlags *flags_p) +{ + GModule *gmodule; + GString *module_id; + JSObject *parent; + GjsNativeModule *native_module; + + if (flags_p) + *flags_p = 0; + + /* Vital to load in global scope so any dependent libs + * are loaded into the main app. We don't want a second + * private copy of GTK or something. + */ + gmodule = g_module_open(filename, 0); + if (gmodule == NULL) { + gjs_throw(context, + "Failed to load '%s': %s", + filename, g_module_error()); + return JS_FALSE; + } + + /* dlopen() as a side effect should have registered us as + * a native module. We just have to reverse-engineer + * the module id from module_obj. + */ + module_id = g_string_new(NULL); + parent = module_obj; + while (parent != NULL) { + jsval value; + + if (gjs_object_get_property(context, parent, "__moduleName__", &value) && + JSVAL_IS_STRING(value)) { + const char *name; + name = gjs_string_get_ascii(value); + + if (module_id->len > 0) + g_string_prepend(module_id, "."); + + g_string_prepend(module_id, name); + } + + /* Move up to parent */ + parent = module_get_parent(context, parent); + } + + gjs_debug(GJS_DEBUG_NATIVE, + "Defining native module '%s'", + module_id->str); + + if (modules != NULL) + native_module = g_hash_table_lookup(modules, module_id->str); + else + native_module = NULL; + + if (native_module == NULL) { + gjs_throw(context, + "No native module '%s' has registered itself", + module_id->str); + g_string_free(module_id, TRUE); + g_module_close(gmodule); + return JS_FALSE; + } + + g_string_free(module_id, TRUE); + + if (flags_p) + *flags_p = native_module->flags; + + /* make the module resident, which makes the close() a no-op + * (basically we leak the module permanently) + */ + g_module_make_resident(gmodule); + g_module_close(gmodule); + + if (native_module->flags & GJS_NATIVE_SUPPLIES_MODULE_OBJ) { + + /* In this case we just throw away "module_obj" eventually, + * since the native module defines itself in the parent of + * module_obj directly. + */ + parent = module_get_parent(context, module_obj); + return (* native_module->func) (context, parent); + } else { + return (* native_module->func) (context, module_obj); + } +} + diff --git a/gi/native.h b/gi/native.h new file mode 100644 index 00000000..0542ef0c --- /dev/null +++ b/gi/native.h @@ -0,0 +1,91 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_NATIVE_H__ +#define __GJS_NATIVE_H__ + +#include <glib.h> + +#include <jsapi.h> + +G_BEGIN_DECLS + +typedef enum { + /* This means that the GjsDefineModuleFunc defines the module + * name in the parent module, as opposed to the normal process + * where the GjsDefineModuleFunc defines module contents. When + * importing imports.foo.bar, this flag means the native module is + * given foo and defines bar in it, while normally the native + * module is given bar and defines stuff in that. + * + * The purpose of this is to allow a module with lazy properties + * by allowing module objects to be custom classes. It's used for + * the gobject-introspection module for example. + */ + GJS_NATIVE_SUPPLIES_MODULE_OBJ = 1 << 0 + +} GjsNativeFlags; + +/* + * In a native module, you define a GjsDefineModuleFunc that + * adds your stuff to module_obj. + * + * You then declare GJS_REGISTER_NATIVE_MODULE("my.module.path", my_module_func) + * + * This declaration will call gjs_register_native_module() when your + * module is dlopen'd. We can't just use a well-known symbol name + * in your module, because we need to dlopen modules with + * global symbols. + */ + +typedef JSBool (* GjsDefineModuleFunc) (JSContext *context, + JSObject *module_obj); + +#define GJS_REGISTER_NATIVE_MODULE_WITH_FLAGS(module_id_string, module_func, flags) \ + __attribute__((constructor)) static void \ + register_native_module (void) \ + { \ + gjs_register_native_module(module_id_string, module_func, flags); \ + } + + +#define GJS_REGISTER_NATIVE_MODULE(module_id_string, module_func) \ + GJS_REGISTER_NATIVE_MODULE_WITH_FLAGS(module_id_string, module_func, 0) + +/* called in constructor function on dlopen() load */ +void gjs_register_native_module (const char *module_id, + GjsDefineModuleFunc func, + GjsNativeFlags flags); + +/* called by importer.c to load a native module once it finds + * it in the search path + */ +JSBool gjs_import_native_module (JSContext *context, + JSObject *module_obj, + const char *filename, + GjsNativeFlags *flags_p); + + +G_END_DECLS + +#endif /* __GJS_NATIVE_H__ */ diff --git a/gi/ns.c b/gi/ns.c new file mode 100644 index 00000000..d8f6574d --- /dev/null +++ b/gi/ns.c @@ -0,0 +1,321 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include "ns.h" +#include "repo.h" +#include "param.h" +#include <gjs/mem.h> + +#include <util/log.h> + +#include <jsapi.h> + +#include <girepository.h> + +#include <string.h> + +typedef struct { + GIRepository *repo; + char *namespace; + +} Ns; + +static struct JSClass gjs_ns_class; + +GJS_DEFINE_PRIV_FROM_JS(Ns, gjs_ns_class) + +/* + * Like JSResolveOp, but flags provide contextual information as follows: + * + * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id + * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment + * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence + * JSRESOLVE_DECLARING var, const, or function prolog declaration opcode + * JSRESOLVE_CLASSNAME class name used when constructing + * + * The *objp out parameter, on success, should be null to indicate that id + * was not resolved; and non-null, referring to obj or one of its prototypes, + * if id was resolved. + */ +static JSBool +ns_new_resolve(JSContext *context, + JSObject *obj, + jsval id, + uintN flags, + JSObject **objp) +{ + Ns *priv; + const char *name; + GIRepository *repo; + GIBaseInfo *info; + JSContext *load_context; + + *objp = NULL; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not resolved, but no error */ + + /* let Object.prototype resolve these */ + if (strcmp(name, "valueOf") == 0 || + strcmp(name, "toString") == 0) + return JS_TRUE; + + priv = priv_from_js(context, obj); + gjs_debug_jsprop(GJS_DEBUG_GNAMESPACE, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv); + + if (priv == NULL) + return JS_TRUE; /* we are the prototype, or have the wrong class */ + + load_context = gjs_runtime_get_load_context(JS_GetRuntime(context)); + + repo = g_irepository_get_default(); + + info = g_irepository_find_by_name(repo, priv->namespace, name); + if (info == NULL) { + /* Special-case fallback hack for GParamSpec */ + if (strcmp(name, "ParamSpec") == 0 && + strcmp(priv->namespace, "GLib") == 0) { + gjs_define_param_class(load_context, + obj, + NULL); + if (gjs_move_exception(load_context, context)) { + return JS_FALSE; + } else { + *objp = obj; /* we defined the property in this object */ + return JS_TRUE; + } + } else { + gjs_throw(context, + "No symbol '%s' in namespace '%s'", + name, priv->namespace); + return JS_FALSE; + } + } + + gjs_debug(GJS_DEBUG_GNAMESPACE, + "Found info type %s for '%s' in namespace '%s'", + gjs_info_type_name(g_base_info_get_type(info)), + g_base_info_get_name(info), + g_base_info_get_namespace(info)); + + if (gjs_define_info(load_context, obj, info)) { + g_base_info_unref(info); + *objp = obj; /* we defined the property in this object */ + return JS_TRUE; + } else { + gjs_debug(GJS_DEBUG_GNAMESPACE, + "Failed to define info '%s'", + g_base_info_get_name(info)); + + g_base_info_unref(info); + + if (!gjs_move_exception(load_context, context)) { + /* set an exception if none was set */ + gjs_throw(context, + "Defining info failed but no exception set"); + } + + return JS_FALSE; + } +} + +/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on + * the prototype in addition to on each instance. When called on the + * prototype, "obj" is the prototype, and "retval" is the prototype + * also, but can be replaced with another object to use instead as the + * prototype. If we don't set JSCLASS_CONSTRUCT_PROTOTYPE we can + * identify the prototype as an object of our class with NULL private + * data. + */ +static JSBool +ns_constructor(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + Ns *priv; + + priv = g_slice_new0(Ns); + + GJS_INC_COUNTER(ns); + + g_assert(priv_from_js(context, obj) == NULL); + JS_SetPrivate(context, obj, priv); + + gjs_debug_lifecycle(GJS_DEBUG_GNAMESPACE, "ns constructor, obj %p priv %p", obj, priv); + + return JS_TRUE; +} + +static void +ns_finalize(JSContext *context, + JSObject *obj) +{ + Ns *priv; + + priv = priv_from_js(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GNAMESPACE, + "finalize, obj %p priv %p", obj, priv); + if (priv == NULL) + return; /* we are the prototype, not a real instance, so constructor never called */ + + if (priv->namespace) + g_free(priv->namespace); + if (priv->repo) + g_object_unref(priv->repo); + + GJS_DEC_COUNTER(ns); + g_slice_free(Ns, priv); +} + +/* The bizarre thing about this vtable is that it applies to both + * instances of the object, and to the prototype that instances of the + * class have. + * + * Also, there's a constructor field in here, but as far as I can + * tell, it would only be used if no constructor were provided to + * JS_InitClass. The constructor from JS_InitClass is not applied to + * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags. + */ +static struct JSClass gjs_ns_class = { + "GIRepositoryNamespace", + JSCLASS_HAS_PRIVATE | + JSCLASS_NEW_RESOLVE | + JSCLASS_NEW_RESOLVE_GETS_START, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_EnumerateStub, + (JSResolveOp) ns_new_resolve, /* needs cast since it's the new resolve signature */ + JS_ConvertStub, + ns_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSPropertySpec gjs_ns_proto_props[] = { + { NULL } +}; + +static JSFunctionSpec gjs_ns_proto_funcs[] = { + { NULL } +}; + +static JSObject* +ns_new(JSContext *context, + const char *ns_name, + GIRepository *repo) +{ + JSObject *ns; + JSObject *global; + Ns *priv; + + /* put constructor in the global namespace */ + global = JS_GetGlobalObject(context); + + if (!gjs_object_has_property(context, global, gjs_ns_class.name)) { + JSObject *prototype; + prototype = JS_InitClass(context, global, + /* parent prototype JSObject* for + * prototype; NULL for + * Object.prototype + */ + NULL, + &gjs_ns_class, + /* constructor for instances (NULL for + * none - just name the prototype like + * Math - rarely correct) + */ + ns_constructor, + /* number of constructor args */ + 0, + /* props of prototype */ + &gjs_ns_proto_props[0], + /* funcs of prototype */ + &gjs_ns_proto_funcs[0], + /* props of constructor, MyConstructor.myprop */ + NULL, + /* funcs of constructor, MyConstructor.myfunc() */ + NULL); + if (prototype == NULL) + gjs_fatal("Can't init class %s", gjs_ns_class.name); + + g_assert(gjs_object_has_property(context, global, gjs_ns_class.name)); + + gjs_debug(GJS_DEBUG_GNAMESPACE, "Initialized class %s prototype %p", + gjs_ns_class.name, prototype); + } + + ns = JS_ConstructObject(context, &gjs_ns_class, NULL, NULL); + if (ns == NULL) + gjs_fatal("No memory to create ns object"); + + priv = priv_from_js(context, ns); + priv->repo = g_object_ref(repo); + priv->namespace = g_strdup(ns_name); + + return ns; +} + +JSObject* +gjs_define_ns(JSContext *context, + JSObject *in_object, + const char *ns_name, + GIRepository *repo) +{ + JSObject *ns; + char *fixed_ns_name, *unfixed_ns_name; + + /* The idea here is to always define properties for both + * fixed (MyModule) and unfixed (myModule) namespace name + * format + */ + + fixed_ns_name = gjs_fix_ns_name(ns_name); + unfixed_ns_name = gjs_unfix_ns_name(ns_name); + + ns = ns_new(context, fixed_ns_name, repo); + + if (!JS_DefineProperty(context, in_object, + fixed_ns_name, OBJECT_TO_JSVAL(ns), + NULL, NULL, + GJS_MODULE_PROP_FLAGS)) + gjs_fatal("no memory to define ns property"); + + if (!JS_DefineProperty(context, in_object, + unfixed_ns_name, OBJECT_TO_JSVAL(ns), + NULL, NULL, + GJS_MODULE_PROP_FLAGS)) + gjs_fatal("no memory to define ns property"); + + gjs_debug(GJS_DEBUG_GNAMESPACE, + "Defined namespace '%s' %p in GIRepository %p", fixed_ns_name, ns, in_object); + + g_free(fixed_ns_name); + g_free(unfixed_ns_name); + + return ns; +} diff --git a/gi/ns.h b/gi/ns.h new file mode 100644 index 00000000..d7eb7509 --- /dev/null +++ b/gi/ns.h @@ -0,0 +1,42 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_NS_H__ +#define __GJS_NS_H__ + +#include <glib.h> + +#include <jsapi.h> + +#include <girepository.h> + +G_BEGIN_DECLS + +JSObject* gjs_define_ns(JSContext *context, + JSObject *in_object, + const char *ns_name, + GIRepository *repo); + +G_END_DECLS + +#endif /* __GJS_NS_H__ */ diff --git a/gi/object.c b/gi/object.c new file mode 100644 index 00000000..089810fb --- /dev/null +++ b/gi/object.c @@ -0,0 +1,1469 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include <string.h> + +#include "object.h" +#include "arg.h" +#include "repo.h" +#include "function.h" +#include "value.h" +#include "keep-alive.h" + +#include <gjs/mem.h> + +#include <util/log.h> + +#include <jsapi.h> + +#include <girepository.h> + +typedef struct { + GIObjectInfo *info; + GObject *gobj; /* NULL if we are the prototype and not an instance */ + JSObject *keep_alive; /* NULL if we are not added to it */ + GType gtype; +} ObjectInstance; + +static ObjectInstance unthreadsafe_template_for_constructor = { NULL, NULL }; + +static struct JSClass gjs_object_instance_class; + +GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(ObjectInstance, gjs_object_instance_class) + +static JSObject* peek_js_obj (JSContext *context, + GObject *gobj); +static void set_js_obj (JSContext *context, + GObject *gobj, + JSObject *obj); + +typedef enum { + SOME_ERROR_OCCURRED = JS_FALSE, + NO_SUCH_G_PROPERTY, + VALUE_WAS_SET +} ValueFromPropertyResult; + +static ValueFromPropertyResult +init_g_param_from_property(JSContext *context, + const char *js_prop_name, + jsval js_value, + GType gtype, + GParameter *parameter) +{ + char *gname; + GParamSpec *param_spec; + void *klass; + + gname = gjs_hyphen_from_camel(js_prop_name); + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, + "Hyphen name %s on %s", gname, g_type_name(gtype)); + + klass = g_type_class_ref(gtype); + param_spec = g_object_class_find_property(G_OBJECT_CLASS(klass), + gname); + g_type_class_unref(klass); + g_free(gname); + + if (param_spec == NULL) { + /* not a GObject prop, so nothing else to do */ + return NO_SUCH_G_PROPERTY; + } + + if ((param_spec->flags & G_PARAM_WRITABLE) == 0) { + /* prevent setting the prop even in JS */ + gjs_throw(context, "Property %s (GObject %s) is not writable", + js_prop_name, param_spec->name); + return SOME_ERROR_OCCURRED; + } + + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, + "Syncing %s to GObject prop %s", + js_prop_name, param_spec->name); + + g_value_init(¶meter->value, G_PARAM_SPEC_VALUE_TYPE(param_spec)); + if (!gjs_value_to_g_value(context, js_value, ¶meter->value)) { + g_value_unset(¶meter->value); + return SOME_ERROR_OCCURRED; + } + + parameter->name = param_spec->name; + + return VALUE_WAS_SET; +} + +/* a hook on getting a property; set value_p to override property's value. + * Return value is JS_FALSE on OOM/exception. + */ +static JSBool +object_instance_get_prop(JSContext *context, + JSObject *obj, + jsval id, + jsval *value_p) +{ + ObjectInstance *priv; + const char *name; + char *gname; + GParamSpec *param; + GValue gvalue = { 0, }; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not resolved, but no error */ + + priv = priv_from_js(context, obj); + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, + "Get prop '%s' hook obj %p priv %p", name, obj, priv); + + if (priv == NULL) + return JS_FALSE; /* wrong class passed in */ + if (priv->gobj == NULL) + return JS_TRUE; /* prototype, not an instance. */ + + gname = gjs_hyphen_from_camel(name); + param = g_object_class_find_property(G_OBJECT_GET_CLASS(priv->gobj), + gname); + g_free(gname); + + if (param == NULL) { + /* leave value_p as it was */ + return JS_TRUE; + } + + if ((param->flags & G_PARAM_READABLE) == 0) { + return JS_TRUE; + } + + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, + "Overriding %s with GObject prop %s", + name, param->name); + + g_value_init(&gvalue, G_PARAM_SPEC_VALUE_TYPE(param)); + g_object_get_property(priv->gobj, param->name, + &gvalue); + if (!gjs_value_from_g_value(context, value_p, &gvalue)) { + g_value_unset(&gvalue); + return JS_FALSE; + } + g_value_unset(&gvalue); + + return JS_TRUE; +} + +/* a hook on setting a property; set value_p to override property value to + * be set. Return value is JS_FALSE on OOM/exception. + */ +static JSBool +object_instance_set_prop(JSContext *context, + JSObject *obj, + jsval id, + jsval *value_p) +{ + ObjectInstance *priv; + const char *name; + GParameter param = { NULL, { 0, }}; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not resolved, but no error */ + + priv = priv_from_js(context, obj); + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, + "Set prop '%s' hook obj %p priv %p", name, obj, priv); + + if (priv == NULL) + return JS_FALSE; /* wrong class passed in */ + if (priv->gobj == NULL) + return JS_TRUE; /* prototype, not an instance. */ + + switch (init_g_param_from_property(context, name, + *value_p, + G_TYPE_FROM_INSTANCE(priv->gobj), + ¶m)) { + case SOME_ERROR_OCCURRED: + return JS_FALSE; + case NO_SUCH_G_PROPERTY: + return JS_TRUE; + case VALUE_WAS_SET: + break; + } + + g_object_set_property(priv->gobj, param.name, + ¶m.value); + + g_value_unset(¶m.value); + + /* note that the prop will also have been set in JS, which I think + * is OK, since we hook get and set so will always override that + * value. We could also use JS_DefineProperty though and specify a + * getter/setter maybe, don't know if that is better. + */ + + return JS_TRUE; +} + +/* + * Like JSResolveOp, but flags provide contextual information as follows: + * + * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id + * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment + * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence + * JSRESOLVE_DECLARING var, const, or object prolog declaration opcode + * JSRESOLVE_CLASSNAME class name used when constructing + * + * The *objp out parameter, on success, should be null to indicate that id + * was not resolved; and non-null, referring to obj or one of its prototypes, + * if id was resolved. + */ +static JSBool +object_instance_new_resolve(JSContext *context, + JSObject *obj, + jsval id, + uintN flags, + JSObject **objp) +{ + ObjectInstance *priv; + const char *name; + + *objp = NULL; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not resolved, but no error */ + + priv = priv_from_js(context, obj); + + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, + "Resolve prop '%s' hook obj %p priv %p gobj %p %s", + name, obj, priv, priv ? priv->gobj : NULL, + (priv && priv->gobj) ? + g_type_name_from_instance((GTypeInstance*) priv->gobj) : "(type unknown)"); + + if (priv == NULL) + return JS_FALSE; /* we are the wrong class */ + + if (priv->gobj == NULL) { + /* We are the prototype, so look for methods and other class properties */ + GIFunctionInfo *method_info; + + /* find_method does not look at methods on parent classes, + * we rely on javascript to walk up the __proto__ chain + * and find those and define them in the right prototype. + */ + method_info = g_object_info_find_method(priv->info, + name); + + /* If it isn't a method on the object, see if it's one on an + * iface the object implements. Note that since JS lacks + * multiple inheritance, we stick the iface methods in the + * object prototype, which means there are many copies of the + * iface methods (one per object class node that introduces + * the iface) + */ + if (method_info == NULL) { + int n_interfaces; + int i; + + n_interfaces = g_object_info_get_n_interfaces(priv->info); + + for (i = 0; i < n_interfaces; ++i) { + GIInterfaceInfo *iface_info; + + iface_info = g_object_info_get_interface(priv->info, i); + + method_info = g_interface_info_find_method(iface_info, name); + + g_base_info_unref( (GIBaseInfo*) iface_info); + + if (method_info != NULL) { + gjs_debug(GJS_DEBUG_GOBJECT, + "Found method %s in interface %d implemented by object", + name, i); + break; + } + } + } + + if (method_info == NULL) { + GType *interfaces; + guint n_interfaces; + guint i; + + interfaces = g_type_interfaces (priv->gtype, &n_interfaces); + for (i = 0; i < n_interfaces; i++) { + GIBaseInfo *base_info; + GIInterfaceInfo *iface_info; + + base_info = g_irepository_find_by_gtype(g_irepository_get_default(), + interfaces[i]); + if (!base_info) + continue; + + if (g_base_info_get_type(base_info) != GI_INFO_TYPE_INTERFACE) { + g_base_info_unref(base_info); + continue; + } + + iface_info = (GIInterfaceInfo*) base_info; + + method_info = g_interface_info_find_method(iface_info, name); + + g_base_info_unref(base_info); + + if (method_info != NULL) { + gjs_debug(GJS_DEBUG_GOBJECT, + "Found method %s in native interface %s", + name, g_type_name(interfaces[i])); + break; + } + } + g_free(interfaces); + } + + if (method_info != NULL) { + const char *method_name; + +#if GJS_VERBOSE_ENABLE_GI_USAGE + _gjs_log_info_usage((GIBaseInfo*) method_info); +#endif + + method_name = g_base_info_get_name( (GIBaseInfo*) method_info); + + gjs_debug(GJS_DEBUG_GOBJECT, + "Defining method %s in prototype for %s (%s.%s)", + method_name, + g_type_name(priv->gtype), + g_base_info_get_namespace( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) priv->info)); + + if (gjs_define_function(context, obj, method_info) == NULL) { + g_base_info_unref( (GIBaseInfo*) method_info); + return JS_FALSE; + } + + *objp = obj; /* we defined the prop in obj */ + + g_base_info_unref( (GIBaseInfo*) method_info); + } + } else { + /* We are an instance, not a prototype, so look for per-instance props that + * we want to define on the JSObject. Generally we do not want to cache + * these in JS, we want to always pull them from the GObject, or + * JS would not see any changes made from C. So we use the get/set prop hooks, + * not this resolve hook. + */ + + JSObject *proto; + ObjectInstance *proto_priv; + + proto = JS_GetPrototype(context, obj); + proto_priv = priv_from_js(context, proto); + if (proto_priv->gtype == G_TYPE_INVALID) { + gjs_debug(GJS_DEBUG_GOBJECT, + "storing gtype %s (%d) to prototype %p", + G_OBJECT_TYPE_NAME(priv->gobj), + (int) G_OBJECT_TYPE(priv->gobj), + proto); + proto_priv->gtype = G_OBJECT_TYPE(priv->gobj); + } else if (proto_priv->gtype != G_OBJECT_TYPE(priv->gobj)) { + gjs_fatal("conflicting gtypes for prototype %s (%d) (was %s (%d))", + G_OBJECT_TYPE_NAME(priv->gobj), + (int) G_OBJECT_TYPE(priv->gobj), + g_type_name(proto_priv->gtype), + (int) proto_priv->gtype); + } + } + + return JS_TRUE; +} + +static void +free_g_params(GParameter *params, + int n_params) +{ + int i; + + for (i = 0; i < n_params; ++i) { + g_value_unset(¶ms[i].value); + } + g_free(params); +} + +/* Set properties from args to constructor (argv[0] is supposed to be + * a hash) + */ +static JSBool +object_instance_props_to_g_parameters(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + GType gtype, + GParameter **gparams_p, + int *n_gparams_p) +{ + JSObject *props; + JSObject *iter; + jsid prop_id; + GArray *gparams; + + if (gparams_p) + *gparams_p = NULL; + if (n_gparams_p) + *n_gparams_p = 0; + + if (argc == 0) + return JS_TRUE; + + if (!JSVAL_IS_OBJECT(argv[0])) { + gjs_throw(context, "argument should be a hash with props to set"); + return JS_FALSE; + } + + props = JSVAL_TO_OBJECT(argv[0]); + + iter = JS_NewPropertyIterator(context, props); + if (iter == NULL) { + gjs_throw(context, "Failed to create property iterator for object props hash"); + return JS_FALSE; + } + + prop_id = JSVAL_VOID; + if (!JS_NextProperty(context, iter, &prop_id)) + return JS_FALSE; + + if (prop_id != JSVAL_VOID) { + gparams = g_array_new(/* nul term */ FALSE, /* clear */ TRUE, + sizeof(GParameter)); + } else { + return JS_TRUE; + } + + while (prop_id != JSVAL_VOID) { + jsval nameval; + const char *name; + jsval value; + GParameter gparam = { NULL, { 0, }}; + + if (!JS_IdToValue(context, prop_id, &nameval)) + goto free_array_and_fail; + + if (!gjs_get_string_id(nameval, &name)) + goto free_array_and_fail; + + if (!gjs_object_require_property(context, props, name, &value)) + goto free_array_and_fail; + + switch (init_g_param_from_property(context, name, + value, + gtype, + &gparam)) { + case SOME_ERROR_OCCURRED: + goto free_array_and_fail; + case NO_SUCH_G_PROPERTY: + gjs_throw(context, "No property %s on this GObject %s", + name, g_type_name(gtype)); + goto free_array_and_fail; + case VALUE_WAS_SET: + break; + } + + g_array_append_val(gparams, gparam); + + prop_id = JSVAL_VOID; + if (!JS_NextProperty(context, iter, &prop_id)) + goto free_array_and_fail; + } + + if (n_gparams_p) + *n_gparams_p = gparams->len; + if (gparams_p) + *gparams_p = (void*) g_array_free(gparams, FALSE); + + return JS_TRUE; + + free_array_and_fail: + { + GParameter *to_free; + int count; + count = gparams->len; + to_free = (void*) g_array_free(gparams, FALSE); + free_g_params(to_free, count); + } + return JS_FALSE; +} + +#define DEBUG_DISPOSE 0 +#if DEBUG_DISPOSE +static void +wrapped_gobj_dispose_notify(gpointer data, + GObject *where_the_object_was) +{ + gjs_debug(GJS_DEBUG_GOBJECT, "JSObject %p GObject %p disposed", data, where_the_object_was); +} +#endif + +static void +gobj_no_longer_kept_alive_func(JSObject *obj, + void *data) +{ + ObjectInstance *priv; + + priv = data; + + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, + "GObject wrapper %p will no longer be kept alive, eligible for collection", + obj); + + priv->keep_alive = NULL; +} + +static void +wrapped_gobj_toggle_notify(gpointer data, + GObject *gobj, + gboolean is_last_ref) +{ + JSRuntime *runtime; + JSContext *context; + JSObject *obj; + ObjectInstance *priv; + + runtime = data; + + context = gjs_runtime_get_load_context(runtime); + + obj = peek_js_obj(context, gobj); + + g_assert(obj != NULL); + + priv = priv_from_js(context, obj); + + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, + "Toggle notify gobj %p obj %p is_last_ref %d keep-alive %p", + gobj, obj, is_last_ref, priv->keep_alive); + + if (is_last_ref) { + /* Change to weak ref so the wrapper-wrappee pair can be + * collected by the GC + */ + if (priv->keep_alive != NULL) { + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Removing object from keep alive"); + gjs_keep_alive_remove_child(context, priv->keep_alive, + gobj_no_longer_kept_alive_func, + obj, + priv); + priv->keep_alive = NULL; + } + } else { + /* Change to strong ref so the wrappee keeps the wrapper alive + * in case the wrapper has data in it that the app cares about + */ + if (priv->keep_alive == NULL) { + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Adding object to keep alive"); + priv->keep_alive = gjs_keep_alive_get_for_load_context(runtime); + gjs_keep_alive_add_child(context, priv->keep_alive, + gobj_no_longer_kept_alive_func, + obj, + priv); + } + } +} + +/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on + * the prototype in addition to on each instance. When called on the + * prototype, "obj" is the prototype, and "retval" is the prototype + * also, but can be replaced with another object to use instead as the + * prototype. + */ +static JSBool +object_instance_constructor(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + ObjectInstance *priv; + ObjectInstance *proto_priv; + JSObject *proto; + gboolean is_proto; + JSClass *obj_class; + JSClass *proto_class; + + priv = g_slice_new0(ObjectInstance); + + GJS_INC_COUNTER(object); + + g_assert(priv_from_js(context, obj) == NULL); + JS_SetPrivate(context, obj, priv); + + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, + "obj instance constructor, obj %p priv %p retval %p", obj, priv, + JSVAL_IS_OBJECT(*retval) ? + JSVAL_TO_OBJECT(*retval) : NULL); + + proto = JS_GetPrototype(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "obj instance __proto__ is %p", proto); + + /* If we're constructing the prototype, its __proto__ is not the same + * class as us, but if we're constructing an instance, the prototype + * has the same class. + */ + obj_class = JS_GetClass(context, obj); + proto_class = JS_GetClass(context, proto); + + is_proto = (obj_class != proto_class); + + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, + "obj instance constructing proto %d, obj class %s proto class %s", + is_proto, obj_class->name, proto_class->name); + + if (!is_proto) { + GType gtype; + + /* If we're the prototype, then post-construct we'll fill in priv->info. + * If we are not the prototype, though, then we'll get ->info from the + * prototype and then create a GObject if we don't have one already. + */ + proto_priv = priv_from_js(context, proto); + if (proto_priv == NULL) { + gjs_debug(GJS_DEBUG_GOBJECT, + "Bad prototype set on object? Must match JSClass of object. JS error should have been reported."); + return JS_FALSE; + } + + priv->info = proto_priv->info; + g_base_info_ref( (GIBaseInfo*) priv->info); + + /* Since gobject-introspection is always creating new info + * objects, == is not meaningful on them, only comparison of + * their names. We prefer to use the info that is already ref'd + * by the prototype for the class. + */ + g_assert(unthreadsafe_template_for_constructor.info == NULL || + strcmp(g_base_info_get_name( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) unthreadsafe_template_for_constructor.info)) + == 0); + unthreadsafe_template_for_constructor.info = NULL; + + if (unthreadsafe_template_for_constructor.gobj == NULL) { + GParameter *params; + int n_params; + + gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) priv->info); + if (gtype == G_TYPE_NONE) { + gjs_throw(context, + "No GType for object '%s'???", + g_base_info_get_name( (GIBaseInfo*) priv->info)); + return JS_FALSE; + } + + if (!object_instance_props_to_g_parameters(context, obj, argc, argv, + gtype, + ¶ms, &n_params)) { + return JS_FALSE; + } + + priv->gobj = g_object_newv(gtype, n_params, params); + + if (G_IS_INITIALLY_UNOWNED(priv->gobj) && + !g_object_is_floating(priv->gobj)) { + /* GtkWindow does not return a ref to caller of g_object_new. + * Need a flag in gobject-introspection to tell us this. + */ + gjs_debug(GJS_DEBUG_GOBJECT, + "Newly-created object is initially unowned but we did not get the " + "floating ref, probably GtkWindow, using hacky workaround"); + g_object_ref(priv->gobj); + } else if (g_object_is_floating(priv->gobj)) { + g_object_ref_sink(priv->gobj); + } else { + /* we should already have a ref */ + } + } else { + priv->gobj = unthreadsafe_template_for_constructor.gobj; + unthreadsafe_template_for_constructor.gobj = NULL; + + g_object_ref_sink(priv->gobj); + } + + g_assert(peek_js_obj(context, priv->gobj) == NULL); + set_js_obj(context, priv->gobj, obj); + +#if DEBUG_DISPOSE + g_object_weak_ref(priv->gobj, wrapped_gobj_dispose_notify, obj); +#endif + + /* OK, here is where things get complicated. We want the + * wrapped gobj to keep the JSObject* wrapper alive, because + * people might set properties on the JSObject* that they care + * about. Therefore, whenever the refcount on the wrapped gobj + * is >1, i.e. whenever something other than the wrapper is + * referencing the wrapped gobj, the wrapped gobj has a strong + * ref (gc-roots the wrapper). When the refcount on the + * wrapped gobj is 1, then we change to a weak ref to allow + * the wrapper to be garbage collected (and thus unref the + * wrappee). + */ + priv->keep_alive = gjs_keep_alive_get_for_load_context(JS_GetRuntime(context)); + gjs_keep_alive_add_child(context, + priv->keep_alive, + gobj_no_longer_kept_alive_func, + obj, + priv); + + g_object_add_toggle_ref(priv->gobj, + wrapped_gobj_toggle_notify, + JS_GetRuntime(context)); + + /* We now have both a ref and a toggle ref, we only want the + * toggle ref. This may immediately remove the GC root + * we just added, since refcount may drop to 1. + */ + g_object_unref(priv->gobj); + + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, + "JSObject created with GObject %p %s", + priv->gobj, g_type_name_from_instance((GTypeInstance*) priv->gobj)); + } + + return JS_TRUE; +} + +static void +object_instance_finalize(JSContext *context, + JSObject *obj) +{ + ObjectInstance *priv; + + priv = priv_from_js(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, + "finalize obj %p priv %p gtype %s gobj %p", obj, priv, + (priv && priv->gobj) ? + g_type_name_from_instance( (GTypeInstance*) priv->gobj) : + "<no gobject>", + priv ? priv->gobj : NULL); + if (priv == NULL) + return; /* we are the prototype, not a real instance, so constructor never called */ + + if (priv->gobj) { + g_assert(priv->gobj->ref_count > 0); + set_js_obj(context, priv->gobj, NULL); + g_object_remove_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify, + JS_GetRuntime(context)); + priv->gobj = NULL; + } + + if (priv->keep_alive != NULL) { + /* This happens when the refcount on the object is still >1, + * for example with global objects GDK never frees like GdkDisplay, + * when we close down the JS runtime. + */ + gjs_debug(GJS_DEBUG_GOBJECT, + "Wrapper was finalized despite being kept alive, has refcount >1"); + + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, + "Removing from keep alive"); + + /* We're in a finalizer while the runtime is about to be + * destroyed. This is not the safest time to be calling back + * into jsapi, but we have to do this or the keep alive could + * be finalized later and call gobj_no_longer_kept_alive_func. + */ + gjs_keep_alive_remove_child(context, priv->keep_alive, + gobj_no_longer_kept_alive_func, + obj, + priv); + } + + if (priv->info) { + g_base_info_unref( (GIBaseInfo*) priv->info); + priv->info = NULL; + } + + GJS_DEC_COUNTER(object); + g_slice_free(ObjectInstance, priv); +} + +JSObject* +gjs_lookup_object_constructor(JSContext *context, + GType gtype, + GIObjectInfo *info) +{ + JSObject *ns; + JSObject *constructor; + + ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); + + if (ns == NULL) + return NULL; + + if (gjs_define_object_class(context, ns, gtype, info, + &constructor, NULL)) + return constructor; + else + return NULL; +} + +JSObject* +gjs_lookup_object_prototype(JSContext *context, + GType gtype, + GIObjectInfo *info) +{ + JSObject *ns; + JSObject *proto; + + ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); + + if (ns == NULL) + return NULL; + + if (gjs_define_object_class(context, ns, gtype, info, NULL, &proto)) + return proto; + else + return NULL; +} + +JSClass* +gjs_lookup_object_class(JSContext *context, + GType gtype, + GIObjectInfo *info) +{ + JSObject *prototype; + + prototype = gjs_lookup_object_prototype(context, gtype, info); + + return JS_GetClass(context, prototype); +} + +static JSBool +real_connect_func(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval, + gboolean after) +{ + ObjectInstance *priv; + GClosure *closure; + gulong id; + const char *signal_name; + + *retval = INT_TO_JSVAL(0); + + priv = priv_from_js(context, obj); + gjs_debug(GJS_DEBUG_GOBJECT, + "connect obj %p priv %p argc %d", obj, priv, argc); + if (priv == NULL) + return JS_FALSE; /* wrong class passed in */ + if (priv->gobj == NULL) { + /* prototype, not an instance. */ + gjs_throw(context, "Can't connect to signals on %s.%s.prototype; only on instances", + g_base_info_get_namespace( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) priv->info)); + return JS_FALSE; + } + + /* Best I can tell, there is no way to know if argv[1] is really + * callable other than to just try it. Checking whether it's a + * function will not detect native objects that provide + * JSClass::call, for example. + */ + + if (argc != 2 || + !JSVAL_IS_STRING(argv[0]) || + !JSVAL_IS_OBJECT(argv[1])) { + gjs_throw(context, "connect() takes two args, the signal name and the callback"); + return JS_FALSE; + } + + closure = gjs_closure_new_marshaled(context, JSVAL_TO_OBJECT(argv[1]), "signal callback"); + if (closure == NULL) + return JS_FALSE; + + signal_name = gjs_string_get_ascii_checked(context, argv[0]); + if (signal_name == NULL) { + g_closure_sink(closure); + return JS_FALSE; + } + + id = g_signal_connect_closure(priv->gobj, + signal_name, + closure, + after); + + if (!JS_NewNumberValue(context, id, retval)) { + g_signal_handler_disconnect(priv->gobj, id); + return JS_FALSE; + } + + return JS_TRUE; +} + +static JSBool +connect_after_func(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + return real_connect_func(context, obj, argc, argv, retval, TRUE); +} + +static JSBool +connect_func(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + return real_connect_func(context, obj, argc, argv, retval, FALSE); +} + +static JSBool +disconnect_func(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + ObjectInstance *priv; + gulong id; + + *retval = JSVAL_VOID; + + priv = priv_from_js(context, obj); + gjs_debug(GJS_DEBUG_GOBJECT, + "disconnect obj %p priv %p argc %d", obj, priv, argc); + + if (priv == NULL) + return JS_FALSE; /* wrong class passed in */ + + if (priv->gobj == NULL) { + /* prototype, not an instance. */ + gjs_throw(context, "Can't disconnect signal on %s.%s.prototype; only on instances", + g_base_info_get_namespace( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) priv->info)); + return JS_FALSE; + } + + if (argc != 1 || + !JSVAL_IS_INT(argv[0])) { + gjs_throw(context, "disconnect() takes one arg, the signal handler id"); + return JS_FALSE; + } + + id = JSVAL_TO_INT(argv[0]); + + g_signal_handler_disconnect(priv->gobj, id); + + return JS_TRUE; +} + +static JSBool +emit_func(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + ObjectInstance *priv; + guint signal_id; + GQuark signal_detail; + GSignalQuery signal_query; + const char *signal_name; + GValue *instance_and_args; + GValue rvalue; + unsigned int i; + gboolean failed; + + *retval = JSVAL_VOID; + + priv = priv_from_js(context, obj); + gjs_debug(GJS_DEBUG_GOBJECT, + "emit obj %p priv %p argc %d", obj, priv, argc); + + if (priv == NULL) + return JS_FALSE; /* wrong class passed in */ + + if (priv->gobj == NULL) { + /* prototype, not an instance. */ + gjs_throw(context, "Can't emit signal on %s.%s.prototype; only on instances", + g_base_info_get_namespace( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) priv->info)); + return JS_FALSE; + } + + if (argc < 1 || + !JSVAL_IS_STRING(argv[0])) { + gjs_throw(context, "emit() first arg is the signal name"); + return JS_FALSE; + } + + signal_name = gjs_string_get_ascii_checked(context, + argv[0]); + if (signal_name == NULL) + return JS_FALSE; + + if (!g_signal_parse_name(signal_name, + G_OBJECT_TYPE(priv->gobj), + &signal_id, + &signal_detail, + FALSE)) { + gjs_throw(context, "No signal '%s' on object '%s'", + signal_name, + g_type_name(G_OBJECT_TYPE(priv->gobj))); + return JS_FALSE; + } + + g_signal_query(signal_id, &signal_query); + + if ((argc - 1) != signal_query.n_params) { + gjs_throw(context, "Signal '%s' on %s requires %d args got %d", + signal_name, + g_type_name(G_OBJECT_TYPE(priv->gobj)), + signal_query.n_params, + argc - 1); + return JS_FALSE; + } + + if (signal_query.return_type != G_TYPE_NONE) { + g_value_init(&rvalue, signal_query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE); + } + + instance_and_args = g_newa(GValue, signal_query.n_params + 1); + memset(instance_and_args, 0, sizeof(GValue) * (signal_query.n_params + 1)); + + g_value_init(&instance_and_args[0], G_TYPE_FROM_INSTANCE(priv->gobj)); + g_value_set_instance(&instance_and_args[0], priv->gobj); + + failed = FALSE; + for (i = 0; i < signal_query.n_params; ++i) { + GValue *value; + value = &instance_and_args[i + 1]; + + g_value_init(value, signal_query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE); + if (!gjs_value_to_g_value(context, argv[i+1], + value)) { + failed = TRUE; + break; + } + } + + if (!failed) { + g_signal_emitv(instance_and_args, signal_id, signal_detail, + &rvalue); + } + + if (signal_query.return_type != G_TYPE_NONE) { + if (!gjs_value_from_g_value(context, + retval, + &rvalue)) + failed = TRUE; + + g_value_unset(&rvalue); + } + + for (i = 0; i < (signal_query.n_params + 1); ++i) { + g_value_unset(&instance_and_args[i]); + } + + return !failed; +} + +/* The bizarre thing about this vtable is that it applies to both + * instances of the object, and to the prototype that instances of the + * class have. + * + * Also, there's a constructor field in here, but as far as I can + * tell, it would only be used if no constructor were provided to + * JS_InitClass. The constructor from JS_InitClass is not applied to + * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags. + */ +static struct JSClass gjs_object_instance_class = { + NULL, /* We copy this class struct with multiple names */ + JSCLASS_HAS_PRIVATE | + JSCLASS_NEW_RESOLVE | + JSCLASS_NEW_RESOLVE_GETS_START | + JSCLASS_CONSTRUCT_PROTOTYPE, + JS_PropertyStub, + JS_PropertyStub, + object_instance_get_prop, + object_instance_set_prop, + JS_EnumerateStub, + (JSResolveOp) object_instance_new_resolve, /* needs cast since it's the new resolve signature */ + JS_ConvertStub, + object_instance_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSPropertySpec gjs_object_instance_proto_props[] = { + { NULL } +}; + +static JSFunctionSpec gjs_object_instance_proto_funcs[] = { + { "connect", connect_func, 0, 0 }, + { "connect_after", connect_after_func, 0, 0 }, + { "disconnect", disconnect_func, 0, 0 }, + { "emit", emit_func, 0, 0 }, + { NULL } +}; + +JSBool +gjs_define_object_class(JSContext *context, + JSObject *in_object, + GType gtype, + GIObjectInfo *info, + JSObject **constructor_p, + JSObject **prototype_p) +{ + const char *constructor_name; + JSObject *prototype; + GIObjectInfo *parent_info; + JSObject *parent_proto; + jsval value; + ObjectInstance *priv; + + /* http://egachine.berlios.de/embedding-sm-best-practice/apa.html + * http://www.sitepoint.com/blogs/2006/01/17/javascript-inheritance/ + * http://www.cs.rit.edu/~atk/JavaScript/manuals/jsobj/ + * + * What we want is: + * + * repoobj.Gtk.Window is constructor for a GtkWindow wrapper JSObject + * (gjs_define_object_constructor() is supposed to define Window in Gtk) + * + * Window.prototype contains the methods on Window, e.g. set_default_size() + * mywindow.__proto__ is Window.prototype + * mywindow.__proto__.__proto__ is Bin.prototype + * mywindow.__proto__.__proto__.__proto__ is Container.prototype + * + * Because Window.prototype is an instance of Window in a sense, + * Window.prototype.__proto__ is Window.prototype, just as + * mywindow.__proto__ is Window.prototype + * + * If we do "mywindow = new Window()" then we should get: + * mywindow.__proto__ == Window.prototype + * which means "mywindow instanceof Window" is true. + * + * Remember "Window.prototype" is "the __proto__ of stuff + * constructed with new Window()" + * + * __proto__ is used to search for properties if you do "this.foo" + * while __parent__ defines the scope to search if you just have + * "foo". + * + * __proto__ is used to look up properties, while .prototype is only + * relevant for constructors and is used to set __proto__ on new'd + * objects. So .prototype only makes sense on constructors. + * + * JS_SetPrototype() and JS_GetPrototype() are for __proto__. + * To set/get .prototype, just use the normal property accessors, + * or JS_InitClass() sets it up automatically. + * + * JavaScript is SO AWESOME + */ + + constructor_name = g_base_info_get_name( (GIBaseInfo*) info); + + /* 'gtype' is the GType of a concrete class (if any) which may or may not + * be defined in the GIRepository. 'info' corresponds to the first known + * ancestor of 'gtype' (or the gtype itself.) + * + * For example: + * gtype=GtkWindow info=Gtk.Window (defined) + * gtype=GLocalFile info=GLib.Object (not defined) + * gtype=GHalMount info=GLib.Object (not defined) + * + * Each GType needs to have distinct JS class, otherwise the JS class for + * first common parent in GIRepository gets used with conflicting gtypes + * when resolving GTypeInterface methods. + * + * In case 'gtype' is not defined in GIRepository use the type name as + * constructor assuming it is unique enough instead of sharing + * 'Object' (or whatever the first known ancestor is) + * + * 'gtype' can be invalid when called from gjs_define_info() + */ + if (gtype != G_TYPE_INVALID) { + GIBaseInfo *gtype_info; + + gtype_info = g_irepository_find_by_gtype(g_irepository_get_default(), + gtype); + if (gtype_info != NULL) { + g_base_info_unref(gtype_info); + } else { + /* defining a class not known to GIRepository */ + constructor_name = g_type_name(gtype); + } + } + + if (gjs_object_get_property(context, in_object, constructor_name, &value)) { + JSObject *constructor; + + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "Existing property '%s' does not look like a constructor", + constructor_name); + return JS_FALSE; + } + + constructor = JSVAL_TO_OBJECT(value); + + gjs_object_get_property(context, constructor, "prototype", &value); + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "prototype property does not appear to exist or has wrong type"); + return JS_FALSE; + } else { + if (prototype_p) + *prototype_p = JSVAL_TO_OBJECT(value); + if (constructor_p) + *constructor_p = constructor; + + return JS_TRUE; + } + } + + parent_proto = NULL; + + /* FIXME: this traverses GIObjectInfo hierarchy too quickly. Should go up + * the GType hierarchy to find the closest parent GIObjectInfo, but we + * don't always have valid 'gtype' to do that. (This is only a problem when + * g-i has only partial knowledge about the GType hierarchy, for example + * with GIO where most concrete types are private and meant to be accessed + * through interfaces only.) + */ + parent_info = g_object_info_get_parent(info); + if (parent_info != NULL) { + GType parent_gtype; + + parent_gtype = g_type_parent(gtype); + parent_proto = gjs_lookup_object_prototype(context, parent_gtype, parent_info); + + g_base_info_unref( (GIBaseInfo*) parent_info); + parent_info = NULL; + } + + prototype = gjs_init_class_dynamic(context, in_object, + /* parent prototype JSObject* for + * prototype; NULL for + * Object.prototype + */ + parent_proto, + g_base_info_get_namespace( (GIBaseInfo*) info), + constructor_name, + &gjs_object_instance_class, + /* constructor for instances (NULL for + * none - just name the prototype like + * Math - rarely correct) + */ + object_instance_constructor, + /* number of constructor args */ + 0, + /* props of prototype */ + &gjs_object_instance_proto_props[0], + /* funcs of prototype */ + &gjs_object_instance_proto_funcs[0], + /* props of constructor, MyConstructor.myprop */ + NULL, + /* funcs of constructor, MyConstructor.myfunc() */ + NULL); + if (prototype == NULL) + gjs_fatal("Can't init class %s", constructor_name); + + g_assert(gjs_object_has_property(context, in_object, constructor_name)); + + /* Put the info in the prototype */ + priv = priv_from_js(context, prototype); + g_assert(priv != NULL); + g_assert(priv->info == NULL); + priv->info = info; + g_base_info_ref( (GIBaseInfo*) priv->info); + priv->gtype = gtype; + + gjs_debug(GJS_DEBUG_GOBJECT, "Defined class %s prototype is %p class %p in object %p", + constructor_name, prototype, JS_GetClass(context, prototype), in_object); + + if (constructor_p) { + *constructor_p = NULL; + gjs_object_get_property(context, in_object, constructor_name, &value); + if (value != JSVAL_VOID) { + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "Property '%s' does not look like a constructor", + constructor_name); + return JS_FALSE; + } + + *constructor_p = JSVAL_TO_OBJECT(value); + } + } + + if (prototype_p) + *prototype_p = prototype; + + return JS_TRUE; +} + +/* multiple JSRuntime could have a proxy to the same GObject, in theory + */ +#define OBJ_KEY_PREFIX_LEN 3 +#define OBJ_KEY_LEN (OBJ_KEY_PREFIX_LEN+sizeof(void*)*2) +static void +get_obj_key(JSRuntime *runtime, + char *buf) +{ + /* not thread safe, but that's fine for now - just nuke the + * cache thingy if we ever need thread safety + */ + static char cached_buf[OBJ_KEY_LEN]; + static JSRuntime *cached_for = NULL; + + if (cached_for != runtime) { + unsigned int i; + union { + const unsigned char bytes[sizeof(void*)]; + void *ptr; + } d; + g_assert(sizeof(d) == sizeof(void*)); + + buf[0] = 'j'; + buf[1] = 's'; + buf[2] = '-'; + d.ptr = runtime; + for (i = 0; i < sizeof(void*)*2; i += 2) { + buf[OBJ_KEY_PREFIX_LEN+i] = 'a' + ((d.bytes[i] & 0xf0) >> 4); + buf[OBJ_KEY_PREFIX_LEN+i+1] = 'a' + (d.bytes[i] & 0x0f); + } + buf[OBJ_KEY_LEN] = '\0'; + strcpy(cached_buf, buf); + cached_for = runtime; + g_assert(strlen(buf) == OBJ_KEY_LEN); + } else { + strcpy(buf, cached_buf); + } +} + +static JSObject* +peek_js_obj(JSContext *context, + GObject *gobj) +{ + char buf[OBJ_KEY_LEN+1]; + + get_obj_key(JS_GetRuntime(context), buf); + + return g_object_get_data(gobj, buf); +} + +static void +set_js_obj(JSContext *context, + GObject *gobj, + JSObject *obj) +{ + char buf[OBJ_KEY_LEN+1]; + + get_obj_key(JS_GetRuntime(context), buf); + + g_object_set_data(gobj, buf, obj); +} + +JSObject* +gjs_object_from_g_object(JSContext *context, + GObject *gobj) +{ + JSObject *obj; + + if (gobj == NULL) + return NULL; + + obj = peek_js_obj(context, gobj); + + if (obj == NULL) { + /* We have to create a wrapper */ + JSObject *proto; + GIBaseInfo *info; + GType gtype; + + gjs_debug_marshal(GJS_DEBUG_GOBJECT, + "Wrapping %s with JSObject", + g_type_name_from_instance((GTypeInstance*) gobj)); + + info = NULL; + gtype = G_TYPE_FROM_INSTANCE(gobj); + while (info == NULL) { + info = g_irepository_find_by_gtype(g_irepository_get_default(), + gtype); + if (info != NULL) + break; + + if (gtype == G_TYPE_OBJECT) + gjs_fatal("No introspection data on GObject - pretty much screwed"); + + gjs_debug(GJS_DEBUG_GOBJECT, + "No introspection data on '%s' so trying parent type '%s'", + g_type_name(gtype), g_type_name(g_type_parent(gtype))); + gtype = g_type_parent(gtype); + } + + if (info == NULL) { + gjs_throw(context, + "Unknown object type %s", + g_type_name(G_TYPE_FROM_INSTANCE(gobj))); + return NULL; + } + + proto = gjs_lookup_object_prototype(context, G_TYPE_FROM_INSTANCE(gobj), (GIObjectInfo*) info); + + /* can't come up with a better approach... */ + unthreadsafe_template_for_constructor.info = (GIObjectInfo*) info; + unthreadsafe_template_for_constructor.gobj = gobj; + + obj = gjs_construct_object_dynamic(context, proto, + 0, NULL); + + g_base_info_unref( (GIBaseInfo*) info); + + g_assert(peek_js_obj(context, gobj) == obj); + } + + return obj; +} + +GObject* +gjs_g_object_from_object(JSContext *context, + JSObject *obj) +{ + ObjectInstance *priv; + + if (obj == NULL) + return NULL; + + priv = priv_from_js(context, obj); + + if (priv == NULL) + return NULL; + + if (priv->gobj == NULL) { + gjs_throw(context, + "Object is %s.%s.prototype, not an object instance - cannot convert to GObject*", + g_base_info_get_namespace( (GIBaseInfo*) priv->info), + g_base_info_get_name( (GIBaseInfo*) priv->info)); + return NULL; + } + + return priv->gobj; +} diff --git a/gi/object.h b/gi/object.h new file mode 100644 index 00000000..8380af30 --- /dev/null +++ b/gi/object.h @@ -0,0 +1,57 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_OBJECT_H__ +#define __GJS_OBJECT_H__ + +#include <glib.h> + +#include <jsapi.h> + +#include <girepository.h> + +G_BEGIN_DECLS + +JSBool gjs_define_object_class (JSContext *context, + JSObject *in_object, + GType gtype, + GIObjectInfo *info, + JSObject **constructor_p, + JSObject **prototype_p); +JSClass* gjs_lookup_object_class (JSContext *context, + GType gtype, + GIObjectInfo *info); +JSObject* gjs_lookup_object_prototype (JSContext *context, + GType gtype, + GIObjectInfo *info); +JSObject* gjs_lookup_object_constructor (JSContext *context, + GType gtype, + GIObjectInfo *info); +JSObject* gjs_object_from_g_object (JSContext *context, + GObject *gobj); +GObject* gjs_g_object_from_object (JSContext *context, + JSObject *obj); + +G_END_DECLS + +#endif /* __GJS_OBJECT_H__ */ diff --git a/gi/param.c b/gi/param.c new file mode 100644 index 00000000..333751c0 --- /dev/null +++ b/gi/param.c @@ -0,0 +1,416 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include <string.h> + +#include "param.h" +#include "repo.h" +#include <gjs/jsapi-util.h> +#include <gjs/mem.h> + +#include <util/log.h> + +#include <jsapi.h> + +typedef struct { + GParamSpec *gparam; /* NULL if we are the prototype and not an instance */ +} Param; + +static Param unthreadsafe_template_for_constructor = { NULL }; + +static struct JSClass gjs_param_class; + +GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(Param, gjs_param_class) + +/* a hook on getting a property; set value_p to override property's value. + * Return value is JS_FALSE on OOM/exception. + */ +static JSBool +param_get_prop(JSContext *context, + JSObject *obj, + jsval id, + jsval *value_p) +{ + Param *priv; + const char *name; + const char *value_str; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not something we affect, but no error */ + + priv = priv_from_js(context, obj); + + gjs_debug_jsprop(GJS_DEBUG_GPARAM, + "Get prop '%s' hook obj %p priv %p", name, obj, priv); + + if (priv == NULL) + return JS_FALSE; /* wrong class */ + + value_str = NULL; + if (strcmp(name, "name") == 0) + value_str = g_param_spec_get_name(priv->gparam); + else if (strcmp(name, "nick") == 0) + value_str = g_param_spec_get_nick(priv->gparam); + else if (strcmp(name, "blurb") == 0) + value_str = g_param_spec_get_blurb(priv->gparam); + + if (value_str != NULL) { + *value_p = STRING_TO_JSVAL(JS_NewStringCopyZ(context, value_str)); + } + + return JS_TRUE; +} + +/* + * Like JSResolveOp, but flags provide contextual information as follows: + * + * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id + * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment + * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence + * JSRESOLVE_DECLARING var, const, or param prolog declaration opcode + * JSRESOLVE_CLASSNAME class name used when constructing + * + * The *objp out parameter, on success, should be null to indicate that id + * was not resolved; and non-null, referring to obj or one of its prototypes, + * if id was resolved. + */ +static JSBool +param_new_resolve(JSContext *context, + JSObject *obj, + jsval id, + uintN flags, + JSObject **objp) +{ + Param *priv; + const char *name; + + *objp = NULL; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not resolved, but no error */ + + priv = priv_from_js(context, obj); + + gjs_debug_jsprop(GJS_DEBUG_GPARAM, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv); + + if (priv == NULL) + return JS_FALSE; /* wrong class */ + + if (priv->gparam == NULL) { + /* We are the prototype, so implement any methods or other class properties */ + + } else { + /* We are an instance, not a prototype, so look for + * per-instance props that we want to define on the + * JSObject. Generally we do not want to cache these in JS, we + * want to always pull them from the C object, or JS would not + * see any changes made from C. So we use the get/set prop + * hooks, not this resolve hook. + */ + } + + return JS_TRUE; +} + +/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on + * the prototype in addition to on each instance. When called on the + * prototype, "obj" is the prototype, and "retval" is the prototype + * also, but can be replaced with another object to use instead as the + * prototype. If we don't set JSCLASS_CONSTRUCT_PROTOTYPE we can + * identify the prototype as an object of our class with NULL private + * data. + */ +static JSBool +param_constructor(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + Param *priv; + Param *proto_priv; + JSClass *obj_class; + JSClass *proto_class; + JSObject *proto; + gboolean is_proto; + + priv = g_slice_new0(Param); + + GJS_INC_COUNTER(param); + + g_assert(priv_from_js(context, obj) == NULL); + JS_SetPrivate(context, obj, priv); + + gjs_debug_lifecycle(GJS_DEBUG_GPARAM, + "param constructor, obj %p priv %p", obj, priv); + + proto = JS_GetPrototype(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GPARAM, "param instance __proto__ is %p", proto); + + /* If we're constructing the prototype, its __proto__ is not the same + * class as us, but if we're constructing an instance, the prototype + * has the same class. + */ + obj_class = JS_GetClass(context, obj); + proto_class = JS_GetClass(context, proto); + + is_proto = (obj_class != proto_class); + + gjs_debug_lifecycle(GJS_DEBUG_GPARAM, + "param instance constructing proto %d, obj class %s proto class %s", + is_proto, obj_class->name, proto_class->name); + + if (!is_proto) { + /* If we're the prototype, then post-construct we'll fill in priv->info. + * If we are not the prototype, though, then we'll get ->info from the + * prototype and then create a GObject if we don't have one already. + */ + proto_priv = priv_from_js(context, proto); + if (proto_priv == NULL) { + gjs_debug(GJS_DEBUG_GPARAM, + "Bad prototype set on object? Must match JSClass of object. JS error should have been reported."); + return JS_FALSE; + } + + if (unthreadsafe_template_for_constructor.gparam == NULL) { + /* To construct these we'd have to wrap all the annoying subclasses. + * Since we only bind ParamSpec for purposes of the GObject::notify signal, + * there isn't much point. + */ + gjs_throw(context, "Unable to construct ParamSpec, can only wrap an existing one"); + return JS_FALSE; + } else { + priv->gparam = g_param_spec_ref(unthreadsafe_template_for_constructor.gparam); + unthreadsafe_template_for_constructor.gparam = NULL; + } + + gjs_debug(GJS_DEBUG_GPARAM, + "JSObject created with param instance %p type %s", + priv->gparam, g_type_name(G_TYPE_FROM_INSTANCE((GTypeInstance*) priv->gparam))); + } + + return JS_TRUE; +} + +static void +param_finalize(JSContext *context, + JSObject *obj) +{ + Param *priv; + + priv = priv_from_js(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GPARAM, + "finalize, obj %p priv %p", obj, priv); + if (priv == NULL) + return; /* wrong class? */ + + if (priv->gparam) { + g_param_spec_unref(priv->gparam); + priv->gparam = NULL; + } + + GJS_DEC_COUNTER(param); + g_slice_free(Param, priv); +} + +/* The bizarre thing about this vtable is that it applies to both + * instances of the object, and to the prototype that instances of the + * class have. + * + * Also, there's a constructor field in here, but as far as I can + * tell, it would only be used if no constructor were provided to + * JS_InitClass. The constructor from JS_InitClass is not applied to + * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags. + */ +static struct JSClass gjs_param_class = { + NULL, /* dynamic */ + JSCLASS_HAS_PRIVATE | + JSCLASS_NEW_RESOLVE | + JSCLASS_NEW_RESOLVE_GETS_START | + JSCLASS_CONSTRUCT_PROTOTYPE, + JS_PropertyStub, + JS_PropertyStub, + param_get_prop, + JS_PropertyStub, + JS_EnumerateStub, + (JSResolveOp) param_new_resolve, /* needs cast since it's the new resolve signature */ + JS_ConvertStub, + param_finalize, + NULL, + NULL, + NULL, + NULL, NULL, NULL, NULL, NULL +}; + +static JSPropertySpec gjs_param_proto_props[] = { + { NULL } +}; + +static JSFunctionSpec gjs_param_proto_funcs[] = { + { NULL } +}; + +JSObject* +gjs_lookup_param_prototype(JSContext *context) +{ + JSObject *ns; + JSObject *proto; + + ns = gjs_lookup_namespace_object_by_name(context, "GLib"); + + if (ns == NULL) + return NULL; + + if (gjs_define_param_class(context, ns, &proto)) + return proto; + else + return NULL; +} + +JSBool +gjs_define_param_class(JSContext *context, + JSObject *in_object, + JSObject **prototype_p) +{ + const char *constructor_name; + JSObject *prototype; + jsval value; + + constructor_name = "ParamSpec"; + + gjs_object_get_property(context, in_object, constructor_name, &value); + if (value != JSVAL_VOID) { + JSObject *constructor; + + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "Existing property '%s' does not look like a constructor", + constructor_name); + return JS_FALSE; + } + + constructor = JSVAL_TO_OBJECT(value); + + gjs_object_get_property(context, constructor, "prototype", &value); + if (!JSVAL_IS_OBJECT(value)) { + gjs_throw(context, "prototype property does not appear to exist or has wrong type"); + return JS_FALSE; + } else { + if (prototype_p) + *prototype_p = JSVAL_TO_OBJECT(value); + + return JS_TRUE; + } + + return JS_TRUE; + } + + /* we could really just use JS_InitClass for this since we have one class instead of + * N classes on-demand. But, this deals with namespacing and such for us. + */ + prototype = gjs_init_class_dynamic(context, in_object, + /* parent prototype JSObject* for + * prototype; NULL for + * Object.prototype + */ + NULL, + "GLib", + constructor_name, + &gjs_param_class, + /* constructor for instances (NULL for + * none - just name the prototype like + * Math - rarely correct) + */ + param_constructor, + /* number of constructor args */ + 0, + /* props of prototype */ + &gjs_param_proto_props[0], + /* funcs of prototype */ + &gjs_param_proto_funcs[0], + /* props of constructor, MyConstructor.myprop */ + NULL, + /* funcs of constructor, MyConstructor.myfunc() */ + NULL); + if (prototype == NULL) + gjs_fatal("Can't init class %s", constructor_name); + + g_assert(gjs_object_has_property(context, in_object, constructor_name)); + + if (prototype_p) + *prototype_p = prototype; + + gjs_debug(GJS_DEBUG_GPARAM, "Defined class %s prototype is %p class %p in object %p", + constructor_name, prototype, JS_GetClass(context, prototype), in_object); + + return JS_TRUE; +} + +JSObject* +gjs_param_from_g_param(JSContext *context, + GParamSpec *gparam) +{ + JSObject *obj; + JSObject *proto; + + if (gparam == NULL) + return NULL; + + gjs_debug(GJS_DEBUG_GPARAM, + "Wrapping %s with JSObject", + g_type_name(G_TYPE_FROM_INSTANCE((GTypeInstance*) gparam))); + + proto = gjs_lookup_param_prototype(context); + + /* can't come up with a better approach... */ + unthreadsafe_template_for_constructor.gparam = gparam; + + obj = gjs_construct_object_dynamic(context, proto, + 0, NULL); + + return obj; +} + +GParamSpec* +gjs_g_param_from_param(JSContext *context, + JSObject *obj) +{ + Param *priv; + + if (obj == NULL) + return NULL; + + priv = priv_from_js(context, obj); + + if (priv == NULL) + return NULL; + + if (priv->gparam == NULL) { + gjs_throw(context, + "Object is a prototype, not an object instance - cannot convert to a paramspec instance"); + return NULL; + } + + return priv->gparam; +} diff --git a/gi/param.h b/gi/param.h new file mode 100644 index 00000000..204eb9b4 --- /dev/null +++ b/gi/param.h @@ -0,0 +1,47 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_PARAM_H__ +#define __GJS_PARAM_H__ + +#include <glib.h> + +#include <jsapi.h> + +#include <girepository.h> + +G_BEGIN_DECLS + +JSBool gjs_define_param_class (JSContext *context, + JSObject *in_object, + JSObject **prototype_p); +GParamSpec* gjs_g_param_from_param (JSContext *context, + JSObject *obj); +JSObject* gjs_param_from_g_param (JSContext *context, + GParamSpec *param); +JSObject* gjs_lookup_param_prototype (JSContext *context); + + +G_END_DECLS + +#endif /* __GJS_PARAM_H__ */ diff --git a/gi/repo.c b/gi/repo.c new file mode 100644 index 00000000..84b3d5bd --- /dev/null +++ b/gi/repo.c @@ -0,0 +1,607 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include "repo.h" +#include "ns.h" +#include "function.h" +#include "object.h" +#include "boxed.h" +#include "enumeration.h" +#include "arg.h" + +#include <gjs/mem.h> + +#include <util/log.h> +#include <util/dirs.h> +#include <util/misc.h> + +#include <jsapi.h> + +#include <girepository.h> +#include <string.h> + +typedef struct { + void *dummy; + +} Repo; + +static struct JSClass gjs_repo_class; + +GJS_DEFINE_PRIV_FROM_JS(Repo, gjs_repo_class) + +static JSObject* +resolve_namespace_object(JSContext *context, + JSObject *repo_obj, + const char *ns_name) +{ + GIRepository *repo; + GError *error; + char *fixed_ns_name; + + fixed_ns_name = gjs_fix_ns_name(ns_name); + + repo = g_irepository_get_default(); + + error = NULL; + g_irepository_require(repo, fixed_ns_name, 0, &error); + if (error != NULL) { + gjs_throw(context, + "Requiring %s fixed as %s: %s", + ns_name, fixed_ns_name, error->message); + g_error_free(error); + g_free(fixed_ns_name); + return JS_FALSE; + } + + g_free(fixed_ns_name); + + /* Defines a property on "obj" (the javascript repo object) + * with the given namespace name, pointing to that namespace + * in the repo. + */ + return gjs_define_ns(context, repo_obj, ns_name, repo); +} + +/* + * Like JSResolveOp, but flags provide contextual information as follows: + * + * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id + * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment + * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence + * JSRESOLVE_DECLARING var, const, or function prolog declaration opcode + * JSRESOLVE_CLASSNAME class name used when constructing + * + * The *objp out parameter, on success, should be null to indicate that id + * was not resolved; and non-null, referring to obj or one of its prototypes, + * if id was resolved. + */ +static JSBool +repo_new_resolve(JSContext *context, + JSObject *obj, + jsval id, + uintN flags, + JSObject **objp) +{ + Repo *priv; + const char *name; + JSContext *load_context; + + *objp = NULL; + + if (!gjs_get_string_id(id, &name)) + return JS_TRUE; /* not resolved, but no error */ + + /* let Object.prototype resolve these */ + if (strcmp(name, "valueOf") == 0 || + strcmp(name, "toString") == 0) + return JS_TRUE; + + priv = priv_from_js(context, obj); + gjs_debug_jsprop(GJS_DEBUG_GREPO, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv); + + if (priv == NULL) + return JS_TRUE; /* we are the prototype, or have the wrong class */ + + load_context = gjs_runtime_get_load_context(JS_GetRuntime(context)); + resolve_namespace_object(load_context, obj, name); + if (gjs_move_exception(load_context, context)) { + return JS_FALSE; + } else { + *objp = obj; /* store the object we defined the prop in */ + return JS_TRUE; + } +} + +/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on + * the prototype in addition to on each instance. When called on the + * prototype, "obj" is the prototype, and "retval" is the prototype + * also, but can be replaced with another object to use instead as the + * prototype. If we don't set JSCLASS_CONSTRUCT_PROTOTYPE we can + * identify the prototype as an object of our class with NULL private + * data. + */ +static JSBool +repo_constructor(JSContext *context, + JSObject *obj, + uintN argc, + jsval *argv, + jsval *retval) +{ + Repo *priv; + + priv = g_slice_new0(Repo); + + GJS_INC_COUNTER(repo); + + g_assert(priv_from_js(context, obj) == NULL); + JS_SetPrivate(context, obj, priv); + + gjs_debug_lifecycle(GJS_DEBUG_GREPO, + "repo constructor, obj %p priv %p", obj, priv); + + return JS_TRUE; +} + +static void +repo_finalize(JSContext *context, + JSObject *obj) +{ + Repo *priv; + + priv = priv_from_js(context, obj); + gjs_debug_lifecycle(GJS_DEBUG_GREPO, + "finalize, obj %p priv %p", obj, priv); + if (priv == NULL) + return; /* we are the prototype, not a real instance, so constructor never called */ + + GJS_DEC_COUNTER(repo); + g_slice_free(Repo, priv); +} + +/* The bizarre thing about this vtable is that it applies to both + * instances of the object, and to the prototype that instances of the + * class have. + * + * Also, there's a constructor field in here, but as far as I can + * tell, it would only be used if no constructor were provided to + * JS_InitClass. The constructor from JS_InitClass is not applied to + * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags. + */ +static struct JSClass gjs_repo_class = { + "GIRepository", /* means "new GIRepository()" works */ + JSCLASS_HAS_PRIVATE | + JSCLASS_NEW_RESOLVE | + JSCLASS_NEW_RESOLVE_GETS_START, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_EnumerateStub, + (JSResolveOp) repo_new_resolve, /* needs cast since it's the new resolve signature */ + JS_ConvertStub, + repo_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSPropertySpec gjs_repo_proto_props[] = { + { NULL } +}; + +static JSFunctionSpec gjs_repo_proto_funcs[] = { + { NULL } +}; + +static JSObject* +repo_new(JSContext *context) +{ + JSObject *repo; + JSObject *global; + + /* We have to define the class in the global object so we can JS_ConstructObject */ + + global = JS_GetGlobalObject(context); + + if (!gjs_object_has_property(context, global, gjs_repo_class.name)) { + JSObject *prototype; + prototype = JS_InitClass(context, global, + /* parent prototype JSObject* for + * prototype; NULL for + * Object.prototype + */ + NULL, + &gjs_repo_class, + /* constructor for instances (NULL for + * none - just name the prototype like + * Math - rarely correct) + */ + repo_constructor, + /* number of constructor args */ + 0, + /* props of prototype */ + &gjs_repo_proto_props[0], + /* funcs of prototype */ + &gjs_repo_proto_funcs[0], + /* props of constructor, MyConstructor.myprop */ + NULL, + /* funcs of constructor, MyConstructor.myfunc() */ + NULL); + if (prototype == NULL) + gjs_fatal("Can't init class %s", gjs_repo_class.name); + + g_assert(gjs_object_has_property(context, global, gjs_repo_class.name)); + + gjs_debug(GJS_DEBUG_GREPO, "Initialized class %s prototype %p", + gjs_repo_class.name, prototype); + } + + repo = JS_ConstructObject(context, &gjs_repo_class, NULL, NULL); + if (repo == NULL) { + gjs_throw(context, "No memory to create repo object"); + return JS_FALSE; + } + + /* FIXME - hack to make namespaces load, since + * gobject-introspection does not yet search a path properly. + */ + { + jsval value; + JS_GetProperty(context, repo, "GLib", &value); + } + + return repo; +} + +JSBool +gjs_define_repo(JSContext *context, + JSObject *module_obj, + const char *name) +{ + JSObject *repo; + + if (gjs_environment_variable_is_set("GJS_USE_UNINSTALLED_FILES")) { + const char *builddir = g_getenv("BUILDDIR"); + + if (builddir != NULL) { + g_irepository_prepend_search_path(builddir); + } + } + + repo = repo_new(context); + + if (!JS_DefineProperty(context, module_obj, + name, OBJECT_TO_JSVAL(repo), + NULL, NULL, + GJS_MODULE_PROP_FLAGS)) + return JS_FALSE; + + return JS_TRUE; +} + +static JSBool +gjs_define_constant(JSContext *context, + JSObject *in_object, + GIConstantInfo *info) +{ + jsval value; + GArgument garg = { 0, }; + GITypeInfo *type_info; + const char *name; + + type_info = g_constant_info_get_type(info); + g_constant_info_get_value(info, &garg); + + if (!gjs_value_from_g_arg(context, &value, type_info, &garg)) { + g_base_info_unref((GIBaseInfo*) type_info); + return JS_FALSE; + } + + g_base_info_unref((GIBaseInfo*) type_info); + + name = g_base_info_get_name((GIBaseInfo*) info); + + if (!JS_DefineProperty(context, in_object, + name, value, + NULL, NULL, + GJS_MODULE_PROP_FLAGS)) + return JS_FALSE; + + return JS_TRUE; +} + +#if GJS_VERBOSE_ENABLE_GI_USAGE +void +_gjs_log_info_usage(GIBaseInfo *info) +{ +#define DIRECTION_STRING(d) ( ((d) == GI_DIRECTION_IN) ? "IN" : ((d) == GI_DIRECTION_OUT) ? "OUT" : "INOUT" ) +#define TRANSFER_STRING(t) ( ((t) == GI_TRANSFER_NOTHING) ? "NOTHING" : ((t) == GI_TRANSFER_CONTAINER) ? "CONTAINER" : "EVERYTHING" ) + + { + char *details; + GIInfoType info_type; + GIBaseInfo *container; + + info_type = g_base_info_get_type(info); + + if (info_type == GI_INFO_TYPE_FUNCTION) { + GString *args; + int n_args; + int i; + GITransfer retval_transfer; + + args = g_string_new("{ "); + + n_args = g_callable_info_get_n_args((GICallableInfo*) info); + for (i = 0; i < n_args; ++i) { + GIArgInfo *arg; + GIDirection direction; + GITransfer transfer; + + arg = g_callable_info_get_arg((GICallableInfo*)info, i); + direction = g_arg_info_get_direction(arg); + transfer = g_arg_info_get_ownership_transfer(arg); + + g_string_append_printf(args, + "{ GI_DIRECTION_%s, GI_TRANSFER_%s }, ", + DIRECTION_STRING(direction), + TRANSFER_STRING(transfer)); + + g_base_info_unref((GIBaseInfo*) arg); + } + if (args->len > 2) + g_string_truncate(args, args->len - 2); /* chop comma */ + + g_string_append(args, " }"); + + retval_transfer = g_callable_info_get_caller_owns((GICallableInfo*) info); + + details = g_strdup_printf(".details = { .func = { .retval_transfer = GI_TRANSFER_%s, .n_args = %d, .args = %s } }", + TRANSFER_STRING(retval_transfer), n_args, args->str); + g_string_free(args, TRUE); + } else { + details = g_strdup_printf(".details = { .nothing = {} }"); + } + + container = g_base_info_get_container(info); + + gjs_debug_gi_usage("{ GI_INFO_TYPE_%s, \"%s\", \"%s\", \"%s\", %s },", + gjs_info_type_name(info_type), + g_base_info_get_namespace(info), + container ? g_base_info_get_name(container) : "", + g_base_info_get_name(info), + details); + g_free(details); + } +} +#endif /* GJS_VERBOSE_ENABLE_GI_USAGE */ + +JSBool +gjs_define_info(JSContext *context, + JSObject *in_object, + GIBaseInfo *info) +{ +#if GJS_VERBOSE_ENABLE_GI_USAGE + _gjs_log_info_usage(info); +#endif + + switch (g_base_info_get_type(info)) { + case GI_INFO_TYPE_FUNCTION: + { + JSObject *f; + f = gjs_define_function(context, in_object, (GIFunctionInfo*) info); + if (f == NULL) + return JS_FALSE; + } + break; + case GI_INFO_TYPE_OBJECT: + if (!gjs_define_object_class(context, in_object, G_TYPE_INVALID, (GIObjectInfo*) info, NULL, NULL)) + return JS_FALSE; + break; + case GI_INFO_TYPE_BOXED: + if (!gjs_define_boxed_class(context, in_object, (GIBoxedInfo*) info, NULL, NULL)) + return JS_FALSE; + break; + case GI_INFO_TYPE_ENUM: + case GI_INFO_TYPE_FLAGS: + if (!gjs_define_enumeration(context, in_object, (GIEnumInfo*) info, NULL)) + return JS_FALSE; + break; + case GI_INFO_TYPE_CONSTANT: + if (!gjs_define_constant(context, in_object, (GIConstantInfo*) info)) + return JS_FALSE; + break; + default: + gjs_throw(context, "API of type %s not implemented, cannot define %s.%s", + gjs_info_type_name(g_base_info_get_type(info)), + g_base_info_get_namespace(info), + g_base_info_get_name(info)); + return JS_FALSE; + } + + return JS_TRUE; +} + +/* Get the namespace object that the GIBaseInfo should be inside */ +JSObject* +gjs_lookup_namespace_object(JSContext *context, + GIBaseInfo *info) +{ + const char *ns; + + ns = g_base_info_get_namespace(info); + if (ns == NULL) { + gjs_throw(context, "%s '%s' does not have a namespace", + gjs_info_type_name(g_base_info_get_type(info)), + g_base_info_get_name(info)); + + return NULL; + } + + return gjs_lookup_namespace_object_by_name(context, ns); +} + +JSObject* +gjs_lookup_namespace_object_by_name(JSContext *context, + const char *ns) +{ + JSContext *load_context; + JSObject *global; + JSObject *repo_obj; + jsval importer; + jsval girepository; + jsval ns_obj; + + /* This is a little bit of a hack, we hardcode an assumption that + * the only repo object that exists is called "imports.gi" and is + * in the load context. + */ + + load_context = gjs_runtime_get_load_context(JS_GetRuntime(context)); + global = JS_GetGlobalObject(load_context); + + importer = JSVAL_VOID; + if (!gjs_object_require_property(load_context, global, "imports", &importer) || + !JSVAL_IS_OBJECT(importer)) { + gjs_log_exception(load_context, NULL); + gjs_throw(context, "No imports property in global object"); + return NULL; + } + + girepository = JSVAL_VOID; + if (!gjs_object_require_property(load_context, JSVAL_TO_OBJECT(importer), + "gi", &girepository) || + !JSVAL_IS_OBJECT(girepository)) { + gjs_log_exception(load_context, NULL); + gjs_throw(context, "No gi property in importer"); + return NULL; + } + + repo_obj = JSVAL_TO_OBJECT(girepository); + + if (!gjs_object_require_property(context, repo_obj, ns, &ns_obj)) + return NULL; + + if (!JSVAL_IS_OBJECT(ns_obj)) { + gjs_throw(context, "Namespace '%s' is not an object?", ns); + return NULL; + } + + return JSVAL_TO_OBJECT(ns_obj); +} + +const char* +gjs_info_type_name(GIInfoType type) +{ + switch (type) { + case GI_INFO_TYPE_INVALID: + return "INVALID"; + case GI_INFO_TYPE_FUNCTION: + return "FUNCTION"; + case GI_INFO_TYPE_CALLBACK: + return "CALLBACK"; + case GI_INFO_TYPE_STRUCT: + return "STRUCT"; + case GI_INFO_TYPE_BOXED: + return "BOXED"; + case GI_INFO_TYPE_ENUM: + return "ENUM"; + case GI_INFO_TYPE_FLAGS: + return "FLAGS"; + case GI_INFO_TYPE_OBJECT: + return "OBJECT"; + case GI_INFO_TYPE_INTERFACE: + return "INTERFACE"; + case GI_INFO_TYPE_CONSTANT: + return "CONSTANT"; + case GI_INFO_TYPE_ERROR_DOMAIN: + return "ERROR_DOMAIN"; + case GI_INFO_TYPE_UNION: + return "UNION"; + case GI_INFO_TYPE_VALUE: + return "VALUE"; + case GI_INFO_TYPE_SIGNAL: + return "SIGNAL"; + case GI_INFO_TYPE_VFUNC: + return "VFUNC"; + case GI_INFO_TYPE_PROPERTY: + return "PROPERTY"; + case GI_INFO_TYPE_FIELD: + return "FIELD"; + case GI_INFO_TYPE_ARG: + return "ARG"; + case GI_INFO_TYPE_TYPE: + return "TYPE"; + case GI_INFO_TYPE_UNRESOLVED: + return "UNRESOLVED"; + } + + return "???"; +} + +char* +gjs_camel_from_hyphen(const char *hyphen_name) +{ + GString *s; + const char *p; + gboolean next_upper; + + s = g_string_sized_new(strlen(hyphen_name) + 1); + + next_upper = FALSE; + for (p = hyphen_name; *p; ++p) { + if (*p == '-' || *p == '_') { + next_upper = TRUE; + } else { + if (next_upper) { + g_string_append_c(s, g_ascii_toupper(*p)); + next_upper = FALSE; + } else { + g_string_append_c(s, *p); + } + } + } + + return g_string_free(s, FALSE); +} + +char* +gjs_hyphen_from_camel(const char *camel_name) +{ + GString *s; + const char *p; + + /* four hyphens should be reasonable guess */ + s = g_string_sized_new(strlen(camel_name) + 4 + 1); + + for (p = camel_name; *p; ++p) { + if (g_ascii_isupper(*p)) { + g_string_append_c(s, '-'); + g_string_append_c(s, g_ascii_tolower(*p)); + } else { + g_string_append_c(s, *p); + } + } + + return g_string_free(s, FALSE); +} diff --git a/gi/repo.h b/gi/repo.h new file mode 100644 index 00000000..b2f3daea --- /dev/null +++ b/gi/repo.h @@ -0,0 +1,60 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_REPO_H__ +#define __GJS_REPO_H__ + +#include <glib.h> + +#include <jsapi.h> + +#include <girepository.h> + +#include <gjs/jsapi-util.h> + +G_BEGIN_DECLS + +JSBool gjs_define_repo (JSContext *context, + JSObject *module_obj, + const char *name); +const char* gjs_info_type_name (GIInfoType type); +JSObject* gjs_lookup_namespace_object (JSContext *context, + GIBaseInfo *info); +JSObject* gjs_lookup_namespace_object_by_name (JSContext *context, + const char *name); +JSObject* gjs_lookup_function_object (JSContext *context, + GIFunctionInfo *info); +JSBool gjs_define_info (JSContext *context, + JSObject *in_object, + GIBaseInfo *info); +char* gjs_camel_from_hyphen (const char *hyphen_name); +char* gjs_hyphen_from_camel (const char *camel_name); + + +#if GJS_VERBOSE_ENABLE_GI_USAGE +void _gjs_log_info_usage(GIBaseInfo *info); +#endif + +G_END_DECLS + +#endif /* __GJS_REPO_H__ */ diff --git a/gi/value.c b/gi/value.c new file mode 100644 index 00000000..ceafb4d7 --- /dev/null +++ b/gi/value.c @@ -0,0 +1,512 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <config.h> + +#include <util/log.h> + +#include "value.h" +#include "closure.h" +#include "arg.h" +#include "param.h" +#include "object.h" +#include "boxed.h" +#include <gjs/jsapi-util.h> + +static void +closure_marshal(GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + JSContext *context; + int argc; + jsval *argv; + jsval rval; + int i; + + gjs_debug_marshal(GJS_DEBUG_GCLOSURE, + "Marshal closure %p", + closure); + + context = gjs_closure_get_context(closure); + if (context == NULL) { + /* We were destroyed; become a no-op */ + return; + } + + argc = n_param_values; + argv = g_newa(jsval, n_param_values); + rval = JSVAL_VOID; + + gjs_set_values(context, argv, argc, JSVAL_VOID); + gjs_root_value_locations(context, argv, argc); + JS_AddRoot(context, &rval); + + for (i = 0; i < argc; ++i) { + const GValue *gval = ¶m_values[i]; + if (!gjs_value_from_g_value(context, &argv[i], gval)) { + gjs_debug(GJS_DEBUG_GCLOSURE, + "Unable to convert arg %d in order to invoke closure", + i); + gjs_log_exception(context, NULL); + goto cleanup; + } + } + + gjs_closure_invoke(closure, argc, argv, &rval); + + if (return_value != NULL) { + if (rval == JSVAL_VOID) { + /* something went wrong invoking, error should be set already */ + goto cleanup; + } + + if (!gjs_value_to_g_value(context, rval, return_value)) { + gjs_debug(GJS_DEBUG_GCLOSURE, + "Unable to convert return value when invoking closure"); + gjs_log_exception(context, NULL); + goto cleanup; + } + } + + cleanup: + gjs_unroot_value_locations(context, argv, argc); + JS_RemoveRoot(context, &rval); +} + +GClosure* +gjs_closure_new_marshaled (JSContext *context, + JSObject *callable, + const char *description) +{ + GClosure *closure; + + closure = gjs_closure_new(context, callable, description); + + g_closure_set_marshal(closure, closure_marshal); + + return closure; +} + +static GType +gjs_value_guess_g_type(jsval value) +{ + if (JSVAL_IS_NULL(value)) + return G_TYPE_POINTER; + + if (JSVAL_IS_STRING(value)) + return G_TYPE_STRING; + + if (JSVAL_IS_INT(value)) + return G_TYPE_INT; + + if (JSVAL_IS_DOUBLE(value)) + return G_TYPE_DOUBLE; + + if (JSVAL_IS_BOOLEAN(value)) + return G_TYPE_BOOLEAN; + + if (JSVAL_IS_OBJECT(value)) { + return G_TYPE_OBJECT; + } + + return G_TYPE_INVALID; +} + +JSBool +gjs_value_to_g_value(JSContext *context, + jsval value, + GValue *gvalue) +{ + GType gtype; + + gtype = G_VALUE_TYPE(gvalue); + + if (gtype == 0) { + gtype = gjs_value_guess_g_type(value); + + if (gtype == G_TYPE_INVALID) { + gjs_throw(context, "Could not guess unspecified GValue type"); + return JS_FALSE; + } + + gjs_debug_marshal(GJS_DEBUG_GCLOSURE, + "Guessed GValue type %s from JS Value", + g_type_name(gtype)); + + g_value_init(gvalue, gtype); + } + + gjs_debug_marshal(GJS_DEBUG_GCLOSURE, + "Converting jsval to gtype %s", + g_type_name(gtype)); + + + if (gtype == G_TYPE_STRING) { + /* Don't use ValueToString since we don't want to just toString() + * everything automatically + */ + if (JSVAL_IS_NULL(value)) { + g_value_set_string(gvalue, NULL); + } else if (JSVAL_IS_STRING(value)) { + gchar *utf8_string; + + if (!gjs_string_to_utf8(context, value, &utf8_string)) + return JS_FALSE; + + g_value_set_string(gvalue, utf8_string); + g_free(utf8_string); + } else { + gjs_throw(context, + "Wrong type %s; string expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else if (gtype == G_TYPE_CHAR) { + gint32 i; + if (JS_ValueToInt32(context, value, &i) && i >= SCHAR_MIN && i <= SCHAR_MAX) { + g_value_set_char(gvalue, (signed char)i); + } else { + gjs_throw(context, + "Wrong type %s; char expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else if (gtype == G_TYPE_UCHAR) { + guint16 i; + if (JS_ValueToUint16(context, value, &i) && i <= UCHAR_MAX) { + g_value_set_uchar(gvalue, (unsigned char)i); + } else { + gjs_throw(context, + "Wrong type %s; unsigned char expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else if (gtype == G_TYPE_INT) { + gint32 i; + if (JS_ValueToInt32(context, value, &i)) { + g_value_set_int(gvalue, i); + } else { + gjs_throw(context, + "Wrong type %s; integer expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else if (gtype == G_TYPE_DOUBLE) { + gdouble d; + if (JS_ValueToNumber(context, value, &d)) { + g_value_set_double(gvalue, d); + } else { + gjs_throw(context, + "Wrong type %s; double expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else if (gtype == G_TYPE_FLOAT) { + gdouble d; + if (JS_ValueToNumber(context, value, &d)) { + g_value_set_float(gvalue, d); + } else { + gjs_throw(context, + "Wrong type %s; float expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else if (gtype == G_TYPE_UINT) { + guint32 i; + if (JS_ValueToECMAUint32(context, value, &i)) { + g_value_set_uint(gvalue, i); + } else { + gjs_throw(context, + "Wrong type %s; unsigned integer expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else if (gtype == G_TYPE_BOOLEAN) { + JSBool b; + + /* JS_ValueToBoolean() pretty much always succeeds, + * which is maybe surprising sometimes, but could + * be handy also... + */ + if (JS_ValueToBoolean(context, value, &b)) { + g_value_set_boolean(gvalue, b); + } else { + gjs_throw(context, + "Wrong type %s; boolean expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { + GObject *gobj; + + gobj = NULL; + if (JSVAL_IS_NULL(value)) { + /* nothing to do */ + } else if (JSVAL_IS_OBJECT(value)) { + JSObject *obj; + obj = JSVAL_TO_OBJECT(value); + gobj = gjs_g_object_from_object(context, obj); + } else { + gjs_throw(context, + "Wrong type %s; object %s expected", + gjs_get_type_name(value), + g_type_name(gtype)); + return JS_FALSE; + } + + g_value_set_object(gvalue, gobj); + } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { + void *gboxed; + + gboxed = NULL; + if (JSVAL_IS_NULL(value)) { + /* nothing to do */ + } else if (JSVAL_IS_OBJECT(value)) { + JSObject *obj; + obj = JSVAL_TO_OBJECT(value); + gboxed = gjs_g_boxed_from_boxed(context, obj); + } else { + gjs_throw(context, + "Wrong type %s; boxed type %s expected", + gjs_get_type_name(value), + g_type_name(gtype)); + return JS_FALSE; + } + + g_value_set_boxed(gvalue, gboxed); + } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { + if (JSVAL_IS_INT(value)) { + GEnumValue *v; + + v = g_enum_get_value(G_ENUM_CLASS(g_type_class_peek(gtype)), + JSVAL_TO_INT(value)); + if (v == NULL) { + gjs_throw(context, + "%d is not a valid value for enumeration %s", + JSVAL_TO_INT(value), g_type_name(gtype)); + return JS_FALSE; + } + + g_value_set_enum(gvalue, v->value); + } else { + gjs_throw(context, + "Wrong type %s; enum %s expected", + gjs_get_type_name(value), + g_type_name(gtype)); + return JS_FALSE; + } + } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) { + if (JSVAL_IS_INT(value)) { + if (!_gjs_flags_value_is_valid(context, + G_FLAGS_CLASS(g_type_class_peek(gtype)), + JSVAL_TO_INT(value))) + return JS_FALSE; + + g_value_set_flags(gvalue, value); + } else { + gjs_throw(context, + "Wrong type %s; flags %s expected", + gjs_get_type_name(value), + g_type_name(gtype)); + return JS_FALSE; + } + } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { + void *gparam; + + gparam = NULL; + if (JSVAL_IS_NULL(value)) { + /* nothing to do */ + } else if (JSVAL_IS_OBJECT(value)) { + JSObject *obj; + obj = JSVAL_TO_OBJECT(value); + gparam = gjs_g_param_from_param(context, obj); + } else { + gjs_throw(context, + "Wrong type %s; param type %s expected", + gjs_get_type_name(value), + g_type_name(gtype)); + return JS_FALSE; + } + + g_value_set_param(gvalue, gparam); + } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { + if (JSVAL_IS_NULL(value)) { + /* Nothing to do */ + } else { + gjs_throw(context, + "Cannot convert non-null JS value to G_POINTER"); + return JS_FALSE; + } + } else if (JSVAL_IS_NUMBER(value) && + g_value_type_transformable(G_TYPE_INT, gtype)) { + /* Only do this crazy gvalue transform stuff after we've + * exhausted everything else. Adding this for + * e.g. ClutterUnit. + */ + gint32 i; + if (JS_ValueToInt32(context, value, &i)) { + GValue int_value = { 0, }; + g_value_init(&int_value, G_TYPE_INT); + g_value_set_int(&int_value, i); + g_value_transform(&int_value, gvalue); + } else { + gjs_throw(context, + "Wrong type %s; integer expected", + gjs_get_type_name(value)); + return JS_FALSE; + } + } else { + gjs_debug(GJS_DEBUG_GCLOSURE, "jsval is number %d gtype fundamental %d transformable to int %d from int %d", + JSVAL_IS_NUMBER(value), + G_TYPE_IS_FUNDAMENTAL(gtype), + g_value_type_transformable(gtype, G_TYPE_INT), + g_value_type_transformable(G_TYPE_INT, gtype)); + + gjs_throw(context, + "Don't know how to convert JavaScript object to GType %s", + g_type_name(gtype)); + return JS_FALSE; + } + + return JS_TRUE; +} + +JSBool +gjs_value_from_g_value(JSContext *context, + jsval *value_p, + const GValue *gvalue) +{ + GType gtype; + + gtype = G_VALUE_TYPE(gvalue); + + gjs_debug_marshal(GJS_DEBUG_GCLOSURE, + "Converting gtype %s to jsval", + g_type_name(gtype)); + + if (gtype == G_TYPE_STRING) { + const char *v; + v = g_value_get_string(gvalue); + if (v == NULL) { + gjs_debug_marshal(GJS_DEBUG_GCLOSURE, + "Converting NULL string to JSVAL_NULL"); + *value_p = JSVAL_NULL; + } else { + if (!gjs_string_from_utf8(context, v, -1, value_p)) + return JS_FALSE; + } + } else if (gtype == G_TYPE_CHAR) { + char v; + v = g_value_get_char(gvalue); + *value_p = INT_TO_JSVAL(v); + } else if (gtype == G_TYPE_UCHAR) { + unsigned char v; + v = g_value_get_uchar(gvalue); + *value_p = INT_TO_JSVAL(v); + } else if (gtype == G_TYPE_INT) { + int v; + v = g_value_get_int(gvalue); + return JS_NewNumberValue(context, v, value_p); + } else if (gtype == G_TYPE_UINT) { + uint v; + v = g_value_get_uint(gvalue); + return JS_NewNumberValue(context, v, value_p); + } else if (gtype == G_TYPE_DOUBLE) { + double d; + d = g_value_get_double(gvalue); + return JS_NewNumberValue(context, d, value_p); + } else if (gtype == G_TYPE_FLOAT) { + double d; + d = g_value_get_float(gvalue); + return JS_NewNumberValue(context, d, value_p); + } else if (gtype == G_TYPE_BOOLEAN) { + gboolean v; + v = g_value_get_boolean(gvalue); + *value_p = BOOLEAN_TO_JSVAL(v); + } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { + GObject *gobj; + JSObject *obj; + + gobj = g_value_get_object(gvalue); + + obj = gjs_object_from_g_object(context, gobj); + *value_p = OBJECT_TO_JSVAL(obj); + } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { + void *gboxed; + JSObject *obj; + + gboxed = g_value_get_boxed(gvalue); + + obj = gjs_boxed_from_g_boxed(context, gtype, gboxed); + *value_p = OBJECT_TO_JSVAL(obj); + } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { + int v; + v = g_value_get_enum(gvalue); + *value_p = INT_TO_JSVAL(v); + } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { + GParamSpec *gparam; + JSObject *obj; + + gparam = g_value_get_param(gvalue); + + obj = gjs_param_from_g_param(context, gparam); + *value_p = OBJECT_TO_JSVAL(obj); + } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { + gpointer pointer; + + pointer = g_value_get_pointer(gvalue); + + if (pointer == NULL) { + *value_p = JSVAL_NULL; + } else { + gjs_throw(context, + "Can't convert non-null pointer to JS value"); + return JS_FALSE; + } + } else if (g_value_type_transformable(gtype, G_TYPE_DOUBLE)) { + GValue double_value = { 0, }; + double v; + g_value_init(&double_value, G_TYPE_DOUBLE); + g_value_transform(gvalue, &double_value); + v = g_value_get_double(&double_value); + return JS_NewNumberValue(context, v, value_p); + } else if (g_value_type_transformable(gtype, G_TYPE_INT)) { + GValue int_value = { 0, }; + int v; + g_value_init(&int_value, G_TYPE_INT); + g_value_transform(gvalue, &int_value); + v = g_value_get_int(&int_value); + return JS_NewNumberValue(context, v, value_p); + } else { + gjs_throw(context, + "Don't know how to convert GType %s to JavaScript object", + g_type_name(gtype)); + return JS_FALSE; + } + + return JS_TRUE; +} diff --git a/gi/value.h b/gi/value.h new file mode 100644 index 00000000..06f5a259 --- /dev/null +++ b/gi/value.h @@ -0,0 +1,45 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2008 LiTL, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __GJS_VALUE_H__ +#define __GJS_VALUE_H__ + +#include <glib-object.h> + +#include <jsapi.h> + +G_BEGIN_DECLS + +JSBool gjs_value_to_g_value (JSContext *context, + jsval value, + GValue *gvalue); +JSBool gjs_value_from_g_value (JSContext *context, + jsval *value_p, + const GValue *gvalue); +GClosure* gjs_closure_new_marshaled (JSContext *context, + JSObject *callable, + const char *description); + +G_END_DECLS + +#endif /* __GJS_VALUE_H__ */ |