FormAssociatedElement.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /*
  2. * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/DOM/Document.h>
  7. #include <LibWeb/HTML/FormAssociatedElement.h>
  8. #include <LibWeb/HTML/HTMLButtonElement.h>
  9. #include <LibWeb/HTML/HTMLFieldSetElement.h>
  10. #include <LibWeb/HTML/HTMLFormElement.h>
  11. #include <LibWeb/HTML/HTMLInputElement.h>
  12. #include <LibWeb/HTML/HTMLLegendElement.h>
  13. #include <LibWeb/HTML/HTMLSelectElement.h>
  14. #include <LibWeb/HTML/HTMLTextAreaElement.h>
  15. #include <LibWeb/HTML/Parser/HTMLParser.h>
  16. namespace Web::HTML {
  17. void FormAssociatedElement::set_form(HTMLFormElement* form)
  18. {
  19. if (m_form)
  20. m_form->remove_associated_element({}, form_associated_element_to_html_element());
  21. m_form = form;
  22. if (m_form)
  23. m_form->add_associated_element({}, form_associated_element_to_html_element());
  24. }
  25. bool FormAssociatedElement::enabled() const
  26. {
  27. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled
  28. auto const& html_element = const_cast<FormAssociatedElement&>(*this).form_associated_element_to_html_element();
  29. // A form control is disabled if any of the following conditions are met:
  30. // 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).
  31. // FIXME: This doesn't check for form-associated custom elements.
  32. if ((is<HTMLButtonElement>(html_element) || is<HTMLInputElement>(html_element) || is<HTMLSelectElement>(html_element) || is<HTMLTextAreaElement>(html_element)) && html_element.has_attribute(HTML::AttributeNames::disabled))
  33. return false;
  34. // 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.
  35. for (auto* fieldset_ancestor = html_element.first_ancestor_of_type<HTMLFieldSetElement>(); fieldset_ancestor; fieldset_ancestor = fieldset_ancestor->first_ancestor_of_type<HTMLFieldSetElement>()) {
  36. if (fieldset_ancestor->has_attribute(HTML::AttributeNames::disabled)) {
  37. auto* first_legend_element_child = fieldset_ancestor->first_child_of_type<HTMLLegendElement>();
  38. if (!first_legend_element_child || !html_element.is_descendant_of(*first_legend_element_child))
  39. return false;
  40. }
  41. }
  42. return true;
  43. }
  44. void FormAssociatedElement::set_parser_inserted(Badge<HTMLParser>)
  45. {
  46. m_parser_inserted = true;
  47. }
  48. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:nodes-are-inserted
  49. void FormAssociatedElement::form_node_was_inserted()
  50. {
  51. // 1. If the form-associated element's parser inserted flag is set, then return.
  52. if (m_parser_inserted)
  53. return;
  54. // 2. Reset the form owner of the form-associated element.
  55. reset_form_owner();
  56. }
  57. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:nodes-are-removed
  58. void FormAssociatedElement::form_node_was_removed()
  59. {
  60. // 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.
  61. if (m_form && &form_associated_element_to_html_element().root() != &m_form->root())
  62. reset_form_owner();
  63. }
  64. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-3
  65. void FormAssociatedElement::form_node_attribute_changed(FlyString const& name, Optional<String> const& value)
  66. {
  67. // When a listed form-associated element's form attribute is set, changed, or removed, then the user agent must
  68. // reset the form owner of that element.
  69. if (name == HTML::AttributeNames::form) {
  70. auto& html_element = form_associated_element_to_html_element();
  71. if (value.has_value())
  72. html_element.document().add_form_associated_element_with_form_attribute(*this);
  73. else
  74. html_element.document().remove_form_associated_element_with_form_attribute(*this);
  75. reset_form_owner();
  76. }
  77. }
  78. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-4
  79. void FormAssociatedElement::element_id_changed(Badge<DOM::Document>)
  80. {
  81. // When a listed form-associated element has a form attribute and the ID of any of the elements in the tree changes,
  82. // then the user agent must reset the form owner of that form-associated element.
  83. VERIFY(form_associated_element_to_html_element().has_attribute(HTML::AttributeNames::form));
  84. reset_form_owner();
  85. }
  86. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-5
  87. void FormAssociatedElement::element_with_id_was_added_or_removed(Badge<DOM::Document>)
  88. {
  89. // When a listed form-associated element has a form attribute and an element with an ID is inserted into or removed
  90. // from the Document, then the user agent must reset the form owner of that form-associated element.
  91. VERIFY(form_associated_element_to_html_element().has_attribute(HTML::AttributeNames::form));
  92. reset_form_owner();
  93. }
  94. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#reset-the-form-owner
  95. void FormAssociatedElement::reset_form_owner()
  96. {
  97. auto& html_element = form_associated_element_to_html_element();
  98. // 1. Unset element's parser inserted flag.
  99. m_parser_inserted = false;
  100. // 2. If all of the following conditions are true
  101. // - element's form owner is not null
  102. // - element is not listed or its form content attribute is not present
  103. // - element's form owner is its nearest form element ancestor after the change to the ancestor chain
  104. // then do nothing, and return.
  105. if (m_form
  106. && (!is_listed() || !html_element.has_attribute(HTML::AttributeNames::form))
  107. && html_element.first_ancestor_of_type<HTMLFormElement>() == m_form.ptr()) {
  108. return;
  109. }
  110. // 3. Set element's form owner to null.
  111. set_form(nullptr);
  112. // 4. If element is listed, has a form content attribute, and is connected, then:
  113. if (is_listed() && html_element.has_attribute(HTML::AttributeNames::form) && html_element.is_connected()) {
  114. // 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.
  115. auto form_value = html_element.attribute(HTML::AttributeNames::form);
  116. html_element.root().for_each_in_inclusive_subtree_of_type<HTMLFormElement>([this, &form_value](HTMLFormElement& form_element) {
  117. if (form_element.id() == form_value) {
  118. set_form(&form_element);
  119. return IterationDecision::Break;
  120. }
  121. return IterationDecision::Continue;
  122. });
  123. }
  124. // 5. Otherwise, if element has an ancestor form element, then associate element with the nearest such ancestor form element.
  125. else {
  126. auto* form_ancestor = html_element.first_ancestor_of_type<HTMLFormElement>();
  127. if (form_ancestor)
  128. set_form(form_ancestor);
  129. }
  130. }
  131. }