Forráskód Böngészése

LibWeb: Support "importmap" scripts

Jamie Mansfield 1 éve
szülő
commit
e487f70bbf

+ 1 - 0
Tests/LibWeb/Text/expected/HTML/HTMLScriptElement-supports-importmap.txt

@@ -0,0 +1 @@
+true

+ 1 - 0
Tests/LibWeb/Text/expected/HTML/import-maps.txt

@@ -0,0 +1 @@
+hello, friends!

+ 7 - 0
Tests/LibWeb/Text/input/HTML/HTMLScriptElement-supports-importmap.html

@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script src="../include.js"></script>
+<script>
+    test(() => {
+        println(HTMLScriptElement.supports("importmap"));
+    });
+</script>

+ 15 - 0
Tests/LibWeb/Text/input/HTML/import-maps.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script src="../include.js"></script>
+<script type="importmap">
+    {
+        "imports": {
+            "application": "./import-maps.js"
+        }
+    }
+</script>
+<script type="module">
+    import * as Application from 'application';
+    test(() => {
+        Application.main();
+    });
+</script>

+ 3 - 0
Tests/LibWeb/Text/input/HTML/import-maps.js

@@ -0,0 +1,3 @@
+export function main() {
+    println("hello, friends!");
+}

+ 2 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -399,6 +399,8 @@ set(SOURCES
     HTML/Scripting/EnvironmentSettingsSnapshot.cpp
     HTML/Scripting/ExceptionReporter.cpp
     HTML/Scripting/Fetching.cpp
+    HTML/Scripting/ImportMap.cpp
+    HTML/Scripting/ImportMapParseResult.cpp
     HTML/Scripting/ModuleMap.cpp
     HTML/Scripting/ModuleScript.cpp
     HTML/Scripting/Script.cpp

+ 24 - 7
Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp

@@ -17,6 +17,8 @@
 #include <LibWeb/HTML/HTMLScriptElement.h>
 #include <LibWeb/HTML/Scripting/ClassicScript.h>
 #include <LibWeb/HTML/Scripting/Fetching.h>
+#include <LibWeb/HTML/Scripting/ImportMapParseResult.h>
+#include <LibWeb/HTML/Window.h>
 #include <LibWeb/Infra/CharacterTypes.h>
 #include <LibWeb/Infra/Strings.h>
 #include <LibWeb/MimeSniff/MimeType.h>
@@ -127,9 +129,11 @@ void HTMLScriptElement::execute_script()
 
         // 2. Run the module script given by el's result.
         (void)verify_cast<JavaScriptModuleScript>(*m_result.get<JS::NonnullGCPtr<Script>>()).run();
-    } else if (m_script_type == ScriptType::ImportMap) {
-        // FIXME: 1. Register an import map given el's relevant global object and el's result.
-        dbgln("FIXME: HTMLScriptElement import map support");
+    }
+    // -> "importmap"
+    else if (m_script_type == ScriptType::ImportMap) {
+        // 1. Register an import map given el's relevant global object and el's result.
+        m_result.get<JS::NonnullGCPtr<ImportMapParseResult>>()->register_import_map(verify_cast<Window>(relevant_global_object(*this)));
     }
 
     // 7. Decrement the ignore-destructive-writes counter of document, if it was incremented in the earlier step.
@@ -433,13 +437,26 @@ void HTMLScriptElement::prepare_script()
         }
         // -> "importmap"
         else if (m_script_type == ScriptType::ImportMap) {
-            // FIXME: 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.
+            // 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<Window>(global) && !verify_cast<Window>(global).import_maps_allowed()) {
+                queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] {
+                    dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error));
+                });
+                return;
+            }
 
-            // FIXME: 2. Set el's relevant global object's import maps allowed to false.
+            // 2. Set el's relevant global object's import maps allowed to false.
+            if (is<Window>(global))
+                verify_cast<Window>(global).set_import_maps_allowed(false);
 
-            // FIXME: 3. Let result be the result of creating an import map parse result given source text and base URL.
+            // 3. 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);
 
-            // FIXME: 4. Mark as ready el given result.
+            // 4. Mark as ready el given result.
+            mark_as_ready(Result(move(result)));
         }
     }
 

