mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 13:36:13 +03:00
LibJS+LibWeb: More bringing module loading closer to spec
In particular, this patch removes three host hooks on JS::VM in favor of the new JS-side module loading stuff.
This commit is contained in:
parent
07f567cd9f
commit
8b7d27b349
Notes:
sideshowbarker
2024-07-16 23:57:20 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/8b7d27b349 Pull-request: https://github.com/SerenityOS/serenity/pull/21967 Issue: https://github.com/SerenityOS/serenity/issues/21737 Issue: https://github.com/SerenityOS/serenity/issues/21899
@ -305,7 +305,7 @@ extern "C" int initialize_repl(char const* time_zone)
|
|||||||
setenv("TZ", time_zone, 1);
|
setenv("TZ", time_zone, 1);
|
||||||
|
|
||||||
g_vm = MUST(JS::VM::create());
|
g_vm = MUST(JS::VM::create());
|
||||||
g_vm->enable_default_host_import_module_dynamically_hook();
|
g_vm->set_dynamic_imports_allowed(true);
|
||||||
|
|
||||||
// NOTE: These will print out both warnings when using something like Promise.reject().catch(...) -
|
// NOTE: These will print out both warnings when using something like Promise.reject().catch(...) -
|
||||||
// which is, as far as I can tell, correct - a promise is created, rejected without handler, and a
|
// which is, as far as I can tell, correct - a promise is created, rejected without handler, and a
|
||||||
|
@ -214,7 +214,7 @@ static Result<void, TestError> run_test(StringView source, StringView filepath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto vm = MUST(JS::VM::create());
|
auto vm = MUST(JS::VM::create());
|
||||||
vm->enable_default_host_import_module_dynamically_hook();
|
vm->set_dynamic_imports_allowed(true);
|
||||||
|
|
||||||
JS::GCPtr<JS::Realm> realm;
|
JS::GCPtr<JS::Realm> realm;
|
||||||
JS::GCPtr<JS::Test262::GlobalObject> global_object;
|
JS::GCPtr<JS::Test262::GlobalObject> global_object;
|
||||||
|
@ -38,7 +38,7 @@ Workbook::Workbook(Vector<NonnullRefPtr<Sheet>>&& sheets, GUI::Window& parent_wi
|
|||||||
m_main_execution_context->realm = &realm;
|
m_main_execution_context->realm = &realm;
|
||||||
m_main_execution_context->is_strict_mode = true;
|
m_main_execution_context->is_strict_mode = true;
|
||||||
m_vm->push_execution_context(*m_main_execution_context);
|
m_vm->push_execution_context(*m_main_execution_context);
|
||||||
m_vm->enable_default_host_import_module_dynamically_hook();
|
m_vm->set_dynamic_imports_allowed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Workbook::set_filename(DeprecatedString const& filename)
|
bool Workbook::set_filename(DeprecatedString const& filename)
|
||||||
|
@ -45,13 +45,13 @@ void GraphLoadingState::visit_edges(Cell::Visitor& visitor)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 16.2.1.5.1 LoadRequestedModules ( [ hostDefined ] ), https://tc39.es/ecma262/#sec-LoadRequestedModules
|
// 16.2.1.5.1 LoadRequestedModules ( [ hostDefined ] ), https://tc39.es/ecma262/#sec-LoadRequestedModules
|
||||||
PromiseCapability& CyclicModule::load_requested_modules(JS::Realm& realm, GCPtr<GraphLoadingState::HostDefined> host_defined)
|
PromiseCapability& CyclicModule::load_requested_modules(GCPtr<GraphLoadingState::HostDefined> host_defined)
|
||||||
{
|
{
|
||||||
// 1. If hostDefined is not present, let hostDefined be EMPTY.
|
// 1. If hostDefined is not present, let hostDefined be EMPTY.
|
||||||
// NOTE: The empty state is handled by hostDefined being an optional without value.
|
// NOTE: The empty state is handled by hostDefined being an optional without value.
|
||||||
|
|
||||||
// 2. Let pc be ! NewPromiseCapability(%Promise%).
|
// 2. Let pc be ! NewPromiseCapability(%Promise%).
|
||||||
auto promise_capability = MUST(new_promise_capability(realm.vm(), realm.intrinsics().promise_constructor()));
|
auto promise_capability = MUST(new_promise_capability(vm(), vm().current_realm()->intrinsics().promise_constructor()));
|
||||||
|
|
||||||
// 3. Let state be the GraphLoadingState Record { [[IsLoading]]: true, [[PendingModulesCount]]: 1, [[Visited]]: « », [[PromiseCapability]]: pc, [[HostDefined]]: hostDefined }.
|
// 3. Let state be the GraphLoadingState Record { [[IsLoading]]: true, [[PendingModulesCount]]: 1, [[Visited]]: « », [[PromiseCapability]]: pc, [[HostDefined]]: hostDefined }.
|
||||||
auto state = heap().allocate_without_realm<GraphLoadingState>(promise_capability, true, 1, HashTable<CyclicModule*> {}, move(host_defined));
|
auto state = heap().allocate_without_realm<GraphLoadingState>(promise_capability, true, 1, HashTable<CyclicModule*> {}, move(host_defined));
|
||||||
@ -101,7 +101,7 @@ void CyclicModule::inner_module_loading(JS::GraphLoadingState& state)
|
|||||||
// ii. Else,
|
// ii. Else,
|
||||||
if (!found_record_in_loaded_modules) {
|
if (!found_record_in_loaded_modules) {
|
||||||
// 1. Perform HostLoadImportedModule(module, required, state.[[HostDefined]], state).
|
// 1. Perform HostLoadImportedModule(module, required, state.[[HostDefined]], state).
|
||||||
vm().host_load_imported_module(realm(), NonnullGCPtr<CyclicModule>(*this), required, state.host_defined, state);
|
vm().host_load_imported_module(NonnullGCPtr<CyclicModule> { *this }, required, state.host_defined, NonnullGCPtr<GraphLoadingState> { state });
|
||||||
|
|
||||||
// 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, which re-enters the graph loading process through ContinueModuleLoading.
|
// 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, which re-enters the graph loading process through ContinueModuleLoading.
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ void CyclicModule::inner_module_loading(JS::GraphLoadingState& state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 16.2.1.5.1.2 ContinueModuleLoading ( state, moduleCompletion ), https://tc39.es/ecma262/#sec-ContinueModuleLoading
|
// 16.2.1.5.1.2 ContinueModuleLoading ( state, moduleCompletion ), https://tc39.es/ecma262/#sec-ContinueModuleLoading
|
||||||
void continue_module_loading(Realm& realm, GraphLoadingState& state, ThrowCompletionOr<Module*> const& module_completion)
|
void continue_module_loading(GraphLoadingState& state, ThrowCompletionOr<NonnullGCPtr<Module>> const& module_completion)
|
||||||
{
|
{
|
||||||
// 1. If state.[[IsLoading]] is false, return UNUSED.
|
// 1. If state.[[IsLoading]] is false, return UNUSED.
|
||||||
if (state.is_loading)
|
if (state.is_loading)
|
||||||
@ -146,10 +146,10 @@ void continue_module_loading(Realm& realm, GraphLoadingState& state, ThrowComple
|
|||||||
|
|
||||||
// 2. If moduleCompletion is a normal completion, then
|
// 2. If moduleCompletion is a normal completion, then
|
||||||
if (!module_completion.is_error()) {
|
if (!module_completion.is_error()) {
|
||||||
auto* module = const_cast<Module*>(module_completion.value());
|
auto module = module_completion.value();
|
||||||
|
|
||||||
// a. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]).
|
// a. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]).
|
||||||
static_cast<CyclicModule*>(module)->inner_module_loading(state);
|
verify_cast<CyclicModule>(*module).inner_module_loading(state);
|
||||||
}
|
}
|
||||||
// 3. Else,
|
// 3. Else,
|
||||||
else {
|
else {
|
||||||
@ -159,7 +159,7 @@ void continue_module_loading(Realm& realm, GraphLoadingState& state, ThrowComple
|
|||||||
auto value = module_completion.throw_completion().value();
|
auto value = module_completion.throw_completion().value();
|
||||||
|
|
||||||
// b. Perform ! Call(state.[[PromiseCapability]].[[Reject]], undefined, « moduleCompletion.[[Value]] »).
|
// b. Perform ! Call(state.[[PromiseCapability]].[[Reject]], undefined, « moduleCompletion.[[Value]] »).
|
||||||
MUST(call(realm.vm(), *state.promise_capability->reject(), js_undefined(), *value));
|
MUST(call(state.vm(), *state.promise_capability->reject(), js_undefined(), *value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Return UNUSED.
|
// 4. Return UNUSED.
|
||||||
@ -318,14 +318,14 @@ ThrowCompletionOr<u32> CyclicModule::inner_module_linking(VM& vm, Vector<Module*
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16.2.1.5.2 Evaluate ( ), https://tc39.es/ecma262/#sec-moduleevaluation
|
// 16.2.1.5.3 Evaluate ( ), https://tc39.es/ecma262/#sec-moduleevaluation
|
||||||
ThrowCompletionOr<Promise*> CyclicModule::evaluate(VM& vm)
|
ThrowCompletionOr<Promise*> CyclicModule::evaluate(VM& vm)
|
||||||
{
|
{
|
||||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] evaluate[{}](vm)", this);
|
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] evaluate[{}](vm)", this);
|
||||||
// 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent.
|
// 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent.
|
||||||
// FIXME: Verify this somehow
|
// FIXME: Verify this somehow
|
||||||
|
|
||||||
// 2. Assert: module.[[Status]] is linked, evaluating-async, or evaluated.
|
// 2. Assert: module.[[Status]] is one of linked, evaluating-async, or evaluated.
|
||||||
VERIFY(m_status == ModuleStatus::Linked || m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated);
|
VERIFY(m_status == ModuleStatus::Linked || m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated);
|
||||||
|
|
||||||
// NOTE: The spec does not catch the case where evaluate is called twice on a script which failed
|
// NOTE: The spec does not catch the case where evaluate is called twice on a script which failed
|
||||||
@ -335,7 +335,7 @@ ThrowCompletionOr<Promise*> CyclicModule::evaluate(VM& vm)
|
|||||||
if (m_top_level_capability != nullptr)
|
if (m_top_level_capability != nullptr)
|
||||||
return verify_cast<Promise>(m_top_level_capability->promise().ptr());
|
return verify_cast<Promise>(m_top_level_capability->promise().ptr());
|
||||||
|
|
||||||
// 3. If module.[[Status]] is evaluating-async or evaluated, set module to module.[[CycleRoot]].
|
// 3. If module.[[Status]] is either evaluating-async or evaluated, set module to module.[[CycleRoot]].
|
||||||
if (m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated) {
|
if (m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated) {
|
||||||
// Note: This will continue this function with module.[[CycleRoot]]
|
// Note: This will continue this function with module.[[CycleRoot]]
|
||||||
VERIFY(m_cycle_root);
|
VERIFY(m_cycle_root);
|
||||||
@ -396,7 +396,7 @@ ThrowCompletionOr<Promise*> CyclicModule::evaluate(VM& vm)
|
|||||||
}
|
}
|
||||||
// 10. Else,
|
// 10. Else,
|
||||||
else {
|
else {
|
||||||
// a. Assert: module.[[Status]] is evaluating-async or evaluated.
|
// a. Assert: module.[[Status]] is either evaluating-async or evaluated.
|
||||||
VERIFY(m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated);
|
VERIFY(m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated);
|
||||||
// b. Assert: module.[[EvaluationError]] is empty.
|
// b. Assert: module.[[EvaluationError]] is empty.
|
||||||
VERIFY(!m_evaluation_error.is_error());
|
VERIFY(!m_evaluation_error.is_error());
|
||||||
@ -830,4 +830,90 @@ NonnullGCPtr<Module> CyclicModule::get_imported_module(ModuleRequest const& requ
|
|||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 13.3.10.1.1 ContinueDynamicImport ( promiseCapability, moduleCompletion ), https://tc39.es/ecma262/#sec-ContinueDynamicImport
|
||||||
|
void continue_dynamic_import(NonnullGCPtr<PromiseCapability> promise_capability, ThrowCompletionOr<NonnullGCPtr<Module>> const& module_completion)
|
||||||
|
{
|
||||||
|
auto& vm = promise_capability->vm();
|
||||||
|
|
||||||
|
// 1. If moduleCompletion is an abrupt completion, then
|
||||||
|
if (module_completion.is_throw_completion()) {
|
||||||
|
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « moduleCompletion.[[Value]] »).
|
||||||
|
MUST(call(vm, *promise_capability->reject(), js_undefined(), *module_completion.throw_completion().value()));
|
||||||
|
|
||||||
|
// b. Return unused.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Let module be moduleCompletion.[[Value]].
|
||||||
|
auto& module = *module_completion.value();
|
||||||
|
|
||||||
|
// 3. Let loadPromise be module.LoadRequestedModules().
|
||||||
|
auto& load_promise = verify_cast<CyclicModule>(module).load_requested_modules({});
|
||||||
|
|
||||||
|
// 4. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures promiseCapability and performs the
|
||||||
|
// following steps when called:
|
||||||
|
auto reject_closure = [promise_capability](VM& vm) -> ThrowCompletionOr<Value> {
|
||||||
|
auto reason = vm.argument(0);
|
||||||
|
|
||||||
|
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »).
|
||||||
|
MUST(call(vm, *promise_capability->reject(), js_undefined(), reason));
|
||||||
|
|
||||||
|
// b. Return unused.
|
||||||
|
return js_undefined();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
|
||||||
|
auto on_rejected = NativeFunction::create(*vm.current_realm(), move(reject_closure), 1, "");
|
||||||
|
|
||||||
|
// 6. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures module, promiseCapability,
|
||||||
|
// and onRejected and performs the following steps when called:
|
||||||
|
auto link_and_evaluate_closure = [&module, promise_capability, on_rejected](VM& vm) -> ThrowCompletionOr<Value> {
|
||||||
|
// a. Let link be Completion(module.Link()).
|
||||||
|
auto link = module.link(vm);
|
||||||
|
|
||||||
|
// b. If link is an abrupt completion, then
|
||||||
|
if (link.is_throw_completion()) {
|
||||||
|
// i. Perform ! Call(promiseCapability.[[Reject]], undefined, « link.[[Value]] »).
|
||||||
|
MUST(call(vm, *promise_capability->reject(), js_undefined(), *link.throw_completion().value()));
|
||||||
|
|
||||||
|
// ii. Return unused.
|
||||||
|
return js_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
// c. Let evaluatePromise be module.Evaluate().
|
||||||
|
auto evaluate_promise = module.evaluate(vm);
|
||||||
|
|
||||||
|
// d. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and
|
||||||
|
// promiseCapability and performs the following steps when called:
|
||||||
|
auto fulfilled_closure = [&module, promise_capability](VM& vm) -> ThrowCompletionOr<Value> {
|
||||||
|
// i. Let namespace be GetModuleNamespace(module).
|
||||||
|
auto namespace_ = module.get_module_namespace(vm);
|
||||||
|
|
||||||
|
// ii. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »).
|
||||||
|
MUST(call(vm, *promise_capability->resolve(), js_undefined(), namespace_.value()));
|
||||||
|
|
||||||
|
// iii. Return unused.
|
||||||
|
return js_undefined();
|
||||||
|
};
|
||||||
|
|
||||||
|
// e. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
|
||||||
|
auto on_fulfilled = NativeFunction::create(*vm.current_realm(), move(fulfilled_closure), 0, "");
|
||||||
|
|
||||||
|
// f. Perform PerformPromiseThen(evaluatePromise, onFulfilled, onRejected).
|
||||||
|
evaluate_promise.value()->perform_then(on_fulfilled, on_rejected, {});
|
||||||
|
|
||||||
|
// g. Return unused.
|
||||||
|
return js_undefined();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 7. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, 0, "", « »).
|
||||||
|
auto link_and_evaluate = NativeFunction::create(*vm.current_realm(), move(link_and_evaluate_closure), 0, "");
|
||||||
|
|
||||||
|
// 8. Perform PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected).
|
||||||
|
// FIXME: This is likely a spec bug, see load_requested_modules.
|
||||||
|
verify_cast<Promise>(*load_promise.promise()).perform_then(link_and_evaluate, on_rejected, {});
|
||||||
|
|
||||||
|
// 9. Return unused.
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ public:
|
|||||||
virtual ThrowCompletionOr<void> link(VM& vm) override final;
|
virtual ThrowCompletionOr<void> link(VM& vm) override final;
|
||||||
virtual ThrowCompletionOr<Promise*> evaluate(VM& vm) override final;
|
virtual ThrowCompletionOr<Promise*> evaluate(VM& vm) override final;
|
||||||
|
|
||||||
virtual PromiseCapability& load_requested_modules(Realm&, GCPtr<GraphLoadingState::HostDefined>);
|
virtual PromiseCapability& load_requested_modules(GCPtr<GraphLoadingState::HostDefined>);
|
||||||
virtual void inner_module_loading(GraphLoadingState& state);
|
virtual void inner_module_loading(GraphLoadingState& state);
|
||||||
|
|
||||||
Vector<ModuleRequest> const& requested_modules() const { return m_requested_modules; }
|
Vector<ModuleRequest> const& requested_modules() const { return m_requested_modules; }
|
||||||
@ -103,6 +103,7 @@ protected:
|
|||||||
Optional<u32> m_pending_async_dependencies; // [[PendingAsyncDependencies]]
|
Optional<u32> m_pending_async_dependencies; // [[PendingAsyncDependencies]]
|
||||||
};
|
};
|
||||||
|
|
||||||
void continue_module_loading(Realm&, GraphLoadingState& state, ThrowCompletionOr<Module*> const&);
|
void continue_module_loading(GraphLoadingState&, ThrowCompletionOr<NonnullGCPtr<Module>> const&);
|
||||||
|
void continue_dynamic_import(NonnullGCPtr<PromiseCapability>, ThrowCompletionOr<NonnullGCPtr<Module>> const& module_completion);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -67,46 +67,52 @@ ThrowCompletionOr<u32> Module::inner_module_evaluation(VM& vm, Vector<Module*>&,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 16.2.1.9 FinishLoadingImportedModule ( referrer, specifier, payload, result ), https://tc39.es/ecma262/#sec-FinishLoadingImportedModule
|
// 16.2.1.9 FinishLoadingImportedModule ( referrer, specifier, payload, result ), https://tc39.es/ecma262/#sec-FinishLoadingImportedModule
|
||||||
// FIXME: We currently implement an outdated version of https://tc39.es/proposal-import-attributes, as such it is not possible to
|
void finish_loading_imported_module(ImportedModuleReferrer referrer, ModuleRequest const& module_request, ImportedModulePayload payload, ThrowCompletionOr<NonnullGCPtr<Module>> const& result)
|
||||||
// use the exact steps from https://tc39.es/proposal-import-attributes/#sec-HostLoadImportedModule here.
|
|
||||||
// FIXME: Support Realm for referrer.
|
|
||||||
void finish_loading_imported_module(Realm& realm, Variant<JS::NonnullGCPtr<JS::Script>, JS::NonnullGCPtr<JS::CyclicModule>> referrer, ModuleRequest const& module_request, GraphLoadingState& payload, ThrowCompletionOr<Module*> const& result)
|
|
||||||
{
|
{
|
||||||
// 1. If result is a normal completion, then
|
// 1. If result is a normal completion, then
|
||||||
if (!result.is_error()) {
|
if (!result.is_error()) {
|
||||||
auto loaded_modules = referrer.visit(
|
// NOTE: Only Script and CyclicModule referrers have the [[LoadedModules]] internal slot.
|
||||||
[](JS::NonnullGCPtr<JS::Script> script) -> Vector<ModuleWithSpecifier> { return script->loaded_modules(); },
|
if (referrer.has<NonnullGCPtr<Script>>() || referrer.has<NonnullGCPtr<CyclicModule>>()) {
|
||||||
[](JS::NonnullGCPtr<JS::CyclicModule> module) -> Vector<ModuleWithSpecifier> { return module->loaded_modules(); });
|
auto loaded_modules = referrer.visit(
|
||||||
|
[](JS::NonnullGCPtr<JS::Script> script) -> Vector<ModuleWithSpecifier> { return script->loaded_modules(); },
|
||||||
|
[](JS::NonnullGCPtr<JS::CyclicModule> module) -> Vector<ModuleWithSpecifier> { return module->loaded_modules(); },
|
||||||
|
[](auto&) {
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
return Vector<ModuleWithSpecifier> {};
|
||||||
|
});
|
||||||
|
|
||||||
bool found_record = false;
|
bool found_record = false;
|
||||||
|
|
||||||
// a.a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then
|
// a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then
|
||||||
for (auto const& record : loaded_modules) {
|
for (auto const& record : loaded_modules) {
|
||||||
if (record.specifier == module_request.module_specifier) {
|
if (record.specifier == module_request.module_specifier) {
|
||||||
// i. Assert: That Record's [[Module]] is result.[[Value]].
|
// i. Assert: That Record's [[Module]] is result.[[Value]].
|
||||||
VERIFY(record.module == result.value());
|
VERIFY(record.module == result.value());
|
||||||
|
found_record = true;
|
||||||
found_record = true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// b. Else,
|
// b. Else,
|
||||||
if (!found_record) {
|
if (!found_record) {
|
||||||
auto* module = const_cast<Module*>(result.value());
|
auto module = result.value();
|
||||||
|
|
||||||
// i. Append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]].
|
// i. Append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]].
|
||||||
loaded_modules.append(ModuleWithSpecifier {
|
loaded_modules.append(ModuleWithSpecifier {
|
||||||
.specifier = module_request.module_specifier,
|
.specifier = module_request.module_specifier,
|
||||||
.module = NonnullGCPtr<Module>(*module) });
|
.module = NonnullGCPtr<Module>(*module) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: 2. If payload is a GraphLoadingState Record, then
|
if (payload.has<NonnullGCPtr<GraphLoadingState>>()) {
|
||||||
// a. Perform ContinueModuleLoading(payload, result)
|
// a. Perform ContinueModuleLoading(payload, result)
|
||||||
continue_module_loading(realm, payload, result);
|
continue_module_loading(payload.get<NonnullGCPtr<GraphLoadingState>>(), result);
|
||||||
|
}
|
||||||
// FIXME: Else,
|
// Else,
|
||||||
// FIXME: a. Perform ContinueDynamicImport(payload, result).
|
else {
|
||||||
|
// a. Perform ContinueDynamicImport(payload, result).
|
||||||
|
continue_dynamic_import(payload.get<NonnullGCPtr<PromiseCapability>>(), result);
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Return unused.
|
// 4. Return unused.
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <AK/DeprecatedFlyString.h>
|
#include <AK/DeprecatedFlyString.h>
|
||||||
#include <LibJS/Heap/GCPtr.h>
|
#include <LibJS/Heap/GCPtr.h>
|
||||||
|
#include <LibJS/ModuleLoading.h>
|
||||||
#include <LibJS/Runtime/Environment.h>
|
#include <LibJS/Runtime/Environment.h>
|
||||||
#include <LibJS/Runtime/Realm.h>
|
#include <LibJS/Runtime/Realm.h>
|
||||||
#include <LibJS/Script.h>
|
#include <LibJS/Script.h>
|
||||||
@ -113,6 +114,6 @@ private:
|
|||||||
class CyclicModule;
|
class CyclicModule;
|
||||||
struct GraphLoadingState;
|
struct GraphLoadingState;
|
||||||
|
|
||||||
void finish_loading_imported_module(Realm&, Variant<NonnullGCPtr<Script>, NonnullGCPtr<CyclicModule>>, ModuleRequest const&, GraphLoadingState&, ThrowCompletionOr<Module*> const&);
|
void finish_loading_imported_module(ImportedModuleReferrer, ModuleRequest const&, ImportedModulePayload, ThrowCompletionOr<NonnullGCPtr<Module>> const&);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1590,7 +1590,7 @@ ThrowCompletionOr<Value> perform_import_call(VM& vm, Value specifier, Value opti
|
|||||||
ModuleRequest request { specifier_string, attributes };
|
ModuleRequest request { specifier_string, attributes };
|
||||||
|
|
||||||
// 13. Perform HostLoadImportedModule(referrer, moduleRequest, empty, promiseCapability).
|
// 13. Perform HostLoadImportedModule(referrer, moduleRequest, empty, promiseCapability).
|
||||||
MUST_OR_THROW_OOM(vm.host_import_module_dynamically(referrer, move(request), promise_capability));
|
vm.host_load_imported_module(referrer, move(request), nullptr, promise_capability);
|
||||||
|
|
||||||
// 13. Return promiseCapability.[[Promise]].
|
// 13. Return promiseCapability.[[Promise]].
|
||||||
return Value { promise_capability->promise() };
|
return Value { promise_capability->promise() };
|
||||||
|
@ -234,8 +234,9 @@ ThrowCompletionOr<Value> shadow_realm_import_value(VM& vm, DeprecatedString spec
|
|||||||
TRY(vm.push_execution_context(eval_context, {}));
|
TRY(vm.push_execution_context(eval_context, {}));
|
||||||
|
|
||||||
// 6. Let referrer be the Realm component of evalContext.
|
// 6. Let referrer be the Realm component of evalContext.
|
||||||
// 7. Perform HostImportModuleDynamically(referrer, specifierString, innerCapability).
|
auto referrer = JS::NonnullGCPtr { *eval_context.realm };
|
||||||
MUST_OR_THROW_OOM(vm.host_import_module_dynamically(NonnullGCPtr { *eval_context.realm }, ModuleRequest { move(specifier_string) }, inner_capability));
|
// 7. Perform HostLoadImportedModule(referrer, specifierString, empty, innerCapability).
|
||||||
|
vm.host_load_imported_module(referrer, ModuleRequest { specifier_string }, nullptr, inner_capability);
|
||||||
|
|
||||||
// 7. Suspend evalContext and remove it from the execution context stack.
|
// 7. Suspend evalContext and remove it from the execution context stack.
|
||||||
// NOTE: We don't support this concept yet.
|
// NOTE: We don't support this concept yet.
|
||||||
|
@ -94,41 +94,8 @@ VM::VM(OwnPtr<CustomData> custom_data, ErrorMessages error_messages)
|
|||||||
return make_job_callback(function_object);
|
return make_job_callback(function_object);
|
||||||
};
|
};
|
||||||
|
|
||||||
host_resolve_imported_module = [&](ImportedModuleReferrer referrer, ModuleRequest const& specifier) {
|
host_load_imported_module = [this](ImportedModuleReferrer referrer, ModuleRequest const& module_request, GCPtr<GraphLoadingState::HostDefined> load_state, ImportedModulePayload payload) -> void {
|
||||||
return resolve_imported_module(referrer, specifier);
|
return load_imported_module(referrer, module_request, load_state, move(payload));
|
||||||
};
|
|
||||||
|
|
||||||
host_import_module_dynamically = [&](ImportedModuleReferrer, ModuleRequest const&, PromiseCapability const& promise_capability) -> ThrowCompletionOr<void> {
|
|
||||||
// By default, we throw on dynamic imports this is to prevent arbitrary file access by scripts.
|
|
||||||
VERIFY(current_realm());
|
|
||||||
auto& realm = *current_realm();
|
|
||||||
auto promise = Promise::create(realm);
|
|
||||||
|
|
||||||
// If you are here because you want to enable dynamic module importing make sure it won't be a security problem
|
|
||||||
// by checking the default implementation of HostImportModuleDynamically and creating your own hook or calling
|
|
||||||
// vm.enable_default_host_import_module_dynamically_hook().
|
|
||||||
promise->reject(Error::create(realm, ErrorType::DynamicImportNotAllowed.message()));
|
|
||||||
|
|
||||||
promise->perform_then(
|
|
||||||
NativeFunction::create(realm, "", [](auto&) -> ThrowCompletionOr<Value> {
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}),
|
|
||||||
NativeFunction::create(realm, "", [&promise_capability](auto& vm) -> ThrowCompletionOr<Value> {
|
|
||||||
auto error = vm.argument(0);
|
|
||||||
|
|
||||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « error »).
|
|
||||||
MUST(call(vm, *promise_capability.reject(), js_undefined(), error));
|
|
||||||
|
|
||||||
// b. Return undefined.
|
|
||||||
return js_undefined();
|
|
||||||
}),
|
|
||||||
{});
|
|
||||||
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
host_finish_dynamic_import = [&](ImportedModuleReferrer referrer, ModuleRequest const& specifier, PromiseCapability const& promise_capability, Promise* promise) {
|
|
||||||
return finish_dynamic_import(referrer, specifier, promise_capability, promise);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
host_get_import_meta_properties = [&](SourceTextModule const&) -> HashMap<PropertyKey, Value> {
|
host_get_import_meta_properties = [&](SourceTextModule const&) -> HashMap<PropertyKey, Value> {
|
||||||
@ -181,13 +148,6 @@ String const& VM::error_message(ErrorMessage type) const
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VM::enable_default_host_import_module_dynamically_hook()
|
|
||||||
{
|
|
||||||
host_import_module_dynamically = [&](ImportedModuleReferrer referrer, ModuleRequest const& specifier, PromiseCapability const& promise_capability) {
|
|
||||||
return import_module_dynamically(referrer, specifier, promise_capability);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Bytecode::Interpreter& VM::bytecode_interpreter()
|
Bytecode::Interpreter& VM::bytecode_interpreter()
|
||||||
{
|
{
|
||||||
return *m_bytecode_interpreter;
|
return *m_bytecode_interpreter;
|
||||||
@ -865,22 +825,32 @@ static DeprecatedString resolve_module_filename(StringView filename, StringView
|
|||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16.2.1.7 HostResolveImportedModule ( referencingScriptOrModule, specifier ), https://tc39.es/ecma262/#sec-hostresolveimportedmodule
|
// 16.2.1.8 HostLoadImportedModule ( referrer, specifier, hostDefined, payload ), https://tc39.es/ecma262/#sec-HostLoadImportedModule
|
||||||
ThrowCompletionOr<NonnullGCPtr<Module>> VM::resolve_imported_module(ImportedModuleReferrer referrer, ModuleRequest const& module_request)
|
void VM::load_imported_module(ImportedModuleReferrer referrer, ModuleRequest const& module_request, GCPtr<GraphLoadingState::HostDefined>, ImportedModulePayload payload)
|
||||||
{
|
{
|
||||||
// An implementation of HostResolveImportedModule must conform to the following requirements:
|
// An implementation of HostLoadImportedModule must conform to the following requirements:
|
||||||
// - If it completes normally, the [[Value]] slot of the completion must contain an instance of a concrete subclass of Module Record.
|
//
|
||||||
// - If a Module Record corresponding to the pair referencingScriptOrModule, moduleRequest does not exist or cannot be created, an exception must be thrown.
|
// - The host environment must perform FinishLoadingImportedModule(referrer, specifier, payload, result),
|
||||||
// - Each time this operation is called with a specific referencingScriptOrModule, moduleRequest.[[Specifier]], moduleRequest.[[Assertions]] triple
|
// where result is either a normal completion containing the loaded Module Record or a throw completion,
|
||||||
// as arguments it must return the same Module Record instance if it completes normally.
|
// either synchronously or asynchronously.
|
||||||
// * It is recommended but not required that implementations additionally conform to the following stronger constraint:
|
// - If this operation is called multiple times with the same (referrer, specifier) pair and it performs
|
||||||
// each time this operation is called with a specific referencingScriptOrModule, moduleRequest.[[Specifier]] pair as arguments it must return the same Module Record instance if it completes normally.
|
// FinishLoadingImportedModule(referrer, specifier, payload, result) where result is a normal completion,
|
||||||
// - moduleRequest.[[Assertions]] must not influence the interpretation of the module or the module specifier;
|
// then it must perform FinishLoadingImportedModule(referrer, specifier, payload, result) with the same result each time.
|
||||||
// instead, it may be used to determine whether the algorithm completes normally or with an abrupt completion.
|
// - The operation must treat payload as an opaque value to be passed through to FinishLoadingImportedModule.
|
||||||
|
//
|
||||||
|
// The actual process performed is host-defined, but typically consists of performing whatever I/O operations are necessary to
|
||||||
|
// load the appropriate Module Record. Multiple different (referrer, specifier) pairs may map to the same Module Record instance.
|
||||||
|
// The actual mapping semantics is host-defined but typically a normalization process is applied to specifier as part of the
|
||||||
|
// mapping process. A typical normalization process would include actions such as expansion of relative and abbreviated path specifiers.
|
||||||
|
|
||||||
// Multiple different referencingScriptOrModule, moduleRequest.[[Specifier]] pairs may map to the same Module Record instance.
|
// Here we check, against the spec, if payload is a promise capability, meaning that this was called for a dynamic import
|
||||||
// The actual mapping semantic is host-defined but typically a normalization process is applied to specifier as part of the mapping process.
|
if (payload.has<NonnullGCPtr<PromiseCapability>>() && !m_dynamic_imports_allowed) {
|
||||||
// A typical normalization process would include actions such as alphabetic case folding and expansion of relative and abbreviated path specifiers.
|
// If you are here because you want to enable dynamic module importing make sure it won't be a security problem
|
||||||
|
// by checking the default implementation of HostImportModuleDynamically and creating your own hook or calling
|
||||||
|
// vm.allow_dynamic_imports().
|
||||||
|
finish_loading_imported_module(referrer, module_request, payload, throw_completion<InternalError>(ErrorType::DynamicImportNotAllowed, module_request.module_specifier));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DeprecatedString module_type;
|
DeprecatedString module_type;
|
||||||
for (auto& attribute : module_request.attributes) {
|
for (auto& attribute : module_request.attributes) {
|
||||||
@ -894,13 +864,18 @@ ThrowCompletionOr<NonnullGCPtr<Module>> VM::resolve_imported_module(ImportedModu
|
|||||||
|
|
||||||
StringView const base_filename = referrer.visit(
|
StringView const base_filename = referrer.visit(
|
||||||
[&](NonnullGCPtr<Realm> const&) {
|
[&](NonnullGCPtr<Realm> const&) {
|
||||||
return "."sv;
|
// Generally within ECMA262 we always get a referencing_script_or_module. However, ShadowRealm gives an explicit null.
|
||||||
|
// To get around this is we attempt to get the active script_or_module otherwise we might start loading "random" files from the working directory.
|
||||||
|
return get_active_script_or_module().visit(
|
||||||
|
[](Empty) {
|
||||||
|
return "."sv;
|
||||||
|
},
|
||||||
|
[](auto const& script_or_module) {
|
||||||
|
return script_or_module->filename();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[&](NonnullGCPtr<Script> const& script) {
|
[&](auto const& script_or_module) {
|
||||||
return script->filename();
|
return script_or_module->filename();
|
||||||
},
|
|
||||||
[&](NonnullGCPtr<Module> const& module) {
|
|
||||||
return module->filename();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
LexicalPath base_path { base_filename };
|
LexicalPath base_path { base_filename };
|
||||||
@ -925,14 +900,15 @@ ThrowCompletionOr<NonnullGCPtr<Module>> VM::resolve_imported_module(ImportedModu
|
|||||||
return DeprecatedString::formatted("Module @ {}", script_or_module.ptr());
|
return DeprecatedString::formatted("Module @ {}", script_or_module.ptr());
|
||||||
});
|
});
|
||||||
|
|
||||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolve_imported_module({}, {})", referencing_module_string, filename);
|
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] load_imported_module({}, {})", referencing_module_string, filename);
|
||||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolved {} + {} -> {}", base_path, module_request.module_specifier, filename);
|
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolved {} + {} -> {}", base_path, module_request.module_specifier, filename);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto* loaded_module_or_end = get_stored_module(referrer, filename, module_type);
|
auto* loaded_module_or_end = get_stored_module(referrer, filename, module_type);
|
||||||
if (loaded_module_or_end != nullptr) {
|
if (loaded_module_or_end != nullptr) {
|
||||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolve_imported_module({}) already loaded at {}", filename, loaded_module_or_end->module.ptr());
|
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] load_imported_module({}) already loaded at {}", filename, loaded_module_or_end->module.ptr());
|
||||||
return NonnullGCPtr(*loaded_module_or_end->module);
|
finish_loading_imported_module(referrer, module_request, payload, *loaded_module_or_end->module);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] reading and parsing module {}", filename);
|
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] reading and parsing module {}", filename);
|
||||||
@ -940,21 +916,25 @@ ThrowCompletionOr<NonnullGCPtr<Module>> VM::resolve_imported_module(ImportedModu
|
|||||||
auto file_or_error = Core::File::open(filename, Core::File::OpenMode::Read);
|
auto file_or_error = Core::File::open(filename, Core::File::OpenMode::Read);
|
||||||
|
|
||||||
if (file_or_error.is_error()) {
|
if (file_or_error.is_error()) {
|
||||||
return throw_completion<SyntaxError>(ErrorType::ModuleNotFound, module_request.module_specifier);
|
finish_loading_imported_module(referrer, module_request, payload, throw_completion<SyntaxError>(ErrorType::ModuleNotFound, module_request.module_specifier));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Don't read the file in one go.
|
// FIXME: Don't read the file in one go.
|
||||||
auto file_content_or_error = file_or_error.value()->read_until_eof();
|
auto file_content_or_error = file_or_error.value()->read_until_eof();
|
||||||
|
|
||||||
if (file_content_or_error.is_error()) {
|
if (file_content_or_error.is_error()) {
|
||||||
if (file_content_or_error.error().code() == ENOMEM)
|
if (file_content_or_error.error().code() == ENOMEM) {
|
||||||
return throw_completion<JS::InternalError>(error_message(::JS::VM::ErrorMessage::OutOfMemory));
|
finish_loading_imported_module(referrer, module_request, payload, throw_completion<JS::InternalError>(error_message(::JS::VM::ErrorMessage::OutOfMemory)));
|
||||||
return throw_completion<SyntaxError>(ErrorType::ModuleNotFound, module_request.module_specifier);
|
return;
|
||||||
|
}
|
||||||
|
finish_loading_imported_module(referrer, module_request, payload, throw_completion<SyntaxError>(ErrorType::ModuleNotFound, module_request.module_specifier));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringView const content_view { file_content_or_error.value().bytes() };
|
StringView const content_view { file_content_or_error.value().bytes() };
|
||||||
|
|
||||||
auto module = TRY([&]() -> ThrowCompletionOr<NonnullGCPtr<Module>> {
|
auto module = [&]() -> ThrowCompletionOr<NonnullGCPtr<Module>> {
|
||||||
// If assertions has an entry entry such that entry.[[Key]] is "type", let type be entry.[[Value]]. The following requirements apply:
|
// If assertions has an entry entry such that entry.[[Key]] is "type", let type be entry.[[Value]]. The following requirements apply:
|
||||||
// If type is "json", then this algorithm must either invoke ParseJSONModule and return the resulting Completion Record, or throw an exception.
|
// If type is "json", then this algorithm must either invoke ParseJSONModule and return the resulting Completion Record, or throw an exception.
|
||||||
if (module_type == "json"sv) {
|
if (module_type == "json"sv) {
|
||||||
@ -971,124 +951,11 @@ ThrowCompletionOr<NonnullGCPtr<Module>> VM::resolve_imported_module(ImportedModu
|
|||||||
return throw_completion<SyntaxError>(module_or_errors.error().first().to_deprecated_string());
|
return throw_completion<SyntaxError>(module_or_errors.error().first().to_deprecated_string());
|
||||||
}
|
}
|
||||||
return module_or_errors.release_value();
|
return module_or_errors.release_value();
|
||||||
}());
|
}();
|
||||||
|
|
||||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] resolve_imported_module(...) parsed {} to {}", filename, module.ptr());
|
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] load_imported_module(...) parsed {} to {}", filename, module);
|
||||||
|
|
||||||
// We have to set it here already in case it references itself.
|
finish_loading_imported_module(referrer, module_request, payload, module);
|
||||||
m_loaded_modules.empend(
|
|
||||||
referrer,
|
|
||||||
filename,
|
|
||||||
module_type,
|
|
||||||
*module,
|
|
||||||
false);
|
|
||||||
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 16.2.1.8 HostImportModuleDynamically ( referencingScriptOrModule, specifier, promiseCapability ), https://tc39.es/ecma262/#sec-hostimportmoduledynamically
|
|
||||||
ThrowCompletionOr<void> VM::import_module_dynamically(ImportedModuleReferrer referrer, ModuleRequest module_request, PromiseCapability const& promise_capability)
|
|
||||||
{
|
|
||||||
auto& realm = *current_realm();
|
|
||||||
|
|
||||||
// Success path:
|
|
||||||
// - At some future time, the host environment must perform FinishDynamicImport(referencingScriptOrModule, moduleRequest, promiseCapability, promise),
|
|
||||||
// where promise is a Promise resolved with undefined.
|
|
||||||
// - Any subsequent call to HostResolveImportedModule after FinishDynamicImport has completed,
|
|
||||||
// given the arguments referencingScriptOrModule and specifier, must return a normal completion
|
|
||||||
// containing a module which has already been evaluated, i.e. whose Evaluate concrete method has
|
|
||||||
// already been called and returned a normal completion.
|
|
||||||
// Failure path:
|
|
||||||
// - At some future time, the host environment must perform
|
|
||||||
// FinishDynamicImport(referencingScriptOrModule, moduleRequest, promiseCapability, promise),
|
|
||||||
// where promise is a Promise rejected with an error representing the cause of failure.
|
|
||||||
|
|
||||||
auto promise = Promise::create(realm);
|
|
||||||
|
|
||||||
ScopeGuard finish_dynamic_import = [&] {
|
|
||||||
host_finish_dynamic_import(referrer, module_request, promise_capability, promise);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Note: If host_resolve_imported_module returns a module it has been loaded successfully and the next call in finish_dynamic_import will retrieve it again.
|
|
||||||
auto module_or_error = host_resolve_imported_module(referrer, module_request);
|
|
||||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] HostImportModuleDynamically(..., {}) -> {}", module_request.module_specifier, module_or_error.is_error() ? "failed" : "passed");
|
|
||||||
if (module_or_error.is_throw_completion()) {
|
|
||||||
promise->reject(*module_or_error.throw_completion().value());
|
|
||||||
} else {
|
|
||||||
auto module = module_or_error.release_value();
|
|
||||||
auto& source_text_module = static_cast<Module&>(*module);
|
|
||||||
|
|
||||||
auto evaluated_or_error = link_and_eval_module(source_text_module);
|
|
||||||
|
|
||||||
if (evaluated_or_error.is_throw_completion()) {
|
|
||||||
promise->reject(*evaluated_or_error.throw_completion().value());
|
|
||||||
} else {
|
|
||||||
promise->fulfill(js_undefined());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// It must return unused.
|
|
||||||
// Note: Just return void always since the resulting value cannot be accessed by user code.
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 16.2.1.9 FinishDynamicImport ( referencingScriptOrModule, specifier, promiseCapability, innerPromise ), https://tc39.es/ecma262/#sec-finishdynamicimport
|
|
||||||
void VM::finish_dynamic_import(ImportedModuleReferrer referrer, ModuleRequest module_request, PromiseCapability const& promise_capability, Promise* inner_promise)
|
|
||||||
{
|
|
||||||
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] finish_dynamic_import on {}", module_request.module_specifier);
|
|
||||||
|
|
||||||
auto& realm = *current_realm();
|
|
||||||
|
|
||||||
// 1. Let fulfilledClosure be a new Abstract Closure with parameters (result) that captures referencingScriptOrModule, specifier, and promiseCapability and performs the following steps when called:
|
|
||||||
auto fulfilled_closure = [referrer = move(referrer), module_request = move(module_request), &promise_capability](VM& vm) -> ThrowCompletionOr<Value> {
|
|
||||||
auto result = vm.argument(0);
|
|
||||||
// a. Assert: result is undefined.
|
|
||||||
VERIFY(result.is_undefined());
|
|
||||||
// b. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier).
|
|
||||||
auto module_record = MUST(vm.host_resolve_imported_module(referrer, module_request));
|
|
||||||
|
|
||||||
// c. Assert: Evaluate has already been invoked on moduleRecord and successfully completed.
|
|
||||||
// Note: If HostResolveImportedModule returns a module evaluate will have been called on it.
|
|
||||||
|
|
||||||
// d. Let namespace be Completion(GetModuleNamespace(moduleRecord)).
|
|
||||||
auto namespace_ = module_record->get_module_namespace(vm);
|
|
||||||
|
|
||||||
// e. If namespace is an abrupt completion, then
|
|
||||||
if (namespace_.is_throw_completion()) {
|
|
||||||
// i. Perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »).
|
|
||||||
MUST(call(vm, *promise_capability.reject(), js_undefined(), *namespace_.throw_completion().value()));
|
|
||||||
}
|
|
||||||
// f. Else,
|
|
||||||
else {
|
|
||||||
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »).
|
|
||||||
MUST(call(vm, *promise_capability.resolve(), js_undefined(), namespace_.release_value()));
|
|
||||||
}
|
|
||||||
// g. Return unused.
|
|
||||||
// NOTE: We don't support returning an empty/optional/unused value here.
|
|
||||||
return js_undefined();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
|
|
||||||
auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 0, "");
|
|
||||||
|
|
||||||
// 3. Let rejectedClosure be a new Abstract Closure with parameters (error) that captures promiseCapability and performs the following steps when called:
|
|
||||||
auto rejected_closure = [&promise_capability](VM& vm) -> ThrowCompletionOr<Value> {
|
|
||||||
auto error = vm.argument(0);
|
|
||||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « error »).
|
|
||||||
MUST(call(vm, *promise_capability.reject(), js_undefined(), error));
|
|
||||||
|
|
||||||
// b. Return unused.
|
|
||||||
// NOTE: We don't support returning an empty/optional/unused value here.
|
|
||||||
return js_undefined();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 4. Let onRejected be CreateBuiltinFunction(rejectedClosure, 0, "", « »).
|
|
||||||
auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 0, "");
|
|
||||||
|
|
||||||
// 5. Perform PerformPromiseThen(innerPromise, onFulfilled, onRejected).
|
|
||||||
inner_promise->perform_then(on_fulfilled, on_rejected, {});
|
|
||||||
|
|
||||||
// 6. Return unused.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VM::push_execution_context(ExecutionContext& context)
|
void VM::push_execution_context(ExecutionContext& context)
|
||||||
|
@ -229,18 +229,14 @@ public:
|
|||||||
// Our implementation of this proposal is outdated however, as such we try to adapt the proposal and living standard
|
// Our implementation of this proposal is outdated however, as such we try to adapt the proposal and living standard
|
||||||
// to match our implementation for now.
|
// to match our implementation for now.
|
||||||
// 16.2.1.8 HostLoadImportedModule ( referrer, moduleRequest, hostDefined, payload ), https://tc39.es/proposal-import-attributes/#sec-HostLoadImportedModule
|
// 16.2.1.8 HostLoadImportedModule ( referrer, moduleRequest, hostDefined, payload ), https://tc39.es/proposal-import-attributes/#sec-HostLoadImportedModule
|
||||||
Function<void(Realm&, Variant<NonnullGCPtr<Script>, NonnullGCPtr<CyclicModule>>, ModuleRequest const&, GCPtr<GraphLoadingState::HostDefined>, GraphLoadingState&)> host_load_imported_module;
|
Function<void(ImportedModuleReferrer, ModuleRequest const&, GCPtr<GraphLoadingState::HostDefined>, ImportedModulePayload)> host_load_imported_module;
|
||||||
|
|
||||||
Function<ThrowCompletionOr<NonnullGCPtr<Module>>(ImportedModuleReferrer, ModuleRequest const&)> host_resolve_imported_module;
|
|
||||||
Function<ThrowCompletionOr<void>(ImportedModuleReferrer, ModuleRequest, PromiseCapability const&)> host_import_module_dynamically;
|
|
||||||
Function<void(ImportedModuleReferrer, ModuleRequest const&, PromiseCapability const&, Promise*)> host_finish_dynamic_import;
|
|
||||||
|
|
||||||
Function<HashMap<PropertyKey, Value>(SourceTextModule&)> host_get_import_meta_properties;
|
Function<HashMap<PropertyKey, Value>(SourceTextModule&)> host_get_import_meta_properties;
|
||||||
Function<void(Object*, SourceTextModule const&)> host_finalize_import_meta;
|
Function<void(Object*, SourceTextModule const&)> host_finalize_import_meta;
|
||||||
|
|
||||||
Function<Vector<DeprecatedString>()> host_get_supported_import_attributes;
|
Function<Vector<DeprecatedString>()> host_get_supported_import_attributes;
|
||||||
|
|
||||||
void enable_default_host_import_module_dynamically_hook();
|
void set_dynamic_imports_allowed(bool value) { m_dynamic_imports_allowed = value; }
|
||||||
|
|
||||||
Function<void(Promise&, Promise::RejectionOperation)> host_promise_rejection_tracker;
|
Function<void(Promise&, Promise::RejectionOperation)> host_promise_rejection_tracker;
|
||||||
Function<ThrowCompletionOr<Value>(JobCallback&, Value, ReadonlySpan<Value>)> host_call_job_callback;
|
Function<ThrowCompletionOr<Value>(JobCallback&, Value, ReadonlySpan<Value>)> host_call_job_callback;
|
||||||
@ -271,12 +267,9 @@ private:
|
|||||||
ThrowCompletionOr<void> property_binding_initialization(BindingPattern const& binding, Value value, Environment* environment);
|
ThrowCompletionOr<void> property_binding_initialization(BindingPattern const& binding, Value value, Environment* environment);
|
||||||
ThrowCompletionOr<void> iterator_binding_initialization(BindingPattern const& binding, IteratorRecord& iterator_record, Environment* environment);
|
ThrowCompletionOr<void> iterator_binding_initialization(BindingPattern const& binding, IteratorRecord& iterator_record, Environment* environment);
|
||||||
|
|
||||||
ThrowCompletionOr<NonnullGCPtr<Module>> resolve_imported_module(ImportedModuleReferrer, ModuleRequest const& module_request);
|
void load_imported_module(ImportedModuleReferrer, ModuleRequest const&, GCPtr<GraphLoadingState::HostDefined>, ImportedModulePayload);
|
||||||
ThrowCompletionOr<void> link_and_eval_module(Module& module);
|
ThrowCompletionOr<void> link_and_eval_module(Module& module);
|
||||||
|
|
||||||
ThrowCompletionOr<void> import_module_dynamically(ImportedModuleReferrer, ModuleRequest module_request, PromiseCapability const& promise_capability);
|
|
||||||
void finish_dynamic_import(ImportedModuleReferrer, ModuleRequest module_request, PromiseCapability const& promise_capability, Promise* inner_promise);
|
|
||||||
|
|
||||||
void set_well_known_symbols(WellKnownSymbols well_known_symbols) { m_well_known_symbols = move(well_known_symbols); }
|
void set_well_known_symbols(WellKnownSymbols well_known_symbols) { m_well_known_symbols = move(well_known_symbols); }
|
||||||
|
|
||||||
Vector<FlatPtr> get_native_stack_trace() const;
|
Vector<FlatPtr> get_native_stack_trace() const;
|
||||||
@ -322,6 +315,8 @@ private:
|
|||||||
OwnPtr<CustomData> m_custom_data;
|
OwnPtr<CustomData> m_custom_data;
|
||||||
|
|
||||||
OwnPtr<Bytecode::Interpreter> m_bytecode_interpreter;
|
OwnPtr<Bytecode::Interpreter> m_bytecode_interpreter;
|
||||||
|
|
||||||
|
bool m_dynamic_imports_allowed { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename GlobalObjectType, typename... Args>
|
template<typename GlobalObjectType, typename... Args>
|
||||||
|
@ -194,7 +194,7 @@ int main(int argc, char** argv)
|
|||||||
|
|
||||||
if (!g_vm) {
|
if (!g_vm) {
|
||||||
g_vm = MUST(JS::VM::create());
|
g_vm = MUST(JS::VM::create());
|
||||||
g_vm->enable_default_host_import_module_dynamically_hook();
|
g_vm->set_dynamic_imports_allowed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Test::JS::TestRunner test_runner(test_root, common_path, print_times, print_progress, print_json, per_file);
|
Test::JS::TestRunner test_runner(test_root, common_path, print_times, print_progress, print_json, per_file);
|
||||||
|
@ -404,7 +404,10 @@ ErrorOr<void> initialize_main_thread_vm()
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 8.1.6.5.3 HostLoadImportedModule(referrer, moduleRequest, loadState, payload), https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
|
// 8.1.6.5.3 HostLoadImportedModule(referrer, moduleRequest, loadState, payload), https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
|
||||||
s_main_thread_vm->host_load_imported_module = [](JS::Realm& realm, Variant<JS::NonnullGCPtr<JS::Script>, JS::NonnullGCPtr<JS::CyclicModule>> referrer, JS::ModuleRequest const& module_request, JS::GCPtr<JS::GraphLoadingState::HostDefined> load_state, JS::NonnullGCPtr<JS::GraphLoadingState> payload) -> void {
|
s_main_thread_vm->host_load_imported_module = [](JS::ImportedModuleReferrer referrer, JS::ModuleRequest const& module_request, JS::GCPtr<JS::GraphLoadingState::HostDefined> load_state, JS::ImportedModulePayload payload) -> void {
|
||||||
|
auto& vm = *s_main_thread_vm;
|
||||||
|
auto& realm = *vm.current_realm();
|
||||||
|
|
||||||
// 1. Let settingsObject be the current settings object.
|
// 1. Let settingsObject be the current settings object.
|
||||||
Optional<HTML::EnvironmentSettingsObject&> settings_object = HTML::current_settings_object();
|
Optional<HTML::EnvironmentSettingsObject&> settings_object = HTML::current_settings_object();
|
||||||
|
|
||||||
@ -447,7 +450,7 @@ ErrorOr<void> initialize_main_thread_vm()
|
|||||||
auto completion = dom_exception_to_throw_completion(main_thread_vm(), url.exception());
|
auto completion = dom_exception_to_throw_completion(main_thread_vm(), url.exception());
|
||||||
|
|
||||||
// 2. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion).
|
// 2. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion).
|
||||||
JS::finish_loading_imported_module(realm, referrer, module_request, payload, completion);
|
JS::finish_loading_imported_module(referrer, module_request, payload, completion);
|
||||||
|
|
||||||
// 3. Return.
|
// 3. Return.
|
||||||
return;
|
return;
|
||||||
@ -475,37 +478,37 @@ ErrorOr<void> initialize_main_thread_vm()
|
|||||||
// 1. Let completion be null.
|
// 1. Let completion be null.
|
||||||
// NOTE: Our JS::Completion does not support non JS::Value types for its [[Value]], a such we
|
// NOTE: Our JS::Completion does not support non JS::Value types for its [[Value]], a such we
|
||||||
// use JS::ThrowCompletionOr here.
|
// use JS::ThrowCompletionOr here.
|
||||||
auto completion = JS::ThrowCompletionOr<JS::Module*> { {} };
|
auto completion = [&]() -> JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Module>> {
|
||||||
|
// 2. If moduleScript is null, then set completion to Completion Record { [[Type]]: throw, [[Value]]: a new TypeError, [[Target]]: empty }.
|
||||||
|
if (!module_script) {
|
||||||
|
return JS::throw_completion(JS::TypeError::create(realm, DeprecatedString::formatted("Loading imported module '{}' failed.", module_request.module_specifier)));
|
||||||
|
}
|
||||||
|
// 3. Otherwise, if moduleScript's parse error is not null, then:
|
||||||
|
else if (!module_script->parse_error().is_empty()) {
|
||||||
|
// 1. Let parseError be moduleScript's parse error.
|
||||||
|
auto parse_error = module_script->parse_error();
|
||||||
|
|
||||||
// 2. If moduleScript is null, then set completion to Completion Record { [[Type]]: throw, [[Value]]: a new TypeError, [[Target]]: empty }.
|
// 2. Set completion to Completion Record { [[Type]]: throw, [[Value]]: parseError, [[Target]]: empty }.
|
||||||
if (!module_script) {
|
return JS::throw_completion(parse_error);
|
||||||
completion = JS::throw_completion(JS::TypeError::create(realm, DeprecatedString::formatted("Loading imported module '{}' failed.", module_request.module_specifier)));
|
|
||||||
}
|
|
||||||
// 3. Otherwise, if moduleScript's parse error is not null, then:
|
|
||||||
else if (!module_script->parse_error().is_empty()) {
|
|
||||||
// 1. Let parseError be moduleScript's parse error.
|
|
||||||
auto parse_error = module_script->parse_error();
|
|
||||||
|
|
||||||
// 2. Set completion to Completion Record { [[Type]]: throw, [[Value]]: parseError, [[Target]]: empty }.
|
// 3. If loadState is not undefined and loadState.[[ParseError]] is null, set loadState.[[ParseError]] to parseError.
|
||||||
completion = JS::throw_completion(parse_error);
|
if (load_state) {
|
||||||
|
auto& load_state_as_fetch_context = static_cast<HTML::FetchContext&>(*load_state);
|
||||||
// 3. If loadState is not undefined and loadState.[[ParseError]] is null, set loadState.[[ParseError]] to parseError.
|
if (load_state_as_fetch_context.parse_error.is_null()) {
|
||||||
if (load_state) {
|
load_state_as_fetch_context.parse_error = parse_error;
|
||||||
auto& load_state_as_fetch_context = static_cast<HTML::FetchContext&>(*load_state);
|
}
|
||||||
if (load_state_as_fetch_context.parse_error.is_null()) {
|
|
||||||
load_state_as_fetch_context.parse_error = parse_error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// 4. Otherwise, set completion to Completion Record { [[Type]]: normal, [[Value]]: result's record, [[Target]]: empty }.
|
||||||
// 4. Otherwise, set completion to Completion Record { [[Type]]: normal, [[Value]]: result's record, [[Target]]: empty }.
|
else {
|
||||||
else {
|
auto* record = static_cast<HTML::JavaScriptModuleScript&>(*module_script).record();
|
||||||
auto* record = static_cast<HTML::JavaScriptModuleScript&>(*module_script).record();
|
|
||||||
|
|
||||||
completion = JS::ThrowCompletionOr<JS::Module*>(record);
|
return JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Module>>(*record);
|
||||||
}
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
// 5. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion).
|
// 5. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion).
|
||||||
JS::finish_loading_imported_module(realm, referrer, module_request, payload, completion);
|
JS::finish_loading_imported_module(referrer, module_request, payload, completion);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 13. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, settingsObject, fetchReferrer,
|
// 13. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, settingsObject, fetchReferrer,
|
||||||
@ -514,51 +517,6 @@ ErrorOr<void> initialize_main_thread_vm()
|
|||||||
HTML::fetch_single_imported_module_script(realm, url.release_value(), *fetch_client, destination, fetch_options, *settings_object, fetch_referrer, module_request, on_single_fetch_complete);
|
HTML::fetch_single_imported_module_script(realm, url.release_value(), *fetch_client, destination, fetch_options, *settings_object, fetch_referrer, module_request, on_single_fetch_complete);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 8.1.6.5.3 HostResolveImportedModule(referencingScriptOrModule, moduleRequest), https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingscriptormodule,-modulerequest)
|
|
||||||
s_main_thread_vm->host_resolve_imported_module = [](JS::ImportedModuleReferrer referrer, JS::ModuleRequest const& module_request) -> JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Module>> {
|
|
||||||
// 1. Let moduleMap and referencingScript be null.
|
|
||||||
Optional<HTML::ModuleMap&> module_map;
|
|
||||||
Optional<HTML::Script&> referencing_script;
|
|
||||||
|
|
||||||
if (referrer.has<JS::NonnullGCPtr<JS::Script>>() || referrer.has<JS::NonnullGCPtr<JS::CyclicModule>>()) {
|
|
||||||
// 1. Set referencingScript to referencingScriptOrModule.[[HostDefined]].
|
|
||||||
referencing_script = verify_cast<HTML::Script>(referrer.has<JS::NonnullGCPtr<JS::Script>>() ? *referrer.get<JS::NonnullGCPtr<JS::Script>>()->host_defined() : *referrer.get<JS::NonnullGCPtr<JS::CyclicModule>>()->host_defined());
|
|
||||||
|
|
||||||
// 2. Set moduleMap to referencingScript's settings object's module map.
|
|
||||||
module_map = referencing_script->settings_object().module_map();
|
|
||||||
}
|
|
||||||
// 3. Otherwise:
|
|
||||||
else {
|
|
||||||
// 1. Assert: there is a current settings object.
|
|
||||||
// NOTE: This is handled by the HTML::current_settings_object() accessor.
|
|
||||||
|
|
||||||
// 2. Set moduleMap to the current settings object's module map.
|
|
||||||
module_map = HTML::current_settings_object().module_map();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]].
|
|
||||||
auto url = MUST(HTML::resolve_module_specifier(referencing_script, module_request.module_specifier));
|
|
||||||
|
|
||||||
// 5. Assert: the previous step never throws an exception, because resolving a module specifier must have been previously successful
|
|
||||||
// with these same two arguments (either while creating the corresponding module script, or in fetch an import() module script graph).
|
|
||||||
// NOTE: Handled by MUST above.
|
|
||||||
|
|
||||||
// 6. Let moduleType be the result of running the module type from module request steps given moduleRequest.
|
|
||||||
auto module_type = HTML::module_type_from_module_request(module_request);
|
|
||||||
|
|
||||||
// 7. Let resolvedModuleScript be moduleMap[(url, moduleType)]. (This entry must exist for us to have gotten to this point.)
|
|
||||||
auto resolved_module_script = module_map->get(url, module_type).value();
|
|
||||||
|
|
||||||
// 8. Assert: resolvedModuleScript is a module script (i.e., is not null or "fetching").
|
|
||||||
VERIFY(resolved_module_script.type == HTML::ModuleMap::EntryType::ModuleScript);
|
|
||||||
|
|
||||||
// 9. Assert: resolvedModuleScript's record is not null.
|
|
||||||
VERIFY(resolved_module_script.module_script->record());
|
|
||||||
|
|
||||||
// 10. Return resolvedModuleScript's record.
|
|
||||||
return JS::NonnullGCPtr(*resolved_module_script.module_script->record());
|
|
||||||
};
|
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,7 +775,7 @@ void fetch_descendants_of_and_link_a_module_script(JS::Realm& realm,
|
|||||||
fetch_client.prepare_to_run_callback();
|
fetch_client.prepare_to_run_callback();
|
||||||
|
|
||||||
// 5. Let loadingPromise be record.LoadRequestedModules(state).
|
// 5. Let loadingPromise be record.LoadRequestedModules(state).
|
||||||
auto& loading_promise = record->load_requested_modules(realm, state);
|
auto& loading_promise = record->load_requested_modules(state);
|
||||||
|
|
||||||
// 6. Upon fulfillment of loadingPromise, run the following steps:
|
// 6. Upon fulfillment of loadingPromise, run the following steps:
|
||||||
WebIDL::upon_fulfillment(loading_promise, [&realm, record, &module_script, on_complete](auto const&) -> WebIDL::ExceptionOr<JS::Value> {
|
WebIDL::upon_fulfillment(loading_promise, [&realm, record, &module_script, on_complete](auto const&) -> WebIDL::ExceptionOr<JS::Value> {
|
||||||
|
@ -562,7 +562,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||||||
s_history_path = TRY(String::formatted("{}/.js-history", Core::StandardPaths::home_directory()));
|
s_history_path = TRY(String::formatted("{}/.js-history", Core::StandardPaths::home_directory()));
|
||||||
|
|
||||||
g_vm = TRY(JS::VM::create());
|
g_vm = TRY(JS::VM::create());
|
||||||
g_vm->enable_default_host_import_module_dynamically_hook();
|
g_vm->set_dynamic_imports_allowed(true);
|
||||||
|
|
||||||
if (!disable_debug_printing) {
|
if (!disable_debug_printing) {
|
||||||
// NOTE: These will print out both warnings when using something like Promise.reject().catch(...) -
|
// NOTE: These will print out both warnings when using something like Promise.reject().catch(...) -
|
||||||
|
Loading…
Reference in New Issue
Block a user