/* * Copyright (c) 2021, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include namespace Web::HTML { void FormAssociatedElement::set_form(HTMLFormElement* form) { if (m_form) m_form->remove_associated_element({}, form_associated_element_to_html_element()); m_form = form; if (m_form) m_form->add_associated_element({}, form_associated_element_to_html_element()); } bool FormAssociatedElement::enabled() const { // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled auto const& html_element = form_associated_element_to_html_element(); // A form control is disabled if any of the following conditions are met: // 1. 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). // FIXME: This doesn't check for form-associated custom elements. if ((is(html_element) || is(html_element) || is(html_element) || is(html_element)) && html_element.has_attribute(HTML::AttributeNames::disabled)) return false; // 2. 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. for (auto* fieldset_ancestor = html_element.first_ancestor_of_type(); fieldset_ancestor; fieldset_ancestor = fieldset_ancestor->first_ancestor_of_type()) { if (fieldset_ancestor->has_attribute(HTML::AttributeNames::disabled)) { auto* first_legend_element_child = fieldset_ancestor->first_child_of_type(); if (!first_legend_element_child || !html_element.is_descendant_of(*first_legend_element_child)) return false; } } return true; } void FormAssociatedElement::set_parser_inserted(Badge) { m_parser_inserted = true; } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:nodes-are-inserted void FormAssociatedElement::form_node_was_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::form_node_was_removed() { // 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 && &form_associated_element_to_html_element().root() != &m_form->root()) reset_form_owner(); } // 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& 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(); // 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() || !html_element.has_attribute(HTML::AttributeNames::form)) && html_element.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() && html_element.has_attribute(HTML::AttributeNames::form) && html_element.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 = html_element.attribute(HTML::AttributeNames::form); html_element.root().for_each_in_inclusive_subtree_of_type([this, &form_value](HTMLFormElement& form_element) { if (form_element.id() == form_value) { set_form(&form_element); return TraversalDecision::Break; } return TraversalDecision::Continue; }); } // 5. Otherwise, if element has an ancestor form element, then associate element with the nearest such ancestor form element. else { auto* form_ancestor = html_element.first_ancestor_of_type(); if (form_ancestor) set_form(form_ancestor); } } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionstart WebIDL::UnsignedLong FormAssociatedElement::selection_start() const { // 1. If this element is an input element, and selectionStart does not apply to this element, return null. // NOTE: This is done by HTMLInputElement before calling this function // 2. If there is no selection, return the code unit offset within the relevant value to the character that // immediately follows the text entry cursor. if (auto cursor = form_associated_element_to_html_element().document().cursor_position()) return cursor->offset(); // FIXME: 3. Return the code unit offset within the relevant value to the character that immediately follows the start of // the selection. return 0; } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionstart-2 WebIDL::ExceptionOr FormAssociatedElement::set_selection_start(Optional const&) { // 1. If this element is an input element, and selectionStart does not apply to this element, throw an // "InvalidStateError" DOMException. // NOTE: This is done by HTMLInputElement before calling this function // FIXME: 2. Let end be the value of this element's selectionEnd attribute. // FIXME: 3. If end is less than the given value, set end to the given value. // FIXME: 4. Set the selection range with the given value, end, and the value of this element's selectionDirection attribute. return {}; } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionend WebIDL::UnsignedLong FormAssociatedElement::selection_end() const { // 1. If this element is an input element, and selectionEnd does not apply to this element, return null. // NOTE: This is done by HTMLInputElement before calling this function // 2. If there is no selection, return the code unit offset within the relevant value to the character that // immediately follows the text entry cursor. if (auto cursor = form_associated_element_to_html_element().document().cursor_position()) return cursor->offset(); // FIXME: 3. Return the code unit offset within the relevant value to the character that immediately follows the end of // the selection. return 0; } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionend-3 WebIDL::ExceptionOr FormAssociatedElement::set_selection_end(Optional const&) { // 1. If this element is an input element, and selectionEnd does not apply to this element, throw an // "InvalidStateError" DOMException. // NOTE: This is done by HTMLInputElement before calling this function // FIXME: 2. Set the selection range with the value of this element's selectionStart attribute, the given value, and the // value of this element's selectionDirection attribute. return {}; } }