Coverage Report

Created: 2026-10-28 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2020, the SerenityOS developers.
3
 * Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
4
 * Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
5
 *
6
 * SPDX-License-Identifier: BSD-2-Clause
7
 */
8
9
#include <LibWeb/Bindings/HTMLSelectElementPrototype.h>
10
#include <LibWeb/Bindings/Intrinsics.h>
11
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
12
#include <LibWeb/DOM/Document.h>
13
#include <LibWeb/DOM/ElementFactory.h>
14
#include <LibWeb/DOM/Event.h>
15
#include <LibWeb/DOM/ShadowRoot.h>
16
#include <LibWeb/HTML/EventNames.h>
17
#include <LibWeb/HTML/HTMLFormElement.h>
18
#include <LibWeb/HTML/HTMLHRElement.h>
19
#include <LibWeb/HTML/HTMLOptGroupElement.h>
20
#include <LibWeb/HTML/HTMLOptionElement.h>
21
#include <LibWeb/HTML/HTMLSelectElement.h>
22
#include <LibWeb/HTML/Numbers.h>
23
#include <LibWeb/HTML/Window.h>
24
#include <LibWeb/Infra/Strings.h>
25
#include <LibWeb/Layout/Node.h>
26
#include <LibWeb/Namespace.h>
27
#include <LibWeb/Page/Page.h>
28
29
namespace Web::HTML {
30
31
JS_DEFINE_ALLOCATOR(HTMLSelectElement);
32
33
HTMLSelectElement::HTMLSelectElement(DOM::Document& document, DOM::QualifiedName qualified_name)
34
0
    : HTMLElement(document, move(qualified_name))
35
0
{
36
0
}
37
38
0
HTMLSelectElement::~HTMLSelectElement() = default;
39
40
void HTMLSelectElement::initialize(JS::Realm& realm)
41
0
{
42
0
    Base::initialize(realm);
43
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLSelectElement);
44
0
}
45
46
void HTMLSelectElement::visit_edges(Cell::Visitor& visitor)
47
0
{
48
0
    Base::visit_edges(visitor);
49
0
    visitor.visit(m_options);
50
0
    visitor.visit(m_selected_options);
51
0
    visitor.visit(m_inner_text_element);
52
0
    visitor.visit(m_chevron_icon_element);
53
54
0
    for (auto const& item : m_select_items) {
55
0
        if (item.has<SelectItemOption>())
56
0
            visitor.visit(item.get<SelectItemOption>().option_element);
57
58
0
        if (item.has<SelectItemOptionGroup>()) {
59
0
            auto item_option_group = item.get<SelectItemOptionGroup>();
60
0
            for (auto const& item : item_option_group.items)
61
0
                visitor.visit(item.option_element);
62
0
        }
63
0
    }
64
0
}
65
66
void HTMLSelectElement::adjust_computed_style(CSS::StyleProperties& style)
67
0
{
68
    // AD-HOC: We rewrite `display: inline` to `display: inline-block`.
69
    //         This is required for the internal shadow tree to work correctly in layout.
70
0
    if (style.display().is_inline_outside() && style.display().is_flow_inside())
71
0
        style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::InlineBlock)));
