/src/serenity/Userland/Libraries/LibWeb/HTML/HTMLAllCollection.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibJS/Runtime/PropertyKey.h> |
8 | | #include <LibWeb/Bindings/HTMLAllCollectionPrototype.h> |
9 | | #include <LibWeb/Bindings/Intrinsics.h> |
10 | | #include <LibWeb/DOM/Document.h> |
11 | | #include <LibWeb/DOM/Element.h> |
12 | | #include <LibWeb/DOM/ParentNode.h> |
13 | | #include <LibWeb/Forward.h> |
14 | | #include <LibWeb/HTML/HTMLAllCollection.h> |
15 | | #include <LibWeb/HTML/HTMLButtonElement.h> |
16 | | #include <LibWeb/HTML/HTMLEmbedElement.h> |
17 | | #include <LibWeb/HTML/HTMLFormElement.h> |
18 | | #include <LibWeb/HTML/HTMLFrameElement.h> |
19 | | #include <LibWeb/HTML/HTMLFrameSetElement.h> |
20 | | #include <LibWeb/HTML/HTMLIFrameElement.h> |
21 | | #include <LibWeb/HTML/HTMLImageElement.h> |
22 | | #include <LibWeb/HTML/HTMLInputElement.h> |
23 | | #include <LibWeb/HTML/HTMLLinkElement.h> |
24 | | #include <LibWeb/HTML/HTMLMapElement.h> |
25 | | #include <LibWeb/HTML/HTMLMetaElement.h> |
26 | | #include <LibWeb/HTML/HTMLObjectElement.h> |
27 | | #include <LibWeb/HTML/HTMLSelectElement.h> |
28 | | #include <LibWeb/HTML/HTMLTextAreaElement.h> |
29 | | #include <LibWeb/Namespace.h> |
30 | | |
31 | | namespace Web::HTML { |
32 | | |
33 | | JS_DEFINE_ALLOCATOR(HTMLAllCollection); |
34 | | |
35 | | JS::NonnullGCPtr<HTMLAllCollection> HTMLAllCollection::create(DOM::ParentNode& root, Scope scope, Function<bool(DOM::Element const&)> filter) |
36 | 0 | { |
37 | 0 | return root.heap().allocate<HTMLAllCollection>(root.realm(), root, scope, move(filter)); |
38 | 0 | } |
39 | | |
40 | | HTMLAllCollection::HTMLAllCollection(DOM::ParentNode& root, Scope scope, Function<bool(DOM::Element const&)> filter) |
41 | 0 | : PlatformObject(root.realm()) |
42 | 0 | , m_root(root) |
43 | 0 | , m_filter(move(filter)) |
44 | 0 | , m_scope(scope) |
45 | 0 | { |
46 | 0 | m_legacy_platform_object_flags = LegacyPlatformObjectFlags { |
47 | 0 | .supports_indexed_properties = true, |
48 | 0 | .supports_named_properties = true, |
49 | 0 | .has_legacy_unenumerable_named_properties_interface_extended_attribute = true, |
50 | 0 | }; |
51 | 0 | } |
52 | | |
53 | 0 | HTMLAllCollection::~HTMLAllCollection() = default; |
54 | | |
55 | | void HTMLAllCollection::initialize(JS::Realm& realm) |
56 | 0 | { |
57 | 0 | Base::initialize(realm); |
58 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLAllCollection); |
59 | 0 | } |
60 | | |
61 | | void HTMLAllCollection::visit_edges(Cell::Visitor& visitor) |
62 | 0 | { |
63 | 0 | Base::visit_edges(visitor); |
64 | 0 | visitor.visit(m_root); |
65 | 0 | } |
66 | | |
67 | | // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#all-named-elements |
68 | | static bool is_all_named_element(DOM::Element const& element) |
69 | 0 | { |
70 | | // The following elements are "all"-named elements: a, button, embed, form, frame, frameset, iframe, img, input, map, meta, object, select, and textarea |
71 | 0 | return is<HTML::HTMLLinkElement>(element) |
72 | 0 | || is<HTML::HTMLButtonElement>(element) |
73 | 0 | || is<HTML::HTMLEmbedElement>(element) |
74 | 0 | || is<HTML::HTMLFormElement>(element) |
75 | 0 | || is<HTML::HTMLFrameElement>(element) |
76 | 0 | || is<HTML::HTMLFrameSetElement>(element) |
77 | 0 | || is<HTML::HTMLIFrameElement>(element) |
78 | 0 | || is<HTML::HTMLImageElement>(element) |
79 | 0 | || is<HTML::HTMLInputElement>(element) |
80 | 0 | || is<HTML::HTMLMapElement>(element) |
81 | 0 | || is<HTML::HTMLMetaElement>(element) |
82 | 0 | || is<HTML::HTMLObjectElement>(element) |
83 | 0 | || is<HTML::HTMLSelectElement>(element) |
84 | 0 | || is<HTML::HTMLTextAreaElement>(element); |
85 | 0 | } |
86 | | |
87 | | JS::MarkedVector<JS::NonnullGCPtr<DOM::Element>> HTMLAllCollection::collect_matching_elements() const |
88 | 0 | { |
89 | 0 | JS::MarkedVector<JS::NonnullGCPtr<DOM::Element>> elements(m_root->heap()); |
90 | 0 | if (m_scope == Scope::Descendants) { |
91 | 0 | m_root->for_each_in_subtree_of_type<DOM::Element>([&](auto& element) { |
92 | 0 | if (m_filter(element)) |
93 | 0 | elements.append(element); |
94 | 0 | return TraversalDecision::Continue; |
95 | 0 | }); |
96 | 0 | } else { |
97 | 0 | m_root->for_each_child_of_type<DOM::Element>([&](auto& element) { |
98 | 0 | if (m_filter(element)) |
99 | 0 | elements.append(element); |
100 | 0 | return IterationDecision::Continue; |
101 | 0 | }); |
102 | 0 | } |
103 | 0 | return elements; |
104 | 0 | } |
105 | | |
106 | | // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmlallcollection-length |
107 | | size_t HTMLAllCollection::length() const |
108 | 0 | { |
109 | | // The length getter steps are to return the number of nodes represented by the collection. |
110 | 0 | return collect_matching_elements().size(); |
111 | 0 | } |
112 | | |
113 | | // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmlallcollection-item |
114 | | Variant<JS::NonnullGCPtr<DOM::HTMLCollection>, JS::NonnullGCPtr<DOM::Element>, Empty> HTMLAllCollection::item(Optional<FlyString> const& name_or_index) const |
115 | 0 | { |
116 | | // 1. If nameOrIndex was not provided, return null. |
117 | 0 | if (!name_or_index.has_value()) |
118 | 0 | return Empty {}; |
119 | | |
120 | | // 2. Return the result of getting the "all"-indexed or named element(s) from this, given nameOrIndex. |
121 | 0 | return get_the_all_indexed_or_named_elements(name_or_index.value()); |
122 | 0 | } |
123 | | |
124 | | // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmlallcollection-nameditem |
125 | | Variant<JS::NonnullGCPtr<DOM::HTMLCollection>, JS::NonnullGCPtr<DOM::Element>, Empty> HTMLAllCollection::named_item(FlyString const& name) const |
126 | 0 | { |
127 | | // The namedItem(name) method steps are to return the result of getting the "all"-named element(s) from this given name. |
128 | 0 | return get_the_all_named_elements(name); |
129 | 0 | } |
130 | | |
131 | | // https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names |
132 | | Vector<FlyString> HTMLAllCollection::supported_property_names() const |
133 | 0 | { |
134 | | // The supported property names consist of the non-empty values of all the id attributes of all the |
135 | | // elements represented by the collection, and the non-empty values of all the name attributes of |
136 | | // all the "all"-named elements represented by the collection, in tree order, ignoring later duplicates, |
137 | | // with the id of an element preceding its name if it contributes both, they differ from each other, and |
138 | | // neither is the duplicate of an earlier entry. |
139 | |
|
140 | 0 | Vector<FlyString> result; |
141 | 0 | auto elements = collect_matching_elements(); |
142 | |
|
143 | 0 | for (auto const& element : elements) { |
144 | 0 | if (auto const& id = element->id(); id.has_value() && !id->is_empty()) { |
145 | 0 | if (!result.contains_slow(id.value())) |
146 | 0 | result.append(id.value()); |
147 | 0 | } |
148 | |
|
149 | 0 | if (is_all_named_element(*element) && element->name().has_value() && !element->name()->is_empty()) { |
150 | 0 | auto name = element->name().value(); |
151 | 0 | if (!name.is_empty() && !result.contains_slow(name)) |
152 | 0 | result.append(move(name)); |
153 | 0 | } |
154 | 0 | } |
155 | | |
156 | | // 3. Return result. |
157 | 0 | return result; |
158 | 0 | } |
159 | | |
160 | | // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#concept-get-all-named |
161 | | Variant<JS::NonnullGCPtr<DOM::HTMLCollection>, JS::NonnullGCPtr<DOM::Element>, Empty> HTMLAllCollection::get_the_all_named_elements(FlyString const& name) const |
162 | 0 | { |
163 | | // 1. If name is the empty string, return null. |
164 | 0 | if (name.is_empty()) |
165 | 0 | return Empty {}; |
166 | | |
167 | | // 2. Let subCollection be an HTMLCollection object rooted at the same Document as collection, whose filter matches only elements that are either: |
168 | 0 | auto sub_collection = DOM::HTMLCollection::create(m_root, DOM::HTMLCollection::Scope::Descendants, [name](DOM::Element const& element) { |
169 | | // * "all"-named elements with a name attribute equal to name, or, |
170 | 0 | if (is_all_named_element(element) && element.name() == name) |
171 | 0 | return true; |
172 | | |
173 | | // * elements with an ID equal to name. |
174 | 0 | return element.id() == name; |
175 | 0 | }); |
176 | | |
177 | | // 3. If there is exactly one element in subCollection, then return that element. |
178 | 0 | auto matching_elements = sub_collection->collect_matching_elements(); |
179 | 0 | if (matching_elements.size() == 1) |
180 | 0 | return matching_elements.first(); |
181 | | |
182 | | // 4. Otherwise, if subCollection is empty, return null. |
183 | 0 | if (matching_elements.is_empty()) |
184 | 0 | return Empty {}; |
185 | | |
186 | | // 5. Otherwise, return subCollection. |
187 | 0 | return sub_collection; |
188 | 0 | } |
189 | | |
190 | | // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#concept-get-all-indexed |
191 | | JS::GCPtr<DOM::Element> HTMLAllCollection::get_the_all_indexed_element(u32 index) const |
192 | 0 | { |
193 | | // To get the "all"-indexed element from an HTMLAllCollection collection given an index index, return the indexth |
194 | | // element in collection, or null if there is no such indexth element. |
195 | 0 | auto elements = collect_matching_elements(); |
196 | 0 | if (index >= elements.size()) |
197 | 0 | return nullptr; |
198 | 0 | return elements[index]; |
199 | 0 | } |
200 | | |
201 | | // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#concept-get-all-indexed-or-named |
202 | | Variant<JS::NonnullGCPtr<DOM::HTMLCollection>, JS::NonnullGCPtr<DOM::Element>, Empty> HTMLAllCollection::get_the_all_indexed_or_named_elements(JS::PropertyKey const& name_or_index) const |
203 | 0 | { |
204 | | // 1. If nameOrIndex, converted to a JavaScript String value, is an array index property name, return the result of getting the "all"-indexed element from |
205 | | // collection given the number represented by nameOrIndex. |
206 | 0 | if (name_or_index.is_number()) { |
207 | 0 | auto maybe_element = get_the_all_indexed_element(name_or_index.as_number()); |
208 | 0 | if (!maybe_element) |
209 | 0 | return Empty {}; |
210 | 0 | return JS::NonnullGCPtr<DOM::Element> { *maybe_element }; |
211 | 0 | } |
212 | | |
213 | | // 2. Return the result of getting the "all"-named element(s) from collection given nameOrIndex. |
214 | 0 | return get_the_all_named_elements(MUST(FlyString::from_deprecated_fly_string(name_or_index.as_string()))); |
215 | 0 | } |
216 | | |
217 | | Optional<JS::Value> HTMLAllCollection::item_value(size_t index) const |
218 | 0 | { |
219 | 0 | if (auto value = get_the_all_indexed_element(index)) |
220 | 0 | return value; |
221 | 0 | return {}; |
222 | 0 | } |
223 | | |
224 | | JS::Value HTMLAllCollection::named_item_value(FlyString const& name) const |
225 | 0 | { |
226 | 0 | return named_item(name).visit( |
227 | 0 | [](Empty) -> JS::Value { return JS::js_undefined(); }, |
228 | 0 | [](auto const& value) -> JS::Value { return value; });Unexecuted instantiation: HTMLAllCollection.cpp:JS::Value Web::HTML::HTMLAllCollection::named_item_value(AK::FlyString const&) const::$_1::operator()<JS::NonnullGCPtr<Web::DOM::HTMLCollection> >(JS::NonnullGCPtr<Web::DOM::HTMLCollection> const&) const Unexecuted instantiation: HTMLAllCollection.cpp:JS::Value Web::HTML::HTMLAllCollection::named_item_value(AK::FlyString const&) const::$_1::operator()<JS::NonnullGCPtr<Web::DOM::Element> >(JS::NonnullGCPtr<Web::DOM::Element> const&) const |
229 | 0 | } |
230 | | |
231 | | } |