script/dom/html/
htmloptionscollection.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cmp::Ordering;
6
7use dom_struct::dom_struct;
8use html5ever::local_name;
9
10use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
11use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
12use crate::dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods;
13use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
14use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
15use crate::dom::bindings::codegen::UnionTypes::{
16    HTMLElementOrLong, HTMLOptionElementOrHTMLOptGroupElement,
17};
18use crate::dom::bindings::error::{Error, ErrorResult};
19use crate::dom::bindings::inheritance::Castable;
20use crate::dom::bindings::reflector::reflect_dom_object;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::bindings::str::DOMString;
23use crate::dom::element::Element;
24use crate::dom::html::htmlcollection::{CollectionFilter, HTMLCollection};
25use crate::dom::html::htmloptionelement::HTMLOptionElement;
26use crate::dom::html::htmlselectelement::HTMLSelectElement;
27use crate::dom::node::{Node, NodeTraits};
28use crate::dom::window::Window;
29use crate::script_runtime::CanGc;
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        window: &Window,
48        select: &HTMLSelectElement,
49        filter: Box<dyn CollectionFilter + 'static>,
50        can_gc: CanGc,
51    ) -> DomRoot<HTMLOptionsCollection> {
52        reflect_dom_object(
53            Box::new(HTMLOptionsCollection::new_inherited(select, filter)),
54            window,
55            can_gc,
56        )
57    }
58
59    fn add_new_elements(&self, count: u32, can_gc: CanGc) -> ErrorResult {
60        let root = self.upcast().root_node();
61        let document = root.owner_document();
62
63        for _ in 0..count {
64            let element =
65                HTMLOptionElement::new(local_name!("option"), None, &document, None, can_gc);
66            let node = element.upcast::<Node>();
67            root.AppendChild(node, can_gc)?;
68        }
69        Ok(())
70    }
71}
72
73impl HTMLOptionsCollectionMethods<crate::DomTypeHolder> for HTMLOptionsCollection {
74    // FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of
75    // HTMLOptionsCollection) implements NamedGetter.
76    // https://github.com/servo/servo/issues/5875
77    //
78    // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem
79    fn NamedGetter(&self, name: DOMString) -> Option<DomRoot<Element>> {
80        self.upcast().NamedItem(name)
81    }
82
83    // https://heycam.github.io/webidl/#dfn-supported-property-names
84    fn SupportedPropertyNames(&self) -> Vec<DOMString> {
85        self.upcast().SupportedPropertyNames()
86    }
87
88    // FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of
89    // HTMLOptionsCollection) implements IndexedGetter.
90    // https://github.com/servo/servo/issues/5875
91    //
92    // https://dom.spec.whatwg.org/#dom-htmlcollection-item
93    fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> {
94        self.upcast().IndexedGetter(index)
95    }
96
97    // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-setter
98    fn IndexedSetter(
99        &self,
100        index: u32,
101        value: Option<&HTMLOptionElement>,
102        can_gc: CanGc,
103    ) -> ErrorResult {
104        if let Some(value) = value {
105            // Step 2
106            let length = self.upcast().Length();
107
108            // Step 3
109            let n = index as i32 - length as i32;
110
111            // Step 4
112            if n > 0 {
113                self.add_new_elements(n as u32, can_gc)?;
114            }
115
116            // Step 5
117            let node = value.upcast::<Node>();
118            let root = self.upcast().root_node();
119            if n >= 0 {
120                Node::pre_insert(node, &root, None, can_gc).map(|_| ())
121            } else {
122                let child = self.upcast().IndexedGetter(index).unwrap();
123                let child_node = child.upcast::<Node>();
124
125                root.ReplaceChild(node, child_node, can_gc).map(|_| ())
126            }
127        } else {
128            // Step 1
129            self.Remove(index as i32);
130            Ok(())
131        }
132    }
133
134    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-length>
135    fn Length(&self) -> u32 {
136        self.upcast().Length()
137    }
138
139    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-length>
140    fn SetLength(&self, length: u32, can_gc: CanGc) {
141        // Step 1. Let current be the number of nodes represented by the collection.
142        let current = self.upcast().Length();
143
144        match length.cmp(&current) {
145            // Step 2. If the given value is greater than current, then:
146            Ordering::Greater => {
147                // Step 2.1 If the given value is greater than 100,000, then return.
148                if length > 100_000 {
149                    return;
150                }
151
152                // Step 2.2 Let n be value − current.
153                let n = length - current;
154
155                // Step 2.3 Append n new option elements with no attributes and no child
156                // nodes to the select element on which this is rooted.
157                self.add_new_elements(n, can_gc).unwrap();
158            },
159            // Step 3. If the given value is less than current, then:
160            Ordering::Less => {
161                // Step 3.1. Let n be current − value.
162                // Step 3.2 Remove the last n nodes in the collection from their parent nodes.
163                for index in (length..current).rev() {
164                    self.Remove(index as i32)
165                }
166            },
167            _ => {},
168        }
169    }
170
171    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-add>
172    fn Add(
173        &self,
174        element: HTMLOptionElementOrHTMLOptGroupElement,
175        before: Option<HTMLElementOrLong>,
176    ) -> ErrorResult {
177        let root = self.upcast().root_node();
178
179        let node: &Node = match element {
180            HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElement(ref element) => {
181                element.upcast()
182            },
183            HTMLOptionElementOrHTMLOptGroupElement::HTMLOptGroupElement(ref element) => {
184                element.upcast()
185            },
186        };
187
188        // Step 1
189        if node.is_ancestor_of(&root) {
190            return Err(Error::HierarchyRequest);
191        }
192
193        if let Some(HTMLElementOrLong::HTMLElement(ref before_element)) = before {
194            // Step 2
195            let before_node = before_element.upcast::<Node>();
196            if !root.is_ancestor_of(before_node) {
197                return Err(Error::NotFound);
198            }
199
200            // Step 3
201            if node == before_node {
202                return Ok(());
203            }
204        }
205
206        // Step 4
207        let reference_node = before.and_then(|before| match before {
208            HTMLElementOrLong::HTMLElement(element) => Some(DomRoot::upcast::<Node>(element)),
209            HTMLElementOrLong::Long(index) => self
210                .upcast()
211                .IndexedGetter(index as u32)
212                .map(DomRoot::upcast::<Node>),
213        });
214
215        // Step 5
216        let parent = if let Some(ref reference_node) = reference_node {
217            reference_node.GetParentNode().unwrap()
218        } else {
219            root
220        };
221
222        // Step 6
223        Node::pre_insert(node, &parent, reference_node.as_deref(), CanGc::note()).map(|_| ())
224    }
225
226    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-remove>
227    fn Remove(&self, index: i32) {
228        if let Some(element) = self.upcast().IndexedGetter(index as u32) {
229            element.Remove(CanGc::note());
230        }
231    }
232
233    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-selectedindex>
234    fn SelectedIndex(&self) -> i32 {
235        self.upcast()
236            .root_node()
237            .downcast::<HTMLSelectElement>()
238            .expect("HTMLOptionsCollection not rooted on a HTMLSelectElement")
239            .SelectedIndex()
240    }
241
242    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-selectedindex>
243    fn SetSelectedIndex(&self, index: i32, can_gc: CanGc) {
244        self.upcast()
245            .root_node()
246            .downcast::<HTMLSelectElement>()
247            .expect("HTMLOptionsCollection not rooted on a HTMLSelectElement")
248            .SetSelectedIndex(index, can_gc)
249    }
250}