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