diff --git a/Tests/LibWeb/Text/expected/HTML/Form-named-property-access.txt b/Tests/LibWeb/Text/expected/HTML/Form-named-property-access.txt index dee9ba8cbde..3476db93e73 100644 --- a/Tests/LibWeb/Text/expected/HTML/Form-named-property-access.txt +++ b/Tests/LibWeb/Text/expected/HTML/Form-named-property-access.txt @@ -1,4 +1,4 @@ - == Elements and Names == + == Elements and Names == formy.length: 12 elements.length: 12 elements[0] === form.foo @@ -38,3 +38,6 @@ elements in changeForFormAttribute: 1 elements in changeForFormAttribute: 0 elements in changeForFormAttribute: 1 elements in changeForFormAttribute: 0 +== Form element appears after a form-associated element == +elements in formAfterInput: 1 +typeof formAfterInput.inputBeforeForm: object diff --git a/Tests/LibWeb/Text/input/HTML/Form-named-property-access.html b/Tests/LibWeb/Text/input/HTML/Form-named-property-access.html index 8b492768e73..1f928a5ae06 100644 --- a/Tests/LibWeb/Text/input/HTML/Form-named-property-access.html +++ b/Tests/LibWeb/Text/input/HTML/Form-named-property-access.html @@ -63,6 +63,9 @@
+ +
+ diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index cc5e9e6a661..f283e0e0692 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -449,6 +449,9 @@ void Document::visit_edges(Cell::Visitor& visitor) visitor.visit(timeline); visitor.visit(m_list_of_available_images); + + for (auto* form_associated_element : m_form_associated_elements_with_form_attribute) + visitor.visit(form_associated_element->form_associated_element_to_html_element()); } // https://w3c.github.io/selection-api/#dom-document-getselection @@ -3683,4 +3686,28 @@ void Document::append_pending_animation_event(Web::DOM::Document::PendingAnimati m_pending_animation_event_queue.append(event); } +void Document::element_id_changed(Badge) +{ + for (auto* form_associated_element : m_form_associated_elements_with_form_attribute) + form_associated_element->element_id_changed({}); +} + +void Document::element_with_id_was_added_or_removed(Badge) +{ + for (auto* form_associated_element : m_form_associated_elements_with_form_attribute) + form_associated_element->element_with_id_was_added_or_removed({}); +} + +void Document::add_form_associated_element_with_form_attribute(HTML::FormAssociatedElement& form_associated_element) +{ + m_form_associated_elements_with_form_attribute.append(&form_associated_element); +} + +void Document::remove_form_associated_element_with_form_attribute(HTML::FormAssociatedElement& form_associated_element) +{ + m_form_associated_elements_with_form_attribute.remove_all_matching([&](auto* element) { + return element == &form_associated_element; + }); +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 3d825472859..778ecb4b99f 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -554,6 +554,12 @@ public: JS::GCPtr latest_entry() const { return m_latest_entry; } void set_latest_entry(JS::GCPtr e) { m_latest_entry = e; } + void element_id_changed(Badge); + void element_with_id_was_added_or_removed(Badge); + + void add_form_associated_element_with_form_attribute(HTML::FormAssociatedElement&); + void remove_form_associated_element_with_form_attribute(HTML::FormAssociatedElement&); + protected: virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; @@ -775,6 +781,8 @@ private: // https://html.spec.whatwg.org/multipage/browsing-the-web.html#scripts-may-run-for-the-newly-created-document bool m_ready_to_run_scripts { false }; + + Vector m_form_associated_elements_with_form_attribute; }; template<> diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index e129c24e96f..9bc01bccfca 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -465,6 +465,8 @@ void Element::attribute_changed(FlyString const& name, Optional const& v m_id = {}; else m_id = value_or_empty; + + document().element_id_changed({}); } else if (name == HTML::AttributeNames::name) { if (!value.has_value()) m_name = {}; @@ -1026,6 +1028,22 @@ int Element::client_height() const return paintable_box()->absolute_padding_box_rect().height().to_int(); } +void Element::inserted() +{ + Base::inserted(); + + if (m_id.has_value()) + document().element_with_id_was_added_or_removed({}); +} + +void Element::removed_from(Node* node) +{ + Base::removed_from(node); + + if (m_id.has_value()) + document().element_with_id_was_added_or_removed({}); +} + void Element::children_changed() { Node::children_changed(); diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index fa1218c52ea..6a0e1b7ccc3 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -375,6 +375,8 @@ protected: Element(Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; + virtual void inserted() override; + virtual void removed_from(Node*) override; virtual void children_changed() override; virtual i32 default_tab_index_value() const; diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp index 333d424830a..7f375ba35b8 100644 --- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -73,24 +74,45 @@ void FormAssociatedElement::form_node_was_removed() } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-3 -void FormAssociatedElement::form_node_attribute_changed(FlyString const& name, Optional const&) +void FormAssociatedElement::form_node_attribute_changed(FlyString const& name, Optional const& value) { // When a listed form-associated element's form attribute is set, changed, or removed, then the user agent must // reset the form owner of that element. if (name == HTML::AttributeNames::form) { + auto& html_element = form_associated_element_to_html_element(); + + if (value.has_value()) + html_element.document().add_form_associated_element_with_form_attribute(*this); + else + html_element.document().remove_form_associated_element_with_form_attribute(*this); + reset_form_owner(); } } +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-4 +void FormAssociatedElement::element_id_changed(Badge) +{ + // When a listed form-associated element has a form attribute and the ID of any of the elements in the tree changes, + // then the user agent must reset the form owner of that form-associated element. + VERIFY(form_associated_element_to_html_element().has_attribute(HTML::AttributeNames::form)); + reset_form_owner(); +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-5 +void FormAssociatedElement::element_with_id_was_added_or_removed(Badge) +{ + // When a listed form-associated element has a form attribute and an element with an ID is inserted into or removed + // from the Document, then the user agent must reset the form owner of that form-associated element. + VERIFY(form_associated_element_to_html_element().has_attribute(HTML::AttributeNames::form)); + reset_form_owner(); +} + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#reset-the-form-owner void FormAssociatedElement::reset_form_owner() { auto& html_element = form_associated_element_to_html_element(); - // Although these aren't in the "reset form owner" algorithm, these here as they are triggers for this algorithm: - // FIXME: When a listed form-associated element has a form attribute and the ID of any of the elements in the tree changes, then the user agent must reset the form owner of that form-associated element. - // FIXME: When a listed form-associated element has a form attribute and an element with an ID is inserted into or removed from the Document, then the user agent must reset the form owner of that form-associated element. - // 1. Unset element's parser inserted flag. m_parser_inserted = false; diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h index 90a69f76106..47a33e765df 100644 --- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -55,6 +55,9 @@ public: void set_form(HTMLFormElement*); + void element_id_changed(Badge); + void element_with_id_was_added_or_removed(Badge); + bool enabled() const; void set_parser_inserted(Badge);