StackOfOpenElements.cpp 6.0 KB

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