[gjs/wip/ptomato/jasper-imports: 5/6] bootstrap: Add a JS implementation of the imports system



commit b47c7b5f594c98cb6dc60b52ffaef646351f3824
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Thu Jan 2 15:59:16 2014 -0500

    bootstrap: Add a JS implementation of the imports system
    
    Re-implement the old importer with a Proxy in bootstrap.js, reducing the
    footprint and making the code significantly easier to understand.

 Makefile-modules.am              |    7 +-
 Makefile.am                      |    3 -
 gi/repo.cpp                      |  380 +------------
 gi/repo.h                        |    4 -
 gjs/bootstrap.cpp                |   53 ++-
 gjs/context.cpp                  |   34 +-
 gjs/context.h                    |    2 +
 gjs/gi.h                         |    3 -
 gjs/importer.cpp                 | 1152 --------------------------------------
 gjs/importer.h                   |   50 --
 gjs/jsapi-util.h                 |    7 -
 modules/bootstrap.js             |  143 +++++-
 modules/importer.cpp             |  127 +++++
 gjs/gi.cpp => modules/importer.h |   25 +-
 modules/modules.cpp              |    2 +
 15 files changed, 358 insertions(+), 1634 deletions(-)
---
diff --git a/Makefile-modules.am b/Makefile-modules.am
index 3165d86..238a32f 100644
--- a/Makefile-modules.am
+++ b/Makefile-modules.am
@@ -1,6 +1,5 @@
 
-NATIVE_MODULES = libconsole.la libsystem.la libmodules_resources.la
-
+NATIVE_MODULES = libconsole.la libsystem.la libmodules_resources.la libimporter.la
 if ENABLE_CAIRO
 NATIVE_MODULES += libcairoNative.la
 endif
@@ -56,3 +55,7 @@ libsystem_la_SOURCES = modules/system.h modules/system.cpp
 libconsole_la_CPPFLAGS = $(JS_NATIVE_MODULE_CPPFLAGS)
 libconsole_la_LIBADD = $(JS_NATIVE_MODULE_LIBADD) $(READLINE_LIBS)
 libconsole_la_SOURCES = modules/console.h modules/console.cpp
+
+libimporter_la_CPPFLAGS = $(JS_NATIVE_MODULE_CPPFLAGS)
+libimporter_la_LIBADD = $(JS_NATIVE_MODULE_LIBADD)
+libimporter_la_SOURCES = modules/importer.h modules/importer.cpp
diff --git a/Makefile.am b/Makefile.am
index f781f35..dea173c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -80,9 +80,7 @@ libgjs_la_SOURCES =           \
        gjs/byteArray.cpp               \
        gjs/context.cpp         \
        gjs/context-private.h           \
-       gjs/importer.cpp                \
        gjs/gi.h                \
-       gjs/gi.cpp              \
        gjs/coverage-internal.h \
        gjs/coverage.cpp \
        gjs/jsapi-constructor-proxy.cpp \
@@ -154,7 +152,6 @@ libgjs_la_SOURCES += $(libgjs_private_source_files)
 # These used to be public headers for external modules
 libgjs_la_SOURCES +=           \
        gjs/byteArray.h         \
-       gjs/importer.h          \
        gjs/jsapi-util.h        \
        gjs/jsapi-wrapper.h     \
        gjs/runtime.h           \
diff --git a/gi/repo.cpp b/gi/repo.cpp
index 65726ef..f0ca12d 100644
--- a/gi/repo.cpp
+++ b/gi/repo.cpp
@@ -45,276 +45,6 @@
 #include <girepository.h>
 #include <string.h>
 
