diff --git a/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Libraries/LibWeb/Bindings/MainThreadVM.cpp index b51e961fa6a..dd374d493a7 100644 --- a/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -532,14 +532,11 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) } } - // 8. Disallow further import maps given moduleMapRealm. - HTML::disallow_further_import_maps(*module_map_realm); - - // 9. Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]], + // 8. Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]], // catching any exceptions. If they throw an exception, let resolutionError be the thrown exception. auto url = HTML::resolve_module_specifier(referencing_script, module_request.module_specifier); - // 10. If the previous step threw an exception, then: + // 9. If the previous step threw an exception, then: if (url.is_exception()) { // 1. Let completion be Completion Record { [[Type]]: throw, [[Value]]: resolutionError, [[Target]]: empty }. auto completion = exception_to_throw_completion(main_thread_vm(), url.exception()); @@ -552,19 +549,19 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) return; } - // 11. Let settingsObject be moduleMapRealm's principal realm's settings object. + // 10. Let settingsObject be moduleMapRealm's principal realm's settings object. auto& settings_object = HTML::principal_realm_settings_object(HTML::principal_realm(*module_map_realm)); - // 12. Let fetchOptions be the result of getting the descendant script fetch options given originalFetchOptions, url, and settingsObject. + // 11. Let fetchOptions be the result of getting the descendant script fetch options given originalFetchOptions, url, and settingsObject. auto fetch_options = HTML::get_descendant_script_fetch_options(original_fetch_options, url.value(), settings_object); - // 13. Let destination be "script". + // 12. Let destination be "script". auto destination = Fetch::Infrastructure::Request::Destination::Script; - // 14. Let fetchClient be moduleMapRealm's principal realm's settings object. + // 13. Let fetchClient be moduleMapRealm's principal realm's settings object. GC::Ref fetch_client { HTML::principal_realm_settings_object(HTML::principal_realm(*module_map_realm)) }; - // 14. If loadState is not undefined, then: + // 15. If loadState is not undefined, then: HTML::PerformTheFetchHook perform_fetch; if (load_state) { auto& fetch_context = static_cast(*load_state); @@ -633,7 +630,7 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) vm.pop_execution_context(); }); - // 15. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, moduleMapRealm, fetchReferrer, + // 16. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, moduleMapRealm, fetchReferrer, // moduleRequest, and onSingleFetchComplete as defined below. // If loadState is not undefined and loadState.[[PerformFetch]] is not null, pass loadState.[[PerformFetch]] along as well. HTML::fetch_single_imported_module_script(*module_map_realm, url.release_value(), *fetch_client, destination, fetch_options, *module_map_realm, fetch_referrer, module_request, perform_fetch, on_single_fetch_complete); diff --git a/Libraries/LibWeb/HTML/HTMLScriptElement.cpp b/Libraries/LibWeb/HTML/HTMLScriptElement.cpp index 629e2b8b050..da90594c34c 100644 --- a/Libraries/LibWeb/HTML/HTMLScriptElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLScriptElement.cpp @@ -460,25 +460,10 @@ void HTMLScriptElement::prepare_script() } // -> "importmap" else if (m_script_type == ScriptType::ImportMap) { - // FIXME: need to check if relevant global object is a Window - is this correct? - auto& global = relevant_global_object(*this); - - // 1. If el's relevant global object's import maps allowed is false, then queue an element task on the DOM manipulation task source given el to fire an event named error at el, and return. - if (is(global) && !verify_cast(global).import_maps_allowed()) { - queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] { - dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error)); - }); - return; - } - - // 2. Set el's relevant global object's import maps allowed to false. - if (is(global)) - verify_cast(global).set_import_maps_allowed(false); - - // 3. Let result be the result of creating an import map parse result given source text and base URL. + // 1. Let result be the result of creating an import map parse result given source text and base URL. auto result = ImportMapParseResult::create(realm(), source_text.to_byte_string(), base_url); - // 4. Mark as ready el given result. + // 2. Mark as ready el given result. mark_as_ready(Result(move(result))); } } diff --git a/Libraries/LibWeb/HTML/Scripting/Environments.cpp b/Libraries/LibWeb/HTML/Scripting/Environments.cpp index bedb963b21d..1379f1d6070 100644 --- a/Libraries/LibWeb/HTML/Scripting/Environments.cpp +++ b/Libraries/LibWeb/HTML/Scripting/Environments.cpp @@ -279,9 +279,8 @@ bool module_type_allowed(JS::Realm const&, StringView module_type) return true; } -// https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps -// https://whatpr.org/html/9893/webappapis.html#disallow-further-import-maps -void disallow_further_import_maps(JS::Realm& realm) +// https://html.spec.whatwg.org/multipage/webappapis.html#add-module-to-resolved-module-set +void add_module_to_resolved_module_set(JS::Realm& realm, String const& serialized_base_url, String const& normalized_specifier, Optional const& as_url) { // 1. Let global be realm's global object. auto& global = realm.global_object(); @@ -290,8 +289,18 @@ void disallow_further_import_maps(JS::Realm& realm) if (!is(global)) return; - // 3. Set global's import maps allowed to false. - verify_cast(global).set_import_maps_allowed(false); + // 3. Let record be a new specifier resolution record, with serialized base URL set to serializedBaseURL, + // specifier set to normalizedSpecifier, and specifier as a URL set to asURL. + // + // NOTE: We set 'specifier as a URL set to asURL' as a bool to simplify logic when merging import maps. + SpecifierResolution resolution { + .serialized_base_url = serialized_base_url, + .specifier = normalized_specifier, + .specifier_is_null_or_url_like_that_is_special = !as_url.has_value() || as_url->is_special(), + }; + + // 4. Append record to global's resolved module set. + return verify_cast(global).append_resolved_module(move(resolution)); } // https://whatpr.org/html/9893/webappapis.html#concept-realm-module-map diff --git a/Libraries/LibWeb/HTML/Scripting/Environments.h b/Libraries/LibWeb/HTML/Scripting/Environments.h index 66e90db5c4f..118cbfeba49 100644 --- a/Libraries/LibWeb/HTML/Scripting/Environments.h +++ b/Libraries/LibWeb/HTML/Scripting/Environments.h @@ -139,7 +139,8 @@ void prepare_to_run_callback(JS::Realm&); void clean_up_after_running_callback(JS::Realm const&); ModuleMap& module_map_of_realm(JS::Realm&); bool module_type_allowed(JS::Realm const&, StringView module_type); -void disallow_further_import_maps(JS::Realm&); + +void add_module_to_resolved_module_set(JS::Realm&, String const& serialized_base_url, String const& normalized_specifier, Optional const& as_url); EnvironmentSettingsObject& incumbent_settings_object(); JS::Realm& incumbent_realm(); diff --git a/Libraries/LibWeb/HTML/Scripting/Fetching.cpp b/Libraries/LibWeb/HTML/Scripting/Fetching.cpp index 3658e0d8efb..deecf0de04b 100644 --- a/Libraries/LibWeb/HTML/Scripting/Fetching.cpp +++ b/Libraries/LibWeb/HTML/Scripting/Fetching.cpp @@ -120,8 +120,8 @@ WebIDL::ExceptionOr resolve_module_specifier(Optional referri if (is(realm->global_object())) import_map = verify_cast(realm->global_object()).import_map(); - // 6. Let baseURLString be baseURL, serialized. - auto base_url_string = base_url->serialize(); + // 6. Let serializedBaseURL be baseURL, serialized. + auto serialized_base_url = base_url->serialize(); // 7. Let asURL be the result of resolving a URL-like module specifier given specifier and baseURL. auto as_url = resolve_url_like_module_specifier(specifier, *base_url); @@ -129,37 +129,49 @@ WebIDL::ExceptionOr resolve_module_specifier(Optional referri // 8. Let normalizedSpecifier be the serialization of asURL, if asURL is non-null; otherwise, specifier. auto normalized_specifier = as_url.has_value() ? as_url->serialize().to_byte_string() : specifier; - // 9. For each scopePrefix → scopeImports of importMap's scopes: + // 9. Let result be a URL-or-null, initially null. + Optional result; + + // 10. For each scopePrefix → scopeImports of importMap's scopes: for (auto const& entry : import_map.scopes()) { // FIXME: Clarify if the serialization steps need to be run here. The steps below assume // scopePrefix to be a string. auto const& scope_prefix = entry.key.serialize(); auto const& scope_imports = entry.value; - // 1. If scopePrefix is baseURLString, or if scopePrefix ends with U+002F (/) and scopePrefix is a code unit prefix of baseURLString, then: - if (scope_prefix == base_url_string || (scope_prefix.ends_with('/') && Infra::is_code_unit_prefix(scope_prefix, base_url_string))) { + // 1. If scopePrefix is serializedBaseURL, or if scopePrefix ends with U+002F (/) and scopePrefix is a code unit prefix of serializedBaseURL, then: + if (scope_prefix == serialized_base_url || (scope_prefix.ends_with('/') && Infra::is_code_unit_prefix(scope_prefix, serialized_base_url))) { // 1. Let scopeImportsMatch be the result of resolving an imports match given normalizedSpecifier, asURL, and scopeImports. auto scope_imports_match = TRY(resolve_imports_match(normalized_specifier, as_url, scope_imports)); - // 2. If scopeImportsMatch is not null, then return scopeImportsMatch. - if (scope_imports_match.has_value()) - return scope_imports_match.release_value(); + // 2. If scopeImportsMatch is not null, then set result to scopeImportsMatch, and break. + if (scope_imports_match.has_value()) { + result = scope_imports_match.release_value(); + break; + } } } - // 10. Let topLevelImportsMatch be the result of resolving an imports match given normalizedSpecifier, asURL, and importMap's imports. - auto top_level_imports_match = TRY(resolve_imports_match(normalized_specifier, as_url, import_map.imports())); + // 11. If result is null, set result be the result of resolving an imports match given normalizedSpecifier, asURL, and importMap's imports. + if (!result.has_value()) + result = TRY(resolve_imports_match(normalized_specifier, as_url, import_map.imports())); - // 11. If topLevelImportsMatch is not null, then return topLevelImportsMatch. - if (top_level_imports_match.has_value()) - return top_level_imports_match.release_value(); + // 12. If result is null, set it to asURL. + // Spec-Note: By this point, if result was null, specifier wasn't remapped to anything by importMap, but it might have been able to be turned into a URL. + if (!result.has_value()) + result = as_url; - // 12. If asURL is not null, then return asURL. - if (as_url.has_value()) - return as_url.release_value(); + // 13. If result is not null, then: + if (result.has_value()) { + // 1. Add module to resolved module set given realm, serializedBaseURL, normalizedSpecifier, and asURL. + add_module_to_resolved_module_set(*realm, serialized_base_url, MUST(String::from_byte_string(normalized_specifier)), as_url); - // 13. Throw a TypeError indicating that specifier was a bare specifier, but was not remapped to anything by importMap. - return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Failed to resolve non relative module specifier '{}' from an import map.", specifier).release_value_but_fixme_should_propagate_errors() }; + // 2. Return result. + return result.release_value(); + } + + // 14. Throw a TypeError indicating that specifier was a bare specifier, but was not remapped to anything by importMap. + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Failed to resolve non relative module specifier '{}' from an import map.", specifier)) }; } // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-an-imports-match @@ -292,34 +304,27 @@ ScriptFetchOptions get_descendant_script_fetch_options(ScriptFetchOptions const& // 1. Let newOptions be a copy of originalOptions. auto new_options = original_options; - // 2. Let integrity be the empty string. - String integrity; + // 2. Let integrity be the result of resolving a module integrity metadata with url and settingsObject. + String integrity = resolve_a_module_integrity_metadata(url, settings_object); - // 3. If settingsObject's global object is a Window object, then set integrity to the result of resolving a module integrity metadata with url and settingsObject. - if (is(settings_object.global_object())) - integrity = resolve_a_module_integrity_metadata(url, settings_object); - - // 4. Set newOptions's integrity metadata to integrity. + // 3. Set newOptions's integrity metadata to integrity. new_options.integrity_metadata = integrity; - // 5. Set newOptions's fetch priority to "auto". + // 4. Set newOptions's fetch priority to "auto". new_options.fetch_priority = Fetch::Infrastructure::Request::Priority::Auto; - // 6. Return newOptions. + // 5. Return newOptions. return new_options; } // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-module-integrity-metadata String resolve_a_module_integrity_metadata(const URL::URL& url, EnvironmentSettingsObject& settings_object) { - // 1. Assert: settingsObject's global object is a Window object. - VERIFY(is(settings_object.global_object())); + // 1. Let map be settingsObject's global object's import map. + auto map = verify_cast(settings_object.global_object()).import_map(); - // 2. Let map be settingsObject's global object's import map. - auto map = static_cast(settings_object.global_object()).import_map(); - - // 3. If map's integrity[url] does not exist, then return the empty string. - // 4. Return map's integrity[url]. + // 2. If map's integrity[url] does not exist, then return the empty string. + // 3. Return map's integrity[url]. return MUST(String::from_byte_string(map.integrity().get(url).value_or(""))); } @@ -860,9 +865,6 @@ void fetch_single_module_script(JS::Realm& realm, // https://whatpr.org/html/9893/webappapis.html#fetch-a-module-script-tree void fetch_external_module_script_graph(JS::Realm& realm, URL::URL const& url, EnvironmentSettingsObject& settings_object, ScriptFetchOptions const& options, OnFetchScriptComplete on_complete) { - // 1. Disallow further import maps given settingsObject's realm. - disallow_further_import_maps(settings_object.realm()); - auto steps = create_on_fetch_script_complete(realm.heap(), [&realm, &settings_object, on_complete, url](auto result) mutable { // 1. If result is null, run onComplete given null, and abort these steps. if (!result) { @@ -875,27 +877,17 @@ void fetch_external_module_script_graph(JS::Realm& realm, URL::URL const& url, E fetch_descendants_of_and_link_a_module_script(realm, module_script, settings_object, Fetch::Infrastructure::Request::Destination::Script, nullptr, on_complete); }); - // 2. Fetch a single module script given url, settingsObject, "script", options, settingsObject's realm, "client", true, and with the following steps given result: + // 1. Fetch a single module script given url, settingsObject, "script", options, settingsObject's realm, "client", true, and with the following steps given result: fetch_single_module_script(realm, url, settings_object, Fetch::Infrastructure::Request::Destination::Script, options, settings_object.realm(), Web::Fetch::Infrastructure::Request::Referrer::Client, {}, TopLevelModule::Yes, nullptr, steps); } // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph -// https://whatpr.org/html/9893/webappapis.html#fetch-an-inline-module-script-graph void fetch_inline_module_script_graph(JS::Realm& realm, ByteString const& filename, ByteString const& source_text, URL::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete) { - // 1. Disallow further import maps given settingsObject's realm. - disallow_further_import_maps(settings_object.realm()); - - // 2. Let script be the result of creating a JavaScript module script using sourceText, settingsObject's realm, baseURL, and options. + // 1. Let script be the result of creating a JavaScript module script using sourceText, settingsObject's realm, baseURL, and options. auto script = JavaScriptModuleScript::create(filename, source_text.view(), settings_object.realm(), base_url).release_value_but_fixme_should_propagate_errors(); - // 3. If script is null, run onComplete given null, and return. - if (!script) { - on_complete->function()(nullptr); - return; - } - - // 5. Fetch the descendants of and link script, given settingsObject, "script", and onComplete. + // 2. Fetch the descendants of and link script, given settingsObject, "script", and onComplete. fetch_descendants_of_and_link_a_module_script(realm, *script, settings_object, Fetch::Infrastructure::Request::Destination::Script, nullptr, on_complete); } diff --git a/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp b/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp index 46c8c32cf30..cc609e3e12f 100644 --- a/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp +++ b/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Jamie Mansfield + * Copyright (c) 2024, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,7 +11,9 @@ #include #include #include +#include #include +#include namespace Web::HTML { @@ -263,4 +266,134 @@ WebIDL::ExceptionOr normalize_module_integrity_map(JS::Realm return normalised; } +// https://html.spec.whatwg.org/multipage/webappapis.html#merge-module-specifier-maps +static ModuleSpecifierMap merge_module_specifier_maps(JS::Realm& realm, ModuleSpecifierMap const& new_map, ModuleSpecifierMap const& old_map) +{ + // 1. Let mergedMap be a deep copy of oldMap. + ModuleSpecifierMap merged_map = old_map; + + // 2. For each specifier → url of newMap: + for (auto const& [specifier, url] : new_map) { + // 1. If specifier exists in oldMap, then: + if (old_map.contains(specifier)) { + // 1. The user agent may report a warning to the console indicating the ignored rule. They may choose to + // avoid reporting if the rule is identical to an existing one. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + MUST(String::formatted("An import map rule for specifier '{}' was ignored as one was already present in the existing import map", specifier))); + + // 2. Continue. + continue; + } + + // 2. Set mergedMap[specifier] to url. + merged_map.set(specifier, url); + } + + // 3. Return mergedMap. + return merged_map; +} + +// https://html.spec.whatwg.org/multipage/webappapis.html#merge-existing-and-new-import-maps +void merge_existing_and_new_import_maps(Window& global, ImportMap& new_import_map) +{ + auto& realm = global.realm(); + + // 1. Let newImportMapScopes be a deep copy of newImportMap's scopes. + auto new_import_map_scopes = new_import_map.scopes(); + + // Spec-Note: We're mutating these copies and removing items from them when they are used to ignore scope-specific + // rules. This is true for newImportMapScopes, as well as to newImportMapImports below. + + // 2. Let oldImportMap be global's import map. + auto& old_import_map = global.import_map(); + + // 3. Let newImportMapImports be a deep copy of newImportMap's imports. + auto new_import_map_imports = new_import_map.imports(); + + // 4. For each scopePrefix → scopeImports of newImportMapScopes: + for (auto& [scope_prefix, scope_imports] : new_import_map_scopes) { + // 1. For each record of global's resolved module set: + for (auto const& record : global.resolved_module_set()) { + // 1. If scopePrefix is record's serialized base URL, or if scopePrefix ends with U+002F (/) and scopePrefix is a code unit prefix of record's serialized base URL, then: + if (scope_prefix == record.serialized_base_url || (scope_prefix.to_string().ends_with('/') && record.serialized_base_url.has_value() && Infra::is_code_unit_prefix(scope_prefix.to_string(), *record.serialized_base_url))) { + // 1. For each specifierKey → resolutionResult of scopeImports: + scope_imports.remove_all_matching([&](ByteString const& specifier_key, Optional const&) { + // 1. If specifierKey is record's specifier, or if all of the following conditions are true: + // * specifierKey ends with U+002F (/); + // * specifierKey is a code unit prefix of record's specifier; + // * either record's specifier as a URL is null or is special, + // then: + if (specifier_key.view() == record.specifier + || (specifier_key.ends_with('/') + && Infra::is_code_unit_prefix(specifier_key, record.specifier) + && record.specifier_is_null_or_url_like_that_is_special)) { + // 1. The user agent may report a warning to the console indicating the ignored rule. They + // may choose to avoid reporting if the rule is identical to an existing one. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + MUST(String::formatted("An import map rule for specifier '{}' was ignored as one was already present in the existing import map", specifier_key))); + + // 2. Remove scopeImports[specifierKey]. + return true; + } + + return false; + }); + } + } + + // 2. If scopePrefix exists in oldImportMap's scopes, then set oldImportMap's scopes[scopePrefix] to the result + // of merging module specifier maps, given scopeImports and oldImportMap's scopes[scopePrefix]. + if (auto it = old_import_map.scopes().find(scope_prefix); it != old_import_map.scopes().end()) { + it->value = merge_module_specifier_maps(realm, scope_imports, it->value); + } + // 3. Otherwise, set oldImportMap's scopes[scopePrefix] to scopeImports. + else { + old_import_map.scopes().set(scope_prefix, scope_imports); + } + } + + // 5. For each url → integrity of newImportMap's integrity: + for (auto const& [url, integrity] : new_import_map.integrity()) { + // 1. If url exists in oldImportMap's integrity, then: + if (old_import_map.integrity().contains(url)) { + // 1. The user agent may report a warning to the console indicating the ignored rule. They may choose to + // avoid reporting if the rule is identical to an existing one. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + MUST(String::formatted("An import map integrity rule for url '{}' was ignored as one was already present in the existing import map", url))); + + // 2. Continue. + continue; + } + + // 2. Set oldImportMap's integrity[url] to integrity. + old_import_map.integrity().set(url, integrity); + } + + // 6. For each record of global's resolved module set: + for (auto const& record : global.resolved_module_set()) { + // 1. For each specifier → url of newImportMapImports: + new_import_map_imports.remove_all_matching([&](ByteString const& specifier, Optional const&) { + // 1. If specifier starts with record's specifier, then: + if (specifier.starts_with(record.specifier)) { + // 1. The user agent may report a warning to the console indicating the ignored rule. They may choose to + // avoid reporting if the rule is identical to an existing one. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + MUST(String::formatted("An import map rule for specifier '{}' was ignored as one was already present in the existing import map", specifier))); + + // 2. Remove newImportMapImports[specifier]. + return true; + } + + return false; + }); + } + + // 7. Set oldImportMap's imports to the result of merge module specifier maps, given newImportMapImports and oldImportMap's imports. + old_import_map.set_imports(merge_module_specifier_maps(realm, new_import_map_imports, old_import_map.imports())); +} + } diff --git a/Libraries/LibWeb/HTML/Scripting/ImportMap.h b/Libraries/LibWeb/HTML/Scripting/ImportMap.h index afd99ce5d6f..77a133edbfd 100644 --- a/Libraries/LibWeb/HTML/Scripting/ImportMap.h +++ b/Libraries/LibWeb/HTML/Scripting/ImportMap.h @@ -44,5 +44,6 @@ Optional normalise_specifier_key(JS::Realm& realm, Deprecat WebIDL::ExceptionOr sort_and_normalise_module_specifier_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url); WebIDL::ExceptionOr> sort_and_normalise_scopes(JS::Realm& realm, JS::Object& original_map, URL::URL base_url); WebIDL::ExceptionOr normalize_module_integrity_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url); +void merge_existing_and_new_import_maps(Window&, ImportMap&); } diff --git a/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp b/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp index 46c9815ddb0..f64a7c96d32 100644 --- a/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp +++ b/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp @@ -73,12 +73,9 @@ void ImportMapParseResult::register_import_map(Window& global) return; } - // 2. Assert: global's import map is an empty import map. - VERIFY(global.import_map().imports().is_empty() && global.import_map().scopes().is_empty()); - - // 3. Set global's import map to result's import map. + // 2. Merge existing and new import maps, given global and result's import map. VERIFY(m_import_map.has_value()); - global.set_import_map(m_import_map.value()); + merge_existing_and_new_import_maps(global, m_import_map.value()); } } diff --git a/Libraries/LibWeb/HTML/UniversalGlobalScope.h b/Libraries/LibWeb/HTML/UniversalGlobalScope.h index 26ff0e38aee..df860fe1744 100644 --- a/Libraries/LibWeb/HTML/UniversalGlobalScope.h +++ b/Libraries/LibWeb/HTML/UniversalGlobalScope.h @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Web::HTML { diff --git a/Libraries/LibWeb/HTML/Window.h b/Libraries/LibWeb/HTML/Window.h index 0e21066b602..6e11baca23f 100644 --- a/Libraries/LibWeb/HTML/Window.h +++ b/Libraries/LibWeb/HTML/Window.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,25 @@ struct WindowPostMessageOptions : public StructuredSerializeOptions { String target_origin { "/"_string }; }; +// https://html.spec.whatwg.org/multipage/webappapis.html#specifier-resolution-record +// A specifier resolution record is a struct. It has the following items: +struct SpecifierResolution { + // A serialized base URL + // A string-or-null that represents the base URL of the specifier, when one exists. + Optional serialized_base_url; + + // A specifier + // A string representing the specifier. + String specifier; + + // A specifier as a URL + // A URL-or-null that represents the URL in case of a URL-like module specifier. + // + // Spec-Note: Implementations can replace specifier as a URL with a boolean that indicates + // that the specifier is either bare or URL-like that is special. + bool specifier_is_null_or_url_like_that_is_special { false }; +}; + class Window final : public DOM::EventTarget , public GlobalEventHandlers @@ -96,11 +114,12 @@ public: GC::Ptr navigable() const; + ImportMap& import_map() { return m_import_map; } ImportMap const& import_map() const { return m_import_map; } void set_import_map(ImportMap const& import_map) { m_import_map = import_map; } - bool import_maps_allowed() const { return m_import_maps_allowed; } - void set_import_maps_allowed(bool import_maps_allowed) { m_import_maps_allowed = import_maps_allowed; } + void append_resolved_module(SpecifierResolution resolution) { m_resolved_module_set.append(move(resolution)); } + Vector const& resolved_module_set() const { return m_resolved_module_set; } WebIDL::ExceptionOr> window_open_steps(StringView url, StringView target, StringView features); @@ -269,11 +288,18 @@ private: GC::Ptr m_current_event; - // https://html.spec.whatwg.org/multipage/webappapis.html#concept-window-import-map + // https://html.spec.whatwg.org/multipage/webappapis.html#concept-global-import-map + // A global object has an import map, initially an empty import map. ImportMap m_import_map; - // https://html.spec.whatwg.org/multipage/webappapis.html#import-maps-allowed - bool m_import_maps_allowed { true }; + // https://html.spec.whatwg.org/multipage/webappapis.html#resolved-module-set + // A global object has a resolved module set, a set of specifier resolution records, initially empty. + // + // Spec-Note: The resolved module set ensures that module specifier resolution returns the same result when called + // multiple times with the same (referrer, specifier) pair. It does that by ensuring that import map rules + // that impact the specifier in its referrer's scope cannot be defined after its initial resolution. For + // now, only Window global objects have their module set data structures modified from the initial empty one. + Vector m_resolved_module_set; GC::Ptr m_screen; GC::Ptr m_navigator; diff --git a/Tests/LibWeb/Text/expected/HTML/import-maps.txt b/Tests/LibWeb/Text/expected/HTML/import-maps.txt index f8b0fc5d6a1..a47f48aaf8b 100644 --- a/Tests/LibWeb/Text/expected/HTML/import-maps.txt +++ b/Tests/LibWeb/Text/expected/HTML/import-maps.txt @@ -1 +1 @@ -hello, friends! +(1) hello, friends! diff --git a/Tests/LibWeb/Text/expected/HTML/multiple-import-maps-confict.txt b/Tests/LibWeb/Text/expected/HTML/multiple-import-maps-confict.txt new file mode 100644 index 00000000000..a47f48aaf8b --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/multiple-import-maps-confict.txt @@ -0,0 +1 @@ +(1) hello, friends! diff --git a/Tests/LibWeb/Text/expected/HTML/multiple-import-maps.txt b/Tests/LibWeb/Text/expected/HTML/multiple-import-maps.txt new file mode 100644 index 00000000000..fd2cdd3268c --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/multiple-import-maps.txt @@ -0,0 +1,2 @@ +(1) hello, friends! +(2) hello, friends! diff --git a/Tests/LibWeb/Text/input/HTML/import-maps-1.js b/Tests/LibWeb/Text/input/HTML/import-maps-1.js new file mode 100644 index 00000000000..c71f92d83bb --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/import-maps-1.js @@ -0,0 +1,3 @@ +export function main() { + println("(1) hello, friends!"); +} diff --git a/Tests/LibWeb/Text/input/HTML/import-maps-2.js b/Tests/LibWeb/Text/input/HTML/import-maps-2.js new file mode 100644 index 00000000000..842ddf9e2b1 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/import-maps-2.js @@ -0,0 +1,3 @@ +export function main() { + println("(2) hello, friends!"); +} diff --git a/Tests/LibWeb/Text/input/HTML/import-maps.html b/Tests/LibWeb/Text/input/HTML/import-maps.html index 53b77438140..9298242abbd 100644 --- a/Tests/LibWeb/Text/input/HTML/import-maps.html +++ b/Tests/LibWeb/Text/input/HTML/import-maps.html @@ -3,7 +3,7 @@ diff --git a/Tests/LibWeb/Text/input/HTML/import-maps.js b/Tests/LibWeb/Text/input/HTML/import-maps.js deleted file mode 100644 index 116b61b85fb..00000000000 --- a/Tests/LibWeb/Text/input/HTML/import-maps.js +++ /dev/null @@ -1,3 +0,0 @@ -export function main() { - println("hello, friends!"); -} diff --git a/Tests/LibWeb/Text/input/HTML/multiple-import-maps-confict.html b/Tests/LibWeb/Text/input/HTML/multiple-import-maps-confict.html new file mode 100644 index 00000000000..98be8175e42 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/multiple-import-maps-confict.html @@ -0,0 +1,22 @@ + + + + + diff --git a/Tests/LibWeb/Text/input/HTML/multiple-import-maps.html b/Tests/LibWeb/Text/input/HTML/multiple-import-maps.html new file mode 100644 index 00000000000..1aca147be96 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/multiple-import-maps.html @@ -0,0 +1,24 @@ + + + + +