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