/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 | | } |