diff --git a/Userland/Libraries/LibWeb/HTML/AttributeNames.h b/Userland/Libraries/LibWeb/HTML/AttributeNames.h
index f43a144daa..9dd77e6199 100644
--- a/Userland/Libraries/LibWeb/HTML/AttributeNames.h
+++ b/Userland/Libraries/LibWeb/HTML/AttributeNames.h
@@ -61,6 +61,7 @@ namespace AttributeNames {
__ENUMERATE_HTML_ATTRIBUTE(event) \
__ENUMERATE_HTML_ATTRIBUTE(face) \
__ENUMERATE_HTML_ATTRIBUTE(for_) \
+ __ENUMERATE_HTML_ATTRIBUTE(form) \
__ENUMERATE_HTML_ATTRIBUTE(formnovalidate) \
__ENUMERATE_HTML_ATTRIBUTE(formtarget) \
__ENUMERATE_HTML_ATTRIBUTE(frame) \
diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
index 4e624cd0a3..5b9c2dd8a0 100644
--- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
@@ -45,4 +45,74 @@ bool FormAssociatedElement::enabled() const
return true;
}
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:nodes-are-inserted
+void FormAssociatedElement::inserted()
+{
+ HTMLElement::inserted();
+
+ // 1. If the form-associated element's parser inserted flag is set, then return.
+ if (m_parser_inserted)
+ return;
+
+ // 2. Reset the form owner of the form-associated element.
+ reset_form_owner();
+}
+
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:nodes-are-removed
+void FormAssociatedElement::removed_from(DOM::Node* node)
+{
+ HTMLElement::removed_from(node);
+
+ // 1. If the form-associated element has a form owner and the form-associated element and its form owner are no longer in the same tree, then reset the form owner of the form-associated element.
+ if (m_form && &root() != &m_form->root())
+ reset_form_owner();
+}
+
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#reset-the-form-owner
+void FormAssociatedElement::reset_form_owner()
+{
+ // 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's form attribute is set, changed, or removed, then the user agent must reset the form owner of that element.
+ // 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;
+
+ // 2. If all of the following conditions are true
+ // - element's form owner is not null
+ // - element is not listed or its form content attribute is not present
+ // - element's form owner is its nearest form element ancestor after the change to the ancestor chain
+ // then do nothing, and return.
+ if (m_form
+ && (!is_listed() || !has_attribute(HTML::AttributeNames::form))
+ && first_ancestor_of_type() == m_form.ptr()) {
+ return;
+ }
+
+ // 3. Set element's form owner to null.
+ set_form(nullptr);
+
+ // 4. If element is listed, has a form content attribute, and is connected, then:
+ if (is_listed() && has_attribute(HTML::AttributeNames::form) && is_connected()) {
+ // 1. If the first element in element's tree, in tree order, to have an ID that is identical to element's form content attribute's value, is a form element, then associate the element with that form element.
+ auto form_value = attribute(HTML::AttributeNames::form);
+ root().for_each_in_inclusive_subtree_of_type([this, &form_value](HTMLFormElement& form_element) mutable {
+ if (form_element.attribute(HTML::AttributeNames::id) == form_value) {
+ set_form(&form_element);
+ return IterationDecision::Break;
+ }
+
+ return IterationDecision::Continue;
+ });
+ }
+
+ // 5. Otherwise, if element has an ancestor form element, then associate element with the nearest such ancestor form element.
+ else {
+ auto* form_ancestor = first_ancestor_of_type();
+ if (form_ancestor)
+ set_form(form_ancestor);
+ }
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
index 07ec1629c6..9342244111 100644
--- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
+++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
@@ -21,6 +21,8 @@ public:
bool enabled() const;
+ void set_parser_inserted(Badge) { m_parser_inserted = true; }
+
// https://html.spec.whatwg.org/multipage/forms.html#category-listed
virtual bool is_listed() const { return false; }
@@ -43,6 +45,15 @@ protected:
private:
WeakPtr m_form;
+
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#parser-inserted-flag
+ bool m_parser_inserted { false };
+
+ void reset_form_owner();
+
+ // ^DOM::Node
+ virtual void inserted() override;
+ virtual void removed_from(DOM::Node*) override;
};
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
index 9605eaf1df..535c405ed5 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
@@ -191,18 +191,6 @@ bool HTMLInputElement::is_focusable() const
return m_text_node;
}
-void HTMLInputElement::inserted()
-{
- HTMLElement::inserted();
- set_form(first_ancestor_of_type());
-}
-
-void HTMLInputElement::removed_from(DOM::Node* old_parent)
-{
- HTMLElement::removed_from(old_parent);
- set_form(nullptr);
-}
-
void HTMLInputElement::parse_attribute(FlyString const& name, String const& value)
{
FormAssociatedElement::parse_attribute(name, value);
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
index 140310d2cc..5027ea12a4 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
@@ -101,10 +101,6 @@ public:
virtual bool is_labelable() const override { return type_state() != TypeAttributeState::Hidden; }
private:
- // ^DOM::Node
- virtual void inserted() override;
- virtual void removed_from(Node*) override;
-
// ^DOM::EventTarget
virtual void did_receive_focus() override;
virtual void run_activation_behavior() override;
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp
index 84cbaabf45..4cfa72a0ee 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp
@@ -19,16 +19,4 @@ HTMLSelectElement::~HTMLSelectElement()
{
}
-void HTMLSelectElement::inserted()
-{
- HTMLElement::inserted();
- set_form(first_ancestor_of_type());
-}
-
-void HTMLSelectElement::removed_from(DOM::Node* old_parent)
-{
- HTMLElement::removed_from(old_parent);
- set_form(nullptr);
-}
-
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.h b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.h
index b6425b3dd3..e26d0e2bef 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.h
@@ -36,11 +36,6 @@ public:
// ^HTMLElement
// https://html.spec.whatwg.org/multipage/forms.html#category-label
virtual bool is_labelable() const override { return true; }
-
-private:
- // ^DOM::Node
- virtual void inserted() override;
- virtual void removed_from(DOM::Node*) override;
};
}
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp
index 4c2e4dda3f..351059e361 100644
--- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp
@@ -490,7 +490,7 @@ void HTMLParser::handle_before_html(HTMLToken& token)
}
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
- auto element = create_element_for(token, Namespace::HTML);
+ auto element = create_element_for(token, Namespace::HTML, document());
document().append_child(element);
m_stack_of_open_elements.push(move(element));
m_insertion_mode = InsertionMode::BeforeHead;
@@ -586,13 +586,64 @@ HTMLParser::AdjustedInsertionLocation HTMLParser::find_appropriate_place_for_ins
return adjusted_insertion_location;
}
-NonnullRefPtr HTMLParser::create_element_for(const HTMLToken& token, const FlyString& namespace_)
+NonnullRefPtr HTMLParser::create_element_for(HTMLToken const& token, FlyString const& namespace_, DOM::Node const& intended_parent)
{
- auto element = create_element(document(), token.tag_name(), namespace_);
+ // FIXME: 1. If the active speculative HTML parser is not null, then return the result of creating a speculative mock element given given namespace, the tag name of the given token, and the attributes of the given token.
+ // FIXME: 2. Otherwise, optionally create a speculative mock element given given namespace, the tag name of the given token, and the attributes of the given token.
+
+ // 3. Let document be intended parent's node document.
+ NonnullRefPtr document = intended_parent.document();
+
+ // 4. Let local name be the tag name of the token.
+ auto local_name = token.tag_name();
+
+ // FIXME: 5. Let is be the value of the "is" attribute in the given token, if such an attribute exists, or null otherwise.
+ // FIXME: 6. Let definition be the result of looking up a custom element definition given document, given namespace, local name, and is.
+ // FIXME: 7. If definition is non-null and the parser was not created as part of the HTML fragment parsing algorithm, then let will execute script be true. Otherwise, let it be false.
+ // FIXME: 8. If will execute script is true, then:
+ // FIXME: 1. Increment document's throw-on-dynamic-markup-insertion counter.
+ // FIXME: 2. If the JavaScript execution context stack is empty, then perform a microtask checkpoint.
+ // FIXME: 3. Push a new element queue onto document's relevant agent's custom element reactions stack.
+
+ // 9. Let element be the result of creating an element given document, localName, given namespace, null, and is.
+ // FIXME: If will execute script is true, set the synchronous custom elements flag; otherwise, leave it unset.
+ // FIXME: Pass in `null` and `is`.
+ auto element = create_element(document, local_name, namespace_);
+
+ // 10. Append each attribute in the given token to element.
+ // FIXME: This isn't the exact `append` the spec is talking about.
token.for_each_attribute([&](auto& attribute) {
element->set_attribute(attribute.local_name, attribute.value);
return IterationDecision::Continue;
});
+
+ // FIXME: 11. If will execute script is true, then:
+ // FIXME: 1. Let queue be the result of popping from document's relevant agent's custom element reactions stack. (This will be the same element queue as was pushed above.)
+ // FIXME: 2. Invoke custom element reactions in queue.
+ // FIXME: 3. Decrement document's throw-on-dynamic-markup-insertion counter.
+
+ // FIXME: 12. If element has an xmlns attribute in the XMLNS namespace whose value is not exactly the same as the element's namespace, that is a parse error.
+ // Similarly, if element has an xmlns:xlink attribute in the XMLNS namespace whose value is not the XLink Namespace, that is a parse error.
+
+ // FIXME: 13. If element is a resettable element, invoke its reset algorithm. (This initializes the element's value and checkedness based on the element's attributes.)
+
+ // 14. If element is a form-associated element and not a form-associated custom element, the form element pointer is not null, there is no template element on the stack of open elements,
+ // element is either not listed or doesn't have a form attribute, and the intended parent is in the same tree as the element pointed to by the form element pointer,
+ // then associate element with the form element pointed to by the form element pointer and set element's parser inserted flag.
+ // FIXME: Check if the element is not a form-associated custom element.
+ if (is(*element)) {
+ auto& form_associated_element = static_cast(*element);
+
+ if (m_form_element
+ && !m_stack_of_open_elements.contains(HTML::TagNames::template_)
+ && (!form_associated_element.is_listed() || !form_associated_element.has_attribute(HTML::AttributeNames::form))
+ && &intended_parent.root() == &m_form_element->root()) {
+ form_associated_element.set_form(m_form_element);
+ form_associated_element.set_parser_inserted({});
+ }
+ }
+
+ // 15. Return element.
return element;
}
@@ -601,8 +652,8 @@ NonnullRefPtr HTMLParser::insert_foreign_element(const HTMLToken&
{
auto adjusted_insertion_location = find_appropriate_place_for_inserting_node();
- // FIXME: Pass in adjusted_insertion_location.parent as the intended parent.
- auto element = create_element_for(token, namespace_);
+ // NOTE: adjusted_insertion_location.parent will be non-null, however, it uses RP to be able to default-initialize HTMLParser::AdjustedInsertionLocation.
+ auto element = create_element_for(token, namespace_, *adjusted_insertion_location.parent);
auto pre_insertion_validity = adjusted_insertion_location.parent->ensure_pre_insertion_validity(element, adjusted_insertion_location.insert_before_sibling);
@@ -735,7 +786,7 @@ void HTMLParser::handle_in_head(HTMLToken& token)
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::script) {
auto adjusted_insertion_location = find_appropriate_place_for_inserting_node();
- auto element = create_element_for(token, Namespace::HTML);
+ auto element = create_element_for(token, Namespace::HTML, *adjusted_insertion_location.parent);
auto& script_element = verify_cast(*element);
script_element.set_parser_document({}, document());
script_element.set_non_blocking({}, false);
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.h b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.h
index 34ffcdead6..c6045223cf 100644
--- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.h
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.h
@@ -113,7 +113,7 @@ private:
void generate_implied_end_tags(const FlyString& exception = {});
void generate_all_implied_end_tags_thoroughly();
- NonnullRefPtr create_element_for(const HTMLToken&, const FlyString& namespace_);
+ NonnullRefPtr create_element_for(HTMLToken const&, FlyString const& namespace_, DOM::Node const& intended_parent);
struct AdjustedInsertionLocation {
RefPtr parent;