gjs r2 - in trunk: . examples gi gjs modules scripts test util
- From: lucasr svn gnome org
- To: svn-commits-list gnome org
- Subject: gjs r2 - in trunk: . examples gi gjs modules scripts test util
- Date: Fri, 10 Oct 2008 21:37:40 +0000 (UTC)
Author: lucasr
Date: Fri Oct 10 21:37:39 2008
New Revision: 2
URL: http://svn.gnome.org/viewvc/gjs?rev=2&view=rev
Log:
Initial import.
Added:
trunk/AUTHORS
trunk/COPYING
trunk/ChangeLog
trunk/MAINTAINERS
trunk/Makefile-examples.am
trunk/Makefile-gi.am
trunk/Makefile-modules.am
trunk/Makefile-test.am
trunk/Makefile.am
trunk/NEWS
trunk/README
trunk/autogen.sh (contents, props changed)
trunk/configure.ac
trunk/examples/
trunk/examples/README
trunk/examples/clutter.js
trunk/examples/gtk.js
trunk/gi/
trunk/gi/arg.c
trunk/gi/arg.h
trunk/gi/boxed.c
trunk/gi/boxed.h
trunk/gi/closure.c
trunk/gi/closure.h
trunk/gi/enumeration.c
trunk/gi/enumeration.h
trunk/gi/function.c
trunk/gi/function.h
trunk/gi/keep-alive.c
trunk/gi/keep-alive.h
trunk/gi/native.c
trunk/gi/native.h
trunk/gi/ns.c
trunk/gi/ns.h
trunk/gi/object.c
trunk/gi/object.h
trunk/gi/param.c
trunk/gi/param.h
trunk/gi/repo.c
trunk/gi/repo.h
trunk/gi/value.c
trunk/gi/value.h
trunk/gjs/
trunk/gjs-1.0.pc.in
trunk/gjs/console.c
trunk/gjs/context-jsapi.h
trunk/gjs/context.c
trunk/gjs/context.h
trunk/gjs/gjs.h
trunk/gjs/importer.c
trunk/gjs/importer.h
trunk/gjs/jsapi-util-array.c
trunk/gjs/jsapi-util-error.c
trunk/gjs/jsapi-util-string.c
trunk/gjs/jsapi-util.c
trunk/gjs/jsapi-util.h
trunk/gjs/mem.c
trunk/gjs/mem.h
trunk/gjs/native.c
trunk/gjs/native.h
trunk/modules/
trunk/modules/gi.c
trunk/modules/gi.h
trunk/modules/lang.js
trunk/scripts/
trunk/scripts/make-tests (contents, props changed)
trunk/test/
trunk/test/gjs-tests.c
trunk/test/test.h
trunk/util/
trunk/util/dirs.c
trunk/util/dirs.h
trunk/util/error.c
trunk/util/error.h
trunk/util/glib.c
trunk/util/glib.h
trunk/util/log.c
trunk/util/log.h
trunk/util/misc.c
trunk/util/misc.h
Added: trunk/AUTHORS
==============================================================================
Added: trunk/COPYING
==============================================================================
--- (empty file)
+++ trunk/COPYING Fri Oct 10 21:37:39 2008
@@ -0,0 +1,19 @@
+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.
Added: trunk/MAINTAINERS
==============================================================================
--- (empty file)
+++ trunk/MAINTAINERS Fri Oct 10 21:37:39 2008
@@ -0,0 +1,15 @@
+Havoc Pennington
+E-mail: hp pobox com
+Userid: hp
+
+Johan Bilien
+E-mail: jobi via ecp fr
+Userid: jobi
+
+Lucas Rocha
+E-mail: lucasr gnome org
+Userid: lucasr
+
+Tommi Komulainen
+E-mail: tommi komulainen iki fi
+Userid: tko
Added: trunk/Makefile-examples.am
==============================================================================
--- (empty file)
+++ trunk/Makefile-examples.am Fri Oct 10 21:37:39 2008
@@ -0,0 +1,3 @@
+EXTRA_DIST += \
+ examples/clutter.js \
+ examples/gtk.js
Added: trunk/Makefile-gi.am
==============================================================================
--- (empty file)
+++ trunk/Makefile-gi.am Fri Oct 10 21:37:39 2008
@@ -0,0 +1,40 @@
+lib_LTLIBRARIES += \
+ libgjs-gi.la
+
+libgjs_gi_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GJS_CFLAGS) \
+ $(GOBJECT_INTROSPECTION_CFLAGS)
+libgjs_gi_la_LIBADD = \
+ $(GOBJECT_INTROSPECTION_LIBS) \
+ libgjs.la
+libgjs_gi_la_LDFLAGS = \
+ -export-symbols-regex "^[^_].*" -version-info 0:0:0 -rdynamic
+
+noinst_HEADERS += \
+ gi/arg.h \
+ gi/boxed.h \
+ gi/closure.h \
+ gi/enumeration.h \
+ gi/function.h \
+ gi/keep-alive.h \
+ gi/native.h \
+ gi/ns.h \
+ gi/object.h \
+ gi/param.h \
+ gi/repo.h \
+ gi/value.h
+
+libgjs_gi_la_SOURCES = \
+ gi/arg.c \
+ gi/boxed.c \
+ gi/closure.c \
+ gi/enumeration.c \
+ gi/function.c \
+ gi/keep-alive.c \
+ gi/native.c \
+ gi/ns.c \
+ gi/object.c \
+ gi/param.c \
+ gi/repo.c \
+ gi/value.c
Added: trunk/Makefile-modules.am
==============================================================================
--- (empty file)
+++ trunk/Makefile-modules.am Fri Oct 10 21:37:39 2008
@@ -0,0 +1,26 @@
+dist_gjsjs_DATA += modules/lang.js
+
+gjsnative_LTLIBRARIES += gi.la
+
+JS_NATIVE_MODULE_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GJS_CFLAGS)
+JS_NATIVE_MODULE_LIBADD = \
+ $(GJS_LIBS) \
+ libgjs.la
+JS_NATIVE_MODULE_LDFLAGS = \
+ -module -avoid-version -Wl,-z,defs -rdynamic
+
+gi_la_CFLAGS = \
+ $(JS_NATIVE_MODULE_CFLAGS) \
+ $(GOBJECT_INTROSPECTION_CFLAGS)
+gi_la_LIBADD = \
+ libgjs-gi.la \
+ $(JS_NATIVE_MODULE_LIBADD) \
+ $(GOBJECT_INTROSPECTION_LIBS)
+gi_la_LDFLAGS = \
+ $(JS_NATIVE_MODULE_LDFLAGS)
+
+gi_la_SOURCES = \
+ modules/gi.h \
+ modules/gi.c
Added: trunk/Makefile-test.am
==============================================================================
--- (empty file)
+++ trunk/Makefile-test.am Fri Oct 10 21:37:39 2008
@@ -0,0 +1,56 @@
+GTESTER = ${TESTS_ENVIRONMENT} gtester
+
+########################################################################
+TEST_PROGS += gjs-tests
+
+gjs_tests_CFLAGS = \
+ -include $(top_srcdir)/test/test.h \
+ $(AM_CFLAGS) \
+ $(GJSTESTS_CFLAGS) \
+ $(gjs_directory_defines) \
+ -I$(top_srcdir)/test
+
+## -rdynamic makes backtraces work
+gjs_tests_LDFLAGS = -rdynamic
+gjs_tests_LDADD = \
+ $(GJSTESTS_LIBS) \
+ libgjs.la
+
+gjs_tests_SOURCES = \
+ test/gjs-tests.c \
+ test/test.h \
+ $(gjstest_files_with_tests)
+
+nodist_gjs_tests_SOURCES = \
+ gjstest.c \
+ gjstest.h
+
+## make-tests always updates the ".stamp" files, but only modifies the
+## actual gjstest.[hc] if they change. make-tests creates both
+## .h.stamp and .c.stamp but if we listed both, make would run
+## make-tests twice.
+gjstest.h.stamp : scripts/make-tests $(gjstest_files_with_tests)
+ $(TESTS_ENVIRONMENT) $(top_srcdir)/scripts/make-tests $(builddir) $(gjstest_files_with_tests)
+
+gjstest.h gjstest.c : gjstest.h.stamp
+ @true
+
+BUILT_SOURCES += $(nodist_gjs_tests_SOURCES)
+CLEANFILES += \
+ $(nodist_gjs_tests_SOURCES) \
+ gjstest.c.stamp \
+ gjstest.h.stamp
+
+EXTRA_DIST += \
+ scripts/make-tests
+
+########################################################################
+TESTS_ENVIRONMENT = \
+ abs_top_srcdir="$(abs_top_srcdir)" \
+ LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):$(FIREFOX_JS_LIBDIR)"
+
+test: ${TEST_PROGS}
+ @test -z "${TEST_PROGS}" || ${GTESTER} --verbose ${TEST_PROGS}
+
+check: test
+
Added: trunk/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/Makefile.am Fri Oct 10 21:37:39 2008
@@ -0,0 +1,100 @@
+bin_PROGRAMS =
+lib_LTLIBRARIES =
+noinst_LTLIBRARIES =
+dist_gjsjs_DATA =
+gjsnative_LTLIBRARIES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST =
+gjstest_files_with_tests =
+TEST_PROGS =
+check_PROGRAMS = $(TEST_PROGS)
+
+gjsjsdir = @gjsjsdir@
+gjsnativedir = @gjsnativedir@
+
+gjsincludedir = $(includedir)/gjs-1.0
+
+########################################################################
+nobase_gjsinclude_HEADERS = \
+ gjs/context.h \
+ gjs/gjs.h \
+ gjs/jsapi-util.h \
+ gjs/native.h
+noinst_HEADERS = \
+ gjs/context-jsapi.h \
+ gjs/importer.h \
+ gjs/mem.h \
+ util/dirs.h \
+ util/error.h \
+ util/glib.h \
+ util/log.h \
+ util/misc.h
+
+########################################################################
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = gjs-1.0.pc
+
+EXTRA_DIST += \
+ gjs-1.0.pc.in
+
+########################################################################
+gjs_directory_defines = \
+ -DGJS_TOP_SRCDIR=\"$(abs_top_srcdir)\" \
+ -DGJS_BUILDDIR=\"$(abs_top_builddir)\" \
+ -DGJS_JS_DIR=\"$(gjsjsdir)\" \
+ -DGJS_NATIVE_DIR=\"$(gjsnativedir)\"
+
+########################################################################
+lib_LTLIBRARIES += libgjs.la
+
+libgjs_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(gjs_directory_defines)
+libgjs_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GJS_CFLAGS)
+libgjs_la_LDFLAGS = \
+ -export-symbols-regex "^[^_]" -version-info 0:0:0 \
+ -R $(FIREFOX_JS_LIBDIR) -rdynamic
+libgjs_la_LIBADD = \
+ $(GJS_LIBS)
+
+libgjs_la_SOURCES = \
+ gjs/context.c \
+ gjs/importer.c \
+ gjs/jsapi-util.c \
+ gjs/jsapi-util-array.c \
+ gjs/jsapi-util-error.c \
+ gjs/jsapi-util-string.c \
+ gjs/mem.c \
+ gjs/native.c \
+ util/dirs.c \
+ util/error.c \
+ util/glib.c \
+ util/log.c \
+ util/misc.c
+
+gjstest_files_with_tests += \
+ gjs/jsapi-util-array.c \
+ gjs/jsapi-util-error.c \
+ gjs/jsapi-util-string.c \
+ util/dirs.c \
+ util/glib.c
+
+include Makefile-gi.am
+include Makefile-modules.am
+include Makefile-examples.am
+########################################################################
+bin_PROGRAMS += gjs-console
+
+gjs_console_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GJS_CFLAGS)
+gjs_console_LDADD = \
+ libgjs.la
+
+gjs_console_SOURCES = gjs/console.c
+
+
+include Makefile-test.am
Added: trunk/NEWS
==============================================================================
Added: trunk/README
==============================================================================
Added: trunk/autogen.sh
==============================================================================
--- (empty file)
+++ trunk/autogen.sh Fri Oct 10 21:37:39 2008
@@ -0,0 +1,44 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+PKG_NAME="gjs"
+REQUIRED_AUTOCONF_VERSION=2.53
+REQUIRED_AUTOMAKE_VERSION=1.7.2
+
+(test -f $srcdir/configure.ac \
+ && test -f $srcdir/autogen.sh) || {
+ echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
+ echo " top-level $PKG_NAME directory"
+ exit 1
+}
+
+DIE=0
+
+# This is a bit complicated here since we can't use gnome-config yet.
+# It'll be easier after switching to pkg-config since we can then
+# use pkg-config to find the gnome-autogen.sh script.
+
+gnome_autogen=
+gnome_datadir=
+
+ifs_save="$IFS"; IFS=":"
+for dir in $PATH ; do
+ test -z "$dir" && dir=.
+ if test -f $dir/gnome-autogen.sh ; then
+ gnome_autogen="$dir/gnome-autogen.sh"
+ gnome_datadir=`echo $dir | sed -e 's,/bin$,/share,'`
+ break
+ fi
+done
+IFS="$ifs_save"
+
+if test -z "$gnome_autogen" ; then
+ echo "You need to install the gnome-common module and make"
+ echo "sure the gnome-autogen.sh script is in your \$PATH."
+ exit 1
+fi
+
+GNOME_DATADIR="$gnome_datadir" USE_GNOME2_MACROS=1 . $gnome_autogen
Added: trunk/configure.ac
==============================================================================
--- (empty file)
+++ trunk/configure.ac Fri Oct 10 21:37:39 2008
@@ -0,0 +1,90 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.61)
+AC_INIT([gjs], [0.1], BUG-REPORT-ADDRESS)
+AM_INIT_AUTOMAKE
+AC_CONFIG_SRCDIR([gjs/console.c])
+AC_CONFIG_HEADER([config.h])
+
+GETTEXT_PACKAGE=gjs
+AC_SUBST([GETTEXT_PACKAGE])
+AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"], [The name of the gettext domain])
+
+AM_MAINTAINER_MODE
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_ISC_POSIX
+AC_HEADER_STDC
+
+# no stupid static libraries
+AM_DISABLE_STATIC
+# avoid libtool for LTCOMPILE, use it only to link
+AC_PROG_LIBTOOL
+dnl DOLT
+
+# Checks for libraries.
+m4_define(gobject_required_version, 2.16.0)
+
+## spidermonkey .pc file name varies across distributions
+PKG_CHECK_EXISTS([firefox-js], [JS_PACKAGE=firefox-js],
+ [PKG_CHECK_EXISTS([xulrunner-js], [JS_PACKAGE=xulrunner-js], [JS_PACKAGE=mozilla-js])])
+
+PKG_CHECK_MODULES(JS, $JS_PACKAGE)
+AC_SUBST(JS_PACKAGE)
+AC_CHECK_LIB([mozjs], [JS_CallTracer], :,
+ [AC_MSG_ERROR([SpiderMonkey is too old, Firefox 3 is required])],
+ [$JS_LIBS])
+
+gjs_packages="gmodule-2.0 gobject-2.0 >= gobject_required_version $JS_PACKAGE"
+gjstests_packages="$gjstests_packages $gjs_packages"
+PKG_CHECK_MODULES([GJS], [$gjs_packages])
+
+## some flavors of Firefox .pc only set sdkdir, not libdir
+FIREFOX_JS_SDKDIR=`$PKG_CONFIG --variable=sdkdir $JS_PACKAGE`
+FIREFOX_JS_LIBDIR=`$PKG_CONFIG --variable=libdir $JS_PACKAGE`
+
+## Ubuntu does not set libdir in mozilla-js.pc
+if test x"$FIREFOX_JS_LIBDIR" = x ; then
+ ## Ubuntu returns xulrunner-devel as the sdkdir, but for the
+ ## libdir we want the runtime location on the target system,
+ ## so can't use -devel.
+ ## The library is in the non-devel directory also.
+ ## Don't ask me why it's in two places.
+ FIREFOX_JS_LIBDIR=`echo "$FIREFOX_JS_SDKDIR" | sed -e 's/-devel//g'`
+
+ if ! test -d "$FIREFOX_JS_LIBDIR" ; then
+ FIREFOX_JS_LIBDIR=
+ fi
+fi
+
+if test x"$FIREFOX_JS_LIBDIR" = x ; then
+ AC_MSG_ERROR([Could not figure out where Firefox JavaScript library lives])
+fi
+
+AC_SUBST(FIREFOX_JS_LIBDIR)
+
+## workaround for Ubuntu Hardy bug where mozilla-js.pc gives CFLAGS
+## -I.../stable while jsapi.h is in .../unstable
+js_include_dir=`$PKG_CONFIG --variable=includedir $JS_PACKAGE`/unstable
+GJS_CFLAGS="$GJS_CFLAGS -I$js_include_dir"
+AC_SUBST([js_include_dir])
+
+
+PKG_CHECK_MODULES([GOBJECT_INTROSPECTION], [gobject-introspection-1.0])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+gjsjsdir="\${datadir}/gjs-1.0"
+gjsnativedir="\${libdir}/gjs-1.0"
+AC_SUBST([gjsjsdir])
+AC_SUBST([gjsnativedir])
+
+# gjs-tests links against everything
+PKG_CHECK_MODULES([GJSTESTS], [$gjstests_packages])
+GJSTESTS_CFLAGS="$GJSTESTS_CFLAGS -I$js_include_dir"
+
+AC_CONFIG_FILES([Makefile gjs-1.0.pc])
+AC_OUTPUT
Added: trunk/examples/README
==============================================================================
--- (empty file)
+++ trunk/examples/README Fri Oct 10 21:37:39 2008
@@ -0,0 +1,3 @@
+In order to run those example scripts, do:
+
+ gjs-console script-filename.js
Added: trunk/examples/clutter.js
==============================================================================
--- (empty file)
+++ trunk/examples/clutter.js Fri Oct 10 21:37:39 2008
@@ -0,0 +1,12 @@
+const Clutter = imports.gi.clutter;
+
+Clutter.init(null, null);
+
+let stage = new Clutter.Stage();
+
+let texture = new Clutter.Texture({ filename: '' });
+
+stage.add_actor(texture);
+stage.show();
+
+Clutter.main();
Added: trunk/examples/gtk.js
==============================================================================
--- (empty file)
+++ trunk/examples/gtk.js Fri Oct 10 21:37:39 2008
@@ -0,0 +1,71 @@
+const Gtk = imports.gi.Gtk;
+
+// This is a callback function. The data arguments are ignored
+// in this example. More on callbacks below.
+function hello(widget) {
+ log("Hello World");
+}
+
+function delete_event(widget, event) {
+ // If you return FALSE in the "delete_event" signal handler,
+ // GTK will emit the "destroy" signal. Returning TRUE means
+ // you don't want the window to be destroyed.
+ // This is useful for popping up 'are you sure you want to quit?'
+ // type dialogs.
+ log("delete event occurred");
+
+ // Change FALSE to TRUE and the main window will not be destroyed
+ // with a "delete_event".
+ return false;
+}
+
+function destroy(widget) {
+ log("destroy signal occurred");
+ Gtk.main_quit();
+}
+
+Gtk.init(0, null);
+
+// create a new window
+let win = new Gtk.Window({ type: Gtk.WindowType.toplevel });
+
+// When the window is given the "delete_event" signal (this is given
+// by the window manager, usually by the "close" option, or on the
+// titlebar), we ask it to call the delete_event () function
+// as defined above.
+win.connect("delete-event", delete_event);
+
+// Here we connect the "destroy" event to a signal handler.
+// This event occurs when we call gtk_widget_destroy() on the window,
+// or if we return FALSE in the "delete_event" callback.
+win.connect("destroy", destroy);
+
+// Sets the border width of the window.
+win.set_border_width(10)
+
+// Creates a new button with the label "Hello World".
+let button = new Gtk.Button({ label: "Hello World" });
+
+// When the button receives the "clicked" signal, it will call the
+// function hello(). The hello() function is defined above.
+button.connect("clicked", hello);
+
+// This will cause the window to be destroyed by calling
+// gtk_widget_destroy(window) when "clicked". Again, the destroy
+// signal could come from here, or the window manager.
+button.connect("clicked", function() {
+ win.destroy();
+ });
+
+// This packs the button into the window (a GTK container).
+win.add(button)
+
+// The final step is to display this newly created widget.
+button.show()
+
+// and the window
+win.show()
+
+// All gtk applications must have a Gtk.main(). Control ends here
+// and waits for an event to occur (like a key press or mouse event).
+Gtk.main();
Added: trunk/gi/arg.c
==============================================================================
--- (empty file)
+++ trunk/gi/arg.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/arg.h
==============================================================================
--- (empty file)
+++ trunk/gi/arg.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/boxed.c
==============================================================================
--- (empty file)
+++ trunk/gi/boxed.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/boxed.h
==============================================================================
--- (empty file)
+++ trunk/gi/boxed.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/closure.c
==============================================================================
--- (empty file)
+++ trunk/gi/closure.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/closure.h
==============================================================================
--- (empty file)
+++ trunk/gi/closure.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/enumeration.c
==============================================================================
--- (empty file)
+++ trunk/gi/enumeration.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/enumeration.h
==============================================================================
--- (empty file)
+++ trunk/gi/enumeration.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/function.c
==============================================================================
--- (empty file)
+++ trunk/gi/function.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/function.h
==============================================================================
--- (empty file)
+++ trunk/gi/function.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/keep-alive.c
==============================================================================
--- (empty file)
+++ trunk/gi/keep-alive.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/keep-alive.h
==============================================================================
--- (empty file)
+++ trunk/gi/keep-alive.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/native.c
==============================================================================
--- (empty file)
+++ trunk/gi/native.c Fri Oct 10 21:37:39 2008
@@ -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);
+ }
+}
+
Added: trunk/gi/native.h
==============================================================================
--- (empty file)
+++ trunk/gi/native.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/ns.c
==============================================================================
--- (empty file)
+++ trunk/gi/ns.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/ns.h
==============================================================================
--- (empty file)
+++ trunk/gi/ns.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/object.c
==============================================================================
--- (empty file)
+++ trunk/gi/object.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/object.h
==============================================================================
--- (empty file)
+++ trunk/gi/object.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/param.c
==============================================================================
--- (empty file)
+++ trunk/gi/param.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/param.h
==============================================================================
--- (empty file)
+++ trunk/gi/param.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/repo.c
==============================================================================
--- (empty file)
+++ trunk/gi/repo.c Fri Oct 10 21:37:39 2008
@@ -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);
+}
Added: trunk/gi/repo.h
==============================================================================
--- (empty file)
+++ trunk/gi/repo.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gi/value.c
==============================================================================
--- (empty file)
+++ trunk/gi/value.c Fri Oct 10 21:37:39 2008
@@ -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;
+}
Added: trunk/gi/value.h
==============================================================================
--- (empty file)
+++ trunk/gi/value.h Fri Oct 10 21:37:39 2008
@@ -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__ */
Added: trunk/gjs-1.0.pc.in
==============================================================================
--- (empty file)
+++ trunk/gjs-1.0.pc.in Fri Oct 10 21:37:39 2008
@@ -0,0 +1,20 @@
+prefix= prefix@
+exec_prefix= exec_prefix@
+libdir= libdir@
+bindir= bindir@
+includedir= includedir@
+datarootdir= datarootdir@
+datadir= datadir@
+js_include_dir= js_include_dir@
+
+gjs_console=${bindir}/gjs-console
+jsdir= gjsjsdir@
+jsnativedir= gjsnativedir@
+
+Cflags: -I${includedir}/gjs-1.0 -I${js_include_dir}
+Requires: gobject-introspection-1.0 @JS_PACKAGE@
+Libs: -L${libdir} -lgjs
+
+Name: gjs-1.0
+Description: JS bindings for GObjects
+Version: @VERSION@
Added: trunk/gjs/console.c
==============================================================================
--- (empty file)
+++ trunk/gjs/console.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,92 @@
+/* -*- 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 <stdlib.h>
+
+#include <util/log.h>
+#include <gjs/context.h>
+#include <gjs/mem.h>
+
+static char **include_path = NULL;
+
+static GOptionEntry entries[] = {
+ { "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, &include_path, "Add the directory DIR to the list of directories to search for js files.", "DIR" },
+ { NULL }
+};
+
+int
+main(int argc, char **argv)
+{
+ GOptionContext *context;
+ GError *error = NULL;
+ GjsContext *js_context;
+ char *script;
+ gsize len;
+ int code;
+
+ context = g_option_context_new(NULL);
+ g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE);
+ if (!g_option_context_parse(context, &argc, &argv, &error))
+ g_error("option parsing failed: %s", error->message);
+
+ if (argc < 2) {
+ /* FIXME add interpretation of stdin, and REPL */
+ g_printerr("Specify a script to run on the command line\n");
+ exit(1);
+ }
+
+ g_type_init();
+
+ error = NULL;
+ if (!g_file_get_contents(argv[1], &script, &len, &error)) {
+ g_printerr("%s\n", error->message);
+ exit(1);
+ }
+
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Creating new context to eval console script");
+ js_context = gjs_context_new_with_search_path(include_path);
+
+ /* prepare command line arguments */
+ if (!gjs_context_define_string_array(js_context, "ARGV",
+ argc - 2, (const char**)argv + 2,
+ &error)) {
+ g_printerr("Failed to defined ARGV: %s", error->message);
+ exit(1);
+ }
+
+ /* evaluate the script */
+ error = NULL;
+ if (!gjs_context_eval(js_context, script, len,
+ argv[1], &code, &error)) {
+ g_printerr("%s\n", error->message);
+ exit(1);
+ }
+
+ gjs_memory_report("before destroying context", FALSE);
+ g_object_unref(js_context);
+ gjs_memory_report("after destroying context", TRUE);
+
+ exit(code);
+}
Added: trunk/gjs/context-jsapi.h
==============================================================================
--- (empty file)
+++ trunk/gjs/context-jsapi.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,38 @@
+/* -*- 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_CONTEXT_JSAPI_H__
+#define __GJS_CONTEXT_JSAPI_H__
+
+#include <gjs/context.h>
+#include <gjs/jsapi-util.h>
+
+G_BEGIN_DECLS
+
+JSContext* gjs_context_get_context (GjsContext *js_context);
+gboolean gjs_context_is_load_context (GjsContext *js_context);
+
+
+G_END_DECLS
+
+#endif /* __GJS_CONTEXT_JSAPI_H__ */
Added: trunk/gjs/context.c
==============================================================================
--- (empty file)
+++ trunk/gjs/context.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,624 @@
+/* -*- 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 "context.h"
+#include "context-jsapi.h"
+#include "importer.h"
+#include "jsapi-util.h"
+
+#include <util/log.h>
+#include <util/error.h>
+
+#include <string.h>
+
+#include <jsapi.h>
+
+static void gjs_context_dispose (GObject *object);
+static void gjs_context_finalize (GObject *object);
+static GObject* gjs_context_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params);
+static void gjs_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gjs_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+struct _GjsContext {
+ GObject parent;
+
+ JSRuntime *runtime;
+ JSContext *context;
+ JSObject *global;
+
+ char **search_path;
+
+ unsigned int we_own_runtime : 1;
+ unsigned int is_load_context : 1;
+};
+
+struct _GjsContextClass {
+ GObjectClass parent;
+};
+
+G_DEFINE_TYPE(GjsContext, gjs_context, G_TYPE_OBJECT);
+
+#if 0
+enum {
+ LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+#endif
+
+enum {
+ PROP_0,
+ PROP_SEARCH_PATH,
+ PROP_RUNTIME,
+ PROP_IS_LOAD_CONTEXT
+};
+
+static JSBool
+gjs_log(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ char *s;
+ JSExceptionState *exc_state;
+ JSString *jstr;
+
+ if (argc != 1) {
+ gjs_throw(context, "Must pass a single argument to log()");
+ return JS_FALSE;
+ }
+
+ /* JS_ValueToString might throw, in which we will only
+ *log that the value could be converted to string */
+ exc_state = JS_SaveExceptionState(context);
+ jstr = JS_ValueToString(context, argv[0]);
+ if (jstr != NULL)
+ argv[0] = STRING_TO_JSVAL(jstr); // GC root
+ JS_RestoreExceptionState(context, exc_state);
+
+ if (jstr == NULL) {
+ gjs_debug(GJS_DEBUG_LOG, "<cannot convert value to string>");
+ return JS_TRUE;
+ }
+
+ if (!gjs_string_to_utf8(context, STRING_TO_JSVAL(jstr), &s))
+ return JS_FALSE;
+
+ gjs_debug(GJS_DEBUG_LOG, "%s", s);
+ g_free(s);
+
+ return JS_TRUE;
+}
+
+static JSBool
+gjs_log_error(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ char *s;
+ JSExceptionState *exc_state;
+ JSString *jstr;
+ jsval exc;
+
+ if (argc != 2) {
+ gjs_throw(context, "Must pass an exception and message string to logError()");
+ return JS_FALSE;
+ }
+
+ exc = argv[0];
+
+ /* JS_ValueToString might throw, in which we will only
+ *log that the value could be converted to string */
+ exc_state = JS_SaveExceptionState(context);
+ jstr = JS_ValueToString(context, argv[1]);
+ if (jstr != NULL)
+ argv[1] = STRING_TO_JSVAL(jstr); // GC root
+ JS_RestoreExceptionState(context, exc_state);
+
+ if (jstr == NULL) {
+ gjs_debug(GJS_DEBUG_ERROR, "<cannot convert value to string>");
+ gjs_log_exception_props(context, exc);
+ return JS_TRUE;
+ }
+
+ if (!gjs_string_to_utf8(context, STRING_TO_JSVAL(jstr), &s))
+ return JS_FALSE;
+
+ gjs_debug(GJS_DEBUG_ERROR, "%s", s);
+ gjs_log_exception_props(context, exc);
+ g_free(s);
+
+ return JS_TRUE;
+}
+
+static JSClass global_class = {
+ "GjsGlobal", JSCLASS_GLOBAL_FLAGS,
+ JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+ JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+static void
+gjs_context_init(GjsContext *js_context)
+{
+
+}
+
+static void
+gjs_context_class_init(GjsContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *pspec;
+
+ object_class->dispose = gjs_context_dispose;
+ object_class->finalize = gjs_context_finalize;
+
+ object_class->constructor = gjs_context_constructor;
+ object_class->get_property = gjs_context_get_property;
+ object_class->set_property = gjs_context_set_property;
+
+ pspec = g_param_spec_pointer("search-path",
+ "Search path",
+ "Path where modules to import should reside",
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_property(object_class,
+ PROP_SEARCH_PATH,
+ pspec);
+
+ pspec = g_param_spec_pointer("runtime",
+ "JSRuntime",
+ "A runtime to use instead of creating our own",
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_property(object_class,
+ PROP_RUNTIME,
+ pspec);
+
+ pspec = g_param_spec_boolean("is-load-context",
+ "IsLoadContext",
+ "Whether this is the load context",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_property(object_class,
+ PROP_IS_LOAD_CONTEXT,
+ pspec);
+}
+
+static void
+gjs_context_dispose(GObject *object)
+{
+ GjsContext *js_context;
+
+ js_context = GJS_CONTEXT(object);
+
+ if (js_context->global != NULL) {
+ JS_RemoveRoot(js_context->context, &js_context->global);
+ js_context->global = NULL;
+ }
+
+ if (js_context->context != NULL) {
+
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Destroying JS context%s",
+ js_context->is_load_context ? " (load context)" : "");
+
+ JS_DestroyContext(js_context->context);
+ js_context->context = NULL;
+ }
+
+ if (js_context->runtime != NULL) {
+ if (js_context->we_own_runtime) {
+ /* Avoid keeping JSContext with a dangling pointer to the
+ * runtime.
+ */
+ gjs_runtime_clear_call_context(js_context->runtime);
+ gjs_runtime_clear_load_context(js_context->runtime);
+
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Destroying JS runtime");
+
+ JS_DestroyRuntime(js_context->runtime);
+
+ /* finalize the dataset from jsapi-util.c ... for
+ * "foreign" runtimes this just never happens for
+ * now... we do this after the runtime itself is destroyed
+ * because we might have finalizers run by
+ * JS_DestroyRuntime() that rely on data we've set on the
+ * runtime, such as the dynamic class structs.
+ */
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Destroying any remaining dataset items on runtime");
+
+ g_dataset_destroy(js_context->runtime);
+ }
+ js_context->runtime = NULL;
+ }
+
+ G_OBJECT_CLASS(gjs_context_parent_class)->dispose(object);
+}
+
+static void
+gjs_context_finalize(GObject *object)
+{
+ GjsContext *js_context;
+
+ js_context = GJS_CONTEXT(object);
+
+ if (js_context->search_path != NULL) {
+ g_strfreev(js_context->search_path);
+ js_context->search_path = NULL;
+ }
+
+ G_OBJECT_CLASS(gjs_context_parent_class)->finalize(object);
+}
+
+static GObject*
+gjs_context_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ GjsContext *js_context;
+
+ object = (* G_OBJECT_CLASS (gjs_context_parent_class)->constructor) (type,
+ n_construct_properties,
+ construct_params);
+
+ js_context = GJS_CONTEXT(object);
+
+ if (js_context->runtime == NULL) {
+ js_context->runtime = JS_NewRuntime(1024*1024 /* max bytes */);
+ if (js_context->runtime == NULL)
+ gjs_fatal("Failed to create javascript runtime");
+ js_context->we_own_runtime = TRUE;
+ }
+
+ js_context->context = JS_NewContext(js_context->runtime, 8192 /* stack chunk size */);
+ if (js_context->context == NULL)
+ gjs_fatal("Failed to create javascript context");
+
+ /* JSOPTION_DONT_REPORT_UNCAUGHT: Don't send exceptions to our
+ * error report handler; instead leave them set. This allows us
+ * to get at the exception object.
+ *
+ * JSOPTION_STRICT: Report warnings to error reporter function.
+ */
+ JS_SetOptions(js_context->context,
+ JS_GetOptions(js_context->context) |
+ JSOPTION_DONT_REPORT_UNCAUGHT |
+ JSOPTION_STRICT);
+
+ JS_SetErrorReporter(js_context->context, gjs_error_reporter);
+
+ /* set ourselves as the private data */
+ JS_SetContextPrivate(js_context->context, js_context);
+
+ /* get all the fancy new language features */
+#define OUR_JS_VERSION JSVERSION_1_8
+ if (JS_GetVersion(js_context->context) != OUR_JS_VERSION) {
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Changing JavaScript version to %s from %s",
+ JS_VersionToString(OUR_JS_VERSION),
+ JS_VersionToString(JS_GetVersion(js_context->context)));
+
+ JS_SetVersion(js_context->context, OUR_JS_VERSION);
+ }
+
+ js_context->global = JS_NewObject(js_context->context, &global_class, NULL, NULL);
+ if (js_context->global == NULL)
+ gjs_fatal("Failed to create javascript global object");
+
+ /* Sets global object and adds builtins to it */
+ if (!JS_InitStandardClasses(js_context->context, js_context->global))
+ gjs_fatal("Failed to init standard javascript classes");
+
+ if (!JS_DefineProperty(js_context->context, js_context->global,
+ "window", OBJECT_TO_JSVAL(js_context->global),
+ NULL, NULL,
+ JSPROP_READONLY | JSPROP_PERMANENT))
+ gjs_fatal("No memory to export global object as 'window'");
+
+ /* this is probably not necessary, having it as global object in
+ * context already roots it presumably? Could not find where it
+ * does in a quick glance through spidermonkey source though.
+ */
+ if (!JS_AddRoot(js_context->context, &js_context->global))
+ gjs_fatal("No memory to add global object as GC root");
+
+ /* Define a global function called log() */
+ if (!JS_DefineFunction(js_context->context, js_context->global,
+ "log",
+ gjs_log,
+ 1, GJS_MODULE_PROP_FLAGS))
+ gjs_fatal("Failed to define log function");
+
+ if (!JS_DefineFunction(js_context->context, js_context->global,
+ "logError",
+ gjs_log_error,
+ 2, GJS_MODULE_PROP_FLAGS))
+ gjs_fatal("Failed to define logError function");
+
+ /* If we created the root importer in the load context,
+ * there would be infinite recursion since the load context
+ * is a GjsContext
+ */
+ if (!js_context->is_load_context) {
+ /* We create the global-to-runtime root importer with the
+ * passed-in search path. If someone else already created
+ * the root importer, this is a no-op.
+ */
+ if (!gjs_create_root_importer(js_context->runtime,
+ js_context->search_path ?
+ (const char**) js_context->search_path :
+ NULL,
+ TRUE))
+ gjs_fatal("Failed to create root importer");
+
+ /* Now copy the global root importer (which we just created,
+ * if it didn't exist) to our global object
+ */
+ if (!gjs_define_root_importer(js_context->context,
+ js_context->global,
+ "imports"))
+ gjs_fatal("Failed to point 'imports' property at root importer");
+ }
+
+ return object;
+}
+
+static void
+gjs_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GjsContext *js_context;
+
+ js_context = GJS_CONTEXT (object);
+
+ switch (prop_id) {
+ case PROP_IS_LOAD_CONTEXT:
+ g_value_set_boolean(value, js_context->is_load_context);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gjs_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GjsContext *js_context;
+
+ js_context = GJS_CONTEXT (object);
+
+ switch (prop_id) {
+ case PROP_SEARCH_PATH:
+ js_context->search_path = g_strdupv(g_value_get_pointer(value));
+ break;
+ case PROP_RUNTIME:
+ js_context->runtime = g_value_get_pointer(value);
+ break;
+ case PROP_IS_LOAD_CONTEXT:
+ js_context->is_load_context = g_value_get_boolean(value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+GjsContext*
+gjs_context_new(void)
+{
+ return g_object_new (GJS_TYPE_CONTEXT, NULL);
+}
+
+GjsContext*
+gjs_context_new_with_search_path(char** search_path)
+{
+ return g_object_new (GJS_TYPE_CONTEXT,
+ "search-path", search_path,
+ NULL);
+}
+
+JSContext*
+gjs_context_get_context(GjsContext *js_context)
+{
+ return js_context->context;
+}
+
+gboolean
+gjs_context_is_load_context(GjsContext *js_context)
+{
+ return js_context->is_load_context;
+}
+
+gboolean
+gjs_context_eval(GjsContext *js_context,
+ const char *script,
+ gssize script_len,
+ const char *filename,
+ int *exit_status_p,
+ GError **error)
+{
+ jsval retval;
+ gboolean success;
+
+ g_object_ref(G_OBJECT(js_context));
+
+ if (exit_status_p)
+ *exit_status_p = 1; /* "Failure" (like a shell script) */
+
+ /* whether we evaluated the script OK; not related to whether
+ * script returned nonzero. We set GError if success = FALSE
+ */
+ success = TRUE;
+
+ /* log and clear exception if it's set (should not be, normally...) */
+ if (gjs_log_exception(js_context->context,
+ NULL)) {
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Exception was set prior to JS_EvaluateScript()");
+ }
+
+ retval = JSVAL_VOID;
+ if (!JS_EvaluateScript(js_context->context,
+ js_context->global,
+ script,
+ script_len >= 0 ? script_len : (gssize) strlen(script),
+ filename,
+ 1, /* line number */
+ &retval)) {
+ char *message;
+
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Script evaluation failed");
+
+ /* if message is NULL then somehow exception wasn't set */
+ message = NULL;
+ gjs_log_exception(js_context->context,
+ &message);
+ if (message) {
+ g_set_error(error,
+ GJS_ERROR,
+ GJS_ERROR_FAILED,
+ "%s", message);
+ g_free(message);
+ } else {
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "JS_EvaluateScript() failed but no exception message?");
+ }
+
+ success = FALSE;
+ }
+
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Script evaluation succeeded");
+
+ if (gjs_log_exception(js_context->context, NULL)) {
+ g_set_error(error,
+ GJS_ERROR,
+ GJS_ERROR_FAILED,
+ "Exception was set even though JS_EvaluateScript() returned true - did you gjs_throw() but not return false somewhere perhaps?");
+ success = FALSE;
+ }
+
+ if (success && exit_status_p) {
+ if (JSVAL_IS_INT(retval)) {
+ int code;
+ if (JS_ValueToInt32(js_context->context, retval, &code)) {
+
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Script returned integer code %d", code);
+
+ *exit_status_p = code;
+ }
+ } else {
+ /* Assume success if no integer was returned */
+ *exit_status_p = 0;
+ }
+ }
+
+ g_object_unref(G_OBJECT(js_context));
+
+ return success;
+}
+
+gboolean
+gjs_context_eval_file(GjsContext *js_context,
+ const char *filename,
+ int *exit_status_p,
+ GError **error)
+{
+ char *script;
+ gsize script_len;
+
+ if (!g_file_get_contents(filename, &script, &script_len, error))
+ return FALSE;
+
+ if (!gjs_context_eval(js_context, script, script_len, filename, exit_status_p, error)) {
+ g_free(script);
+ return FALSE;
+ }
+
+ g_free(script);
+ return TRUE;
+}
+
+gboolean
+gjs_context_define_string_array(GjsContext *js_context,
+ const char *array_name,
+ gssize array_length,
+ const char **array_values,
+ GError **error)
+{
+ if (!gjs_define_string_array(js_context->context,
+ js_context->global,
+ array_name, array_length, array_values,
+ JSPROP_READONLY | JSPROP_PERMANENT)) {
+ char *message;
+
+ message = NULL;
+ gjs_log_exception(js_context->context, &message);
+ if (message) {
+ g_set_error(error,
+ GJS_ERROR,
+ GJS_ERROR_FAILED,
+ "%s", message);
+ g_free(message);
+ } else {
+ message = "gjs_define_string_array() failed but no exception message?";
+ gjs_debug(GJS_DEBUG_CONTEXT, "%s", message);
+ g_set_error(error,
+ GJS_ERROR,
+ GJS_ERROR_FAILED,
+ "%s", message);
+ }
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
Added: trunk/gjs/context.h
==============================================================================
--- (empty file)
+++ trunk/gjs/context.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,67 @@
+/* -*- 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_CONTEXT_H__
+#define __GJS_CONTEXT_H__
+
+#if !defined(__GJS_GJS_H__)
+#warning Include <gjs/gjs.h> instead of <gjs/context.h>
+#endif
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GjsContext GjsContext;
+typedef struct _GjsContextClass GjsContextClass;
+
+#define GJS_TYPE_CONTEXT (gjs_context_get_type ())
+#define GJS_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GJS_TYPE_CONTEXT, GjsContext))
+#define GJS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GJS_TYPE_CONTEXT, GjsContextClass))
+#define GJS_IS_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GJS_TYPE_CONTEXT))
+#define GJS_IS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GJS_TYPE_CONTEXT))
+#define GJS_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GJS_TYPE_CONTEXT, GjsContextClass))
+
+GType gjs_context_get_type (void) G_GNUC_CONST;
+
+GjsContext* gjs_context_new (void);
+GjsContext* gjs_context_new_with_search_path (char **search_path);
+gboolean gjs_context_eval_file (GjsContext *js_context,
+ const char *filename,
+ int *exit_status_p,
+ GError **error);
+gboolean gjs_context_eval (GjsContext *js_context,
+ const char *script,
+ gssize script_len,
+ const char *filename,
+ int *exit_status_p,
+ GError **error);
+gboolean gjs_context_define_string_array (GjsContext *js_context,
+ const char *array_name,
+ gssize array_length,
+ const char **array_values,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GJS_CONTEXT_H__ */
Added: trunk/gjs/gjs.h
==============================================================================
--- (empty file)
+++ trunk/gjs/gjs.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,31 @@
+/* -*- 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_GJS_H__
+#define __GJS_GJS_H__
+
+#include <gjs/context.h>
+#include <gjs/jsapi-util.h>
+#include <gjs/native.h>
+
+#endif /* __GJS_GJS_H__ */
Added: trunk/gjs/importer.c
==============================================================================
--- (empty file)
+++ trunk/gjs/importer.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,928 @@
+/* -*- 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 <util/dirs.h>
+#include <util/glib.h>
+
+#include <jsapi.h>
+
+#include "importer.h"
+#include "jsapi-util.h"
+#include "mem.h"
+#include "native.h"
+
+#include <string.h>
+
+typedef struct {
+ void *dummy;
+} Importer;
+
+typedef struct {
+ GPtrArray *elements;
+ unsigned int index;
+} ImporterIterator;
+
+static struct JSClass gjs_importer_class;
+
+GJS_DEFINE_PRIV_FROM_JS(Importer, gjs_importer_class)
+
+static JSBool
+define_meta_properties(JSContext *context,
+ JSObject *module_obj,
+ const char *module_name,
+ JSObject *parent)
+{
+ gboolean parent_is_module;
+
+ /* We define both __moduleName__ and __parentModule__ to null
+ * on the root importer
+ */
+ parent_is_module = JS_InstanceOf(context, parent, &gjs_importer_class, NULL);
+
+ gjs_debug(GJS_DEBUG_IMPORTER, "Defining parent %p of %p '%s' is mod %d",
+ parent, module_obj, module_name ? module_name : "<root>", parent_is_module);
+
+ if (!JS_DefineProperty(context, module_obj,
+ "__moduleName__",
+ parent_is_module ?
+ STRING_TO_JSVAL(JS_NewStringCopyZ(context, module_name)) :
+ JSVAL_NULL,
+ NULL, NULL,
+ /* don't set ENUMERATE since we wouldn't want to copy
+ * this symbol to any other object for example.
+ */
+ JSPROP_READONLY | JSPROP_PERMANENT))
+ return JS_FALSE;
+
+ if (!JS_DefineProperty(context, module_obj,
+ "__parentModule__",
+ parent_is_module ? OBJECT_TO_JSVAL(parent) : JSVAL_NULL,
+ NULL, NULL,
+ /* don't set ENUMERATE since we wouldn't want to copy
+ * this symbol to any other object for example.
+ */
+ JSPROP_READONLY | JSPROP_PERMANENT))
+ return JS_FALSE;
+
+ return JS_TRUE;
+}
+
+static JSBool
+import_directory(JSContext *context,
+ JSObject *obj,
+ const char *name,
+ const char **full_paths)
+{
+ JSObject *importer;
+
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Importing directory '%s'",
+ name);
+
+ /* We define a sub-importer that has only the given directories on
+ * its search path. gjs_define_importer() exits if it fails, so
+ * this always succeeds.
+ */
+ importer = gjs_define_importer(context, obj, name, full_paths, FALSE);
+ if (importer == NULL)
+ return JS_FALSE;
+
+ return JS_TRUE;
+}
+
+static JSBool
+finish_import(JSContext *context,
+ const char *name)
+{
+ if (JS_IsExceptionPending(context)) {
+ /* I am not sure whether this can happen, but if it does we want to trap it.
+ */
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Module '%s' reported an exception but gjs_import_native_module() returned TRUE",
+ name);
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+finish_import_and_define(JSContext *context,
+ JSObject *obj,
+ JSObject *module_obj,
+ const char *name)
+{
+ if (!finish_import(context, name))
+ return JS_FALSE;
+
+ if (!JS_DefineProperty(context, obj,
+ name, OBJECT_TO_JSVAL(module_obj),
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS)) {
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Failed to define '%s' in importer",
+ name);
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+import_native_file(JSContext *context,
+ JSObject *obj,
+ const char *name,
+ const char *full_path)
+{
+ JSObject *module_obj;
+ GjsNativeFlags flags;
+ JSBool retval = JS_FALSE;
+
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Importing '%s'", full_path);
+
+ module_obj = JS_ConstructObject(context, NULL, NULL, NULL);
+ if (module_obj == NULL) {
+ return JS_FALSE;
+ }
+
+ JS_AddRoot(context, &module_obj);
+
+ if (!define_meta_properties(context, module_obj, name, obj))
+ goto out;
+
+ if (!gjs_import_native_module(context, module_obj, full_path, &flags))
+ goto out;
+
+ if (flags & GJS_NATIVE_SUPPLIES_MODULE_OBJ) {
+ /* In this case module_obj just gets garbage collected,
+ * we don't end up using it.
+ */
+ if (!finish_import(context, name))
+ goto out;
+ } else {
+ if (!finish_import_and_define(context, obj, module_obj, name))
+ goto out;
+ }
+
+ retval = JS_TRUE;
+
+ out:
+ JS_RemoveRoot(context, &module_obj);
+
+ return retval;
+}
+
+static JSBool
+import_file(JSContext *context,
+ JSObject *obj,
+ const char *name,
+ const char *full_path)
+{
+ char *script;
+ gsize script_len;
+ JSObject *module_obj;
+ GError *error;
+ jsval retval;
+
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Importing '%s'", full_path);
+
+ module_obj = JS_ConstructObject(context, NULL, NULL, NULL);
+ if (module_obj == NULL) {
+ return JS_FALSE;
+ }
+
+ if (!define_meta_properties(context, module_obj, name, obj))
+ return JS_FALSE;
+
+ script = NULL;
+ script_len = 0;
+
+ error = NULL;
+ if (!g_file_get_contents(full_path, &script, &script_len, &error)) {
+ gjs_throw(context, "Could not open %s: %s", full_path, error->message);
+ g_error_free(error);
+ return JS_FALSE;
+ }
+
+ g_assert(script != NULL);
+
+ if (!JS_EvaluateScript(context,
+ module_obj,
+ script,
+ script_len,
+ full_path,
+ 1, /* line number */
+ &retval)) {
+ g_free(script);
+
+ /* If JSOPTION_DONT_REPORT_UNCAUGHT is set then the exception
+ * would be left set after the evaluate and not go to the error
+ * reporter function.
+ */
+ if (JS_IsExceptionPending(context)) {
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Module '%s' left an exception set",
+ name);
+ gjs_log_and_keep_exception(context, NULL);
+ } else {
+ gjs_throw(context,
+ "JS_EvaluateScript() returned FALSE but did not set exception");
+ }
+
+ return JS_FALSE;
+ }
+
+ g_free(script);
+
+ if (!finish_import_and_define(context, obj, module_obj, name))
+ return JS_FALSE;
+
+ return JS_TRUE;
+}
+
+static JSBool
+do_import(JSContext *context,
+ JSObject *obj,
+ Importer *priv,
+ const char *name)
+{
+ char *filename;
+ char *native_filename;
+ char *full_path;
+ char *dirname = NULL;
+ jsval search_path_val;
+ JSObject *search_path;
+ jsuint search_path_len;
+ jsuint i;
+ JSBool result;
+ GPtrArray *directories;
+
+ if (!gjs_object_require_property(context, obj, "searchPath", &search_path_val)) {
+ return JS_FALSE;
+ }
+
+ if (!JSVAL_IS_OBJECT(search_path_val)) {
+ gjs_throw(context, "searchPath property on importer is not an object");
+ return JS_FALSE;
+ }
+
+ search_path = JSVAL_TO_OBJECT(search_path_val);
+
+ if (!JS_IsArrayObject(context, search_path)) {
+ gjs_throw(context, "searchPath property on importer is not an array");
+ return JS_FALSE;
+ }
+
+ if (!JS_GetArrayLength(context, search_path, &search_path_len)) {
+ gjs_throw(context, "searchPath array has no length");
+ return JS_FALSE;
+ }
+
+ result = JS_FALSE;
+
+ filename = g_strdup_printf("%s.js", name);
+ native_filename = g_strdup_printf("%s.so", name);
+ full_path = NULL;
+ directories = NULL;
+
+ for (i = 0; i < search_path_len; ++i) {
+ jsval elem;
+
+ elem = JSVAL_VOID;
+ if (!JS_GetElement(context, search_path, i, &elem)) {
+ /* this means there was an exception, while elem == JSVAL_VOID
+ * means no element found
+ */
+ goto out;
+ }
+
+ if (elem == JSVAL_VOID)
+ continue;
+
+ if (!JSVAL_IS_STRING(elem)) {
+ gjs_throw(context, "importer searchPath contains non-string");
+ goto out;
+ }
+
+ if (!gjs_string_to_utf8(context, elem, &dirname))
+ goto out; /* Error message already set */
+
+ /* First try importing a directory (a sub-importer) */
+ if (full_path)
+ g_free(full_path);
+ full_path = g_build_filename(dirname, name,
+ NULL);
+
+ if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) {
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Adding directory '%s' to child importer '%s'",
+ full_path, name);
+ if (directories == NULL) {
+ directories = g_ptr_array_new();
+ }
+ g_ptr_array_add(directories, full_path);
+ /* don't free it twice - pass ownership to ptr array */
+ full_path = NULL;
+ }
+
+ /* If we just added to directories, we know we don't need to
+ * check for a file. If we added to directories on an earlier
+ * iteration, we want to ignore any files later in the
+ * path. So, always skip the rest of the loop block if we have
+ * directories.
+ */
+ if (directories != NULL) {
+ continue;
+ }
+
+ /* Second, if it's not a directory, try importing a file */
+ g_free(full_path);
+ full_path = g_build_filename(dirname, filename,
+ NULL);
+
+ if (g_file_test(full_path, G_FILE_TEST_EXISTS)) {
+ if (import_file(context, obj, name, full_path)) {
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "successfully imported module '%s'", name);
+ result = JS_TRUE;
+ }
+
+ /* Don't keep searching path if we fail to load the file for
+ * reasons other than it doesn't exist... i.e. broken files
+ * block searching for nonbroken ones
+ */
+ goto out;
+ }
+
+ /* Finally see if it's a native module */
+ g_free(full_path);
+ full_path = g_build_filename(dirname, native_filename,
+ NULL);
+ if (g_file_test(full_path, G_FILE_TEST_EXISTS)) {
+ if (import_native_file(context, obj, name, full_path)) {
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "successfully imported module '%s'", name);
+ result = JS_TRUE;
+ }
+
+ /* Don't keep searching path if we fail to load the file for
+ * reasons other than it doesn't exist... i.e. broken files
+ * block searching for nonbroken ones
+ */
+ goto out;
+ }
+
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "JS import '%s' not found in %s",
+ name, dirname);
+ }
+
+ if (directories != NULL) {
+ /* NULL-terminate the char** */
+ g_ptr_array_add(directories, NULL);
+
+ if (import_directory(context, obj, name,
+ (const char**) directories->pdata)) {
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "successfully imported directory '%s'", name);
+ result = JS_TRUE;
+ }
+ }
+
+ out:
+ if (directories != NULL) {
+ char **str_array;
+
+ /* NULL-terminate the char**
+ * (maybe for a second time, but doesn't matter)
+ */
+ g_ptr_array_add(directories, NULL);
+
+ str_array = (char**) directories->pdata;
+ g_ptr_array_free(directories, FALSE);
+ g_strfreev(str_array);
+ }
+
+ g_free(full_path);
+ g_free(filename);
+ g_free(native_filename);
+ g_free(dirname);
+
+ if (!result &&
+ !JS_IsExceptionPending(context)) {
+ /* If no exception occurred, the problem is just that we got to the
+ * end of the path. Be sure an exception is set.
+ */
+ gjs_throw(context, "No JS module '%s' found in search path", name);
+ }
+
+ return result;
+}
+
+static ImporterIterator *
+importer_iterator_new()
+{
+ ImporterIterator *iter;
+
+ iter = g_slice_new0(ImporterIterator);
+
+ iter->elements = g_ptr_array_new();
+ iter->index = 0;
+
+ return iter;
+}
+
+static void
+importer_iterator_free(ImporterIterator *iter)
+{
+ g_ptr_array_foreach(iter->elements, (GFunc)g_free, NULL);
+ g_ptr_array_free(iter->elements, TRUE);
+ g_slice_free(ImporterIterator, iter);
+}
+
+/*
+ * Like JSEnumerateOp, but enum provides contextual information as follows:
+ *
+ * JSENUMERATE_INIT: allocate private enum struct in state_p, return number
+ * of elements in *id_p
+ * JSENUMERATE_NEXT: return next property id in *id_p, and if no new property
+ * free state_p and set to JSVAL_NULL
+ * JSENUMERATE_DESTROY : destroy state_p
+ *
+ * Note that in a for ... in loop, this will be called first on the object,
+ * then on its prototype.
+ *
+ */
+static JSBool
+importer_new_enumerate(JSContext *context,
+ JSObject *object,
+ JSIterateOp enum_op,
+ jsval *state_p,
+ jsid *id_p)
+{
+ ImporterIterator *iter;
+
+ switch (enum_op) {
+ case JSENUMERATE_INIT: {
+ Importer *priv;
+ JSObject *search_path;
+ jsval search_path_val;
+ jsuint search_path_len;
+ jsuint i;
+
+ if (state_p)
+ *state_p = JSVAL_NULL;
+
+ if (id_p)
+ *id_p = JSVAL_ZERO;
+
+ priv = priv_from_js(context, object);
+ if (!priv)
+ /* we are enumerating the prototype properties */
+ return JS_TRUE;
+
+ if (!gjs_object_require_property(context, object, "searchPath", &search_path_val))
+ return JS_FALSE;
+
+ if (!JSVAL_IS_OBJECT(search_path_val)) {
+ gjs_throw(context, "searchPath property on importer is not an object");
+ return JS_FALSE;
+ }
+
+ search_path = JSVAL_TO_OBJECT(search_path_val);
+
+ if (!JS_IsArrayObject(context, search_path)) {
+ gjs_throw(context, "searchPath property on importer is not an array");
+ return JS_FALSE;
+ }
+
+ if (!JS_GetArrayLength(context, search_path, &search_path_len)) {
+ gjs_throw(context, "searchPath array has no length");
+ return JS_FALSE;
+ }
+
+ iter = importer_iterator_new();
+
+ for (i = 0; i < search_path_len; ++i) {
+ char *dirname = NULL;
+ const char *filename;
+ jsval elem;
+ GDir *dir = NULL;
+
+ elem = JSVAL_VOID;
+ if (!JS_GetElement(context, search_path, i, &elem)) {
+ /* this means there was an exception, while elem == JSVAL_VOID
+ * means no element found
+ */
+ importer_iterator_free(iter);
+ return JS_FALSE;
+ }
+
+ if (elem == JSVAL_VOID)
+ continue;
+
+ if (!JSVAL_IS_STRING(elem)) {
+ gjs_throw(context, "importer searchPath contains non-string");
+ importer_iterator_free(iter);
+ return JS_FALSE;
+ }
+
+ if (!gjs_string_to_utf8(context, elem, &dirname)) {
+ importer_iterator_free(iter);
+ return JS_FALSE; /* Error message already set */
+ }
+
+ dir = g_dir_open(dirname, 0, NULL);
+
+ if (!dir) {
+ g_free(dirname);
+ continue;
+ }
+
+ while ((filename = g_dir_read_name(dir))) {
+ char *full_path = g_build_filename(dirname, filename, NULL);
+
+ if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) {
+ g_ptr_array_add(iter->elements, g_strdup(filename));
+ } else {
+ if (g_str_has_suffix(filename, ".so") ||
+ g_str_has_suffix(filename, ".js")) {
+ g_ptr_array_add(iter->elements,
+ g_strndup(filename, strlen(filename) - 3));
+ }
+ }
+
+ g_free(full_path);
+ }
+ g_dir_close(dir);
+
+ g_free(dirname);
+ }
+
+ if (state_p)
+ *state_p = PRIVATE_TO_JSVAL(iter);
+
+ if (id_p)
+ *id_p = INT_TO_JSVAL(iter->elements->len);
+
+ break;
+ }
+
+ case JSENUMERATE_NEXT: {
+ jsval element_val;
+
+ if (!state_p) {
+ gjs_throw(context, "Enumerate with no iterator set?");
+ return JS_FALSE;
+ }
+
+ iter = JSVAL_TO_PRIVATE(*state_p);
+
+ if (iter->index < iter->elements->len) {
+ if (!gjs_string_from_utf8(context,
+ g_ptr_array_index(iter->elements,
+ iter->index++),
+ -1,
+ &element_val))
+ return JS_FALSE;
+
+ if (!JS_ValueToId(context, element_val, id_p))
+ return JS_FALSE;
+
+ break;
+ }
+ /* else fall through to destroying the iterator */
+ }
+
+ case JSENUMERATE_DESTROY: {
+ if (state_p && *state_p != JSVAL_NULL) {
+ iter = JSVAL_TO_PRIVATE(*state_p);
+
+ importer_iterator_free(iter);
+
+ *state_p = JSVAL_NULL;
+ }
+ }
+ }
+
+ 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 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
+importer_new_resolve(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp)
+{
+ Importer *priv;
+ const char *name;
+ JSContext *load_context;
+
+ *objp = NULL;
+
+ name = gjs_string_get_ascii(id);
+
+ /* let Object.prototype resolve these */
+ if (strcmp(name, "valueOf") == 0 ||
+ strcmp(name, "toString") == 0 ||
+ strcmp(name, "__iterator__") == 0)
+ return JS_TRUE;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_jsprop(GJS_DEBUG_IMPORTER, "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 */
+
+ /* We always import in the special load context. */
+ load_context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+ if (do_import(load_context, obj, priv, name)) {
+ *objp = obj;
+ return JS_TRUE;
+ } else {
+ /* Move the exception to the calling context from load context.
+ */
+ if (!gjs_move_exception(load_context, context)) {
+ /* set an exception since none was set */
+ gjs_throw(context, "No exception was set, but import failed somehow");
+ }
+ 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
+importer_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ Importer *priv;
+
+ priv = g_slice_new0(Importer);
+
+ GJS_INC_COUNTER(importer);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+ JS_SetPrivate(context, obj, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_IMPORTER,
+ "importer constructor, obj %p priv %p", obj, priv);
+
+ return JS_TRUE;
+}
+
+static void
+importer_finalize(JSContext *context,
+ JSObject *obj)
+{
+ Importer *priv;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_IMPORTER,
+ "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(importer);
+ g_slice_free(Importer, 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_importer_class = {
+ "GjsFileImporter",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START |
+ JSCLASS_NEW_ENUMERATE,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ (JSEnumerateOp) importer_new_enumerate, /* needs cast since it's the new enumerate signature */
+ (JSResolveOp) importer_new_resolve, /* needs cast since it's the new resolve signature */
+ JS_ConvertStub,
+ importer_finalize,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+static JSPropertySpec gjs_importer_proto_props[] = {
+ { NULL }
+};
+
+static JSFunctionSpec gjs_importer_proto_funcs[] = {
+ { NULL }
+};
+
+static JSObject*
+importer_new(JSContext *context)
+{
+ JSObject *importer;
+ Importer *priv;
+ JSObject *global;
+
+ global = JS_GetGlobalObject(context);
+
+ if (!gjs_object_has_property(context, global, gjs_importer_class.name)) {
+ JSObject *prototype;
+ prototype = JS_InitClass(context, global,
+ /* parent prototype JSObject* for
+ * prototype; NULL for
+ * Object.prototype
+ */
+ NULL,
+ &gjs_importer_class,
+ /* constructor for instances (NULL for
+ * none - just name the prototype like
+ * Math - rarely correct)
+ */
+ importer_constructor,
+ /* number of constructor args */
+ 0,
+ /* props of prototype */
+ &gjs_importer_proto_props[0],
+ /* funcs of prototype */
+ &gjs_importer_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_importer_class.name);
+
+ g_assert(gjs_object_has_property(context, global, gjs_importer_class.name));
+
+ gjs_debug(GJS_DEBUG_IMPORTER, "Initialized class %s prototype %p",
+ gjs_importer_class.name, prototype);
+ }
+
+ importer = JS_ConstructObject(context, &gjs_importer_class, NULL, NULL);
+ if (importer == NULL)
+ gjs_fatal("No memory to create ns object");
+
+ priv = priv_from_js(context, importer);
+
+ return importer;
+}
+
+JSObject*
+gjs_define_importer(JSContext *context,
+ JSObject *in_object,
+ const char *importer_name,
+ const char **initial_search_path,
+ gboolean add_standard_search_path)
+{
+ JSObject *importer;
+ char **paths[3] = {0};
+ char **search_path;
+
+ paths[0] = (char**)initial_search_path;
+ if (add_standard_search_path) {
+ /* Stick the "standard" shared search path after the provided one. */
+ paths[1] = gjs_get_search_path(GJS_DIRECTORY_SHARED_JAVASCRIPT);
+ paths[2] = gjs_get_search_path(GJS_DIRECTORY_SHARED_JAVASCRIPT_NATIVE);
+ }
+
+ search_path = gjs_g_strv_concat(paths, 3);
+ g_strfreev(paths[1]);
+ g_strfreev(paths[2]);
+
+ importer = importer_new(context);
+
+ /* API users can replace this property from JS, is the idea */
+ if (!gjs_define_string_array(context, importer,
+ "searchPath", -1, (const char **)search_path,
+ /* settable (no READONLY) but not deleteable (PERMANENT) */
+ JSPROP_PERMANENT | JSPROP_ENUMERATE))
+ gjs_fatal("no memory to define importer search path prop");
+
+ g_strfreev(search_path);
+
+ if (!define_meta_properties(context, importer, importer_name, in_object))
+ gjs_fatal("failed to define meta properties on importer");
+
+ if (!JS_DefineProperty(context, in_object,
+ importer_name, OBJECT_TO_JSVAL(importer),
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS))
+ gjs_fatal("no memory to define importer property");
+
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Defined importer '%s' %p in %p", importer_name, importer, in_object);
+
+ return importer;
+}
+
+/* If this were called twice for the same runtime with different args it
+ * would basically be a bug, but checking for that is a lot of code so
+ * we just ignore all calls after the first and hope the args are the same.
+ */
+JSBool
+gjs_create_root_importer(JSRuntime *runtime,
+ const char **initial_search_path,
+ gboolean add_standard_search_path)
+{
+ JSContext *context;
+
+ context = gjs_runtime_get_load_context(runtime);
+
+ if (!gjs_object_has_property(context,
+ JS_GetGlobalObject(context),
+ "imports")) {
+ if (gjs_define_importer(context, JS_GetGlobalObject(context),
+ "imports",
+ initial_search_path, add_standard_search_path) == NULL)
+ return JS_FALSE;
+ } else {
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Someone else already created root importer, ignoring second request");
+ return JS_TRUE;
+ }
+
+ return JS_TRUE;
+}
+
+JSBool
+gjs_define_root_importer(JSContext *context,
+ JSObject *in_object,
+ const char *importer_name)
+{
+ JSContext *load_context;
+ jsval value;
+
+ load_context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+
+ if (!gjs_object_require_property(load_context,
+ JS_GetGlobalObject(load_context),
+ "imports", &value) ||
+ !JSVAL_IS_OBJECT(value)) {
+ gjs_debug(GJS_DEBUG_IMPORTER, "Root importer did not exist, couldn't get from load context; must create it");
+ return JS_FALSE;
+ }
+
+ if (!JS_DefineProperty(context, in_object,
+ importer_name, value,
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS)) {
+ gjs_debug(GJS_DEBUG_IMPORTER, "DefineProperty %s on %p failed",
+ importer_name, in_object);
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
Added: trunk/gjs/importer.h
==============================================================================
--- (empty file)
+++ trunk/gjs/importer.h Fri Oct 10 21:37:39 2008
@@ -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_IMPORTER_H__
+#define __GJS_IMPORTER_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_create_root_importer (JSRuntime *runtime,
+ const char **initial_search_path,
+ gboolean add_standard_search_path);
+JSBool gjs_define_root_importer (JSContext *context,
+ JSObject *in_object,
+ const char *importer_name);
+JSObject* gjs_define_importer (JSContext *context,
+ JSObject *in_object,
+ const char *importer_name,
+ const char **initial_search_path,
+ gboolean add_standard_search_path);
+
+
+G_END_DECLS
+
+#endif /* __GJS_IMPORTER_H__ */
Added: trunk/gjs/jsapi-util-array.c
==============================================================================
--- (empty file)
+++ trunk/gjs/jsapi-util-array.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,336 @@
+/* -*- 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 "jsapi-util.h"
+
+/* Maximum number of elements allowed in a GArray of rooted jsvals.
+ * We pre-alloc that amount and then never allow the array to grow,
+ * or we'd have invalid memory rooted if the internals of GArray decide
+ * to move the contents to a new memory area
+ */
+#define ARRAY_MAX_LEN 32
+
+/**
+ * gjs_rooted_array_new:
+ *
+ * Creates an opaque data type that holds jsvals and keeps
+ * their location (NOT their value) GC-rooted.
+ *
+ * Returns: an opaque object prepared to hold GC root locations.
+ **/
+GjsRootedArray*
+gjs_rooted_array_new()
+{
+ GArray *array;
+
+ /* we prealloc ARRAY_MAX_LEN to avoid realloc */
+ array = g_array_sized_new(FALSE, /* zero-terminated */
+ FALSE, /* clear */
+ sizeof(jsval), /* element size */
+ ARRAY_MAX_LEN); /* reserved size */
+
+ return (GjsRootedArray*) array;
+}
+
+/* typesafe wrapper */
+static void
+add_root_jsval(JSContext *context,
+ jsval *value_p)
+{
+ JS_AddRoot(context, value_p);
+}
+
+/* typesafe wrapper */
+static void
+remove_root_jsval(JSContext *context,
+ jsval *value_p)
+{
+ JS_RemoveRoot(context, value_p);
+}
+
+/**
+ * gjs_rooted_array_append:
+ * @context: a #JSContext
+ * @array: a #GjsRootedArray created by gjs_rooted_array_new()
+ * @value: a jsval
+ *
+ * Appends @jsval to @array, calling JS_AddRoot on the location where it's stored.
+ *
+ **/
+void
+gjs_rooted_array_append(JSContext *context,
+ GjsRootedArray *array,
+ jsval value)
+{
+ GArray *garray;
+
+ g_return_if_fail(context != NULL);
+ g_return_if_fail(array != NULL);
+
+ garray = (GArray*) array;
+
+ if (garray->len >= ARRAY_MAX_LEN) {
+ gjs_throw(context, "Maximum number of values (%d)",
+ ARRAY_MAX_LEN);
+ return;
+ }
+
+ g_array_append_val(garray, value);
+ add_root_jsval(context, & g_array_index(garray, jsval, garray->len - 1));
+}
+
+/**
+ * gjs_rooted_array_get:
+ * @context: a #JSContext
+ * @array: an array
+ * @i: element to return
+ * Returns: value of an element
+ */
+jsval
+gjs_rooted_array_get(JSContext *context,
+ GjsRootedArray *array,
+ int i)
+{
+ GArray *garray;
+
+ g_return_val_if_fail(context != NULL, JSVAL_VOID);
+ g_return_val_if_fail(array != NULL, JSVAL_VOID);
+
+ garray = (GArray*) array;
+
+ if (i < 0 || i >= (int) garray->len) {
+ gjs_throw(context, "Index %d is out of range", i);
+ return JSVAL_VOID;
+ }
+
+ return g_array_index(garray, jsval, i);
+}
+
+/**
+ * gjs_rooted_array_get_data:
+ *
+ * @context: a #JSContext
+ * @array: an array
+ * Returns: the rooted jsval locations in the array
+ */
+jsval*
+gjs_rooted_array_get_data(JSContext *context,
+ GjsRootedArray *array)
+{
+ GArray *garray;
+
+ g_return_val_if_fail(context != NULL, NULL);
+ g_return_val_if_fail(array != NULL, NULL);
+
+ garray = (GArray*) array;
+
+ return (jsval*) garray->data;
+}
+
+/**
+ * gjs_rooted_array_get_length:
+ *
+ * @context: a #JSContext
+ * @array: an array
+ * Returns: number of jsval in the rooted array
+ */
+int
+gjs_rooted_array_get_length (JSContext *context,
+ GjsRootedArray *array)
+{
+ GArray *garray;
+
+ g_return_val_if_fail(context != NULL, 0);
+ g_return_val_if_fail(array != NULL, 0);
+
+ garray = (GArray*) array;
+
+ return garray->len;
+}
+
+/**
+ * gjs_root_value_locations:
+ * @context: a #JSContext
+ * @locations: contiguous locations in memory that store jsvals (must be initialized)
+ * @n_locations: the number of locations to root
+ *
+ * Calls JS_AddRoot() on each address in @locations.
+ *
+ **/
+void
+gjs_root_value_locations(JSContext *context,
+ jsval *locations,
+ int n_locations)
+{
+ int i;
+
+ g_return_if_fail(context != NULL);
+ g_return_if_fail(locations != NULL);
+ g_return_if_fail(n_locations >= 0);
+
+ for (i = 0; i < n_locations; i++) {
+ add_root_jsval(context, ((jsval*)locations) + i);
+ }
+}
+
+/**
+ * gjs_unroot_value_locations:
+ * @context: a #JSContext
+ * @locations: contiguous locations in memory that store jsvals and have been added as GC roots
+ * @n_locations: the number of locations to unroot
+ *
+ * Calls JS_RemoveRoot() on each address in @locations.
+ *
+ **/
+void
+gjs_unroot_value_locations(JSContext *context,
+ jsval *locations,
+ int n_locations)
+{
+ int i;
+
+ g_return_if_fail(context != NULL);
+ g_return_if_fail(locations != NULL);
+ g_return_if_fail(n_locations >= 0);
+
+ for (i = 0; i < n_locations; i++) {
+ remove_root_jsval(context, ((jsval*)locations) + i);
+ }
+}
+
+/**
+ * gjs_set_values:
+ * @context: a #JSContext
+ * @locations: array of jsval
+ * @n_locations: the number of elements to set
+ * @initializer: what to set each element to
+ *
+ * Assigns initializer to each member of the given array.
+ *
+ **/
+void
+gjs_set_values(JSContext *context,
+ jsval *locations,
+ int n_locations,
+ jsval initializer)
+{
+ int i;
+
+ g_return_if_fail(context != NULL);
+ g_return_if_fail(locations != NULL);
+ g_return_if_fail(n_locations >= 0);
+
+ for (i = 0; i < n_locations; i++) {
+ locations[i] = initializer;
+ }
+}
+
+/**
+ * gjs_rooted_array_free:
+ * @context: a #JSContext
+ * @array: a #GjsRootedArray created with gjs_rooted_array_new()
+ * @free_segment: whether or not to free and unroot the internal jsval array
+ *
+ * Frees the memory allocated for the #GjsRootedArray. If @free_segment is
+ * %TRUE the internal memory block allocated for the jsval array will
+ * be freed and unrooted also.
+ *
+ * Returns: the jsval array if it was not freed
+ **/
+jsval*
+gjs_rooted_array_free(JSContext *context,
+ GjsRootedArray *array,
+ gboolean free_segment)
+{
+ GArray *garray;
+
+ g_return_val_if_fail(context != NULL, NULL);
+ g_return_val_if_fail(array != NULL, NULL);
+
+ garray = (GArray*) array;
+
+ if (free_segment)
+ gjs_unroot_value_locations(context, (jsval*) garray->data, garray->len);
+
+ return (jsval*) g_array_free(garray, free_segment);
+}
+
+#if GJS_BUILD_TESTS
+#include <string.h>
+
+static void
+test_error_reporter(JSContext *context,
+ const char *message,
+ JSErrorReport *report)
+{
+ g_printerr("error reported by test: %s\n", message);
+}
+
+#define N_ELEMS 15
+
+void
+gjstest_test_func_gjs_jsapi_util_array(void)
+{
+ JSRuntime *runtime;
+ JSContext *context;
+ JSObject *global;
+ GjsRootedArray *array;
+ int i;
+ jsval value;
+
+ runtime = JS_NewRuntime(1024*1024 /* max bytes */);
+ context = JS_NewContext(runtime, 8192);
+ global = JS_NewObject(context, NULL, NULL, NULL);
+ JS_SetGlobalObject(context, global);
+ JS_InitStandardClasses(context, global);
+
+ JS_SetErrorReporter(context, test_error_reporter);
+
+ array = gjs_rooted_array_new();
+
+ for (i = 0; i < N_ELEMS; i++) {
+ value = STRING_TO_JSVAL(JS_NewStringCopyZ(context, "abcdefghijk"));
+ gjs_rooted_array_append(context, array, value);
+ }
+
+ JS_ClearNewbornRoots(context);
+ JS_GC(context);
+
+ for (i = 0; i < N_ELEMS; i++) {
+ const char *ascii;
+
+ value = gjs_rooted_array_get(context, array, i);
+ g_assert(JSVAL_IS_STRING(value));
+ ascii = JS_GetStringBytes(JSVAL_TO_STRING(value));
+ /* if the string was freed, hopefully this will fail
+ * even if we didn't crash yet
+ */
+ g_assert(strcmp(ascii, "abcdefghijk") == 0);
+ }
+
+ gjs_rooted_array_free(context, array, TRUE);
+}
+
+#endif /* GJS_BUILD_TESTS */
Added: trunk/gjs/jsapi-util-error.c
==============================================================================
--- (empty file)
+++ trunk/gjs/jsapi-util-error.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,247 @@
+/* -*- 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 "jsapi-util.h"
+
+#include <util/log.h>
+
+#include <string.h>
+
+/*
+ * See:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=166436
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=215173
+ *
+ * Very surprisingly, jsapi.h lacks any way to "throw new Error()"
+ *
+ * So here is an awful hack inspired by
+ * http://egachine.berlios.de/embedding-sm-best-practice/embedding-sm-best-practice.html#error-handling
+ */
+static void
+gjs_throw_valist(JSContext *context,
+ const char *format,
+ va_list args)
+{
+ JSString* jstr;
+ char *s;
+ jsval retval;
+ jsval argv[1];
+ JSFunction *func;
+ const char *body;
+ JSBool result;
+ const char *names[] = { "message" };
+ guint options;
+
+ s = g_strdup_vprintf(format, args);
+
+ if (JS_IsExceptionPending(context)) {
+ /* Often it's unclear whether a given jsapi.h function
+ * will throw an exception, so we will throw ourselves
+ * "just in case"; in those cases, we don't want to
+ * overwrite an exception that already exists.
+ * (Do log in case our second exception adds more info,
+ * but don't log as topic ERROR because if the exception is
+ * caught we don't want an ERROR in the logs.)
+ */
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Ignoring second exception: '%s'",
+ s);
+ g_free(s);
+ return;
+ }
+
+ result = JS_FALSE;
+
+ JS_EnterLocalRootScope(context);
+
+ jstr = JS_NewStringCopyZ(context, s);
+ if (jstr == NULL) {
+ JS_ReportError(context, "Failed to copy exception string");
+ goto out;
+ }
+
+ body = "throw new Error(message);";
+ func = JS_CompileFunction(context,
+ JS_GetGlobalObject(context), /* parent object (scope chain) */
+ NULL, /* name of function if we wanted to define it in parent */
+ 1, /* nargs */
+ &names[0], /* array of arg names if we had args */
+ body,
+ strlen(body),
+ "gjs_throw", /* file */
+ 0); /* line */
+
+ if (func == NULL) {
+ JS_ReportError(context, "Failed to compile function");
+ goto out;
+ }
+
+ /* we need JS_CallFunctionValue() to leave the exception set */
+ options = JS_GetOptions(context);
+ if (!(options & JSOPTION_DONT_REPORT_UNCAUGHT)) {
+ JS_SetOptions(context, options | JSOPTION_DONT_REPORT_UNCAUGHT);
+ }
+
+ retval = JSVAL_VOID;
+ argv[0] = STRING_TO_JSVAL(jstr);
+
+ /* note the return value is whether function succeeded, which it shouldn't, since it
+ * throws...
+ */
+ JS_CallFunctionValue(context,
+ JS_GetGlobalObject(context),
+ OBJECT_TO_JSVAL(JS_GetFunctionObject(func)),
+ 1, &argv[0],
+ &retval);
+
+ if (!(options & JSOPTION_DONT_REPORT_UNCAUGHT)) {
+ JS_SetOptions(context, options);
+ }
+
+ if (!JS_IsExceptionPending(context)) {
+ JS_ReportError(context,
+ "Failed to set exception by calling our exception-setting function");
+ goto out;
+ }
+
+ result = JS_TRUE;
+
+ out:
+
+ JS_LeaveLocalRootScope(context);
+
+ if (!result) {
+ /* try just reporting it to error handler? should not
+ * happen though pretty much
+ */
+ JS_ReportError(context,
+ "Failed to throw exception '%s'",
+ s);
+ }
+ g_free(s);
+}
+
+/* Throws an exception, like "throw new Error(message)"
+ *
+ * If an exception is already set in the context, this will
+ * NOT overwrite it. That's an important semantic since
+ * we want the "root cause" exception. To overwrite,
+ * use JS_ClearPendingException() first.
+ */
+void
+gjs_throw(JSContext *context,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ gjs_throw_valist(context, format, args);
+ va_end(args);
+}
+
+#if GJS_BUILD_TESTS
+static void
+test_error_reporter(JSContext *context,
+ const char *message,
+ JSErrorReport *report)
+{
+ g_printerr("error reported by test: %s\n", message);
+}
+
+void
+gjstest_test_func_gjs_jsapi_util_error_throw(void)
+{
+ JSRuntime *runtime;
+ JSContext *context;
+ JSObject *global;
+ jsval exc, value, previous;
+ const char *s;
+
+ /* create a runtime just to avoid tangling this test with all the
+ * code surrounding how we create one normally in context.c
+ */
+ runtime = JS_NewRuntime(1024*1024 /* max bytes */);
+ context = JS_NewContext(runtime, 8192);
+ global = JS_NewObject(context, NULL, NULL, NULL);
+ JS_SetGlobalObject(context, global);
+ JS_InitStandardClasses(context, global);
+
+ JS_SetErrorReporter(context, test_error_reporter);
+
+ /* Test that we can throw */
+
+ gjs_throw(context, "This is an exception %d", 42);
+
+ g_assert(JS_IsExceptionPending(context));
+
+ exc = JSVAL_VOID;
+ JS_GetPendingException(context, &exc);
+ g_assert(exc != JSVAL_VOID);
+
+ value = JSVAL_VOID;
+ JS_GetProperty(context, JSVAL_TO_OBJECT(exc), "message",
+ &value);
+
+ g_assert(JSVAL_IS_STRING(value));
+
+ /* JS_GetStringBytes() is broken for non-ASCII but that's OK here */
+ s = JS_GetStringBytes(JSVAL_TO_STRING(value));
+ g_assert(s != NULL);
+ if (strcmp(s, "This is an exception 42") != 0) {
+ g_error("Exception has wrong message '%s'",
+ s);
+ }
+
+ /* keep this around before we clear it */
+ previous = exc;
+ JS_AddRoot(context, &previous);
+
+ JS_ClearPendingException(context);
+
+ g_assert(!JS_IsExceptionPending(context));
+
+ /* Check that we don't overwrite a pending exception */
+ JS_SetPendingException(context, previous);
+
+ g_assert(JS_IsExceptionPending(context));
+
+ gjs_throw(context, "Second different exception %s", "foo");
+
+ g_assert(JS_IsExceptionPending(context));
+
+ exc = JSVAL_VOID;
+ JS_GetPendingException(context, &exc);
+ g_assert(exc != JSVAL_VOID);
+ g_assert(exc == previous);
+
+ JS_RemoveRoot(context, &previous);
+
+ JS_DestroyContext(context);
+ JS_DestroyRuntime(runtime);
+ JS_ShutDown();
+}
+
+#endif /* GJS_BUILD_TESTS */
Added: trunk/gjs/jsapi-util-string.c
==============================================================================
--- (empty file)
+++ trunk/gjs/jsapi-util-string.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,245 @@
+/* -*- 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 "jsapi-util.h"
+
+JSBool
+gjs_string_to_utf8(JSContext *context,
+ const jsval string_val,
+ char **utf8_string_p)
+{
+ jschar *s;
+ size_t s_length;
+ char *utf8_string;
+ GError *error;
+
+ if (!JSVAL_IS_STRING(string_val)) {
+ gjs_throw(context,
+ "Object is not a string, cannot convert to UTF-8");
+ return JS_FALSE;
+ }
+
+ s = JS_GetStringChars(JSVAL_TO_STRING(string_val));
+ s_length = JS_GetStringLength(JSVAL_TO_STRING(string_val));
+
+ error = NULL;
+ utf8_string = g_utf16_to_utf8(s,
+ (glong)s_length,
+ NULL, NULL,
+ &error);
+
+ if (!utf8_string) {
+ gjs_throw(context,
+ "Failed to convert JS string to "
+ "UTF-8: %s",
+ error->message);
+ g_error_free(error);
+ return JS_FALSE;
+ }
+
+ *utf8_string_p = utf8_string;
+ return JS_TRUE;
+
+}
+
+JSBool
+gjs_string_from_utf8(JSContext *context,
+ const char *utf8_string,
+ gsize n_bytes,
+ jsval *value_p)
+{
+ jschar *u16_string;
+ glong u16_string_length;
+ JSString *s;
+ GError *error;
+
+ /* intentionally using n_bytes even though glib api suggests n_chars; with
+ * n_chars (from g_utf8_strlen()) the result appears truncated
+ */
+
+ error = NULL;
+ u16_string = g_utf8_to_utf16(utf8_string,
+ n_bytes,
+ NULL,
+ &u16_string_length,
+ &error);
+
+ if (!u16_string) {
+ gjs_throw(context,
+ "Failed to convert UTF-8 string to "
+ "JS string: %s",
+ error->message);
+ g_error_free(error);
+ return JS_FALSE;
+ }
+
+ s = JS_NewUCStringCopyN(context,
+ (jschar*)u16_string,
+ u16_string_length);
+ g_free(u16_string);
+
+ if (!s)
+ return JS_FALSE;
+
+ *value_p = STRING_TO_JSVAL(s);
+ return JS_TRUE;
+}
+
+/**
+ * gjs_string_get_ascii:
+ * @value: a jsval
+ *
+ * Get the char array in the JSString contained in @value.
+ * The string is expected to be encoded in ASCII, otherwise
+ * you will get garbage out. See the documentation for
+ * JS_GetStringBytes() for more details.
+ *
+ * Returns: an ASCII C string
+ **/
+const char*
+gjs_string_get_ascii(jsval value)
+{
+ g_return_val_if_fail(JSVAL_IS_STRING(value), NULL);
+
+ return JS_GetStringBytes(JSVAL_TO_STRING(value));
+}
+
+/**
+ * gjs_string_get_ascii_checked:
+ * @context: a JSContext
+ * @value: a jsval
+ *
+ * If the given value is not a string, throw an exception and return %NULL.
+ * Otherwise, return the ascii bytes of the string. If the string is not
+ * ASCII, you will get corrupted garbage.
+ *
+ * Returns: an ASCII C string or %NULL on error
+ **/
+const char*
+gjs_string_get_ascii_checked(JSContext *context,
+ jsval value)
+{
+ if (!JSVAL_IS_STRING(value)) {
+ gjs_throw(context, "A string was expected, but value was not a string");
+ return NULL;
+ }
+
+ return JS_GetStringBytes(JSVAL_TO_STRING(value));
+}
+
+/**
+ * gjs_get_string_id:
+ * @id_val: a jsval that is an object hash key (could be an int or string)
+ * @name_p place to store ASCII string version of key
+ *
+ * If the id is not a string ID, return false and set *name_p to %NULL.
+ * Otherwise, return true and fill in *name_p with ASCII name of id.
+ *
+ * Returns: true if *name_p is non-%NULL
+ **/
+JSBool
+gjs_get_string_id (jsval id_val,
+ const char **name_p)
+{
+ if (JSVAL_IS_STRING(id_val)) {
+ *name_p = JS_GetStringBytes(JSVAL_TO_STRING(id_val));
+ return JS_TRUE;
+ } else {
+ *name_p = NULL;
+ return JS_FALSE;
+ }
+}
+
+
+#if GJS_BUILD_TESTS
+
+static void
+test_error_reporter(JSContext *context,
+ const char *message,
+ JSErrorReport *report)
+{
+ g_printerr("error reported by test: %s\n", message);
+}
+
+void
+gjstest_test_func_gjs_jsapi_util_string_js_string_utf8(void)
+{
+ const char *utf8_string = "\303\211\303\226 foobar \343\203\237";
+ char *utf8_result;
+ JSRuntime *runtime;
+ JSContext *context;
+ JSObject *global;
+ jsval js_string;
+
+ runtime = JS_NewRuntime(1024*1024 /* max bytes */);
+ context = JS_NewContext(runtime, 8192);
+ global = JS_NewObject(context, NULL, NULL, NULL);
+ JS_SetGlobalObject(context, global);
+ JS_InitStandardClasses(context, global);
+
+ JS_SetErrorReporter(context, test_error_reporter);
+
+ g_assert(gjs_string_from_utf8(context, utf8_string, -1, &js_string) == JS_TRUE);
+ g_assert(js_string);
+ g_assert(JSVAL_IS_STRING(js_string));
+ g_assert(gjs_string_to_utf8(context, js_string, &utf8_result) == JS_TRUE);
+
+ JS_DestroyContext(context);
+ JS_DestroyRuntime(runtime);
+
+ g_assert(g_str_equal(utf8_string, utf8_result));
+
+ g_free(utf8_result);
+}
+
+void
+gjstest_test_func_gjs_jsapi_util_string_get_ascii(void)
+{
+ JSRuntime *runtime;
+ JSContext *context;
+ JSObject *global;
+ const char *ascii_string = "Hello, world";
+ JSString *js_string;
+ jsval void_value;
+
+ runtime = JS_NewRuntime(1024*1024 /* max bytes */);
+ context = JS_NewContext(runtime, 8192);
+ global = JS_NewObject(context, NULL, NULL, NULL);
+ JS_SetGlobalObject(context, global);
+ JS_InitStandardClasses(context, global);
+
+ JS_SetErrorReporter(context, test_error_reporter);
+
+ js_string = JS_NewStringCopyZ(context, ascii_string);
+ g_assert(g_str_equal(gjs_string_get_ascii(STRING_TO_JSVAL(js_string)), ascii_string));
+ void_value = JSVAL_VOID;
+ g_assert(gjs_string_get_ascii_checked(context, void_value) == NULL);
+ g_assert(JS_IsExceptionPending(context));
+
+ JS_DestroyContext(context);
+ JS_DestroyRuntime(runtime);
+}
+
+#endif /* GJS_BUILD_TESTS */
Added: trunk/gjs/jsapi-util.c
==============================================================================
--- (empty file)
+++ trunk/gjs/jsapi-util.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,967 @@
+/* -*- 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 "jsapi-util.h"
+#include "context-jsapi.h"
+
+#include <util/log.h>
+#include <util/glib.h>
+
+#include <string.h>
+
+typedef struct {
+ GHashTable *dynamic_classes;
+} RuntimeData;
+
+typedef struct {
+ JSClass base;
+ JSClass *static_class;
+} DynamicJSClass;
+
+void*
+gjs_runtime_get_data(JSRuntime *runtime,
+ const char *name)
+{
+ return g_dataset_get_data(runtime, name);
+}
+
+void
+gjs_runtime_set_data(JSRuntime *runtime,
+ const char *name,
+ void *data,
+ GDestroyNotify dnotify)
+{
+ g_dataset_set_data_full(runtime, name, data, dnotify);
+}
+
+/* The "load context" is the one we use for loading
+ * modules and initializing classes.
+ */
+JSContext*
+gjs_runtime_get_load_context(JSRuntime *runtime)
+{
+ GjsContext *context;
+
+ context = gjs_runtime_get_data(runtime, "gjs-load-context");
+ if (context == NULL) {
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Creating load context for runtime %p",
+ runtime);
+ context = g_object_new(GJS_TYPE_CONTEXT,
+ "runtime", runtime,
+ "is-load-context", TRUE,
+ NULL);
+ gjs_runtime_set_data(runtime,
+ "gjs-load-context",
+ context,
+ g_object_unref);
+ }
+
+ return gjs_context_get_context(context);
+}
+
+static JSContext*
+gjs_runtime_peek_load_context(JSRuntime *runtime)
+{
+ GjsContext *context;
+
+ context = gjs_runtime_get_data(runtime, "gjs-load-context");
+ if (context == NULL) {
+ return NULL;
+ } else {
+ return gjs_context_get_context(context);
+ }
+}
+
+void
+gjs_runtime_clear_load_context(JSRuntime *runtime)
+{
+ gjs_debug(GJS_DEBUG_CONTEXT, "Clearing load context");
+ gjs_runtime_set_data(runtime,
+ "gjs-load-context",
+ NULL,
+ NULL);
+ gjs_debug(GJS_DEBUG_CONTEXT, "Load context cleared");
+}
+
+/* The call context exists because when we call a closure, the scope
+ * chain on the context is set to the original scope chain of the
+ * closure. We want to avoid using any existing context (especially
+ * the load context) because the closure "messes up" the scope chain
+ * on the context.
+ *
+ * Unlike the load context, which is expected to be an eternal
+ * singleton, we only cache the call context for efficiency. It would
+ * be just as workable to recreate it for each call.
+ */
+JSContext*
+gjs_runtime_get_call_context(JSRuntime *runtime)
+{
+ GjsContext *context;
+
+ context = gjs_runtime_get_data(runtime, "gjs-call-context");
+ if (context == NULL) {
+ gjs_debug(GJS_DEBUG_CONTEXT,
+ "Creating call context for runtime %p",
+ runtime);
+ context = g_object_new(GJS_TYPE_CONTEXT,
+ "runtime", runtime,
+ NULL);
+ gjs_runtime_set_data(runtime,
+ "gjs-call-context",
+ context,
+ g_object_unref);
+ }
+
+ return gjs_context_get_context(context);
+}
+
+static JSContext*
+gjs_runtime_peek_call_context(JSRuntime *runtime)
+{
+ GjsContext *context;
+
+ context = gjs_runtime_get_data(runtime, "gjs-call-context");
+ if (context == NULL) {
+ return NULL;
+ } else {
+ return gjs_context_get_context(context);
+ }
+}
+
+void
+gjs_runtime_clear_call_context(JSRuntime *runtime)
+{
+ gjs_debug(GJS_DEBUG_CONTEXT, "Clearing call context");
+ gjs_runtime_set_data(runtime,
+ "gjs-call-context",
+ NULL,
+ NULL);
+ gjs_debug(GJS_DEBUG_CONTEXT, "Call context cleared");
+}
+
+static void
+runtime_data_destroy_notify(void *data)
+{
+ RuntimeData *rd = data;
+ void *key;
+ void *value;
+
+ while (gjs_g_hash_table_remove_one(rd->dynamic_classes, &key, &value)) {
+ JSClass *clasp = value;
+
+ gjs_debug(GJS_DEBUG_GREPO,
+ "Finalizing dynamic class '%s'",
+ clasp->name);
+
+ g_free( (char*) clasp->name); /* we know we malloc'd the char* even though it's const */
+ g_slice_free(DynamicJSClass, (DynamicJSClass*) clasp);
+ }
+
+ g_hash_table_destroy(rd->dynamic_classes);
+ g_slice_free(RuntimeData, rd);
+}
+
+static RuntimeData*
+get_data_from_runtime(JSRuntime *runtime)
+{
+ RuntimeData *rd;
+
+ rd = gjs_runtime_get_data(runtime, "gjs-api-util-data");
+ if (rd == NULL) {
+ rd = g_slice_new0(RuntimeData);
+ rd->dynamic_classes = g_hash_table_new(g_direct_hash, g_direct_equal);
+ gjs_runtime_set_data(runtime, "gjs-api-util-data",
+ rd, runtime_data_destroy_notify);
+ }
+
+ return rd;
+}
+
+static RuntimeData*
+get_data_from_context(JSContext *context)
+{
+ return get_data_from_runtime(JS_GetRuntime(context));
+}
+
+/* Checks whether an object has a property; unlike JS_GetProperty(),
+ * never sets an exception. Treats a property with a value of JSVAL_VOID
+ * the same as an absent property and returns false in both cases.
+ */
+gboolean
+gjs_object_has_property(JSContext *context,
+ JSObject *obj,
+ const char *property_name)
+{
+ return gjs_object_get_property(context, obj, property_name, NULL);
+}
+
+/* Checks whether an object has a property; unlike JS_GetProperty(),
+ * never sets an exception. Treats a property with a value of JSVAL_VOID
+ * the same as an absent property and returns false in both cases.
+ * Always initializes *value_p, if only to JSVAL_VOID, even if it
+ * returns FALSE.
+ */
+gboolean
+gjs_object_get_property(JSContext *context,
+ JSObject *obj,
+ const char *property_name,
+ jsval *value_p)
+{
+ jsval value;
+ JSExceptionState *state;
+
+ value = JSVAL_VOID;
+ state = JS_SaveExceptionState(context);
+ JS_GetProperty(context, obj, property_name, &value);
+ JS_RestoreExceptionState(context, state);
+
+ if (value_p)
+ *value_p = value;
+
+ return value != JSVAL_VOID;
+}
+
+/* Returns whether the object had the property; if the object did
+ * not have the property, always sets an exception. Treats
+ * "the property's value is JSVAL_VOID" the same as "no such property,"
+ * while JS_GetProperty() treats only "no such property" as an error.
+ * Guarantees that *value_p is set to something, if only JSVAL_VOID,
+ * even if an exception is set and false is returned.
+ */
+gboolean
+gjs_object_require_property(JSContext *context,
+ JSObject *obj,
+ const char *property_name,
+ jsval *value_p)
+{
+ jsval value;
+
+ value = JSVAL_VOID;
+ JS_GetProperty(context, obj, property_name, &value);
+
+ if (value_p)
+ *value_p = value;
+
+ if (value != JSVAL_VOID) {
+ JS_ClearPendingException(context); /* in case JS_GetProperty() was on crack */
+ return TRUE;
+ } else {
+ /* remember gjs_throw() is a no-op if JS_GetProperty()
+ * already set an exception
+ */
+ gjs_throw(context,
+ "No property '%s' in object %p (or its value was undefined)",
+ property_name, obj);
+ return FALSE;
+ }
+}
+
+JSObject*
+gjs_init_class_dynamic(JSContext *context,
+ JSObject *in_object,
+ JSObject *parent_proto,
+ const char *ns_name,
+ const char *class_name,
+ JSClass *clasp,
+ JSNative constructor,
+ uintN nargs,
+ JSPropertySpec *ps,
+ JSFunctionSpec *fs,
+ JSPropertySpec *static_ps,
+ JSFunctionSpec *static_fs)
+{
+ jsval value;
+ char *private_name;
+ JSObject *prototype;
+
+ if (clasp->name != NULL) {
+ g_warning("Dynamic class should not have a name in the JSClass struct");
+ return NULL;
+ }
+
+ /* We replace the passed-in context and global object with our
+ * runtime-global permanent load context. Otherwise, in a
+ * process with multiple contexts, we'd arbitrarily define
+ * the class in whatever global object initialized the
+ * class first, which is not desirable.
+ */
+ context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+
+ /* JS_InitClass() wants to define the constructor in the global object, so
+ * we give it a private and namespaced name... passing in the namespace
+ * object instead of global object seems to break JS_ConstructObject()
+ * which then can't find the constructor for the class. I am probably
+ * missing something.
+ */
+ private_name = g_strdup_printf("_private_%s_%s", ns_name, class_name);
+
+ prototype = NULL;
+ if (gjs_object_get_property(context, JS_GetGlobalObject(context),
+ private_name, &value) &&
+ JSVAL_IS_OBJECT(value)) {
+ jsval proto_val;
+
+ g_free(private_name); /* don't need it anymore */
+
+ if (!gjs_object_require_property(context, JSVAL_TO_OBJECT(value),
+ "prototype", &proto_val) ||
+ !JSVAL_IS_OBJECT(proto_val)) {
+ gjs_throw(context, "prototype was not defined or not an object?");
+ return NULL;
+ }
+ prototype = JSVAL_TO_OBJECT(proto_val);
+ } else {
+ DynamicJSClass *class_copy;
+ RuntimeData *rd;
+
+ rd = get_data_from_context(context);
+
+ class_copy = g_slice_new0(DynamicJSClass);
+ class_copy->base = *clasp;
+
+ class_copy->base.name = private_name; /* Pass ownership of memory */
+ class_copy->static_class = clasp;
+
+ /* record the allocated class to be destroyed with the runtime and so
+ * we can do an IS_DYNAMIC_CLASS check
+ */
+ g_hash_table_replace(rd->dynamic_classes,
+ class_copy, class_copy);
+
+ gjs_debug(GJS_DEBUG_GREPO,
+ "Initializing dynamic class %s %p",
+ class_name, class_copy);
+
+ prototype = JS_InitClass(context, JS_GetGlobalObject(context),
+ parent_proto, &class_copy->base,
+ constructor, nargs,
+ ps, fs,
+ static_ps, static_fs);
+
+ /* Retrieve the property again so we can define it in
+ * in_object
+ */
+ if (!gjs_object_require_property(context, JS_GetGlobalObject(context),
+ class_copy->base.name, &value))
+ return NULL;
+ }
+ g_assert(value != JSVAL_VOID);
+ g_assert(prototype != NULL);
+
+ /* Now manually define our constructor with a sane name, in the
+ * namespace object.
+ */
+ if (!JS_DefineProperty(context, in_object,
+ class_name,
+ value,
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS))
+ return NULL;
+
+ return prototype;
+}
+
+void*
+gjs_get_instance_private_dynamic(JSContext *context,
+ JSObject *obj,
+ JSClass *static_clasp,
+ jsval *argv)
+{
+ RuntimeData *rd;
+ JSClass *obj_class;
+
+ if (static_clasp->name != NULL) {
+ g_warning("Dynamic class should not have a name in the JSClass struct");
+ return NULL;
+ }
+
+ obj_class = JS_GetClass(context, obj);
+ g_assert(obj_class != NULL);
+
+ rd = get_data_from_context(context);
+ g_assert(rd != NULL);
+
+ /* Check that it's safe to cast to DynamicJSClass */
+ if (g_hash_table_lookup(rd->dynamic_classes, obj_class) == NULL) {
+ gjs_throw(context,
+ "Object %p proto %p doesn't have a dynamically-registered class, it has %s",
+ obj, JS_GetPrototype(context, obj), obj_class->name);
+ return NULL;
+ }
+
+ if (static_clasp != ((DynamicJSClass*) obj_class)->static_class) {
+ gjs_throw(context, "Object is not a dynamically-registered class based on expected static class pointer");
+ return NULL;
+ }
+
+ return JS_GetInstancePrivate(context, obj, obj_class, argv);
+}
+
+JSObject*
+gjs_construct_object_dynamic(JSContext *context,
+ JSObject *proto,
+ uintN argc,
+ jsval *argv)
+{
+ RuntimeData *rd;
+ JSClass *proto_class;
+
+ /* We replace the passed-in context and global object with our
+ * runtime-global permanent load context. Otherwise, JS_ConstructObject
+ * can't find the constructor in whatever random global object is set
+ * on the passed-in context.
+ */
+ context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+
+ proto_class = JS_GetClass(context, proto);
+
+ rd = get_data_from_context(context);
+
+ /* Check that it's safe to cast to DynamicJSClass */
+ if (g_hash_table_lookup(rd->dynamic_classes, proto_class) == NULL) {
+ gjs_throw(context, "Prototype is not for a dynamically-registered class");
+ return NULL;
+ }
+
+ gjs_debug_lifecycle(GJS_DEBUG_GREPO,
+ "Constructing instance of dynamic class %s %p from proto %p",
+ proto_class->name, proto_class, proto);
+
+ if (argc > 0)
+ return JS_ConstructObjectWithArguments(context, proto_class, proto, NULL, argc, argv);
+ else
+ return JS_ConstructObject(context, proto_class, proto, NULL);
+}
+
+JSObject*
+gjs_define_string_array(JSContext *context,
+ JSObject *in_object,
+ const char *array_name,
+ gssize array_length,
+ const char **array_values,
+ uintN attrs)
+{
+ GArray *elems;
+ JSObject *array;
+ int i;
+
+ if (!JS_EnterLocalRootScope(context))
+ return JS_FALSE;
+
+ if (array_length == -1)
+ array_length = g_strv_length((char**)array_values);
+
+ elems = g_array_sized_new(FALSE, FALSE, sizeof(jsval), array_length);
+
+ for (i = 0; i < array_length; ++i) {
+ jsval element;
+ element = STRING_TO_JSVAL(JS_NewStringCopyZ(context, array_values[i]));
+ g_array_append_val(elems, element);
+ }
+
+ array = JS_NewArrayObject(context, elems->len, (jsval*) elems->data);
+ g_array_free(elems, TRUE);
+
+ if (array != NULL) {
+ if (!JS_DefineProperty(context, in_object,
+ array_name, OBJECT_TO_JSVAL(array),
+ NULL, NULL, attrs))
+ array = NULL;
+ }
+
+ JS_LeaveLocalRootScope(context);
+ return array;
+}
+
+const char*
+gjs_value_debug_string(JSContext *context,
+ jsval value)
+{
+ JSString *str;
+
+ str = JS_ValueToString(context, value);
+
+ if (str == NULL) {
+ if (JSVAL_IS_OBJECT(value)) {
+ /* Specifically the Call object (see jsfun.c in spidermonkey)
+ * does not have a toString; there may be others also.
+ */
+ JSClass *klass;
+
+ klass = JS_GetClass(context, JSVAL_TO_OBJECT(value));
+ if (klass != NULL) {
+ str = JS_NewStringCopyZ(context, klass->name);
+ JS_ClearPendingException(context);
+ if (str == NULL) {
+ return "[out of memory copying class name]";
+ }
+ } else {
+ gjs_log_exception(context, NULL);
+ return "[unknown object]";
+ }
+ } else {
+ return "[unknown non-object]";
+ }
+ }
+
+ g_assert(str != NULL);
+
+ return JS_GetStringBytes(str);
+}
+
+void
+gjs_log_object_props(JSContext *context,
+ JSObject *obj,
+ GjsDebugTopic topic,
+ const char *prefix)
+{
+ JSObject *props_iter;
+ jsid prop_id;
+
+ /* We potentially create new strings, plus the property iterator,
+ * that could get collected as we go through this process. So
+ * create a local root scope.
+ */
+ JS_EnterLocalRootScope(context);
+
+ props_iter = JS_NewPropertyIterator(context, obj);
+ if (props_iter == NULL) {
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Failed to create property iterator for object props");
+ goto done;
+ }
+
+ prop_id = JSVAL_VOID;
+ if (!JS_NextProperty(context, props_iter, &prop_id))
+ goto done;
+
+ while (prop_id != JSVAL_VOID) {
+ jsval nameval;
+ const char *name;
+ jsval propval;
+
+ if (!JS_IdToValue(context, prop_id, &nameval))
+ goto next;
+
+ if (!gjs_get_string_id(nameval, &name))
+ goto next;
+
+ if (!gjs_object_get_property(context, obj, name, &propval))
+ goto next;
+
+ gjs_debug(topic,
+ "%s%s = '%s'",
+ prefix, name,
+ gjs_value_debug_string(context, propval));
+
+ next:
+ prop_id = JSVAL_VOID;
+ if (!JS_NextProperty(context, props_iter, &prop_id))
+ break;
+ }
+
+ done:
+ JS_LeaveLocalRootScope(context);
+}
+
+void
+gjs_explain_scope(JSContext *context,
+ const char *title)
+{
+ JSContext *load_context;
+ JSContext *call_context;
+ JSObject *global;
+ JSObject *parent;
+ GString *chain;
+
+ gjs_debug(GJS_DEBUG_SCOPE,
+ "=== %s ===",
+ title);
+
+ load_context = gjs_runtime_peek_load_context(JS_GetRuntime(context));
+ call_context = gjs_runtime_peek_call_context(JS_GetRuntime(context));
+
+ JS_EnterLocalRootScope(context);
+
+ gjs_debug(GJS_DEBUG_SCOPE,
+ " Context: %p %s",
+ context,
+ context == load_context ? "(LOAD CONTEXT)" :
+ context == call_context ? "(CALL CONTEXT)" :
+ "");
+
+ global = JS_GetGlobalObject(context);
+ gjs_debug(GJS_DEBUG_SCOPE,
+ " Global: %p %s",
+ global, gjs_value_debug_string(context, OBJECT_TO_JSVAL(global)));
+
+ parent = JS_GetScopeChain(context);
+ chain = g_string_new(NULL);
+ while (parent != NULL) {
+ const char *debug;
+ debug = gjs_value_debug_string(context, OBJECT_TO_JSVAL(parent));
+
+ if (chain->len > 0)
+ g_string_append(chain, ", ");
+
+ g_string_append_printf(chain, "%p %s",
+ parent, debug);
+ parent = JS_GetParent(context, parent);
+ }
+ gjs_debug(GJS_DEBUG_SCOPE,
+ " Chain: %s",
+ chain->str);
+ g_string_free(chain, TRUE);
+
+ JS_LeaveLocalRootScope(context);
+}
+
+void
+gjs_log_exception_props(JSContext *context,
+ jsval exc)
+{
+ /* This is useful when the exception was never sent to an error reporter
+ * due to JSOPTION_DONT_REPORT_UNCAUGHT, or if the exception was not
+ * a normal Error object so jsapi didn't know how to report it sensibly.
+ */
+ if (JSVAL_IS_NULL(exc)) {
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Exception was null");
+ } else if (JSVAL_IS_OBJECT(exc)) {
+ JSObject *exc_obj;
+
+ exc_obj = JSVAL_TO_OBJECT(exc);
+
+ /* I guess this is a SpiderMonkey bug. If we don't get these
+ * properties here, only 'message' shows up when we enumerate
+ * all properties below. I did not debug in detail, so maybe
+ * it's something wrong with our enumeration loop below. In
+ * any case, if you remove this code block, check that "throw
+ * Error()" still results in printing all four of these props.
+ * For me right now, if you remove this block, only message
+ * gets printed.
+ */
+ gjs_object_has_property(context, exc_obj, "stack");
+ gjs_object_has_property(context, exc_obj, "fileName");
+ gjs_object_has_property(context, exc_obj, "lineNumber");
+ gjs_object_has_property(context, exc_obj, "message");
+
+ gjs_log_object_props(context, exc_obj,
+ GJS_DEBUG_ERROR,
+ " ");
+ } else if (JSVAL_IS_STRING(exc)) {
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Exception was a String");
+ } else {
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Exception had some strange type");
+ }
+}
+
+static JSBool
+log_and_maybe_keep_exception(JSContext *context,
+ char **message_p,
+ gboolean keep)
+{
+ jsval exc = JSVAL_VOID;
+ JSString *s;
+ char *message;
+ JSBool retval = JS_FALSE;
+
+ if (message_p)
+ *message_p = NULL;
+
+ JS_AddRoot(context, &exc);
+ if (!JS_GetPendingException(context, &exc))
+ goto out;
+
+ JS_ClearPendingException(context);
+
+ s = JS_ValueToString(context, exc);
+
+ if (s == NULL) {
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Failed to convert exception to string");
+ goto out; /* Exception should be thrown already */
+ }
+
+ if (!gjs_string_to_utf8(context, STRING_TO_JSVAL(s), &message)) {
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Failed to convert exception string to UTF-8");
+ goto out; /* Error already set */
+ }
+
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Exception was: %s",
+ message);
+
+ if (message_p) {
+ *message_p = message;
+ } else {
+ g_free(message);
+ }
+
+ gjs_log_exception_props(context, exc);
+
+ /* We clear above and then set it back so any exceptions
+ * from the logging process don't overwrite the original
+ */
+ if (keep)
+ JS_SetPendingException(context, exc);
+
+ retval = JS_TRUE;
+
+ out:
+ JS_RemoveRoot(context, &exc);
+
+ return retval;
+}
+
+JSBool
+gjs_log_exception(JSContext *context,
+ char **message_p)
+{
+ return log_and_maybe_keep_exception(context, message_p, FALSE);
+}
+
+JSBool
+gjs_log_and_keep_exception(JSContext *context,
+ char **message_p)
+{
+ return log_and_maybe_keep_exception(context, message_p, TRUE);
+}
+
+JSBool
+gjs_move_exception(JSContext *src_context,
+ JSContext *dest_context)
+{
+ /* NOTE: src and dest could be the same. */
+ jsval exc;
+ if (JS_GetPendingException(src_context, &exc)) {
+ if (src_context != dest_context) {
+ JS_SetPendingException(dest_context, exc);
+ JS_ClearPendingException(src_context);
+ }
+ return JS_TRUE;
+ } else {
+ return JS_FALSE;
+ }
+}
+
+JSBool
+gjs_call_function_value(JSContext *context,
+ JSObject *obj,
+ jsval fval,
+ uintN argc,
+ jsval *argv,
+ jsval *rval)
+{
+ JSBool result;
+ JSContext *call_context;
+
+ call_context = gjs_runtime_get_call_context(JS_GetRuntime(context));
+
+ result = JS_CallFunctionValue(call_context, obj, fval,
+ argc, argv, rval);
+ gjs_move_exception(call_context, context);
+
+ return result;
+}
+
+void
+gjs_error_reporter(JSContext *context,
+ const char *message,
+ JSErrorReport *report)
+{
+ const char *warning;
+
+ if ((report->flags & JSREPORT_WARNING) != 0) {
+ /* We manually insert "WARNING" into the output instead of
+ * having GJS_DEBUG_WARNING because it's convenient to
+ * search for 'JS ERROR' to find all problems
+ */
+ warning = "WARNING: ";
+
+ /* suppress bogus warnings. See mozilla/js/src/js.msg */
+ switch (report->errorNumber) {
+ /* 162, JSMSG_UNDEFINED_PROP: warns every time a lazy property
+ * is resolved, since the property starts out
+ * undefined. When this is a real bug it should usually
+ * fail somewhere else anyhow.
+ */
+ case 162:
+ return;
+ }
+ } else {
+ warning = "REPORTED: ";
+ }
+
+ gjs_debug(GJS_DEBUG_ERROR,
+ "%s'%s'",
+ warning,
+ message);
+
+ gjs_debug(GJS_DEBUG_ERROR,
+ "%sfile '%s' line %u exception %d number %d",
+ warning,
+ report->filename, report->lineno,
+ (report->flags & JSREPORT_EXCEPTION) != 0,
+ report->errorNumber);
+}
+
+static JSBool
+log_prop(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p,
+ const char *what)
+{
+ if (JSVAL_IS_STRING(id)) {
+ const char *name;
+
+ name = gjs_string_get_ascii(id);
+ gjs_debug(GJS_DEBUG_PROPS,
+ "prop %s: %s",
+ name, what);
+ } else if (JSVAL_IS_INT(id)) {
+ gjs_debug(GJS_DEBUG_PROPS,
+ "prop %d: %s",
+ JSVAL_TO_INT(id), what);
+ } else {
+ gjs_debug(GJS_DEBUG_PROPS,
+ "prop not-sure-what: %s",
+ what);
+ }
+
+ return JS_TRUE;
+}
+
+JSBool
+gjs_get_prop_verbose_stub(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ return log_prop(context, obj, id, value_p, "get");
+}
+
+JSBool
+gjs_set_prop_verbose_stub(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ return log_prop(context, obj, id, value_p, "set");
+}
+
+JSBool
+gjs_add_prop_verbose_stub(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ return log_prop(context, obj, id, value_p, "add");
+}
+
+JSBool
+gjs_delete_prop_verbose_stub(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ return log_prop(context, obj, id, value_p, "delete");
+}
+
+/* get a debug string for type tag in jsval */
+const char*
+gjs_get_type_name(jsval value)
+{
+ if (JSVAL_IS_NULL(value)) {
+ return "null";
+ } else if (value == JSVAL_VOID) {
+ return "undefined";
+ } else if (JSVAL_IS_INT(value)) {
+ return "integer";
+ } else if (JSVAL_IS_DOUBLE(value)) {
+ return "double";
+ } else if (JSVAL_IS_BOOLEAN(value)) {
+ return "boolean";
+ } else if (JSVAL_IS_STRING(value)) {
+ return "string";
+ } else if (JSVAL_IS_OBJECT(value)) {
+ return "object";
+ } else {
+ return "<unknown>";
+ }
+}
+
+/* ensure that the namespace name is in MyModule format */
+char *
+gjs_fix_ns_name(const char *ns_name)
+{
+ char *namespace;
+ gboolean is_glib;
+
+ if (strcmp(ns_name, "gIO") == 0) {
+ return g_strdup("Gio");
+ }
+
+ namespace = g_strdup(ns_name);
+
+ is_glib = (g_ascii_strcasecmp(ns_name, "glib") == 0);
+
+ namespace[0] = g_ascii_toupper(namespace[0]);
+
+ if (is_glib) {
+ namespace[1] = g_ascii_toupper(namespace[1]);
+ }
+
+ return namespace;
+}
+
+/* ensure that the namespace name is in myModule format */
+char *
+gjs_unfix_ns_name(const char *ns_name)
+{
+ char *namespace;
+ gboolean is_glib;
+
+ if (strcmp(ns_name, "Gio") == 0) {
+ return g_strdup("gIO");
+ }
+
+ namespace = g_strdup(ns_name);
+
+ is_glib = (g_ascii_strcasecmp(ns_name, "glib") == 0);
+
+ namespace[0] = g_ascii_tolower(namespace[0]);
+
+ if (is_glib) {
+ namespace[1] = g_ascii_tolower(namespace[1]);
+ }
+
+ return namespace;
+}
Added: trunk/gjs/jsapi-util.h
==============================================================================
--- (empty file)
+++ trunk/gjs/jsapi-util.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,237 @@
+/* -*- 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_JSAPI_UTIL_H__
+#define __GJS_JSAPI_UTIL_H__
+
+#ifndef __GJS_GJS_H__
+#warning Include <gjs/gjs.h> instead of <gjs/jsapi-util.h>
+#endif
+
+#include <jsapi.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct GjsRootedArray GjsRootedArray;
+
+/* Flags that should be set on properties exported from native code modules.
+ * Basically set these on API, but do NOT set them on data.
+ *
+ * READONLY: forbid setting prop to another value
+ * PERMANENT: forbid deleting the prop
+ * ENUMERATE: allows copyProperties to work among other reasons to have it
+ */
+#define GJS_MODULE_PROP_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE)
+
+/* priv_from_js_with_typecheck checks that the object is in fact an
+ * instance of the specified class before accessing its private data.
+ * Keep in mind that the function can return JS_TRUE and still fill the
+ * out parameter with NULL if the object is the prototype for a class
+ * without the JSCLASS_CONSTRUCT_PROTOTYPE flag or if it the class simply
+ * does not have any private data.
+ */
+#define GJS_DEFINE_PRIV_FROM_JS(type, class) \
+ __attribute__((unused)) static JSBool \
+ priv_from_js_with_typecheck(JSContext *context, \
+ JSObject *object, \
+ type **out) \
+ {\
+ if (!out) \
+ return JS_FALSE; \
+ if (!JS_InstanceOf(context, object, &class, NULL)) \
+ return JS_FALSE; \
+ *out = JS_GetInstancePrivate(context, object, &class, NULL); \
+ return JS_TRUE; \
+ }\
+ static type*\
+ priv_from_js(JSContext *context, \
+ JSObject *object) \
+ {\
+ return JS_GetInstancePrivate(context, object, &class, NULL); \
+ }
+
+
+#define GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(type, class) \
+ __attribute__((unused)) static JSBool\
+ priv_from_js_with_typecheck(JSContext *context, \
+ JSObject *object, \
+ type **out) \
+ {\
+ if (!out) \
+ return JS_FALSE; \
+ if (!JS_InstanceOf(context, object, &class, NULL)) \
+ return JS_FALSE; \
+ *out = gjs_get_instance_private_dynamic(context, object, &class, NULL); \
+ return JS_TRUE; \
+ }\
+ static type*\
+ priv_from_js(JSContext *context, \
+ JSObject *object) \
+ {\
+ return gjs_get_instance_private_dynamic(context, object, &class, NULL); \
+ }
+
+void* gjs_runtime_get_data (JSRuntime *runtime,
+ const char *name);
+void gjs_runtime_set_data (JSRuntime *runtime,
+ const char *name,
+ void *data,
+ GDestroyNotify dnotify);
+JSContext* gjs_runtime_get_load_context (JSRuntime *runtime);
+void gjs_runtime_clear_load_context (JSRuntime *runtime);
+JSContext* gjs_runtime_get_call_context (JSRuntime *runtime);
+void gjs_runtime_clear_call_context (JSRuntime *runtime);
+gboolean gjs_object_has_property (JSContext *context,
+ JSObject *obj,
+ const char *property_name);
+gboolean gjs_object_get_property (JSContext *context,
+ JSObject *obj,
+ const char *property_name,
+ jsval *value_p);
+gboolean gjs_object_require_property (JSContext *context,
+ JSObject *obj,
+ const char *property_name,
+ jsval *value_p);
+JSObject * gjs_init_class_dynamic (JSContext *context,
+ JSObject *in_object,
+ JSObject *parent_proto,
+ const char *ns_name,
+ const char *class_name,
+ JSClass *clasp,
+ JSNative constructor,
+ uintN nargs,
+ JSPropertySpec *ps,
+ JSFunctionSpec *fs,
+ JSPropertySpec *static_ps,
+ JSFunctionSpec *static_fs);
+void* gjs_get_instance_private_dynamic (JSContext *context,
+ JSObject *obj,
+ JSClass *static_clasp,
+ jsval *argv);
+void* gjs_get_instance_private_dynamic (JSContext *context,
+ JSObject *obj,
+ JSClass *static_clasp,
+ jsval *argv);
+JSObject* gjs_construct_object_dynamic (JSContext *context,
+ JSObject *proto,
+ uintN argc,
+ jsval *argv);
+JSObject* gjs_define_string_array (JSContext *context,
+ JSObject *obj,
+ const char *array_name,
+ gssize array_length,
+ const char **array_values,
+ uintN attrs);
+void gjs_throw (JSContext *context,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+JSBool gjs_log_exception (JSContext *context,
+ char **message_p);
+JSBool gjs_log_and_keep_exception (JSContext *context,
+ char **message_p);
+JSBool gjs_move_exception (JSContext *src_context,
+ JSContext *dest_context);
+void gjs_log_exception_props (JSContext *context,
+ jsval exc);
+#ifdef __GJS_UTIL_LOG_H__
+void gjs_log_object_props (JSContext *context,
+ JSObject *obj,
+ GjsDebugTopic topic,
+ const char *prefix);
+#endif
+const char* gjs_value_debug_string (JSContext *context,
+ jsval value);
+void gjs_explain_scope (JSContext *context,
+ const char *title);
+JSBool gjs_call_function_value (JSContext *context,
+ JSObject *obj,
+ jsval fval,
+ uintN argc,
+ jsval *argv,
+ jsval *rval);
+void gjs_error_reporter (JSContext *context,
+ const char *message,
+ JSErrorReport *report);
+JSBool gjs_get_prop_verbose_stub (JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p);
+JSBool gjs_set_prop_verbose_stub (JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p);
+JSBool gjs_add_prop_verbose_stub (JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p);
+JSBool gjs_delete_prop_verbose_stub (JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p);
+JSBool gjs_string_to_utf8 (JSContext *context,
+ const jsval string_val,
+ char **utf8_string_p);
+JSBool gjs_string_from_utf8 (JSContext *context,
+ const char *utf8_string,
+ gsize n_bytes,
+ jsval *value_p);
+const char* gjs_string_get_ascii (jsval value);
+const char* gjs_string_get_ascii_checked (JSContext *context,
+ jsval value);
+JSBool gjs_get_string_id (jsval id_val,
+ const char **name_p);
+const char* gjs_get_type_name (jsval value);
+
+char* gjs_fix_ns_name (const char *ns_name);
+char* gjs_unfix_ns_name (const char *ns_name);
+
+GjsRootedArray* gjs_rooted_array_new (void);
+void gjs_rooted_array_append (JSContext *context,
+ GjsRootedArray *array,
+ jsval value);
+jsval gjs_rooted_array_get (JSContext *context,
+ GjsRootedArray *array,
+ int i);
+jsval* gjs_rooted_array_get_data (JSContext *context,
+ GjsRootedArray *array);
+int gjs_rooted_array_get_length (JSContext *context,
+ GjsRootedArray *array);
+jsval* gjs_rooted_array_free (JSContext *context,
+ GjsRootedArray *array,
+ gboolean free_segment);
+void gjs_set_values (JSContext *context,
+ jsval *locations,
+ int n_locations,
+ jsval initializer);
+void gjs_root_value_locations (JSContext *context,
+ jsval *locations,
+ int n_locations);
+void gjs_unroot_value_locations (JSContext *context,
+ jsval *locations,
+ int n_locations);
+
+
+G_END_DECLS
+
+#endif /* __GJS_JSAPI_UTIL_H__ */
Added: trunk/gjs/mem.c
==============================================================================
--- (empty file)
+++ trunk/gjs/mem.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,106 @@
+/* -*- 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 "mem.h"
+#include <util/log.h>
+
+#define GJS_DEFINE_COUNTER(name) \
+ GjsMemCounter gjs_counter_ ## name = { \
+ 0, #name \
+ };
+
+
+GJS_DEFINE_COUNTER(everything)
+
+GJS_DEFINE_COUNTER(boxed)
+GJS_DEFINE_COUNTER(closure)
+GJS_DEFINE_COUNTER(database)
+GJS_DEFINE_COUNTER(dbus_exports)
+GJS_DEFINE_COUNTER(function)
+GJS_DEFINE_COUNTER(importer)
+GJS_DEFINE_COUNTER(ns)
+GJS_DEFINE_COUNTER(object)
+GJS_DEFINE_COUNTER(param)
+GJS_DEFINE_COUNTER(repo)
+GJS_DEFINE_COUNTER(resultset)
+GJS_DEFINE_COUNTER(weakhash)
+
+#define GJS_LIST_COUNTER(name) \
+ & gjs_counter_ ## name
+
+static GjsMemCounter* counters[] = {
+ GJS_LIST_COUNTER(boxed),
+ GJS_LIST_COUNTER(closure),
+ GJS_LIST_COUNTER(database),
+ GJS_LIST_COUNTER(dbus_exports),
+ GJS_LIST_COUNTER(function),
+ GJS_LIST_COUNTER(importer),
+ GJS_LIST_COUNTER(ns),
+ GJS_LIST_COUNTER(object),
+ GJS_LIST_COUNTER(param),
+ GJS_LIST_COUNTER(repo),
+ GJS_LIST_COUNTER(resultset),
+ GJS_LIST_COUNTER(weakhash)
+};
+
+void
+gjs_memory_report(const char *where,
+ gboolean die_if_leaks)
+{
+ int i;
+ int n_counters;
+ int total_objects;
+
+ gjs_debug(GJS_DEBUG_MEMORY,
+ "Memory report: %s",
+ where);
+
+ n_counters = G_N_ELEMENTS(counters);
+
+ total_objects = 0;
+ for (i = 0; i < n_counters; ++i) {
+ total_objects += counters[i]->value;
+ }
+
+ if (total_objects != GJS_GET_COUNTER(everything)) {
+ gjs_debug(GJS_DEBUG_MEMORY,
+ "Object counts don't add up!");
+ }
+
+ gjs_debug(GJS_DEBUG_MEMORY,
+ " %d objects currently alive",
+ GJS_GET_COUNTER(everything));
+
+ for (i = 0; i < n_counters; ++i) {
+ gjs_debug(GJS_DEBUG_MEMORY,
+ " %12s = %d",
+ counters[i]->name,
+ counters[i]->value);
+ }
+
+ if (die_if_leaks && GJS_GET_COUNTER(everything) > 0) {
+ g_error("%s: JavaScript objects were leaked.", where);
+ }
+}
Added: trunk/gjs/mem.h
==============================================================================
--- (empty file)
+++ trunk/gjs/mem.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,75 @@
+/* -*- 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_MEM_H__
+#define __GJS_MEM_H__
+
+#include <glib.h>
+#include <jsapi.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ int value;
+ const char *name;
+} GjsMemCounter;
+
+#define GJS_DECLARE_COUNTER(name) \
+ extern GjsMemCounter gjs_counter_ ## name ;
+
+GJS_DECLARE_COUNTER(everything)
+
+GJS_DECLARE_COUNTER(boxed)
+GJS_DECLARE_COUNTER(closure)
+GJS_DECLARE_COUNTER(database)
+GJS_DECLARE_COUNTER(dbus_exports)
+GJS_DECLARE_COUNTER(function)
+GJS_DECLARE_COUNTER(importer)
+GJS_DECLARE_COUNTER(ns)
+GJS_DECLARE_COUNTER(object)
+GJS_DECLARE_COUNTER(param)
+GJS_DECLARE_COUNTER(repo)
+GJS_DECLARE_COUNTER(resultset)
+GJS_DECLARE_COUNTER(weakhash)
+
+#define GJS_INC_COUNTER(name) \
+ do { \
+ gjs_counter_everything.value += 1; \
+ gjs_counter_ ## name .value += 1; \
+ } while (0)
+
+#define GJS_DEC_COUNTER(name) \
+ do { \
+ gjs_counter_everything.value -= 1; \
+ gjs_counter_ ## name .value -= 1; \
+ } while (0)
+
+#define GJS_GET_COUNTER(name) \
+ (gjs_counter_ ## name .value)
+
+void gjs_memory_report(const char *where,
+ gboolean die_if_leaks);
+
+G_END_DECLS
+
+#endif /* __GJS_MEM_H__ */
Added: trunk/gjs/native.c
==============================================================================
--- (empty file)
+++ trunk/gjs/native.c Fri Oct 10 21:37:39 2008
@@ -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 "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);
+ }
+}
+
Added: trunk/gjs/native.h
==============================================================================
--- (empty file)
+++ trunk/gjs/native.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,95 @@
+/* -*- 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__
+
+#ifndef __GJS_GJS_H__
+#warning Include <gjs/gjs.h> instead of <gjs/native.h>
+#endif
+
+#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__ */
Added: trunk/modules/gi.c
==============================================================================
--- (empty file)
+++ trunk/modules/gi.c Fri Oct 10 21:37:39 2008
@@ -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.
+ */
+
+#include "gi.h"
+
+#include <util/misc.h>
+
+#include <string.h>
+
+#include "gi/native.h"
+#include "gi/repo.h"
+
+JSBool
+gjs_define_gi_stuff(JSContext *context,
+ JSObject *module_obj)
+{
+ return gjs_define_repo(context, module_obj, "gi");
+}
+
+/* GJS_NATIVE_SUPPLIES_MODULE_OBJ means we will define "gi" instead
+ * of defining the stuff inside "gi" only.
+ */
+GJS_REGISTER_NATIVE_MODULE_WITH_FLAGS("gi",
+ gjs_define_gi_stuff,
+ GJS_NATIVE_SUPPLIES_MODULE_OBJ)
Added: trunk/modules/gi.h
==============================================================================
--- (empty file)
+++ trunk/modules/gi.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,40 @@
+/* -*- 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_GI_H__
+#define __GJS_GI_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_define_gi_stuff (JSContext *context,
+ JSObject *module_obj);
+
+G_END_DECLS
+
+#endif /* __GJS_GI_H__ */
Added: trunk/modules/lang.js
==============================================================================
--- (empty file)
+++ trunk/modules/lang.js Fri Oct 10 21:37:39 2008
@@ -0,0 +1,80 @@
+// 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.
+
+// Utilities that are "meta-language" things like manipulating object props
+
+function countProperties(obj) {
+ let count = 0;
+ for (let property in obj) {
+ count += 1;
+ }
+ return count;
+}
+
+function _copyProperty(source, dest, property) {
+ let getterFunc = source.__lookupGetter__(property);
+ let setterFunc = source.__lookupSetter__(property);
+
+ if (getterFunc) {
+ dest.__defineGetter__(property, getterFunc);
+ }
+
+ if (setterFunc) {
+ dest.__defineSetter__(property, setterFunc);
+ }
+
+ if (!setterFunc && !getterFunc) {
+ dest[property] = source[property];
+ }
+}
+
+function copyProperties(source, dest) {
+ for (let property in source) {
+ _copyProperty(source, dest, property);
+ }
+}
+
+function copyPublicProperties(source, dest) {
+ for (let property in source) {
+ if (typeof(property) == 'string' &&
+ property.substring(0, 1) == '_') {
+ continue;
+ } else {
+ _copyProperty(source, dest, property);
+ }
+ }
+}
+
+function copyPropertiesNoOverwrite(source, dest) {
+ for (let property in source) {
+ if (!(property in dest)) {
+ _copyProperty(source, dest, property);
+ }
+ }
+}
+
+function removeNullProperties(obj) {
+ for (let property in obj) {
+ if (obj[property] == null)
+ delete obj[property];
+ else if (typeof(obj[property]) == 'object')
+ removeNullProperties(obj[property]);
+ }
+}
Added: trunk/scripts/make-tests
==============================================================================
--- (empty file)
+++ trunk/scripts/make-tests Fri Oct 10 21:37:39 2008
@@ -0,0 +1,168 @@
+#! /usr/bin/python
+
+import os
+import sys
+import stat
+import re
+import errno
+
+FILE_NOT_THERE=1
+FILE_REGULAR=2
+FILE_DIRECTORY=3
+FILE_SYMLINK=4
+
+## return tuple of (kind, mtime, size), or (FILE_NOT_THERE,0,0) if file not found
+def stat_file(filename):
+ try:
+ s = os.stat(filename)
+ kind = 0
+ if stat.S_ISDIR(s.st_mode):
+ kind = FILE_DIRECTORY
+ elif stat.S_ISLNK(s.st_mode):
+ kind = FILE_SYMLINK
+ elif stat.S_ISREG(s.st_mode):
+ kind = FILE_REGULAR
+ else:
+ raise Exception("Unknown file type (not a directory, regular, or link) %s" % filename)
+
+ return (kind, s.st_mtime, s.st_size)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ return (FILE_NOT_THERE, 0, 0)
+ else:
+ raise
+
+test_func_re = re.compile('gjstest_test_func_([a-zA-Z0-9_]+)')
+
+def find_tests(base_filename, full_filename):
+ f = open(full_filename)
+ test_funcs = [] ## list of (path, funcname)
+
+ found_expected_test_func = False
+ in_tests = False
+ for l in f.readlines():
+ if 'GJS_BUILD_TESTS' in l:
+ if '#if' in l:
+ if in_tests:
+ raise Exception('GJS_BUILD_TESTS nested inside itself? ' + base_filename)
+ else:
+ in_tests = True
+ elif '#endif' in l:
+ # GJS_BUILD_TESTS should have been in a comment post-endif
+ if not in_tests:
+ raise Exception('#endif /* GJS_BUILD_TESTS */ found but no #if in ' + base_filename)
+ else:
+ in_tests = False
+
+ ## do the substring check before re match so we can plow
+ ## through the file quickly and only do the more expensive
+ ## match on relevant lines
+ elif 'gjstest_test_func_' in l:
+ match = test_func_re.search(l)
+ if not match:
+ raise Exception('line does not match test_func_re in ' + base_filename + ': ' + l)
+ subname = match.group(1)
+ funcname = 'gjstest_test_func_' + subname
+
+ if not in_tests:
+ raise Exception("Test func %s in %s not inside GJS_BUILD_TESTS" % (funcname, base_filename))
+
+ ## check namespacing
+ expected_subname = ''
+ rest = base_filename
+ while rest != '':
+ (rest, last) = os.path.split(rest)
+ if last != '':
+ last = last.replace('-', '_')
+ if expected_subname != '':
+ expected_subname = last + '_' + expected_subname
+ else:
+ (last, ext) = os.path.splitext(last)
+ expected_subname = last
+
+ if not subname.startswith(expected_subname):
+ raise Exception("Test funcs in '%s' should start with gjstest_test_func_%s" % (base_filename, expected_subname))
+
+ test_path = '/' + subname.replace('_', '/')
+
+ test_funcs.append((test_path, funcname))
+
+ if in_tests:
+ raise Exception('no #endif /* GJS_BUILD_TESTS */ found - comment with GJS_BUILD_TESTS in it is mandatory')
+
+ ## return a tuple, so we can add other kinds of stuff to find later
+ return (test_funcs)
+
+output_dir = sys.argv[1]
+input_files = sys.argv[2:]
+top_srcdir = os.getenv('abs_top_srcdir')
+
+out_header_name = os.path.join(output_dir, "gjstest.h")
+out_impl_name = os.path.join(output_dir, "gjstest.c")
+
+(out_header_kind, out_header_mtime, out_header_size) = stat_file(out_header_name)
+(out_impl_kind, out_impl_mtime, out_impl_size) = stat_file(out_impl_name)
+
+out_header_tmp_name = out_header_name + ".stamp"
+out_impl_tmp_name = out_impl_name + ".stamp"
+
+all_test_funcs = []
+
+for filename in input_files:
+ (test_funcs) = find_tests(filename, os.path.join(top_srcdir, filename))
+ all_test_funcs = all_test_funcs + test_funcs
+
+header_out = open(out_header_tmp_name, 'w')
+impl_out = open(out_impl_tmp_name, 'w')
+
+def write_generic_c_boilerplate(f):
+ f.write('/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */\n')
+ f.write('/* FILE AUTOGENERATED DO NOT EDIT */\n')
+ f.write('\n')
+
+write_generic_c_boilerplate(header_out)
+write_generic_c_boilerplate(impl_out)
+
+header_out.write('#ifndef __GJS_GJSTEST_GENERATED_H__\n')
+header_out.write('#define __GJS_GJSTEST_GENERATED_H__\n\n')
+header_out.write('#include <glib.h>\n\n')
+header_out.write('G_BEGIN_DECLS\n\n')
+
+header_out.write('void gjstest_add_all_tests(void);\n\n')
+
+impl_out.write('#include <gjstest.h>\n\n')
+
+impl_out.write('void\n')
+impl_out.write('gjstest_add_all_tests(void)\n')
+impl_out.write('{\n')
+
+for (path, func) in all_test_funcs:
+ impl_out.write(' g_test_add_func("%s", %s);\n' % (path, func))
+
+ header_out.write('void %s(void);\n' % (func))
+
+impl_out.write('}\n')
+
+header_out.write('\n')
+header_out.write('G_END_DECLS\n\n')
+header_out.write('#endif /* __GJS_GJSTEST_GENERATED_H__ */\n')
+
+## close so we can stat and rename
+impl_out.close()
+header_out.close()
+
+## rename only if changed, to avoid needless rebuilds.
+## we use the size to decide if it changed... not really
+## quite kosher, but will fail infrequently enough to
+## not be annoying
+(new_header_kind, new_header_mtime, new_header_size) = stat_file(out_header_tmp_name)
+(new_impl_kind, new_impl_mtime, new_impl_size) = stat_file(out_impl_tmp_name)
+
+if new_header_size != out_header_size or new_impl_size != out_impl_size:
+ print "Replacing old %s and %s" % (out_header_name, out_impl_name)
+ print " %s was %d bytes now %d" % (out_header_name, out_header_size, new_header_size)
+ print " %s was %d bytes now %d" % (out_impl_name, out_impl_size, new_impl_size)
+ os.rename(out_header_tmp_name, out_header_name)
+ os.rename(out_impl_tmp_name, out_impl_name)
+else:
+ print "%s and %s appear to be unchanged, not updating" % (out_header_name, out_impl_name)
Added: trunk/test/gjs-tests.c
==============================================================================
--- (empty file)
+++ trunk/test/gjs-tests.c Fri Oct 10 21:37:39 2008
@@ -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.
+ */
+
+#include <config.h>
+#include <glib.h>
+
+#include "test.h"
+
+/* we redefine main in test.h so we can include files with tests that have a main();
+ * here we actually want main, so we have to kill that off
+ */
+#undef main
+int
+main(int argc,
+ char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ gjstest_add_all_tests();
+
+ g_test_run();
+
+ return 0;
+}
Added: trunk/test/test.h
==============================================================================
--- (empty file)
+++ trunk/test/test.h Fri Oct 10 21:37:39 2008
@@ -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_TEST_TEST_H__
+#define __GJS_TEST_TEST_H__
+
+#include <glib.h>
+
+/* gjstest.h has prototypes for all test functions */
+#include <gjstest.h>
+
+G_BEGIN_DECLS
+
+/* Enable the test portion of each file */
+#define GJS_BUILD_TESTS 1
+
+/* We're building all files with tests into one
+ * huge binary, so disable any main() functions
+ */
+#define main static not_main
+
+G_END_DECLS
+
+#endif /* __GJS_TEST_TEST_H__ */
Added: trunk/util/dirs.c
==============================================================================
--- (empty file)
+++ trunk/util/dirs.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,165 @@
+/* -*- 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 "dirs.h"
+
+static gboolean initialized = FALSE;
+static gboolean installed = FALSE;
+static char ** cached[6];
+
+static void
+init_search_paths(void)
+{
+ guint i;
+
+ if (initialized)
+ return;
+
+ initialized = TRUE;
+
+ installed = ! gjs_environment_variable_is_set("GJS_USE_UNINSTALLED_FILES");
+
+ for (i = 0; i < G_N_ELEMENTS(cached); ++i) {
+ cached[i] = NULL;
+ }
+}
+
+static void
+ensure_search_path_in_cache(GjsDirectoryType dir_type)
+{
+ GPtrArray *path;
+ G_CONST_RETURN gchar* G_CONST_RETURN * system_data_dirs;
+
+ g_assert(G_N_ELEMENTS(cached) > (unsigned int) dir_type);
+
+ init_search_paths();
+
+ if (cached[dir_type] != NULL) {
+ return;
+ }
+
+ path = g_ptr_array_new();
+ system_data_dirs = g_get_system_data_dirs();
+
+ switch (dir_type) {
+ case GJS_DIRECTORY_SHARED_JAVASCRIPT: {
+ int i;
+
+ if (installed) {
+ g_ptr_array_add(path, g_strdup(GJS_JS_DIR));
+ } else {
+ char *s;
+ s = g_build_filename(GJS_TOP_SRCDIR, "modules", NULL);
+ g_ptr_array_add(path, s);
+ }
+
+ for (i = 0; system_data_dirs[i] != NULL; ++i) {
+ char *s;
+ s = g_build_filename(system_data_dirs[i], "gjs-1.0", NULL);
+ g_ptr_array_add(path, s);
+ }
+ }
+ break;
+
+ case GJS_DIRECTORY_SHARED_JAVASCRIPT_NATIVE: {
+ if (installed) {
+ g_ptr_array_add(path, g_strdup(GJS_NATIVE_DIR));
+ } else {
+ char *s;
+ s = g_build_filename(GJS_BUILDDIR, ".libs", NULL);
+ g_ptr_array_add(path, s);
+ }
+ }
+ break;
+
+ }
+
+ g_ptr_array_add(path, NULL);
+
+ cached[dir_type] = (char**) g_ptr_array_free(path, FALSE);
+}
+
+char**
+gjs_get_search_path(GjsDirectoryType dir_type)
+{
+ ensure_search_path_in_cache(dir_type);
+
+ g_assert(cached[dir_type] != NULL);
+
+ return g_strdupv(cached[dir_type]);
+}
+
+char*
+gjs_find_file_on_path (GjsDirectoryType dir_type,
+ const char *filename)
+{
+ int i;
+ char **path;
+
+ ensure_search_path_in_cache(dir_type);
+
+ path = cached[dir_type];
+
+ for (i = 0; path[i] != NULL; ++i) {
+ char *s;
+ gboolean exists;
+
+ s = g_build_filename(path[i], filename, NULL);
+
+ exists = g_file_test(s, G_FILE_TEST_EXISTS);
+
+ if (exists) {
+ return s;
+ }
+
+ g_free(s);
+ }
+
+ return NULL;
+}
+
+#if GJS_BUILD_TESTS
+#include <stdlib.h>
+
+void
+gjstest_test_func_util_dirs(void)
+{
+ char *filename;
+
+ putenv("GJS_USE_UNINSTALLED_FILES=1");
+
+ g_assert(gjs_find_file_on_path(GJS_DIRECTORY_SHARED_JAVASCRIPT, "no-such-file-as-this.js") == NULL);
+ g_assert(gjs_find_file_on_path(GJS_DIRECTORY_SHARED_JAVASCRIPT_NATIVE, "no-such-file-as-this.so") == NULL);
+
+ filename = gjs_find_file_on_path(GJS_DIRECTORY_SHARED_JAVASCRIPT, "lang.js");
+ g_assert(filename != NULL);
+ g_free(filename);
+
+ filename = gjs_find_file_on_path(GJS_DIRECTORY_SHARED_JAVASCRIPT_NATIVE, "gi.so");
+ g_assert(filename != NULL);
+ g_free(filename);
+}
+
+#endif /* GJS_BUILD_TESTS */
Added: trunk/util/dirs.h
==============================================================================
--- (empty file)
+++ trunk/util/dirs.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,43 @@
+/* -*- 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_UTIL_DIRS_H__
+#define __GJS_UTIL_DIRS_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GJS_DIRECTORY_SHARED_JAVASCRIPT,
+ GJS_DIRECTORY_SHARED_JAVASCRIPT_NATIVE
+} GjsDirectoryType;
+
+char** gjs_get_search_path (GjsDirectoryType dir_type);
+char* gjs_find_file_on_path (GjsDirectoryType dir_type,
+ const char *filename);
+
+
+G_END_DECLS
+
+#endif /* __GJS_UTIL_DIRS_H__ */
Added: trunk/util/error.c
==============================================================================
--- (empty file)
+++ trunk/util/error.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,31 @@
+/* -*- 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 "error.h"
+
+GQuark
+gjs_error_quark (void)
+{
+ return g_quark_from_static_string ("gjs-error-quark");
+}
Added: trunk/util/error.h
==============================================================================
--- (empty file)
+++ trunk/util/error.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,40 @@
+/* -*- 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_UTIL_ERROR_H__
+#define __GJS_UTIL_ERROR_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+GQuark gjs_error_quark(void);
+#define GJS_ERROR gjs_error_quark()
+
+typedef enum {
+ GJS_ERROR_FAILED
+} GjsError;
+
+G_END_DECLS
+
+#endif /* __GJS_UTIL_ERROR_H__ */
Added: trunk/util/glib.c
==============================================================================
--- (empty file)
+++ trunk/util/glib.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,168 @@
+/* -*- 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 "glib.h"
+
+typedef struct {
+ void *key;
+ void *value;
+} StoreOneData;
+
+static gboolean
+get_first_one_predicate(void *key,
+ void *value,
+ void *data)
+{
+ StoreOneData *sod = data;
+
+ sod->key = key;
+ sod->value = value;
+
+ /* found it! */
+ return TRUE;
+}
+
+static gboolean
+remove_or_steal_one(GHashTable *hash,
+ void **key_p,
+ void **value_p,
+ gboolean steal)
+{
+ StoreOneData sod;
+
+ sod.key = NULL;
+ sod.value = NULL;
+ g_hash_table_find(hash, get_first_one_predicate, &sod);
+
+ if (sod.key == NULL)
+ return FALSE;
+
+ if (key_p)
+ *key_p = sod.key;
+ if (value_p)
+ *value_p = sod.value;
+
+ if (steal)
+ g_hash_table_steal(hash, sod.key);
+ else
+ g_hash_table_remove(hash, sod.key);
+
+ return sod.value != NULL;
+}
+
+gboolean
+gjs_g_hash_table_remove_one(GHashTable *hash,
+ void **key_p,
+ void **value_p)
+{
+ return remove_or_steal_one(hash, key_p, value_p, FALSE);
+}
+
+gboolean
+gjs_g_hash_table_steal_one(GHashTable *hash,
+ void **key_p,
+ void **value_p)
+{
+ return remove_or_steal_one(hash, key_p, value_p, TRUE);
+}
+
+/** gjs_g_strv_concat:
+ *
+ * Concate an array of string arrays to one string array. The strings in each
+ * array is copied to the resulting array.
+ *
+ * @strv_array: array of NULL-terminated arrays of strings. NULL elements are
+ * allowed.
+ * @len: number of arrays in @strv_array
+ *
+ * @return: a newly allocated NULL-terminated array of strings. Use
+ * g_strfreev() to free it
+ */
+char**
+gjs_g_strv_concat(char ***strv_array, int len)
+{
+ GPtrArray *array;
+ int i;
+
+ array = g_ptr_array_sized_new(16);
+
+ for (i = 0; i < len; i++) {
+ char **strv;
+ int j;
+
+ strv = strv_array[i];
+ if (strv == NULL)
+ continue;
+
+ for (j = 0; strv[j] != NULL; ++j)
+ g_ptr_array_add(array, g_strdup(strv[j]));
+ }
+
+ g_ptr_array_add(array, NULL);
+
+ return (char**)g_ptr_array_free(array, FALSE);
+}
+
+#if GJS_BUILD_TESTS
+
+void
+gjstest_test_func_util_glib_strv_concat_null(void)
+{
+ char **ret;
+
+ ret = gjs_g_strv_concat(NULL, 0);
+ g_assert(ret != NULL);
+ g_assert(ret[0] == NULL);
+
+ g_strfreev(ret);
+}
+
+void
+gjstest_test_func_util_glib_strv_concat_pointers(void)
+{
+ char *strv0[2] = {"foo", NULL};
+ char *strv1[1] = {NULL};
+ char **strv2 = NULL;
+ char *strv3[2] = {"bar", NULL};
+ char **stuff[4];
+ char **ret;
+
+ stuff[0] = strv0;
+ stuff[1] = strv1;
+ stuff[2] = strv2;
+ stuff[3] = strv3;
+
+ ret = gjs_g_strv_concat(stuff, 4);
+ g_assert(ret != NULL);
+ g_assert_cmpstr(ret[0], ==, strv0[0]); /* same string */
+ g_assert(ret[0] != strv0[0]); /* different pointer */
+ g_assert_cmpstr(ret[1], ==, strv3[0]);
+ g_assert(ret[1] != strv3[0]);
+ g_assert(ret[2] == NULL);
+
+ g_strfreev(ret);
+}
+
+#endif /* GJS_BUILD_TESTS */
Added: trunk/util/glib.h
==============================================================================
--- (empty file)
+++ trunk/util/glib.h Fri Oct 10 21:37:39 2008
@@ -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_UTIL_GLIB_H__
+#define __GJS_UTIL_GLIB_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gboolean gjs_g_hash_table_remove_one (GHashTable *hash,
+ void **key_p,
+ void **value_p);
+gboolean gjs_g_hash_table_steal_one (GHashTable *hash,
+ void **key_p,
+ void **value_p);
+char** gjs_g_strv_concat (char ***strv_array,
+ int len);
+
+G_END_DECLS
+
+#endif /* __GJS_UTIL_GLIB_H__ */
Added: trunk/util/log.c
==============================================================================
--- (empty file)
+++ trunk/util/log.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,285 @@
+/* -*- 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 "log.h"
+#include "misc.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+void
+gjs_fatal(const char *format, ...)
+{
+ va_list args;
+ char *s;
+
+ va_start (args, format);
+ s = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ fputs(s, stderr);
+
+ if (!g_str_has_suffix(s, "\n"))
+ fputs("\n", stderr);
+ fputs("\n", stderr);
+
+ fflush(stderr);
+
+ g_free(s);
+
+ abort();
+ exit(1); /* not reached unless there's some weird SIGABRT handler */
+}
+
+/* prefix is allowed if it's in the ;-delimited environment variable
+ * GJS_DEBUG_TOPICS or if that variable is not set.
+ */
+static gboolean
+is_allowed_prefix (const char *prefix)
+{
+ static const char *topics = NULL;
+ static char **prefixes = NULL;
+ gboolean found = FALSE;
+ int i;
+
+ if (topics == NULL) {
+ topics = g_getenv("GJS_DEBUG_TOPICS");
+
+ if (!topics)
+ return TRUE;
+
+ /* We never really free this, should be gone when the process exits */
+ prefixes = g_strsplit(topics, ";", -1);
+ }
+
+ if (!prefixes)
+ return TRUE;
+
+ for (i = 0; prefixes[i] != NULL; i++) {
+ if (!strcmp(prefixes[i], prefix)) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ return found;
+}
+
+#define PREFIX_LENGTH 12
+
+void
+gjs_debug(GjsDebugTopic topic,
+ const char *format,
+ ...)
+{
+ static FILE *logfp = NULL;
+ static gboolean strace_timestamps = FALSE;
+ static gboolean checked_for_timestamp = FALSE;
+ static gboolean print_timestamp = FALSE;
+ static GTimer *timer = NULL;
+ const char *prefix;
+ va_list args;
+ char *s;
+ gboolean error;
+
+ if (!checked_for_timestamp) {
+ print_timestamp = gjs_environment_variable_is_set("GJS_DEBUG_TIMESTAMP");
+ checked_for_timestamp = TRUE;
+ }
+
+ if (print_timestamp && !timer) {
+ timer = g_timer_new();
+ }
+
+ if (logfp == NULL) {
+ const char *debug_output = g_getenv("GJS_DEBUG_OUTPUT");
+ if (debug_output != NULL) {
+ const char *log_file;
+ char *free_me;
+ char *c;
+
+ /* Allow debug-%u.log for per-pid logfiles as otherwise log
+ * messages from multiple processes can overwrite each other.
+ *
+ * (printf below should be safe as we check '%u' is the only format
+ * string)
+ */
+ c = strchr(debug_output, '%');
+ if (c && c[1] == 'u' && !strchr(c+1, '%')) {
+ free_me = g_strdup_printf(debug_output, (guint)getpid());
+ log_file = free_me;
+ } else {
+ log_file = debug_output;
+ free_me = NULL;
+ }
+
+ logfp = fopen(log_file, "w");
+ if (!logfp)
+ fprintf(stderr, "Failed to open log file `%s': %s\n",
+ log_file, g_strerror(errno));
+
+ g_free(free_me);
+ }
+
+ if (logfp == NULL)
+ logfp = stderr;
+
+ strace_timestamps = gjs_environment_variable_is_set("GJS_STRACE_TIMESTAMPS");
+ }
+
+ error = FALSE;
+ prefix = "???";
+ switch (topic) {
+ case GJS_DEBUG_STRACE_TIMESTAMP:
+ /* return early if strace timestamps are disabled, avoiding
+ * printf format overhead and so forth.
+ */
+ if (!strace_timestamps)
+ return;
+ /* this is a special magic topic for use with
+ * git clone http://www.gnome.org/~federico/git/performance-scripts.git
+ * http://www.gnome.org/~federico/news-2006-03.html#timeline-tools
+ */
+ prefix = "MARK";
+ break;
+ case GJS_DEBUG_GI_USAGE:
+ prefix = "JS GI USE";
+ break;
+ case GJS_DEBUG_ERROR:
+ prefix = "JS ERROR";
+ error = TRUE;
+ break;
+ case GJS_DEBUG_MEMORY:
+ prefix = "JS MEMORY";
+ break;
+ case GJS_DEBUG_LOG:
+ prefix = "JS LOG";
+ break;
+ case GJS_DEBUG_CONTEXT:
+ prefix = "JS CTX";
+ break;
+ case GJS_DEBUG_IMPORTER:
+ prefix = "JS IMPORT";
+ break;
+ case GJS_DEBUG_NATIVE:
+ prefix = "JS NATIVE";
+ break;
+ case GJS_DEBUG_DBUS:
+ prefix = "JS DBUS";
+ break;
+ case GJS_DEBUG_KEEP_ALIVE:
+ prefix = "JS KP ALV";
+ break;
+ case GJS_DEBUG_GREPO:
+ prefix = "JS G REPO";
+ break;
+ case GJS_DEBUG_GNAMESPACE:
+ prefix = "JS G NS";
+ break;
+ case GJS_DEBUG_GOBJECT:
+ prefix = "JS G OBJ";
+ break;
+ case GJS_DEBUG_GFUNCTION:
+ prefix = "JS G FUNC";
+ break;
+ case GJS_DEBUG_GCLOSURE:
+ prefix = "JS G CLSR";
+ break;
+ case GJS_DEBUG_GBOXED:
+ prefix = "JS G BXD";
+ break;
+ case GJS_DEBUG_GENUM:
+ prefix = "JS G ENUM";
+ break;
+ case GJS_DEBUG_GPARAM:
+ prefix = "JS G PRM";
+ break;
+ case GJS_DEBUG_DATABASE:
+ prefix = "JS DB";
+ break;
+ case GJS_DEBUG_RESULTSET:
+ prefix = "JS RS";
+ break;
+ case GJS_DEBUG_WEAK_HASH:
+ prefix = "JS WEAK";
+ break;
+ case GJS_DEBUG_MAINLOOP:
+ prefix = "JS MAINLOOP";
+ break;
+ case GJS_DEBUG_PROPS:
+ prefix = "JS PROPS";
+ break;
+ case GJS_DEBUG_SCOPE:
+ prefix = "JS SCOPE";
+ break;
+ case GJS_DEBUG_HTTP:
+ prefix = "JS HTTP";
+ break;
+ }
+
+ if (!is_allowed_prefix(prefix))
+ return;
+
+ va_start (args, format);
+ s = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ if (topic == GJS_DEBUG_STRACE_TIMESTAMP) {
+ /* Put a magic string in strace output */
+ char *s2;
+ s2 = g_strdup_printf("%s: gjs: %s",
+ prefix, s);
+ access(s2, F_OK);
+ g_free(s2);
+ } else {
+ if (print_timestamp) {
+ static gdouble previous = 0.0;
+ gdouble total = g_timer_elapsed(timer, NULL) * 1000.0;
+ gdouble since = total - previous;
+
+ fprintf(logfp, "%g ", total);
+ if (since > 50.0) {
+ fprintf(logfp, "!! ");
+ } else if (since > 100.0) {
+ fprintf(logfp, "!!! ");
+ } else if (since > 200.0) {
+ fprintf(logfp, "!!!!");
+ } else {
+ fprintf(logfp, " ");
+ }
+ previous = total;
+ }
+ fprintf(logfp, "%*s: %s%s", PREFIX_LENGTH, prefix, error ? "!!! " : "", s);
+ if (!g_str_has_suffix(s, "\n"))
+ fputs("\n", logfp);
+ fflush(logfp);
+ }
+
+ g_free(s);
+}
Added: trunk/util/log.h
==============================================================================
--- (empty file)
+++ trunk/util/log.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,138 @@
+/* -*- 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_UTIL_LOG_H__
+#define __GJS_UTIL_LOG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/* The idea of this is to be able to have one big log file for the entire
+ * environment, and grep out what you care about. So each module or app
+ * should have its own entry in the enum. Be sure to add new enum entries
+ * to the switch in log.c
+ */
+typedef enum {
+ GJS_DEBUG_STRACE_TIMESTAMP,
+ GJS_DEBUG_GI_USAGE,
+ GJS_DEBUG_ERROR,
+ GJS_DEBUG_MEMORY,
+ GJS_DEBUG_LOG,
+ GJS_DEBUG_CONTEXT,
+ GJS_DEBUG_IMPORTER,
+ GJS_DEBUG_NATIVE,
+ GJS_DEBUG_DBUS,
+ GJS_DEBUG_KEEP_ALIVE,
+ GJS_DEBUG_GREPO,
+ GJS_DEBUG_GNAMESPACE,
+ GJS_DEBUG_GOBJECT,
+ GJS_DEBUG_GFUNCTION,
+ GJS_DEBUG_GCLOSURE,
+ GJS_DEBUG_GBOXED,
+ GJS_DEBUG_GENUM,
+ GJS_DEBUG_GPARAM,
+ GJS_DEBUG_DATABASE,
+ GJS_DEBUG_RESULTSET,
+ GJS_DEBUG_WEAK_HASH,
+ GJS_DEBUG_MAINLOOP,
+ GJS_DEBUG_PROPS,
+ GJS_DEBUG_SCOPE,
+ GJS_DEBUG_HTTP,
+} GjsDebugTopic;
+
+/* These defines are because we have some pretty expensive and
+ * extremely verbose debug output in certain areas, that's useful
+ * sometimes, but just too much to compile in by default. The areas
+ * tend to be broader and less focused than the ones represented by
+ * GjsDebugTopic.
+ *
+ * Don't use these special "disabled by default" log macros to print
+ * anything that's an abnormal or error situation.
+ *
+ * Don't use them for one-time events, either. They are for routine
+ * stuff that happens over and over and would deluge the logs, so
+ * should be off by default.
+ */
+
+/* Whether to be verbose about JavaScript property access and resolution */
+#ifndef GJS_VERBOSE_ENABLE_PROPS
+#define GJS_VERBOSE_ENABLE_PROPS 0
+#endif
+
+/* Whether to be verbose about JavaScript function arg and closure marshaling */
+#ifndef GJS_VERBOSE_ENABLE_MARSHAL
+#define GJS_VERBOSE_ENABLE_MARSHAL 1
+#endif
+
+/* Whether to be verbose about constructing, destroying, and gc-rooting
+ * various kinds of JavaScript thingy
+ */
+#ifndef GJS_VERBOSE_ENABLE_LIFECYCLE
+#define GJS_VERBOSE_ENABLE_LIFECYCLE 0
+#endif
+
+/* Whether to log all gobject-introspection types and methods we use
+ */
+#ifndef GJS_VERBOSE_ENABLE_GI_USAGE
+#define GJS_VERBOSE_ENABLE_GI_USAGE 0
+#endif
+
+#if GJS_VERBOSE_ENABLE_PROPS
+#define gjs_debug_jsprop(topic, format...) \
+ do { gjs_debug(topic, format); } while(0)
+#else
+#define gjs_debug_jsprop(topic, format...)
+#endif
+
+#if GJS_VERBOSE_ENABLE_MARSHAL
+#define gjs_debug_marshal(topic, format...) \
+ do { gjs_debug(topic, format); } while(0)
+#else
+#define gjs_debug_marshal(topic, format...)
+#endif
+
+#if GJS_VERBOSE_ENABLE_LIFECYCLE
+#define gjs_debug_lifecycle(topic, format...) \
+ do { gjs_debug(topic, format); } while(0)
+#else
+#define gjs_debug_lifecycle(topic, format...)
+#endif
+
+#if GJS_VERBOSE_ENABLE_GI_USAGE
+#define gjs_debug_gi_usage(format...) \
+ do { gjs_debug(GJS_DEBUG_GI_USAGE, format); } while(0)
+#else
+#define gjs_debug_gi_usage(format...)
+#endif
+
+void gjs_fatal(const char *format,
+ ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN;
+
+void gjs_debug(GjsDebugTopic topic,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+G_END_DECLS
+
+#endif /* __GJS_UTIL_LOG_H__ */
Added: trunk/util/misc.c
==============================================================================
--- (empty file)
+++ trunk/util/misc.c Fri Oct 10 21:37:39 2008
@@ -0,0 +1,40 @@
+/* -*- 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 "misc.h"
+
+gboolean
+gjs_environment_variable_is_set(const char *env_variable_name)
+{
+ const char *s;
+
+ s = g_getenv(env_variable_name);
+ if (s == NULL)
+ return FALSE;
+
+ if (*s == '\0')
+ return FALSE;
+
+ return TRUE;
+}
Added: trunk/util/misc.h
==============================================================================
--- (empty file)
+++ trunk/util/misc.h Fri Oct 10 21:37:39 2008
@@ -0,0 +1,35 @@
+/* -*- 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_UTIL_MISC_H__
+#define __GJS_UTIL_MISC_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gboolean gjs_environment_variable_is_set (const char *env_variable_name);
+
+G_END_DECLS
+
+#endif /* __GJS_UTIL_MISC_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]