Support dynamic import

The static `import` should be prefered over the dynamic but it might be
usefull when loading an object from a json file.
This commit is contained in:
phosit
2025-01-15 22:08:06 +01:00
committed by phosit
parent 252df0a1db
commit a40caaffc4
5 changed files with 82 additions and 9 deletions
+38 -7
View File
@@ -65,6 +65,18 @@ namespace
return std::get<1>(*std::get<0>(insertResult)).m_ModuleObject;
}
[[nodiscard]] JSObject* Resolve(const ScriptRequest& rq,
ModuleLoader::RegistryType& registry, JS::HandleObject moduleRequest)
{
std::string includeString;
const JS::RootedValue pathValue{rq.cx,
JS::StringValue(JS::GetModuleRequestSpecifier(rq.cx, moduleRequest))};
if (!Script::FromJSVal(rq, pathValue, includeString))
throw std::logic_error{"The module-name to import isn't a string."};
return CompileModule(rq, registry, includeString);
}
[[nodiscard]] JSObject* Evaluate(const ScriptRequest& rq, JS::HandleObject mod)
{
if (!JS::ModuleLink(rq.cx, mod))
@@ -230,13 +242,7 @@ void ModuleLoader::Future::SetReservedSlot(JS::Value privateValue) noexcept
try
{
const ScriptRequest rq{cx};
std::string includeString;
const JS::RootedValue pathValue{rq.cx,
JS::StringValue(JS::GetModuleRequestSpecifier(rq.cx, moduleRequest))};
if (!Script::FromJSVal(rq, pathValue, includeString))
throw std::logic_error{"The module-name to import isn't a string."};
return CompileModule(rq, rq.GetScriptInterface().GetModuleLoader().m_Registry, includeString);
return Resolve(rq, rq.GetScriptInterface().GetModuleLoader().m_Registry, moduleRequest);
}
catch (const std::exception& e)
{
@@ -249,4 +255,29 @@ void ModuleLoader::Future::SetReservedSlot(JS::Value privateValue) noexcept
return nullptr;
}
}
[[nodiscard]] bool ModuleLoader::DynamicImportHook(JSContext* cx, JS::HandleValue referencingPrivate,
JS::HandleObject moduleRequest, JS::HandleObject promise) noexcept
{
const ScriptRequest rq{cx};
try
{
JS::RootedObject mod{rq.cx, Resolve(rq, rq.GetScriptInterface().GetModuleLoader().m_Registry,
moduleRequest)};
JS::RootedObject evaluationPromise{rq.cx, Evaluate(rq, mod)};
return JS::FinishDynamicModuleImport(rq.cx, evaluationPromise, referencingPrivate,
moduleRequest, promise);
}
catch (const std::exception& e)
{
LOGERROR("%s", e.what());
return JS::FinishDynamicModuleImport(rq.cx, nullptr, referencingPrivate, moduleRequest,
promise);
}
catch (...)
{
return JS::FinishDynamicModuleImport(rq.cx, nullptr, referencingPrivate, moduleRequest,
promise);
}
}
} // namespace Script
+2
View File
@@ -102,6 +102,8 @@ private:
// Functions used by the `ScriptContext`.
[[nodiscard]] static JSObject* ResolveHook(JSContext* cx, JS::HandleValue,
JS::HandleObject moduleRequest) noexcept;
[[nodiscard]] static bool DynamicImportHook(JSContext* cx, JS::HandleValue referencingPrivate,
JS::HandleObject moduleRequest, JS::HandleObject promise) noexcept;
RegistryType m_Registry;
};
+6 -2
View File
@@ -154,14 +154,18 @@ ScriptContext::ScriptContext(int contextSize, uint32_t heapGrowthBytesGCTrigger)
JS::SetJobQueue(m_cx, m_JobQueue.get());
JS::SetPromiseRejectionTrackerCallback(m_cx, &Script::UnhandledRejectedPromise);
JS::SetModuleResolveHook(JS_GetRuntime(m_cx), &Script::ModuleLoader::ResolveHook);
JSRuntime* runtime{JS_GetRuntime(m_cx)};
JS::SetModuleResolveHook(runtime, &Script::ModuleLoader::ResolveHook);
JS::SetModuleDynamicImportHook(runtime, &Script::ModuleLoader::DynamicImportHook);
}
ScriptContext::~ScriptContext()
{
ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be active (initialized and not yet shut down) when destroying a ScriptContext!");
JS::SetModuleResolveHook(JS_GetRuntime(m_cx), nullptr);
JSRuntime* runtime{JS_GetRuntime(m_cx)};
JS::SetModuleDynamicImportHook(runtime, nullptr);
JS::SetModuleResolveHook(runtime, nullptr);
// Switch back to normal performance mode to avoid assertion in debug mode.
js::gc::SetPerformanceHint(m_cx, js::gc::PerformanceHint::Normal);
@@ -22,6 +22,7 @@
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ModuleLoader.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/Promises.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptInterface.h"
@@ -369,4 +370,35 @@ public:
TS_ASSERT(Script::GetProperty(rq, moduleValue, "value", value));
TS_ASSERT_EQUALS(value, 6);
}
void test_DynamicImport()
{
ScriptInterface script{"Test", "Test", g_ScriptContext};
const ScriptRequest rq{script};
auto future = script.GetModuleLoader().LoadModule(rq, "dynamic_import.js");
g_ScriptContext->RunJobs();
JS::RootedObject ns{rq.cx, future.Get()};
JS::RootedValue moduleValue{rq.cx, JS::ObjectValue(*ns)};
JS::RootedValue promise{rq.cx};
TS_ASSERT(ScriptFunction::Call(rq, moduleValue, "default", &promise));
TS_ASSERT(promise.isObject());
JS::RootedObject promiseObject{rq.cx, &promise.toObject()};
TS_ASSERT(JS::IsPromiseObject(promiseObject));
TS_ASSERT_EQUALS(JS::GetPromiseState(promiseObject), JS::PromiseState::Pending);
g_ScriptContext->RunJobs();
TS_ASSERT_EQUALS(JS::GetPromiseState(promiseObject), JS::PromiseState::Fulfilled);
JS::RootedValue piModule{rq.cx, JS::GetPromiseResult(promiseObject)};
double pi{0.0};
TS_ASSERT(Script::FromJSProperty(rq, piModule, "default", pi));
TS_ASSERT_LESS_THAN(pi, 3.1416);
TS_ASSERT_LESS_THAN(3.1415, pi);
}
};