StackOfOpenElements.cpp 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/DOM/Element.h>
  7. #include <LibWeb/HTML/Parser/HTMLParser.h>
  8. #include <LibWeb/HTML/Parser/StackOfOpenElements.h>
  9. namespace Web::HTML {
  10. static Vector<FlyString> s_base_list { "applet", "caption", "html", "table", "td", "th", "marquee", "object", "template" };
  11. StackOfOpenElements::~StackOfOpenElements() = default;
  12. bool StackOfOpenElements::has_in_scope_impl(const FlyString& tag_name, const Vector<FlyString>& list) const
  13. {
  14. for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
  15. auto& node = m_elements.at(i);
  16. if (node.local_name() == tag_name)
  17. return true;
  18. if (list.contains_slow(node.local_name()))
  19. return false;
  20. }
  21. VERIFY_NOT_REACHED();
  22. }
  23. bool StackOfOpenElements::has_in_scope(const FlyString& tag_name) const
  24. {
  25. return has_in_scope_impl(tag_name, s_base_list);
  26. }
  27. bool StackOfOpenElements::has_in_scope_impl(const DOM::Element& target_node, const Vector<FlyString>& list) const
  28. {
  29. for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
  30. auto& node = m_elements.at(i);
  31. if (&node == &target_node)
  32. return true;
  33. if (list.contains_slow(node.local_name()))
  34. return false;
  35. }
  36. VERIFY_NOT_REACHED();
  37. }
  38. bool StackOfOpenElements::has_in_scope(const DOM::Element& target_node) const
  39. {
  40. return has_in_scope_impl(target_node, s_base_list);
  41. }
  42. bool StackOfOpenElements::has_in_button_scope(const FlyString& tag_name) const
  43. {
  44. auto list = s_base_list;
  45. list.append("button");
  46. return has_in_scope_impl(tag_name, list);
  47. }
  48. bool StackOfOpenElements::has_in_table_scope(const FlyString& tag_name) const
  49. {
  50. return has_in_scope_impl(tag_name, { "html", "table", "template" });
  51. }
  52. bool StackOfOpenElements::has_in_list_item_scope(const FlyString& tag_name) const
  53. {
  54. auto list = s_base_list;
  55. list.append("ol");
  56. list.append("ul");
  57. return has_in_scope_impl(tag_name, list);
  58. }
  59. // https://html.spec.whatwg.org/multipage/parsing.html#has-an-element-in-select-scope
  60. // The stack of open elements is said to have a particular element in select scope
  61. // when it has that element in the specific scope consisting of all element types except the following:
  62. // - optgroup in the HTML namespace
  63. // - option in the HTML namespace
  64. // NOTE: In this case it's "all element types _except_"
  65. bool StackOfOpenElements::has_in_select_scope(const FlyString& tag_name) const
  66. {
  67. // https://html.spec.whatwg.org/multipage/parsing.html#has-an-element-in-the-specific-scope
  68. for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
  69. // 1. Initialize node to be the current node (the bottommost node of the stack).
  70. auto& node = m_elements.at(i);
  71. // 2. If node is the target node, terminate in a match state.
  72. if (node.local_name() == tag_name)
  73. return true;
  74. // 3. Otherwise, if node is one of the element types in list, terminate in a failure state.
  75. // NOTE: Here "list" refers to all elements except option and optgroup
  76. if (node.local_name() != HTML::TagNames::option && node.local_name() != HTML::TagNames::optgroup)
  77. return false;
  78. // 4. Otherwise, set node to the previous entry in the stack of open elements and return to step 2.
  79. }
  80. // [4.] (This will never fail, since the loop will always terminate in the previous step if the top of the stack
  81. // — an html element — is reached.)
  82. VERIFY_NOT_REACHED();
  83. }
  84. bool StackOfOpenElements::contains(const DOM::Element& element) const
  85. {
  86. for (auto& element_on_stack : m_elements) {
  87. if (&element == &element_on_stack)
  88. return true;
  89. }
  90. return false;
  91. }
  92. bool StackOfOpenElements::contains(const FlyString& tag_name) const
  93. {
  94. for (auto& element_on_stack : m_elements) {
  95. if (element_on_stack.local_name() == tag_name)
  96. return true;
  97. }
  98. return false;
  99. }
  100. void StackOfOpenElements::pop_until_an_element_with_tag_name_has_been_popped(const FlyString& tag_name)
  101. {
  102. while (m_elements.last().local_name() != tag_name)
  103. (void)pop();
  104. (void)pop();
  105. }
  106. DOM::Element* StackOfOpenElements::topmost_special_node_below(const DOM::Element& formatting_element)
  107. {
  108. DOM::Element* found_element = nullptr;
  109. for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
  110. auto& element = m_elements[i];
  111. if (&element == &formatting_element)
  112. break;
  113. if (HTMLParser::is_special_tag(element.local_name(), element.namespace_()))
  114. found_element = &element;
  115. }
  116. return found_element;
  117. }
  118. StackOfOpenElements::LastElementResult StackOfOpenElements::last_element_with_tag_name(const FlyString& tag_name)
  119. {
  120. for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
  121. auto& element = m_elements[i];
  122. if (element.local_name() == tag_name)
  123. return { &element, i };
  124. }
  125. return { nullptr, -1 };
  126. }
  127. DOM::Element* StackOfOpenElements::element_immediately_above(DOM::Element const& target)
  128. {
  129. bool found_target = false;
  130. for (ssize_t i = m_elements.size() - 1; i >= 0; --i) {
  131. auto& element = m_elements[i];
  132. if (&element == &target) {
  133. found_target = true;
  134. } else if (found_target)
  135. return &element;
  136. }
  137. return nullptr;
  138. }
  139. void StackOfOpenElements::remove(const DOM::Element& element)
  140. {
  141. m_elements.remove_first_matching([&element](DOM::Element const& other) {
  142. return &other == &element;
  143. });
  144. }
  145. void StackOfOpenElements::replace(const DOM::Element& to_remove, NonnullRefPtr<DOM::Element> to_add)
  146. {
  147. for (size_t i = 0; i < m_elements.size(); i++) {
  148. if (&m_elements[i] == &to_remove) {
  149. m_elements.remove(i);
  150. m_elements.insert(i, move(to_add));
  151. break;
  152. }
  153. }
  154. }
  155. void StackOfOpenElements::insert_immediately_below(NonnullRefPtr<DOM::Element> element_to_add, DOM::Element const& target)
  156. {
  157. for (size_t i = 0; i < m_elements.size(); i++) {
  158. if (&m_elements[i] == &target) {
  159. m_elements.insert(i + 1, move(element_to_add));
  160. break;
  161. }
  162. }
  163. }
  164. }