72
0
}
73
74
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-select-size
75
WebIDL::UnsignedLong HTMLSelectElement::size() const
76
0
{
77
    // The size IDL attribute must reflect the respective content attributes of the same name. The size IDL attribute has a default value of 0.
78
0
    if (auto size_string = get_attribute(HTML::AttributeNames::size); size_string.has_value()) {
79
        // The display size of a select element is the result of applying the rules for parsing non-negative integers
80
        // to the value of element's size attribute, if it has one and parsing it is successful.
81
0
        if (auto size = parse_non_negative_integer(*size_string); size.has_value())
82
0
            return *size;
83
0
    }
84
85
    // If applying those rules to the attribute's value is not successful or if the size attribute is absent,
86
    // then the element's display size is 4 if the element's multiple content attribute is present, and 1 otherwise.
87
0
    if (has_attribute(AttributeNames::multiple))
88
0
        return 4;
89
0
    return 1;
90
0
}
91
92
WebIDL::ExceptionOr<void> HTMLSelectElement::set_size(WebIDL::UnsignedLong size)
93
0
{
94
0
    return set_attribute(HTML::AttributeNames::size, String::number(size));
95
0
}
96
97
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-options
98
JS::GCPtr<HTMLOptionsCollection> const& HTMLSelectElement::options()
99
0
{
100
0
    if (!m_options) {
101
0
        m_options = HTMLOptionsCollection::create(*this, [](DOM::Element const& element) {
102
            // https://html.spec.whatwg.org/multipage/form-elements.html#concept-select-option-list
103
            // The list of options for a select element consists of all the option element children of
104
            // the select element, and all the option element children of all the optgroup element children
105
            // of the select element, in tree order.
106
0
            return is<HTMLOptionElement>(element);
107
0
        });
108
0
    }
109
0
    return m_options;
110
0
}
111
112
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-length
113
WebIDL::UnsignedLong HTMLSelectElement::length()
114
0
{
115
    // The length IDL attribute must return the number of nodes represented by the options collection. On setting, it must act like the attribute of the same name on the options collection.
116
0
    return const_cast<HTMLOptionsCollection&>(*options()).length();
117
0
}
118
119
WebIDL::ExceptionOr<void> HTMLSelectElement::set_length(WebIDL::UnsignedLong length)
120
0
{
121
    // On setting, it must act like the attribute of the same name on the options collection.
122
0
    return const_cast<HTMLOptionsCollection&>(*options()).set_length(length);
123
0
}
124
125
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-item
126
HTMLOptionElement* HTMLSelectElement::item(WebIDL::UnsignedLong index)
127
0
{
128
    // The item(index) method must return the value returned by the method of the same name on the options collection, when invoked with the same argument.
129
0
    return verify_cast<HTMLOptionElement>(const_cast<HTMLOptionsCollection&>(*options()).item(index));
130
0
}
131
132
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-nameditem
133
HTMLOptionElement* HTMLSelectElement::named_item(FlyString const& name)
134
0
{
135
    // The namedItem(name) method must return the value returned by the method of the same name on the options collection, when invoked with the same argument.
136
0
    return verify_cast<HTMLOptionElement>(const_cast<HTMLOptionsCollection&>(*options()).named_item(name));
137
0
}
138
139
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-add
140
WebIDL::ExceptionOr<void> HTMLSelectElement::add(HTMLOptionOrOptGroupElement element, Optional<HTMLElementOrElementIndex> before)
141
0
{
142
    // Similarly, the add(element, before) method must act like its namesake method on that same options collection.
143
0
    TRY(const_cast<HTMLOptionsCollection&>(*options()).add(move(element), move(before)));
144
145
0
    update_selectedness(); // Not in spec
146
147
0
    return {};
148
0
}
149
150
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-remove
151
void HTMLSelectElement::remove()
152
0
{
153
    // The remove() method must act like its namesake method on that same options collection when it has arguments,
154
    // and like its namesake method on the ChildNode interface implemented by the HTMLSelectElement ancestor interface Element when it has no arguments.
155
0
    ChildNode::remove_binding();
156
0
}
157
158
void HTMLSelectElement::remove(WebIDL::Long index)
159
0
{
160
0
    const_cast<HTMLOptionsCollection&>(*options()).remove(index);
161
0
}
162
163
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-selectedoptions
164
JS::NonnullGCPtr<DOM::HTMLCollection> HTMLSelectElement::selected_options()
165
0
{
166
    // The selectedOptions IDL attribute must return an HTMLCollection rooted at the select node,
167
    // whose filter matches the elements in the list of options that have their selectedness set to true.
168
0
    if (!m_selected_options) {
169
0
        m_selected_options = DOM::HTMLCollection::create(*this, DOM::HTMLCollection::Scope::Descendants, [](Element const& element) {
170
0
            if (is<HTML::HTMLOptionElement>(element)) {
171
0
                auto const& option_element = verify_cast<HTMLOptionElement>(element);
172
0
                return option_element.selected();
173
0
            }
174
0
            return false;
175
0
        });
176
0
    }
177
0
    return *m_selected_options;
178
0
}
179
180
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-select-option-list
181
Vector<JS::Handle<HTMLOptionElement>> HTMLSelectElement::list_of_options() const
182
0
{
183
    // The list of options for a select element consists of all the option element children of the select element,
184
    // and all the option element children of all the optgroup element children of the select element, in tree order.
185
0
    Vector<JS::Handle<HTMLOptionElement>> list;
186
187
0
    for_each_child_of_type<HTMLOptionElement>([&](HTMLOptionElement& option_element) {
188
0
        list.append(JS::make_handle(option_element));
189
0
        return IterationDecision::Continue;
190
0
    });
191
192
0
    for_each_child_of_type<HTMLOptGroupElement>([&](HTMLOptGroupElement const& optgroup_element) {
193
0
        optgroup_element.for_each_child_of_type<HTMLOptionElement>([&](HTMLOptionElement& option_element) {
194
0
            list.append(JS::make_handle(option_element));
195
0
            return IterationDecision::Continue;
196
0
        });
197
0
        return IterationDecision::Continue;
198
0
    });
199
200
0
    return list;
201
0
}
202
203
// https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element:concept-form-reset-control
204
void HTMLSelectElement::reset_algorithm()
205
0
{
206
    // The reset algorithm for select elements is to go through all the option elements in the element's list of options,
207
0
    for (auto const& option_element : list_of_options()) {
208
        // set their selectedness to true if the option element has a selected attribute, and false otherwise,
209
0
        option_element->m_selected = option_element->has_attribute(AttributeNames::selected);
210
        // set their dirtiness to false,
211
0
        option_element->m_dirty = false;
212
        // and then have the option elements ask for a reset.
213
0
        option_element->ask_for_a_reset();
214
0
    }
215
0
}
216
217
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-selectedindex
218
WebIDL::Long HTMLSelectElement::selected_index() const
219
0
{
220
    // The selectedIndex IDL attribute, on getting, must return the index of the first option element in the list of options
221
    // in tree order that has its selectedness set to true, if any. If there isn't one, then it must return −1.
222
223
0
    WebIDL::Long index = 0;
224
0
    for (auto const& option_element : list_of_options()) {
225
0
        if (option_element->selected())
226
0
            return index;
227
0
        ++index;
228
0
    }
229
0
    return -1;
230
0
}
231
232
void HTMLSelectElement::set_selected_index(WebIDL::Long index)
233
0
{
234
    // On setting, the selectedIndex attribute must set the selectedness of all the option elements in the list of options to false,
235
    // and then the option element in the list of options whose index is the given new value,
236
    // if any, must have its selectedness set to true and its dirtiness set to true.
237
0
    auto options = list_of_options();
238
0
    for (auto& option : options)
239
0
        option->m_selected = false;
240
241
0
    if (index < 0 || index >= static_cast<int>(options.size()))
242
0
        return;
243
244
0
    auto& selected_option = options[index];
245
0
    selected_option->m_selected = true;
246
0
    selected_option->m_dirty = true;
247
0
}
248
249
// https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex
250
i32 HTMLSelectElement::default_tab_index_value() const
251
0
{
252
    // See the base function for the spec comments.
253
0
    return 0;
254
0
}
255
256
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-type
257
String const& HTMLSelectElement::type() const
258
0
{
259
    // The type IDL attribute, on getting, must return the string "select-one" if the multiple attribute is absent, and the string "select-multiple" if the multiple attribute is present.
260
0
    static String const select_one = "select-one"_string;
261
0
    static String const select_multiple = "select-multiple"_string;
262
263
0
    if (!has_attribute(AttributeNames::multiple))
264
0
        return select_one;
265
266
0
    return select_multiple;
267
0
}
268
269
Optional<ARIA::Role> HTMLSelectElement::default_role() const
270
0
{
271
    // https://www.w3.org/TR/html-aria/#el-select-multiple-or-size-greater-1
272
0
    if (has_attribute(AttributeNames::multiple))
273
0
        return ARIA::Role::listbox;
274
0
    if (has_attribute(AttributeNames::size)) {
275
0
        if (auto size_string = get_attribute(HTML::AttributeNames::size); size_string.has_value()) {
276
0
            if (auto size = size_string->to_number<int>(); size.has_value() && *size > 1)
277
0
                return ARIA::Role::listbox;
278
0
        }
279
0
    }
280
    // https://www.w3.org/TR/html-aria/#el-select
281
0
    return ARIA::Role::combobox;
282
0
}
283
284
String HTMLSelectElement::value() const
285
0
{
286
0
    for (auto const& option_element : list_of_options())
287
0
        if (option_element->selected())
288
0
            return option_element->value();
289
0
    return ""_string;
290
0
}
291
292
WebIDL::ExceptionOr<void> HTMLSelectElement::set_value(String const& value)
293
0
{
294
0
    for (auto const& option_element : list_of_options())
295
0
        option_element->set_selected(option_element->value() == value);
296
0
    update_inner_text_element();
297
0
    return {};
298
0
}
299
300
void HTMLSelectElement::queue_input_and_change_events()
301
0
{
302
    // When the user agent is to send select update notifications, queue an element task on the user interaction task source given the select element to run these steps:
303
0
    queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
304
        // FIXME: 1. Set the select element's user interacted to true.
305
306
        // 2. Fire an event named input at the select element, with the bubbles and composed attributes initialized to true.
307
0
        auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
308
0
        input_event->set_bubbles(true);
309
0
        input_event->set_composed(true);
310
0
        dispatch_event(input_event);
311
312
        // 3. Fire an event named change at the select element, with the bubbles attribute initialized to true.
313
0
        auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
314
0
        change_event->set_bubbles(true);
315
0
        dispatch_event(*change_event);
316
0
    });
