StackOfOpenElements.cpp 6.2 KB

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