+ 3 - 2
Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h

@@ -10,6 +10,7 @@
 #include <LibWeb/DOM/DocumentLoadEventDelayer.h>
 #include <LibWeb/HTML/CORSSettingAttribute.h>
 #include <LibWeb/HTML/HTMLElement.h>
+#include <LibWeb/HTML/Scripting/ImportMapParseResult.h>
 #include <LibWeb/HTML/Scripting/Script.h>
 #include <LibWeb/ReferrerPolicy/ReferrerPolicy.h>
 
@@ -47,7 +48,7 @@ public:
     // https://html.spec.whatwg.org/multipage/scripting.html#dom-script-supports
     static bool supports(JS::VM&, StringView type)
     {
-        return type.is_one_of("classic"sv, "module"sv);
+        return type.is_one_of("classic"sv, "module"sv, "importmap"sv);
     }
 
     void set_source_line_number(Badge<HTMLParser>, size_t source_line_number) { m_source_line_number = source_line_number; }
@@ -81,7 +82,7 @@ private:
         struct Null { };
     };
 
-    using Result = Variant<ResultState::Uninitialized, ResultState::Null, JS::NonnullGCPtr<HTML::Script>>;
+    using Result = Variant<ResultState::Uninitialized, ResultState::Null, JS::NonnullGCPtr<HTML::Script>, JS::NonnullGCPtr<HTML::ImportMapParseResult>>;
 
     // https://html.spec.whatwg.org/multipage/scripting.html#mark-as-ready
     void mark_as_ready(Result);

+ 211 - 0
Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp

