|
@@ -50,6 +50,37 @@ VM::VM(OwnPtr<CustomData> custom_data)
|
|
|
return resolve_imported_module(move(referencing_script_or_module), specifier);
|
|
|
};
|
|
|
|
|
|
+ host_import_module_dynamically = [&](ScriptOrModule, ModuleRequest const&, PromiseCapability promise_capability) {
|
|
|
+ // By default, we throw on dynamic imports this is to prevent arbitrary file access by scripts.
|
|
|
+ VERIFY(current_realm());
|
|
|
+ auto& global_object = current_realm()->global_object();
|
|
|
+ auto* promise = Promise::create(global_object);
|
|
|
+
|
|
|
+ // 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(global_object, ErrorType::DynamicImportNotAllowed.message()));
|
|
|
+
|
|
|
+ promise->perform_then(
|
|
|
+ NativeFunction::create(global_object, "", [](auto&, auto&) -> ThrowCompletionOr<Value> {
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+ }),
|
|
|
+ NativeFunction::create(global_object, "", [reject = make_handle(promise_capability.reject)](auto& vm, auto& global_object) -> ThrowCompletionOr<Value> {
|
|
|
+ auto error = vm.argument(0);
|
|
|
+
|
|
|
+ // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « error »).
|
|
|
+ MUST(JS::call(global_object, reject.cell(), js_undefined(), error));
|
|
|
+
|
|
|
+ // b. Return undefined.
|
|
|
+ return js_undefined();
|
|
|
+ }),
|
|
|
+ {});
|
|
|
+ };
|
|
|
+
|
|
|
+ host_finish_dynamic_import = [&](ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability promise_capability, Promise* promise) {
|
|
|
+ return finish_dynamic_import(move(referencing_script_or_module), specifier, promise_capability, promise);
|
|
|
+ };
|
|
|
+
|
|
|
#define __JS_ENUMERATE(SymbolName, snake_name) \
|
|
|
m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
|
|
|
JS_ENUMERATE_WELL_KNOWN_SYMBOLS
|
|
@@ -60,6 +91,13 @@ VM::~VM()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
+void VM::enable_default_host_import_module_dynamically_hook()
|
|
|
+{
|
|
|
+ host_import_module_dynamically = [&](ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability promise_capability) {
|
|
|
+ return import_module_dynamically(move(referencing_script_or_module), specifier, promise_capability);
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
Interpreter& VM::interpreter()
|
|
|
{
|
|
|
VERIFY(!m_interpreters.is_empty());
|
|
@@ -848,4 +886,127 @@ ThrowCompletionOr<NonnullRefPtr<Module>> VM::resolve_imported_module(ScriptOrMod
|
|
|
return module;
|
|
|
}
|
|
|
|
|
|
+// 16.2.1.8 HostImportModuleDynamically ( referencingScriptOrModule, specifier, promiseCapability ), https://tc39.es/ecma262/#sec-hostimportmoduledynamically
|
|
|
+void VM::import_module_dynamically(ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability promise_capability)
|
|
|
+{
|
|
|
+ auto& global_object = current_realm()->global_object();
|
|
|
+
|
|
|
+ // Success path:
|
|
|
+ // - At some future time, the host environment must perform FinishDynamicImport(referencingScriptOrModule, specifier, 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 complete normally.
|
|
|
+ // - The completion value of any subsequent call to HostResolveImportedModule after FinishDynamicImport has completed,
|
|
|
+ // given the arguments referencingScriptOrModule and specifier, must be 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, specifier, promiseCapability, promise),
|
|
|
+ // where promise is a Promise rejected with an error representing the cause of failure.
|
|
|
+
|
|
|
+ auto* promise = Promise::create(global_object);
|
|
|
+
|
|
|
+ ScopeGuard finish_dynamic_import = [&] {
|
|
|
+ host_finish_dynamic_import(referencing_script_or_module, specifier, promise_capability, promise);
|
|
|
+ };
|
|
|
+
|
|
|
+ // Generally within ECMA262 we always get a referencing_script_or_moulde. 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.
|
|
|
+ if (referencing_script_or_module.has<Empty>()) {
|
|
|
+ referencing_script_or_module = get_active_script_or_module();
|
|
|
+
|
|
|
+ // If there is no ScriptOrModule in any of the execution contexts
|
|
|
+ if (referencing_script_or_module.has<Empty>()) {
|
|
|
+ // Throw an error for now
|
|
|
+ promise->reject(InternalError::create(global_object, String::formatted(ErrorType::ModuleNotFoundNoReferencingScript.message(), specifier.module_specifier)));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ VERIFY(!exception());
|
|
|
+ // 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(referencing_script_or_module, specifier);
|
|
|
+ dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] HostImportModuleDynamically(..., {}) -> {}", specifier.module_specifier, module_or_error.is_error() ? "failed" : "passed");
|
|
|
+ if (module_or_error.is_throw_completion()) {
|
|
|
+ // Note: We should not leak the exception thrown in host_resolve_imported_module.
|
|
|
+ clear_exception();
|
|
|
+ promise->reject(*module_or_error.throw_completion().value());
|
|
|
+ } else {
|
|
|
+ // Note: If you are here because this VERIFY is failing overwrite host_import_module_dynamically
|
|
|
+ // because this is LibJS internal logic which won't always work
|
|
|
+ auto module = module_or_error.release_value();
|
|
|
+ VERIFY(is<SourceTextModule>(*module));
|
|
|
+ auto& source_text_module = static_cast<SourceTextModule&>(*module);
|
|
|
+
|
|
|
+ auto evaluated_or_error = link_and_eval_module(source_text_module);
|
|
|
+
|
|
|
+ if (evaluated_or_error.is_throw_completion()) {
|
|
|
+ // Note: Again we don't want to leak the exception from link_and_eval_module.
|
|
|
+ clear_exception();
|
|
|
+ promise->reject(*evaluated_or_error.throw_completion().value());
|
|
|
+ } else {
|
|
|
+ VERIFY(!exception());
|
|
|
+ promise->fulfill(js_undefined());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // It must return NormalCompletion(undefined).
|
|
|
+ // Note: Just return void always since the resulting value cannot be accessed by user code.
|
|
|
+}
|
|
|
+
|
|
|
+// 16.2.1.9 FinishDynamicImport ( referencingScriptOrModule, specifier, promiseCapability, innerPromise ), https://tc39.es/ecma262/#sec-finishdynamicimport
|
|
|
+void VM::finish_dynamic_import(ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability promise_capability, Promise* inner_promise)
|
|
|
+{
|
|
|
+ dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] finish_dynamic_import on {}", specifier.module_specifier);
|
|
|
+
|
|
|
+ // 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 = [referencing_script_or_module, specifier, promise_capability](VM& vm, GlobalObject& global_object) -> 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(referencing_script_or_module, specifier));
|
|
|
+
|
|
|
+ // 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 GetModuleNamespace(moduleRecord).
|
|
|
+ auto namespace_ = module_record->get_module_namespace(vm);
|
|
|
+
|
|
|
+ VERIFY(!vm.exception());
|
|
|
+ // e. If namespace is an abrupt completion, then
|
|
|
+ if (namespace_.is_throw_completion()) {
|
|
|
+ // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »).
|
|
|
+ MUST(JS::call(global_object, promise_capability.reject, js_undefined(), *namespace_.throw_completion().value()));
|
|
|
+ }
|
|
|
+ // f. Else,
|
|
|
+ else {
|
|
|
+ // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »).
|
|
|
+ MUST(JS::call(global_object, promise_capability.resolve, js_undefined(), namespace_.release_value()));
|
|
|
+ }
|
|
|
+ // g. Return undefined.
|
|
|
+ return js_undefined();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 2. Let onFulfilled be ! CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
|
|
|
+ auto* on_fulfilled = NativeFunction::create(current_realm()->global_object(), "", move(fulfilled_closure));
|
|
|
+
|
|
|
+ // 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, GlobalObject& global_object) -> ThrowCompletionOr<Value> {
|
|
|
+ auto error = vm.argument(0);
|
|
|
+ // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « error »).
|
|
|
+ MUST(JS::call(global_object, promise_capability.reject, js_undefined(), error));
|
|
|
+ // b. Return undefined.
|
|
|
+ return js_undefined();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 4. Let onRejected be ! CreateBuiltinFunction(rejectedClosure, 0, "", « »).
|
|
|
+ auto* on_rejected = NativeFunction::create(current_realm()->global_object(), "", move(rejected_closure));
|
|
|
+
|
|
|
+ // 5. Perform ! PerformPromiseThen(innerPromise, onFulfilled, onRejected).
|
|
|
+ inner_promise->perform_then(on_fulfilled, on_rejected, {});
|
|
|
+
|
|
|
+ VERIFY(!exception());
|
|
|
+}
|
|
|
+
|
|
|
}
|