Ver Fonte

LibWeb: Implement CloseWatcher API

This implements most of the CloseWatcher API from the html spec.

AbortSignal support is unimplemented.

Integration with dialogs and popovers is also unimplemented.
Luke Warlow há 1 ano atrás
pai
commit
b216046234

+ 1 - 0
Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp

@@ -42,6 +42,7 @@ static bool is_platform_object(Type const& type)
         "CanvasGradient"sv,
         "CanvasPattern"sv,
         "CanvasRenderingContext2D"sv,
+        "CloseWatcher"sv,
         "CryptoKey"sv,
         "Document"sv,
         "DocumentType"sv,

+ 2 - 0
Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn

@@ -22,6 +22,8 @@ source_set("HTML") {
     "CanvasPattern.cpp",
     "CanvasRenderingContext2D.cpp",
     "CloseEvent.cpp",
+    "CloseWatcher.cpp",
+    "CloseWatcherManager.cpp",
     "DOMParser.cpp",
     "DOMStringMap.cpp",
     "DataTransfer.cpp",

+ 1 - 0
Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni

@@ -114,6 +114,7 @@ standard_idl_files = [
   "//Userland/Libraries/LibWeb/HTML/CanvasPattern.idl",
   "//Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl",
   "//Userland/Libraries/LibWeb/HTML/CloseEvent.idl",
+  "//Userland/Libraries/LibWeb/HTML/CloseWatcher.idl",
   "//Userland/Libraries/LibWeb/HTML/CustomElements/CustomElementRegistry.idl",
   "//Userland/Libraries/LibWeb/HTML/DataTransfer.idl",
   "//Userland/Libraries/LibWeb/HTML/DOMParser.idl",

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

@@ -259,6 +259,8 @@ set(SOURCES
     HTML/CanvasPattern.cpp
     HTML/CanvasRenderingContext2D.cpp
     HTML/CloseEvent.cpp
+    HTML/CloseWatcher.cpp
+    HTML/CloseWatcherManager.cpp
     HTML/CORSSettingAttribute.cpp
     HTML/CrossOrigin/AbstractOperations.cpp
     HTML/CrossOrigin/Reporting.cpp

+ 10 - 3
Userland/Libraries/LibWeb/DOM/EventTarget.cpp

@@ -22,6 +22,7 @@
 #include <LibWeb/DOM/EventDispatcher.h>
 #include <LibWeb/DOM/EventTarget.h>
 #include <LibWeb/DOM/IDLEventListener.h>
+#include <LibWeb/HTML/CloseWatcherManager.h>
 #include <LibWeb/HTML/ErrorEvent.h>
 #include <LibWeb/HTML/EventHandler.h>
 #include <LibWeb/HTML/EventNames.h>
@@ -796,7 +797,9 @@ bool EventTarget::dispatch_event(Event& event)
     // FIXME: 3. Extend windows with the active window of each of document's ancestor navigables.
     // FIXME: 4. Extend windows with the active window of each of document's descendant navigables,
     //           filtered to include only those navigables whose active document's origin is same origin with document's origin.
-    // FIXME: 5. For each window in windows, set window's last activation timestamp to the current high resolution time.
+    // FIXME: 5. For each window in windows:
+    // FIXME: 5.1 Set window's last activation timestamp to the current high resolution time.
+    // FIXME: 5.2 Notify the close watcher manager about user activation given window.
 
     // FIXME: This is ad-hoc, but works for now.
     if (is_activation_triggering_input_event()) {
@@ -804,11 +807,15 @@ bool EventTarget::dispatch_event(Event& event)
         auto current_time = HighResolutionTime::relative_high_resolution_time(unsafe_shared_time, realm().global_object());
 
         if (is<HTML::Window>(this)) {
-            static_cast<HTML::Window*>(this)->set_last_activation_timestamp(current_time);
+            auto* window = static_cast<HTML::Window*>(this);
+            window->set_last_activation_timestamp(current_time);
+            window->close_watcher_manager()->notify_about_user_activation();
         } else if (is<DOM::Element>(this)) {
             auto const* element = static_cast<DOM::Element const*>(this);
-            if (auto window = element->document().window())
+            if (auto window = element->document().window()) {
                 window->set_last_activation_timestamp(current_time);
+                window->close_watcher_manager()->notify_about_user_activation();
+            }
         }
     }
 

+ 2 - 0
Userland/Libraries/LibWeb/Forward.h

@@ -344,6 +344,8 @@ class BrowsingContextGroup;
 class CanvasRenderingContext2D;
 class ClassicScript;
 class CloseEvent;
+class CloseWatcher;
+class CloseWatcherManager;
 class CustomElementDefinition;
 class CustomElementRegistry;
 class DecodedImageData;

+ 167 - 0
Userland/Libraries/LibWeb/HTML/CloseWatcher.cpp

@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2024, the Ladybird developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/TypeCasts.h>
+#include <LibWeb/Bindings/CloseWatcherPrototype.h>
+#include <LibWeb/Bindings/Intrinsics.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/EventDispatcher.h>
+#include <LibWeb/DOM/IDLEventListener.h>
+#include <LibWeb/HTML/CloseWatcher.h>
+#include <LibWeb/HTML/CloseWatcherManager.h>
+#include <LibWeb/HTML/EventHandler.h>
+#include <LibWeb/HTML/Window.h>
+
+namespace Web::HTML {
+
+JS_DEFINE_ALLOCATOR(CloseWatcher);
+
+// https://html.spec.whatwg.org/multipage/interaction.html#establish-a-close-watcher
+JS::NonnullGCPtr<CloseWatcher> CloseWatcher::establish(HTML::Window& window)
+{
+    // 1. Assert: window's associated Document is fully active.
+    VERIFY(window.associated_document().is_fully_active());
+
+    // 2. Let closeWatcher be a new close watcher
+    auto close_watcher = window.heap().allocate<CloseWatcher>(window.realm(), window.realm());
+
+    // 3. Let manager be window's associated close watcher manager
+    auto manager = window.close_watcher_manager();
+
+    // 4 - 6. Moved to CloseWatcherManager::add
+    manager->add(close_watcher);
+
+    // 7. Return close_watcher.
+    return close_watcher;
+}
+
+// https://html.spec.whatwg.org/multipage/interaction.html#dom-closewatcher
+WebIDL::ExceptionOr<JS::NonnullGCPtr<CloseWatcher>> CloseWatcher::construct_impl(JS::Realm& realm, CloseWatcherOptions const& options)
+{
+    // 1. If this's relevant global object's associated Document is not fully active, then return an "InvalidStateError" DOMException.
+    // FIXME: Not in spec explicitly, but this should account for detached iframes too. See /close-watcher/frame-removal.html WPT.
+    auto& window = verify_cast<HTML::Window>(realm.global_object());
+    if (!window.associated_document().is_fully_active())
+        return WebIDL::InvalidStateError::create(realm, "The document is not fully active."_fly_string);
+
+    // 2. Let close_watcher be the result of establishing a close watcher
+    auto close_watcher = establish(window);
+
+    // 3. If options["signal"] exists, then:
+    if (options.signal) {
+        // FIXME: 3.1 If options["signal"]'s aborted, then destroy closeWatcher.
+        // FIXME: 3.2 Add the following steps to options["signal"]:
+    }
+
+    return close_watcher;
+}
+
+CloseWatcher::CloseWatcher(JS::Realm& realm)
+    : DOM::EventTarget(realm)
+{
+}
+
+// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-request-close
+bool CloseWatcher::request_close()
+{
+    // 1. If closeWatcher is not active, then return.
+    if (!m_is_active)
+        return true;
+
+    // 2. If closeWatcher's is running cancel action is true, then return true.
+    if (m_is_running_cancel_action)
+        return true;
+
+    // 3. Let window be closeWatcher's window.
+    auto& window = verify_cast<HTML::Window>(realm().global_object());
+
+    // 4. If window's associated Document is not fully active, then return true.
+    if (!window.associated_document().is_fully_active())
+        return true;
+
+    // 5. Let canPreventClose be true if window's close watcher manager's groups's size is less than window's close watcher manager's allowed number of groups,
+    // and window has history-action activation; otherwise false.
+    auto manager = window.close_watcher_manager();
+    bool can_prevent_close = manager->can_prevent_close() && window.has_history_action_activation();
+    // 6. Set closeWatcher's is running cancel action to true.
+    m_is_running_cancel_action = true;
+    // 7. Let shouldContinue be the result of running closeWatcher's cancel action given canPreventClose.
+    bool should_continue = dispatch_event(DOM::Event::create(realm(), HTML::EventNames::cancel, { .cancelable = can_prevent_close }));
+    // 8. Set closeWatcher's is running cancel action to false.
+    m_is_running_cancel_action = false;
+    // 9. If shouldContinue is false, then:
+    if (!should_continue) {
+        // 9.1 Assert: canPreventClose is true.
+        VERIFY(can_prevent_close);
+        // 9.2 Consume history-action user activation given window.
+        window.consume_history_action_user_activation();
+        return false;
+    }
+
+    // 10. Close closeWatcher.
+    close();
+
+    // 11. Return true.
+    return true;
+}
+
+// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-close
+void CloseWatcher::close()
+{
+    // 1. If closeWatcher is not active, then return.
+    if (!m_is_active)
+        return;
+
+    // 2. If closeWatcher's window's associated Document is not fully active, then return.
+    if (!verify_cast<HTML::Window>(realm().global_object()).associated_document().is_fully_active())
+        return;
+
+    // 3. Destroy closeWatcher.
+    destroy();
+
+    // 4. Run closeWatcher's close action.
+    dispatch_event(DOM::Event::create(realm(), HTML::EventNames::close));
+}
+
+// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-destroy
+void CloseWatcher::destroy()
+{
+    // 1. Let manager be closeWatcher's window's close watcher manager.
+    auto manager = verify_cast<HTML::Window>(realm().global_object()).close_watcher_manager();
+
+    // 2-3. Moved to CloseWatcherManager::remove
+    manager->remove(*this);
+
+    m_is_active = false;
+}
+
+void CloseWatcher::initialize(JS::Realm& realm)
+{
+    Base::initialize(realm);
+    WEB_SET_PROTOTYPE_FOR_INTERFACE(CloseWatcher);
+}
+
+void CloseWatcher::set_oncancel(WebIDL::CallbackType* event_handler)
+{
+    set_event_handler_attribute(HTML::EventNames::cancel, event_handler);
+}
+
+WebIDL::CallbackType* CloseWatcher::oncancel()
+{
+    return event_handler_attribute(HTML::EventNames::cancel);
+}
+
+void CloseWatcher::set_onclose(WebIDL::CallbackType* event_handler)
+{
+    set_event_handler_attribute(HTML::EventNames::close, event_handler);
+}
+
+WebIDL::CallbackType* CloseWatcher::onclose()
+{
+    return event_handler_attribute(HTML::EventNames::close);
+}
+
+}

+ 49 - 0
Userland/Libraries/LibWeb/HTML/CloseWatcher.h

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024, the Ladybird developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web::HTML {
+
+// https://html.spec.whatwg.org/multipage/interaction.html#closewatcheroptions
+struct CloseWatcherOptions {
+    JS::GCPtr<DOM::AbortSignal> signal;
+};
+
+// https://html.spec.whatwg.org/multipage/interaction.html#the-closewatcher-interface
+class CloseWatcher final : public DOM::EventTarget {
+    WEB_PLATFORM_OBJECT(CloseWatcher, DOM::EventTarget);
+    JS_DECLARE_ALLOCATOR(CloseWatcher);
+
+public:
+    static WebIDL::ExceptionOr<JS::NonnullGCPtr<CloseWatcher>> construct_impl(JS::Realm&, CloseWatcherOptions const& = {});
+
+    bool request_close();
+    void close();
+    void destroy();
+
+    virtual ~CloseWatcher() override = default;
+
+    void set_oncancel(WebIDL::CallbackType*);
+    WebIDL::CallbackType* oncancel();
+
+    void set_onclose(WebIDL::CallbackType*);
+    WebIDL::CallbackType* onclose();
+
+private:
+    CloseWatcher(JS::Realm&);
+    [[nodiscard]] static JS::NonnullGCPtr<CloseWatcher> establish(HTML::Window&);
+
+    virtual void initialize(JS::Realm&) override;
+
+    bool m_is_running_cancel_action { false };
+    bool m_is_active { true };
+};
+
+}

+ 19 - 0
Userland/Libraries/LibWeb/HTML/CloseWatcher.idl

@@ -0,0 +1,19 @@
+#import <DOM/EventTarget.idl>
+#import <DOM/EventHandler.idl>
+
+// https://html.spec.whatwg.org/multipage/interaction.html#closewatcher
+[Exposed=Window]
+interface CloseWatcher : EventTarget {
+  constructor(optional CloseWatcherOptions options = {});
+
+  undefined requestClose();
+  undefined close();
+  undefined destroy();
+
+  attribute EventHandler oncancel;
+  attribute EventHandler onclose;
+};
+
+dictionary CloseWatcherOptions {
+  AbortSignal signal;
+};

+ 120 - 0
Userland/Libraries/LibWeb/HTML/CloseWatcherManager.cpp

@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2024, the Ladybird developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/TypeCasts.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/EventDispatcher.h>
+#include <LibWeb/DOM/IDLEventListener.h>
+#include <LibWeb/HTML/CloseWatcher.h>
+#include <LibWeb/HTML/CloseWatcherManager.h>
+#include <LibWeb/HTML/Window.h>
+
+namespace Web::HTML {
+
+JS_DEFINE_ALLOCATOR(CloseWatcherManager);
+
+JS::NonnullGCPtr<CloseWatcherManager> CloseWatcherManager::create(JS::Realm& realm)
+{
+    return realm.heap().allocate<CloseWatcherManager>(realm, realm);
+}
+
+CloseWatcherManager::CloseWatcherManager(JS::Realm& realm)
+    : PlatformObject(realm)
+{
+}
+
+void CloseWatcherManager::add(JS::NonnullGCPtr<CloseWatcher> close_watcher)
+{
+    // If manager's groups's size is less than manager's allowed number of groups
+    if (m_groups.size() < m_allowed_number_of_groups) {
+        // then append « closeWatcher » to manager's groups.
+        JS::MarkedVector<JS::NonnullGCPtr<CloseWatcher>> new_group(realm().heap());
+        new_group.append(close_watcher);
+        m_groups.append(move(new_group));
+    } else {
+        // Assert: manager's groups's size is at least 1 in this branch, since manager's allowed number of groups is always at least 1.
+        VERIFY(!m_groups.is_empty());
+        // Append closeWatcher to manager's groups's last item.
+        m_groups.last().append(close_watcher);
+    }
+
+    // Set manager's next user interaction allows a new group to true.
+    m_next_user_interaction_allows_a_new_group = true;
+}
+
+void CloseWatcherManager::remove(CloseWatcher const& close_watcher)
+{
+    // 2. For each group of manager's groups: remove closeWatcher from group
+    for (auto& group : m_groups) {
+        group.remove_first_matching([&close_watcher](JS::NonnullGCPtr<CloseWatcher>& entry) {
+            return entry.ptr() == &close_watcher;
+        });
+    }
+    // 3. Remove any item from manager's group that is empty
+    m_groups.remove_all_matching([](auto& group) { return group.is_empty(); });
+}
+
+// https://html.spec.whatwg.org/multipage/interaction.html#process-close-watchers
+bool CloseWatcherManager::process_close_watchers()
+{
+    // 1. Let processedACloseWatcher be false.
+    bool processed_a_close_watcher = false;
+    // 2. If window's close watcher manager's groups is not empty:
+    if (!m_groups.is_empty()) {
+        // 2.1 Let group be the last item in window's close watcher manager's groups.
+        auto& group = m_groups.last();
+        // Ambiguous spec wording. We copy the groups to avoid modifying the original while iterating.
+        // See https://github.com/whatwg/html/issues/10240
+        JS::MarkedVector<JS::NonnullGCPtr<CloseWatcher>> group_copy(realm().heap());
+        group_copy.ensure_capacity(group.size());
+        for (auto& close_watcher : group) {
+            group_copy.append(close_watcher);
+        }
+        // 2.2 For each closeWatcher of group, in reverse order:
+        for (auto it = group_copy.rbegin(); it != group_copy.rend(); ++it) {
+            // 2.1.1 Set processedACloseWatcher to true.
+            processed_a_close_watcher = true;
+            // 2.1.2 Let shouldProceed be the result of requesting to close closeWatcher.
+            bool should_proceed = (*it)->request_close();
+            // 2.1.3 If shouldProceed is false, then break;
+            if (!should_proceed)
+                break;
+        }
+    }
+    // 3. If window's close watcher manager's allowed number of groups is greater than 1, decrement it by 1.
+    if (m_allowed_number_of_groups > 1)
+        m_allowed_number_of_groups--;
+
+    // 4. Return processedACloseWatcher.
+    return processed_a_close_watcher;
+}
+
+// https://html.spec.whatwg.org/multipage/interaction.html#notify-the-close-watcher-manager-about-user-activation
+void CloseWatcherManager::notify_about_user_activation()
+{
+    // 1. Let manager be window's close watcher manager.
+    // 2. If manager's next user interaction allows a new group is true, then increment manager's allowed number of groups.
+    if (m_next_user_interaction_allows_a_new_group)
+        m_allowed_number_of_groups++;
+    // 3. Set manager's next user interaction allows a new group to false.
+    m_next_user_interaction_allows_a_new_group = false;
+}
+
+// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-request-close
+bool CloseWatcherManager::can_prevent_close()
+{
+    // 5. Let canPreventClose be true if window's close watcher manager's groups's size is less than window's close watcher manager's allowed number of groups...
+    return m_groups.size() < m_allowed_number_of_groups;
+}
+
+void CloseWatcherManager::visit_edges(JS::Cell::Visitor& visitor)
+{
+    Base::visit_edges(visitor);
+
+    visitor.visit(m_groups);
+}
+
+}

+ 40 - 0
Userland/Libraries/LibWeb/HTML/CloseWatcherManager.h

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2024, the Ladybird developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/DOM/EventTarget.h>
+
+namespace Web::HTML {
+
+// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-manager
+class CloseWatcherManager final : public Bindings::PlatformObject {
+    WEB_PLATFORM_OBJECT(CloseWatcherManager, Bindings::PlatformObject);
+    JS_DECLARE_ALLOCATOR(CloseWatcherManager);
+
+public:
+    [[nodiscard]] static JS::NonnullGCPtr<CloseWatcherManager> create(JS::Realm&);
+
+    void add(JS::NonnullGCPtr<CloseWatcher>);
+    void remove(CloseWatcher const&);
+
+    bool process_close_watchers();
+
+    void notify_about_user_activation();
+    bool can_prevent_close();
+
+private:
+    explicit CloseWatcherManager(JS::Realm&);
+
+    virtual void visit_edges(Cell::Visitor&) override;
+
+    Vector<Vector<JS::NonnullGCPtr<CloseWatcher>>> m_groups;
+    uint32_t m_allowed_number_of_groups { 1 };
+    bool m_next_user_interaction_allows_a_new_group { true };
+};
+
+}

+ 12 - 0
Userland/Libraries/LibWeb/HTML/Window.cpp

@@ -33,6 +33,7 @@
 #include <LibWeb/DOM/HTMLCollection.h>
 #include <LibWeb/DOMURL/DOMURL.h>
 #include <LibWeb/HTML/BrowsingContext.h>
+#include <LibWeb/HTML/CloseWatcherManager.h>
 #include <LibWeb/HTML/CustomElements/CustomElementRegistry.h>
 #include <LibWeb/HTML/DocumentState.h>
 #include <LibWeb/HTML/EventHandler.h>
@@ -129,6 +130,7 @@ void Window::visit_edges(JS::Cell::Visitor& visitor)
     visitor.visit(m_pdf_viewer_mime_type_objects);
     visitor.visit(m_count_queuing_strategy_size_function);
     visitor.visit(m_byte_length_queuing_strategy_size_function);
+    visitor.visit(m_close_watcher_manager);
 }
 
 void Window::finalize()
@@ -973,6 +975,16 @@ JS::NonnullGCPtr<Navigator> Window::navigator()
     return JS::NonnullGCPtr { *m_navigator };
 }
 
+// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-manager
+JS::NonnullGCPtr<CloseWatcherManager> Window::close_watcher_manager()
+{
+    auto& realm = this->realm();
+
+    if (!m_close_watcher_manager)
+        m_close_watcher_manager = heap().allocate<CloseWatcherManager>(realm, realm);
+    return JS::NonnullGCPtr { *m_close_watcher_manager };
+}
+
 // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-alert
 void Window::alert(String const& message)
 {

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

@@ -162,6 +162,7 @@ public:
     WebIDL::ExceptionOr<JS::GCPtr<WindowProxy>> open(Optional<String> const& url, Optional<String> const& target, Optional<String> const& features);
 
     [[nodiscard]] JS::NonnullGCPtr<Navigator> navigator();
+    [[nodiscard]] JS::NonnullGCPtr<CloseWatcherManager> close_watcher_manager();
 
     void alert(String const& message = {});
     bool confirm(Optional<String> const& message);
@@ -269,6 +270,7 @@ private:
     JS::GCPtr<CSS::Screen> m_screen;
     JS::GCPtr<Navigator> m_navigator;
     JS::GCPtr<Location> m_location;
+    JS::GCPtr<CloseWatcherManager> m_close_watcher_manager;
 
     // https://html.spec.whatwg.org/multipage/nav-history-apis.html#window-navigation-api
     JS::GCPtr<Navigation> m_navigation;

+ 4 - 0
Userland/Libraries/LibWeb/Page/EventHandler.cpp

@@ -8,6 +8,7 @@
 #include <LibWeb/DOM/Range.h>
 #include <LibWeb/DOM/Text.h>
 #include <LibWeb/HTML/BrowsingContext.h>
+#include <LibWeb/HTML/CloseWatcherManager.h>
 #include <LibWeb/HTML/Focus.h>
 #include <LibWeb/HTML/HTMLAnchorElement.h>
 #include <LibWeb/HTML/HTMLFormElement.h>
@@ -778,6 +779,9 @@ bool EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u32 code
         return focus_next_element();
     }
 
+    if (key == UIEvents::KeyCode::Key_Escape)
+        return document->window()->close_watcher_manager()->process_close_watchers();
+
     auto& realm = document->realm();
 
     if (auto selection = document->get_selection()) {

+ 1 - 0
Userland/Libraries/LibWeb/idl_files.cmake

@@ -98,6 +98,7 @@ libweb_js_bindings(HTML/CanvasGradient)
 libweb_js_bindings(HTML/CanvasPattern)
 libweb_js_bindings(HTML/CanvasRenderingContext2D)
 libweb_js_bindings(HTML/CloseEvent)
+libweb_js_bindings(HTML/CloseWatcher)
 libweb_js_bindings(HTML/CustomElements/CustomElementRegistry)
 libweb_js_bindings(HTML/DOMParser)
 libweb_js_bindings(HTML/DOMStringMap)