[gjs/esm-local-imports: 4/6] esm: Enable ESM static imports
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm-local-imports: 4/6] esm: Enable ESM static imports
- Date: Mon, 15 Jun 2020 16:41:12 +0000 (UTC)
commit 1dd2ff6fd188e389b4c8f7277aebd80c41f5c028
Author: Evan Welsh <noreply evanwelsh com>
Date: Sat Jun 6 12:10:22 2020 -0500
esm: Enable ESM static imports
gi/repo.cpp | 95 ++++--
gjs/.eslintrc.yml | 22 ++
gjs/context-private.h | 7 +-
gjs/context.cpp | 207 ++++++++++--
gjs/global.cpp | 144 ++++++++-
gjs/global.h | 9 +-
gjs/importer.cpp | 17 +-
gjs/internal.cpp | 450 +++++++++++++++++++++++++++
gjs/internal.h | 66 ++++
gjs/jsapi-util.cpp | 4 +
gjs/jsapi-util.h | 3 +
gjs/module.cpp | 151 ++++++++-
gjs/module.h | 76 +++++
gjs/module.js | 218 +++++++++++++
installed-tests/esm-test.sh | 25 ++
installed-tests/esm.test.in | 4 +
installed-tests/esm/default-import.js | 3 +
installed-tests/esm/default-import.js.output | 1 +
installed-tests/esm/exports.js | 3 +
installed-tests/esm/gi-import.js | 3 +
installed-tests/esm/gi-import.js.output | 1 +
installed-tests/esm/named-import.js | 3 +
installed-tests/esm/named-import.js.output | 1 +
installed-tests/esm/system-import.js | 3 +
installed-tests/esm/system-import.js.output | 3 +
installed-tests/meson.build | 39 +++
js.gresource.xml | 7 +
meson.build | 1 +
modules/esm/.eslintrc.yml | 4 +
modules/esm/gi.js | 14 +
modules/esm/system.js | 1 +
31 files changed, 1540 insertions(+), 45 deletions(-)
---
diff --git a/gi/repo.cpp b/gi/repo.cpp
index 93ba9447..ca306366 100644
--- a/gi/repo.cpp
+++ b/gi/repo.cpp
@@ -58,6 +58,7 @@
#include "gjs/jsapi-class.h"
#include "gjs/jsapi-util.h"
#include "gjs/mem-private.h"
+#include "gjs/module.h"
#include "util/log.h"
struct JSFunctionSpec;
@@ -150,8 +151,13 @@ static bool resolve_namespace_object(JSContext* context,
return false;
JS::RootedValue override(context);
- if (!lookup_override_function(context, ns_id, &override))
- return false;
+
+ // We do not load overrides on the internal global.
+ if (gjs_global_is_type(context, GjsGlobalType::DEFAULT)) {
+ if (!lookup_override_function(context, ns_id, &override)) {
+ return false;
+ }
+ }
JS::RootedValue result(context);
if (!override.isUndefined() &&
@@ -637,37 +643,90 @@ lookup_override_function(JSContext *cx,
}
return true;
- fail:
+fail:
saved_exc.drop();
return false;
}
-JSObject*
-gjs_lookup_namespace_object_by_name(JSContext *context,
- JS::HandleId ns_name)
-{
- auto global = gjs_get_import_global(context);
- JS::RootedValue importer(
- context, gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS));
- g_assert(importer.isObject());
-
- JS::RootedObject repo(context), importer_obj(context, &importer.toObject());
- const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
- if (!gjs_object_require_property(context, importer_obj, "importer",
- atoms.gi(), &repo)) {
+GJS_JSAPI_RETURN_CONVENTION
+static JSObject* lookup_namespace(JSContext* context, JS::HandleId ns_name) {
+ auto native_registry = gjs_get_native_module_registry(context);
+ auto it = native_registry->lookup("gi");
+ if (!it.found()) {
gjs_log_exception(context);
- gjs_throw(context, "No gi property in importer");
+ gjs_throw(context, "No gi property in native registry");
return NULL;
}
+ JS::RootedObject gi(context, it->value());
JS::RootedObject retval(context);
- if (!gjs_object_require_property(context, repo, "GI repository object",
+ if (!gjs_object_require_property(context, gi, "GI repository object",
ns_name, &retval))
return NULL;
return retval;
}
+GJS_JSAPI_RETURN_CONVENTION
+static JSObject* lookup_internal_namespace(JSContext* cx,
+ JS::HandleId ns_name) {
+ auto gcx = GjsContextPrivate::from_cx(cx);
+ JS::RootedObject global(cx, gcx->internal_global());
+ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+
+ // The internal global only supports GObject, Gio, GLib, and private
+ // namespaces.
+ if (ns_name == atoms.gobject() || ns_name == atoms.gio() ||
+ ns_name == atoms.glib() || ns_name == atoms.private_ns_marker()) {
+ JS::RootedObject retval(cx);
+
+ if (!gjs_object_require_property(
+ cx, global, "internal namespace import", ns_name, &retval))
+ return nullptr;
+
+ return retval;
+ } else if (JSID_IS_STRING(ns_name)) {
+ JS::RootedString str(cx, JSID_TO_STRING(ns_name));
+
+ JS::UniqueChars name(gjs_string_to_utf8(cx, JS::StringValue(str)));
+
+ gjs_throw(
+ cx,
+ "Attempted to load unknown GI namespace (%s) on internal global.",
+ name.get());
+ } else {
+ gjs_throw(cx,
+ "Attempted to load invalid GI namespace on internal global.");
+ }
+
+ return nullptr;
+}
+
+JSObject* gjs_lookup_namespace_object_by_name(JSContext* context,
+ JS::HandleId ns_name) {
+ JSObject* global = JS::CurrentGlobalOrNull(context);
+ JSObject* ns = nullptr;
+
+ switch (gjs_global_get_type(global)) {
+ case GjsGlobalType::DEFAULT:
+ ns = lookup_namespace(context, ns_name);
+ break;
+ case GjsGlobalType::INTERNAL:
+ ns = lookup_internal_namespace(context, ns_name);
+ break;
+ case GjsGlobalType::DEBUGGER:
+ ns = nullptr;
+ break;
+ }
+
+ if (!ns) {
+ return nullptr;
+ }
+
+ JS::RootedObject retval(context, ns);
+ return retval;
+}
+
const char*
gjs_info_type_name(GIInfoType type)
{
diff --git a/gjs/.eslintrc.yml b/gjs/.eslintrc.yml
new file mode 100644
index 00000000..748035e5
--- /dev/null
+++ b/gjs/.eslintrc.yml
@@ -0,0 +1,22 @@
+---
+extends: ../.eslintrc.yml
+globals:
+ ARGV: off
+ Debugger: readonly
+ GIRepositoryGType: off
+ globalThis: readonly
+ imports: off
+ Intl: readonly
+ log: readonly
+ logError: readonly
+ print: readonly
+ printerr: readonly
+ GLib: readonly
+ Gio: readonly
+ ByteUtils: readonly
+ lookupModule: readonly
+ registerModule: readonly
+ lookupInternalModule: readonly
+ registerInternalModule: readonly
+ setModuleResolveHook: readonly
+ getModuleUri: readonly
\ No newline at end of file
diff --git a/gjs/context-private.h b/gjs/context-private.h
index 537ae2de..bd711ba5 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -95,6 +95,7 @@ class GjsContextPrivate : public JS::JobQueue {
GjsContext* m_public_context;
JSContext* m_cx;
JS::Heap<JSObject*> m_global;
+ JS::Heap<JSObject*> m_internal_global;
GThread* m_owner_thread;
char* m_program_name;
@@ -187,6 +188,9 @@ class GjsContextPrivate : public JS::JobQueue {
GJS_USE GjsContext* public_context(void) const { return m_public_context; }
GJS_USE JSContext* context(void) const { return m_cx; }
GJS_USE JSObject* global(void) const { return m_global.get(); }
+ GJS_USE JSObject* internal_global(void) const {
+ return m_internal_global.get();
+ }
GJS_USE GjsProfiler* profiler(void) const { return m_profiler; }
GJS_USE const GjsAtoms& atoms(void) const { return *m_atoms; }
GJS_USE bool destroying(void) const { return m_destroying; }
@@ -259,7 +263,8 @@ class GjsContextPrivate : public JS::JobQueue {
void unregister_unhandled_promise_rejection(uint64_t id);
bool register_module(const char* identifier, const char* filename,
- const char* mod_text, size_t mod_len, GError** error);
+ const char* module, ssize_t module_len,
+ GError** error);
void set_sweeping(bool value);
diff --git a/gjs/context.cpp b/gjs/context.cpp
index e705a793..a4d14d71 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -82,9 +82,11 @@
#include "gjs/error-types.h"
#include "gjs/global.h"
#include "gjs/importer.h"
+#include "gjs/internal.h"
#include "gjs/jsapi-util-args.h"
#include "gjs/jsapi-util.h"
#include "gjs/mem.h"
+#include "gjs/module.h"
#include "gjs/native.h"
#include "gjs/profiler-private.h"
#include "gjs/profiler.h"
@@ -333,6 +335,8 @@ gjs_context_class_init(GjsContextClass *klass)
void GjsContextPrivate::trace(JSTracer* trc, void* data) {
auto* gjs = static_cast<GjsContextPrivate*>(data);
JS::TraceEdge<JSObject*>(trc, &gjs->m_global, "GJS global object");
+ JS::TraceEdge<JSObject*>(trc, &gjs->m_internal_global,
+ "GJS internal global object");
gjs->m_atoms->trace(trc);
gjs->m_job_queue.trace(trc);
gjs->m_object_init_list.trace(trc);
@@ -419,6 +423,7 @@ void GjsContextPrivate::dispose(void) {
gjs_debug(GJS_DEBUG_CONTEXT, "Ending trace on global object");
JS_RemoveExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this);
m_global = nullptr;
+ m_internal_global = nullptr;
gjs_debug(GJS_DEBUG_CONTEXT, "Freeing allocated resources");
delete m_fundamental_table;
@@ -504,23 +509,50 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
m_atoms = new GjsAtoms();
- JS::RootedObject global(
- m_cx, gjs_create_global_object(cx, GjsGlobalType::DEFAULT));
+ JS::RootedObject internal_global(
+ m_cx, gjs_create_global_object(cx, GjsGlobalType::INTERNAL));
- if (!global) {
+ if (!internal_global) {
gjs_log_exception(m_cx);
- g_error("Failed to initialize global object");
+ g_error("Failed to initialize internal global object");
}
- JSAutoRealm ar(m_cx, global);
+ JSAutoRealm ar(m_cx, internal_global);
- m_global = global;
+ m_internal_global = internal_global;
JS_AddExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this);
if (!m_atoms->init_atoms(m_cx)) {
gjs_log_exception(m_cx);
g_error("Failed to initialize global strings");
}
+
+ if (!gjs_define_global_properties(m_cx, internal_global,
+ GjsGlobalType::INTERNAL,
+ "GJS internal global", "nullptr")) {
+ gjs_log_exception(m_cx);
+ g_warning("Failed to define properties on internal global object.");
+ }
+
+ JS::RootedObject global(
+ m_cx,
+ gjs_create_global_object(cx, GjsGlobalType::DEFAULT, internal_global));
+
+ if (!global) {
+ gjs_log_exception(m_cx);
+ g_error("Failed to initialize global object");
+ }
+
+ m_global = global;
+
+ // Load internal script *must* be called from the internal realm.
+ if (!gjs_load_internal_script(cx, "module")) {
+ gjs_log_exception(cx);
+ g_warning("Failed to load internal dynamic module hooks.");
+ }
+
+ auto realm = JS::EnterRealm(m_cx, global);
+
std::vector<std::string> paths;
if (m_search_path)
paths = {m_search_path, m_search_path + g_strv_length(m_search_path)};
@@ -542,6 +574,8 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
gjs_log_exception(m_cx);
g_error("Failed to define properties on global object");
}
+
+ JS::LeaveRealm(m_cx, realm);
}
void GjsContextPrivate::set_args(std::vector<std::string> args) {
@@ -1053,24 +1087,161 @@ bool GjsContextPrivate::eval(const char* script, ssize_t script_len,
bool GjsContextPrivate::eval_module(const char* identifier,
uint8_t* exit_status_p, GError** error) {
- // TODO Implement eval_module
- g_error(
- "GjsContextPrivate::eval_module is not implemented. Exiting with "
- "error.");
+ bool auto_profile = m_should_profile;
- return false;
+ if (auto_profile &&
+ (_gjs_profiler_is_running(m_profiler) || m_should_listen_sigusr2))
+ auto_profile = false;
+
+ if (auto_profile)
+ gjs_profiler_start(m_profiler);
+
+ JSAutoRealm ac(m_cx, m_global);
+
+ auto it = gjs_get_esm_registry(m_cx)->lookup(identifier);
+
+ if (!it) {
+ g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
+ "Cannot find module with identifier %s.", identifier);
+ return false;
+ }
+
+ bool ok = true;
+
+ JS::RootedObject obj(m_cx, it->value());
+
+ if (!JS::ModuleInstantiate(m_cx, obj)) {
+ gjs_log_exception(m_cx);
+ g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
+ "Failed to instantiate module %s.", identifier);
+
+ return false;
+ }
+
+ if (!JS::ModuleEvaluate(m_cx, obj)) {
+ ok = false;
+ }
+
+ schedule_gc_if_needed();
+
+ if (JS_IsExceptionPending(m_cx)) {
+ gjs_log_exception(m_cx);
+ g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
+ "Uncaught exception in %s.", identifier);
+ return false;
+ }
+
+ gjs_debug(GJS_DEBUG_CONTEXT, "Module evaluation succeeded for module %s.",
+ identifier);
+
+ /* The promise job queue should be drained even on error, to finish
+ * outstanding async tasks before the context is torn down. Drain after
+ * uncaught exceptions have been reported since draining runs callbacks.
+ */
+ {
+ JS::AutoSaveExceptionState saved_exc(m_cx);
+ ok = run_jobs_fallible() && ok;
+ }
+
+ if (auto_profile)
+ gjs_profiler_stop(m_profiler);
+
+ if (!ok) {
+ uint8_t code;
+
+ if (should_exit(&code)) {
+ *exit_status_p = code;
+ g_set_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT,
+ "Exit with code %d", code);
+ return false;
+ }
+
+ if (!JS_IsExceptionPending(m_cx)) {
+ g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
+ "Module %s terminated with an uncatchable exception",
+ identifier);
+ } else {
+ g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
+ "Module %s threw an exception", identifier);
+ }
+
+ gjs_log_exception(m_cx);
+ /* No exit code from script, but we don't want to exit(0) */
+ *exit_status_p = 1;
+ return false;
+ }
+
+ if (exit_status_p) {
+ /* Assume success if no integer was returned */
+ *exit_status_p = 0;
+ }
+
+ return true;
}
bool GjsContextPrivate::register_module(const char* identifier,
const char* filename,
- const char* module, size_t module_len,
+ const char* script, ssize_t script_len,
GError** error) {
- // TODO Implement register_module
- g_warning(
- "GjsContextPrivate::register_module is not yet implemented. Printing "
- "module text...");
- g_warning("%s", module);
- return true;
+ JSAutoRealm ac(m_cx, m_global);
+
+ // Module registration uses exceptions to report errors
+ // so we'll store the exception state, clear it, attempt to load the
+ // module, then restore the original exception state.
+ JS::AutoSaveExceptionState exp_state(m_cx);
+
+ auto module = new GjsESModule(identifier, filename);
+
+ auto esm_registry = gjs_get_esm_registry(m_cx);
+
+ auto it = esm_registry->lookupForAdd(filename);
+
+ if (it.found()) {
+ gjs_throw(m_cx, "Module '%s' already registered", filename);
+ return false;
+ }
+
+ JS::RootedObject module_record(m_cx,
+ module->compile(m_cx, script, script_len));
+
+ if (module_record && !esm_registry->add(it, identifier, module_record)) {
+ JS_ReportOutOfMemory(m_cx);
+ return false;
+ }
+
+ if (module_record) {
+ return true;
+ }
+
+ // Our message could come from memory owned by us or by the runtime.
+ const char* msg = nullptr;
+
+ JS::RootedValue exc(m_cx);
+ if (JS_GetPendingException(m_cx, &exc)) {
+ JS::RootedObject exc_obj(m_cx, &exc.toObject());
+ JSErrorReport* report = JS_ErrorFromException(m_cx, exc_obj);
+ if (report) {
+ msg = report->message().c_str();
+ } else {
+ JS::RootedString js_message(m_cx, JS::ToString(m_cx, exc));
+
+ if (js_message) {
+ JS::UniqueChars cstr(JS_EncodeStringToUTF8(m_cx, js_message));
+ msg = cstr.get();
+ }
+ }
+ }
+
+ g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
+ "Error registering module '%s': %s", identifier,
+ msg ? msg : "unknown");
+
+ // We've successfully handled the exception so we can clear it.
+ // This is necessary because AutoSaveExceptionState doesn't erase
+ // exceptions when it restores the previous exception state.
+ JS_ClearPendingException(m_cx);
+
+ return false;
}
bool
diff --git a/gjs/global.cpp b/gjs/global.cpp
index e05644a4..88cb420c 100644
--- a/gjs/global.cpp
+++ b/gjs/global.cpp
@@ -58,6 +58,7 @@
#include "gjs/context-private.h"
#include "gjs/engine.h"
#include "gjs/global.h"
+#include "gjs/internal.h"
#include "gjs/jsapi-util-args.h"
#include "gjs/jsapi-util.h"
#include "gjs/module.h"
@@ -311,11 +312,24 @@ class GjsGlobal {
JS_FN("printerr", gjs_printerr, 0, GJS_MODULE_PROP_FLAGS),
JS_FS_END};
+ static void setup_global(JSContext* cx, JSObject* global) {
+ JSAutoRealm ac(cx, global);
+
+ JSRuntime* rt = JS_GetRuntime(cx);
+
+ JS::SetModuleResolveHook(rt, gjs_module_resolve);
+ JS::SetModuleMetadataHook(rt, gjs_populate_module_meta);
+ }
+
public:
GJS_USE
static JSObject* create(JSContext* cx) {
auto global = global_create_new(cx, &klass);
+ if (global) {
+ setup_global(cx, global);
+ }
+
return global;
}
@@ -324,6 +338,10 @@ class GjsGlobal {
JSObject* cmp_global) {
auto global = global_create_with_existing(cx, cmp_global, &klass);
+ if (global) {
+ setup_global(cx, global);
+ }
+
return global;
}
@@ -331,6 +349,11 @@ class GjsGlobal {
static bool define_properties(JSContext* cx, JS::HandleObject global,
const char* realm_name,
const char* bootstrap_script) {
+ gjs_set_global_slot(global, GjsGlobalSlot::ES_MODULE_REGISTRY,
+ JS::PrivateValue(new GjsModuleRegistry()));
+ gjs_set_global_slot(global, GjsGlobalSlot::NATIVE_MODULE_REGISTRY,
+ JS::PrivateValue(new GjsModuleRegistry()));
+
const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
if (!JS_DefinePropertyById(cx, global, atoms.window(), global,
JSPROP_READONLY | JSPROP_PERMANENT) ||
@@ -423,6 +446,102 @@ class GjsDebuggerGlobal {
}
};
+class GjsInternalGlobal {
+ static constexpr JSFunctionSpec static_funcs[] = {
+ JS_FN("getModuleUri", GetModuleUri, 1, 0),
+ JS_FN("compileAndEvalModule", CompileAndEvalModule, 1, 0),
+ JS_FN("debug", Debug, 1, 0),
+ JS_FN("lookupInternalModule", LookupInternalModule, 1, 0),
+ JS_FN("lookupModule", LookupModule, 1, 0),
+ JS_FN("registerModule", RegisterModule, 5, 0),
+ JS_FN("registerInternalModule", RegisterInternalModule, 5, 0),
+ JS_FN("setModuleResolveHook", SetModuleResolveHook, 1, 0),
+ JS_FS_END};
+
+ static constexpr JSClass klass = {
+ "GjsInternalGlobal",
+ JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(
+ static_cast<uint32_t>(GjsInternalGlobalSlot::LAST)),
+ &JS::DefaultGlobalClassOps,
+ };
+
+ public:
+ GJS_USE
+ static JSObject* create(JSContext* cx) {
+ return global_create_new(cx, &klass);
+ }
+
+ GJS_USE
+ static JSObject* create_with_compartment(JSContext* cx,
+ JSObject* cmp_global) {
+ return global_create_with_existing(cx, cmp_global, &klass);
+ }
+
+ static bool define_properties(JSContext* cx, JS::HandleObject global,
+ const char* realm_name,
+ const char* bootstrap_script G_GNUC_UNUSED) {
+ gjs_set_global_slot(global, GjsInternalGlobalSlot::SCRIPT_REGISTRY,
+ JS::PrivateValue(new GjsModuleRegistry()));
+ gjs_set_global_slot(global, GjsInternalGlobalSlot::MODULE_REGISTRY,
+ JS::PrivateValue(new GjsModuleRegistry()));
+
+ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+
+ JS::Realm* realm = JS::GetObjectRealmOrNull(global);
+ g_assert(realm && "Global object must be associated with a realm");
+ // const_cast is allowed here if we never free the realm data
+ JS::SetRealmPrivate(realm, const_cast<char*>(realm_name));
+
+ if (!JS_DefineFunctions(cx, global, static_funcs)) {
+ return false;
+ }
+
+ // GI Modules
+
+ GError* error = nullptr;
+
+ if (!g_irepository_require(nullptr, "GObject", "2.0",
+ GIRepositoryLoadFlags(0), &error) ||
+ !g_irepository_require(nullptr, "GLib", "2.0",
+ GIRepositoryLoadFlags(0), &error) ||
+ !g_irepository_require(nullptr, "Gio", "2.0",
+ GIRepositoryLoadFlags(0), &error)) {
+ gjs_throw_gerror_message(cx, error);
+ g_error_free(error);
+ return false;
+ }
+
+ JS::RootedObject gobject(cx, gjs_create_ns(cx, "GObject"));
+ JS::RootedObject glib(cx, gjs_create_ns(cx, "GLib"));
+ JS::RootedObject gio(cx, gjs_create_ns(cx, "Gio"));
+ JS::RootedObject privateNS(cx, JS_NewPlainObject(cx));
+
+ if (!JS_DefinePropertyById(cx, global, atoms.private_ns_marker(),
+ privateNS, JSPROP_PERMANENT) ||
+ !JS_DefinePropertyById(cx, global, atoms.gobject(), glib,
+ JSPROP_PERMANENT) ||
+ !JS_DefinePropertyById(cx, global, atoms.glib(), glib,
+ JSPROP_PERMANENT) ||
+ !JS_DefinePropertyById(cx, global, atoms.gio(), gio,
+ JSPROP_PERMANENT)) {
+ return false;
+ }
+
+ // Native Modules
+
+ JS::RootedObject byteArray(cx, JS_NewPlainObject(cx));
+
+ if (!gjs_load_native_module(cx, "_byteArrayNative", &byteArray) ||
+ !JS_DefineProperty(cx, global, "ByteUtils", byteArray,
+ JSPROP_PERMANENT)) {
+ gjs_throw(cx, "Failed to define byteArray functions.");
+ return false;
+ }
+
+ return true;
+ }
+};
+
/**
* gjs_create_global_object:
* @cx: a #JSContext
@@ -441,6 +560,9 @@ JSObject* gjs_create_global_object(JSContext* cx, GjsGlobalType global_type,
case GjsGlobalType::DEBUGGER:
return GjsDebuggerGlobal::create_with_compartment(
cx, current_global);
+ case GjsGlobalType::INTERNAL:
+ return GjsInternalGlobal::create_with_compartment(
+ cx, current_global);
default:
return nullptr;
}
@@ -451,14 +573,14 @@ JSObject* gjs_create_global_object(JSContext* cx, GjsGlobalType global_type,
return GjsGlobal::create(cx);
case GjsGlobalType::DEBUGGER:
return GjsDebuggerGlobal::create(cx);
+ case GjsGlobalType::INTERNAL:
+ return GjsInternalGlobal::create(cx);
default:
return nullptr;
}
}
-GjsGlobalType gjs_global_get_type(JSContext* cx) {
- auto global = JS::CurrentGlobalOrNull(cx);
-
+GjsGlobalType gjs_global_get_type(JSObject* global) {
g_assert(global && "gjs_global_get_type called when no global is present");
auto global_type = gjs_get_global_slot(global, GjsGlobalSlot::GLOBAL_TYPE);
@@ -469,13 +591,14 @@ GjsGlobalType gjs_global_get_type(JSContext* cx) {
return static_cast<GjsGlobalType>(global_type.toInt32());
}
-GjsGlobalType gjs_global_get_type(JSObject* global) {
+bool gjs_global_is_type(JSContext* context, GjsGlobalType type) {
+ auto global = JS::CurrentGlobalOrNull(context);
auto global_type = gjs_get_global_slot(global, GjsGlobalSlot::GLOBAL_TYPE);
g_assert(global_type.isInt32() &&
"Invalid type for GLOBAL_TYPE slot. Expected int32.");
- return static_cast<GjsGlobalType>(global_type.toInt32());
+ return type == static_cast<GjsGlobalType>(global_type.toInt32());
}
/**
@@ -518,6 +641,9 @@ bool gjs_define_global_properties(JSContext* cx, JS::HandleObject global,
case GjsGlobalType::DEBUGGER:
return GjsDebuggerGlobal::define_properties(cx, global, realm_name,
bootstrap_script);
+ case GjsGlobalType::INTERNAL:
+ return GjsInternalGlobal::define_properties(cx, global, realm_name,
+ bootstrap_script);
}
return false;
@@ -530,6 +656,8 @@ void gjs_set_global_slot(JSObject* global, GlobalSlot slot, JS::Value value) {
}
template void gjs_set_global_slot(JSObject* global, GjsGlobalSlot slot,
JS::Value value);
+template void gjs_set_global_slot(JSObject* global, GjsInternalGlobalSlot slot,
+ JS::Value value);
template <typename GlobalSlot>
JS::Value gjs_get_global_slot(JSObject* global, GlobalSlot slot) {
@@ -537,6 +665,8 @@ JS::Value gjs_get_global_slot(JSObject* global, GlobalSlot slot) {
global, JSCLASS_GLOBAL_SLOT_COUNT + static_cast<uint32_t>(slot));
}
template JS::Value gjs_get_global_slot(JSObject* global, GjsGlobalSlot slot);
+template JS::Value gjs_get_global_slot(JSObject* global,
+ GjsInternalGlobalSlot slot);
decltype(GjsGlobal::klass) constexpr GjsGlobal::klass;
decltype(GjsGlobal::static_funcs) constexpr GjsGlobal::static_funcs;
@@ -544,3 +674,7 @@ decltype(GjsGlobal::static_funcs) constexpr GjsGlobal::static_funcs;
decltype(GjsDebuggerGlobal::klass) constexpr GjsDebuggerGlobal::klass;
decltype(
GjsDebuggerGlobal::static_funcs) constexpr GjsDebuggerGlobal::static_funcs;
+
+decltype(GjsInternalGlobal::klass) constexpr GjsInternalGlobal::klass;
+decltype(
+ GjsInternalGlobal::static_funcs) constexpr GjsInternalGlobal::static_funcs;
diff --git a/gjs/global.h b/gjs/global.h
index 41df240d..88842c93 100644
--- a/gjs/global.h
+++ b/gjs/global.h
@@ -66,7 +66,14 @@ enum class GjsGlobalSlot : uint32_t {
LAST,
};
-GjsGlobalType gjs_global_get_type(JSContext* cx);
+enum class GjsInternalGlobalSlot : uint32_t {
+ MODULE_REGISTRY = static_cast<uint32_t>(GjsGlobalSlot::LAST),
+ SCRIPT_REGISTRY,
+ IMPORT_HOOK,
+ LAST
+};
+
+bool gjs_global_is_type(JSContext* cx, GjsGlobalType type);
GjsGlobalType gjs_global_get_type(JSObject* global);
GJS_JSAPI_RETURN_CONVENTION
diff --git a/gjs/importer.cpp b/gjs/importer.cpp
index d294301c..6be101fa 100644
--- a/gjs/importer.cpp
+++ b/gjs/importer.cpp
@@ -50,6 +50,7 @@
#include <js/Utility.h> // for UniqueChars
#include <js/Value.h>
#include <jsapi.h> // for JS_DefinePropertyById, JS_DefineP...
+#include <jsfriendapi.h>
#include <jspubtd.h> // for JSProto_Error
#include <mozilla/UniquePtr.h>
#include <mozilla/Vector.h>
@@ -300,10 +301,24 @@ gjs_import_native_module(JSContext *cx,
{
gjs_debug(GJS_DEBUG_IMPORTER, "Importing '%s'", parse_name);
+ auto native_registry = gjs_get_native_module_registry(cx);
+ auto it = native_registry->lookupForAdd(parse_name);
+
JS::RootedObject module(cx);
+
+ if (it.found()) {
+ module.set(it->value().get());
+ return define_meta_properties(cx, module, nullptr, parse_name,
+ importer) &&
+ JS_DefineProperty(cx, importer, parse_name, module,
+ GJS_MODULE_PROP_FLAGS);
+ }
+
return gjs_load_native_module(cx, parse_name, &module) &&
+ native_registry->put(parse_name, module) &&
define_meta_properties(cx, module, nullptr, parse_name, importer) &&
- JS_DefineProperty(cx, importer, parse_name, module, GJS_MODULE_PROP_FLAGS);
+ JS_DefineProperty(cx, importer, parse_name, module,
+ GJS_MODULE_PROP_FLAGS);
}
GJS_JSAPI_RETURN_CONVENTION
diff --git a/gjs/internal.cpp b/gjs/internal.cpp
new file mode 100644
index 00000000..e5c7ed7c
--- /dev/null
+++ b/gjs/internal.cpp
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2020 Evan Welsh <contact evanwelsh com>
+ *
+ * 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 "gjs/internal.h"
+
+#include <config.h>
+#include <gio/gio.h>
+#include <girepository.h>
+#include <glib-object.h>
+#include <glib.h>
+#include <js/Class.h>
+#include <js/CompilationAndEvaluation.h>
+#include <js/CompileOptions.h>
+#include <js/Conversions.h>
+#include <js/GCVector.h> // for RootedVector
+#include <js/Promise.h>
+#include <js/PropertyDescriptor.h>
+#include <js/RootingAPI.h>
+#include <js/SourceText.h>
+#include <js/TypeDecls.h>
+#include <jsapi.h> // for JS_DefinePropertyById, ...
+#include <stddef.h> // for size_t
+#include <sys/types.h> // for ssize_t
+
+#include <codecvt> // for codecvt_utf8_utf16
+#include <locale> // for wstring_convert
+#include <string> // for u16string
+#include <vector>
+
+#include "gjs/context-private.h"
+#include "gjs/context.h"
+#include "gjs/error-types.h"
+#include "gjs/global.h"
+#include "gjs/importer.h"
+#include "gjs/jsapi-util.h"
+#include "gjs/mem-private.h"
+#include "gjs/module.h"
+#include "gjs/native.h"
+#include "util/log.h"
+
+// You have to be very careful in this file to only do operations within the
+// correct global!
+using AutoGFile = GjsAutoUnref<GFile>;
+
+bool gjs_load_internal_script(JSContext* cx, const char* identifier) {
+ GjsAutoChar full_path(
+ g_strdup_printf("resource://org/gnome/gjs/gjs/%s.js", identifier));
+ AutoGFile gfile(g_file_new_for_uri(full_path));
+
+ char* script_text_raw;
+ gsize script_text_len;
+ GError* error = nullptr;
+
+ if (!g_file_load_contents(gfile, NULL, &script_text_raw, &script_text_len,
+ nullptr, &error)) {
+ gjs_throw(cx, "Failed to read internal resource: %s \n%s",
+ full_path.get(), error->message);
+ return false;
+ }
+
+ GjsAutoChar script_text(script_text_raw);
+
+ JS::CompileOptions options(cx);
+ options.setIntroductionType("Internal Script Loader");
+ options.setFileAndLine(full_path, 1);
+ options.setSelfHostingMode(false);
+
+ std::u16string utf16_string =
+ gjs_utf8_script_to_utf16(script_text, script_text_len);
+ // COMPAT: This could use JS::SourceText<mozilla::Utf8Unit> directly,
+ // but that messes up code coverage. See bug
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1404784
+ JS::SourceText<char16_t> buf;
+ if (!buf.init(cx, utf16_string.c_str(), utf16_string.size(),
+ JS::SourceOwnership::Borrowed))
+ return false;
+
+ JS::RootedObject internal_global(cx, gjs_get_internal_global(cx));
+
+ JSAutoRealm ar(cx, internal_global);
+ JS::RootedValue ignored_retval(cx);
+ JS::RootedObject module(cx, JS_NewPlainObject(cx));
+ JS::RootedObjectVector scope_chain(cx);
+
+ if (!scope_chain.append(module)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ auto success = JS::Evaluate(cx, scope_chain, options, buf, &ignored_retval);
+
+ if (!success) {
+ gjs_log_exception(cx);
+ return false;
+ }
+ auto add = gjs_get_internal_script_registry(cx)->lookupForAdd(identifier);
+
+ if (add.found()) {
+ gjs_throw(cx, "Internal script %s already loaded.", identifier);
+ return false;
+ }
+
+ GjsAutoChar iden(g_strdup(identifier));
+
+ if (!gjs_get_internal_script_registry(cx)->add(add, iden.get(), module)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+bool GetModuleUri(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "getModuleUri", 1)) {
+ return false;
+ }
+
+ JS::RootedValue importer(cx, args[0]);
+
+ if (importer.isUndefined()) {
+ gjs_throw(cx,
+ "Cannot import from relative path when module path "
+ "is unknown.");
+ return false;
+ }
+ // The module from which the resolve request is coming
+ GjsESModule* priv_module = static_cast<GjsESModule*>(importer.toPrivate());
+ // Get the module's path.
+ auto module_location = priv_module->uri();
+ // Get the module's directory.
+ const gchar* module_file_location = module_location.c_str();
+ return gjs_string_from_utf8(cx, module_file_location, args.rval());
+}
+
+bool SetModuleResolveHook(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "setModuleResolveHook", 1)) {
+ return false;
+ }
+
+ JS::RootedValue mv(cx, args[0]);
+
+ // The hook is stored in the internal global.
+ JS::RootedObject global(cx, gjs_get_internal_global(cx));
+ gjs_set_global_slot(global, GjsInternalGlobalSlot::IMPORT_HOOK, mv);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+bool CompileAndEvalModule(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "compileAndEvalModule", 1)) {
+ return false;
+ }
+
+ JS::RootedString s1(cx, args[0].toString());
+
+ JS::UniqueChars id = JS_EncodeStringToUTF8(cx, s1);
+
+ {
+ JSAutoRealm ar(cx, gjs_get_import_global(cx));
+ auto registry = gjs_get_esm_registry(cx);
+ auto result = registry->lookup(id.get());
+ if (result) {
+ JS::RootedObject res(cx, result->value());
+ auto init = JS::ModuleInstantiate(cx, res);
+ if (!init) {
+ gjs_log_exception(cx);
+ }
+
+ auto eval = JS::ModuleEvaluate(cx, res);
+
+ if (!eval) {
+ gjs_log_exception(cx);
+ }
+
+ args.rval().setBoolean(init && eval);
+ } else {
+ args.rval().setBoolean(false);
+ }
+ }
+
+ return true;
+}
+
+bool gjs_require_module(JSContext* m_cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
+
+ if (argc != 1) {
+ gjs_throw(m_cx, "Must pass a single argument to require()");
+ return false;
+ }
+
+ JS::AutoSaveExceptionState exc_state(m_cx);
+ JS::RootedString jstr(m_cx, JS::ToString(m_cx, argv[0]));
+ exc_state.restore();
+
+ if (!jstr) {
+ g_message("JS LOG: <cannot convert value to string>");
+ return true;
+ }
+
+ JS::UniqueChars id(JS_EncodeStringToUTF8(m_cx, jstr));
+
+ if (!id) {
+ gjs_throw(m_cx, "Invalid native id.");
+ return false;
+ }
+
+ auto native_registry = gjs_get_native_module_registry(m_cx);
+
+ auto nativeModuleAdd = native_registry->lookupForAdd(id.get());
+
+ if (nativeModuleAdd.found()) {
+ JS::RootedObject obj(m_cx, nativeModuleAdd->value().get());
+
+ argv.rval().setObject(*obj);
+ return true;
+ }
+
+ JS::RootedObject native_obj(m_cx);
+
+ if (!gjs_load_native_module(m_cx, id.get(), &native_obj)) {
+ gjs_throw(m_cx, "Failed to load native module: %s", id.get());
+ return false;
+ }
+
+ if (!native_registry->add(nativeModuleAdd, id.get(), native_obj)) {
+ JS_ReportOutOfMemory(m_cx);
+ return false;
+ }
+
+ argv.rval().setObject(*native_obj);
+ return true;
+}
+
+static bool register_module(JSContext* cx, const char* identifier,
+ const char* path, const char* text, size_t length,
+ bool* success) {
+ auto esm_registry = gjs_get_esm_registry(cx);
+
+ auto it = esm_registry->lookupForAdd(path);
+
+ if (it.found()) {
+ gjs_throw(cx, "Module '%s' already registered", path);
+ return false;
+ }
+
+ auto module = new GjsESModule(identifier, path);
+
+ JS::RootedObject module_record(cx, module->compile(cx, text, length));
+
+ if (module_record && !esm_registry->add(it, identifier, module_record)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ *success = module_record;
+
+ return true;
+}
+
+// registerModule(id: string, path: string, text: string, length: number,
+// unused: boolean)
+bool RegisterModule(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "registerModule", 5)) {
+ return false;
+ }
+
+ JS::RootedString str0(cx, args[0].toString()), // id
+ str1(cx, args[1].toString()), // path
+ str2(cx, args[2].toString()); // text
+
+ JS::UniqueChars id = JS_EncodeStringToUTF8(cx, str0);
+ JS::UniqueChars path = JS_EncodeStringToUTF8(cx, str1);
+ JS::UniqueChars text = JS_EncodeStringToUTF8(cx, str2);
+ auto length = args[3].toInt32();
+
+ {
+ JSAutoRealm ar(cx, gjs_get_import_global(cx));
+
+ bool success = false;
+ bool result = register_module(cx, id.get(), path.get(), text.get(),
+ length, &success);
+
+ args.rval().setBoolean(success);
+
+ return result;
+ }
+}
+
+static bool register_internal_module(JSContext* cx, const char* identifier,
+ const char* filename, const char* module,
+ size_t module_len, bool* success) {
+ auto internal_registry = gjs_get_internal_module_registry(cx);
+ auto it = internal_registry->lookupForAdd(identifier);
+
+ if (it.found()) {
+ gjs_throw(cx, "Internal module '%s' is already registered", identifier);
+ return false;
+ }
+
+ auto internal_module = new GjsESModule(identifier, filename, true);
+
+ JS::RootedObject module_record(
+ cx, internal_module->compile(cx, module, module_len));
+
+ if (module_record &&
+ !internal_registry->add(it, identifier, module_record)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ *success = module_record;
+
+ return true;
+}
+
+// registerInternalModule(id: string, path: string, text: string, length:
+// number)
+bool RegisterInternalModule(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "registerInternalModule", 4)) {
+ return false;
+ }
+
+ JS::RootedString str0(cx, args[0].toString()), // id
+ str1(cx, args[1].toString()), // path
+ str2(cx, args[2].toString()); // text
+
+ JS::UniqueChars id = JS_EncodeStringToUTF8(cx, str0);
+ JS::UniqueChars path = JS_EncodeStringToUTF8(cx, str1);
+ JS::UniqueChars text = JS_EncodeStringToUTF8(cx, str2);
+ auto length = args[3].toInt32();
+
+ {
+ JSAutoRealm ar(cx, gjs_get_import_global(cx));
+
+ bool success = false;
+ bool result = register_internal_module(cx, id.get(), path.get(),
+ text.get(), length, &success);
+ args.rval().setBoolean(success);
+
+ return result;
+ }
+}
+
+// lookupInternalModule(id: string)
+bool LookupInternalModule(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "lookupInternalModule", 1)) {
+ return false;
+ }
+
+ JS::RootedString s1(cx, args[0].toString());
+
+ JS::UniqueChars id = JS_EncodeStringToUTF8(cx, s1);
+
+ {
+ JSAutoRealm ar(cx, gjs_get_import_global(cx));
+ auto registry = gjs_get_internal_module_registry(cx);
+ auto it = registry->lookup(id.get());
+
+ if (!it.found()) {
+ args.rval().setNull();
+ return true;
+ }
+ JS::RootedObject lookup(cx, it->value());
+ if (!lookup) {
+ args.rval().setNull();
+ } else {
+ args.rval().setObject(*lookup.get());
+ }
+ }
+ return true;
+}
+
+// lookupModule(id: string)
+bool LookupModule(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "lookupModule", 1)) {
+ return false;
+ }
+
+ JS::RootedString s1(cx, args[0].toString());
+
+ JS::UniqueChars id = JS_EncodeStringToUTF8(cx, s1);
+
+ {
+ JSAutoRealm ar(cx, gjs_get_import_global(cx));
+ auto registry = gjs_get_esm_registry(cx);
+ auto it = registry->lookup(id.get());
+
+ if (!it.found()) {
+ args.rval().setNull();
+ return true;
+ }
+ JS::RootedObject lookup(cx, it->value());
+
+ if (!lookup) {
+ args.rval().setNull();
+ } else {
+ args.rval().setObject(*lookup.get());
+ }
+ }
+ return true;
+}
+
+// debug(msg: string)
+bool Debug(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "debug", 1)) {
+ return false;
+ }
+
+ JS::RootedString s1(cx, args[0].toString());
+
+ JS::UniqueChars id = JS_EncodeStringToUTF8(cx, s1);
+
+ gjs_debug(GJS_DEBUG_IMPORTER, id.get());
+
+ return true;
+}
diff --git a/gjs/internal.h b/gjs/internal.h
new file mode 100644
index 00000000..98aa7f8b
--- /dev/null
+++ b/gjs/internal.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020 Evan Welsh <contact evanwelsh com>
+ *
+ * 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_INTERNAL_H_
+#define GJS_INTERNAL_H_
+
+#include <config.h>
+#include <gio/gio.h>
+#include <js/GCHashTable.h>
+#include <js/GCVector.h>
+#include <js/TypeDecls.h>
+#include <jsapi.h> // for JS_GetContextPrivate
+#include <jsfriendapi.h> // for ScriptEnvironmentPreparer
+
+#include <string>
+
+#include "gjs/macros.h"
+
+bool gjs_load_internal_script(JSContext* cx, const char* identifier);
+
+// setModuleResolveHook
+bool SetModuleResolveHook(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// compileAndEvalModule(id: string)
+bool CompileAndEvalModule(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// registerModule(id: string, path: string, text: string, length: number, ?:
+// boolean)
+bool RegisterModule(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// registerInternalModule(id: string, path: string, text: string, length:
+// number, is_legacy: boolean)
+bool RegisterInternalModule(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// lookupInternalModule(id: string)
+bool LookupInternalModule(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// lookupModule(id: string)
+bool LookupModule(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// debug(msg: string)
+bool Debug(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// getModuleUri(module): string
+bool GetModuleUri(JSContext* cx, unsigned argc, JS::Value* vp);
+
+#endif // GJS_INTERNAL_H_
diff --git a/gjs/jsapi-util.cpp b/gjs/jsapi-util.cpp
index 4d7193b2..e997bdb7 100644
--- a/gjs/jsapi-util.cpp
+++ b/gjs/jsapi-util.cpp
@@ -657,6 +657,10 @@ JSObject* gjs_get_import_global(JSContext* cx) {
return GjsContextPrivate::from_cx(cx)->global();
}
+JSObject* gjs_get_internal_global(JSContext* cx) {
+ return GjsContextPrivate::from_cx(cx)->internal_global();
+}
+
#if defined(G_OS_WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1900))
/* Unfortunately Visual Studio's C++ .lib somehow did not contain the right
* codecvt stuff that we need to convert from utf8 to utf16 (char16_t), so we
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index a4bee3bc..91bf08da 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -212,6 +212,9 @@ struct GCPolicy<GjsAutoParam> : public IgnoreGCPolicy<GjsAutoParam> {};
GJS_USE
JSObject* gjs_get_import_global (JSContext *context);
+GJS_USE
+JSObject* gjs_get_internal_global(JSContext* context);
+
void gjs_throw_constructor_error (JSContext *context);
void gjs_throw_abstract_constructor_error(JSContext *context,
diff --git a/gjs/module.cpp b/gjs/module.cpp
index 6942ee46..bbe21afd 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -1,6 +1,7 @@
/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/*
* Copyright (c) 2017 Philip Chimento <philip chimento gmail com>
+ * Copyright (c) 2020 Evan Welsh <contact evanwelsh com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -26,15 +27,22 @@
#include <stddef.h> // for size_t
#include <sys/types.h> // for ssize_t
+#include <codecvt> // for codecvt_utf8_utf16
+#include <locale> // for wstring_convert
#include <string> // for u16string
+#include <vector>
#include <gio/gio.h>
+#include <girepository.h>
+#include <glib-object.h>
#include <glib.h>
#include <js/Class.h>
#include <js/CompilationAndEvaluation.h>
#include <js/CompileOptions.h>
+#include <js/Conversions.h>
#include <js/GCVector.h> // for RootedVector
+#include <js/Promise.h>
#include <js/PropertyDescriptor.h>
#include <js/RootingAPI.h>
#include <js/SourceText.h>
@@ -42,15 +50,21 @@
#include <jsapi.h> // for JS_DefinePropertyById, ...
#include "gjs/context-private.h"
+#include "gjs/error-types.h"
+#include "gjs/global.h"
+#include "gjs/importer.h"
#include "gjs/jsapi-util.h"
#include "gjs/mem-private.h"
#include "gjs/module.h"
+#include "gjs/native.h"
#include "util/log.h"
+using AutoGFile = GjsAutoUnref<GFile>;
+
class GjsScriptModule {
char *m_name;
- GjsScriptModule(const char* name) {
+ explicit GjsScriptModule(const char* name) {
m_name = g_strdup(name);
GJS_INC_COUNTER(module);
}
@@ -240,6 +254,30 @@ class GjsScriptModule {
}
};
+JSObject* GjsESModule::compile(JSContext* m_cx, const char* mod_text,
+ size_t mod_len) {
+ JS::CompileOptions options(m_cx);
+ options.setFileAndLine(m_uri.c_str(), 1).setSourceIsLazy(false);
+
+ std::u16string utf16_string(gjs_utf8_script_to_utf16(mod_text, mod_len));
+
+ JS::SourceText<char16_t> buf;
+ if (!buf.init(m_cx, utf16_string.c_str(), utf16_string.size(),
+ JS::SourceOwnership::Borrowed))
+ return nullptr;
+
+ JS::RootedObject new_module(m_cx);
+
+ if (!JS::CompileModule(m_cx, options, buf, &new_module)) {
+ gjs_log_exception(m_cx);
+ return nullptr;
+ }
+
+ JS::SetModulePrivate(new_module, JS::PrivateValue(this));
+
+ return new_module;
+}
+
/**
* gjs_module_import:
* @cx: the JS context
@@ -268,3 +306,114 @@ gjs_module_import(JSContext *cx,
decltype(GjsScriptModule::klass) constexpr GjsScriptModule::klass;
decltype(GjsScriptModule::class_ops) constexpr GjsScriptModule::class_ops;
+
+GjsModuleRegistry* gjs_get_native_module_registry(JSContext* js_context) {
+ auto global = gjs_get_import_global(js_context);
+ auto native_registry =
+ gjs_get_global_slot(global, GjsGlobalSlot::NATIVE_MODULE_REGISTRY);
+
+ return static_cast<GjsModuleRegistry*>(native_registry.toPrivate());
+}
+
+GjsModuleRegistry* gjs_get_esm_registry(JSContext* js_context) {
+ auto global = gjs_get_import_global(js_context);
+ auto esm_registry =
+ gjs_get_global_slot(global, GjsGlobalSlot::ES_MODULE_REGISTRY);
+
+ return static_cast<GjsModuleRegistry*>(esm_registry.toPrivate());
+}
+
+GjsModuleRegistry* gjs_get_internal_script_registry(JSContext* js_context) {
+ auto global = gjs_get_internal_global(js_context);
+ auto script_registry =
+ gjs_get_global_slot(global, GjsInternalGlobalSlot::SCRIPT_REGISTRY);
+
+ return static_cast<GjsModuleRegistry*>(script_registry.toPrivate());
+}
+
+GjsModuleRegistry* gjs_get_internal_module_registry(JSContext* js_context) {
+ auto global = gjs_get_internal_global(js_context);
+ auto script_registry =
+ gjs_get_global_slot(global, GjsInternalGlobalSlot::MODULE_REGISTRY);
+
+ return static_cast<GjsModuleRegistry*>(script_registry.toPrivate());
+}
+
+static bool populate_module_meta(JSContext* m_cx,
+ JS::Handle<JS::Value> private_ref,
+ JS::Handle<JSObject*> meta_object_handle) {
+ JS::RootedObject meta_object(m_cx, meta_object_handle);
+ JS::RootedValue uri_val(m_cx, JS::UndefinedValue());
+ bool allow_require = true;
+
+ if (!private_ref.isUndefined()) {
+ GjsESModule* module =
+ static_cast<GjsESModule*>(private_ref.toPrivate());
+
+ auto uri = module->uri();
+
+ allow_require = module->isInternal();
+
+ JS::Rooted<JSString*> uri_str(m_cx,
+ JS_NewStringCopyZ(m_cx, uri.c_str()));
+
+ if (!uri_str) {
+ JS_ReportOutOfMemory(m_cx);
+ return false;
+ }
+
+ uri_val.setString(uri_str);
+ }
+
+ if (!JS_DefineProperty(m_cx, meta_object, "url", uri_val,
+ JSPROP_ENUMERATE)) {
+ gjs_throw(m_cx, "Could not define import.meta.url");
+ return false;
+ }
+
+ if (allow_require) {
+ if (!JS_DefineFunction(m_cx, meta_object, "require", gjs_require_module,
+ 1, GJS_MODULE_PROP_FLAGS)) {
+ gjs_throw(m_cx, "Could not define require!");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importer,
+ JS::HandleString specifier) {
+ g_assert(gjs_global_is_type(cx, GjsGlobalType::DEFAULT) &&
+ "gjs_module_resolve can only be called from on the default "
+ "global.");
+
+ GjsContextPrivate* gjs_cx = GjsContextPrivate::from_cx(cx);
+
+ JS::RootedObject global(cx, gjs_cx->internal_global());
+ JSAutoRealm ar(cx, global);
+
+ JS::RootedValue hookValue(
+ cx, gjs_get_global_slot(global, GjsInternalGlobalSlot::IMPORT_HOOK));
+
+ JS::AutoValueArray<3> args(cx);
+ args[0].set(importer);
+ args[1].setString(specifier);
+
+ JS::RootedValue result(cx);
+
+ if (!JS_CallFunctionValue(cx, nullptr, hookValue, args, &result)) {
+ gjs_log_exception(cx);
+ return nullptr;
+ }
+
+ JS::RootedObject module(cx, result.toObjectOrNull());
+
+ return module;
+}
+
+bool gjs_populate_module_meta(JSContext* m_cx,
+ JS::Handle<JS::Value> private_ref,
+ JS::Handle<JSObject*> meta_object) {
+ return populate_module_meta(m_cx, private_ref, meta_object);
+}
diff --git a/gjs/module.h b/gjs/module.h
index c63d6852..a61bc470 100644
--- a/gjs/module.h
+++ b/gjs/module.h
@@ -1,6 +1,7 @@
/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/*
* Copyright (c) 2017 Philip Chimento <philip chimento gmail com>
+ * Copyright (c) 2020 Evan Welsh <contact evanwelsh com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -28,10 +29,42 @@
#include <gio/gio.h>
+#include <js/GCHashTable.h>
+#include <js/GCVector.h>
#include <js/TypeDecls.h>
+#include <jsapi.h> // for JS_GetContextPrivate
+#include <jsfriendapi.h> // for ScriptEnvironmentPreparer
+
+#include <string>
#include "gjs/macros.h"
+class CppStringHashPolicy {
+ public:
+ typedef std::string Lookup;
+
+ static js::HashNumber hash(const Lookup& l) {
+ return std::hash<std::string>{}(std::string(l));
+ }
+
+ static bool match(const std::string& k, const Lookup& l) {
+ return k.compare(l) == 0;
+ }
+
+ static void rekey(std::string* k, const std::string& newKey) {
+ *k = newKey;
+ }
+};
+
+namespace JS {
+template <>
+struct GCPolicy<std::string> : public IgnoreGCPolicy<std::string> {};
+} // namespace JS
+
+using GjsModuleRegistry =
+ JS::GCHashMap<std::string, JS::Heap<JSObject*>, CppStringHashPolicy,
+ js::SystemAllocPolicy>;
+
GJS_JSAPI_RETURN_CONVENTION
JSObject *
gjs_module_import(JSContext *cx,
@@ -40,4 +73,47 @@ gjs_module_import(JSContext *cx,
const char *name,
GFile *file);
+class GjsESModule {
+ std::string m_identifier;
+ std::string m_uri;
+ bool m_is_internal;
+
+ public:
+ GjsESModule(std::string module_identifier, std::string module_uri,
+ bool is_internal) {
+ m_is_internal = is_internal;
+ m_uri = module_uri;
+ m_identifier = module_identifier;
+ }
+
+ GjsESModule(std::string module_identifier, std::string module_uri)
+ : GjsESModule(module_identifier, module_uri, false) {}
+
+ void setUri(std::string uri) { m_uri = uri; }
+
+ std::string uri() { return m_uri; }
+
+ std::string identifier() { return m_identifier; }
+
+ bool isInternal() { return m_is_internal; }
+
+ GJS_JSAPI_RETURN_CONVENTION
+ JSObject* compile(JSContext* cx, const char* mod_text, size_t mod_len);
+};
+
+bool gjs_require_module(JSContext* js_context, unsigned argc, JS::Value* vp);
+
+GjsModuleRegistry* gjs_get_native_module_registry(JSContext* js_context);
+GjsModuleRegistry* gjs_get_esm_registry(JSContext* js_context);
+GjsModuleRegistry* gjs_get_internal_module_registry(JSContext* js_context);
+GjsModuleRegistry* gjs_get_internal_script_registry(JSContext* js_context);
+
+GJS_JSAPI_RETURN_CONVENTION
+JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue mod_val,
+ JS::HandleString specifier);
+
+bool gjs_populate_module_meta(JSContext* m_cx,
+ JS::Handle<JS::Value> private_ref,
+ JS::Handle<JSObject*> meta_object);
+
#endif // GJS_MODULE_H_
diff --git a/gjs/module.js b/gjs/module.js
new file mode 100644
index 00000000..ff44ff41
--- /dev/null
+++ b/gjs/module.js
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2020 Evan Welsh <contact evanwelsh com>
+ *
+ * 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.
+ */
+
+/* global debug */
+
+// NOTE: Gio, GLib, and GObject are *raw* and have no overrides.
+
+function isValidUri(uri) {
+ const uri_scheme = GLib.uri_parse_scheme(uri);
+
+ // Handle relative imports from URI-based modules.
+ if (!uri_scheme)
+ return false;
+
+ // Only allow imports from file:// and resource:// URIs
+ return uri_scheme === 'file' || uri_scheme === 'resource';
+}
+
+function isRelativePath(id) {
+ // Check if the path is relative.
+ return id.startsWith('./') || id.startsWith('../');
+}
+
+function resolveAbsolutePath(id) {
+ const output = Gio.File.new_for_path(id);
+
+ return {output, full_path: output.get_path()};
+}
+
+function isAbsolutePath(id) {
+ return id.startsWith('/');
+}
+
+function resolveURI(id) {
+ const output = Gio.File.new_for_uri(id);
+
+ return {output, full_path: output.get_uri()};
+}
+
+function resolveRelativePath(id, module_uri) {
+ // If a module has a path, we'll have stored it in the host field
+ let full_path;
+ let output;
+
+ if (!module_uri)
+ throw new Error('Cannot import from relative path when module path is unknown.');
+
+
+ debug(`module_uri: ${module_uri}`);
+
+ const uri_scheme = GLib.uri_parse_scheme(module_uri);
+
+ // Handle relative imports from URI-based modules.
+ if (uri_scheme) {
+ // Only allow relative imports from file:// and resource:// URIs
+ if (isValidUri(uri_scheme)) {
+ let module_file = Gio.File.new_for_uri(module_uri);
+ let module_parent_file = module_file.get_parent();
+
+ output = module_parent_file.resolve_relative_path(id);
+ full_path = output.get_uri();
+ } else {
+ throw new Error(
+ 'Relative imports can only occur from file:// and resource:// URIs');
+ }
+ } else {
+ // Get the module directory path.
+ const module_file = Gio.File.new_for_path(module_uri);
+ const module_parent_file = module_file.get_parent();
+
+ // Resolve file relative to the module directory.
+ output = module_parent_file.resolve_relative_path(id);
+ full_path = output.get_path();
+ }
+
+ return {output, full_path};
+}
+
+function resolveId(id, module_uri) {
+ // If a module has a path, we'll have stored it in the host field
+ let full_path = null;
+ let output = null;
+
+ if (isAbsolutePath(id))
+ ({output, full_path} = resolveAbsolutePath(id));
+ else if (isRelativePath(id))
+ ({output, full_path} = resolveRelativePath(id, module_uri));
+ else if (isValidUri(id))
+ ({output, full_path} = resolveURI(id));
+
+
+ if (!output || !full_path)
+ return null;
+
+
+ return {output, full_path};
+}
+
+function fromBytes(bytes) {
+ return ByteUtils.toString(bytes, 'utf-8');
+}
+
+function loadFileSync(output, full_path) {
+ try {
+ const [, bytes] = output.load_contents(null);
+ return fromBytes(bytes);
+ } catch (error) {
+ throw new Error(`Unable to load file from: ${full_path}`);
+ }
+}
+
+const ESM_MODULE_URI = 'resource:///org/gnome/gjs/modules/esm/';
+const CORE_MODULE_URI = 'resource:///org/gnome/gjs/modules/core/';
+
+function buildInternalPaths(id) {
+ const directory_uris = [ESM_MODULE_URI, CORE_MODULE_URI];
+
+ return directory_uris
+ .map(uri => {
+ const full_path = GLib.build_pathv('/', [uri, `${id}.js`]);
+
+ debug(`Built path ${full_path} with ${id} for ${uri}.`);
+
+ return {uri, full_path, file: Gio.File.new_for_uri(full_path)};
+ });
+}
+
+function resolveModule(id, module_uri) {
+ // Check if the module has already been loaded
+ //
+ // Order:
+ // - Local imports
+ // - Internal imports
+
+ debug(`Resolving: ${id}`);
+
+ let lookup_module = lookupModule(id);
+
+ if (lookup_module)
+ return lookup_module;
+
+
+ lookup_module = lookupInternalModule(id);
+
+ if (lookup_module)
+ return lookup_module;
+
+
+ // 1) Resolve path and URI-based imports.
+
+ const resolved = resolveId(id, module_uri);
+
+ if (resolved) {
+ const {output, full_path} = resolved;
+
+ debug(`Full path found: ${full_path}`);
+
+ lookup_module = lookupModule(full_path);
+
+ // Check if module is already loaded (relative handling)
+ if (lookup_module)
+ return lookup_module;
+
+
+ const text = loadFileSync(output);
+
+ if (!registerModule(full_path, full_path, text, text.length, false))
+ throw new Error(`Failed to register module: ${full_path}`);
+
+
+ return lookupModule(full_path);
+ }
+
+ // 2) Resolve internal imports.
+
+ const result = buildInternalPaths(id).find(({file}) => file && file.query_exists(null));
+
+ if (!result)
+ throw new Error(`Attempted to load unregistered global module: ${id}`);
+
+
+ const {full_path, file} = result;
+
+ const text = loadFileSync(file, full_path);
+
+ if (!registerInternalModule(id, full_path, text, text.length))
+ return null;
+
+
+ return lookupInternalModule(id);
+}
+
+setModuleResolveHook((referencingInfo, specifier) => {
+ debug('Starting module import...');
+ const uri = getModuleUri(referencingInfo);
+ debug(`Found base URI: ${uri}`);
+
+ return resolveModule(specifier, uri);
+});
diff --git a/installed-tests/esm-test.sh b/installed-tests/esm-test.sh
new file mode 100755
index 00000000..f19b3657
--- /dev/null
+++ b/installed-tests/esm-test.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then
+ gjs="$TOP_BUILDDIR/gjs-console"
+else
+ gjs=gjs-console
+fi
+
+echo 1..1
+
+JS_SCRIPT="$1"
+EXPECTED_OUTPUT="$1.output"
+THE_DIFF=$("$gjs" -m "$JS_SCRIPT" \
+ | diff -u "$EXPECTED_OUTPUT" -)
+if test $? -ne 0; then
+ echo "not ok 1 - $1 # command failed"
+ exit 1
+fi
+
+if test -n "$THE_DIFF"; then
+ echo "not ok 1 - $1"
+ echo "$THE_DIFF" | while read line; do echo "#$line"; done
+else
+ echo "ok 1 - $1"
+fi
diff --git a/installed-tests/esm.test.in b/installed-tests/esm.test.in
new file mode 100644
index 00000000..763091fd
--- /dev/null
+++ b/installed-tests/esm.test.in
@@ -0,0 +1,4 @@
+[Test]
+Type=session
+Exec=@installed_tests_execdir@/esm-test.sh @installed_tests_execdir@/esm/@name@
+Output=TAP
diff --git a/installed-tests/esm/default-import.js b/installed-tests/esm/default-import.js
new file mode 100644
index 00000000..d070eda2
--- /dev/null
+++ b/installed-tests/esm/default-import.js
@@ -0,0 +1,3 @@
+import $ from './exports.js';
+
+print($);
\ No newline at end of file
diff --git a/installed-tests/esm/default-import.js.output b/installed-tests/esm/default-import.js.output
new file mode 100644
index 00000000..7ed6ff82
--- /dev/null
+++ b/installed-tests/esm/default-import.js.output
@@ -0,0 +1 @@
+5
diff --git a/installed-tests/esm/exports.js b/installed-tests/esm/exports.js
new file mode 100644
index 00000000..c846291d
--- /dev/null
+++ b/installed-tests/esm/exports.js
@@ -0,0 +1,3 @@
+export default 5;
+
+export const NamedExport = 'Hello, World';
diff --git a/installed-tests/esm/gi-import.js b/installed-tests/esm/gi-import.js
new file mode 100644
index 00000000..734b1658
--- /dev/null
+++ b/installed-tests/esm/gi-import.js
@@ -0,0 +1,3 @@
+import gi from 'gi';
+
+print(gi.require('GObject'));
diff --git a/installed-tests/esm/gi-import.js.output b/installed-tests/esm/gi-import.js.output
new file mode 100644
index 00000000..12a2890d
--- /dev/null
+++ b/installed-tests/esm/gi-import.js.output
@@ -0,0 +1 @@
+[object GIRepositoryNamespace]
diff --git a/installed-tests/esm/named-import.js b/installed-tests/esm/named-import.js
new file mode 100644
index 00000000..0c4ef4e0
--- /dev/null
+++ b/installed-tests/esm/named-import.js
@@ -0,0 +1,3 @@
+import { NamedExport } from './exports.js';
+
+print(NamedExport);
diff --git a/installed-tests/esm/named-import.js.output b/installed-tests/esm/named-import.js.output
new file mode 100644
index 00000000..3fa0d4b9
--- /dev/null
+++ b/installed-tests/esm/named-import.js.output
@@ -0,0 +1 @@
+Hello, World
diff --git a/installed-tests/esm/system-import.js b/installed-tests/esm/system-import.js
new file mode 100644
index 00000000..756c0725
--- /dev/null
+++ b/installed-tests/esm/system-import.js
@@ -0,0 +1,3 @@
+import system from 'system';
+
+print(system.exit);
diff --git a/installed-tests/esm/system-import.js.output b/installed-tests/esm/system-import.js.output
new file mode 100644
index 00000000..06e7382b
--- /dev/null
+++ b/installed-tests/esm/system-import.js.output
@@ -0,0 +1,3 @@
+function exit() {
+ [native code]
+}
diff --git a/installed-tests/meson.build b/installed-tests/meson.build
index 9210957f..9ed1fa8d 100644
--- a/installed-tests/meson.build
+++ b/installed-tests/meson.build
@@ -40,6 +40,45 @@ endforeach
subdir('js')
+# esm script tests #
+
+esm_tests = [
+ 'default-import',
+ 'named-import',
+ 'gi-import',
+ 'system-import'
+]
+
+esm_test_driver = find_program(files('esm-test.sh'))
+if get_option('installed_tests')
+ install_data('esm-test.sh', install_dir: installed_tests_execdir)
+endif
+
+foreach test : esm_tests
+ test_file = files('esm' / '@0@.js'.format(test))
+
+ test('@0@ command'.format(test), esm_test_driver,
+ args: test_file, env: tests_environment, protocol: 'tap',
+ suite: 'ESM')
+
+ test_description_subst = {
+ 'name': '@0@.js'.format(test),
+ 'installed_tests_execdir': installed_tests_execdir,
+ }
+ test_description = configure_file(configuration: test_description_subst,
+ input: 'esm.test.in',
+ output: '@0@.test'.format(test),
+ install: get_option('installed_tests'),
+ install_dir: installed_tests_metadir)
+
+ if get_option('installed_tests')
+ install_data(test_file, install_dir: installed_tests_execdir / 'esm')
+ install_data('esm' / '@0@.js'.format(test),
+ 'esm' / '@0@.js.output'.format(test),
+ install_dir: installed_tests_execdir / 'esm')
+ endif
+endforeach
+
# Debugger script tests #
debugger_tests = [
diff --git a/js.gresource.xml b/js.gresource.xml
index f0c3b934..dd5a5341 100644
--- a/js.gresource.xml
+++ b/js.gresource.xml
@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/gjs">
+ <!-- Internal scripts -->
+ <file>gjs/module.js</file>
+
+ <!-- ESM-based modules -->
+ <file>modules/esm/gi.js</file>
+ <file>modules/esm/system.js</file>
+
<!-- Script-based Modules -->
<file>modules/script/_bootstrap/debugger.js</file>
<file>modules/script/_bootstrap/default.js</file>
diff --git a/meson.build b/meson.build
index 574ae1b1..3b72adc4 100644
--- a/meson.build
+++ b/meson.build
@@ -380,6 +380,7 @@ libgjs_sources = [
'gjs/error-types.cpp',
'gjs/global.cpp', 'gjs/global.h',
'gjs/importer.cpp', 'gjs/importer.h',
+ 'gjs/internal.cpp', 'gjs/internal.h',
'gjs/mem.cpp', 'gjs/mem-private.h',
'gjs/module.cpp', 'gjs/module.h',
'gjs/native.cpp', 'gjs/native.h',
diff --git a/modules/esm/.eslintrc.yml b/modules/esm/.eslintrc.yml
new file mode 100644
index 00000000..610e5085
--- /dev/null
+++ b/modules/esm/.eslintrc.yml
@@ -0,0 +1,4 @@
+---
+extends: '../../.eslintrc.yml'
+parserOptions:
+ ecmaVersion: 2020
diff --git a/modules/esm/gi.js b/modules/esm/gi.js
new file mode 100644
index 00000000..274d3827
--- /dev/null
+++ b/modules/esm/gi.js
@@ -0,0 +1,14 @@
+const gi = import.meta.require('gi');
+
+const Gi = {
+ require(name, version = null) {
+ if (version !== null) {
+ gi.versions[name] = version;
+ }
+
+ return gi[name];
+ }
+}
+Object.freeze(Gi);
+
+export default Gi;
\ No newline at end of file
diff --git a/modules/esm/system.js b/modules/esm/system.js
new file mode 100644
index 00000000..1523b7be
--- /dev/null
+++ b/modules/esm/system.js
@@ -0,0 +1 @@
+export default import.meta.require('system');
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]