@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Console.h>
+#include <LibJS/Runtime/ConsoleObject.h>
+#include <LibWeb/Bindings/HostDefined.h>
+#include <LibWeb/DOMURL/DOMURL.h>
+#include <LibWeb/HTML/Scripting/Fetching.h>
+#include <LibWeb/HTML/Scripting/ImportMap.h>
+#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
+#include <LibWeb/Infra/JSON.h>
+
+namespace Web::HTML {
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string
+WebIDL::ExceptionOr<ImportMap> parse_import_map_string(JS::Realm& realm, ByteString const& input, URL::URL base_url)
+{
+    HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) };
+
+    // 1. Let parsed be the result of parsing a JSON string to an Infra value given input.
+    auto parsed = TRY(Infra::parse_json_string_to_javascript_value(realm, input));
+
+    // 2. If parsed is not an ordered map, then throw a TypeError indicating that the top-level value needs to be a JSON object.
+    if (!parsed.is_object())
+        return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The top-level value of an importmap needs to be a JSON object.").release_value_but_fixme_should_propagate_errors() };
+    auto& parsed_object = parsed.as_object();
+
+    // 3. Let sortedAndNormalizedImports be an empty ordered map.
+    ModuleSpecifierMap sorted_and_normalised_imports;
+
+    // 4. If parsed["imports"] exists, then:
+    if (TRY(parsed_object.has_property("imports"))) {
+        auto imports = TRY(parsed_object.get("imports"));
+
+        // If parsed["imports"] is not an ordered map, then throw a TypeError indicating that the value for the "imports" top-level key needs to be a JSON object.
+        if (!imports.is_object())
+            return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The 'imports' top-level value of an importmap needs to be a JSON object.").release_value_but_fixme_should_propagate_errors() };
+
+        // Set sortedAndNormalizedImports to the result of sorting and normalizing a module specifier map given parsed["imports"] and baseURL.
+        sorted_and_normalised_imports = TRY(sort_and_normalise_module_specifier_map(realm, imports.as_object(), base_url));
+    }
+
+    // 5. Let sortedAndNormalizedScopes be an empty ordered map.
+    HashMap<URL::URL, ModuleSpecifierMap> sorted_and_normalised_scopes;
+
+    // 6. If parsed["scopes"] exists, then:
+    if (TRY(parsed_object.has_property("scopes"))) {
+        auto scopes = TRY(parsed_object.get("scopes"));
+
+        // If parsed["scopes"] is not an ordered map, then throw a TypeError indicating that the value for the "scopes" top-level key needs to be a JSON object.
+        if (!scopes.is_object())
+            return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The 'scopes' top-level value of an importmap needs to be a JSON object.").release_value_but_fixme_should_propagate_errors() };
+
+        // Set sortedAndNormalizedScopes to the result of sorting and normalizing scopes given parsed["scopes"] and baseURL.
+        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.
+    for (auto& key : parsed_object.shape().property_table().keys()) {
+        if (key.as_string() == "imports" || key.as_string() == "imports")
+            continue;
+
+        auto& console = realm.intrinsics().console_object()->console();
+        console.output_debug_message(JS::Console::LogLevel::Warn,
+            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.
+    ImportMap import_map;
+    import_map.set_imports(sorted_and_normalised_imports);
+    return import_map;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-specifier-key
+WebIDL::ExceptionOr<Optional<DeprecatedFlyString>> normalise_specifier_key(JS::Realm& realm, DeprecatedFlyString specifier_key, URL::URL base_url)
+{
+    // 1. If specifierKey is the empty string, then:
+    if (specifier_key.is_empty()) {
+        // 1. The user agent may report a warning to the console indicating that specifier keys may not be the empty string.
+        auto& console = realm.intrinsics().console_object()->console();
+        console.output_debug_message(JS::Console::LogLevel::Warn,
+            TRY_OR_THROW_OOM(realm.vm(), String::formatted("Specifier keys may not be empty")));
+
+        // 2. Return null.
+        return Optional<DeprecatedFlyString> {};
+    }
+
+    // 2. Let url be the result of resolving a URL-like module specifier, given specifierKey and baseURL.
+    auto url = resolve_url_like_module_specifier(specifier_key, base_url);
+
+    // 3. If url is not null, then return the serialization of url.
+    if (url.has_value())
+        return url->serialize();
+
+    // 4. Return specifierKey.
+    return specifier_key;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-a-module-specifier-map
+WebIDL::ExceptionOr<ModuleSpecifierMap> sort_and_normalise_module_specifier_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url)
+{
+    // 1. Let normalized be an empty ordered map.
+    ModuleSpecifierMap normalised;
+
+    // 2. For each specifierKey → value of originalMap:
+    for (auto& specifier_key : original_map.shape().property_table().keys()) {
+        auto value = TRY(original_map.get(specifier_key.as_string()));
+
+        // 1. Let normalizedSpecifierKey be the result of normalizing a specifier key given specifierKey and baseURL.
+        auto normalised_specifier_key = TRY(normalise_specifier_key(realm, specifier_key.as_string(), base_url));
+
+        // 2. If normalizedSpecifierKey is null, then continue.
+        if (!normalised_specifier_key.has_value())
+            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 addresses 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("Addresses need to be strings")));
+
+            // 2. Set normalized[normalizedSpecifierKey] to null.
+            normalised.set(normalised_specifier_key.value(), {});
+
+            // 3. Continue.
+            continue;
+        }
+
+        // 4. Let addressURL be the result of resolving a URL-like module specifier given value and baseURL.
+        auto address_url = resolve_url_like_module_specifier(value.as_string().byte_string(), base_url);
+
+        // 5. If addressURL is null, then:
+        if (!address_url.has_value()) {
+            // 1. The user agent may report a warning to the console indicating that the address was invalid.
+            auto& console = realm.intrinsics().console_object()->console();
+            console.output_debug_message(JS::Console::LogLevel::Warn,
+                TRY_OR_THROW_OOM(realm.vm(), String::formatted("Address was invalid")));
+
+            // 2. Set normalized[normalizedSpecifierKey] to null.
+            normalised.set(normalised_specifier_key.value(), {});
+
+            // 3. Continue.
+            continue;
+        }
+
+        // 6. If specifierKey ends with U+002F (/), and the serialization of addressURL does not end with U+002F (/), then:
+        if (specifier_key.as_string().ends_with("/"sv) && !address_url->serialize().ends_with("/"sv)) {
+            // 1. The user agent may report a warning to the console indicating that an invalid address was given for the specifier key specifierKey; since specifierKey ends with a slash, the address needs to as well.
+            auto& console = realm.intrinsics().console_object()->console();
+            console.output_debug_message(JS::Console::LogLevel::Warn,
+                TRY_OR_THROW_OOM(realm.vm(), String::formatted("An invalid address was given for the specifier key ({}); since specifierKey ends with a slash, the address needs to as well", specifier_key.as_string())));
+
+            // 2. Set normalized[normalizedSpecifierKey] to null.
+            normalised.set(normalised_specifier_key.value(), {});
+
+            // 3. Continue.
+            continue;
+        }
+
+        // 7. Set normalized[normalizedSpecifierKey] to addressURL.
+        normalised.set(normalised_specifier_key.value(), address_url.value());
+    }
+
+    // 3. Return the result of sorting in descending order normalized, with an entry a being less than an entry b if a's key is code unit less than b's key.
+    return normalised;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-scopes
+WebIDL::ExceptionOr<HashMap<URL::URL, ModuleSpecifierMap>> sort_and_normalise_scopes(JS::Realm& realm, JS::Object& original_map, URL::URL base_url)
+{
+    // 1. Let normalized be an empty ordered map.
+    HashMap<URL::URL, ModuleSpecifierMap> normalised;
+
+    // 2. For each scopePrefix → potentialSpecifierMap of originalMap:
+    for (auto& scope_prefix : original_map.shape().property_table().keys()) {
+        auto potential_specifier_map = TRY(original_map.get(scope_prefix.as_string()));
+
+        // 1. If potentialSpecifierMap is not an ordered map, then throw a TypeError indicating that the value of the scope with prefix scopePrefix needs to be a JSON object.
+        if (!potential_specifier_map.is_object())
+            return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The value of the scope with the prefix '{}' needs to be a JSON object.", scope_prefix.as_string()).release_value_but_fixme_should_propagate_errors() };
+
+        // 2. Let scopePrefixURL be the result of URL parsing scopePrefix with baseURL.
+        auto scope_prefix_url = DOMURL::parse(scope_prefix.as_string(), base_url);
+
+        // 3. If scopePrefixURL is failure, then:
+        if (!scope_prefix_url.is_valid()) {
+            // 1. The user agent may report a warning to the console that the scope prefix URL was not parseable.
+            auto& console = realm.intrinsics().console_object()->console();
+            console.output_debug_message(JS::Console::LogLevel::Warn,
+                TRY_OR_THROW_OOM(realm.vm(), String::formatted("The scope prefix URL ({}) was not parseable", scope_prefix.as_string())));
+
+            // 2. Continue.
+            continue;
+        }
+
+        // 4. Let normalizedScopePrefix be the serialization of scopePrefixURL.
+        auto normalised_scope_prefix = scope_prefix_url.serialize();
+
+        // 5. Set normalized[normalizedScopePrefix] to the result of sorting and normalizing a module specifier map given potentialSpecifierMap and baseURL.
+        normalised.set(normalised_scope_prefix, TRY(sort_and_normalise_module_specifier_map(realm, potential_specifier_map.as_object(), base_url)));
+    }
+
+    // 3. Return the result of sorting in descending order normalized, with an entry a being less than an entry b if a's key is code unit less than b's key.
+    return normalised;
+}
+
+}

