summaryrefslogtreecommitdiff
path: root/gjs/module.cpp
diff options
context:
space:
mode:
authorEvan Welsh <contact@evanwelsh.com>2020-12-03 19:35:59 -0600
committerPhilip Chimento <philip.chimento@gmail.com>2021-02-06 19:50:20 -0800
commit6f8b3cb39bc3995706e3d092e4733b8f53b5344d (patch)
tree050d2ca156c026d162d94280590d7ccacfc5a6e1 /gjs/module.cpp
parentd155aef23487527f4a29ddb7754505263cf71d10 (diff)
downloadgjs-6f8b3cb39bc3995706e3d092e4733b8f53b5344d.tar.gz
esm: Enable static module imports.
(Changes from Philip folded in: tests, moving file operations into internal.cpp, store module loader in global, some renames, some added comments)
Diffstat (limited to 'gjs/module.cpp')
-rw-r--r--gjs/module.cpp215
1 files changed, 215 insertions, 0 deletions
diff --git a/gjs/module.cpp b/gjs/module.cpp
index d10b4727..c810f7c9 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -5,6 +5,7 @@
#include <config.h>
#include <stddef.h> // for size_t
+#include <string.h>
#include <sys/types.h> // for ssize_t
#include <string> // for u16string
@@ -12,22 +13,31 @@
#include <gio/gio.h>
#include <glib.h>
+#include <js/CallArgs.h>
+#include <js/CharacterEncoding.h> // for ConstUTF8CharsZ
#include <js/Class.h>
#include <js/CompilationAndEvaluation.h>
#include <js/CompileOptions.h>
+#include <js/Conversions.h>
#include <js/GCVector.h> // for RootedVector
+#include <js/Id.h>
#include <js/PropertyDescriptor.h>
#include <js/RootingAPI.h>
#include <js/SourceText.h>
#include <js/TypeDecls.h>
+#include <js/Utility.h> // for UniqueChars
#include <js/Value.h>
+#include <js/ValueArray.h>
#include <jsapi.h> // for JS_DefinePropertyById, ...
+#include "gjs/atoms.h"
#include "gjs/context-private.h"
#include "gjs/global.h"
+#include "gjs/jsapi-util-args.h"
#include "gjs/jsapi-util.h"
#include "gjs/mem-private.h"
#include "gjs/module.h"
+#include "gjs/native.h"
#include "util/log.h"
class GjsScriptModule {
@@ -269,3 +279,208 @@ JSObject* gjs_get_native_registry(JSObject* global) {
g_assert(native_registry.isObject());
return &native_registry.toObject();
}
+
+/**
+ * gjs_get_module_registry:
+ *
+ * @brief Retrieves a global's module registry from the MODULE_REGISTRY slot.
+ * Registries are JS Maps. See gjs_get_native_registry for more detail.
+ *
+ * @param cx the current #JSContext
+ * @param global a global #JSObject
+ *
+ * @returns the registry map as a #JSObject
+ */
+JSObject* gjs_get_module_registry(JSObject* global) {
+ JS::Value esm_registry =
+ gjs_get_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY);
+
+ g_assert(esm_registry.isObject());
+ return &esm_registry.toObject();
+}
+
+/**
+ * gjs_module_load:
+ *
+ * Loads and registers a module given a specifier and
+ * URI.
+ *
+ * @returns whether an error occurred while resolving the specifier.
+ */
+JSObject* gjs_module_load(JSContext* cx, const char* identifier,
+ const char* file_uri) {
+ g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) ||
+ gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) &&
+ "gjs_module_load can only be called from module-enabled "
+ "globals.");
+
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedValue v_loader(
+ cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
+ g_assert(v_loader.isObject());
+ JS::RootedObject loader(cx, &v_loader.toObject());
+
+ JS::ConstUTF8CharsZ id_chars(identifier, strlen(identifier));
+ JS::ConstUTF8CharsZ uri_chars(file_uri, strlen(file_uri));
+ JS::RootedString id(cx, JS_NewStringCopyUTF8Z(cx, id_chars));
+ if (!id)
+ return nullptr;
+ JS::RootedString uri(cx, JS_NewStringCopyUTF8Z(cx, uri_chars));
+ if (!uri)
+ return nullptr;
+
+ JS::RootedValueArray<2> args(cx);
+ args[0].setString(id);
+ args[1].setString(uri);
+
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Module resolve hook for module '%s' (%s), global %p", identifier,
+ file_uri, global.get());
+
+ JS::RootedValue result(cx);
+ if (!JS::Call(cx, loader, "moduleLoadHook", args, &result))
+ return nullptr;
+
+ g_assert(result.isObject() && "Module hook failed to return an object!");
+ return &result.toObject();
+}
+
+/**
+ * import_native_module_sync:
+ *
+ * @brief Synchronously imports native "modules" from the import global's
+ * native registry. This function does not do blocking I/O so it is
+ * safe to call it synchronously for accessing native "modules" within
+ * modules. This function is always called within the import global's
+ * realm.
+ *
+ * Compare gjs_import_native_module() for the legacy importer.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while importing the native module.
+ */
+static bool import_native_module_sync(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::UniqueChars id;
+ if (!gjs_parse_call_args(cx, "importSync", args, "s", "identifier", &id))
+ return false;
+
+ JS::RootedObject global(cx, gjs_get_import_global(cx));
+ JSAutoRealm ar(cx, global);
+
+ JS::AutoSaveExceptionState exc_state(cx);
+
+ JS::RootedObject native_registry(cx, gjs_get_native_registry(global));
+ JS::RootedObject v_module(cx);
+
+ JS::RootedId key(cx, gjs_intern_string_to_id(cx, id.get()));
+ if (!gjs_global_registry_get(cx, native_registry, key, &v_module))
+ return false;
+
+ if (v_module) {
+ args.rval().setObject(*v_module);
+ return true;
+ }
+
+ JS::RootedObject native_obj(cx);
+ if (!gjs_load_native_module(cx, id.get(), &native_obj)) {
+ gjs_throw(cx, "Failed to load native module: %s", id.get());
+ return false;
+ }
+
+ if (!gjs_global_registry_set(cx, native_registry, key, native_obj))
+ return false;
+
+ args.rval().setObject(*native_obj);
+ return true;
+}
+
+/**
+ * gjs_populate_module_meta:
+ *
+ * Hook SpiderMonkey calls to populate the import.meta object.
+ * Defines a property "import.meta.url", and additionally a method
+ * "import.meta.importSync" if this is an internal module.
+ *
+ * @param private_ref the private value for the #Module object
+ * @param meta the import.meta object
+ *
+ * @returns whether an error occurred while populating the module meta.
+ */
+bool gjs_populate_module_meta(JSContext* cx, JS::HandleValue private_ref,
+ JS::HandleObject meta) {
+ g_assert(private_ref.isObject());
+ JS::RootedObject module(cx, &private_ref.toObject());
+
+ gjs_debug(GJS_DEBUG_IMPORTER, "Module metadata hook for module %p",
+ &private_ref.toObject());
+
+ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+ JS::RootedValue v_uri(cx);
+ if (!JS_GetPropertyById(cx, module, atoms.uri(), &v_uri) ||
+ !JS_DefinePropertyById(cx, meta, atoms.url(), v_uri,
+ GJS_MODULE_PROP_FLAGS))
+ return false;
+
+ JS::RootedValue v_internal(cx);
+ if (!JS_GetPropertyById(cx, module, atoms.internal(), &v_internal))
+ return false;
+ if (JS::ToBoolean(v_internal)) {
+ gjs_debug(GJS_DEBUG_IMPORTER, "Defining meta.importSync for module %p",
+ &private_ref.toObject());
+ if (!JS_DefineFunctionById(cx, meta, atoms.importSync(),
+ import_native_module_sync, 1,
+ GJS_MODULE_PROP_FLAGS))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * gjs_module_resolve:
+ *
+ * Hook SpiderMonkey calls to resolve import specifiers.
+ *
+ * @param importingModulePriv the private value of the #Module object initiating
+ * the import.
+ * @param specifier the import specifier to resolve
+ *
+ * @returns whether an error occurred while resolving the specifier.
+ */
+JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv,
+ JS::HandleString specifier) {
+ g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) ||
+ gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) &&
+ "gjs_module_resolve can only be called from module-enabled "
+ "globals.");
+ g_assert(importingModulePriv.isObject() &&
+ "the importing module can't be null, don't add import to the "
+ "bootstrap script");
+
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedValue v_loader(
+ cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
+ g_assert(v_loader.isObject());
+ JS::RootedObject loader(cx, &v_loader.toObject());
+
+ JS::RootedValueArray<2> args(cx);
+ args[0].set(importingModulePriv);
+ args[1].setString(specifier);
+
+ gjs_debug(GJS_DEBUG_IMPORTER,
+ "Module resolve hook for module '%s' (relative to %p), global %p",
+ gjs_debug_string(specifier).c_str(),
+ &importingModulePriv.toObject(), global.get());
+
+ JS::RootedValue result(cx);
+ if (!JS::Call(cx, loader, "moduleResolveHook", args, &result))
+ return nullptr;
+
+ g_assert(result.isObject() && "resolve hook failed to return an object!");
+ return &result.toObject();
+}