diff options
author | Jasper St. Pierre <jstpierre@mecheye.net> | 2014-01-02 15:59:16 -0500 |
---|---|---|
committer | Jasper St. Pierre <jstpierre@mecheye.net> | 2015-06-17 16:55:37 -0700 |
commit | 75b676e8164eb09e3da1a9bd4ceae15c9c1073a1 (patch) | |
tree | ea9f2d85982b9f4fa98349ae4c11e3a7275c5120 | |
parent | d4b8115affa6dd6c42b0073c83e6f1a54431c774 (diff) | |
download | gjs-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.am | 7 | ||||
-rw-r--r-- | Makefile.am | 5 | ||||
-rw-r--r-- | gi/repo.cpp | 384 | ||||
-rw-r--r-- | gi/repo.h | 3 | ||||
-rw-r--r-- | gi/union.cpp | 1 | ||||
-rw-r--r-- | gjs/bootstrap.cpp | 42 | ||||
-rw-r--r-- | gjs/context.cpp | 30 | ||||
-rw-r--r-- | gjs/context.h | 2 | ||||
-rw-r--r-- | gjs/gi.h | 2 | ||||
-rw-r--r-- | gjs/gjs-module.h | 1 | ||||
-rw-r--r-- | gjs/importer.cpp | 1118 | ||||
-rw-r--r-- | gjs/importer.h | 50 | ||||
-rw-r--r-- | gjs/jsapi-util.h | 7 | ||||
-rw-r--r-- | modules/bootstrap.js | 143 | ||||
-rw-r--r-- | modules/importer.cpp | 153 | ||||
-rw-r--r-- | modules/importer.h (renamed from gjs/gi.cpp) | 25 | ||||
-rw-r--r-- | modules/modules.cpp | 2 |
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* @@ -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 @@ -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); } |