+ 10 - 0
Userland/Libraries/LibWeb/HTML/Scripting/ImportMap.h

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2022, networkException <networkexception@serenityos.org>
+ * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -7,6 +8,8 @@
 #pragma once
 
 #include <AK/HashMap.h>
+#include <LibURL/URL.h>
+#include <LibWeb/WebIDL/ExceptionOr.h>
 
 namespace Web::HTML {
 
@@ -19,13 +22,20 @@ public:
 
     ModuleSpecifierMap const& imports() const { return m_imports; }
     ModuleSpecifierMap& imports() { return m_imports; }
+    void set_imports(ModuleSpecifierMap const& imports) { m_imports = imports; }
 
     HashMap<URL::URL, ModuleSpecifierMap> const& scopes() const { return m_scopes; }
     HashMap<URL::URL, ModuleSpecifierMap>& scopes() { return m_scopes; }
+    void set_scopes(HashMap<URL::URL, ModuleSpecifierMap> const& scopes) { m_scopes = scopes; }
 
 private:
     ModuleSpecifierMap m_imports;
     HashMap<URL::URL, ModuleSpecifierMap> m_scopes;
 };
 
+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);
+
 }

+ 72 - 0
Userland/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp

@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/ModuleRequest.h>
+#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
+#include <LibWeb/HTML/Scripting/ImportMapParseResult.h>
+#include <LibWeb/HTML/Window.h>
+#include <LibWeb/WebIDL/DOMException.h>
+
+namespace Web::HTML {
+
+JS_DEFINE_ALLOCATOR(ImportMapParseResult);
+
+ImportMapParseResult::ImportMapParseResult() = default;
+
+ImportMapParseResult::~ImportMapParseResult() = default;
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#create-an-import-map-parse-result
+JS::NonnullGCPtr<ImportMapParseResult> ImportMapParseResult::create(JS::Realm& realm, ByteString const& input, URL::URL base_url)
+{
+    // 1. Let result be an import map parse result whose import map is null and whose error to rethrow is null.
+    auto result = realm.heap().allocate<ImportMapParseResult>(realm);
+    result->set_error_to_rethrow(JS::js_null());
+
+    // 2. Parse an import map string given input and baseURL, catching any exceptions.
+    auto import_map = parse_import_map_string(realm, input, base_url);
+
+    // 2.1. If this threw an exception, then set result's error to rethrow to that exception.
+    // FIXME: rethrow the original exception
+    if (import_map.is_exception())
+        result->set_error_to_rethrow(JS::Error::create(realm, "Failed to parse import map string"sv));
+
+    // 2.2. Otherwise, set result's import map to the return value.
+    else
+        result->set_import_map(import_map.release_value());
+
+    // 3. Return result.
+    return result;
+}
+
+void ImportMapParseResult::visit_host_defined_self(JS::Cell::Visitor& visitor)
+{
+    visitor.visit(*this);
+}
+
+void ImportMapParseResult::visit_edges(Visitor& visitor)
+{
+    Base::visit_edges(visitor);
+    visitor.visit(m_error_to_rethrow);
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
+void ImportMapParseResult::register_import_map(Window& global)
+{
+    // 1. If result's error to rethrow is not null, then report the exception given by result's error to rethrow and return.
+    if (!m_error_to_rethrow.is_null()) {
+        HTML::report_exception(m_error_to_rethrow, global.realm());
+        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.
+    VERIFY(m_import_map.has_value());
+    global.set_import_map(m_import_map.value());
+}
+
+}

+ 52 - 0
Userland/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.h

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Heap/Cell.h>
+#include <LibJS/Script.h>
+#include <LibURL/URL.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/HTML/Scripting/ImportMap.h>
+
+namespace Web::HTML {
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#import-map-parse-result
+class ImportMapParseResult
+    : public JS::Cell
+    , public JS::Script::HostDefined {
+    JS_CELL(Script, JS::Cell);
+    JS_DECLARE_ALLOCATOR(ImportMapParseResult);
+
+public:
+    virtual ~ImportMapParseResult() override;
+
+    static JS::NonnullGCPtr<ImportMapParseResult> create(JS::Realm& realm, ByteString const& input, URL::URL base_url);
+
+    [[nodiscard]] Optional<ImportMap> const& import_map() const { return m_import_map; }
+    void set_import_map(ImportMap const& value) { m_import_map = value; }
+
+    [[nodiscard]] JS::Value error_to_rethrow() const { return m_error_to_rethrow; }
+    void set_error_to_rethrow(JS::Value value) { m_error_to_rethrow = value; }
+
+    void register_import_map(Window& global);
+
+protected:
+    ImportMapParseResult();
+
+    virtual void visit_edges(Visitor&) override;
+
+private:
+    virtual void visit_host_defined_self(JS::Cell::Visitor&) override;
+
+    // https://html.spec.whatwg.org/multipage/webappapis.html#impr-import-map
+    Optional<ImportMap> m_import_map;
+
+    // https://html.spec.whatwg.org/multipage/webappapis.html#impr-error-to-rethrow
+    JS::Value m_error_to_rethrow;
+};
+
+}

+ 1 - 0
Userland/Libraries/LibWeb/HTML/Window.h

@@ -93,6 +93,7 @@ public:
     JS::GCPtr<Navigable> navigable() const;
 
     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; }