gjs r2 - in trunk: . examples gi gjs modules scripts test util



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(&parameter->value, G_PARAM_SPEC_VALUE_TYPE(param_spec));
+    if (!gjs_value_to_g_value(context, js_value, &parameter->value)) {
+        g_value_unset(&parameter->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),
+                                       &param)) {
+    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,
+                          &param.value);
+
+    g_value_unset(&param.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(&params[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,
+                                                       &params, &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 = &param_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]