Coverage Report

Created: 2026-02-16 07:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/DOM/HTMLCollection.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibWeb/Bindings/HTMLCollectionPrototype.h>
9
#include <LibWeb/Bindings/Intrinsics.h>
10
#include <LibWeb/DOM/Document.h>
11
#include <LibWeb/DOM/Element.h>
12
#include <LibWeb/DOM/HTMLCollection.h>
13
#include <LibWeb/DOM/ParentNode.h>
14
#include <LibWeb/Namespace.h>
15
16
namespace Web::DOM {
17
18
JS_DEFINE_ALLOCATOR(HTMLCollection);
19
20
JS::NonnullGCPtr<HTMLCollection> HTMLCollection::create(ParentNode& root, Scope scope, Function<bool(Element const&)> filter)
21
0
{
22
0
    return root.heap().allocate<HTMLCollection>(root.realm(), root, scope, move(filter));
23
0
}
24
25
HTMLCollection::HTMLCollection(ParentNode& root, Scope scope, Function<bool(Element const&)> filter)
26
0
    : PlatformObject(root.realm())
27
0
    , m_root(root)
28
0
    , m_filter(move(filter))
29
0
    , m_scope(scope)
30
0
{
31
0
    m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
32
0
        .supports_indexed_properties = true,
33
0
        .supports_named_properties = true,
34
0
        .has_legacy_unenumerable_named_properties_interface_extended_attribute = true,
35
0
    };
36
0
}
37
38
0
HTMLCollection::~HTMLCollection() = default;
39
40
void HTMLCollection::initialize(JS::Realm& realm)
41
0
{
42
0
    Base::initialize(realm);
43
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLCollection);
44
0
}
45
46
void HTMLCollection::visit_edges(Cell::Visitor& visitor)
47
0
{
48
0
    Base::visit_edges(visitor);
49
0
    visitor.visit(m_root);
50
0
    visitor.visit(m_cached_elements);
51
0
    if (m_cached_name_to_element_mappings)
52
0
        visitor.visit(*m_cached_name_to_element_mappings);
53
0
}
54
55
void HTMLCollection::update_name_to_element_mappings_if_needed() const
56
0
{
57
0
    update_cache_if_needed();
58
0
    if (m_cached_name_to_element_mappings)
59
0
        return;
60
0
    m_cached_name_to_element_mappings = make<OrderedHashMap<FlyString, JS::NonnullGCPtr<Element>>>();
61
0
    for (auto const& element : m_cached_elements) {
62
        // 1. If element has an ID which is not in result, append element’s ID to result.
63
0
        if (auto const& id = element->id(); id.has_value()) {
64
0
            if (!id.value().is_empty() && !m_cached_name_to_element_mappings->contains(id.value()))
65
0
                m_cached_name_to_element_mappings->set(id.value(), element);
66
0
        }
67
68
        // 2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string nor is in result, append element’s name attribute value to result.
69
0
        if (element->namespace_uri() == Namespace::HTML && element->name().has_value()) {
70
0
            auto element_name = element->name().value();
71
0
            if (!element_name.is_empty() && !m_cached_name_to_element_mappings->contains(element_name))
72
0
                m_cached_name_to_element_mappings->set(move(element_name), element);
73
0
        }
74
0
    }
75
0
}
76
77
void HTMLCollection::update_cache_if_needed() const
78
0
{
79
    // Nothing to do, the DOM hasn't updated since we last built the cache.
80
0
    if (m_cached_dom_tree_version == root()->document().dom_tree_version())
81
0
        return;
82
83
0
    m_cached_elements.clear();
84
0
    m_cached_name_to_element_mappings = nullptr;
85
0
    if (m_scope == Scope::Descendants) {
86
0
        m_root->for_each_in_subtree_of_type<Element>([&](auto& element) {
87
0
            if (m_filter(element))
88
0
                m_cached_elements.append(element);
89
0
            return TraversalDecision::Continue;
90
0
        });
91
0
    } else {
92
0
        m_root->for_each_child_of_type<Element>([&](auto& element) {
93
0
            if (m_filter(element))
94
0
                m_cached_elements.append(element);
95
0
            return IterationDecision::Continue;
96
0
        });
97
0
    }
98
0
    m_cached_dom_tree_version = root()->document().dom_tree_version();
99
0
}
100
101
JS::MarkedVector<JS::NonnullGCPtr<Element>> HTMLCollection::collect_matching_elements() const
102
0
{
103
0
    update_cache_if_needed();
104
0
    JS::MarkedVector<JS::NonnullGCPtr<Element>> elements(heap());
105
0
    for (auto& element : m_cached_elements)
106
0
        elements.append(element);
107
0
    return elements;
108
0
}
109
110
// https://dom.spec.whatwg.org/#dom-htmlcollection-length
111
size_t HTMLCollection::length() const
112
0
{
113
    // The length getter steps are to return the number of nodes represented by the collection.
114
0
    update_cache_if_needed();
115
0
    return m_cached_elements.size();
116
0
}
117
118
// https://dom.spec.whatwg.org/#dom-htmlcollection-item
119
Element* HTMLCollection::item(size_t index) const
120
0
{
121
    // The item(index) method steps are to return the indexth element in the collection. If there is no indexth element in the collection, then the method must return null.
122
0
    update_cache_if_needed();
123
0
    if (index >= m_cached_elements.size())
124
0
        return nullptr;
125
0
    return m_cached_elements[index];
126
0
}
127
128
// https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key
129
Element* HTMLCollection::named_item(FlyString const& key) const
130
0
{
131
    // 1. If key is the empty string, return null.
132
0
    if (key.is_empty())
133
0
        return nullptr;
134
135
0
    update_name_to_element_mappings_if_needed();
136
0
    if (auto it = m_cached_name_to_element_mappings->get(key); it.has_value())
137
0
        return it.value();
138
0
    return nullptr;
139
0
}
140
141
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
142
bool HTMLCollection::is_supported_property_name(FlyString const& name) const
143
0
{
144
0
    update_name_to_element_mappings_if_needed();
145
0
    return m_cached_name_to_element_mappings->contains(name);
146
0
}
147
148
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
149
Vector<FlyString> HTMLCollection::supported_property_names() const
150
0
{
151
    // 1. Let result be an empty list.
152
0
    Vector<FlyString> result;
153
154
    // 2. For each element represented by the collection, in tree order:
155
0
    update_name_to_element_mappings_if_needed();
156
0
    for (auto const& it : *m_cached_name_to_element_mappings) {
157
0
        result.append(it.key);
158
0
    }
159
160
    // 3. Return result.
161
0
    return result;
162
0
}
163
164
Optional<JS::Value> HTMLCollection::item_value(size_t index) const
165
0
{
166
0
    auto* element = item(index);
167
0
    if (!element)
168
0
        return {};
169
0
    return element;
170
0
}
171
172
JS::Value HTMLCollection::named_item_value(FlyString const& name) const
173
0
{
174
0
    auto* element = named_item(name);
175
0
    if (!element)
176
0
        return JS::js_undefined();
177
0
    return element;
178
0
}
179
}