diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index b7ae469ccc..950925f325 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1536,9 +1536,9 @@ DeprecatedFlyString ExportStatement::local_name_for_default = "*default*"; static void dump_assert_clauses(ModuleRequest const& request) { - if (!request.assertions.is_empty()) { + if (!request.attributes.is_empty()) { out("[ "); - for (auto& assertion : request.assertions) + for (auto& assertion : request.attributes) out("{}: {}, ", assertion.key, assertion.value); out(" ]"); } @@ -1882,15 +1882,15 @@ ThrowCompletionOr Program::global_declaration_instantiation(VM& vm, Global return {}; } -ModuleRequest::ModuleRequest(DeprecatedFlyString module_specifier_, Vector assertions_) +ModuleRequest::ModuleRequest(DeprecatedFlyString module_specifier_, Vector attributes) : module_specifier(move(module_specifier_)) - , assertions(move(assertions_)) + , attributes(move(attributes)) { - // Perform step 10.e. from EvaluateImportCall, https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call - // or step 2. from 2.7 Static Semantics: AssertClauseToAssertions, https://tc39.es/proposal-import-assertions/#sec-assert-clause-to-assertions + // Perform step 10.e. from EvaluateImportCall, https://tc39.es/proposal-import-attributes/#sec-evaluate-import-call + // or step 2. from WithClauseToAttributes, https://tc39.es/proposal-import-attributes/#sec-with-clause-to-attributes // e. / 2. Sort assertions by the code point order of the [[Key]] of each element. // NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in. - quick_sort(assertions, [](Assertion const& lhs, Assertion const& rhs) { + quick_sort(this->attributes, [](ImportAttribute const& lhs, ImportAttribute const& rhs) { return lhs.key < rhs.key; }); } diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 27545afb4b..323fc8f155 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -162,6 +162,7 @@ class ClassExpression; struct ClassFieldDefinition; class Completion; class Console; +class CyclicModule; class DeclarativeEnvironment; class DeferGC; class ECMAScriptFunctionObject; @@ -178,6 +179,7 @@ class FunctionNode; struct FunctionParameter; class GlobalEnvironment; class GlobalObject; +struct GraphLoadingState; class HandleImpl; class Heap; class HeapBlock; diff --git a/Userland/Libraries/LibJS/ModuleLoading.h b/Userland/Libraries/LibJS/ModuleLoading.h new file mode 100644 index 0000000000..98e302b8de --- /dev/null +++ b/Userland/Libraries/LibJS/ModuleLoading.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023, networkException + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS { + +using ImportedModuleReferrer = Variant, NonnullGCPtr, NonnullGCPtr>; +using ImportedModulePayload = Variant, NonnullGCPtr>; + +} diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 3fbe0657ca..8a38a1815e 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -1230,7 +1230,7 @@ NonnullRefPtr Parser::parse_import_call() // ImportCall[Yield, Await]: // import(AssignmentExpression[+In, ?Yield, ?Await] ,opt) // import(AssignmentExpression[+In, ?Yield, ?Await] ,AssignmentExpression[+In, ?Yield, ?Await] ,opt) - // From https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call + // From https://tc39.es/proposal-import-attributes/#sec-evaluate-import-call consume(TokenType::Import); consume(TokenType::ParenOpen); @@ -4468,8 +4468,10 @@ void Parser::check_identifier_name_for_assignment_validity(DeprecatedFlyString c } } -bool Parser::match_assert_clause() const +bool Parser::match_with_clause() const { + if (m_state.current_token.original_value() == "with"sv) + return true; return !m_state.current_token.trivia_contains_line_terminator() && m_state.current_token.original_value() == "assert"sv; } @@ -4495,7 +4497,7 @@ DeprecatedFlyString Parser::consume_string_value() return value; } -// AssertClause, https://tc39.es/proposal-import-assertions/#prod-AssertClause +// WithClause, https://tc39.es/proposal-import-attributes/#prod-WithClause ModuleRequest Parser::parse_module_request() { // Does not include the 'from' since that is not always required. @@ -4507,10 +4509,10 @@ ModuleRequest Parser::parse_module_request() ModuleRequest request { consume_string_value() }; - if (!match_assert_clause()) + if (!match_with_clause()) return request; - VERIFY(m_state.current_token.original_value() == "assert"sv); + VERIFY(m_state.current_token.original_value().is_one_of("with"sv, "assert"sv)); consume(TokenType::Identifier); consume(TokenType::CurlyOpen); @@ -4522,18 +4524,18 @@ ModuleRequest Parser::parse_module_request() } else if (match_identifier_name()) { key = consume().value(); } else { - expected("IdentifierName or StringValue as AssertionKey"); + expected("IdentifierName or StringValue as WithKey"); consume(); } consume(TokenType::Colon); if (match(TokenType::StringLiteral)) { - for (auto& entries : request.assertions) { + for (auto& entries : request.attributes) { if (entries.key == key) - syntax_error(DeprecatedString::formatted("Duplicate assertion clauses with name: {}", key)); + syntax_error(DeprecatedString::formatted("Duplicate attribute clauses with name: {}", key)); } - request.add_assertion(move(key), parse_string_literal(m_state.current_token)->value().to_deprecated_string()); + request.add_attribute(move(key), parse_string_literal(m_state.current_token)->value().to_deprecated_string()); } consume(TokenType::StringLiteral); @@ -4554,9 +4556,9 @@ NonnullRefPtr Parser::parse_import_statement(Program& pro { // We use the extended syntax which adds: // ImportDeclaration: - // import ImportClause FromClause [no LineTerminator here] AssertClause; - // import ModuleSpecifier [no LineTerminator here] AssertClause; - // From: https://tc39.es/proposal-import-assertions/#prod-ImportDeclaration + // import ImportClause FromClause [no LineTerminator here] WithClause; + // import ModuleSpecifier [no LineTerminator here] WithClause; + // From: https://tc39.es/proposal-import-attributes/#prod-ImportDeclaration auto rule_start = push_start(); if (program.type() != Program::Type::Module) @@ -4714,8 +4716,8 @@ NonnullRefPtr Parser::parse_export_statement(Program& pro { // We use the extended syntax which adds: // ExportDeclaration: - // export ExportFromClause FromClause [no LineTerminator here] AssertClause ; - // From: https://tc39.es/proposal-import-assertions/#prod-ExportDeclaration + // export ExportFromClause FromClause [no LineTerminator here] WithClause ; + // From: https://tc39.es/proposal-import-attributes/#sec-exports auto rule_start = push_start(); if (program.type() != Program::Type::Module) diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 06c0ce47ac..f244e6fa51 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -225,7 +225,7 @@ private: bool match_secondary_expression(ForbiddenTokens forbidden = {}) const; bool match_statement() const; bool match_export_or_import() const; - bool match_assert_clause() const; + bool match_with_clause() const; enum class AllowUsingDeclaration { No, diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 87af53314f..1f9ef67f1f 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -1459,25 +1460,53 @@ Completion dispose_resources(VM& vm, GCPtr disposable, C return completion; } +// https://tc39.es/proposal-import-attributes/#sec-AllImportAttributesSupported +static bool all_import_attributes_supported(VM& vm, Vector const& attributes) +{ + // 1. Let supported be HostGetSupportedImportAttributes(). + auto supported = vm.host_get_supported_import_attributes(); + + // 2. For each ImportAttribute Record attribute of attributes, do + for (auto const& attribute : attributes) { + // a. If supported does not contain attribute.[[Key]], return false. + if (!supported.contains_slow(attribute.key)) + return false; + } + + // 3. Return true. + return true; +} + ThrowCompletionOr perform_import_call(VM& vm, Value specifier, Value options_value) { auto& realm = *vm.current_realm(); - // 2.1.1.1 EvaluateImportCall ( specifierExpression [ , optionsExpression ] ), https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call - // 1. Let referencingScriptOrModule be GetActiveScriptOrModule(). - auto referencing_script_or_module = vm.get_active_script_or_module(); + // 13.3.10.2 EvaluateImportCall ( specifierExpression [ , optionsExpression ] ), https://tc39.es/proposal-import-attributes/#sec-evaluate-import-call + // 1. Let referrer be GetActiveScriptOrModule(). + auto referrer = [&]() -> ImportedModuleReferrer { + auto active_script_or_module = vm.get_active_script_or_module(); - // 6. Let promiseCapability be ! NewPromiseCapability(%Promise%). + // 2. If referrer is null, set referrer to the current Realm Record. + if (active_script_or_module.has()) + return NonnullGCPtr { realm }; + + if (active_script_or_module.has>()) + return active_script_or_module.get>(); + + return NonnullGCPtr { verify_cast(*active_script_or_module.get>()) }; + }(); + + // 7. Let promiseCapability be ! NewPromiseCapability(%Promise%). auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor())); - // 7. Let specifierString be Completion(ToString(specifier)). - // 8. IfAbruptRejectPromise(specifierString, promiseCapability). + // 8. Let specifierString be Completion(ToString(specifier)). + // 9. IfAbruptRejectPromise(specifierString, promiseCapability). auto specifier_string = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, specifier.to_deprecated_string(vm)); - // 9. Let assertions be a new empty List. - Vector assertions; + // 10. Let attributes be a new empty List. + Vector attributes; - // 10. If options is not undefined, then + // 11. If options is not undefined, then if (!options_value.is_undefined()) { // a. If Type(options) is not Object, if (!options_value.is_object()) { @@ -1489,14 +1518,22 @@ ThrowCompletionOr perform_import_call(VM& vm, Value specifier, Value opti return Value { promise_capability->promise() }; } - // b. Let assertionsObj be Get(options, "assert"). - // c. IfAbruptRejectPromise(assertionsObj, promiseCapability). - auto assertion_object = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, options_value.get(vm, vm.names.assert)); + // b. Let attributesObj be Completion(Get(options, "with")). + // c. IfAbruptRejectPromise(attributesObj, promiseCapability). + auto attributes_obj = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, options_value.get(vm, vm.names.with)); - // d. If assertionsObj is not undefined, - if (!assertion_object.is_undefined()) { - // i. If Type(assertionsObj) is not Object, - if (!assertion_object.is_object()) { + // d. Normative Optional, Deprecated + // 11. If the host supports the deprecated assert keyword for import attributes and attributesObj is undefined, then + if (attributes_obj.is_undefined()) { + // i. Set attributesObj to Completion(Get(options, "assert")). + // ii. IfAbruptRejectPromise(attributesObj, promiseCapability). + attributes_obj = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, options_value.get(vm, vm.names.assert)); + } + + // e. If attributesObj is not undefined, + if (!attributes_obj.is_undefined()) { + // i. If Type(attributesObj) is not Object, + if (!attributes_obj.is_object()) { auto error = TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted(ErrorType::NotAnObject.message(), "ImportOptionsAssertions"))); // 1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). MUST(call(vm, *promise_capability->reject(), js_undefined(), error)); @@ -1505,20 +1542,17 @@ ThrowCompletionOr perform_import_call(VM& vm, Value specifier, Value opti return Value { promise_capability->promise() }; } - // ii. Let keys be EnumerableOwnPropertyNames(assertionsObj, key). - // iii. IfAbruptRejectPromise(keys, promiseCapability). - auto keys = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, assertion_object.as_object().enumerable_own_property_names(Object::PropertyKind::Key)); + // ii. Let entries be Completion(EnumerableOwnProperties(attributesObj, key+value)). + // iii. IfAbruptRejectPromise(entries, promiseCapability). + auto entries = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, attributes_obj.as_object().enumerable_own_property_names(Object::PropertyKind::KeyAndValue)); - // iv. Let supportedAssertions be ! HostGetSupportedImportAssertions(). - auto supported_assertions = vm.host_get_supported_import_assertions(); + // iv. For each entry of entries, do + for (auto const& entry : entries) { + // 1. Let key be ! Get(entry, "0"). + auto key = MUST(entry.get(vm, PropertyKey(0))); - // v. For each String key of keys, - for (auto const& key : keys) { - auto property_key = MUST(key.to_property_key(vm)); - - // 1. Let value be Get(assertionsObj, key). - // 2. IfAbruptRejectPromise(value, promiseCapability). - auto value = TRY_OR_REJECT_WITH_VALUE(vm, promise_capability, assertion_object.get(vm, property_key)); + // 2. Let value be ! Get(entry, "1"). + auto value = MUST(entry.get(vm, PropertyKey(1))); // 3. If Type(value) is not String, then if (!value.is_string()) { @@ -1530,22 +1564,33 @@ ThrowCompletionOr perform_import_call(VM& vm, Value specifier, Value opti return Value { promise_capability->promise() }; } - // 4. If supportedAssertions contains key, then - if (supported_assertions.contains_slow(property_key.to_string())) { - // a. Append { [[Key]]: key, [[Value]]: value } to assertions. - assertions.empend(property_key.to_string(), value.as_string().deprecated_string()); - } + // 4. Append the ImportAttribute Record { [[Key]]: key, [[Value]]: value } to attributes. + attributes.empend(key.as_string().deprecated_string(), value.as_string().deprecated_string()); } } - // e. Sort assertions by the code point order of the [[Key]] of each element. NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in. - // Note: This is done when constructing the ModuleRequest. + + // f. If AllImportAttributesSupported(attributes) is false, then + if (!all_import_attributes_supported(vm, attributes)) { + auto error = TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted(ErrorType::NotAnObject.message(), "ImportOptionsAssertions"))); + // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + MUST(call(vm, *promise_capability->reject(), js_undefined(), error)); + + // ii. Return promiseCapability.[[Promise]]. + return Value { promise_capability->promise() }; + } + + // g. Sort attributes according to the lexicographic order of their [[Key]] fields, + // treating the value of each such field as a sequence of UTF-16 code unit values. + // NOTE: This sorting is observable only in that hosts are prohibited from + // distinguishing among attributes by the order they occur in. + // NOTE: This is done when constructing the ModuleRequest. } - // 11. Let moduleRequest be a new ModuleRequest Record { [[Specifier]]: specifierString, [[Assertions]]: assertions }. - ModuleRequest request { specifier_string, assertions }; + // 12. Let moduleRequest be a new ModuleRequest Record { [[Specifier]]: specifierString, [[Attributes]]: attributes }. + ModuleRequest request { specifier_string, attributes }; - // 12. Perform HostImportModuleDynamically(referencingScriptOrModule, moduleRequest, promiseCapability). - MUST_OR_THROW_OOM(vm.host_import_module_dynamically(referencing_script_or_module, move(request), promise_capability)); + // 13. Perform HostLoadImportedModule(referrer, moduleRequest, empty, promiseCapability). + MUST_OR_THROW_OOM(vm.host_import_module_dynamically(referrer, move(request), promise_capability)); // 13. Return promiseCapability.[[Promise]]. return Value { promise_capability->promise() }; diff --git a/Userland/Libraries/LibJS/Runtime/ModuleRequest.h b/Userland/Libraries/LibJS/Runtime/ModuleRequest.h index 3ce814d9d2..71735b3fba 100644 --- a/Userland/Libraries/LibJS/Runtime/ModuleRequest.h +++ b/Userland/Libraries/LibJS/Runtime/ModuleRequest.h @@ -18,13 +18,14 @@ struct ModuleWithSpecifier { NonnullGCPtr module; // [[Module]] }; -// 2.9 ModuleRequest Records, https://tc39.es/proposal-import-assertions/#sec-modulerequest-record -struct ModuleRequest { - struct Assertion { - DeprecatedString key; - DeprecatedString value; - }; +// https://tc39.es/proposal-import-attributes/#importattribute-record +struct ImportAttribute { + DeprecatedString key; + DeprecatedString value; +}; +// https://tc39.es/proposal-import-attributes/#modulerequest-record +struct ModuleRequest { ModuleRequest() = default; explicit ModuleRequest(DeprecatedFlyString specifier) @@ -32,15 +33,15 @@ struct ModuleRequest { { } - ModuleRequest(DeprecatedFlyString module_specifier, Vector assertions); + ModuleRequest(DeprecatedFlyString specifier, Vector attributes); - void add_assertion(DeprecatedString key, DeprecatedString value) + void add_attribute(DeprecatedString key, DeprecatedString value) { - assertions.empend(move(key), move(value)); + attributes.empend(move(key), move(value)); } DeprecatedFlyString module_specifier; // [[Specifier]] - Vector assertions; // [[Assertions]] + Vector attributes; // [[Attributes]] }; } diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp index c610821e54..7e2f55adc7 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -233,8 +233,9 @@ ThrowCompletionOr shadow_realm_import_value(VM& vm, DeprecatedString spec // 5. Push evalContext onto the execution context stack; evalContext is now the running execution context. TRY(vm.push_execution_context(eval_context, {})); - // 6. Perform HostImportModuleDynamically(null, specifierString, innerCapability). - MUST_OR_THROW_OOM(vm.host_import_module_dynamically(Empty {}, ModuleRequest { move(specifier_string) }, inner_capability)); + // 6. Let referrer be the Realm component of evalContext. + // 7. Perform HostImportModuleDynamically(referrer, specifierString, innerCapability). + MUST_OR_THROW_OOM(vm.host_import_module_dynamically(NonnullGCPtr { *eval_context.realm }, ModuleRequest { move(specifier_string) }, inner_capability)); // 7. Suspend evalContext and remove it from the execution context stack. // NOTE: We don't support this concept yet. diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index d636fd0ea2..74b5777b74 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -94,11 +94,11 @@ VM::VM(OwnPtr custom_data, ErrorMessages error_messages) return make_job_callback(function_object); }; - host_resolve_imported_module = [&](ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier) { - return resolve_imported_module(move(referencing_script_or_module), specifier); + host_resolve_imported_module = [&](ImportedModuleReferrer referrer, ModuleRequest const& specifier) { + return resolve_imported_module(referrer, specifier); }; - host_import_module_dynamically = [&](ScriptOrModule, ModuleRequest const&, PromiseCapability const& promise_capability) -> ThrowCompletionOr { + host_import_module_dynamically = [&](ImportedModuleReferrer, ModuleRequest const&, PromiseCapability const& promise_capability) -> ThrowCompletionOr { // By default, we throw on dynamic imports this is to prevent arbitrary file access by scripts. VERIFY(current_realm()); auto& realm = *current_realm(); @@ -127,8 +127,8 @@ VM::VM(OwnPtr custom_data, ErrorMessages error_messages) return {}; }; - host_finish_dynamic_import = [&](ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability const& promise_capability, Promise* promise) { - return finish_dynamic_import(move(referencing_script_or_module), specifier, promise_capability, promise); + 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 { @@ -138,7 +138,7 @@ VM::VM(OwnPtr custom_data, ErrorMessages error_messages) host_finalize_import_meta = [&](Object*, SourceTextModule const&) { }; - host_get_supported_import_assertions = [&] { + host_get_supported_import_attributes = [&] { return Vector { "type" }; }; @@ -183,8 +183,8 @@ String const& VM::error_message(ErrorMessage type) const void VM::enable_default_host_import_module_dynamically_hook() { - host_import_module_dynamically = [&](ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier, PromiseCapability const& promise_capability) { - return import_module_dynamically(move(referencing_script_or_module), specifier, promise_capability); + host_import_module_dynamically = [&](ImportedModuleReferrer referrer, ModuleRequest const& specifier, PromiseCapability const& promise_capability) { + return import_module_dynamically(referrer, specifier, promise_capability); }; } @@ -760,7 +760,7 @@ ScriptOrModule VM::get_active_script_or_module() const return m_execution_context_stack[0]->script_or_module; } -VM::StoredModule* VM::get_stored_module(ScriptOrModule const&, DeprecatedString const& filename, DeprecatedString const&) +VM::StoredModule* VM::get_stored_module(ImportedModuleReferrer const&, DeprecatedString const& filename, DeprecatedString const&) { // Note the spec says: // Each time this operation is called with a specific referencingScriptOrModule, specifier pair as arguments @@ -801,7 +801,7 @@ ThrowCompletionOr VM::link_and_eval_module(Module& module) dbgln("Warning: Using multiple modules as entry point can lead to unexpected results"); m_loaded_modules.empend( - NonnullGCPtr(module), + ImportedModuleReferrer { NonnullGCPtr { verify_cast(module) } }, module.filename(), DeprecatedString {}, // Null type module, @@ -866,7 +866,7 @@ static DeprecatedString resolve_module_filename(StringView filename, StringView } // 16.2.1.7 HostResolveImportedModule ( referencingScriptOrModule, specifier ), https://tc39.es/ecma262/#sec-hostresolveimportedmodule -ThrowCompletionOr> VM::resolve_imported_module(ScriptOrModule referencing_script_or_module, ModuleRequest const& module_request) +ThrowCompletionOr> VM::resolve_imported_module(ImportedModuleReferrer referrer, ModuleRequest const& module_request) { // An implementation of HostResolveImportedModule 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. @@ -882,18 +882,25 @@ ThrowCompletionOr> VM::resolve_imported_module(ScriptOrModu // The actual mapping semantic 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 alphabetic case folding and expansion of relative and abbreviated path specifiers. - // We only allow "type" as a supported assertion so it is the only valid key that should ever arrive here. - VERIFY(module_request.assertions.is_empty() || (module_request.assertions.size() == 1 && module_request.assertions.first().key == "type")); - auto module_type = module_request.assertions.is_empty() ? DeprecatedString {} : module_request.assertions.first().value; + DeprecatedString module_type; + for (auto& attribute : module_request.attributes) { + if (attribute.key == "type"sv) { + module_type = attribute.value; + break; + } + } dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] module at {} has type {}", module_request.module_specifier, module_type); - StringView base_filename = referencing_script_or_module.visit( - [&](Empty) { + StringView const base_filename = referrer.visit( + [&](NonnullGCPtr const&) { return "."sv; }, - [&](auto& script_or_module) { - return script_or_module->filename(); + [&](NonnullGCPtr