Serenity Operating System
1/*
2 * Copyright (c) 2020, the SerenityOS developers.
3 * Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <LibWeb/Bindings/Intrinsics.h>
9#include <LibWeb/HTML/HTMLFormElement.h>
10#include <LibWeb/HTML/HTMLOptGroupElement.h>
11#include <LibWeb/HTML/HTMLOptionElement.h>
12#include <LibWeb/HTML/HTMLSelectElement.h>
13
14namespace Web::HTML {
15
16HTMLSelectElement::HTMLSelectElement(DOM::Document& document, DOM::QualifiedName qualified_name)
17 : HTMLElement(document, move(qualified_name))
18{
19}
20
21HTMLSelectElement::~HTMLSelectElement() = default;
22
23JS::ThrowCompletionOr<void> HTMLSelectElement::initialize(JS::Realm& realm)
24{
25 MUST_OR_THROW_OOM(Base::initialize(realm));
26 set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLSelectElementPrototype>(realm, "HTMLSelectElement"));
27
28 return {};
29}
30
31void HTMLSelectElement::visit_edges(Cell::Visitor& visitor)
32{
33 Base::visit_edges(visitor);
34 visitor.visit(m_options.ptr());
35}
36
37// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-options
38JS::GCPtr<HTMLOptionsCollection> const& HTMLSelectElement::options()
39{
40 if (!m_options) {
41 m_options = HTMLOptionsCollection::create(*this, [](DOM::Element const& element) {
42 // https://html.spec.whatwg.org/multipage/form-elements.html#concept-select-option-list
43 // The list of options for a select element consists of all the option element children of
44 // the select element, and all the option element children of all the optgroup element children
45 // of the select element, in tree order.
46 return is<HTMLOptionElement>(element);
47 }).release_value_but_fixme_should_propagate_errors();
48 }
49 return m_options;
50}
51
52// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-length
53size_t HTMLSelectElement::length()
54{
55 // 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.
56 return const_cast<HTMLOptionsCollection&>(*options()).length();
57}
58
59// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-item
60DOM::Element* HTMLSelectElement::item(size_t index)
61{
62 // 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.
63 return const_cast<HTMLOptionsCollection&>(*options()).item(index);
64}
65
66// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-nameditem
67DOM::Element* HTMLSelectElement::named_item(DeprecatedFlyString const& name)
68{
69 // 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.
70 return const_cast<HTMLOptionsCollection&>(*options()).named_item(name);
71}
72
73// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-add
74WebIDL::ExceptionOr<void> HTMLSelectElement::add(HTMLOptionOrOptGroupElement element, Optional<HTMLElementOrElementIndex> before)
75{
76 // Similarly, the add(element, before) method must act like its namesake method on that same options collection.
77 return const_cast<HTMLOptionsCollection&>(*options()).add(move(element), move(before));
78}
79
80// https://html.spec.whatwg.org/multipage/form-elements.html#concept-select-option-list
81Vector<JS::Handle<HTMLOptionElement>> HTMLSelectElement::list_of_options() const
82{
83 // The list of options for a select element consists of all the option element children of the select element,
84 // and all the option element children of all the optgroup element children of the select element, in tree order.
85 Vector<JS::Handle<HTMLOptionElement>> list;
86
87 for_each_child_of_type<HTMLOptionElement>([&](HTMLOptionElement& option_element) {
88 list.append(JS::make_handle(option_element));
89 });
90
91 for_each_child_of_type<HTMLOptGroupElement>([&](HTMLOptGroupElement const& optgroup_element) {
92 optgroup_element.for_each_child_of_type<HTMLOptionElement>([&](HTMLOptionElement& option_element) {
93 list.append(JS::make_handle(option_element));
94 });
95 });
96
97 return list;
98}
99
100// https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element:concept-form-reset-control
101void HTMLSelectElement::reset_algorithm()
102{
103 // The reset algorithm for select elements is to go through all the option elements in the element's list of options,
104 for (auto const& option_element : list_of_options()) {
105 // set their selectedness to true if the option element has a selected attribute, and false otherwise,
106 option_element->m_selected = option_element->has_attribute(AttributeNames::selected);
107 // set their dirtiness to false,
108 option_element->m_dirty = false;
109 // and then have the option elements ask for a reset.
110 option_element->ask_for_a_reset();
111 }
112}
113
114// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-selectedindex
115int HTMLSelectElement::selected_index() const
116{
117 // The selectedIndex IDL attribute, on getting, must return the index of the first option element in the list of options
118 // in tree order that has its selectedness set to true, if any. If there isn't one, then it must return −1.
119
120 int index = 0;
121 for (auto const& option_element : list_of_options()) {
122 if (option_element->selected())
123 return index;
124 ++index;
125 }
126 return -1;
127}
128
129void HTMLSelectElement::set_selected_index(int index)
130{
131 // On setting, the selectedIndex attribute must set the selectedness of all the option elements in the list of options to false,
132 // and then the option element in the list of options whose index is the given new value,
133 // if any, must have its selectedness set to true and its dirtiness set to true.
134 auto options = list_of_options();
135 for (auto& option : options)
136 option->m_selected = false;
137
138 if (index < 0 || index >= static_cast<int>(options.size()))
139 return;
140
141 auto& selected_option = options[index];
142 selected_option->m_selected = true;
143 selected_option->m_dirty = true;
144}
145
146// https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex
147i32 HTMLSelectElement::default_tab_index_value() const
148{
149 // See the base function for the spec comments.
150 return 0;
151}
152
153// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-type
154DeprecatedString const& HTMLSelectElement::type() const
155{
156 // 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.
157 static DeprecatedString select_one = "select-one"sv;
158 static DeprecatedString select_multiple = "select-multiple"sv;
159
160 if (!has_attribute(AttributeNames::multiple))
161 return select_one;
162
163 return select_multiple;
164}
165
166Optional<ARIA::Role> HTMLSelectElement::default_role() const
167{
168 // https://www.w3.org/TR/html-aria/#el-select-multiple-or-size-greater-1
169 if (has_attribute("multiple"))
170 return ARIA::Role::listbox;
171 if (has_attribute("size")) {
172 auto size_attribute = attribute("size").to_int();
173 if (size_attribute.has_value() && size_attribute.value() > 1)
174 return ARIA::Role::listbox;
175 }
176 // https://www.w3.org/TR/html-aria/#el-select
177 return ARIA::Role::combobox;
178}
179
180}