Coverage Report

Created: 2026-09-26 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}