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::{QualName, local_name, ns};
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::{CustomElementCreationMode, Element, ElementCreator};
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 = Element::create(
65                QualName::new(None, ns!(html), local_name!("option")),
66                None,
67                &document,
68                ElementCreator::ScriptCreated,
69                CustomElementCreationMode::Asynchronous,
70                None,
71                can_gc,
72            );
73            let node = element.upcast::<Node>();
74            root.AppendChild(node, can_gc)?;
75        }
76        Ok(())
77    }
78}
79
80impl HTMLOptionsCollectionMethods<crate::DomTypeHolder> for HTMLOptionsCollection {
81    // FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of
82    // HTMLOptionsCollection) implements NamedGetter.
83    // https://github.com/servo/servo/issues/5875
84    //
85    // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem
86    fn NamedGetter(&self, name: DOMString) -> Option<DomRoot<Element>> {
87        self.upcast().NamedItem(name)
88    }
89
90    // https://heycam.github.io/webidl/#dfn-supported-property-names
91    fn SupportedPropertyNames(&self) -> Vec<DOMString> {
92        self.upcast().SupportedPropertyNames()
93    }
94
95    // FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of
96    // HTMLOptionsCollection) implements IndexedGetter.
97    // https://github.com/servo/servo/issues/5875
98    //
99    // https://dom.spec.whatwg.org/#dom-htmlcollection-item
100    fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> {
101        self.upcast().IndexedGetter(index)
102    }
103
104    // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-setter
105    fn IndexedSetter(
106        &self,
107        index: u32,
108        value: Option<&HTMLOptionElement>,
109        can_gc: CanGc,
110    ) -> ErrorResult {
111        if let Some(value) = value {
112            // Step 2
113            let length = self.upcast().Length();
114
115            // Step 3
116            let n = index as i32 - length as i32;
117
118            // Step 4
119            if n > 0 {
120                self.add_new_elements(n as u32, can_gc)?;
121            }
122
123            // Step 5
124            let node = value.upcast::<Node>();
125            let root = self.upcast().root_node();
126            if n >= 0 {
127                Node::pre_insert(node, &root, None, can_gc).map(|_| ())
128            } else {
129                let child = self.upcast().IndexedGetter(index).unwrap();
130                let child_node = child.upcast::<Node>();
131
132                root.ReplaceChild(node, child_node, can_gc).map(|_| ())
133            }
134        } else {
135            // Step 1
136            self.Remove(index as i32);
137            Ok(())
138        }
139    }
140
141    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-length>
142    fn Length(&self) -> u32 {
143        self.upcast().Length()
144    }
145
146    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-length>
147    fn SetLength(&self, length: u32, can_gc: CanGc) {
148        // Step 1. Let current be the number of nodes represented by the collection.
149        let current = self.upcast().Length();
150
151        match length.cmp(&current) {
152            // Step 2. If the given value is greater than current, then:
153            Ordering::Greater => {
154                // Step 2.1 If the given value is greater than 100,000, then return.
155                if length > 100_000 {
156                    return;
157                }
158
159                // Step 2.2 Let n be value − current.
160                let n = length - current;
161
162                // Step 2.3 Append n new option elements with no attributes and no child
163                // nodes to the select element on which this is rooted.
164                self.add_new_elements(n, can_gc).unwrap();
165            },
166            // Step 3. If the given value is less than current, then:
167            Ordering::Less => {
168                // Step 3.1. Let n be current − value.
169                // Step 3.2 Remove the last n nodes in the collection from their parent nodes.
170                for index in (length..current).rev() {
171                    self.Remove(index as i32)
172                }
173            },
174            _ => {},
175        }
176    }
177
178    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-add>
179    fn Add(
180        &self,
181        element: HTMLOptionElementOrHTMLOptGroupElement,
182        before: Option<HTMLElementOrLong>,
183    ) -> ErrorResult {
184        let root = self.upcast().root_node();
185
186        let node: &Node = match element {
187            HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElement(ref element) => {
188                element.upcast()
189            },
190            HTMLOptionElementOrHTMLOptGroupElement::HTMLOptGroupElement(ref element) => {
191                element.upcast()
192            },
193        };
194
195        // Step 1
196        if node.is_ancestor_of(&root) {
197            return Err(Error::HierarchyRequest);
198        }
199
200        if let Some(HTMLElementOrLong::HTMLElement(ref before_element)) = before {
201            // Step 2
202            let before_node = before_element.upcast::<Node>();
203            if !root.is_ancestor_of(before_node) {
204                return Err(Error::NotFound(None));
205            }
206
207            // Step 3
208            if node == before_node {
209                return Ok(());
210            }
211        }
212
213        // Step 4
214        let reference_node = before.and_then(|before| match before {
215            HTMLElementOrLong::HTMLElement(element) => Some(DomRoot::upcast::<Node>(element)),
216            HTMLElementOrLong::Long(index) => self
217                .upcast()
218                .IndexedGetter(index as u32)
219                .map(DomRoot::upcast::<Node>),
220        });
221
222        // Step 5
223        let parent = if let Some(ref reference_node) = reference_node {
224            reference_node.GetParentNode().unwrap()
225        } else {
226            root
227        };
228
229        // Step 6
230        Node::pre_insert(node, &parent, reference_node.as_deref(), CanGc::note()).map(|_| ())
231    }
232
233    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-remove>
234    fn Remove(&self, index: i32) {
235        if let Some(element) = self.upcast().IndexedGetter(index as u32) {
236            element.Remove(CanGc::note());
237        }
238    }
239
240    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-selectedindex>
241    fn SelectedIndex(&self) -> i32 {
242        self.upcast()
243            .root_node()
244            .downcast::<HTMLSelectElement>()
245            .expect("HTMLOptionsCollection not rooted on a HTMLSelectElement")
246            .SelectedIndex()
247    }
248
249    /// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-selectedindex>
250    fn SetSelectedIndex(&self, index: i32, can_gc: CanGc) {
251        self.upcast()
252            .root_node()
253            .downcast::<HTMLSelectElement>()
254            .expect("HTMLOptionsCollection not rooted on a HTMLSelectElement")
255            .SetSelectedIndex(index, can_gc)
256    }
257}