mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 09:45:06 +02:00
LibWeb: Queue form{Associated,Disabled}Callback where appropriate
This commit is contained in:
Notes:
github-actions[bot]
2026-03-25 13:21:11 +00:00
Author: https://github.com/Lubrsi Commit: https://github.com/LadybirdBrowser/ladybird/commit/4cea5d43e6a Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8534 Reviewed-by: https://github.com/AtkinsSJ ✅ Reviewed-by: https://github.com/tcl3
@@ -104,8 +104,7 @@ bool FormAssociatedElement::enabled() const
|
||||
|
||||
// A form control is disabled if any of the following are true:
|
||||
// - The element is a button, input, select, textarea, or form-associated custom element, and the disabled attribute is specified on this element (regardless of its value); or
|
||||
// FIXME: This doesn't check for form-associated custom elements.
|
||||
if ((is<HTMLButtonElement>(html_element) || is<HTMLInputElement>(html_element) || is<HTMLSelectElement>(html_element) || is<HTMLTextAreaElement>(html_element)) && html_element.has_attribute(HTML::AttributeNames::disabled))
|
||||
if ((is<HTMLButtonElement>(html_element) || is<HTMLInputElement>(html_element) || is<HTMLSelectElement>(html_element) || is<HTMLTextAreaElement>(html_element) || form_associated_element_to_html_element().is_form_associated_custom_element()) && html_element.has_attribute(HTML::AttributeNames::disabled))
|
||||
return false;
|
||||
|
||||
// - The element is a descendant of a fieldset element whose disabled attribute is specified, and is not a descendant of that fieldset element's first legend element child, if any.
|
||||
@@ -206,6 +205,14 @@ void FormAssociatedElement::reset_form_owner()
|
||||
return;
|
||||
}
|
||||
|
||||
// AD-HOC: The spec states:
|
||||
// - When the user agent resets the form owner of a form-associated custom element and doing so changes the
|
||||
// form owner, its formAssociatedCallback is called, given the new form owner (or null if no owner) as an
|
||||
// argument.
|
||||
// However, this is not specified as algorithmic steps, so we have to do this ourselves.
|
||||
// Spec issue: https://github.com/whatwg/html/issues/12169
|
||||
GC::Ptr<HTMLFormElement> old_form { m_form.ptr() };
|
||||
|
||||
// 3. Set element's form owner to null.
|
||||
set_form(nullptr);
|
||||
|
||||
@@ -230,6 +237,55 @@ void FormAssociatedElement::reset_form_owner()
|
||||
if (form_ancestor)
|
||||
set_form(form_ancestor);
|
||||
}
|
||||
|
||||
// See the AD-HOC comment above.
|
||||
if (m_form != old_form && html_element.is_form_associated_custom_element()) {
|
||||
GC::RootVector<JS::Value> arguments { html_element.heap() };
|
||||
arguments.append(JS::Value(m_form.ptr()));
|
||||
html_element.enqueue_a_custom_element_callback_reaction(CustomElementReactionNames::formAssociatedCallback, move(arguments));
|
||||
}
|
||||
}
|
||||
|
||||
void FormAssociatedElement::form_associated_element_was_inserted()
|
||||
{
|
||||
update_face_disabled_state();
|
||||
}
|
||||
|
||||
void FormAssociatedElement::form_associated_element_was_removed(DOM::Node*)
|
||||
{
|
||||
update_face_disabled_state();
|
||||
}
|
||||
|
||||
void FormAssociatedElement::form_associated_element_was_moved(GC::Ptr<DOM::Node>)
|
||||
{
|
||||
update_face_disabled_state();
|
||||
}
|
||||
|
||||
void FormAssociatedElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const&, Optional<String> const&, Optional<FlyString> const&)
|
||||
{
|
||||
if (name == HTML::AttributeNames::disabled)
|
||||
update_face_disabled_state();
|
||||
}
|
||||
|
||||
// AD-HOC: The specification states that when the disabled state of a form-associated custom element is changed,
|
||||
// its formDisabledCallback should be enqueued. However, there are no explicit algorithmic steps for this
|
||||
// outside of "upgrade an element". We track the disabled state and enqueue the callback when it changes.
|
||||
// See: https://github.com/whatwg/html/issues/12169
|
||||
void FormAssociatedElement::update_face_disabled_state()
|
||||
{
|
||||
auto& html_element = form_associated_element_to_html_element();
|
||||
if (!html_element.is_form_associated_custom_element())
|
||||
return;
|
||||
|
||||
bool is_disabled = !enabled();
|
||||
if (is_disabled == m_face_disabled_state)
|
||||
return;
|
||||
|
||||
m_face_disabled_state = is_disabled;
|
||||
|
||||
GC::RootVector<JS::Value> arguments { html_element.heap() };
|
||||
arguments.append(JS::Value(is_disabled));
|
||||
html_element.enqueue_a_custom_element_callback_reaction(CustomElementReactionNames::formDisabledCallback, move(arguments));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webdriver/#dfn-clear-algorithm
|
||||
|
||||
@@ -120,14 +120,18 @@ public:
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#mutability
|
||||
virtual bool is_mutable() const { return true; }
|
||||
|
||||
void reset_form_owner();
|
||||
|
||||
void update_face_disabled_state();
|
||||
|
||||
protected:
|
||||
FormAssociatedElement() = default;
|
||||
virtual ~FormAssociatedElement() = default;
|
||||
|
||||
virtual void form_associated_element_was_inserted() { }
|
||||
virtual void form_associated_element_was_removed(DOM::Node*) { }
|
||||
virtual void form_associated_element_was_moved(GC::Ptr<DOM::Node>) { }
|
||||
virtual void form_associated_element_attribute_changed(FlyString const&, Optional<String> const&, Optional<String> const&, Optional<FlyString> const&) { }
|
||||
virtual void form_associated_element_was_inserted();
|
||||
virtual void form_associated_element_was_removed(DOM::Node*);
|
||||
virtual void form_associated_element_was_moved(GC::Ptr<DOM::Node>);
|
||||
virtual void form_associated_element_attribute_changed(FlyString const&, Optional<String> const&, Optional<String> const&, Optional<FlyString> const&);
|
||||
|
||||
void form_node_was_inserted();
|
||||
void form_node_was_removed();
|
||||
@@ -135,13 +139,15 @@ protected:
|
||||
void form_node_attribute_changed(FlyString const&, Optional<String> const&);
|
||||
|
||||
private:
|
||||
void reset_form_owner();
|
||||
|
||||
GC::Weak<HTMLFormElement> m_form;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#parser-inserted-flag
|
||||
bool m_parser_inserted { false };
|
||||
|
||||
// AD-HOC: Cached disabled state for form-associated custom elements, used to detect changes
|
||||
// and enqueue formDisabledCallback. Only meaningful for FACEs.
|
||||
bool m_face_disabled_state { false };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#custom-validity-error-message
|
||||
String m_custom_validity_error_message;
|
||||
};
|
||||
|
||||
@@ -59,6 +59,19 @@ bool HTMLFieldSetElement::is_disabled() const
|
||||
return false;
|
||||
}
|
||||
|
||||
void HTMLFieldSetElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
|
||||
{
|
||||
Base::attribute_changed(name, old_value, value, namespace_);
|
||||
|
||||
if (name == HTML::AttributeNames::disabled) {
|
||||
for_each_in_subtree_of_type<HTMLElement>([](auto& element) {
|
||||
if (element.is_form_associated_custom_element())
|
||||
element.update_face_disabled_state();
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-fieldset-elements
|
||||
GC::Ptr<DOM::HTMLCollection> const& HTMLFieldSetElement::elements()
|
||||
{
|
||||
|
||||
@@ -52,6 +52,8 @@ private:
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
virtual void attribute_changed(FlyString const&, Optional<String> const&, Optional<String> const&, Optional<FlyString> const&) override;
|
||||
|
||||
virtual bool is_html_fieldset_element() const override { return true; }
|
||||
|
||||
GC::Ptr<DOM::HTMLCollection> m_elements;
|
||||
|
||||
Reference in New Issue
Block a user