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/HTMLOptionsCollection.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2022, the SerenityOS developers.
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibWeb/Bindings/HTMLOptionsCollectionPrototype.h>
8
#include <LibWeb/Bindings/Intrinsics.h>
9
#include <LibWeb/DOM/ElementFactory.h>
10
#include <LibWeb/HTML/HTMLOptGroupElement.h>
11
#include <LibWeb/HTML/HTMLOptionElement.h>
12
#include <LibWeb/HTML/HTMLOptionsCollection.h>
13
#include <LibWeb/HTML/HTMLSelectElement.h>
14
#include <LibWeb/Namespace.h>
15
#include <LibWeb/WebIDL/DOMException.h>
16
17
namespace Web::HTML {
18
19
JS_DEFINE_ALLOCATOR(HTMLOptionsCollection);
20
21
JS::NonnullGCPtr<HTMLOptionsCollection> HTMLOptionsCollection::create(DOM::ParentNode& root, Function<bool(DOM::Element const&)> filter)
22
0
{
23
0
    return root.heap().allocate<HTMLOptionsCollection>(root.realm(), root, move(filter));
24
0
}
25
26
HTMLOptionsCollection::HTMLOptionsCollection(DOM::ParentNode& root, Function<bool(DOM::Element const&)> filter)
27
0
    : DOM::HTMLCollection(root, Scope::Descendants, move(filter))
28
0
{
29
0
    m_legacy_platform_object_flags->has_indexed_property_setter = true;
30
0
    m_legacy_platform_object_flags->indexed_property_setter_has_identifier = true;
31
0
}
32
33
0
HTMLOptionsCollection::~HTMLOptionsCollection() = default;
34
35
void HTMLOptionsCollection::initialize(JS::Realm& realm)
36
0
{
37
0
    Base::initialize(realm);
38
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLOptionsCollection);
39
0
}
40
41
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmloptionscollection-length
42
WebIDL::ExceptionOr<void> HTMLOptionsCollection::set_length(WebIDL::UnsignedLong value)
43
0
{
44
    // 1. Let current be the number of nodes represented by the collection.
45
0
    auto current = static_cast<WebIDL::UnsignedLong>(length());
46
47
    // 2. If the given value is greater than current, then:
48
0
    if (value > current) {
49
        // 2.1. If the given value is greater than 100,000, then return.
50
0
        if (value > 100'000)
51
0
            return {};
52
53
        // 2.2. Let n be value − current.
54
0
        auto n = value - current;
55
56
        // 2.3. Append n new option elements with no attributes and no child nodes to the select element on which this is rooted.
57
        // Mutation events must be fired as if a DocumentFragment containing the new option elements had been inserted.
58
0
        auto root_element = root();
59
0
        for (WebIDL::UnsignedLong i = 0; i < n; i++)
60
0
            TRY(root_element->append_child(TRY(DOM::create_element(root_element->document(), HTML::TagNames::option, Namespace::HTML))));
61
0
    }
62
63
    // 3. If the given value is less than current, then:
64
0
    if (value < current) {
65
        // 3.1. Let n be current − value.
66
0
        auto n = current - value;
67
68
        // 3.2. Remove the last n nodes in the collection from their parent nodes.
69
0
        for (auto i = 0u; i < n; i++)
70
0
            item(length() - 1)->remove();
71
0
    }
72
73
0
    return {};
74
0
}
75
76
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmloptionscollection-setter
77
WebIDL::ExceptionOr<void> HTMLOptionsCollection::set_value_of_indexed_property(u32 index, JS::Value unconverted_option)
78
0
{
79
    // The spec doesn't seem to require this, but it's consistent with length handling and other browsers
80
0
    if (index >= 100'000) {
81
0
        return {};
82
0
    }
83
84
    // 1. If value is null, invoke the steps for the remove method with index as the argument, and return.
85
0
    if (unconverted_option.is_null()) {
86
0
        remove(static_cast<WebIDL::Long>(index));
87
0
        return {};
88
0
    }
89
90
0
    if (!unconverted_option.is_object() || !is<HTMLOptionElement>(unconverted_option.as_object())) {
91
0
        return WebIDL::TypeMismatchError::create(realm(), "The value provided is not an HTMLOptionElement"_string);
92
0
    }
93
94
0
    auto& option = static_cast<HTMLOptionElement&>(unconverted_option.as_object());
95
96
    // 2. Let length be the number of nodes represented by the collection.
97
0
    auto length = this->length();
98
99
0
    auto root_element = root();
100
101
0
    if (index >= length) {
102
        // 3. Let n be index minus length.
103
0
        auto n = index - length;
104
105
        // 4. If n is greater than zero, then append a DocumentFragment consisting of n-1 new option elements with no attributes and no child nodes to the select element on which the HTMLOptionsCollection is rooted.
106
0
        if (n > 0) {
107
0
            for (WebIDL::UnsignedLong i = 0; i < n - 1; i++) {
108
0
                TRY(root_element->append_child(TRY(DOM::create_element(root_element->document(), HTML::TagNames::option, Namespace::HTML))));
109
0
            }
110
0
        }
111
112
        // 5. If n is greater than or equal to zero, append value to the select element.
113
0
        TRY(root_element->append_child(option));
114
0
    } else {
115
        // 5 (cont). Otherwise, replace the indexth element in the collection by value.
116
0
        TRY(root_element->replace_child(option, *item(index)));
117
0
    }
118
119
0
    return {};
120
0
}
121
122
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmloptionscollection-add
123
WebIDL::ExceptionOr<void> HTMLOptionsCollection::add(HTMLOptionOrOptGroupElement element, Optional<HTMLElementOrElementIndex> before)
124
0
{
125
0
    auto resolved_element = element.visit(
126
0
        [](auto& e) -> JS::Handle<HTMLElement> {
127
0
            return JS::make_handle(static_cast<HTML::HTMLElement&>(*e));
128
0
        });
Unexecuted instantiation: HTMLOptionsCollection.cpp:JS::Handle<Web::HTML::HTMLElement> Web::HTML::HTMLOptionsCollection::add(AK::Variant<JS::Handle<Web::HTML::HTMLOptionElement>, JS::Handle<Web::HTML::HTMLOptGroupElement> >, AK::Optional<AK::Variant<JS::Handle<Web::HTML::HTMLElement>, int> >)::$_0::operator()<JS::Handle<Web::HTML::HTMLOptionElement> >(JS::Handle<Web::HTML::HTMLOptionElement>&) const
Unexecuted instantiation: HTMLOptionsCollection.cpp:JS::Handle<Web::HTML::HTMLElement> Web::HTML::HTMLOptionsCollection::add(AK::Variant<JS::Handle<Web::HTML::HTMLOptionElement>, JS::Handle<Web::HTML::HTMLOptGroupElement> >, AK::Optional<AK::Variant<JS::Handle<Web::HTML::HTMLElement>, int> >)::$_0::operator()<JS::Handle<Web::HTML::HTMLOptGroupElement> >(JS::Handle<Web::HTML::HTMLOptGroupElement>&) const
129
130
0
    JS::GCPtr<DOM::Node> before_element;
131
0
    if (before.has_value() && before->has<JS::Handle<HTMLElement>>())
132
0
        before_element = before->get<JS::Handle<HTMLElement>>().ptr();
133
134
    // 1. If element is an ancestor of the select element on which the HTMLOptionsCollection is rooted, then throw a "HierarchyRequestError" DOMException.
135
0
    if (resolved_element->is_ancestor_of(root()))
136
0
        return WebIDL::HierarchyRequestError::create(realm(), "The provided element is an ancestor of the root select element."_string);
137
138
    // 2. If before is an element, but that element isn't a descendant of the select element on which the HTMLOptionsCollection is rooted, then throw a "NotFoundError" DOMException.
139
0
    if (before_element && !before_element->is_descendant_of(root()))
140
0
        return WebIDL::NotFoundError::create(realm(), "The 'before' element is not a descendant of the root select element."_string);
141
142
    // 3. If element and before are the same element, then return.
143
0
    if (before_element && (resolved_element.ptr() == before_element.ptr()))
144
0
        return {};
145
146
    // 4. If before is a node, then let reference be that node. Otherwise, if before is an integer, and there is a beforeth node in the collection, let reference be that node. Otherwise, let reference be null.
147
0
    JS::GCPtr<DOM::Node> reference;
148
149
0
    if (before_element)
150
0
        reference = move(before_element);
151
0
    else if (before.has_value() && before->has<i32>())
152
0
        reference = item(before->get<i32>());
153
154
    // 5. If reference is not null, let parent be the parent node of reference. Otherwise, let parent be the select element on which the HTMLOptionsCollection is rooted.
155
0
    DOM::Node* parent = reference ? reference->parent() : root().ptr();
156
157
    // 6. Pre-insert element into parent node before reference.
158
0
    (void)TRY(parent->pre_insert(*resolved_element, reference));
159
160
0
    return {};
161
0
}
162
163
// https://html.spec.whatwg.org/#dom-htmloptionscollection-remove
164
void HTMLOptionsCollection::remove(WebIDL::Long index)
165
0
{
166
    // 1. If the number of nodes represented by the collection is zero, return.
167
0
    if (length() == 0)
168
0
        return;
169
170
    // 2. If index is not a number greater than or equal to 0 and less than the number of nodes represented by the collection, return.
171
0
    if (index < 0 || static_cast<WebIDL::UnsignedLong>(index) >= length())
172
0
        return;
173
174
    // 3. Let element be the indexth element in the collection.
175
0
    auto* element = this->item(index);
176
177
    // 4. Remove element from its parent node.
178
0
    element->remove();
179
0
}
180
181
// https://html.spec.whatwg.org/#dom-htmloptionscollection-selectedindex
182
WebIDL::Long HTMLOptionsCollection::selected_index() const
183
0
{
184
    // The selectedIndex IDL attribute must act like the identically named attribute
185
    // on the select element on which the HTMLOptionsCollection is rooted.
186
0
    return verify_cast<HTMLSelectElement>(*root()).selected_index();
187
0
}
188
189
void HTMLOptionsCollection::set_selected_index(WebIDL::Long index)
190
0
{
191
0
    verify_cast<HTMLSelectElement>(*root()).set_selected_index(index);
192
0
}
193
194
}