[gjs/esm/static-imports: 7/10] esm: Enable static module imports.
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm/static-imports: 7/10] esm: Enable static module imports.
- Date: Tue, 24 Nov 2020 19:34:38 +0000 (UTC)
commit 7d75d5daa9af8f0e61231779d2f04ea6676c6a82
Author: Evan Welsh <noreply evanwelsh com>
Date: Wed Oct 28 18:52:46 2020 -0500
esm: Enable static module imports.
gi/repo.cpp | 37 ++++
gjs/context-private.h | 4 +
gjs/context.cpp | 234 +++++++++++++++++++++---
gjs/global.cpp | 155 ++++++++++++++++
gjs/global.h | 15 ++
gjs/internal.cpp | 456 ++++++++++++++++++++++++++++++++++++++++++++++
gjs/internal.h | 30 +++
gjs/jsapi-util.cpp | 18 ++
gjs/jsapi-util.h | 2 +
gjs/module.cpp | 150 +++++++++++++++
gjs/module.h | 16 ++
js.gresource.xml | 10 +
lib/.eslintrc.yml | 26 +++
lib/bootstrap/module.js | 310 +++++++++++++++++++++++++++++++
lib/entry.js | 7 +
lib/jsconfig.json | 29 +++
lib/modules/esm.js | 220 ++++++++++++++++++++++
lib/modules/gi.js | 22 +++
lib/types.d.ts | 111 +++++++++++
meson.build | 1 +
modules/esm/.eslintrc.yml | 5 +
modules/esm/gi.js | 22 +++
modules/esm/system.js | 17 ++
tools/package.json | 11 +-
tools/yarn.lock | 73 ++++++++
25 files changed, 1955 insertions(+), 26 deletions(-)
---
diff --git a/gi/repo.cpp b/gi/repo.cpp
index fa5757da..d4c1b3c2 100644
--- a/gi/repo.cpp
+++ b/gi/repo.cpp
@@ -585,6 +585,40 @@ static JSObject* lookup_namespace(JSContext* cx, JSObject* global,
return retval;
}
+GJS_JSAPI_RETURN_CONVENTION
+static JSObject* lookup_internal_namespace(JSContext* cx, JSObject* global,
+ JS::HandleId ns_name) {
+ JS::RootedObject native_registry(cx, gjs_get_native_registry(cx, 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_global_registry_get(cx, native_registry, 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* cx,
JS::HandleId ns_name) {
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
@@ -594,6 +628,9 @@ JSObject* gjs_lookup_namespace_object_by_name(JSContext* cx,
case GjsGlobalType::DEFAULT:
ns = lookup_namespace(cx, global, ns_name);
break;
+ case GjsGlobalType::INTERNAL:
+ ns = lookup_internal_namespace(cx, global, ns_name);
+ break;
case GjsGlobalType::DEBUGGER:
ns = nullptr;
// It is not possible to load namespace objects in the debugger
diff --git a/gjs/context-private.h b/gjs/context-private.h
index 5f1aec75..84fde9a6 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -75,6 +75,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;
@@ -169,6 +170,9 @@ class GjsContextPrivate : public JS::JobQueue {
}
[[nodiscard]] JSContext* context() const { return m_cx; }
[[nodiscard]] JSObject* global() const { return m_global.get(); }
+ [[nodiscard]] JSObject* internal_global(void) const {
+ return m_internal_global.get();
+ }
[[nodiscard]] GjsProfiler* profiler() const { return m_profiler; }
[[nodiscard]] const GjsAtoms& atoms() const { return *m_atoms; }
[[nodiscard]] bool destroying() const { return m_destroying; }
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 5a430015..5805c2aa 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -34,11 +34,15 @@
#include <js/AllocPolicy.h> // for SystemAllocPolicy
#include <js/CallArgs.h> // for UndefinedHandleValue
+#include <js/CharacterEncoding.h>
#include <js/CompilationAndEvaluation.h>
#include <js/CompileOptions.h>
+#include <js/Conversions.h>
+#include <js/ErrorReport.h>
#include <js/GCAPI.h> // for JS_GC, JS_AddExtraGCRootsTr...
#include <js/GCHashTable.h> // for WeakCache
#include <js/GCVector.h> // for RootedVector
+#include <js/Modules.h>
#include <js/Promise.h> // for JobQueue::SavedJobQueue
#include <js/PropertyDescriptor.h> // for JSPROP_PERMANENT, JSPROP_RE...
#include <js/RootingAPI.h>
@@ -51,6 +55,7 @@
#include <js/ValueArray.h>
#include <jsapi.h> // for JS_IsExceptionPending, ...
#include <jsfriendapi.h> // for DumpHeap, IgnoreNurseryObjects
+#include <mozilla/HashTable.h>
#include <mozilla/UniquePtr.h>
#include "gi/object.h"
@@ -64,8 +69,10 @@
#include "gjs/error-types.h"
#include "gjs/global.h"
#include "gjs/importer.h"
+#include "gjs/internal.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"
@@ -306,6 +313,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);
@@ -392,6 +401,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;
@@ -477,17 +487,17 @@ 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)) {
@@ -495,6 +505,28 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
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.
+
+ 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)};
@@ -516,6 +548,33 @@ 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);
+
+ JS::SetModuleResolveHook(rt, gjs_module_resolve);
+ JS::SetModuleMetadataHook(rt, gjs_populate_module_meta);
+
+ if (!JS_DefineProperty(m_cx, internal_global, "moduleGlobalThis", global,
+ JSPROP_PERMANENT)) {
+ g_warning("Failed to define module global in internal global.");
+ }
+
+ if (!gjs_load_internal_module(cx, "bootstrap/module")) {
+ gjs_log_exception(cx);
+ g_error("Failed to load internal module loaders.");
+ }
+
+ JS::RootedObject entry(
+ cx, gjs_module_load(cx, "resource:///org/gnome/gjs/lib/entry.js",
+ "resource:///org/gnome/gjs/lib/entry.js"));
+
+ if (!JS::ModuleInstantiate(cx, entry)) {
+ gjs_log_exception(cx);
+ }
+
+ if (!JS::ModuleEvaluate(cx, entry)) {
+ gjs_log_exception(cx);
+ }
}
static void
@@ -1020,32 +1079,159 @@ 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(ewlsh): Implement eval_module
- g_print("GjsContextPrivate::eval_module called on identifier %s.",
- identifier);
+ bool auto_profile = m_should_profile;
+
+ if (auto_profile &&
+ (_gjs_profiler_is_running(m_profiler) || m_should_listen_sigusr2))
+ auto_profile = false;
- *exit_status_p = 1;
- *error = nullptr;
+ if (auto_profile)
+ gjs_profiler_start(m_profiler);
- g_error(
- "GjsContextPrivate::eval_module is not implemented. Exiting with "
- "error.");
+ JSAutoRealm ac(m_cx, m_global);
- return false;
+ JS::RootedObject registry(m_cx, gjs_get_module_registry(m_cx, m_global));
+
+ JS::RootedValue keyv(m_cx);
+
+ if (!gjs_string_from_utf8(m_cx, identifier, &keyv)) {
+ return false;
+ }
+
+ JS::RootedId key(m_cx);
+
+ if (!JS_ValueToId(m_cx, keyv, &key)) {
+ return false;
+ }
+
+ JS::RootedObject obj(m_cx);
+
+ if (!gjs_global_registry_get(m_cx, registry, key, &obj) || !obj) {
+ g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
+ "Cannot find module with identifier %s.", identifier);
+ return false;
+ }
+
+ bool ok = true;
+
+ 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,
- GError** error G_GNUC_UNUSED) {
- // TODO(ewlsh): Implement register_module
- g_warning(
- "GjsContextPrivate::register_module is not yet implemented. Printing "
- "module...");
+ const char* file_uri, GError** error) {
+ JSAutoRealm ac(m_cx, m_global);
- *error = nullptr;
+ // 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);
- g_warning("Identifier: %s\nFilename: %s\n", identifier, filename);
- return true;
+ JS::RootedObject module(m_cx, gjs_module_load(m_cx, identifier, file_uri));
+
+ if (!module) {
+ gjs_log_exception(m_cx);
+ return false;
+ }
+
+ if (module) {
+ 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 70fcf317..86be5639 100644
--- a/gjs/global.cpp
+++ b/gjs/global.cpp
@@ -32,6 +32,7 @@
#include "gjs/context-private.h"
#include "gjs/engine.h"
#include "gjs/global.h"
+#include "gjs/internal.h"
#include "gjs/jsapi-util.h"
#include "gjs/module.h"
#include "gjs/native.h"
@@ -189,6 +190,14 @@ class GjsGlobal : GjsBaseGlobal {
gjs_set_global_slot(global, GjsGlobalSlot::NATIVE_REGISTRY,
JS::ObjectValue(*native_registry));
+ JS::RootedObject module_registry(cx, JS::NewMapObject(cx));
+
+ if (!module_registry) {
+ return false;
+ }
+ gjs_set_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY,
+ JS::ObjectValue(*module_registry));
+
JS::Value v_importer =
gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS);
g_assert(((void) "importer should be defined before passing null "
@@ -257,6 +266,135 @@ class GjsDebuggerGlobal : GjsBaseGlobal {
}
};
+class GjsInternalGlobal : GjsBaseGlobal {
+ static constexpr JSFunctionSpec static_funcs[] = {
+ JS_FN("compileModule", CompileModule, 2, 0),
+ JS_FN("compileInternalModule", CompileInternalModule, 2, 0),
+ JS_FN("getRegistry", GetRegistry, 1, 0),
+ JS_FN("importSync", ImportSync, 1, 0),
+ JS_FN("setModuleLoadHook", SetModuleLoadHook, 3, 0),
+ JS_FN("setModuleMetaHook", SetModuleMetaHook, 2, 0),
+ JS_FN("setModulePrivate", SetModulePrivate, 2, 0),
+ JS_FN("setModuleResolveHook", SetModuleResolveHook, 2, 0),
+ JS_FS_END};
+
+ static constexpr JSClassOps classops = {nullptr, // addProperty
+ nullptr, // deleteProperty
+ nullptr, // enumerate
+ JS_NewEnumerateStandardClasses,
+ JS_ResolveStandardClass,
+ JS_MayResolveStandardClass,
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // hasInstance
+ nullptr, // construct
+ JS_GlobalObjectTraceHook};
+
+ static constexpr JSClass klass = {
+ "GjsInternalGlobal",
+ JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(
+ static_cast<uint32_t>(GjsInternalGlobalSlot::LAST)),
+ &classops,
+ };
+
+ public:
+ [[nodiscard]] static JSObject* create(JSContext* cx) {
+ return GjsBaseGlobal::create(cx, &klass);
+ }
+
+ [[nodiscard]] static JSObject* create_with_compartment(
+ JSContext* cx, JS::HandleObject cmp_global) {
+ return GjsBaseGlobal::create_with_compartment(cx, cmp_global, &klass);
+ }
+
+ static bool define_properties(JSContext* cx, JS::HandleObject global,
+ const char* realm_name,
+ const char* bootstrap_script G_GNUC_UNUSED) {
+ 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));
+
+ JSAutoRealm ar(cx, global);
+ JS::RootedObject native_registry(cx, JS::NewMapObject(cx));
+
+ if (!native_registry) {
+ return false;
+ }
+
+ gjs_set_global_slot(global, GjsGlobalSlot::NATIVE_REGISTRY,
+ JS::ObjectValue(*native_registry));
+
+ JS::RootedObject module_registry(cx, JS::NewMapObject(cx));
+
+ if (!module_registry) {
+ return false;
+ }
+ gjs_set_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY,
+ JS::ObjectValue(*module_registry));
+ 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 (!gjs_global_registry_set(cx, native_registry,
+ atoms.private_ns_marker(), privateNS) ||
+ !gjs_global_registry_set(cx, native_registry, atoms.gobject(),
+ gobject) ||
+ !gjs_global_registry_set(cx, native_registry, atoms.glib(), glib) ||
+ !gjs_global_registry_set(cx, native_registry, atoms.gio(), gio) ||
+ !JS_DefinePropertyById(cx, global, atoms.glib(), glib,
+ JSPROP_PERMANENT) ||
+ !JS_DefinePropertyById(cx, global, atoms.gio(), gio,
+ JSPROP_PERMANENT) ||
+ !JS_DefinePropertyById(cx, global, atoms.gobject(), gobject,
+ 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;
+ }
+ JS::RootedObject io(cx, JS_NewPlainObject(cx));
+
+ if (!gjs_load_native_module(cx, "_print", &io) ||
+ !JS_DefineProperty(cx, global, "IO", io, JSPROP_PERMANENT)) {
+ gjs_throw(cx, "Failed to define IO functions.");
+ return false;
+ }
+
+ return true;
+ }
+};
+
/**
* gjs_create_global_object:
* @cx: a #JSContext
@@ -275,6 +413,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;
}
@@ -285,6 +426,8 @@ 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;
}
@@ -437,6 +580,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;
@@ -449,6 +595,8 @@ template void gjs_set_global_slot(JSObject* global, GjsBaseGlobalSlot 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);
JS::Value detail::get_global_slot(JSObject* global, uint32_t slot) {
return JS_GetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot);
@@ -456,6 +604,8 @@ JS::Value detail::get_global_slot(JSObject* global, uint32_t slot) {
template JS::Value gjs_get_global_slot(JSObject* global,
GjsBaseGlobalSlot 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;
@@ -464,3 +614,8 @@ decltype(GjsGlobal::static_props) constexpr GjsGlobal::static_props;
decltype(GjsDebuggerGlobal::klass) constexpr GjsDebuggerGlobal::klass;
decltype(
GjsDebuggerGlobal::static_funcs) constexpr GjsDebuggerGlobal::static_funcs;
+
+decltype(GjsInternalGlobal::klass) constexpr GjsInternalGlobal::klass;
+decltype(GjsInternalGlobal::classops) constexpr GjsInternalGlobal::classops;
+decltype(
+ GjsInternalGlobal::static_funcs) constexpr GjsInternalGlobal::static_funcs;
diff --git a/gjs/global.h b/gjs/global.h
index d087f851..822ff089 100644
--- a/gjs/global.h
+++ b/gjs/global.h
@@ -19,6 +19,7 @@
enum class GjsGlobalType {
DEFAULT,
DEBUGGER,
+ INTERNAL,
};
enum class GjsBaseGlobalSlot : uint32_t {
@@ -32,6 +33,14 @@ enum class GjsDebuggerGlobalSlot : uint32_t {
enum class GjsGlobalSlot : uint32_t {
IMPORTS = static_cast<uint32_t>(GjsBaseGlobalSlot::LAST),
+ // Stores the import resolution hook
+ IMPORT_HOOK,
+ // Stores the module creation hook
+ MODULE_HOOK,
+ // Stores the metadata population hook
+ META_HOOK,
+ // Stores the module registry
+ MODULE_REGISTRY,
NATIVE_REGISTRY,
PROTOTYPE_gtype,
PROTOTYPE_importer,
@@ -54,6 +63,10 @@ enum class GjsGlobalSlot : uint32_t {
LAST,
};
+enum class GjsInternalGlobalSlot : uint32_t {
+ LAST = static_cast<uint32_t>(GjsGlobalSlot::LAST),
+};
+
bool gjs_global_is_type(JSContext* cx, GjsGlobalType type);
GjsGlobalType gjs_global_get_type(JSObject* global);
@@ -84,6 +97,7 @@ template <typename Slot>
inline void gjs_set_global_slot(JSObject* global, Slot slot, JS::Value value) {
static_assert(std::is_same_v<GjsBaseGlobalSlot, Slot> ||
std::is_same_v<GjsGlobalSlot, Slot> ||
+ std::is_same_v<GjsInternalGlobalSlot, Slot> ||
std::is_same_v<GjsDebuggerGlobalSlot, Slot>,
"Must use a GJS global slot enum");
detail::set_global_slot(global, static_cast<uint32_t>(slot), value);
@@ -93,6 +107,7 @@ template <typename Slot>
inline JS::Value gjs_get_global_slot(JSObject* global, Slot slot) {
static_assert(std::is_same_v<GjsBaseGlobalSlot, Slot> ||
std::is_same_v<GjsGlobalSlot, Slot> ||
+ std::is_same_v<GjsInternalGlobalSlot, Slot> ||
std::is_same_v<GjsDebuggerGlobalSlot, Slot>,
"Must use a GJS global slot enum");
return detail::get_global_slot(global, static_cast<uint32_t>(slot));
diff --git a/gjs/internal.cpp b/gjs/internal.cpp
new file mode 100644
index 00000000..d769695e
--- /dev/null
+++ b/gjs/internal.cpp
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020 Evan Welsh <contact evanwelsh com>
+
+#include "gjs/internal.h"
+
+#include <config.h>
+#include <gio/gio.h>
+#include <girepository.h>
+#include <glib-object.h>
+#include <glib.h>
+#include <js/Array.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/Modules.h>
+#include <js/Promise.h>
+#include <js/PropertyDescriptor.h>
+#include <js/RootingAPI.h>
+#include <js/SourceText.h>
+#include <js/TypeDecls.h>
+#include <js/Wrapper.h>
+#include <jsapi.h> // for JS_DefinePropertyById, ...
+#include <jsfriendapi.h>
+#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/engine.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"
+
+#include "gi/repo.h"
+
+// NOTE: You have to be very careful in this file to only do operations within
+// the correct global!
+
+/**
+ * gjs_load_internal_module:
+ *
+ * Loads a module source from an internal resource,
+ * resource:///org/gnome/gjs/lib/{#identifier}.js, registers it in the internal
+ * global's module registry, and proceeds to compile, initialize, and evaluate
+ * the module.
+ *
+ * @param cx the current JSContext
+ * @param identifier the identifier of the internal module
+ *
+ * @returns whether an error occurred while loading or evaluating the module.
+ */
+bool gjs_load_internal_module(JSContext* cx, const char* identifier) {
+ GjsAutoChar full_path(
+ g_strdup_printf("resource:///org/gnome/gjs/lib/%s.js", identifier));
+
+ char* script;
+ size_t script_len;
+
+ if (!gjs_load_internal_source(cx, full_path, &script, &script_len)) {
+ return false;
+ }
+
+ std::u16string utf16_string = gjs_utf8_script_to_utf16(script, script_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::CompileOptions options(cx);
+ options.setIntroductionType("Internal Module Bootstrap");
+ options.setFileAndLine(full_path, 1);
+ options.setSelfHostingMode(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::CompileModule(cx, options, buf));
+ JS::RootedObject registry(cx, gjs_get_module_registry(cx, internal_global));
+
+ JS::RootedId key(cx);
+ JS::RootedValue keyVal(cx);
+
+ if (!gjs_string_from_utf8(cx, full_path, &keyVal)) {
+ gjs_throw(cx, "no string");
+ return false;
+ }
+ if (!JS_ValueToId(cx, keyVal, &key)) {
+ gjs_throw(cx, "bad val to id");
+ return false;
+ }
+
+ if (!gjs_global_registry_set(cx, registry, key, module)) {
+ gjs_throw(cx, "failed to set registry");
+ return false;
+ }
+
+ if (!JS::ModuleInstantiate(cx, module)) {
+ gjs_log_exception(cx);
+ gjs_throw(cx, "not instantiation");
+ return false;
+ }
+
+ if (!JS::ModuleEvaluate(cx, module)) {
+ gjs_log_exception(cx);
+ gjs_throw(cx, "failed to evaluate");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * SetModuleLoadHook:
+ *
+ * Sets the MODULE_HOOK slot of the passed global to the second argument which
+ * must be an object. Setting a non-function object is possible but will throw
+ * a not-callable error when gjs_module_load is used.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while setting the module hook.
+ */
+bool SetModuleLoadHook(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "setModuleLoadHook", 2)) {
+ return false;
+ }
+
+ JS::RootedValue gv(cx, args[0]);
+ JS::RootedValue mv(cx, args[1]);
+
+ g_assert(gv.isObject());
+
+ // The hook is stored in the internal global.
+ JS::RootedObject global(cx, &gv.toObject());
+ gjs_set_global_slot(global, GjsGlobalSlot::MODULE_HOOK, mv);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * SetModuleResolveHook:
+ *
+ * Sets the IMPORT_HOOK slot of the passed global to the second argument which
+ * must be an object. Setting a non-function object is possible but will throw
+ * a not-callable error when gjs_module_load is used.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while setting the import hook.
+ */
+bool SetModuleResolveHook(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "setModuleResolveHook", 2)) {
+ return false;
+ }
+
+ JS::RootedValue gv(cx, args[0]);
+ JS::RootedValue mv(cx, args[1]);
+
+ g_assert(gv.isObject());
+
+ // The hook is stored in the internal global.
+ JS::RootedObject global(cx, &gv.toObject());
+ gjs_set_global_slot(global, GjsGlobalSlot::IMPORT_HOOK, mv);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * SetModuleResolveHook:
+ *
+ * Sets the META_HOOK slot of the passed global to the second argument which
+ * must be an object. Setting a non-function object is possible but will throw
+ * a not-callable error when gjs_module_load is used.
+ *
+ * The META_HOOK is passed two parameters, a plain object for population with
+ * meta properties and the module's private object.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while setting the meta hook.
+ */
+bool SetModuleMetaHook(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "setModuleMetaHook", 2)) {
+ return false;
+ }
+
+ JS::RootedValue gv(cx, args[0]);
+ JS::RootedValue mv(cx, args[1]);
+
+ g_assert(gv.isObject());
+
+ // The hook is stored in the internal global.
+ JS::RootedObject global(cx, &gv.toObject());
+ gjs_set_global_slot(global, GjsGlobalSlot::META_HOOK, mv);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * compile_module:
+ *
+ * Compiles the a module source text into an internal #Module object
+ * given the module's URI as the first argument.
+ *
+ * @param cx the current JSContext
+ * @param args the call args from the native function call
+ *
+ * @returns whether an error occurred while compiling the module.
+ */
+static bool compile_module(JSContext* cx, JS::CallArgs args) {
+ g_assert(args[0].isString());
+ g_assert(args[1].isString());
+
+ JS::RootedString s1(cx, args[0].toString());
+ JS::RootedString s2(cx, args[1].toString());
+
+ JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, s1);
+ JS::UniqueChars text = JS_EncodeStringToUTF8(cx, s2);
+ size_t text_len = strlen(text.get());
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(uri.get(), 1).setSourceIsLazy(false);
+
+ std::u16string utf16_string(gjs_utf8_script_to_utf16(text.get(), text_len));
+
+ JS::SourceText<char16_t> buf;
+ if (!buf.init(cx, utf16_string.c_str(), utf16_string.size(),
+ JS::SourceOwnership::Borrowed))
+ return false;
+
+ JS::RootedObject new_module(cx, JS::CompileModule(cx, options, buf));
+ if (!new_module) {
+ gjs_log_exception(cx);
+ return false;
+ }
+ args.rval().setObjectOrNull(new_module);
+
+ return true;
+}
+
+/**
+ * CompileInternalModule:
+ *
+ * Compiles a module source text within the internal global's realm.
+ *
+ * NOTE: Modules compiled with this function can only be executed
+ * within the internal global's realm.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while compiling the module.
+ */
+bool CompileInternalModule(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "compileInternalModule", 2)) {
+ return false;
+ }
+
+ JS::RootedObject global(cx, gjs_get_internal_global(cx));
+ JSAutoRealm ar(cx, global);
+
+ return compile_module(cx, args);
+}
+
+/**
+ * CompileModule:
+ *
+ * Compiles a module source text within the import global's realm.
+ *
+ * NOTE: Modules compiled with this function can only be executed
+ * within the import global's realm.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while compiling the module.
+ */
+bool CompileModule(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "compileModule", 2)) {
+ return false;
+ }
+
+ JS::RootedObject global(cx, gjs_get_import_global(cx));
+ JSAutoRealm ar(cx, global);
+
+ return compile_module(cx, args);
+}
+
+/**
+ * SetModulePrivate:
+ *
+ * Sets the private object of an internal #Module object.
+ * The private object must be a #JSObject.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while setting the private.
+ */
+bool SetModulePrivate(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "setModulePrivate", 1)) {
+ return false;
+ }
+
+ JS::RootedObject moduleObj(cx, &args[0].toObject());
+ JS::RootedObject privateObj(cx, &args[1].toObject());
+
+ JS::SetModulePrivate(moduleObj, JS::ObjectValue(*privateObj));
+ return true;
+}
+
+/**
+ * ImportSync:
+ *
+ * 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.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while importing the native module.
+ */
+bool ImportSync(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 importSync()");
+ return false;
+ }
+
+ JS::RootedObject global(m_cx, gjs_get_import_global(m_cx));
+
+ JSAutoRealm ar(m_cx, global);
+
+ 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;
+ }
+
+ JS::RootedObject native_registry(m_cx,
+ gjs_get_native_registry(m_cx, global));
+ JS::RootedObject map_val(m_cx);
+
+ JS::RootedId map_key(m_cx);
+ if (!JS_StringToId(m_cx, jstr, &map_key)) {
+ return false;
+ }
+
+ if (!gjs_global_registry_get(m_cx, native_registry, map_key, &map_val)) {
+ return false;
+ }
+ if (map_val) {
+ argv.rval().setObject(*map_val);
+ 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 (!gjs_global_registry_set(m_cx, native_registry, map_key, native_obj)) {
+ return false;
+ }
+
+ argv.rval().setObject(*native_obj);
+ return true;
+}
+
+/**
+ * GetRegistry:
+ *
+ * Retrieves the module registry for the passed global object.
+ *
+ * @param cx the current JSContext
+ * @param argc
+ * @param vp
+ *
+ * @returns whether an error occurred while retrieving the registry.
+ */
+bool GetRegistry(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "getRegistry", 1)) {
+ return false;
+ }
+
+ JS::RootedObject global(cx, &args[0].toObject()); // global
+
+ {
+ JSAutoRealm ar(cx, global);
+
+ JS::RootedObject registry(cx, gjs_get_module_registry(cx, global));
+
+ args.rval().setObject(*registry);
+
+ return true;
+ }
+}
diff --git a/gjs/internal.h b/gjs/internal.h
new file mode 100644
index 00000000..91e47f59
--- /dev/null
+++ b/gjs/internal.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020 Evan Welsh <contact evanwelsh com>
+
+#ifndef GJS_INTERNAL_H_
+#define GJS_INTERNAL_H_
+
+#include <config.h>
+
+#include <js/TypeDecls.h>
+#include <jsapi.h>
+
+bool gjs_load_internal_module(JSContext* cx, const char* identifier);
+
+bool CompileModule(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool CompileInternalModule(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool GetRegistry(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool ImportSync(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool SetModuleLoadHook(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool SetModuleMetaHook(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool SetModulePrivate(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool SetModuleResolveHook(JSContext* cx, unsigned argc, JS::Value* vp);
+
+#endif // GJS_INTERNAL_H_
diff --git a/gjs/jsapi-util.cpp b/gjs/jsapi-util.cpp
index 1bcfed44..7ff6843f 100644
--- a/gjs/jsapi-util.cpp
+++ b/gjs/jsapi-util.cpp
@@ -633,6 +633,24 @@ JSObject* gjs_get_import_global(JSContext* cx) {
return GjsContextPrivate::from_cx(cx)->global();
}
+/**
+ * gjs_get_internal_global:
+ *
+ * Gets the "internal global" for the context's runtime. The internal
+ * global object is the global object used for all "internal" JavaScript
+ * code (e.g. the module loader) that should not be accessible from users'
+ * code.
+ *
+ * @param context a #JSContext
+ *
+ * @returns the "internal global" for the context's
+ * runtime. Will never return %NULL while GJS has an active context
+ * for the runtime.
+ */
+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 ee004037..527e36f2 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -388,6 +388,8 @@ struct GCPolicy<GjsAutoParam> : public IgnoreGCPolicy<GjsAutoParam> {};
[[nodiscard]] JSObject* gjs_get_import_global(JSContext* cx);
+[[nodiscard]] JSObject* gjs_get_internal_global(JSContext* cx);
+
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 6de166e8..3bf3c104 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -273,3 +273,153 @@ JSObject* gjs_get_native_registry(JSContext* cx, JSObject* global) {
return root_registry;
}
+
+/**
+ * gjs_get_module_registry:
+ *
+ * Retrieves a global's module registry from the MODULE_REGISTRY slot.
+ * Registries are actually JS Maps.
+ *
+ * @param cx the current #JSContext
+ * @param global a global #JSObject
+ *
+ * @returns the registry map as a #JSObject
+ */
+JSObject* gjs_get_module_registry(JSContext* cx, JSObject* global) {
+ JS::Value esm_registry =
+ gjs_get_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY);
+
+ g_assert(esm_registry.isObject());
+
+ JS::RootedObject root_registry(cx, &esm_registry.toObject());
+
+ return root_registry;
+}
+
+/**
+ * gjs_module_load:
+ *
+ * Loads and registers a module given a specifier and
+ * URI.
+ *
+ * @param importer the private value of the #Module object initiating the import
+ * or undefined.
+ * @param meta_object the import.meta object
+ *
+ * @returns whether an error occurred while resolving the specifier.
+ */
+JSObject* gjs_module_load(JSContext* cx, const char* identifier,
+ const char* file_uri) {
+ JS::RootedValue id_value(cx), uri_value(cx);
+
+ if (!gjs_string_from_utf8(cx, identifier, &id_value) ||
+ !gjs_string_from_utf8(cx, file_uri, &uri_value)) {
+ return nullptr;
+ }
+
+ JS::RootedString id(cx, id_value.toString()), uri(cx, uri_value.toString());
+
+ 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 hook(
+ cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_HOOK));
+
+ JS::RootedValueArray<2> args(cx);
+ args[0].setString(id);
+ args[1].setString(uri);
+
+ JS::RootedValue result(cx);
+
+ if (!JS_CallFunctionValue(cx, nullptr, hook, args, &result)) {
+ gjs_log_exception(cx);
+ return nullptr;
+ }
+
+ g_assert(result.isObject() && "Module hook failed to return an object!");
+
+ JS::RootedObject module(cx, &result.toObject());
+
+ return module;
+}
+
+/**
+ * gjs_populate_module_meta:
+ *
+ * Hook SpiderMonkey calls to populate the import.meta object.
+ *
+ * @param private_ref the private value for the #Module object
+ * @param meta_object the import.meta object
+ *
+ * @returns whether an error occurred while populating the module meta.
+ */
+bool gjs_populate_module_meta(JSContext* cx, JS::Handle<JS::Value> private_ref,
+ JS::Handle<JSObject*> meta_object_handle) {
+ JS::RootedObject meta(cx, meta_object_handle);
+
+ if (private_ref.isObject()) {
+ JS::RootedObject module(cx, &private_ref.toObject());
+
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+
+ JS::RootedValue hook(
+ cx, gjs_get_global_slot(global, GjsGlobalSlot::META_HOOK));
+
+ JS::RootedValueArray<3> args(cx);
+ args[0].setObject(*module);
+ args[1].setObject(*meta);
+
+ JS::RootedValue result(cx);
+
+ JS::RootedValue ignore_result(cx);
+ if (!JS_CallFunctionValue(cx, nullptr, hook, args, &ignore_result)) {
+ gjs_log_exception(cx);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * gjs_module_resolve:
+ *
+ * Hook SpiderMonkey calls to resolve import specifiers.
+ *
+ * @param importer the private value of the #Module object initiating the import
+ * or undefined.
+ * @param meta_object the import.meta object
+ *
+ * @returns whether an error occurred while resolving the specifier.
+ */
+JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importer,
+ 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.");
+
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+
+ JS::RootedValue hookValue(
+ cx, gjs_get_global_slot(global, GjsGlobalSlot::IMPORT_HOOK));
+
+ JS::RootedValueArray<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.toObject());
+
+ return module;
+}
diff --git a/gjs/module.h b/gjs/module.h
index eb81fe50..b2638933 100644
--- a/gjs/module.h
+++ b/gjs/module.h
@@ -25,4 +25,20 @@ gjs_module_import(JSContext *cx,
GJS_JSAPI_RETURN_CONVENTION
JSObject* gjs_get_native_registry(JSContext* cx, JSObject* global);
+GJS_JSAPI_RETURN_CONVENTION
+JSObject* gjs_get_module_registry(JSContext* cx, JSObject* global);
+
+GJS_JSAPI_RETURN_CONVENTION
+JSObject* gjs_module_load(JSContext* cx, const char* identifier,
+ const char* uri);
+
+GJS_JSAPI_RETURN_CONVENTION
+JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue mod_val,
+ JS::HandleString specifier);
+
+GJS_JSAPI_RETURN_CONVENTION
+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/js.gresource.xml b/js.gresource.xml
index c6b9210c..c3a29d94 100644
--- a/js.gresource.xml
+++ b/js.gresource.xml
@@ -2,6 +2,16 @@
<!-- SPDX-License-Identifier: MIT OR LGPL-2.0-or-later -->
<gresources>
<gresource prefix="/org/gnome/gjs">
+ <!-- Internal scripts -->
+ <file>lib/bootstrap/module.js</file>
+ <file>lib/modules/esm.js</file>
+ <file>lib/modules/gi.js</file>
+ <file>lib/entry.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/lib/.eslintrc.yml b/lib/.eslintrc.yml
new file mode 100644
index 00000000..3ed3d6ef
--- /dev/null
+++ b/lib/.eslintrc.yml
@@ -0,0 +1,26 @@
+---
+extends: ../.eslintrc.yml
+parserOptions:
+ sourceType: 'module'
+ ecmaVersion: 2020
+globals:
+ ARGV: off
+ Debugger: readonly
+ GIRepositoryGType: off
+ imports: off
+ Intl: readonly
+ log: off
+ logError: off
+ print: off
+ printerr: off
+ GLib: readonly
+ Gio: readonly
+ ByteUtils: readonly
+ moduleGlobalThis: readonly
+ compileModule: readonly
+ compileInternalModule: readonly
+ setModuleResolveHook: readonly
+ setModuleMetaHook: readonly
+ setModuleLoadHook: readonly
+ setModulePrivate: readonly
+ getRegistry: readonly
\ No newline at end of file
diff --git a/lib/bootstrap/module.js b/lib/bootstrap/module.js
new file mode 100644
index 00000000..0c5fda23
--- /dev/null
+++ b/lib/bootstrap/module.js
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020 Evan Welsh <contact evanwelsh com>
+
+// NOTE: Gio, GLib, and GObject have no overrides.
+
+/** @typedef {{ uri: string; scheme: string; host: string; path: string; query: Query }} Uri */
+
+/** @typedef {{ load(uri: Uri): [string, boolean]; }} SchemeHandler */
+/** @typedef {{ [key: string]: string | undefined; }} Query */
+
+/**
+ * Thrown when there is an error importing a module.
+ */
+export class ImportError extends Error {
+ /**
+ * @param {string | undefined} message the import error message
+ */
+ constructor(message) {
+ super(message);
+
+ this.name = 'ImportError';
+ }
+}
+
+/**
+ * ESModule is the "private" object of every module.
+ */
+export class ESModule {
+ /**
+ *
+ * @param {string} id the module's identifier
+ * @param {string} uri the module's URI
+ * @param {boolean} [internal] whether this module is "internal"
+ */
+ constructor(id, uri, internal = false) {
+ this.id = id;
+ this.uri = uri;
+ this.internal = internal;
+ }
+}
+
+/**
+ * Returns whether a string represents a relative path (e.g. ./, ../)
+ *
+ * @param {string} path a path to check if relative
+ * @returns {boolean}
+ */
+function isRelativePath(path) {
+ // Check if the path is relative.
+ return path.startsWith('./') || path.startsWith('../');
+}
+
+/**
+ * Encodes a Uint8Array into a UTF-8 string
+ *
+ * @param {Uint8Array} bytes the bytes to convert
+ * @returns {string}
+ */
+function fromBytes(bytes) {
+ return ByteUtils.toString(bytes, 'utf-8');
+}
+
+/**
+ * @param {import("gio").File} file the Gio.File to load from.
+ * @returns {string}
+ */
+function loadFileSync(file) {
+ try {
+ const [, bytes] = file.load_contents(null);
+
+ return fromBytes(bytes);
+ } catch (error) {
+ throw new ImportError(`Unable to load file from: ${file.get_uri()}`);
+ }
+}
+
+/**
+ * Synchronously loads a file's text from a URI.
+ *
+ * @param {string} uri the URI to load
+ * @returns {string}
+ */
+export function loadResourceOrFile(uri) {
+ let output = Gio.File.new_for_uri(uri);
+
+ return loadFileSync(output);
+}
+
+/**
+ * Resolves a relative path against a URI.
+ *
+ * @param {string} uri the base URI
+ * @param {string} relativePath the relative path to resolve against the base URI
+ * @returns {Uri}
+ */
+function resolveRelativeResourceOrFile(uri, relativePath) {
+ let module_file = Gio.File.new_for_uri(uri);
+ let module_parent_file = module_file.get_parent();
+
+ if (module_parent_file) {
+ let output = module_parent_file.resolve_relative_path(relativePath);
+
+ return parseURI(output.get_uri());
+ }
+
+ throw new ImportError('File does not have a valid parent!');
+}
+
+
+/**
+ * Parses a string into a Uri object
+ *
+ * @param {string} uri the URI to parse as a string
+ * @returns {Uri}
+ */
+export function parseURI(uri) {
+ try {
+ const parsed = GLib.Uri.parse(uri, GLib.UriFlags.NONE);
+
+ const raw_query = parsed.get_query();
+ const query = raw_query ? GLib.Uri.parse_params(raw_query, -1, '&', GLib.UriParamsFlags.NONE) : {};
+
+ return {
+ uri,
+ scheme: parsed.get_scheme(),
+ host: parsed.get_host(),
+ path: parsed.get_path(),
+ query,
+
+ };
+ } catch (error) {
+ throw new ImportError(`Attempted to import invalid URI: ${uri}`);
+ }
+}
+
+/**
+ * Handles resolving and loading URIs.
+ *
+ * @class
+ */
+export class ModuleLoader {
+ /**
+ * @param {typeof globalThis} global the global object to handle module resolution
+ */
+ constructor(global) {
+ this.global = global;
+ }
+
+ /**
+ * Loads a file or resource URI synchronously
+ *
+ * @param {Uri} uri the file or resource URI to load
+ * @returns {[string] | [string, boolean] | null}
+ */
+ loadURI(uri) {
+ if (uri.scheme === 'file' || uri.scheme === 'resource')
+ return [loadResourceOrFile(uri.uri)];
+
+
+ return null;
+ }
+
+ /**
+ * Resolves an import specifier given an optional parent importer.
+ *
+ * @param {string} specifier the import specifier
+ * @param {string | null} [parentURI] the URI of the module importing the specifier
+ * @returns {Uri | null}
+ */
+ resolveSpecifier(specifier, parentURI = null) {
+ try {
+ const uri = parseURI(specifier);
+
+ if (uri)
+ return uri;
+ } catch (err) {
+
+ }
+
+ if (isRelativePath(specifier)) {
+ if (!parentURI)
+ throw new ImportError('Cannot import from relative path when module path is unknown.');
+
+
+ return this.resolveRelativePath(specifier, parentURI);
+ }
+
+
+ return null;
+ }
+
+ /**
+ * Resolves a path relative to a URI, throwing an ImportError if
+ * the parentURI isn't valid.
+ *
+ * @param {string} relativePath the relative path to resolve against the base URI
+ * @param {string} parentURI the parent URI
+ * @returns {Uri}
+ */
+ resolveRelativePath(relativePath, parentURI) {
+ // Ensure the parent URI is valid.
+ parseURI(parentURI);
+
+ // Handle relative imports from URI-based modules.
+ return resolveRelativeResourceOrFile(parentURI, relativePath);
+ }
+
+ /**
+ * Compiles a module source text with the module's URI
+ *
+ * @param {ESModule} module a module private object
+ * @param {string} text the module source text to compile
+ * @returns {import("../types").Module}
+ */
+ compileModule(module, text) {
+ const compiled = compileInternalModule(module.uri, text);
+
+ setModulePrivate(compiled, module);
+
+ return compiled;
+ }
+
+ /**
+ * @param {string} specifier the specifier (e.g. relative path, root package) to resolve
+ * @param {string | null} parentURI the URI of the module triggering this resolve
+ *
+ * @returns {import("../types").Module | null}
+ */
+ resolveModule(specifier, parentURI) {
+ const registry = getRegistry(this.global);
+
+
+ // Check if the module has already been loaded
+
+ let module = registry.get(specifier);
+
+ if (module)
+ return module;
+
+
+ // 1) Resolve path and URI-based imports.
+
+ const uri = this.resolveSpecifier(specifier, parentURI);
+
+ if (uri) {
+ module = registry.get(uri.uri);
+ //
+ // Check if module is already loaded (relative handling)
+ if (module)
+ return module;
+
+
+ const result = this.loadURI(uri);
+
+ if (!result)
+ return null;
+
+
+ const [text, internal = false] = result;
+
+ const esmodule = new ESModule(uri.uri, uri.uri, internal);
+
+ const compiled = this.compileModule(esmodule, text);
+
+ if (!compiled)
+ throw new ImportError(`Failed to register module: ${uri}`);
+
+
+ registry.set(uri.uri, compiled);
+ return compiled;
+ }
+
+ return null;
+ }
+}
+
+export const internalModuleLoader = new ModuleLoader(globalThis);
+
+setModuleResolveHook(globalThis, (module, specifier) => {
+ const resolved = internalModuleLoader.resolveModule(specifier, module?.uri ?? null);
+
+ if (!resolved)
+ throw new ImportError(`Module not found: ${specifier}`);
+
+
+ return resolved;
+});
+
+setModuleMetaHook(globalThis, (module, meta) => {
+ meta.url = module.uri;
+});
+
+setModuleLoadHook(globalThis, (id, uri) => {
+ const m = new ESModule(id, uri);
+
+ const result = internalModuleLoader.loadURI(parseURI(uri));
+
+ if (!result)
+ throw new ImportError(`URI not found: ${uri}`);
+
+
+ const [text] = result;
+ const compiled = internalModuleLoader.compileModule(m, text);
+
+ const registry = getRegistry(globalThis);
+
+ registry.set(uri, compiled);
+
+ return compiled;
+});
diff --git a/lib/entry.js b/lib/entry.js
new file mode 100644
index 00000000..64ee7e06
--- /dev/null
+++ b/lib/entry.js
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020 Evan Welsh <contact evanwelsh com>
+
+// This file is called *after* ./bootstrap sets up internal module resolution.
+
+// Setup ES modules.
+import './modules/esm.js';
diff --git a/lib/jsconfig.json b/lib/jsconfig.json
new file mode 100644
index 00000000..bde26124
--- /dev/null
+++ b/lib/jsconfig.json
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020 Evan Welsh <contact evanwelsh com>
+
+{
+ "compilerOptions": {
+ "target": "es2019",
+ "module": "es2020",
+ "moduleResolution": "node",
+ "checkJs": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "strict": true,
+ "paths": {
+ "*": [
+ "node_modules/@gi-types/*",
+ "*"
+ ]
+ },
+ "baseUrl": "../"
+ },
+ "include": [
+ "entry.js",
+ "types.d.ts",
+ "bootstrap/lib.js",
+ "modules/esm.js",
+ "modules/gi.js"
+ ]
+}
\ No newline at end of file
diff --git a/lib/modules/esm.js b/lib/modules/esm.js
new file mode 100644
index 00000000..b2e4e3f6
--- /dev/null
+++ b/lib/modules/esm.js
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020 Evan Welsh <contact evanwelsh com>
+
+import {ESModule, ImportError, loadResourceOrFile, ModuleLoader, parseURI} from '../bootstrap/module.js';
+
+import {generateModule} from './gi.js';
+
+export class ESModuleLoader extends ModuleLoader {
+ /**
+ * @param {typeof globalThis} global the global object to register modules with.
+ */
+ constructor(global) {
+ super(global);
+
+ /**
+ * @type {Set<string>}
+ *
+ * The set of "module" URIs (the module search path)
+ */
+ this.moduleURIs = new Set();
+
+ /**
+ * @type {Map<string, import("../bootstrap/module.js").SchemeHandler>}
+ *
+ * A map of handlers for URI schemes (e.g. gi://)
+ */
+ this.schemeHandlers = new Map();
+ }
+
+ /**
+ * @param {ESModule} module a module private object
+ * @param {string} text the module source text
+ */
+ compileModule(module, text) {
+ const compiled = compileModule(module.uri, text);
+
+ setModulePrivate(compiled, module);
+
+ return compiled;
+ }
+
+ /**
+ * @param {string} specifier the package specifier
+ * @returns {string[]} the possible internal URIs
+ */
+ buildInternalURIs(specifier) {
+ const {moduleURIs} = this;
+
+ const builtURIs = [];
+
+ for (const uri of moduleURIs) {
+ const builtURI = `${uri}/${specifier}.js`;
+
+ builtURIs.push(builtURI);
+ }
+
+ return builtURIs;
+ }
+
+ /**
+ * @param {string} scheme the URI scheme to register
+ * @param {import("../bootstrap/module.js").SchemeHandler} handler a handler
+ */
+ registerScheme(scheme, handler) {
+ this.schemeHandlers.set(scheme, handler);
+ }
+
+ /**
+ * @param {import("../bootstrap/module.js").Uri} uri a Uri object to load
+ */
+ loadURI(uri) {
+ const {schemeHandlers} = this;
+
+ if (uri.scheme) {
+ const loader = schemeHandlers.get(uri.scheme);
+
+ if (loader)
+ return loader.load(uri);
+ }
+
+ const result = super.loadURI(uri);
+
+ if (result)
+ return result;
+
+ throw new ImportError(`Unable to load module from invalid URI: ${uri.uri}`);
+ }
+
+ /**
+ * Registers an internal resource URI as a bare-specifier root.
+ *
+ * For example, registering "resource:///org/gnome/gjs/modules/esm/" allows
+ * import "system" if "resource:///org/gnome/gjs/modules/esm/system.js"
+ * exists.
+ *
+ * @param {string} uri the URI to register.
+ */
+ registerModuleURI(uri) {
+ const {moduleURIs} = this;
+
+ moduleURIs.add(uri);
+ }
+
+ /**
+ * Resolves a module import with optional handling for relative imports.
+ *
+ * @param {string} specifier the module specifier to resolve for an import
+ * @param {string | null} moduleURI the importing module's URI or null if importing from the entry point
+ * @returns {import("../types").Module}
+ */
+ resolveModule(specifier, moduleURI) {
+ const module = super.resolveModule(specifier, moduleURI);
+
+ if (module)
+ return module;
+
+
+ // 2) Resolve internal imports.
+
+ const uri = this.buildInternalURIs(specifier).find(u => {
+ let file = Gio.File.new_for_uri(u);
+
+ return file && file.query_exists(null);
+ });
+
+ if (!uri)
+ throw new ImportError(`Attempted to load unregistered global module: ${specifier}`);
+
+ const parsed = parseURI(uri);
+
+ if (parsed.scheme !== 'file' && parsed.scheme !== 'resource')
+ throw new ImportError('Only file:// and resource:// URIs are currently supported.');
+
+
+ const text = loadResourceOrFile(parsed.uri);
+
+ const priv = new ESModule(specifier, uri, true);
+
+ const compiled = this.compileModule(priv, text);
+
+ if (!compiled)
+ throw new ImportError(`Failed to register module: ${uri}`);
+
+ const registry = getRegistry(this.global);
+
+ if (!registry.has(specifier))
+ registry.set(specifier, compiled);
+
+ return compiled;
+ }
+}
+
+export const moduleLoader = new ESModuleLoader(moduleGlobalThis);
+
+// Always let ESM-specific modules take priority over core modules.
+moduleLoader.registerModuleURI('resource:///org/gnome/gjs/modules/esm/');
+moduleLoader.registerModuleURI('resource:///org/gnome/gjs/modules/core/');
+
+const giVersionMap = new Map();
+
+giVersionMap.set('GLib', '2.0');
+giVersionMap.set('Gio', '2.0');
+giVersionMap.set('GObject', '2.0');
+
+/**
+ * @param {string} lib the GI namespace to get the version for.
+ */
+function getGIVersionMap(lib) {
+ return giVersionMap.get(lib);
+}
+
+moduleLoader.registerScheme('gi', {
+ /**
+ * @param {import("../bootstrap/module.js").Uri} uri the URI to load
+ */
+ load(uri) {
+ const version = uri.query.version ?? getGIVersionMap(uri.host);
+
+ if (!version)
+ throw new ImportError(`No version specified for ${uri.host}.`);
+
+ giVersionMap.set(uri.host, version);
+
+ return [generateModule(uri.host, version), true];
+ },
+});
+
+
+/**
+ * @param {ESModule} module
+ * @param {ImportMeta} meta
+ */
+setModuleMetaHook(moduleGlobalThis, (module, meta) => {
+ meta.url = module.uri;
+
+ if (module.internal)
+ meta.importSync = globalThis.importSync;
+});
+
+/**
+ * @param {string} id
+ * @param {string} uri
+ */
+setModuleLoadHook(moduleGlobalThis, (id, uri) => {
+ const priv = new ESModule(id, uri);
+
+ const [text] = moduleLoader.loadURI(parseURI(uri));
+ const compiled = moduleLoader.compileModule(priv, text);
+
+ const registry = getRegistry(moduleGlobalThis);
+
+ registry.set(uri, compiled);
+
+ return compiled;
+});
+
+setModuleResolveHook(moduleGlobalThis, (module, specifier) => {
+ return moduleLoader.resolveModule(specifier, module.uri);
+});
+
diff --git a/lib/modules/gi.js b/lib/modules/gi.js
new file mode 100644
index 00000000..fb0076dc
--- /dev/null
+++ b/lib/modules/gi.js
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020 Evan Welsh <contact evanwelsh com>
+
+/**
+ * Creates a module source text to expose a GI namespace via a default export.
+ *
+ * @param {string} namespace the GI namespace to import
+ * @param {string} version the version string of the namespace
+ *
+ * @returns {string} the generated module source text
+ */
+export function generateModule(namespace, version) {
+ const source = `
+ import $$gi from 'gi';
+
+ const $$ns = $$gi.require('${namespace}', '${version}');
+
+ export default $$ns;
+ `;
+
+ return source;
+}
diff --git a/lib/types.d.ts b/lib/types.d.ts
new file mode 100644
index 00000000..935cddc5
--- /dev/null
+++ b/lib/types.d.ts
@@ -0,0 +1,111 @@
+import * as glib from "glib";
+import * as gio from "gio";
+
+import { ESModule } from "./bootstrap/module";
+
+interface Module {
+ // This prevents any object's type from structurally matching Module
+ __internal: never;
+}
+
+export type { Module };
+
+declare global {
+ export interface ImportMeta {
+ /**
+ *
+ */
+ url?: string;
+
+ /**
+ *
+ */
+ importSync?: <T = any>(id: string) => T;
+ }
+
+ /**
+ *
+ * @param id
+ */
+ export function importSync<T = any>(id: string): T
+
+ /**
+ *
+ * @param msg
+ */
+ export function debug(msg: string): void;
+
+ /**
+ *
+ * @param uri
+ * @param text
+ */
+ export function compileModule(uri: string, text: string): Module;
+
+ /**
+ *
+ * @param uri
+ * @param text
+ */
+ export function compileInternalModule(uri: string, text: string): Module;
+
+ /**
+ *
+ * @param module
+ * @param private
+ */
+ export function setModulePrivate(module: Module, private: ESModule);
+
+ /**
+ *
+ * @param global
+ * @param hook
+ */
+ export function setModuleLoadHook(global: typeof globalThis, hook: (id: string, uri: string) => Module);
+
+ /**
+ *
+ * @param global
+ * @param hook
+ */
+ export function setModuleResolveHook(global: typeof globalThis, hook: (module: ESModule, specifier:
string) => Module): void;
+
+ /**
+ *
+ * @param global
+ * @param hook
+ */
+ export function setModuleMetaHook(global: typeof globalThis, hook: (module: ESModule, meta: ImportMeta)
=> void): void;
+
+ /**
+ *
+ * @param args
+ */
+ export function registerModule(...args: any[]): any;
+
+ /**
+ *
+ * @param global
+ */
+ export function getRegistry(global: typeof globalThis): Map<string, Module>;
+
+ /**
+ *
+ */
+ export const moduleGlobalThis: typeof globalThis;
+
+ /**
+ *
+ */
+ export const GLib: typeof glib
+
+ /**
+ *
+ */
+ export const Gio: typeof gio;
+
+ /**
+ *
+ */
+ export const ByteUtils: any;
+}
diff --git a/meson.build b/meson.build
index 2f0d0f00..8e6181a3 100644
--- a/meson.build
+++ b/meson.build
@@ -399,6 +399,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..0b3bc0a2
--- /dev/null
+++ b/modules/esm/.eslintrc.yml
@@ -0,0 +1,5 @@
+---
+extends: '../../.eslintrc.yml'
+parserOptions:
+ sourceType: 'module'
+ ecmaVersion: 2020
diff --git a/modules/esm/gi.js b/modules/esm/gi.js
new file mode 100644
index 00000000..f9cbc374
--- /dev/null
+++ b/modules/esm/gi.js
@@ -0,0 +1,22 @@
+const gi = import.meta.importSync('gi');
+
+const Gi = {
+ require(name, version = null) {
+ if (version !== null)
+ gi.versions[name] = version;
+
+ if (name === 'versions')
+ throw new Error('Cannot import namespace "versions", use the version parameter of Gi.require to
specify versions.');
+
+
+ return gi[name];
+ },
+};
+Object.freeze(Gi);
+
+Gi.require('GjsPrivate');
+Gi.require('GLib');
+Gi.require('GObject');
+Gi.require('Gio');
+
+export default Gi;
diff --git a/modules/esm/system.js b/modules/esm/system.js
new file mode 100644
index 00000000..cecaa4d7
--- /dev/null
+++ b/modules/esm/system.js
@@ -0,0 +1,17 @@
+const system = import.meta.importSync('system');
+
+export default system;
+
+export let addressOf = system.addressOf;
+
+export let refcount = system.refcount;
+
+export let breakpoint = system.breakpoint;
+
+export let gc = system.gc;
+
+export let exit = system.exit;
+
+export let version = system.version;
+
+export let programInvocationName = system.programInvocationName;
diff --git a/tools/package.json b/tools/package.json
index 5920f17b..812eacda 100644
--- a/tools/package.json
+++ b/tools/package.json
@@ -6,10 +6,17 @@
"private": true,
"scripts": {
"lint": "cd .. && eslint . --format unix --resolve-plugins-relative-to tools",
- "fix": "yarn lint --fix"
+ "fix": "yarn lint --fix",
+ "typecheck": "yarn run typecheck:lib",
+ "typecheck:lib": "cd .. && tsc -p lib/jsconfig.json"
},
"devDependencies": {
+ "@gi-types/gio": "^2.66.3",
+ "@gi-types/glib": "^2.66.3",
+ "@gi-types/gobject": "^2.66.3",
+ "@gi-types/gtk": "^3.24.3",
"eslint": "^7.12.1",
- "eslint-plugin-jsdoc": "^30.7.3"
+ "eslint-plugin-jsdoc": "^30.7.3",
+ "typescript": "^4.0.5"
}
}
diff --git a/tools/yarn.lock b/tools/yarn.lock
index 7a1e6daa..5e256b2d 100644
--- a/tools/yarn.lock
+++ b/tools/yarn.lock
@@ -39,6 +39,74 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
+"@gi-types/atk@^2.36.3":
+ version "2.36.3"
+ resolved
"https://registry.yarnpkg.com/@gi-types/atk/-/atk-2.36.3.tgz#5853b6eb48d92a2cafcfa92c5c1d1ade4d63f559"
+ integrity sha512-bUU8nGdMwZlnidgHsH4Rk4VfSX+ZXNbJJw27E98ujZG1dTM/qeIam5nWq4z74K9YvFK6E1g0b5c76LTbDpxLCg==
+ dependencies:
+ "@gi-types/gobject" "^2.66.3"
+
+"@gi-types/cairo@^1.0.2":
+ version "1.0.2"
+ resolved
"https://registry.yarnpkg.com/@gi-types/cairo/-/cairo-1.0.2.tgz#abc51eb22331d6d20f01cea7e86a8538befdac70"
+ integrity sha512-b6jOZtt7XqeHlMPk+6MjL76f6rhPjt0oFlsZG7nOoP3OILRD9146dCvOmQTybdmLtycvgW2P73O7GAFFtf8zIA==
+
+"@gi-types/gdk@^3.24.2":
+ version "3.24.2"
+ resolved
"https://registry.yarnpkg.com/@gi-types/gdk/-/gdk-3.24.2.tgz#06708399d9c66babc88299aa21bbd51b5499b04c"
+ integrity sha512-0fgRJFJXRAUU7/ePgbznqPSrc33vOIhWMFNLhlanNS7G2qWyjsm8GLyXD+h8iWAtCg4JxznBJKoDPyfAul3e+Q==
+ dependencies:
+ "@gi-types/cairo" "^1.0.2"
+ "@gi-types/gdkpixbuf" "^2.0.2"
+ "@gi-types/gio" "^2.66.2"
+ "@gi-types/gobject" "^2.66.2"
+ "@gi-types/pango" "^1.0.2"
+
+"@gi-types/gdkpixbuf@^2.0.2":
+ version "2.0.3"
+ resolved
"https://registry.yarnpkg.com/@gi-types/gdkpixbuf/-/gdkpixbuf-2.0.3.tgz#a2a19b8d8604a5d5706dee43b051e129543480be"
+ integrity sha512-ATfMjEtmNHKNgjaBuIjpzh5RgTO1zk3D6ltwqHbPg5qQxWREe7kCSiI7eRsikP285u4e0tSGb6UJDamYZxUU2Q==
+ dependencies:
+ "@gi-types/gio" "^2.66.3"
+ "@gi-types/gobject" "^2.66.3"
+
+"@gi-types/gio@^2.66.2", "@gi-types/gio@^2.66.3":
+ version "2.66.3"
+ resolved
"https://registry.yarnpkg.com/@gi-types/gio/-/gio-2.66.3.tgz#a9a071be3617d929778ee4036270031b341be2d8"
+ integrity sha512-ROjWtIqzWAODgEKIK0umz2i2kZoKiWks2beV111u+XOo/CdVpIQn3F8VJAAPhXoTD+hyhrBDt9NcnOQZGHV3ug==
+ dependencies:
+ "@gi-types/gobject" "^2.66.3"
+
+"@gi-types/glib@^2.66.2", "@gi-types/glib@^2.66.3":
+ version "2.66.3"
+ resolved
"https://registry.yarnpkg.com/@gi-types/glib/-/glib-2.66.3.tgz#795e6566f25de5dddaab72fa30c16136334c80c7"
+ integrity sha512-EoT/w9ImW5QyMicZ/I9yh3CzgZWcTPrm96siyC5qApNzblLsNixt3MzPQvnFoKqdLNCHrbAcgXTHZKzyqVztww==
+
+"@gi-types/gobject@^2.66.2", "@gi-types/gobject@^2.66.3":
+ version "2.66.3"
+ resolved
"https://registry.yarnpkg.com/@gi-types/gobject/-/gobject-2.66.3.tgz#41bc8b141a5bb78bcbce6252646d7ef71fa8b275"
+ integrity sha512-VP756l82z8jVPNWowpsdbJORYGx1HyZMxZzL10+FGTdgKiTzx+cMuMD066h4g2PYTTuOGy6vr+xbUcnhLonYhQ==
+ dependencies:
+ "@gi-types/glib" "^2.66.2"
+
+"@gi-types/gtk@^3.24.3":
+ version "3.24.3"
+ resolved
"https://registry.yarnpkg.com/@gi-types/gtk/-/gtk-3.24.3.tgz#a5d5c12edf27313c54f22d8def69625e933eb0c7"
+ integrity sha512-nCyRGXsd8SJ94iqfrA8rCWBKC4x07YeUIBWeDCqJzU+9Gfacfl/XTxptoEFXRFnksrHRbj5DmKllix9N25U4KA==
+ dependencies:
+ "@gi-types/atk" "^2.36.3"
+ "@gi-types/gdk" "^3.24.2"
+ "@gi-types/gio" "^2.66.3"
+ "@gi-types/gobject" "^2.66.3"
+
+"@gi-types/pango@^1.0.2":
+ version "1.0.3"
+ resolved
"https://registry.yarnpkg.com/@gi-types/pango/-/pango-1.0.3.tgz#4fba9b6c110180674ec42e3581fd061d12ef7f62"
+ integrity sha512-tQ7c5yf2cyla3ta5ctEOtI/z5nX4NJteYCV3XoNkC8EMRvcyiDqYi6tGUG3ErnUG4EjczgVBh902gZ2K5lQwvg==
+ dependencies:
+ "@gi-types/cairo" "^1.0.2"
+ "@gi-types/gobject" "^2.66.2"
+
acorn-jsx@^5.2.0:
version "5.3.1"
resolved
"https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
@@ -734,6 +802,11 @@ type-fest@^0.8.1:
resolved
"https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
+typescript@^4.0.5:
+ version "4.0.5"
+ resolved
"https://registry.yarnpkg.com/typescript/-/typescript-4.0.5.tgz#ae9dddfd1069f1cb5beb3ef3b2170dd7c1332389"
+ integrity sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==
+
uri-js@^4.2.2:
version "4.4.0"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602"
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]