From 85842c1739915293e66d262f80501d325bdfd283 Mon Sep 17 00:00:00 2001 From: Noah <74223879+Noah55-maker@users.noreply.github.com> Date: Mon, 31 Mar 2025 01:43:20 -0700 Subject: [PATCH] LibWeb: Stub out several methods for ElementInternals This begins implementation on form-associated custom elements. This fixes a few WPT tests which I'm importing. Co-authored-by: Sam Atkins --- Libraries/LibWeb/HTML/ElementInternals.cpp | 162 ++++++++++++++++++ Libraries/LibWeb/HTML/ElementInternals.h | 35 ++++ Libraries/LibWeb/HTML/ElementInternals.idl | 18 +- Libraries/LibWeb/HTML/HTMLElement.cpp | 11 +- Libraries/LibWeb/HTML/HTMLElement.h | 2 + .../ElementInternals-NotSupportedError.txt | 6 + ...ntInternals-setFormValue-nullish-value.txt | 7 + .../ElementInternals-NotSupportedError.html | 24 +++ ...tInternals-setFormValue-nullish-value.html | 45 +++++ 9 files changed, 300 insertions(+), 10 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/custom-elements/form-associated/ElementInternals-NotSupportedError.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/custom-elements/form-associated/ElementInternals-setFormValue-nullish-value.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/custom-elements/form-associated/ElementInternals-NotSupportedError.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/custom-elements/form-associated/ElementInternals-setFormValue-nullish-value.html diff --git a/Libraries/LibWeb/HTML/ElementInternals.cpp b/Libraries/LibWeb/HTML/ElementInternals.cpp index 036534dd8c0..988e7bfe01d 100644 --- a/Libraries/LibWeb/HTML/ElementInternals.cpp +++ b/Libraries/LibWeb/HTML/ElementInternals.cpp @@ -46,6 +46,168 @@ GC::Ptr ElementInternals::shadow_root() const return shadow; } +// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-setformvalue +WebIDL::ExceptionOr ElementInternals::set_form_value(Variant, String, GC::Root> value, Optional, String, GC::Root>> state) +{ + // 1. Let element be this's target element. + auto element = m_target_element; + + // 2. If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException. + if (!element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + (void)value; + (void)state; + + // FIXME: 3. Set target element's submission value to value if value is not a FormData object, or to a clone of value's entry list otherwise. + + // FIXME: 4. If the state argument of the function is omitted, set element's state to its submission value. + + // FIXME: 5. Otherwise, if state is a FormData object, set element's state to a clone of state's entry list. + + // FIXME: 6. Otherwise, set element's state to state. + + dbgln("FIXME: ElementInternals::set_form_value()"); + return {}; +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-form +WebIDL::ExceptionOr> ElementInternals::form() const +{ + // Form-associated custom elements don't have form IDL attribute. Instead, their ElementInternals object has a form IDL attribute. + // On getting, it must throw a "NotSupportedError" DOMException if the target element is not a form-associated custom element. + // Otherwise, it must return the element's form owner, or null if there isn't one. + if (!m_target_element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + dbgln("FIXME: ElementInternals::form()"); + return WebIDL::NotFoundError::create(realm(), "FIXME: ElementInternals::form()"_string); +} + +// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-setvalidity +WebIDL::ExceptionOr ElementInternals::set_validity(ValidityStateFlags const& flags, Optional message, Optional> anchor) +{ + // 1. Let element be this's target element. + auto element = m_target_element; + + // 2. If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException. + if (!element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + // 3. If flags contains one or more true values and message is not given or is the empty string, then throw a TypeError. + if (flags.has_one_or_more_true_values() && (!message.has_value() || message->is_empty())) { + return WebIDL::SimpleException { + WebIDL::SimpleExceptionType::TypeError, + "Invalid flag(s) and empty message"sv + }; + } + + // FIXME: 4. For each entry flag → value of flags, set element's validity flag with the name flag to value. + + // FIXME: 5. Set element's validation message to the empty string if message is not given or all of element's validity flags are false, or to message otherwise. + + // FIXME: 6. If element's customError validity flag is true, then set element's custom validity error message to element's validation message. Otherwise, set element's custom validity error message to the empty string. + + // FIXME: 7. Set element's validation anchor to null if anchor is not given. Otherwise, if anchor is not a shadow-including descendant of element, then throw a "NotFoundError" DOMException. Otherwise, set element's validation anchor to anchor. + if (!anchor.has_value() || !anchor.value().ptr()) { + // FIXME + } else if (!anchor.value()->is_shadow_including_descendant_of(element)) { + return WebIDL::NotFoundError::create(realm(), "Anchor is not a shadow-including descendant of element"_string); + } else { + // FIXME + } + + dbgln("FIXME: ElementInternals::set_validity()"); + return {}; +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-willvalidate +WebIDL::ExceptionOr ElementInternals::will_validate() const +{ + // The willValidate attribute of ElementInternals interface, on getting, must throw a "NotSupportedError" DOMException if + // the target element is not a form-associated custom element. Otherwise, it must return true if the target element is a + // candidate for constraint validation, and false otherwise. + if (!m_target_element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + dbgln("FIXME: ElementInternals::will_validate()"); + return true; +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-validity +WebIDL::ExceptionOr> ElementInternals::validity() const +{ + // The validity attribute of ElementInternals interface, on getting, must throw a "NotSupportedError" DOMException if + // the target element is not a form-associated custom element. Otherwise, it must return a ValidityState object that + // represents the validity states of the target element. This object is live. + if (!m_target_element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + dbgln("FIXME: ElementInternals::validity()"); + return WebIDL::NotSupportedError::create(realm(), "FIXME: ElementInternals::validity()"_string); +} + +// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-validationmessage +WebIDL::ExceptionOr ElementInternals::validation_message() const +{ + // 1. Let element be this's target element. + auto element = m_target_element; + + // 2. If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException. + if (!element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + // FIXME: 3. Return element's validation message. + + dbgln("FIXME: ElementInternals::validation_message()"); + return "FIXME: ElementInternals::validation_message()"_string; +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-checkvalidity +WebIDL::ExceptionOr ElementInternals::check_validity() const +{ + // 1. Let element be this ElementInternals's target element. + auto element = m_target_element; + + // 2. If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException. + if (!element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + // FIXME: 3. Run the check validity steps on element. + + dbgln("FIXME: ElementInternals::check_validity()"); + return true; +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-reportvalidity +WebIDL::ExceptionOr ElementInternals::report_validity() const +{ + // 1. Let element be this ElementInternals's target element + auto element = m_target_element; + + // 2. If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException. + if (!element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + // FIXME: 3. Run the report validity steps on element. + + dbgln("FIXME: ElementInternals::report_validity()"); + return true; +} + +// https://html.spec.whatwg.org/multipage/forms.html#dom-elementinternals-labels +WebIDL::ExceptionOr> ElementInternals::labels() +{ + // Form-associated custom elements don't have a labels IDL attribute. Instead, their ElementInternals object has a labels IDL attribute. + // On getting, it must throw a "NotSupportedError" DOMException if the target element is not a form-associated custom element. + // Otherwise, it must return that NodeList object, and that same value must always be returned. + if (!m_target_element->is_form_associated_custom_element()) + return WebIDL::NotSupportedError::create(realm(), "Element is not a form-associated custom element"_string); + + dbgln("FIXME: ElementInternals::labels()"); + return WebIDL::NotSupportedError::create(realm(), "FIXME: ElementInternals::labels()"_string); +} + void ElementInternals::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE(ElementInternals); diff --git a/Libraries/LibWeb/HTML/ElementInternals.h b/Libraries/LibWeb/HTML/ElementInternals.h index 4079d3a06ec..e57e086462c 100644 --- a/Libraries/LibWeb/HTML/ElementInternals.h +++ b/Libraries/LibWeb/HTML/ElementInternals.h @@ -9,10 +9,32 @@ #include #include #include +#include #include +#include +#include +#include namespace Web::HTML { +struct ValidityStateFlags { + bool value_missing = false; + bool type_mismatch = false; + bool pattern_mismatch = false; + bool too_long = false; + bool too_short = false; + bool range_underflow = false; + bool range_overflow = false; + bool step_mismatch = false; + bool bad_input = false; + bool custom_error = false; + + bool has_one_or_more_true_values() const + { + return value_missing || type_mismatch || pattern_mismatch || too_long || too_short || range_underflow || range_overflow || step_mismatch || bad_input || custom_error; + } +}; + // https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals class ElementInternals final : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(ElementInternals, Bindings::PlatformObject); @@ -23,6 +45,19 @@ public: GC::Ptr shadow_root() const; + WebIDL::ExceptionOr set_form_value(Variant, String, GC::Root> value, Optional, String, GC::Root>> state); + + WebIDL::ExceptionOr> form() const; + + WebIDL::ExceptionOr set_validity(ValidityStateFlags const& flags, Optional message, Optional> anchor); + WebIDL::ExceptionOr will_validate() const; + WebIDL::ExceptionOr> validity() const; + WebIDL::ExceptionOr validation_message() const; + WebIDL::ExceptionOr check_validity() const; + WebIDL::ExceptionOr report_validity() const; + + WebIDL::ExceptionOr> labels(); + private: explicit ElementInternals(JS::Realm&, HTMLElement& target_element); diff --git a/Libraries/LibWeb/HTML/ElementInternals.idl b/Libraries/LibWeb/HTML/ElementInternals.idl index 4e1cc29adcb..34912a2e2f0 100644 --- a/Libraries/LibWeb/HTML/ElementInternals.idl +++ b/Libraries/LibWeb/HTML/ElementInternals.idl @@ -7,21 +7,21 @@ interface ElementInternals { readonly attribute ShadowRoot? shadowRoot; // Form-associated custom elements - [FIXME] undefined setFormValue((File or USVString or FormData)? value, + undefined setFormValue((File or USVString or FormData)? value, optional (File or USVString or FormData)? state); - [FIXME] readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; - [FIXME] undefined setValidity(optional ValidityStateFlags flags = {}, + 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(); + readonly attribute boolean willValidate; + readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); - [FIXME] readonly attribute NodeList labels; + readonly attribute NodeList labels; // Custom state pseudo-class [FIXME, SameObject] readonly attribute CustomStateSet states; diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 656737fdd6c..a23a1b82ee0 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -833,6 +833,15 @@ void HTMLElement::click() m_click_in_progress = false; } +// https://html.spec.whatwg.org/multipage/custom-elements.html#form-associated-custom-element +bool HTMLElement::is_form_associated_custom_element() +{ + // An autonomous custom element is called a form-associated custom element if the element is associated with a + // custom element definition whose form-associated field is set to true. + auto definition = document().lookup_custom_element_definition(namespace_uri(), local_name(), is_value()); + return definition->form_associated(); +} + Optional HTMLElement::default_role() const { // https://www.w3.org/TR/html-aria/#el-address @@ -1023,7 +1032,7 @@ 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"_string); + return WebIDL::NotSupportedError::create(realm(), "ElementInternals cannot be attached to a customized built-in element"_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()); diff --git a/Libraries/LibWeb/HTML/HTMLElement.h b/Libraries/LibWeb/HTML/HTMLElement.h index cfb8d6de9d6..15a00c558fe 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Libraries/LibWeb/HTML/HTMLElement.h @@ -156,6 +156,8 @@ public: virtual bool is_valid_invoker_command(String&) { return false; } virtual void invoker_command_steps(DOM::Element&, String&) { } + bool is_form_associated_custom_element(); + protected: HTMLElement(DOM::Document&, DOM::QualifiedName); diff --git a/Tests/LibWeb/Text/expected/wpt-import/custom-elements/form-associated/ElementInternals-NotSupportedError.txt b/Tests/LibWeb/Text/expected/wpt-import/custom-elements/form-associated/ElementInternals-NotSupportedError.txt new file mode 100644 index 00000000000..3133438d7c4 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/custom-elements/form-associated/ElementInternals-NotSupportedError.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass Form-related operations and attributes should throw NotSupportedErrors for non-form-associated custom elements. \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/custom-elements/form-associated/ElementInternals-setFormValue-nullish-value.txt b/Tests/LibWeb/Text/expected/wpt-import/custom-elements/form-associated/ElementInternals-setFormValue-nullish-value.txt new file mode 100644 index 00000000000..76fc62520b5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/custom-elements/form-associated/ElementInternals-setFormValue-nullish-value.txt @@ -0,0 +1,7 @@ +Harness status: OK + +Found 2 tests + +2 Pass +Pass ElementInternals.setFormValue(null) clears submission value +Pass ElementInternals.setFormValue(undefined) clears submission value \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/custom-elements/form-associated/ElementInternals-NotSupportedError.html b/Tests/LibWeb/Text/input/wpt-import/custom-elements/form-associated/ElementInternals-NotSupportedError.html new file mode 100644 index 00000000000..250f3e204df --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/custom-elements/form-associated/ElementInternals-NotSupportedError.html @@ -0,0 +1,24 @@ + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/custom-elements/form-associated/ElementInternals-setFormValue-nullish-value.html b/Tests/LibWeb/Text/input/wpt-import/custom-elements/form-associated/ElementInternals-setFormValue-nullish-value.html new file mode 100644 index 00000000000..c6dcb085d13 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/custom-elements/form-associated/ElementInternals-setFormValue-nullish-value.html @@ -0,0 +1,45 @@ + + + + ElementInternals.setFormValue(nullish value) should clear submission value + + + + + + +
+ +
+ +
+ +
+ + + +