317
0
}
318
319
void HTMLSelectElement::set_is_open(bool open)
320
0
{
321
0
    if (open == m_is_open)
322
0
        return;
323
324
0
    m_is_open = open;
325
0
    invalidate_style(DOM::StyleInvalidationReason::HTMLSelectElementSetIsOpen);
326
0
}
327
328
bool HTMLSelectElement::has_activation_behavior() const
329
0
{
330
0
    return true;
331
0
}
332
333
static String strip_newlines(Optional<String> string)
334
0
{
335
    // FIXME: Move this to a more general function
336
0
    if (!string.has_value())
337
0
        return {};
338
339
0
    StringBuilder builder;
340
0
    for (auto c : string.value().bytes_as_string_view()) {
341
0
        if (c == '\r' || c == '\n') {
342
0
            builder.append(' ');
343
0
        } else {
344
0
            builder.append(c);
345
0
        }
346
0
    }
347
0
    return MUST(Infra::strip_and_collapse_whitespace(MUST(builder.to_string())));
348
0
}
349
350
// https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
351
void HTMLSelectElement::show_the_picker_if_applicable()
352
0
{
353
    // FIXME: Deduplicate with HTMLInputElement
354
    // To show the picker, if applicable for a select element:
355
356
    // 1. If element's relevant global object does not have transient activation, then return.
357
0
    auto& global_object = relevant_global_object(*this);
358
0
    if (!is<HTML::Window>(global_object))
359
0
        return;
360
0
    auto& relevant_global_object = static_cast<HTML::Window&>(global_object);
361
0
    if (!relevant_global_object.has_transient_activation())
362
0
        return;
363
364
    // 2. If element is not mutable, then return.
365
0
    if (!enabled())
366
0
        return;
367
368
    // 3. Consume user activation given element's relevant global object.
369
0
    relevant_global_object.consume_user_activation();
370
371
    // 4. If element's type attribute is in the File Upload state, then run these steps in parallel:
372
    // Not Applicable to select elements
373
374
    // 5. Otherwise, the user agent should show any relevant user interface for selecting a value for element,
375
    //    in the way it normally would when the user interacts with the control. (If no such UI applies to element, then this step does nothing.)
376
    //    If such a user interface is shown, it must respect the requirements stated in the relevant parts of the specification for how element
377
    //    behaves given its type attribute state. (For example, various sections describe restrictions on the resulting value string.)
378
    //    This step can have side effects, such as closing other pickers that were previously shown by this algorithm.
379
    //    (If this closes a file selection picker, then per the above that will lead to firing either input and change events, or a cancel event.)
380
381
    // Populate select items
382
0
    m_select_items.clear();
383
0
    u32 id_counter = 1;
384
0
    for (auto const& child : children_as_vector()) {
385
0
        if (is<HTMLOptGroupElement>(*child)) {
386
0
            auto& opt_group_element = verify_cast<HTMLOptGroupElement>(*child);
387
0
            Vector<SelectItemOption> option_group_items;
388
0
            for (auto const& child : opt_group_element.children_as_vector()) {
389
0
                if (is<HTMLOptionElement>(*child)) {
390
0
                    auto& option_element = verify_cast<HTMLOptionElement>(*child);
391
0
                    option_group_items.append(SelectItemOption { id_counter++, strip_newlines(option_element.text_content()), option_element.value(), option_element.selected(), option_element.disabled(), option_element });
392
0
                }
393
0
            }
394
0
            m_select_items.append(SelectItemOptionGroup { opt_group_element.get_attribute(AttributeNames::label).value_or(String {}), option_group_items });
395
0
        }
396
397
0
        if (is<HTMLOptionElement>(*child)) {
398
0
            auto& option_element = verify_cast<HTMLOptionElement>(*child);
399
0
            m_select_items.append(SelectItemOption { id_counter++, strip_newlines(option_element.text_content()), option_element.value(), option_element.selected(), option_element.disabled(), option_element });
400
0
        }
401
402
0
        if (is<HTMLHRElement>(*child))
403
0
            m_select_items.append(SelectItemSeparator {});
404
0
    }
405
406
    // Request select dropdown
407
0
    auto weak_element = make_weak_ptr<HTMLSelectElement>();
408
0
    auto rect = get_bounding_client_rect();
409
0
    auto position = document().navigable()->to_top_level_position(Web::CSSPixelPoint { rect->x(), rect->y() });
410
0
    document().page().did_request_select_dropdown(weak_element, position, CSSPixels(rect->width()), m_select_items);
411
0
    set_is_open(true);
412
0
}
413
414
// https://html.spec.whatwg.org/multipage/input.html#dom-select-showpicker
415
WebIDL::ExceptionOr<void> HTMLSelectElement::show_picker()
416
0
{
417
    // FIXME: Deduplicate with HTMLInputElement
418
    // The showPicker() method steps are:
419
420
    // 1. If this is not mutable, then throw an "InvalidStateError" DOMException.
421
0
    if (!enabled())
422
0
        return WebIDL::InvalidStateError::create(realm(), "Element is not mutable"_string);
423
424
    // 2. If this's relevant settings object's origin is not same origin with this's relevant settings object's top-level origin,
425
    // and this is a select element, then throw a "SecurityError" DOMException.
426
0
    if (!relevant_settings_object(*this).origin().is_same_origin(relevant_settings_object(*this).top_level_origin)) {
427
0
        return WebIDL::SecurityError::create(realm(), "Cross origin pickers are not allowed"_string);
428
0
    }
429
430
    // 3. If this's relevant global object does not have transient activation, then throw a "NotAllowedError" DOMException.
431
    // FIXME: The global object we get here should probably not need casted to Window to check for transient activation
432
0
    auto& global_object = relevant_global_object(*this);
433
0
    if (!is<HTML::Window>(global_object) || !static_cast<HTML::Window&>(global_object).has_transient_activation()) {
434
0
        return WebIDL::NotAllowedError::create(realm(), "Too long since user activation to show picker"_string);
435
0
    }
436
437
    // FIXME: 4. If this is a select element, and this is not being rendered, then throw a "NotSupportedError" DOMException.
438
439
    // 5. Show the picker, if applicable, for this.
440
0
    show_the_picker_if_applicable();
441
0
    return {};
442
0
}
443
444
void HTMLSelectElement::activation_behavior(DOM::Event const& event)
445
0
{
446
0
    if (event.is_trusted())
447
0
        show_the_picker_if_applicable();
448
0
}
449
450
void HTMLSelectElement::did_select_item(Optional<u32> const& id)
451
0
{
452
0
    set_is_open(false);
453
454
0
    if (!id.has_value())
455
0
        return;
456
457
0
    for (auto const& option_element : list_of_options())
458
0
        option_element->set_selected(false);
459
460
0
    for (auto const& item : m_select_items) {
461
0
        if (item.has<SelectItemOption>()) {
462
0
            auto const& item_option = item.get<SelectItemOption>();
463
0
            if (item_option.id == *id)
464
0
                item_option.option_element->set_selected(true);
465
0
        }
466
0
        if (item.has<SelectItemOptionGroup>()) {
467
0
            auto item_option_group = item.get<SelectItemOptionGroup>();
468
0
            for (auto const& item_option : item_option_group.items) {
469
0
                if (item_option.id == *id)
470
0
                    item_option.option_element->set_selected(true);
471
0
            }
472
0
        }
473
0
    }
474
475
0
    update_inner_text_element();
476
0
    queue_input_and_change_events();
477
0
}
478
479
void HTMLSelectElement::form_associated_element_was_inserted()
480
0
{
481
0
    create_shadow_tree_if_needed();
482
483
    // Wait until children are ready
484
0
    queue_an_element_task(HTML::Task::Source::Microtask, [this] {
485
0
        update_selectedness();
486
0
    });
487
0
}
488
489
void HTMLSelectElement::form_associated_element_was_removed(DOM::Node*)
490
0
{
491
0
    set_shadow_root(nullptr);
492
0
}
493
494
void HTMLSelectElement::computed_css_values_changed()
495
0
{
496
    // Hide chevron icon when appearance is none
497
0
    if (m_chevron_icon_element) {
498
0
        auto appearance = computed_css_values()->appearance();
499
0
        if (appearance.has_value() && *appearance == CSS::Appearance::None) {
500
0
            MUST(m_chevron_icon_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"_string));
501
0
        } else {
502
0
            MUST(m_chevron_icon_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"_string));
503
0
        }
504
0
    }
505
0
}
506
507
void HTMLSelectElement::create_shadow_tree_if_needed()
508
0
{
509
0
    if (shadow_root())
510
0
        return;
511
512
0
    auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
513
0
    set_shadow_root(shadow_root);
514
515
0
    auto border = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
516
0
    MUST(border->set_attribute(HTML::AttributeNames::style, R"~~~(
517
0
        display: flex;
518
0
        align-items: center;
519
0
        height: 100%;
520
0
    )~~~"_string));
521
0
    MUST(shadow_root->append_child(border));
522
523
0
    m_inner_text_element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
524
0
    MUST(m_inner_text_element->set_attribute(HTML::AttributeNames::style, R"~~~(
525
0
        flex: 1;
526
0
    )~~~"_string));
527
0
    MUST(border->append_child(*m_inner_text_element));
528
529
    // FIXME: Find better way to add chevron icon
530
0
    m_chevron_icon_element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
531
0
    MUST(m_chevron_icon_element->set_attribute(HTML::AttributeNames::style, R"~~~(
532
0
        width: 16px;
533
0
        height: 16px;
534
0
        margin-left: 4px;
535
0
    )~~~"_string));
536
0
    MUST(m_chevron_icon_element->set_inner_html("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z\" /></svg>"sv));
537
0
    MUST(border->append_child(*m_chevron_icon_element));
538
539
0
    update_inner_text_element();
540
0
}
541
542
void HTMLSelectElement::update_inner_text_element()
543
0
{
544
0
    if (!m_inner_text_element)
545
0
        return;
546
547
    // Update inner text element to text content of selected option
548
0
    for (auto const& option_element : list_of_options()) {
549
0
        if (option_element->selected()) {
550
0
            m_inner_text_element->set_text_content(strip_newlines(option_element->text_content()));
551
0
            return;
552
0
        }
553
0
    }
554
0
}
555
556
// https://html.spec.whatwg.org/multipage/form-elements.html#selectedness-setting-algorithm
557
void HTMLSelectElement::update_selectedness()
558
0
{
559
0
    if (has_attribute(AttributeNames::multiple))
560
0
        return;
561
562
    // If element's multiple attribute is absent, and element's display size is 1,
563
0
    if (size() == 1) {
564
0
        bool has_selected_elements = false;
565
0
        for (auto const& option_element : list_of_options()) {
566
0
            if (option_element->selected()) {
567
0
                has_selected_elements = true;
568
0
                break;
569
0
            }
570
0
        }
571
572
        // and no option elements in the element's list of options have their selectedness set to true,
573
0
        if (!has_selected_elements) {
574
            // then set the selectedness of the first option element in the list of options in tree order
575
            // that is not disabled, if any, to true, and return.
576
0
            for (auto const& option_element : list_of_options()) {
577
0
                if (!option_element->disabled()) {
578
0
                    option_element->set_selected_internal(true);
579
0
                    update_inner_text_element();
580
0
                    return;
581
0
                }
582
0
            }
583
0
        }
584
0
    }
585
586
    // If element's multiple attribute is absent,
587
    // and two or more option elements in element's list of options have their selectedness set to true,
588
    // then set the selectedness of all but the last option element with its selectedness set to true
589
    // in the list of options in tree order to false.
590
0
    int number_of_selected = 0;
591
0
    for (auto const& option_element : list_of_options()) {
592
0
        if (option_element->selected())
593
0
            ++number_of_selected;
594
0
    }
595
    // and two or more option elements in element's list of options have their selectedness set to true,
596
0
    if (number_of_selected >= 2) {
597
        // then set the selectedness of all but the last option element with its selectedness set to true
598
        // in the list of options in tree order to false.
599
0
        for (auto const& option_element : list_of_options()) {
600
0
            if (number_of_selected == 1) {
601
0
                break;
602
0
            }
603
0
            option_element->set_selected_internal(false);
604
0
            --number_of_selected;
605
0
        }
606
0
    }
607
0
    update_inner_text_element();
608
0
}
609
}