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;