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);