diff --git a/binaries/data/mods/_test.scriptinterface/module/meta.js b/binaries/data/mods/_test.scriptinterface/module/meta.js new file mode 100644 index 0000000000..567fe9a2fc --- /dev/null +++ b/binaries/data/mods/_test.scriptinterface/module/meta.js @@ -0,0 +1,4 @@ +export function getMeta() +{ + return import.meta; +} diff --git a/source/scriptinterface/ModuleLoader.cpp b/source/scriptinterface/ModuleLoader.cpp index 65f4653038..194fde9b60 100644 --- a/source/scriptinterface/ModuleLoader.cpp +++ b/source/scriptinterface/ModuleLoader.cpp @@ -160,6 +160,10 @@ ModuleLoader::CompiledModule::CompiledModule(const ScriptRequest& rq, const VfsP throw std::invalid_argument{fmt::format("Unable to compile module: \"{}\".", filePathStr)}; } + + JS::RootedValue modInfo{rq.cx}; + Script::CreateObject(rq, &modInfo, "path", filePathStr); + JS::SetModulePrivate(m_ModuleObject, modInfo); } ModuleLoader::Future::Future(const ScriptRequest& rq, ModuleLoader& loader, const VfsPath& modulePath): @@ -236,6 +240,26 @@ void ModuleLoader::Future::SetReservedSlot(JS::Value privateValue) noexcept return Future{rq, *this, modulePath}; } +/** + * This is only executed once per module. Following accesses of `import.meta` + * evaluate to the same object. + */ +[[nodiscard]] bool ModuleLoader::MetadataHook(JSContext* cx, JS::HandleValue privateValue, + JS::HandleObject metaObject) noexcept +{ + const ScriptRequest rq{cx}; + + JS::RootedValue path{cx}; + if (!Script::GetProperty(rq, privateValue, "path", &path)) + return false; + + JS::RootedValue metaValue{cx, JS::ObjectValue(*metaObject)}; + if (!Script::SetProperty(rq, metaValue, "path", path)) + return false; + + return true; +} + [[nodiscard]] JSObject* ModuleLoader::ResolveHook(JSContext* cx, JS::HandleValue, JS::HandleObject moduleRequest) noexcept { diff --git a/source/scriptinterface/ModuleLoader.h b/source/scriptinterface/ModuleLoader.h index 6ffd3a946e..8a56ac7cf2 100644 --- a/source/scriptinterface/ModuleLoader.h +++ b/source/scriptinterface/ModuleLoader.h @@ -100,6 +100,8 @@ public: private: // Functions used by the `ScriptContext`. + [[nodiscard]] static bool MetadataHook(JSContext* cx, JS::HandleValue privateValue, + JS::HandleObject metaObject) noexcept; [[nodiscard]] static JSObject* ResolveHook(JSContext* cx, JS::HandleValue, JS::HandleObject moduleRequest) noexcept; [[nodiscard]] static bool DynamicImportHook(JSContext* cx, JS::HandleValue referencingPrivate, diff --git a/source/scriptinterface/ScriptContext.cpp b/source/scriptinterface/ScriptContext.cpp index 1ff3304c05..7b5a157ce4 100644 --- a/source/scriptinterface/ScriptContext.cpp +++ b/source/scriptinterface/ScriptContext.cpp @@ -155,6 +155,7 @@ ScriptContext::ScriptContext(int contextSize, uint32_t heapGrowthBytesGCTrigger) JS::SetPromiseRejectionTrackerCallback(m_cx, &Script::UnhandledRejectedPromise); JSRuntime* runtime{JS_GetRuntime(m_cx)}; + JS::SetModuleMetadataHook(runtime, &Script::ModuleLoader::MetadataHook); JS::SetModuleResolveHook(runtime, &Script::ModuleLoader::ResolveHook); JS::SetModuleDynamicImportHook(runtime, &Script::ModuleLoader::DynamicImportHook); } @@ -166,6 +167,7 @@ ScriptContext::~ScriptContext() JSRuntime* runtime{JS_GetRuntime(m_cx)}; JS::SetModuleDynamicImportHook(runtime, nullptr); JS::SetModuleResolveHook(runtime, nullptr); + JS::SetModuleMetadataHook(runtime, nullptr); // Switch back to normal performance mode to avoid assertion in debug mode. js::gc::SetPerformanceHint(m_cx, js::gc::PerformanceHint::Normal); diff --git a/source/scriptinterface/tests/test_Module.h b/source/scriptinterface/tests/test_Module.h index a0241972cf..936b01cc78 100644 --- a/source/scriptinterface/tests/test_Module.h +++ b/source/scriptinterface/tests/test_Module.h @@ -401,4 +401,24 @@ public: TS_ASSERT_LESS_THAN(pi, 3.1416); TS_ASSERT_LESS_THAN(3.1415, pi); } + + void test_Meta() + { + ScriptInterface script{"Test", "Test", g_ScriptContext}; + const ScriptRequest rq{script}; + + auto future = script.GetModuleLoader().LoadModule(rq, "meta.js"); + + g_ScriptContext->RunJobs(); + + JS::RootedObject ns{rq.cx, future.Get()}; + const JS::RootedValue modNamespace{rq.cx, JS::ObjectValue(*ns)}; + + JS::RootedValue meta{rq.cx}; + TS_ASSERT(ScriptFunction::Call(rq, modNamespace, "getMeta", &meta)); + + std::string path; + TS_ASSERT(Script::GetProperty(rq, meta, "path", path)); + TS_ASSERT_STR_EQUALS(path, "meta.js"); + } };