diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp index af0884ff64afa2..2ac53f5323998c 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -424,11 +424,11 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) // 3. Let referencingScript be null. Optional referencing_script; - // FIXME: 4. Let fetchOptions be the default classic script fetch options. - auto fetch_options = HTML::default_classic_script_fetch_options(); + // 4. Let originalFetchOptions be the default classic script fetch options. + auto original_fetch_options = HTML::default_classic_script_fetch_options(); // 5. Let fetchReferrer be "client". - auto fetch_referrer = Fetch::Infrastructure::Request::Referrer::Client; + Fetch::Infrastructure::Request::ReferrerType fetch_referrer = Fetch::Infrastructure::Request::Referrer::Client; // 6. If referrer is a Script Record or a Module Record, then: if (referrer.has>() || referrer.has>()) { @@ -438,11 +438,10 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) // 2. Set settingsObject to referencingScript's settings object. settings_object = referencing_script->settings_object(); - // FIXME: 3. Set fetchOptions to the new descendant script fetch options for referencingScript's fetch options. + // 3. Set fetchReferrer to referencingScript's base URL. + fetch_referrer = referencing_script->base_url(); - // FIXME: 4. Assert: fetchOptions is not null, as referencingScript is a classic script or a JavaScript module script. - - // FIXME: 5. Set fetchReferrer to referrer's base URL. + // FIXME: 4. Set originalFetchOptions to referencingScript's fetch options. } // 7. Disallow further import maps given settingsObject. @@ -465,13 +464,16 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) return; } - // 10. Let destination be "script". + // 10. Let fetchOptions be the result of getting the descendant script fetch options given originalFetchOptions, url, and settingsObject. + auto fetch_options = MUST(HTML::get_descendant_script_fetch_options(original_fetch_options, url.value(), *settings_object)); + + // 11. Let destination be "script". auto destination = Fetch::Infrastructure::Request::Destination::Script; - // 11. Let fetchClient be settingsObject. + // 12. Let fetchClient be settingsObject. JS::NonnullGCPtr fetch_client { *settings_object }; - // 12. If loadState is not undefined, then: + // 13. If loadState is not undefined, then: HTML::PerformTheFetchHook perform_fetch; if (load_state) { auto& fetch_context = static_cast(*load_state); @@ -539,7 +541,7 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) vm.pop_execution_context(); }); - // 13. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, settingsObject, fetchReferrer, + // 14. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, settingsObject, 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(realm, url.release_value(), *fetch_client, destination, fetch_options, *settings_object, fetch_referrer, module_request, perform_fetch, on_single_fetch_complete); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp index a6de0683fe099e..936c4578ebb4e3 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp @@ -416,6 +416,10 @@ void HTMLScriptElement::prepare_script() } // -> "module" else if (m_script_type == ScriptType::Module) { + // If el does not have an integrity attribute, then set options's integrity metadata to the result of resolving a module integrity metadata with url and settings object. + if (!has_attribute(HTML::AttributeNames::integrity)) + options.integrity_metadata = MUST(resolve_a_module_integrity_metadata(url, settings_object)); + // Fetch an external module script graph given url, settings object, options, and onComplete. fetch_external_module_script_graph(realm(), url, settings_object, options, on_complete); } diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp index 8ffadda10ecdcb..bd5f8d1c3af093 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp @@ -280,6 +280,43 @@ static void set_up_module_script_request(Fetch::Infrastructure::Request& request request.set_priority(options.fetch_priority); } +// https://html.spec.whatwg.org/multipage/webappapis.html#get-the-descendant-script-fetch-options +WebIDL::ExceptionOr get_descendant_script_fetch_options(ScriptFetchOptions const& original_options, URL::URL const& url, EnvironmentSettingsObject& settings_object) +{ + // 1. Let newOptions be a copy of originalOptions. + auto new_options = original_options; + + // 2. Let integrity be the empty string. + String integrity; + + // 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 = TRY(resolve_a_module_integrity_metadata(url, settings_object)); + + // 4. Set newOptions's integrity metadata to integrity. + new_options.integrity_metadata = integrity; + + // 5. Set newOptions's fetch priority to "auto". + new_options.fetch_priority = Fetch::Infrastructure::Request::Priority::Auto; + + // 6. Return newOptions. + return new_options; +} + +// https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-module-integrity-metadata +WebIDL::ExceptionOr 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())); + + // 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]. + return MUST(String::from_byte_string(map.integrity().get(url).value_or(""))); +} + // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script WebIDL::ExceptionOr fetch_classic_script(JS::NonnullGCPtr element, URL::URL const& url, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete) { @@ -858,7 +895,7 @@ void fetch_single_imported_module_script(JS::Realm& realm, Fetch::Infrastructure::Request::Destination destination, ScriptFetchOptions const& options, EnvironmentSettingsObject& settings_object, - Fetch::Infrastructure::Request::Referrer referrer, + Fetch::Infrastructure::Request::ReferrerType referrer, JS::ModuleRequest const& module_request, PerformTheFetchHook perform_fetch, OnFetchScriptComplete on_complete) diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h index a43cba57cfb69c..bec3d8ffbb4cb8 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h @@ -86,7 +86,8 @@ ByteString module_type_from_module_request(JS::ModuleRequest const&); WebIDL::ExceptionOr resolve_module_specifier(Optional referring_script, ByteString const& specifier); WebIDL::ExceptionOr> resolve_imports_match(ByteString const& normalized_specifier, Optional as_url, ModuleSpecifierMap const&); Optional resolve_url_like_module_specifier(ByteString const& specifier, URL::URL const& base_url); - +WebIDL::ExceptionOr get_descendant_script_fetch_options(ScriptFetchOptions const& original_options, URL::URL const& url, EnvironmentSettingsObject& settings_object); +WebIDL::ExceptionOr resolve_a_module_integrity_metadata(URL::URL const& url, EnvironmentSettingsObject& settings_object); WebIDL::ExceptionOr fetch_classic_script(JS::NonnullGCPtr, URL::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete); WebIDL::ExceptionOr fetch_classic_worker_script(URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook, OnFetchScriptComplete); WebIDL::ExceptionOr> fetch_a_classic_worker_imported_script(URL::URL const&, HTML::EnvironmentSettingsObject&, PerformTheFetchHook = nullptr); @@ -95,7 +96,7 @@ WebIDL::ExceptionOr fetch_worklet_module_worker_script_graph(URL::URL cons void fetch_internal_module_script_graph(JS::Realm&, JS::ModuleRequest const& module_request, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, Script& referring_script, HashTable const& visited_set, PerformTheFetchHook, OnFetchScriptComplete on_complete); void fetch_external_module_script_graph(JS::Realm&, URL::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions const&, OnFetchScriptComplete on_complete); void fetch_inline_module_script_graph(JS::Realm&, ByteString const& filename, ByteString const& source_text, URL::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete); -void fetch_single_imported_module_script(JS::Realm&, URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, EnvironmentSettingsObject& settings_object, Fetch::Infrastructure::Request::Referrer, JS::ModuleRequest const&, PerformTheFetchHook, OnFetchScriptComplete on_complete); +void fetch_single_imported_module_script(JS::Realm&, URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, EnvironmentSettingsObject& settings_object, Fetch::Infrastructure::Request::ReferrerType, JS::ModuleRequest const&, PerformTheFetchHook, OnFetchScriptComplete on_complete); void fetch_descendants_of_a_module_script(JS::Realm&, JavaScriptModuleScript& module_script, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination, HashTable visited_set, PerformTheFetchHook, OnFetchScriptComplete callback); void fetch_descendants_of_and_link_a_module_script(JS::Realm&, JavaScriptModuleScript&, EnvironmentSettingsObject&, Fetch::Infrastructure::Request::Destination, PerformTheFetchHook, OnFetchScriptComplete on_complete); diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp index 52caff18b513a2..003466d675f214 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp +++ b/Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp @@ -58,9 +58,24 @@ WebIDL::ExceptionOr parse_import_map_string(JS::Realm& realm, ByteStr sorted_and_normalised_scopes = TRY(sort_and_normalise_scopes(realm, scopes.as_object(), base_url)); } - // 7. If parsed's keys contains any items besides "imports" or "scopes", then the user agent should report a warning to the console indicating that an invalid top-level key was present in the import map. + // 7. Let normalizedIntegrity be an empty ordered map. + ModuleIntegrityMap normalised_integrity; + + // 8. If parsed["integrity"] exists, then: + if (TRY(parsed_object.has_property("integrity"))) { + auto integrity = TRY(parsed_object.get("integrity")); + + // 1. If parsed["integrity"] is not an ordered map, then throw a TypeError indicating that the value for the "integrity" top-level key needs to be a JSON object. + if (!integrity.is_object()) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The 'integrity' top-level value of an importmap needs to be a JSON object.").release_value_but_fixme_should_propagate_errors() }; + + // 2. Set normalizedIntegrity to the result of normalizing a module integrity map given parsed["integrity"] and baseURL. + normalised_integrity = TRY(normalize_module_integrity_map(realm, integrity.as_object(), base_url)); + } + + // 9. If parsed's keys contains any items besides "imports", "scopes", or "integrity", then the user agent should report a warning to the console indicating that an invalid top-level key was present in the import map. for (auto& key : parsed_object.shape().property_table().keys()) { - if (key.as_string().is_one_of("imports", "scopes")) + if (key.as_string().is_one_of("imports", "scopes", "integrity")) continue; auto& console = realm.intrinsics().console_object()->console(); @@ -68,10 +83,11 @@ WebIDL::ExceptionOr parse_import_map_string(JS::Realm& realm, ByteStr TRY_OR_THROW_OOM(realm.vm(), String::formatted("An invalid top-level key ({}) was present in the import map", key.as_string()))); } - // 8. Return an import map whose imports are sortedAndNormalizedImports and whose scopes are sortedAndNormalizedScopes. + // 10. Return an import map whose imports are sortedAndNormalizedImports, whose scopes are sortedAndNormalizedScopes, and whose integrity are normalizedIntegrity. ImportMap import_map; import_map.set_imports(sorted_and_normalised_imports); import_map.set_scopes(sorted_and_normalised_scopes); + import_map.set_integrity(normalised_integrity); return import_map; } @@ -209,4 +225,47 @@ WebIDL::ExceptionOr> sort_and_normalise_sc return normalised; } +// https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-module-integrity-map +WebIDL::ExceptionOr normalize_module_integrity_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url) +{ + // 1. Let normalized be an empty ordered map. + ModuleIntegrityMap normalised; + + // 2. For each key → value of originalMap: + for (auto& key : original_map.shape().property_table().keys()) { + auto value = TRY(original_map.get(key.as_string())); + + // 1. Let resolvedURL be the result of resolving a URL-like module specifier given key and baseURL. + auto resolved_url = resolve_url_like_module_specifier(key.as_string(), base_url); + + // 2. If resolvedURL is null, then: + if (!resolved_url.has_value()) { + // 1. The user agent may report a warning to the console indicating that the key failed to resolve. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + TRY_OR_THROW_OOM(realm.vm(), String::formatted("Failed to resolve key ({})", key.as_string()))); + + // 2. Continue. + continue; + } + + // 3. If value is not a string, then: + if (!value.is_string()) { + // 1. The user agent may report a warning to the console indicating that integrity metadata values need to be strings. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + TRY_OR_THROW_OOM(realm.vm(), String::formatted("Integrity metadata value for '{}' needs to be a string", key.as_string()))); + + // 2. Continue. + continue; + } + + // 4. Set normalized[resolvedURL] to value. + normalised.set(resolved_url.release_value(), value.as_string().byte_string()); + } + + // 3. Return normalized. + return normalised; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.h b/Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.h index d0326bf8efc536..aa667fae63a5d7 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.h +++ b/Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.h @@ -14,6 +14,7 @@ namespace Web::HTML { using ModuleSpecifierMap = HashMap>; +using ModuleIntegrityMap = HashMap; // https://html.spec.whatwg.org/multipage/webappapis.html#import-map class ImportMap { @@ -28,14 +29,20 @@ class ImportMap { HashMap& scopes() { return m_scopes; } void set_scopes(HashMap const& scopes) { m_scopes = scopes; } + ModuleIntegrityMap const& integrity() const { return m_integrity; } + ModuleIntegrityMap integrity() { return m_integrity; } + void set_integrity(ModuleIntegrityMap const& integrity) { m_integrity = integrity; } + private: ModuleSpecifierMap m_imports; HashMap m_scopes; + ModuleIntegrityMap m_integrity; }; WebIDL::ExceptionOr parse_import_map_string(JS::Realm& realm, ByteString const& input, URL::URL base_url); WebIDL::ExceptionOr> normalise_specifier_key(JS::Realm& realm, DeprecatedFlyString specifier_key, URL::URL base_url); 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); }