mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-29 08:11:13 +00:00
LibWeb: Support SRI for import maps
See: - https://github.com/whatwg/html/commit/b2fdca1
This commit is contained in:
parent
124779a376
commit
bab086694a
Notes:
github-actions[bot]
2024-08-14 20:07:19 +00:00
Author: https://github.com/jamierocks Commit: https://github.com/LadybirdBrowser/ladybird/commit/bab086694a0 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1029 Reviewed-by: https://github.com/tcl3 ✅
|
@ -422,11 +422,11 @@ ErrorOr<void> initialize_main_thread_vm(HTML::EventLoop::Type type)
|
|||
// 3. Let referencingScript be null.
|
||||
Optional<HTML::Script&> 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<JS::NonnullGCPtr<JS::Script>>() || referrer.has<JS::NonnullGCPtr<JS::CyclicModule>>()) {
|
||||
|
@ -436,11 +436,10 @@ ErrorOr<void> 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.
|
||||
|
@ -463,13 +462,16 @@ ErrorOr<void> 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<HTML::FetchContext&>(*load_state);
|
||||
|
@ -537,7 +539,7 @@ ErrorOr<void> 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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<ScriptFetchOptions> 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<Window>(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<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<Window>(settings_object.global_object()));
|
||||
|
||||
// 2. Let map be settingsObject's global object's import map.
|
||||
auto map = static_cast<Window const&>(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<void> fetch_classic_script(JS::NonnullGCPtr<HTMLScriptElement> 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)
|
||||
|
|
|
@ -86,7 +86,8 @@ ByteString module_type_from_module_request(JS::ModuleRequest const&);
|
|||
WebIDL::ExceptionOr<URL::URL> resolve_module_specifier(Optional<Script&> referring_script, ByteString const& specifier);
|
||||
WebIDL::ExceptionOr<Optional<URL::URL>> resolve_imports_match(ByteString const& normalized_specifier, Optional<URL::URL> as_url, ModuleSpecifierMap const&);
|
||||
Optional<URL::URL> resolve_url_like_module_specifier(ByteString const& specifier, URL::URL const& base_url);
|
||||
|
||||
WebIDL::ExceptionOr<ScriptFetchOptions> get_descendant_script_fetch_options(ScriptFetchOptions const& original_options, URL::URL const& url, EnvironmentSettingsObject& settings_object);
|
||||
WebIDL::ExceptionOr<String> resolve_a_module_integrity_metadata(URL::URL const& url, EnvironmentSettingsObject& settings_object);
|
||||
WebIDL::ExceptionOr<void> fetch_classic_script(JS::NonnullGCPtr<HTMLScriptElement>, URL::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete);
|
||||
WebIDL::ExceptionOr<void> fetch_classic_worker_script(URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook, OnFetchScriptComplete);
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<ClassicScript>> fetch_a_classic_worker_imported_script(URL::URL const&, HTML::EnvironmentSettingsObject&, PerformTheFetchHook = nullptr);
|
||||
|
@ -95,7 +96,7 @@ WebIDL::ExceptionOr<void> 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<ModuleLocationTuple> 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<ModuleLocationTuple> 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);
|
||||
|
|
|
@ -58,9 +58,24 @@ WebIDL::ExceptionOr<ImportMap> 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<ImportMap> 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<HashMap<URL::URL, ModuleSpecifierMap>> sort_and_normalise_sc
|
|||
return normalised;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-module-integrity-map
|
||||
WebIDL::ExceptionOr<ModuleIntegrityMap> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
namespace Web::HTML {
|
||||
|
||||
using ModuleSpecifierMap = HashMap<ByteString, Optional<URL::URL>>;
|
||||
using ModuleIntegrityMap = HashMap<URL::URL, ByteString>;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#import-map
|
||||
class ImportMap {
|
||||
|
@ -28,14 +29,20 @@ public:
|
|||
HashMap<URL::URL, ModuleSpecifierMap>& scopes() { return m_scopes; }
|
||||
void set_scopes(HashMap<URL::URL, ModuleSpecifierMap> 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<URL::URL, ModuleSpecifierMap> m_scopes;
|
||||
ModuleIntegrityMap m_integrity;
|
||||
};
|
||||
|
||||
WebIDL::ExceptionOr<ImportMap> parse_import_map_string(JS::Realm& realm, ByteString const& input, URL::URL base_url);
|
||||
WebIDL::ExceptionOr<Optional<DeprecatedFlyString>> normalise_specifier_key(JS::Realm& realm, DeprecatedFlyString specifier_key, URL::URL base_url);
|
||||
WebIDL::ExceptionOr<ModuleSpecifierMap> sort_and_normalise_module_specifier_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url);
|
||||
WebIDL::ExceptionOr<HashMap<URL::URL, ModuleSpecifierMap>> sort_and_normalise_scopes(JS::Realm& realm, JS::Object& original_map, URL::URL base_url);
|
||||
WebIDL::ExceptionOr<ModuleIntegrityMap> normalize_module_integrity_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url);
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue