diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 9b6cca90035..a7854251434 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -48,6 +48,7 @@ static bool is_platform_object(Type const& type) "DocumentType"sv, "DOMRectReadOnly"sv, "DynamicsCompressorNode"sv, + "ElementInternals"sv, "EventTarget"sv, "FileList"sv, "FontFace"sv, diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn index 357e360fdf4..7ae2f3248b6 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn @@ -30,6 +30,7 @@ source_set("HTML") { "Dates.cpp", "DecodedImageData.cpp", "DocumentState.cpp", + "ElementInternals.cpp", "ErrorEvent.cpp", "EventHandler.cpp", "EventNames.cpp", diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni b/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni index 0220a12cd0f..3863d42d2ea 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni @@ -119,6 +119,7 @@ standard_idl_files = [ "//Userland/Libraries/LibWeb/HTML/DataTransfer.idl", "//Userland/Libraries/LibWeb/HTML/DOMParser.idl", "//Userland/Libraries/LibWeb/HTML/DOMStringMap.idl", + "//Userland/Libraries/LibWeb/HTML/ElementInternals.idl", "//Userland/Libraries/LibWeb/HTML/ErrorEvent.idl", "//Userland/Libraries/LibWeb/HTML/EventSource.idl", "//Userland/Libraries/LibWeb/HTML/FormDataEvent.idl", diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 1ec25502d6d..4cbbe688c02 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -273,6 +273,7 @@ set(SOURCES HTML/DOMParser.cpp HTML/DOMStringMap.cpp HTML/DataTransfer.cpp + HTML/ElementInternals.cpp HTML/ErrorEvent.cpp HTML/EventHandler.cpp HTML/EventSource.cpp diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index 047b57ec40a..0092d73184d 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -405,6 +405,8 @@ protected: virtual bool id_reference_exists(String const&) const override; + CustomElementState custom_element_state() const { return m_custom_element_state; } + private: void make_html_uppercased_qualified_name(); diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index fe0ffee7feb..ccbe3463a5a 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -352,6 +352,7 @@ class DecodedImageData; class DocumentState; class DOMParser; class DOMStringMap; +class ElementInternals; class ErrorEvent; class EventHandler; class EventLoop; diff --git a/Userland/Libraries/LibWeb/HTML/ElementInternals.cpp b/Userland/Libraries/LibWeb/HTML/ElementInternals.cpp new file mode 100644 index 00000000000..d6d9490bcd7 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/ElementInternals.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::HTML { + +JS_DEFINE_ALLOCATOR(ElementInternals); + +JS::NonnullGCPtr ElementInternals::create(JS::Realm& realm, HTMLElement& target_element) +{ + return realm.heap().allocate(realm, realm, target_element); +} + +ElementInternals::ElementInternals(JS::Realm& realm, HTMLElement& target_element) + : Bindings::PlatformObject(realm) + , m_target_element(target_element) +{ +} + +// https://html.spec.whatwg.org/#dom-elementinternals-shadowroot +JS::GCPtr ElementInternals::shadow_root() const +{ + // 1. Let target be this's target element. + auto target = m_target_element; + + // 2. If target is not a shadow host, then return null. + if (!target->is_shadow_host()) + return nullptr; + + // 3. Let shadow be target's shadow root. + auto shadow = target->shadow_root(); + + // 4. If shadow's available to element internals is false, then return null. + if (!shadow->available_to_element_internals()) + return nullptr; + + // 5. Return shadow. + return shadow; +} + +void ElementInternals::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(ElementInternals); +} + +void ElementInternals::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_target_element); +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/ElementInternals.h b/Userland/Libraries/LibWeb/HTML/ElementInternals.h new file mode 100644 index 00000000000..6c957875f3c --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/ElementInternals.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals +class ElementInternals final : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(ElementInternals, Bindings::PlatformObject); + JS_DECLARE_ALLOCATOR(ElementInternals); + +public: + static JS::NonnullGCPtr create(JS::Realm&, HTMLElement& target_element); + + JS::GCPtr shadow_root() const; + +private: + explicit ElementInternals(JS::Realm&, HTMLElement& target_element); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(JS::Cell::Visitor& visitor) override; + + // https://html.spec.whatwg.org/multipage/custom-elements.html#internals-target + JS::NonnullGCPtr m_target_element; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/ElementInternals.idl b/Userland/Libraries/LibWeb/HTML/ElementInternals.idl new file mode 100644 index 00000000000..4e1cc29adcb --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/ElementInternals.idl @@ -0,0 +1,44 @@ +#import + +// https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals +[Exposed=Window] +interface ElementInternals { + // Shadow root access + readonly attribute ShadowRoot? shadowRoot; + + // Form-associated custom elements + [FIXME] undefined setFormValue((File or USVString or FormData)? value, + optional (File or USVString or FormData)? state); + + [FIXME] readonly attribute HTMLFormElement? form; + + [FIXME] undefined setValidity(optional ValidityStateFlags flags = {}, + optional DOMString message, + optional HTMLElement anchor); + [FIXME] readonly attribute boolean willValidate; + [FIXME] readonly attribute ValidityState validity; + [FIXME] readonly attribute DOMString validationMessage; + [FIXME] boolean checkValidity(); + [FIXME] boolean reportValidity(); + + [FIXME] readonly attribute NodeList labels; + + // Custom state pseudo-class + [FIXME, SameObject] readonly attribute CustomStateSet states; +}; + +// Accessibility semantics +// ElementInternals includes ARIAMixin; + +dictionary ValidityStateFlags { + boolean valueMissing = false; + boolean typeMismatch = false; + boolean patternMismatch = false; + boolean tooLong = false; + boolean tooShort = false; + boolean rangeUnderflow = false; + boolean rangeOverflow = false; + boolean stepMismatch = false; + boolean badInput = false; + boolean customError = false; +}; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp index bf1e28fbb8d..b5a4cf58205 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -60,6 +62,7 @@ void HTMLElement::visit_edges(Cell::Visitor& visitor) Base::visit_edges(visitor); visitor.visit(m_dataset); visitor.visit(m_labels); + visitor.visit(m_attached_internals); } JS::NonnullGCPtr HTMLElement::dataset() @@ -608,6 +611,40 @@ TokenizedFeature::NoOpener HTMLElement::get_an_elements_noopener(StringView targ return TokenizedFeature::NoOpener::No; } +WebIDL::ExceptionOr> HTMLElement::attach_internals() +{ + // 1. If this's is value is not null, then throw a "NotSupportedError" DOMException. + if (is_value().has_value()) + return WebIDL::NotSupportedError::create(realm(), "ElementInternals cannot be attached to a customized build-in element"_fly_string); + + // 2. Let definition be the result of looking up a custom element definition given this's node document, its namespace, its local name, and null as the is value. + auto definition = document().lookup_custom_element_definition(namespace_uri(), local_name(), is_value()); + + // 3. If definition is null, then throw an "NotSupportedError" DOMException. + if (!definition) + return WebIDL::NotSupportedError::create(realm(), "ElementInternals cannot be attached to an element that is not a custom element"_fly_string); + + // 4. If definition's disable internals is true, then throw a "NotSupportedError" DOMException. + if (definition->disable_internals()) + return WebIDL::NotSupportedError::create(realm(), "ElementInternals are disabled for this custom element"_fly_string); + + // 5. If this's attached internals is non-null, then throw an "NotSupportedError" DOMException. + if (m_attached_internals) + return WebIDL::NotSupportedError::create(realm(), "ElementInternals already attached"_fly_string); + + // 6. If this's custom element state is not "precustomized" or "custom", then throw a "NotSupportedError" DOMException. + if (!first_is_one_of(custom_element_state(), DOM::CustomElementState::Precustomized, DOM::CustomElementState::Custom)) + return WebIDL::NotSupportedError::create(realm(), "Custom element is in an invalid state to attach ElementInternals"_fly_string); + + // 7. Set this's attached internals to a new ElementInternals instance whose target element is this. + auto internals = ElementInternals::create(realm(), *this); + + m_attached_internals = internals; + + // 8. Return this's attached internals. + return { internals }; +} + void HTMLElement::did_receive_focus() { if (m_content_editable_state != ContentEditableState::True) diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.h b/Userland/Libraries/LibWeb/HTML/HTMLElement.h index 0b047bf0b85..71643bc7bd3 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.h @@ -75,6 +75,8 @@ public: String get_an_elements_target() const; TokenizedFeature::NoOpener get_an_elements_noopener(StringView target) const; + WebIDL::ExceptionOr> attach_internals(); + protected: HTMLElement(DOM::Document&, DOM::QualifiedName); @@ -97,6 +99,9 @@ private: JS::GCPtr m_labels; + // https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals + JS::GCPtr m_attached_internals; + enum class ContentEditableState { True, False, diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLElement.idl index 7cab08527aa..d4031754f23 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLElement.idl +++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.idl @@ -1,5 +1,6 @@ #import #import +#import #import #import @@ -28,7 +29,7 @@ interface HTMLElement : Element { [LegacyNullToEmptyString, CEReactions] attribute DOMString innerText; [LegacyNullToEmptyString, CEReactions] attribute DOMString outerText; - [FIXME] ElementInternals attachInternals(); + ElementInternals attachInternals(); // The popover API [FIXME] undefined showPopover(); diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index b75c5d4ca48..28248608780 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -103,6 +103,7 @@ libweb_js_bindings(HTML/CustomElements/CustomElementRegistry) libweb_js_bindings(HTML/DOMParser) libweb_js_bindings(HTML/DOMStringMap) libweb_js_bindings(HTML/DataTransfer) +libweb_js_bindings(HTML/ElementInternals) libweb_js_bindings(HTML/ErrorEvent) libweb_js_bindings(HTML/EventSource) libweb_js_bindings(HTML/FormDataEvent)