summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJasper St. Pierre <jstpierre@mecheye.net>2014-01-02 15:59:16 -0500
committerJasper St. Pierre <jstpierre@mecheye.net>2015-06-17 16:55:37 -0700
commit75b676e8164eb09e3da1a9bd4ceae15c9c1073a1 (patch)
treeea9f2d85982b9f4fa98349ae4c11e3a7275c5120
parentd4b8115affa6dd6c42b0073c83e6f1a54431c774 (diff)
downloadgjs-wip/imports-rewrite.tar.gz
bootstrap: Add a JS implementation of the imports systemwip/imports-rewrite
Re-implement the old importer with a Proxy in bootstrap.js, reducing the footprint and making the code significantly easier to understand.
-rw-r--r--Makefile-modules.am7
-rw-r--r--Makefile.am5
-rw-r--r--gi/repo.cpp384
-rw-r--r--gi/repo.h3
-rw-r--r--gi/union.cpp1
-rw-r--r--gjs/bootstrap.cpp42
-rw-r--r--gjs/context.cpp30
-rw-r--r--gjs/context.h2
-rw-r--r--gjs/gi.h2
-rw-r--r--gjs/gjs-module.h1
-rw-r--r--gjs/importer.cpp1118
-rw-r--r--gjs/importer.h50
-rw-r--r--gjs/jsapi-util.h7
-rw-r--r--modules/bootstrap.js143
-rw-r--r--modules/importer.cpp153
-rw-r--r--modules/importer.h (renamed from gjs/gi.cpp)25
-rw-r--r--modules/modules.cpp2
17 files changed, 377 insertions, 1598 deletions
diff --git a/Makefile-modules.am b/Makefile-modules.am
index 4b6e356d..4151c5d7 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_CFLAGS)
libconsole_la_LIBADD = $(JS_NATIVE_MODULE_LIBADD) $(READLINE_LIBS)
libconsole_la_SOURCES = modules/console.h modules/console.cpp
+
+libimporter_la_CPPFLAGS = $(JS_NATIVE_MODULE_CFLAGS)
+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 835bb866..57744628 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -35,7 +35,6 @@ nobase_gjs_module_include_HEADERS = \
gjs/compat.h \
gjs/coverage.h \
gjs/byteArray.h \
- gjs/importer.h \
gjs/jsapi-util.h \
gjs/runtime.h \
gjs/type-module.h \
@@ -112,10 +111,8 @@ libgjs_la_SOURCES = \
gjs/byteArray.cpp \
gjs/context.cpp \
gjs/bootstrap.cpp \
- gjs/importer.cpp \
gjs/gi.h \
- gjs/gi.cpp \
- gjs/coverage.cpp \
+ gjs/coverage.cpp \
gjs/jsapi-private.cpp \
gjs/jsapi-util.cpp \
gjs/jsapi-dynamic-class.cpp \
diff --git a/gi/repo.cpp b/gi/repo.cpp
index fd665ed2..2b56e75c 100644
--- a/gi/repo.cpp
+++ b/gi/repo.cpp
@@ -39,6 +39,7 @@
#include <gjs/compat.h>
#include <gjs/jsapi-private.h>
+#include <gjs/runtime.h>
#include <util/log.h>
#include <util/misc.h>
@@ -46,298 +47,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 JSObject * lookup_override_function(JSContext *, jsid);
-
-static JSBool
-get_version_for_ns (JSContext *context,
- JSObject *repo_obj,
- jsid ns_id,
- char **version)
-{
- jsid versions_name;
- jsval versions_val;
- JSObject *versions;
- jsval version_val;
-
- versions_name = gjs_context_get_const_string(context, GJS_STRING_GI_VERSIONS);
- if (!gjs_object_require_property(context, repo_obj, "GI repository object", versions_name, &versions_val) ||
- !JSVAL_IS_OBJECT(versions_val)) {
- gjs_throw(context, "No 'versions' property in GI repository object");
- return JS_FALSE;
- }
-
- versions = JSVAL_TO_OBJECT(versions_val);
-
- *version = NULL;
- if (JS_GetPropertyById(context, versions, ns_id, &version_val) &&
- JSVAL_IS_STRING(version_val)) {
- gjs_string_to_utf8(context, version_val, version);
- }
-
- return JS_TRUE;
-}
-
-static JSBool
-resolve_namespace_object(JSContext *context,
- JSObject *repo_obj,
- jsid ns_id,
- const char *ns_name)
-{
- char *version = NULL;
- JSObject *override;
- jsval result;
- JSObject *gi_namespace = NULL;
- JSBool ret = JS_FALSE;
-
- JS_BeginRequest(context);
-
- if (!get_version_for_ns(context, repo_obj, ns_id, &version))
- goto out;
-
- /* Defines a property on "obj" (the javascript repo object)
- * with the given namespace name, pointing to that namespace
- * in the repo.
- */
- if (!gjs_import_gi_module(context, ns_name, version, &gi_namespace))
- goto out;
-
- JS_AddObjectRoot(context, &gi_namespace);
-
- /* 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, OBJECT_TO_JSVAL(gi_namespace),
- NULL, NULL,
- GJS_MODULE_PROP_FLAGS))
- g_error("no memory to define ns property");
-
- override = lookup_override_function(context, ns_id);
- if (override && !JS_CallFunctionValue (context,
- gi_namespace, /* thisp */
- OBJECT_TO_JSVAL(override), /* callee */
- 0, /* argc */
- NULL, /* argv */
- &result))
- goto out;
-
- gjs_debug(GJS_DEBUG_GNAMESPACE,
- "Defined namespace '%s' %p in GIRepository %p", ns_name, gi_namespace, repo_obj);
-
- ret = JS_TRUE;
- gjs_schedule_gc_if_needed(context);
-
- out:
- g_free(version);
- if (gi_namespace)
- JS_RemoveObjectRoot(context, &gi_namespace);
- JS_EndRequest(context);
- return ret;
-}
-
-/*
- * 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,
- JS::HandleObject obj,
- JS::HandleId id,
- unsigned flags,
- JS::MutableHandleObject objp)
-{
- Repo *priv;
- char *name;
- JSBool ret = JS_TRUE;
-
- if (!gjs_get_string_id(context, id, &name))
- return JS_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, priv);
-
- if (priv == NULL) /* we are the prototype, or have the wrong class */
- goto out;
-
- if (!resolve_namespace_object(context, obj, id, name)) {
- ret = JS_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,
- 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,
- JSCLASS_NO_OPTIONAL_MEMBERS
-};
-
-JSPropertySpec gjs_repo_proto_props[] = {
- { NULL }
-};
-
-JSFunctionSpec gjs_repo_proto_funcs[] = {
- { NULL }
-};
-
-static JSObject*
-repo_new(JSContext *context)
-{
- Repo *priv;
- JSObject *repo;
- JSObject *global;
- JSObject *versions;
- JSObject *private_ns;
- JSBool found;
- jsid versions_name, private_ns_name;
-
- global = 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
- */
- NULL,
- &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);
- }
-
- repo = JS_NewObject(context, &gjs_repo_class, NULL, 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, priv);
-
- versions = JS_NewObject(context, NULL, NULL, global);
- versions_name = gjs_context_get_const_string(context, GJS_STRING_GI_VERSIONS);
- JS_DefinePropertyById(context, repo,
- versions_name,
- OBJECT_TO_JSVAL(versions),
- NULL, NULL,
- JSPROP_PERMANENT);
-
- private_ns = JS_NewObject(context, NULL, NULL, global);
- private_ns_name = gjs_context_get_const_string(context, GJS_STRING_PRIVATE_NS_MARKER);
- JS_DefinePropertyById(context, repo,
- private_ns_name,
- OBJECT_TO_JSVAL(private_ns),
- NULL, NULL, JSPROP_PERMANENT);
-
- /* 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_out,
- const char *name)
-{
- JSObject *repo;
-
- repo = repo_new(context);
- *module_out = repo;
-
- return JS_TRUE;
-}
-
static JSBool
gjs_define_constant(JSContext *context,
JSObject *in_object,
@@ -533,7 +242,6 @@ JSObject*
gjs_lookup_private_namespace(JSContext *context)
{
jsid ns_name;
-
ns_name = gjs_context_get_const_string(context, GJS_STRING_PRIVATE_NS_MARKER);
return gjs_lookup_namespace_object_by_name(context, ns_name);
}
@@ -559,95 +267,29 @@ gjs_lookup_namespace_object(JSContext *context,
return gjs_lookup_namespace_object_by_name(context, ns_name);
}
-static JSObject*
-lookup_override_function(JSContext *context,
- jsid ns_name)
-{
- jsval importer;
- jsval overridespkg;
- jsval module;
- jsval function;
- jsid overrides_name, object_init_name;
-
- JS_BeginRequest(context);
-
- importer = gjs_get_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS);
- g_assert(JSVAL_IS_OBJECT(importer));
-
- overridespkg = JSVAL_VOID;
- overrides_name = gjs_context_get_const_string(context, GJS_STRING_GI_OVERRIDES);
- if (!gjs_object_require_property(context, JSVAL_TO_OBJECT(importer), "importer",
- overrides_name, &overridespkg) ||
- !JSVAL_IS_OBJECT(overridespkg))
- goto fail;
-
- module = JSVAL_VOID;
- if (!gjs_object_require_property(context, JSVAL_TO_OBJECT(overridespkg), "GI repository object", ns_name, &module)
- || !JSVAL_IS_OBJECT(module))
- goto fail;
-
- object_init_name = gjs_context_get_const_string(context, GJS_STRING_GOBJECT_INIT);
- if (!gjs_object_require_property(context, JSVAL_TO_OBJECT(module), "override module",
- object_init_name, &function) ||
- !JSVAL_IS_OBJECT(function))
- goto fail;
-
- JS_EndRequest(context);
- return JSVAL_TO_OBJECT(function);
-
- fail:
- JS_ClearPendingException(context);
- JS_EndRequest(context);
- return NULL;
-}
-
JSObject*
gjs_lookup_namespace_object_by_name(JSContext *context,
jsid ns_name)
{
- JSObject *repo_obj;
- jsval importer;
- jsval girepository;
- jsval ns_obj;
- jsid gi_name;
+ char *name;
+ char *script;
+ jsval ns_val;
+ JSObject *ns_obj = NULL;
JS_BeginRequest(context);
- importer = gjs_get_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS);
- g_assert(JSVAL_IS_OBJECT(importer));
-
- girepository = JSVAL_VOID;
- gi_name = gjs_context_get_const_string(context, GJS_STRING_GI_MODULE);
- if (!gjs_object_require_property(context, JSVAL_TO_OBJECT(importer), "importer",
- gi_name, &girepository) ||
- !JSVAL_IS_OBJECT(girepository)) {
- gjs_log_exception(context);
- gjs_throw(context, "No gi property in importer");
- goto fail;
- }
-
- repo_obj = JSVAL_TO_OBJECT(girepository);
-
- if (!gjs_object_require_property(context, repo_obj, "GI repository object", ns_name, &ns_obj)) {
- goto fail;
- }
-
- if (!JSVAL_IS_OBJECT(ns_obj)) {
- char *name;
-
- gjs_get_string_id(context, ns_name, &name);
- gjs_throw(context, "Namespace '%s' is not an object?", name);
+ if (!gjs_get_string_id(context, ns_name, &name))
+ goto out;
- g_free(name);
- goto fail;
- }
+ script = g_strdup_printf("imports.gi.%s;", name);
+ if (!gjs_eval_with_scope(context, NULL, script, -1, "<internal>", &ns_val))
+ goto out;
- JS_EndRequest(context);
- return JSVAL_TO_OBJECT(ns_obj);
+ ns_obj = JSVAL_TO_OBJECT(ns_val);
- fail:
+ out:
JS_EndRequest(context);
- return NULL;
+ return ns_obj;
}
const char*
diff --git a/gi/repo.h b/gi/repo.h
index ba1fd90a..c2b277ef 100644
--- a/gi/repo.h
+++ b/gi/repo.h
@@ -32,9 +32,6 @@
G_BEGIN_DECLS
-JSBool gjs_define_repo (JSContext *context,
- JSObject **module_out,
- 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/gi/union.cpp b/gi/union.cpp
index 23ca458a..1a28bfff 100644
--- a/gi/union.cpp
+++ b/gi/union.cpp
@@ -25,7 +25,6 @@
#include <string.h>
-/* include first for logging related #define used in repo.h */
#include <util/log.h>
#include "union.h"
diff --git a/gjs/bootstrap.cpp b/gjs/bootstrap.cpp
index b049ec4b..406c26f5 100644
--- a/gjs/bootstrap.cpp
+++ b/gjs/bootstrap.cpp
@@ -26,9 +26,48 @@
#include <gjs/gjs.h>
#include "bootstrap.h"
+#include "native.h"
#include <gio/gio.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 JSBool
+import_native_module(JSContext *context,
+ unsigned argc,
+ jsval *vp)
+{
+ JSBool ret = JS_FALSE;
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ char *module_name = NULL;
+ JSObject *module_obj;
+
+ if (!gjs_parse_call_args(context, "importNativeModule", "s", args,
+ "moduleName", &module_name))
+ goto out;
+
+ if (!gjs_import_native_module(context, module_name, &module_obj))
+ goto out;
+
+ ret = JS_TRUE;
+ args.rval().setObjectOrNull(module_obj);
+
+ out:
+ g_free(module_name);
+ return ret;
+}
+
+static JSFunctionSpec environment_funcs[] = {
+ { "importNativeModule", JSOP_WRAPPER (import_native_module), 1, GJS_MODULE_PROP_FLAGS },
+ { NULL },
+};
+
static gboolean
define_bootstrap_environment(JSContext *context,
JSObject **environment_out)
@@ -38,6 +77,9 @@ define_bootstrap_environment(JSContext *context,
if (!environment)
return FALSE;
+ if (!JS_DefineFunctions(context, environment, &environment_funcs[0]))
+ return FALSE;
+
*environment_out = environment;
return TRUE;
}
diff --git a/gjs/context.cpp b/gjs/context.cpp
index d1bd77c4..20d12df2 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-private.h"
#include "jsapi-util.h"
#include "native.h"
@@ -78,10 +77,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",
};
@@ -321,7 +318,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();
}
@@ -443,24 +439,6 @@ gjs_context_constructed(GObject *object)
if (!JS_DefineFunctions(js_context->context, js_context->global, &global_funcs[0]))
g_error("Failed to define properties on the global object");
- /* 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");
@@ -793,6 +771,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;
+}
+
G_CONST_RETURN char * G_CONST_RETURN *
gjs_get_search_path(void)
{
diff --git a/gjs/context.h b/gjs/context.h
index 175cccaf..54969a05 100644
--- a/gjs/context.h
+++ b/gjs/context.h
@@ -77,6 +77,8 @@ void gjs_context_gc (GjsContext *context);
void gjs_dumpstack (void);
+const char ** gjs_context_get_search_path (GjsContext *context);
+
G_CONST_RETURN char * G_CONST_RETURN * gjs_get_search_path (void);
G_END_DECLS
diff --git a/gjs/gi.h b/gjs/gi.h
index 837d03d0..786fc61c 100644
--- a/gjs/gi.h
+++ b/gjs/gi.h
@@ -30,8 +30,6 @@
G_BEGIN_DECLS
-JSBool gjs_define_gi_stuff (JSContext *context,
- JSObject **module_out);
JSBool gjs_define_private_gi_stuff (JSContext *context,
JSObject **module_out);
diff --git a/gjs/gjs-module.h b/gjs/gjs-module.h
index f85f89b5..f6441399 100644
--- a/gjs/gjs-module.h
+++ b/gjs/gjs-module.h
@@ -27,7 +27,6 @@
#include <gjs/gjs.h>
#include <gjs/native.h>
#include <gjs/mem.h>
-#include <gjs/importer.h>
#include <gjs/runtime.h>
#include <gjs/jsapi-util.h>
diff --git a/gjs/importer.cpp b/gjs/importer.cpp
deleted file mode 100644
index 47659963..00000000
--- a/gjs/importer.cpp
+++ /dev/null
@@ -1,1118 +0,0 @@
-/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
-/*
- * Copyright (c) 2008-2010 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/glib.h>
-
-#include <gjs/gjs-module.h>
-#include <gjs/importer.h>
-#include <gjs/compat.h>
-
-#include <gio/gio.h>
-
-#include <string.h>
-
-#define MODULE_INIT_FILENAME "__init__.js"
-
-typedef struct {
- gboolean is_root;
-} Importer;
-
-typedef struct {
- GPtrArray *elements;
- unsigned int index;
-} ImporterIterator;
-
-extern 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 *full_path,
- 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 = parent && 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 (full_path != NULL) {
- if (!JS_DefineProperty(context, module_obj,
- "__file__",
- STRING_TO_JSVAL(JS_NewStringCopyZ(context, full_path)),
- 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,
- "__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
-define_import(JSContext *context,
- JSObject *obj,
- JSObject *module_obj,
- const char *name)
-{
- if (!JS_DefineProperty(context, obj,
- name, OBJECT_TO_JSVAL(module_obj),
- NULL, NULL,
- GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) {
- gjs_debug(GJS_DEBUG_IMPORTER,
- "Failed to define '%s' in importer",
- name);
- return JS_FALSE;
- }
-
- return JS_TRUE;
-}
-
-/* Make the property we set in define_import permament;
- * we do this after the import succesfully completes.
- */
-static JSBool
-seal_import(JSContext *context,
- JSObject *obj,
- const char *name)
-{
- JSBool found;
- unsigned attrs;
-
- if (!JS_GetPropertyAttributes(context, obj, name,
- &attrs, &found) || !found) {
- gjs_debug(GJS_DEBUG_IMPORTER,
- "Failed to get attributes to seal '%s' in importer",
- name);
- return JS_FALSE;
- }
-
- attrs |= JSPROP_PERMANENT;
-
- if (!JS_SetPropertyAttributes(context, obj, name,
- attrs, &found) || !found) {
- gjs_debug(GJS_DEBUG_IMPORTER,
- "Failed to set attributes to seal '%s' in importer",
- name);
- return JS_FALSE;
- }
-
- return JS_TRUE;
-}
-
-/* An import failed. Delete the property pointing to the import
- * from the parent namespace. In complicated situations this might
- * not be sufficient to get us fully back to a sane state. If:
- *
- * - We import module A
- * - module A imports module B
- * - module B imports module A, storing a reference to the current
- * module A module object
- * - module A subsequently throws an exception
- *
- * Then module B is left imported, but the imported module B has
- * a reference to the failed module A module object. To handle this
- * we could could try to track the entire "import operation" and
- * roll back *all* modifications made to the namespace objects.
- * It's not clear that the complexity would be worth the small gain
- * in robustness. (You can still come up with ways of defeating
- * the attempt to clean up.)
- */
-static void
-cancel_import(JSContext *context,
- JSObject *obj,
- const char *name)
-{
- gjs_debug(GJS_DEBUG_IMPORTER,
- "Cleaning up from failed import of '%s'",
- name);
-
- if (!JS_DeleteProperty(context, obj, name)) {
- gjs_debug(GJS_DEBUG_IMPORTER,
- "Failed to delete '%s' in importer",
- name);
- }
-}
-
-static JSBool
-import_native_file(JSContext *context,
- JSObject *obj,
- const char *name)
-{
- JSObject *module_obj;
- JSBool retval = JS_FALSE;
-
- gjs_debug(GJS_DEBUG_IMPORTER, "Importing '%s'", name);
-
- if (!gjs_import_native_module(context, name, &module_obj))
- goto out;
-
- if (!define_meta_properties(context, module_obj, NULL, name, obj))
- goto out;
-
- 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);
- goto out;
- }
-
- if (!JS_DefineProperty(context, obj,
- name, OBJECT_TO_JSVAL(module_obj),
- NULL, NULL, GJS_MODULE_PROP_FLAGS))
- goto out;
-
- retval = JS_TRUE;
-
- out:
- return retval;
-}
-
-static JSObject *
-create_module_object(JSContext *context)
-{
- return JS_NewObject(context, NULL, NULL, NULL);
-}
-
-static JSBool
-import_file(JSContext *context,
- const char *name,
- GFile *file,
- JSObject *module_obj)
-{
- JSBool ret = JS_FALSE;
- char *script = NULL;
- char *full_path = NULL;
- gsize script_len = 0;
- jsval script_retval;
- GError *error = NULL;
-
- JS::CompileOptions options(context);
-
- if (!(g_file_load_contents(file, NULL, &script, &script_len, NULL, &error))) {
- if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY) &&
- !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY) &&
- !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
- gjs_throw_g_error(context, error);
- else
- g_error_free(error);
-
- goto out;
- }
-
- g_assert(script != NULL);
-
- full_path = g_file_get_parse_name (file);
-
- if (!gjs_eval_with_scope(context, module_obj, script, script_len,
- full_path, NULL))
- goto out;
-
- ret = JS_TRUE;
-
- out:
- g_free(script);
- g_free(full_path);
- return ret;
-}
-
-static JSObject *
-load_module_init(JSContext *context,
- JSObject *in_object,
- const char *full_path)
-{
- JSObject *module_obj;
- JSBool found;
- jsid module_init_name;
- GFile *file;
-
- /* First we check if js module has already been loaded */
- module_init_name = gjs_context_get_const_string(context, GJS_STRING_MODULE_INIT);
- if (JS_HasPropertyById(context, in_object, module_init_name, &found) && found) {
- jsval module_obj_val;
-
- if (JS_GetPropertyById(context,
- in_object,
- module_init_name,
- &module_obj_val)) {
- return JSVAL_TO_OBJECT(module_obj_val);
- }
- }
-
- module_obj = create_module_object (context);
- file = g_file_new_for_commandline_arg(full_path);
- if (!import_file (context, "__init__", file, module_obj))
- goto out;
-
- if (!JS_DefinePropertyById(context, in_object,
- module_init_name, OBJECT_TO_JSVAL(module_obj),
- NULL, NULL,
- GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT))
- goto out;
-
- out:
- g_object_unref (file);
- return module_obj;
-}
-
-static void
-load_module_elements(JSContext *context,
- JSObject *in_object,
- ImporterIterator *iter,
- const char *init_path) {
- JSObject *module_obj;
- JSObject *jsiter;
-
- module_obj = load_module_init(context, in_object, init_path);
-
- if (module_obj != NULL) {
- jsid idp;
-
- jsiter = JS_NewPropertyIterator(context, module_obj);
-
- if (jsiter == NULL) {
- return;
- }
-
- if (!JS_NextProperty(context, jsiter, &idp)) {
- return;
- }
-
- while (!JSID_IS_VOID(idp)) {
- char *name;
-
- if (!gjs_get_string_id(context, idp, &name)) {
- continue;
- }
-
- /* Pass ownership of name */
- g_ptr_array_add(iter->elements, name);
-
- if (!JS_NextProperty(context, jsiter, &idp)) {
- break;
- }
- }
- }
-}
-
-static JSBool
-import_file_on_module(JSContext *context,
- JSObject *obj,
- const char *name,
- GFile *file)
-{
- JSObject *module_obj;
- JSBool retval = JS_FALSE;
- char *full_path = NULL;
-
- module_obj = create_module_object (context);
-
- if (!define_import(context, obj, module_obj, name))
- goto out;
-
- if (!import_file(context, name, file, module_obj))
- goto out;
-
- full_path = g_file_get_parse_name (file);
- if (!define_meta_properties(context, module_obj, full_path, name, obj))
- goto out;
-
- if (!seal_import(context, obj, name))
- goto out;
-
- retval = JS_TRUE;
-
- out:
- if (!retval)
- cancel_import(context, obj, name);
-
- g_free (full_path);
- return retval;
-}
-
-static JSBool
-do_import(JSContext *context,
- JSObject *obj,
- Importer *priv,
- const char *name)
-{
- char *filename;
- char *full_path;
- char *dirname = NULL;
- jsval search_path_val;
- JSObject *search_path;
- JSObject *module_obj = NULL;
- guint32 search_path_len;
- guint32 i;
- JSBool result;
- GPtrArray *directories;
- jsid search_path_name;
- GFile *gfile;
- gboolean exists;
-
- search_path_name = gjs_context_get_const_string(context, GJS_STRING_SEARCH_PATH);
- if (!gjs_object_require_property(context, obj, "importer", search_path_name, &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);
- full_path = NULL;
- directories = NULL;
-
- /* First try importing an internal module like byteArray */
- if (priv->is_root &&
- gjs_is_registered_native_module(context, obj, name) &&
- import_native_file(context, obj, name)) {
- gjs_debug(GJS_DEBUG_IMPORTER,
- "successfully imported module '%s'", name);
- result = JS_TRUE;
- goto out;
- }
-
- 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 (JSVAL_IS_VOID(elem))
- continue;
-
- if (!JSVAL_IS_STRING(elem)) {
- gjs_throw(context, "importer searchPath contains non-string");
- goto out;
- }
-
- g_free(dirname);
- dirname = NULL;
-
- if (!gjs_string_to_utf8(context, elem, &dirname))
- goto out; /* Error message already set */
-
- /* Ignore empty path elements */
- if (dirname[0] == '\0')
- continue;
-
- /* Try importing __init__.js and loading the symbol from it */
- if (full_path)
- g_free(full_path);
- full_path = g_build_filename(dirname, MODULE_INIT_FILENAME,
- NULL);
-
- module_obj = load_module_init(context, obj, full_path);
- if (module_obj != NULL) {
- jsval obj_val;
-
- if (JS_GetProperty(context,
- module_obj,
- name,
- &obj_val)) {
- if (!JSVAL_IS_VOID(obj_val) &&
- JS_DefineProperty(context, obj,
- name, obj_val,
- NULL, NULL,
- GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) {
- result = JS_TRUE;
- goto out;
- }
- }
- }
-
- /* Second try importing a directory (a sub-importer) */
- if (full_path)
- g_free(full_path);
- full_path = g_build_filename(dirname, name,
- NULL);
- gfile = g_file_new_for_commandline_arg(full_path);
-
- if (g_file_query_file_type(gfile, (GFileQueryInfoFlags) 0, NULL) == G_FILE_TYPE_DIRECTORY) {
- 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;
- }
-
- g_object_unref(gfile);
-
- /* 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;
- }
-
- /* Third, if it's not a directory, try importing a file */
- g_free(full_path);
- full_path = g_build_filename(dirname, filename,
- NULL);
- gfile = g_file_new_for_commandline_arg(full_path);
- exists = g_file_query_exists(gfile, NULL);
-
- if (!exists) {
- gjs_debug(GJS_DEBUG_IMPORTER,
- "JS import '%s' not found in %s",
- name, dirname);
-
- g_object_unref(gfile);
- continue;
- }
-
- if (import_file_on_module (context, obj, name, gfile)) {
- gjs_debug(GJS_DEBUG_IMPORTER,
- "successfully imported module '%s'", name);
- result = JS_TRUE;
- }
-
- g_object_unref(gfile);
-
- /* 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;
- }
-
- 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(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(void)
-{
- 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,
- JS::HandleObject object,
- JSIterateOp enum_op,
- JS::MutableHandleValue statep,
- JS::MutableHandleId idp)
-{
- ImporterIterator *iter;
-
- switch (enum_op) {
- case JSENUMERATE_INIT_ALL:
- case JSENUMERATE_INIT: {
- Importer *priv;
- JSObject *search_path;
- jsval search_path_val;
- guint32 search_path_len;
- guint32 i;
- jsid search_path_name;
-
- statep.set(JSVAL_NULL);
-
- idp.set(INT_TO_JSID(0));
-
- priv = priv_from_js(context, object);
-
- if (!priv)
- /* we are enumerating the prototype properties */
- return JS_TRUE;
-
- search_path_name = gjs_context_get_const_string(context, GJS_STRING_SEARCH_PATH);
- if (!gjs_object_require_property(context, object, "importer", search_path_name, &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;
- char *init_path;
- 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 (JSVAL_IS_VOID(elem))
- 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 */
- }
-
- init_path = g_build_filename(dirname, MODULE_INIT_FILENAME,
- NULL);
-
- load_module_elements(context, object, iter, init_path);
-
- g_free(init_path);
-
- dir = g_dir_open(dirname, 0, NULL);
-
- if (!dir) {
- g_free(dirname);
- continue;
- }
-
- while ((filename = g_dir_read_name(dir))) {
- char *full_path;
-
- /* skip hidden files and directories (.svn, .git, ...) */
- if (filename[0] == '.')
- continue;
-
- /* skip module init file */
- if (strcmp(filename, MODULE_INIT_FILENAME) == 0)
- continue;
-
- 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, "." G_MODULE_SUFFIX) ||
- 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);
- }
-
- statep.set(PRIVATE_TO_JSVAL(iter));
-
- idp.set(INT_TO_JSID(iter->elements->len));
-
- break;
- }
-
- case JSENUMERATE_NEXT: {
- jsval element_val;
-
- if (JSVAL_IS_NULL(statep)) /* Iterating prototype */
- return JS_TRUE;
-
- iter = (ImporterIterator*) JSVAL_TO_PRIVATE(statep);
-
- if (iter->index < iter->elements->len) {
- if (!gjs_string_from_utf8(context,
- (const char*) g_ptr_array_index(iter->elements,
- iter->index++),
- -1,
- &element_val))
- return JS_FALSE;
-
- jsid id;
- if (!JS_ValueToId(context, element_val, &id))
- return JS_FALSE;
- idp.set(id);
-
- break;
- }
- /* else fall through to destroying the iterator */
- }
-
- case JSENUMERATE_DESTROY: {
- if (!JSVAL_IS_NULL(statep)) {
- iter = (ImporterIterator*) JSVAL_TO_PRIVATE(statep);
-
- importer_iterator_free(iter);
-
- statep.set(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,
- JS::HandleObject obj,
- JS::HandleId id,
- unsigned flags,
- JS::MutableHandleObject objp)
-{
- Importer *priv;
- char *name;
- JSBool ret = JS_TRUE;
- jsid module_init_name;
-
- module_init_name = gjs_context_get_const_string(context, GJS_STRING_MODULE_INIT);
- if (id == module_init_name)
- return JS_TRUE;
-
- if (!gjs_get_string_id(context, id, &name))
- return JS_FALSE;
-
- /* let Object.prototype resolve these */
- if (strcmp(name, "valueOf") == 0 ||
- strcmp(name, "toString") == 0 ||
- strcmp(name, "__iterator__") == 0)
- goto out;
- 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) /* we are the prototype, or have the wrong class */
- goto out;
- JS_BeginRequest(context);
- if (do_import(context, obj, priv, name)) {
- objp.set(obj);
- } else {
- ret = JS_FALSE;
- }
- JS_EndRequest(context);
-
- out:
- g_free(name);
- return ret;
-}
-
-GJS_NATIVE_CONSTRUCTOR_DEFINE_ABSTRACT(importer)
-
-static void
-importer_finalize(JSFreeOp *fop,
- JSObject *obj)
-{
- Importer *priv;
-
- priv = (Importer*) JS_GetPrivate(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 */
-
- 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.
- */
-struct JSClass gjs_importer_class = {
- "GjsFileImporter",
- JSCLASS_HAS_PRIVATE |
- JSCLASS_NEW_RESOLVE |
- JSCLASS_NEW_ENUMERATE,
- JS_PropertyStub,
- JS_DeletePropertyStub,
- JS_PropertyStub,
- JS_StrictPropertyStub,
- (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
-};
-
-JSPropertySpec gjs_importer_proto_props[] = {
- { NULL }
-};
-
-JSFunctionSpec gjs_importer_proto_funcs[] = {
- { NULL }
-};
-
-static JSObject*
-importer_new(JSContext *context,
- gboolean is_root)
-{
- JSObject *importer;
- Importer *priv;
- JSObject *global;
- JSBool found;
-
- global = gjs_get_import_global(context);
-
- if (!JS_HasProperty(context, global, gjs_importer_class.name, &found))
- g_error("HasProperty call failed creating importer class");
-
- if (!found) {
- 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)
- */
- gjs_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)
- g_error("Can't init class %s", gjs_importer_class.name);
-
- gjs_debug(GJS_DEBUG_IMPORTER, "Initialized class %s prototype %p",
- gjs_importer_class.name, prototype);
- }
-
- importer = JS_NewObject(context, &gjs_importer_class, NULL, global);
- if (importer == NULL)
- g_error("No memory to create importer importer");
-
- priv = g_slice_new0(Importer);
- priv->is_root = is_root;
-
- GJS_INC_COUNTER(importer);
-
- g_assert(priv_from_js(context, importer) == NULL);
- JS_SetPrivate(importer, priv);
-
- gjs_debug_lifecycle(GJS_DEBUG_IMPORTER,
- "importer constructor, obj %p priv %p", importer, priv);
-
- return importer;
-}
-
-static JSObject*
-gjs_create_importer(JSContext *context,
- const char *importer_name,
- const char **initial_search_path,
- gboolean add_standard_search_path,
- gboolean is_root,
- JSObject *in_object)
-{
- JSObject *importer;
- char **paths[2] = {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] = (char**)gjs_get_search_path();
- }
-
- search_path = gjs_g_strv_concat(paths, 2);
-
- importer = importer_new(context, is_root);
-
- /* 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))
- g_error("no memory to define importer search path prop");
-
- g_strfreev(search_path);
-
- if (!define_meta_properties(context, importer, NULL, importer_name, in_object))
- g_error("failed to define meta properties on 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;
-
- importer = gjs_create_importer(context, importer_name, initial_search_path, add_standard_search_path, FALSE, in_object);
-
- if (!JS_DefineProperty(context, in_object,
- importer_name, OBJECT_TO_JSVAL(importer),
- NULL, NULL,
- GJS_MODULE_PROP_FLAGS))
- g_error("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(JSContext *context,
- const char **initial_search_path,
- gboolean add_standard_search_path)
-{
- jsval importer;
-
- JS_BeginRequest(context);
-
- importer = gjs_get_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS);
-
- if (G_UNLIKELY (!JSVAL_IS_VOID(importer))) {
- gjs_debug(GJS_DEBUG_IMPORTER,
- "Someone else already created root importer, ignoring second request");
-
- JS_EndRequest(context);
- return JS_TRUE;
- }
-
- importer = OBJECT_TO_JSVAL(gjs_create_importer(context, "imports",
- initial_search_path,
- add_standard_search_path,
- TRUE, NULL));
- gjs_set_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS, importer);
-
- JS_EndRequest(context);
- return JS_TRUE;
-}
-
-JSBool
-gjs_define_root_importer(JSContext *context,
- JSObject *in_object)
-{
- jsval importer;
- JSBool success;
- jsid imports_name;
-
- success = JS_FALSE;
- JS_BeginRequest(context);
-
- importer = gjs_get_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS);
- imports_name = gjs_context_get_const_string(context, GJS_STRING_IMPORTS);
- if (!JS_DefinePropertyById(context, in_object,
- imports_name, importer,
- NULL, NULL,
- GJS_MODULE_PROP_FLAGS)) {
- gjs_debug(GJS_DEBUG_IMPORTER, "DefineProperty imports on %p failed",
- in_object);
- goto fail;
- }
-
- success = JS_TRUE;
- fail:
- JS_EndRequest(context);
- return success;
-}
diff --git a/gjs/importer.h b/gjs/importer.h
deleted file mode 100644
index afd4ab19..00000000
--- a/gjs/importer.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*- 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__
-
-#if !defined (__GJS_GJS_MODULE_H__) && !defined (GJS_COMPILATION)
-#error "Only <gjs/gjs-module.h> can be included directly."
-#endif
-
-#include <glib.h>
-#include "gjs/jsapi-util.h"
-
-G_BEGIN_DECLS
-
-JSBool gjs_create_root_importer (JSContext *context,
- const char **initial_search_path,
- gboolean add_standard_search_path);
-JSBool gjs_define_root_importer (JSContext *context,
- JSObject *in_object);
-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__ */
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index 6e987136..35ba939e 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -46,7 +46,6 @@ enum {
};
typedef enum {
- GJS_GLOBAL_SLOT_IMPORTS,
GJS_GLOBAL_SLOT_KEEP_ALIVE,
GJS_GLOBAL_SLOT_BYTE_ARRAY_PROTOTYPE,
GJS_GLOBAL_SLOT_LAST,
@@ -407,15 +406,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 45ab5331..7e27a5f0 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 00000000..10b3ed8e
--- /dev/null
+++ b/modules/importer.cpp
@@ -0,0 +1,153 @@
+/* -*- 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 "importer.h"
+#include <gjs/gjs-module.h>
+#include <gjs/byteArray.h>
+#include "gi/ns.h"
+
+static JSBool
+import_gi_module(JSContext *context,
+ unsigned argc,
+ jsval *vp)
+{
+ JSBool ret = JS_FALSE;
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ jsval retval = JSVAL_VOID;
+ char *module_name = NULL;
+ char *module_version = NULL;
+ JSObject *module_obj;
+
+ if (!gjs_parse_call_args(context, "importGIModule", "s?s", args,
+ "moduleName", &module_name,
+ "moduleVersion", &module_version))
+ goto out;
+
+ if (!gjs_import_gi_module(context, module_name, module_version, &module_obj))
+ goto out;
+
+ ret = JS_TRUE;
+ args.rval().setObject(*module_obj);
+
+ out:
+ g_free(module_name);
+ g_free(module_version);
+ return ret;
+}
+
+static JSBool
+eval_with_scope(JSContext *context,
+ unsigned argc,
+ jsval *vp)
+{
+ JSBool ret = JS_FALSE;
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JSObject *scope;
+ JSObject *script_obj;
+ guint8 *script;
+ gsize script_len;
+ char *filename = NULL;
+ jsval retval;
+
+ if (!gjs_parse_call_args(context, "evalWithScope", "oos", args,
+ "scope", &scope,
+ "script", &script_obj,
+ "filename", &filename))
+ goto out;
+
+ gjs_byte_array_peek_data (context, script_obj, &script, &script_len);
+
+ if (!gjs_eval_with_scope(context, scope, (const char *) script, script_len, filename, &retval))
+ goto out;
+
+ ret = JS_TRUE;
+ args.rval().set(retval);
+
+ out:
+ g_free(filename);
+ return ret;
+}
+
+static JSBool
+get_builtin_search_path(JSContext *context,
+ unsigned argc,
+ jsval *vp)
+{
+ GjsContext *gjs_context = GJS_CONTEXT(JS_GetContextPrivate(context));
+ 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;
+ GArray *elems;
+ JSObject *search_path_obj;
+ 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 = g_array_sized_new(FALSE, FALSE, sizeof(jsval),
+ context_search_path_length + global_search_path_length);
+
+ for (i = 0; i < context_search_path_length; i++) {
+ jsval element = STRING_TO_JSVAL(JS_NewStringCopyZ(context, context_search_path[i]));
+ g_array_append_val(elems, element);
+ }
+
+ for (i = 0; i < global_search_path_length; i++) {
+ jsval element = STRING_TO_JSVAL(JS_NewStringCopyZ(context, global_search_path[i]));
+ g_array_append_val(elems, element);
+ }
+
+ search_path_obj = JS_NewArrayObject(context, elems->len, (jsval *)elems->data);
+ g_array_free(elems, TRUE);
+
+ args.rval().setObject(*search_path_obj);
+ return JS_TRUE;
+}
+
+static JSFunctionSpec module_funcs[] = {
+ { "importGIModule", JSOP_WRAPPER (import_gi_module), 2, GJS_MODULE_PROP_FLAGS },
+ { "evalWithScope", JSOP_WRAPPER (eval_with_scope), 3, GJS_MODULE_PROP_FLAGS },
+ { "getBuiltinSearchPath", JSOP_WRAPPER (get_builtin_search_path), 0, GJS_MODULE_PROP_FLAGS },
+ { NULL },
+};
+
+JSBool
+gjs_js_define_importer_stuff(JSContext *context,
+ JSObject **module_out)
+{
+ JSObject *module;
+
+ module = JS_NewObject(context, NULL, NULL, NULL);
+
+ if (!JS_DefineFunctions(context, module, &module_funcs[0]))
+ return JS_FALSE;
+
+ *module_out = module;
+ return JS_TRUE;
+}
diff --git a/gjs/gi.cpp b/modules/importer.h
index 6ff48479..5fa25b07 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/compat.h"
-#include "gi/repo.h"
+JSBool gjs_js_define_importer_stuff (JSContext *context,
+ JSObject **module_out);
-JSBool
-gjs_define_gi_stuff(JSContext *context,
- JSObject **module_out)
-{
- return gjs_define_repo(context, module_out, "gi");
-}
+G_END_DECLS
+
+#endif /* __GJS_MODULE_IMPORTER_H__ */
diff --git a/modules/modules.cpp b/modules/modules.cpp
index aae35696..0ccc4326 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);
}