-typedef struct {
-    void *dummy;
-
-} Repo;
-
-extern struct JSClass gjs_repo_class;
-
-GJS_DEFINE_PRIV_FROM_JS(Repo, gjs_repo_class)
-
-static bool lookup_override_function(JSContext *, JS::HandleId,
-                                     JS::MutableHandleValue);
-
-static bool
-get_version_for_ns (JSContext       *context,
-                    JS::HandleObject repo_obj,
-                    JS::HandleId     ns_id,
-                    char           **version)
-{
-    JS::RootedObject versions(context);
-    JS::RootedValue version_val(context);
-    JS::RootedId versions_name(context,
-        gjs_context_get_const_string(context, GJS_STRING_GI_VERSIONS));
-
-    if (!gjs_object_require_property_value(context, repo_obj,
-                                           "GI repository object", versions_name,
-                                           &versions))
-        return false;
-
-    if (!gjs_object_require_property_value(context, versions, NULL, ns_id, version)) {
-        /* Property not actually required, so clear an exception */
-        JS_ClearPendingException(context);
-        *version = NULL;
-    }
-
-    return true;
-}
-
-static bool
-resolve_namespace_object(JSContext       *context,
-                         JS::HandleObject repo_obj,
-                         JS::HandleId     ns_id,
-                         const char      *ns_name)
-{
-    char *version;
-
-    JSAutoRequest ar(context);
-
-    if (!get_version_for_ns(context, repo_obj, ns_id, &version))
-        return false;
-
-    /* Defines a property on "obj" (the javascript repo object)
-     * with the given namespace name, pointing to that namespace
-     * in the repo.
-     */
-    JS::RootedObject gi_namespace(context);
-    if (!gjs_import_gi_module(context, ns_name, version, &gi_namespace)) {
-        g_free(version);
-        return false;
-    }
-
-    g_free(version);
-
-    /* Define the property early, to avoid reentrancy issues if
-       the override module looks for namespaces that import this */
-    if (!JS_DefineProperty(context, repo_obj, ns_name, gi_namespace,
-                           GJS_MODULE_PROP_FLAGS))
-        g_error("no memory to define ns property");
-
-    JS::RootedValue override(context);
-    if (!lookup_override_function(context, ns_id, &override))
-        return false;
-
-    JS::RootedValue result(context);
-    if (!override.isUndefined() &&
-        !JS_CallFunctionValue (context, gi_namespace, /* thisp */
-                               override, /* callee */
-                               JS::HandleValueArray::empty(), &result))
-        return false;
-
-    gjs_debug(GJS_DEBUG_GNAMESPACE,
-              "Defined namespace '%s' %p in GIRepository %p", ns_name,
-              gi_namespace.get(), repo_obj.get());
-
-    gjs_schedule_gc_if_needed(context);
-    return true;
-}
-
-/*
- * 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 bool
-repo_new_resolve(JSContext *context,
-                 JS::HandleObject obj,
-                 JS::HandleId id,
-                 JS::MutableHandleObject objp)
-{
-    Repo *priv;
-    char *name;
-    bool ret = true;
-
-    if (!gjs_get_string_id(context, id, &name))
-        return true; /* not resolved, but no error */
-
-    /* let Object.prototype resolve these */
-    if (strcmp(name, "valueOf") == 0 ||
-        strcmp(name, "toString") == 0)
-        goto out;
-
-    priv = priv_from_js(context, obj);
-    gjs_debug_jsprop(GJS_DEBUG_GREPO, "Resolve prop '%s' hook obj %p priv %p",
-                     name, obj.get(), priv);
-
-    if (priv == NULL) /* we are the prototype, or have the wrong class */
-        goto out;
-
-    if (!resolve_namespace_object(context, obj, id, name)) {
-        ret = false;
-    } else {
-        objp.set(obj); /* store the object we defined the prop in */
-    }
-
- out:
-    g_free(name);
-    return ret;
-}
-
-GJS_NATIVE_CONSTRUCTOR_DEFINE_ABSTRACT(repo)
-
-static void
-repo_finalize(JSFreeOp *fop,
-              JSObject *obj)
-{
-    Repo *priv;
-
-    priv = (Repo*) JS_GetPrivate(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 */
-
-    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.
- */
-struct JSClass gjs_repo_class = {
-    "GIRepository", /* means "new GIRepository()" works */
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_NEW_RESOLVE |
-    JSCLASS_IMPLEMENTS_BARRIERS,
-    JS_PropertyStub,
-    JS_DeletePropertyStub,
-    JS_PropertyStub,
-    JS_StrictPropertyStub,
-    JS_EnumerateStub,
-    (JSResolveOp) repo_new_resolve, /* needs cast since it's the new resolve signature */
-    JS_ConvertStub,
-    repo_finalize
-};
-
-JSPropertySpec gjs_repo_proto_props[] = {
-    JS_PS_END
-};
-
-JSFunctionSpec gjs_repo_proto_funcs[] = {
-    JS_FS_END
-};
-
-static JSObject*
-repo_new(JSContext *context)
-{
-    Repo *priv;
-    JSObject *versions;
-    JSObject *private_ns;
-    bool found;
-
-    JS::RootedObject global(context, gjs_get_import_global(context));
-
-    if (!JS_HasProperty(context, global, gjs_repo_class.name, &found))
-        return NULL;
-    if (!found) {
-        JSObject *prototype;
-        prototype = JS_InitClass(context, global,
-                                 /* parent prototype JSObject* for
-                                  * prototype; NULL for
-                                  * Object.prototype
-                                  */
-                                 JS::NullPtr(),
-                                 &gjs_repo_class,
-                                 /* constructor for instances (NULL for
-                                  * none - just name the prototype like
-                                  * Math - rarely correct)
-                                  */
-                                 gjs_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)
-            g_error("Can't init class %s", gjs_repo_class.name);
-
-        gjs_debug(GJS_DEBUG_GREPO, "Initialized class %s prototype %p",
-                  gjs_repo_class.name, prototype);
-    }
-
-    JS::RootedObject repo(context,
-        JS_NewObject(context, &gjs_repo_class, JS::NullPtr(), global));
-    if (repo == NULL) {
-        gjs_throw(context, "No memory to create repo object");
-        return NULL;
-    }
-
-    priv = g_slice_new0(Repo);
-
-    GJS_INC_COUNTER(repo);
-
-    g_assert(priv_from_js(context, repo) == NULL);
-    JS_SetPrivate(repo, priv);
-
-    gjs_debug_lifecycle(GJS_DEBUG_GREPO,
-                        "repo constructor, obj %p priv %p", repo.get(), priv);
-
-    versions = JS_NewObject(context, NULL, JS::NullPtr(), global);
-    JS::RootedId versions_name(context,
-        gjs_context_get_const_string(context, GJS_STRING_GI_VERSIONS));
-    JS_DefinePropertyById(context, repo,
-                          versions_name,
-                          JS::ObjectValue(*versions),
-                          NULL, NULL,
-                          JSPROP_PERMANENT);
-
-    private_ns = JS_NewObject(context, NULL, JS::NullPtr(), global);
-    JS::RootedId private_ns_name(context,
-        gjs_context_get_const_string(context, GJS_STRING_PRIVATE_NS_MARKER));
-    JS_DefinePropertyById(context, repo,
-                          private_ns_name,
-                          JS::ObjectValue(*private_ns),
-                          NULL, NULL, JSPROP_PERMANENT);
-
-    /* FIXME - hack to make namespaces load, since
-     * gobject-introspection does not yet search a path properly.
-     */
-    {
-        JS::RootedValue value(context);
-        JS_GetProperty(context, repo, "GLib", &value);
-    }
-
-    return repo;
-}
-
-bool
-gjs_define_repo(JSContext              *cx,
-                JS::MutableHandleObject repo,
-                const char             *name)
-{
-    repo.set(repo_new(cx));
-    return true;
-}
-
 static bool
 gjs_define_constant(JSContext       *context,
                     JS::HandleObject in_object,
@@ -555,114 +285,24 @@ gjs_lookup_namespace_object(JSContext  *context,
     return gjs_lookup_namespace_object_by_name(context, ns_name);
 }
 
-/* Check if an exception's 'name' property is equal to compare_name. Ignores
- * all errors that might arise. Requires request. */
-static bool
-error_has_name(JSContext       *cx,
-               JS::HandleValue  thrown_value,
-               JSString        *compare_name)
-{
-    if (!thrown_value.isObject())
-        return false;
-
-    JS::AutoSaveExceptionState saved_exc(cx);
-    JS::RootedId name_id(cx, gjs_context_get_const_string(cx, GJS_STRING_NAME));
-    JS::RootedObject exc(cx, &thrown_value.toObject());
-    JS::RootedValue exc_name(cx);
-    bool retval = false;
-
-    if (!JS_GetPropertyById(cx, exc, name_id, &exc_name))
-        goto out;
-
-    int32_t cmp_result;
-    if (!JS_CompareStrings(cx, exc_name.toString(), compare_name, &cmp_result))
-        goto out;
-
-    if (cmp_result == 0)
-        retval = true;
-
-out:
-    saved_exc.restore();
-    return retval;
-}
-
-static bool
-lookup_override_function(JSContext             *cx,
-                         JS::HandleId           ns_name,
-                         JS::MutableHandleValue function)
-{
-    JSAutoRequest ar(cx);
-    JS::AutoSaveExceptionState saved_exc(cx);
-
-    JS::RootedValue importer(cx, gjs_get_global_slot(cx, GJS_GLOBAL_SLOT_IMPORTS));
-    g_assert(importer.isObject());
-
-    JS::RootedId overrides_name(cx,
-        gjs_context_get_const_string(cx, GJS_STRING_GI_OVERRIDES));
-    JS::RootedId object_init_name(cx,
-        gjs_context_get_const_string(cx, GJS_STRING_GOBJECT_INIT));
-    JS::RootedObject overridespkg(cx), module(cx);
-    JS::RootedObject importer_obj(cx, &importer.toObject());
-
-    if (!gjs_object_require_property_value(cx, importer_obj, "importer",
-                                           overrides_name, &overridespkg))
-        goto fail;
-
-    if (!gjs_object_require_property_value(cx, overridespkg, "GI repository object",
-                                           ns_name, &module)) {
-        JS::RootedValue exc(cx);
-        JS_GetPendingException(cx, &exc);
-
-        /* If the exception was an ImportError (i.e., module not found) then
-         * we simply didn't have an override, don't throw an exception */
-        if (error_has_name(cx, exc, JS_InternString(cx, "ImportError"))) {
-            saved_exc.restore();
-            return true;
-        }
-
-        goto fail;
-    }
-
-    if (!gjs_object_require_property(cx, module, "override module",
-                                     object_init_name, function) ||
-        !function.isObjectOrNull()) {
-        gjs_throw(cx, "Unexpected value for _init in overrides module");
-        goto fail;
-    }
-    return true;
-
- fail:
-    saved_exc.drop();
-    return false;
-}
-
 JSObject*
-gjs_lookup_namespace_object_by_name(JSContext      *context,
-                                    JS::HandleId    ns_name)
+gjs_lookup_namespace_object_by_name(JSContext   *context,
+                                    JS::HandleId ns_name)
 {
-    JSAutoRequest ar(context);
+    g_autofree char *name = NULL;
 
-    JS::RootedValue importer(context,
-        gjs_get_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS));
-    g_assert(importer.isObject());
-
-    JS::RootedId gi_name(context,
-        gjs_context_get_const_string(context, GJS_STRING_GI_MODULE));
-    JS::RootedObject repo(context), importer_obj(context, &importer.toObject());
+    JSAutoRequest ar(context);
 
-    if (!gjs_object_require_property_value(context, importer_obj, "importer",
-                                           gi_name, &repo)) {
-        gjs_log_exception(context);
-        gjs_throw(context, "No gi property in importer");
+    if (!gjs_get_string_id(context, ns_name, &name))
         return NULL;
-    }
 
-    JS::RootedObject retval(context);
-    if (!gjs_object_require_property_value(context, repo, "GI repository object",
-                                           ns_name, &retval))
+    g_autofree char *script = g_strdup_printf("imports.gi.%s;", name);
+    JS::RootedValue ns_val(context);
+    if (!gjs_eval_with_scope(context, JS::NullPtr(), script, -1, "<internal>",
+                             &ns_val))
         return NULL;
 
-    return retval;
+    return &ns_val.toObject();
 }
 
 const char*
diff --git a/gi/repo.h b/gi/repo.h
index a3a38fc..46be4b3 100644
--- a/gi/repo.h
+++ b/gi/repo.h
@@ -34,10 +34,6 @@
 
 G_BEGIN_DECLS
 
-bool gjs_define_repo(JSContext              *cx,
-                     JS::MutableHandleObject repo,
-                     const char             *name);
-
 const char* gjs_info_type_name                  (GIInfoType      type);
 JSObject*   gjs_lookup_private_namespace        (JSContext      *context);
 JSObject*   gjs_lookup_namespace_object         (JSContext      *context,
diff --git a/gjs/bootstrap.cpp b/gjs/bootstrap.cpp
index 8c6f4f5..372f487 100644
--- a/gjs/bootstrap.cpp
+++ b/gjs/bootstrap.cpp
@@ -27,15 +27,64 @@
 
 #include "bootstrap.h"
 #include "gjs/jsapi-util.h"
+#include "gjs/jsapi-util-args.h"
 #include "gjs/jsapi-wrapper.h"
+#include "native.h"
+
+/* The bootstrap process is the thing that sets up the import system.
+ * As such, we give it a hook to import any native modules it may need.
+ *
+ * The rest of the functionality that the bootstrap code needs should be
+ * in independent native modules which can be imported by this API,
+ * rather than in the bootstrap environment.
+ */
+
+static bool
+import_native_module(JSContext *cx,
+                     unsigned   argc,
+                     jsval     *vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    g_autofree char *module_name = NULL;
+
+    if (!gjs_parse_call_args(cx, "importNativeModule", args, "s",
+                             "moduleName", &module_name))
+        return false;
+
+    JS::RootedObject module_obj(cx);
+    if (!gjs_import_native_module(cx, module_name, &module_obj))
+        return false;
+
+    args.rval().setObjectOrNull(module_obj);
+    return true;
+}
+
+static JSFunctionSpec environment_funcs[] = {
+    JS_FS("importNativeModule", import_native_module, 1, GJS_MODULE_PROP_FLAGS),
+    JS_FS_END,
+};
+
+static bool
+define_bootstrap_environment(JSContext              *cx,
+                             JS::MutableHandleObject environment)
+{
+    environment.set(JS_NewObject(cx, NULL, JS::NullPtr(), JS::NullPtr()));
+
+    if (!environment)
+        return false;
+
+    return JS_DefineFunctions(cx, environment, &environment_funcs[0]);
+}
 
 #define BOOTSTRAP_FILE "resource:///org/gnome/gjs/modules/bootstrap.js"
 
 bool
 gjs_run_bootstrap(JSContext *cx)
 {
-    JS::RootedObject environment(cx,
-        JS_NewObject(cx, NULL, JS::NullPtr(), JS::NullPtr()));
+    JS::RootedObject environment(cx);
+    define_bootstrap_environment(cx, &environment);
+    if (!environment)
+        return false;
 
     g_autoptr(GFile) file = g_file_new_for_uri(BOOTSTRAP_FILE);
     g_autofree char *script = NULL;
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 6838d69..6496005 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -27,7 +27,6 @@
 
 #include "context-private.h"
 #include "bootstrap.h"
-#include "importer.h"
 #include "jsapi-constructor-proxy.h"
 #include "jsapi-private.h"
 #include "jsapi-util.h"
@@ -82,10 +81,8 @@ struct _GjsContext {
 /* Keep this consistent with GjsConstString */
 static const char *const_strings[] = {
     "constructor", "prototype", "length",
-    "imports", "__parentModule__", "__init__", "searchPath",
     "__gjsKeepAlive", "__gjsPrivateNS",
-    "gi", "versions", "overrides",
-    "_init", "_instance_init", "_new_internal", "new",
+    "gi", "_init", "_instance_init", "_new_internal", "new",
     "message", "code", "stack", "fileName", "lineNumber", "name",
     "x", "y", "width", "height", "__modulePath__"
 };
@@ -360,7 +357,6 @@ gjs_context_class_init(GjsContextClass *klass)
 
     gjs_register_native_module("byteArray", gjs_define_byte_array_stuff);
     gjs_register_native_module("_gi", gjs_define_private_gi_stuff);
-    gjs_register_native_module("gi", gjs_define_gi_stuff);
 
     gjs_register_static_modules();
 }
@@ -493,23 +489,8 @@ gjs_context_constructed(GObject *object)
 
     gjs_define_constructor_proxy_factory(js_context->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->context,
-                                  js_context->search_path ?
-                                  (const char**) js_context->search_path :
-                                  NULL,
-                                  true))
-        g_error("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))
-        g_error("Failed to point 'imports' property at root importer");
+    if (!gjs_run_bootstrap(js_context->context))
+        g_error("Failed to bootstrap GJS context");
 
     /* FIXME: We should define the Promise object before any imports, in case
      * the imports want to use it. Currently that's not possible as it needs to
@@ -517,9 +498,6 @@ gjs_context_constructed(GObject *object)
     if(!gjs_define_promise_object(js_context->context, global))
         g_error("Failed to define global Promise object");
 
-    if (!gjs_run_bootstrap(js_context->context))
-        g_error("Failed to bootstrap GJS context");
-
     JS_EndRequest(js_context->context);
 
     g_mutex_lock (&contexts_lock);
@@ -888,6 +866,12 @@ gjs_get_import_global(JSContext *context)
     return gjs_context->global;
 }
 
+const char **
+gjs_context_get_search_path(GjsContext *context)
+{
+    return (const char **) context->search_path;
+}
+
 const char * const *
 gjs_get_search_path(void)
 {
diff --git a/gjs/context.h b/gjs/context.h
index c1abff0..44997d6 100644
--- a/gjs/context.h
+++ b/gjs/context.h
@@ -78,6 +78,8 @@ void            gjs_context_gc                    (GjsContext  *context);
 
 void            gjs_dumpstack                     (void);
 
+const char **gjs_context_get_search_path(GjsContext *context);
+
 const char * const *gjs_get_search_path(void);
 
 G_END_DECLS
diff --git a/gjs/gi.h b/gjs/gi.h
index 22d1b70..3445687 100644
--- a/gjs/gi.h
+++ b/gjs/gi.h
@@ -30,9 +30,6 @@
 
 G_BEGIN_DECLS
 
-bool gjs_define_gi_stuff(JSContext              *cx,
-                         JS::MutableHandleObject module);
-
 bool gjs_define_private_gi_stuff(JSContext              *cx,
                                  JS::MutableHandleObject module);
 
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index c26e40f..b9429a5 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -45,7 +45,6 @@ enum {
 };
 
 typedef enum {
-    GJS_GLOBAL_SLOT_IMPORTS,
     GJS_GLOBAL_SLOT_KEEP_ALIVE,
     GJS_GLOBAL_SLOT_BYTE_ARRAY_PROTOTYPE,
     GJS_GLOBAL_SLOT_LAST,
@@ -441,15 +440,9 @@ typedef enum {
   GJS_STRING_CONSTRUCTOR,
   GJS_STRING_PROTOTYPE,
   GJS_STRING_LENGTH,
-  GJS_STRING_IMPORTS,
-  GJS_STRING_PARENT_MODULE,
-  GJS_STRING_MODULE_INIT,
-  GJS_STRING_SEARCH_PATH,
   GJS_STRING_KEEP_ALIVE_MARKER,
   GJS_STRING_PRIVATE_NS_MARKER,
   GJS_STRING_GI_MODULE,
-  GJS_STRING_GI_VERSIONS,
-  GJS_STRING_GI_OVERRIDES,
   GJS_STRING_GOBJECT_INIT,
   GJS_STRING_INSTANCE_INIT,
   GJS_STRING_NEW_INTERNAL,
diff --git a/modules/bootstrap.js b/modules/bootstrap.js
index 45ab533..7e27a5f 100644
--- a/modules/bootstrap.js
+++ b/modules/bootstrap.js
@@ -1,6 +1,143 @@
-(function(exports) {
+(function(exports, importNativeModule) {
     "use strict";
 
-    // Do early initialization here.
+    const Importer = importNativeModule('_importer');
+    const Gio = Importer.importGIModule('Gio', '2.0');
 
-})(window);
+    function runOverridesForGIModule(module, moduleID) {
+        let overridesModule = imports.overrides[moduleID];
+        if (!overridesModule)
+            return;
+
+        let initFunc = overridesModule._init;
+        if (!initFunc)
+            return;
+
+        initFunc.call(module);
+    }
+
+    function importGIModuleWithOverrides(parent, moduleID, moduleVersion) {
+        let module = Importer.importGIModule(moduleID, moduleVersion);
+        parent[moduleID] = module;
+        runOverridesForGIModule(module, moduleID);
+    }
+
+    function installImports() {
+        // Implement the global "imports" object.
+
+        // imports.gi
+        let gi = new Proxy({
+            versions: {},
+            __gjsPrivateNS: {},
+        }, {
+            get: function(target, name) {
+                if (!target[name]) {
+                    let version = target.versions[name] || null;
+                    importGIModuleWithOverrides(target, name, version);
+                }
+
+                return target[name];
+            },
+        });
+
+        function importModule(module, file) {
+            let success, script;
+            try {
+                [success, script] = file.load_contents(null);
+            } catch(e) {
+                return null;
+            }
+
+            // Don't catch errors for the eval, as those should propagate
+            // back up to the user...
+            Importer.evalWithScope(module, script, file.get_parse_name());
+            return module;
+        }
+
+        function importFile(parent, name, file) {
+            let module = {};
+            parent[name] = module;
+            module.__file__ = file.get_parse_name();
+            module.__moduleName__ = name;
+            module.__parentModule__ = parent;
+            importModule(module, file);
+        }
+
+        function importDirectory(parent, name) {
+            let searchPath = parent.searchPath.map(function(path) {
+                return path + '/' + name;
+            }).filter(function(path) {
+                let file = Gio.File.new_for_commandline_arg(path);
+                let type = file.query_file_type(Gio.FileQueryInfoFlags.NONE, null);
+                return (type == Gio.FileType.DIRECTORY);
+            });
+
+            let module = createSearchPathImporter();
+            parent[name] = module;
+            module.searchPath = searchPath;
+            module.__moduleName__ = name;
+            module.__parentModule__ = parent;
+
+            tryImport(module, '__init__');
+        }
+
+        function tryImport(proxy, name) {
+            function tryPath(path) {
+                let file, type;
+                file = Gio.File.new_for_commandline_arg(path);
+                type = file.query_file_type(Gio.FileQueryInfoFlags.NONE, null);
+                if (type == Gio.FileType.DIRECTORY) {
+                    importDirectory(proxy, name);
+                    return true;
+                } else {
+                    file = Gio.File.new_for_commandline_arg(path + '.js');
+                    if (file.query_exists(null)) {
+                        importFile(proxy, name, file);
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            for (let path of proxy.searchPath) {
+                let modulePath = path + '/' + name;
+                if (tryPath(modulePath))
+                    return;
+            }
+        }
+
+        function createSearchPathImporter() {
+            let proxy = new Proxy({ __init__: {} }, {
+                get: function(target, name) {
+                    if (target.__init__[name])
+                        return target.__init__[name];
+
+                    if (!target[name])
+                        tryImport(proxy, name);
+
+                    return target[name];
+                },
+            });
+            return proxy;
+        }
+
+        let rootDirectoryImporter = createSearchPathImporter();
+        rootDirectoryImporter.searchPath = Importer.getBuiltinSearchPath();
+
+        // root importer, checks for native modules
+        let rootImporter = new Proxy(rootDirectoryImporter, {
+            get: function(target, name) {
+                if (!target[name])
+                    target[name] = importNativeModule(name);
+                if (!target[name])
+                    target[name] = rootDirectoryImporter[name];
+                return target[name];
+            },
+        });
+        rootImporter.gi = gi;
+
+        exports.imports = rootImporter;
+    }
+    installImports();
+
+})(window, importNativeModule);
diff --git a/modules/importer.cpp b/modules/importer.cpp
new file mode 100644
index 0000000..f15e02f
--- /dev/null
+++ b/modules/importer.cpp
@@ -0,0 +1,127 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * 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 "gi/ns.h"
+#include "gjs/byteArray.h"
+#include "gjs/context.h"
+#include "gjs/jsapi-util-args.h"
+#include "importer.h"
+
+static bool
+import_gi_module(JSContext *cx,
+                 unsigned   argc,
+                 jsval     *vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    g_autofree char *module_name = NULL;
+    g_autofree char *module_version = NULL;
+
+    if (!gjs_parse_call_args(cx, "importGIModule", args, "s?s",
+                             "moduleName", &module_name,
+                             "moduleVersion", &module_version))
+        return false;
+
+    JS::RootedObject module_obj(cx);
+    if (!gjs_import_gi_module(cx, module_name, module_version, &module_obj))
+        return false;
+
+    args.rval().setObject(*module_obj);
+    return true;
+}
+
+static bool
+eval_with_scope(JSContext *cx,
+                unsigned   argc,
+                jsval     *vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedObject scope(cx);
+    JS::RootedObject script_obj(cx);
+    guint8 *script;
+    size_t script_len;
+    g_autofree char *filename = NULL;
+
+    if (!gjs_parse_call_args(cx, "evalWithScope", args, "oos",
+                             "scope", &scope,
+                             "script", &script_obj,
+                             "filename", &filename))
+        return false;
+
+    gjs_byte_array_peek_data(cx, script_obj, &script, &script_len);
+
+    return gjs_eval_with_scope(cx, scope, (const char *) script, script_len,
+                               filename, args.rval());
+}
+
+static bool
+get_builtin_search_path(JSContext *cx,
+                        unsigned   argc,
+                        jsval     *vp)
+{
+    GjsContext *gjs_context = GJS_CONTEXT(JS_GetContextPrivate(cx));
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    const char **context_search_path;
+    int context_search_path_length;
+    const char **global_search_path;
+    int global_search_path_length;
+    JS::AutoValueVector elems(cx);
+    int i;
+
+    context_search_path = gjs_context_get_search_path(gjs_context);
+    context_search_path_length = context_search_path ? g_strv_length((char **) context_search_path) : 0;
+    global_search_path = (const char **) gjs_get_search_path();
+    global_search_path_length = global_search_path ? g_strv_length((char **) global_search_path) : 0;
+
+    elems.reserve(context_search_path_length + global_search_path_length);
+
+    JS::RootedValue element(cx);
+    for (i = 0; i < context_search_path_length; i++) {
+        element.setString(JS_NewStringCopyZ(cx, context_search_path[i]));
+        elems.infallibleAppend(element);
+    }
+
+    for (i = 0; i < global_search_path_length; i++) {
+        element.setString(JS_NewStringCopyZ(cx, global_search_path[i]));
+        elems.infallibleAppend(element);
+    }
+
+    args.rval().setObject(*JS_NewArrayObject(cx, elems));
+    return true;
+}
+
+static JSFunctionSpec module_funcs[] = {
+    JS_FS("importGIModule", import_gi_module, 2, GJS_MODULE_PROP_FLAGS),
+    JS_FS("evalWithScope", eval_with_scope, 3, GJS_MODULE_PROP_FLAGS),
+    JS_FS("getBuiltinSearchPath", get_builtin_search_path, 0, GJS_MODULE_PROP_FLAGS),
+    JS_FS_END,
+};
+
+bool
+gjs_js_define_importer_stuff(JSContext              *cx,
+                             JS::MutableHandleObject module)
+{
+    module.set(JS_NewObject(cx, NULL, JS::NullPtr(), JS::NullPtr()));
+    return JS_DefineFunctions(cx, module, &module_funcs[0]);
+}
diff --git a/gjs/gi.cpp b/modules/importer.h
similarity index 76%
rename from gjs/gi.cpp
rename to modules/importer.h
index b82618d..b126e59 100644
--- a/gjs/gi.cpp
+++ b/modules/importer.h
@@ -1,6 +1,6 @@
 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
 /*
- * Copyright (c) 2008  litl, LLC
+ * Copyright 2013 Red Hat, Inc.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to
@@ -21,19 +21,18 @@
  * IN THE SOFTWARE.
  */
 
-#include "gi.h"
+#ifndef GJS_MODULE_IMPORTER_H
+#define GJS_MODULE_IMPORTER_H
 
-#include <util/misc.h>
+#include <config.h>
+#include <glib.h>
+#include "gjs/jsapi-util.h"
 
-#include <string.h>
+G_BEGIN_DECLS
 
-#include "gjs/native.h"
-#include "gjs/jsapi-wrapper.h"
-#include "gi/repo.h"
+bool gjs_js_define_importer_stuff(JSContext              *context,
+                                  JS::MutableHandleObject module_out);
 
-bool
-gjs_define_gi_stuff(JSContext              *cx,
-                    JS::MutableHandleObject module)
-{
-    return gjs_define_repo(cx, module, "gi");
-}
+G_END_DECLS
+
+#endif  /* GJS_MODULE_IMPORTER_H */
diff --git a/modules/modules.cpp b/modules/modules.cpp
index 555171d..4223599 100644
--- a/modules/modules.cpp
+++ b/modules/modules.cpp
@@ -32,6 +32,7 @@
 
 #include "system.h"
 #include "console.h"
+#include "importer.h"
 
 void
 gjs_register_static_modules (void)
@@ -40,5 +41,6 @@ gjs_register_static_modules (void)
     gjs_register_native_module("cairoNative", gjs_js_define_cairo_stuff);
 #endif
     gjs_register_native_module("system", gjs_js_define_system_stuff);
+    gjs_register_native_module("_importer", gjs_js_define_importer_stuff);
     gjs_register_native_module("console", gjs_define_console_stuff);
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]