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