script/dom/html/
htmloptionscollection.rs1use std::cmp::Ordering;
6
7use dom_struct::dom_struct;
8use html5ever::{QualName, local_name, ns};
9use js::context::{JSContext, NoGC};
10use script_bindings::reflector::reflect_dom_object_with_cx;
11
12use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
13use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
14use crate::dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods;
15use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
16use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
17use crate::dom::bindings::codegen::UnionTypes::{
18 HTMLElementOrLong, HTMLOptionElementOrHTMLOptGroupElement,
19};
20use crate::dom::bindings::error::{Error, ErrorResult};
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::root::DomRoot;
23use crate::dom::bindings::str::DOMString;
24use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator};
25use crate::dom::html::htmlcollection::{CollectionFilter, HTMLCollection};
26use crate::dom::html::htmloptionelement::HTMLOptionElement;
27use crate::dom::html::htmlselectelement::HTMLSelectElement;
28use crate::dom::node::{Node, NodeTraits};
29use crate::dom::window::Window;
30
31#[dom_struct]
32pub(crate) struct HTMLOptionsCollection {
33 collection: HTMLCollection,
34}
35
36impl HTMLOptionsCollection {
37 fn new_inherited(
38 select: &HTMLSelectElement,
39 filter: Box<dyn CollectionFilter + 'static>,
40 ) -> HTMLOptionsCollection {
41 HTMLOptionsCollection {
42 collection: HTMLCollection::new_inherited(select.upcast(), filter),
43 }
44 }
45
46 pub(crate) fn new(
47 cx: &mut JSContext,
48 window: &Window,
49 select: &HTMLSelectElement,
50 filter: Box<dyn CollectionFilter + 'static>,
51 ) -> DomRoot<HTMLOptionsCollection> {
52 reflect_dom_object_with_cx(
53 Box::new(HTMLOptionsCollection::new_inherited(select, filter)),
54 window,
55 cx,
56 )
57 }
58
59 fn add_new_elements(&self, cx: &mut JSContext, count: u32) -> ErrorResult {
60 let root = self.upcast().root_node();
61 let document = root.owner_document();
62
63 for _ in 0..count {
64 let element = Element::create(
65 cx,
66 QualName::new(None, ns!(html), local_name!("option")),
67 None,
68 &document,
69 ElementCreator::ScriptCreated,
70 CustomElementCreationMode::Asynchronous,
71 None,
72 );
73 let node = element.upcast::<Node>();
74 root.AppendChild(cx, node)?;
75 }
76 Ok(())
77 }
78}
79
80impl HTMLOptionsCollectionMethods<crate::DomTypeHolder> for HTMLOptionsCollection {
81 fn NamedGetter(&self, cx: &JSContext, name: DOMString) -> Option<DomRoot<Element>> {
87 self.upcast().NamedItem(cx, name)
88 }
89
90 fn SupportedPropertyNames(&self, no_gc: &NoGC) -> Vec<DOMString> {
92 self.upcast().SupportedPropertyNames(no_gc)
93 }
94
95 fn IndexedGetter(&self, cx: &JSContext, index: u32) -> Option<DomRoot<Element>> {
101 self.upcast().IndexedGetter(cx, index)
102 }
103
104 fn IndexedSetter(
106 &self,
107 cx: &mut JSContext,
108 index: u32,
109 value: Option<&HTMLOptionElement>,
110 ) -> ErrorResult {
111 if let Some(value) = value {
112 let length = self.upcast().Length(cx);
114
115 let n = index as i32 - length as i32;
117
118 if n > 0 {
120 self.add_new_elements(cx, n as u32)?;
121 }
122
123 let node = value.upcast::<Node>();
125 let root = self.upcast().root_node();
126 if n >= 0 {
127 Node::pre_insert(cx, node, &root, None).map(|_| ())
128 } else {
129 let child = self.upcast().IndexedGetter(cx, index).unwrap();
130 let child_node = child.upcast::<Node>();
131
132 root.ReplaceChild(cx, node, child_node).map(|_| ())
133 }
134 } else {
135 self.Remove(cx, index as i32);
137 Ok(())
138 }
139 }
140
141 fn Length(&self, cx: &JSContext) -> u32 {
143 self.upcast().Length(cx)
144 }
145
146 fn SetLength(&self, cx: &mut JSContext, length: u32) {
148 let current = self.upcast().Length(cx);
150
151 match length.cmp(¤t) {
152 Ordering::Greater => {
154 if length > 100_000 {
156 return;
157 }
158
159 let n = length - current;
161
162 self.add_new_elements(cx, n).unwrap();
165 },
166 Ordering::Less => {
168 for index in (length..current).rev() {
171 self.Remove(cx, index as i32)
172 }
173 },
174 _ => {},
175 }
176 }
177
178 fn Add(
180 &self,
181 cx: &mut JSContext,
182 element: HTMLOptionElementOrHTMLOptGroupElement,
183 before: Option<HTMLElementOrLong>,
184 ) -> ErrorResult {
185 let root = self.upcast().root_node();
186
187 let node: &Node = match element {
188 HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElement(ref element) => {
189 element.upcast()
190 },
191 HTMLOptionElementOrHTMLOptGroupElement::HTMLOptGroupElement(ref element) => {
192 element.upcast()
193 },
194 };
195
196 if node.is_ancestor_of(&root) {
200 return Err(Error::HierarchyRequest(None));
201 }
202
203 if let Some(HTMLElementOrLong::HTMLElement(ref before_element)) = before {
204 let before_node = before_element.upcast::<Node>();
208 if !root.is_ancestor_of(before_node) {
209 return Err(Error::NotFound(None));
210 }
211
212 if node == before_node {
214 return Ok(());
215 }
216 }
217
218 let reference_node = before.and_then(|before| match before {
222 HTMLElementOrLong::HTMLElement(element) => Some(DomRoot::upcast::<Node>(element)),
223 HTMLElementOrLong::Long(index) => self
224 .upcast()
225 .IndexedGetter(cx, index as u32)
226 .map(DomRoot::upcast::<Node>),
227 });
228
229 let parent = if let Some(ref reference_node) = reference_node {
233 reference_node.GetParentNode().unwrap()
234 } else {
235 root
236 };
237
238 Node::pre_insert(cx, node, &parent, reference_node.as_deref()).map(|_| ())
240 }
241
242 fn Remove(&self, cx: &mut JSContext, index: i32) {
244 if let Some(element) = self.upcast().IndexedGetter(cx, index as u32) {
245 element.Remove(cx);
246 }
247 }
248
249 fn SelectedIndex(&self, cx: &JSContext) -> i32 {
251 self.upcast()
252 .root_node()
253 .downcast::<HTMLSelectElement>()
254 .expect("HTMLOptionsCollection not rooted on a HTMLSelectElement")
255 .SelectedIndex(cx)
256 }
257
258 fn SetSelectedIndex(&self, cx: &mut JSContext, index: i32) {
260 self.upcast()
261 .root_node()
262 .downcast::<HTMLSelectElement>()
263 .expect("HTMLOptionsCollection not rooted on a HTMLSelectElement")
264 .SetSelectedIndex(cx, index)
265 }
266}