Files
ladybird/Libraries/LibWeb/HTML/ElementInternals.cpp
Shannon Booth 5adfd1c43a LibWeb/Bindings: Generate struct definitions from IDL dictionaries
Previously we were inconsistent by generating code for enum definitions
but not generating code for dictionaries. With future changes to the
IDL generator to expose helpers to convert to and from IDL values
this produced circular depdendencies. To solve this problem, also
generate the dictionary definitions in bindings headers.
2026-05-09 10:49:49 +02:00

277 lines
12 KiB
C++

/*
* Copyright (c) 2024, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/ElementInternals.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/FileAPI/File.h>
#include <LibWeb/HTML/ElementInternals.h>
#include <LibWeb/HTML/HTMLElement.h>
#include <LibWeb/XHR/FormData.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(ElementInternals);
GC::Ref<ElementInternals> ElementInternals::create(JS::Realm& realm, HTMLElement& target_element)
{
return realm.create<ElementInternals>(realm, target_element);
}
ElementInternals::ElementInternals(JS::Realm& realm, HTMLElement& target_element)
: Bindings::PlatformObject(realm)
, m_target_element(target_element)
{
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-shadowroot
GC::Ptr<DOM::ShadowRoot> 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;
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-setformvalue
WebIDL::ExceptionOr<void> ElementInternals::set_form_value(ElementInternalsFormValue value, Optional<ElementInternalsFormValue> 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"_utf16);
// 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.
auto submission_value = value.visit(
[](GC::Root<FileAPI::File> const& file) -> FormAssociatedElement::FACESubmissionValue {
return GC::Ref { *file };
},
[](String const& string) -> FormAssociatedElement::FACESubmissionValue {
return string;
},
[](GC::Root<XHR::FormData> const& form_data) -> FormAssociatedElement::FACESubmissionValue {
return form_data->entry_list();
},
[](Empty const& empty) -> FormAssociatedElement::FACESubmissionValue {
return empty;
});
element->set_face_submission_value({}, submission_value);
// 4. If the state argument of the function is omitted, set element's state to its submission value.
if (!state.has_value()) {
element->set_face_state({}, submission_value);
}
// 5. Otherwise, if state is a FormData object, set element's state to a clone of state's entry list.
// 6. Otherwise, set element's state to state.
else {
auto state_value = state.value().visit(
[](GC::Root<FileAPI::File> const& file) -> FormAssociatedElement::FACESubmissionValue {
return GC::Ref { *file };
},
[](String const& string) -> FormAssociatedElement::FACESubmissionValue {
return string;
},
[](GC::Root<XHR::FormData> const& form_data) -> FormAssociatedElement::FACESubmissionValue {
return form_data->entry_list();
},
[](Empty const& empty) -> FormAssociatedElement::FACESubmissionValue {
return empty;
});
element->set_face_state({}, state_value);
}
return {};
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-form
WebIDL::ExceptionOr<GC::Ptr<HTMLFormElement>> 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"_utf16);
return m_target_element->form();
}
static bool validity_flags_has_one_or_more_true_values(Bindings::ValidityStateFlags const& flags)
{
return flags.value_missing
|| flags.type_mismatch
|| flags.pattern_mismatch
|| flags.too_long
|| flags.too_short
|| flags.range_underflow
|| flags.range_overflow
|| flags.step_mismatch
|| flags.bad_input
|| flags.custom_error;
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-setvalidity
WebIDL::ExceptionOr<void> ElementInternals::set_validity(Bindings::ValidityStateFlags const& flags, Optional<String> message, GC::Ptr<HTMLElement> 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"_utf16);
// 3. If flags contains one or more true values and message is not given or is the empty string, then throw a TypeError.
if (validity_flags_has_one_or_more_true_values(flags) && (!message.has_value() || message->is_empty())) {
return WebIDL::SimpleException {
WebIDL::SimpleExceptionType::TypeError,
"Invalid flag(s) and empty message"sv
};
}
// 4. For each entry flag → value of flags, set element's validity flag with the name flag to value.
element->set_face_validity_flags({}, flags);
// 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.
String validation_message;
if (message.has_value() && validity_flags_has_one_or_more_true_values(flags))
validation_message = message.release_value();
element->set_face_validation_message({}, validation_message);
// 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.
element->set_custom_validity_error_message({}, flags.custom_error ? validation_message : ""_string);
// 7. If anchor is not given, then set it to element.
if (!anchor) {
anchor = element;
}
// 8. Otherwise, if anchor is not a shadow-including inclusive descendant of element, then throw a "NotFoundError" DOMException.
else if (!anchor->is_shadow_including_inclusive_descendant_of(element)) {
return WebIDL::NotFoundError::create(realm(), "Anchor is not a shadow-including descendant of element"_utf16);
}
// 9. Set element's validation anchor to anchor.
element->set_face_validation_anchor({}, anchor);
return {};
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-willvalidate
WebIDL::ExceptionOr<bool> 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"_utf16);
return m_target_element->is_candidate_for_constraint_validation();
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-validity
WebIDL::ExceptionOr<GC::Ref<ValidityState const>> 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"_utf16);
return ValidityState::create(realm(), m_target_element);
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-validationmessage
WebIDL::ExceptionOr<String> 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"_utf16);
// 3. Return element's validation message.
return element->face_validation_message();
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-checkvalidity
WebIDL::ExceptionOr<bool> 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"_utf16);
// 3. Run the check validity steps on element.
return element->check_validity_steps();
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-elementinternals-reportvalidity
WebIDL::ExceptionOr<bool> 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"_utf16);
// 3. Run the report validity steps on element.
return element->report_validity_steps();
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-elementinternals-labels
WebIDL::ExceptionOr<GC::Ptr<DOM::NodeList>> 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"_utf16);
return m_target_element->labels();
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-states
GC::Ptr<CustomStateSet> ElementInternals::states()
{
// The states getter steps are to return this's target element's states set.
return m_target_element->ensure_custom_state_set();
}
void ElementInternals::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(ElementInternals);
Base::initialize(realm);
}
void ElementInternals::visit_edges(JS::Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_target_element);
}
}