Skip to main content

script/dom/node/
nodelist.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::cell::RefCell;
6
7use dom_struct::dom_struct;
8use script_bindings::reflector::{Reflector, reflect_dom_object};
9use stylo_atoms::Atom;
10
11use crate::dom::ChildrenMutation;
12use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
13use crate::dom::bindings::root::{Dom, DomRoot};
14use crate::dom::bindings::str::DOMString;
15use crate::dom::document::Document;
16use crate::dom::html::htmlelement::HTMLElement;
17use crate::dom::html::htmlformelement::HTMLFormElement;
18use crate::dom::node::Node;
19use crate::dom::window::Window;
20use crate::script_runtime::CanGc;
21
22#[derive(JSTraceable, MallocSizeOf)]
23#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
24pub(crate) enum NodeListType {
25    Simple(Vec<Dom<Node>>),
26    Children(ChildrenList),
27    Labels(LabelsList),
28    Radio(RadioList),
29    ElementsByName(ElementsByNameList),
30}
31
32// https://dom.spec.whatwg.org/#interface-nodelist
33#[dom_struct]
34pub(crate) struct NodeList {
35    reflector_: Reflector,
36    list_type: NodeListType,
37}
38
39impl NodeList {
40    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
41    pub(crate) fn new_inherited(list_type: NodeListType) -> NodeList {
42        NodeList {
43            reflector_: Reflector::new(),
44            list_type,
45        }
46    }
47
48    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
49    pub(crate) fn new(
50        window: &Window,
51        list_type: NodeListType,
52        can_gc: CanGc,
53    ) -> DomRoot<NodeList> {
54        reflect_dom_object(Box::new(NodeList::new_inherited(list_type)), window, can_gc)
55    }
56
57    pub(crate) fn new_simple_list<T>(window: &Window, iter: T, can_gc: CanGc) -> DomRoot<NodeList>
58    where
59        T: Iterator<Item = DomRoot<Node>>,
60    {
61        NodeList::new(
62            window,
63            NodeListType::Simple(iter.map(|r| Dom::from_ref(&*r)).collect()),
64            can_gc,
65        )
66    }
67
68    pub(crate) fn new_simple_list_slice(
69        window: &Window,
70        slice: &[&Node],
71        can_gc: CanGc,
72    ) -> DomRoot<NodeList> {
73        NodeList::new(
74            window,
75            NodeListType::Simple(slice.iter().map(|r| Dom::from_ref(*r)).collect()),
76            can_gc,
77        )
78    }
79
80    pub(crate) fn new_child_list(window: &Window, node: &Node, can_gc: CanGc) -> DomRoot<NodeList> {
81        NodeList::new(
82            window,
83            NodeListType::Children(ChildrenList::new(node)),
84            can_gc,
85        )
86    }
87
88    pub(crate) fn new_labels_list(
89        window: &Window,
90        element: &HTMLElement,
91        can_gc: CanGc,
92    ) -> DomRoot<NodeList> {
93        NodeList::new(
94            window,
95            NodeListType::Labels(LabelsList::new(element)),
96            can_gc,
97        )
98    }
99
100    pub(crate) fn new_elements_by_name_list(
101        window: &Window,
102        document: &Document,
103        name: DOMString,
104        can_gc: CanGc,
105    ) -> DomRoot<NodeList> {
106        NodeList::new(
107            window,
108            NodeListType::ElementsByName(ElementsByNameList::new(document, name)),
109            can_gc,
110        )
111    }
112
113    pub(crate) fn empty(window: &Window, can_gc: CanGc) -> DomRoot<NodeList> {
114        NodeList::new(window, NodeListType::Simple(vec![]), can_gc)
115    }
116}
117
118impl NodeListMethods<crate::DomTypeHolder> for NodeList {
119    /// <https://dom.spec.whatwg.org/#dom-nodelist-length>
120    fn Length(&self) -> u32 {
121        match self.list_type {
122            NodeListType::Simple(ref elems) => elems.len() as u32,
123            NodeListType::Children(ref list) => list.len(),
124            NodeListType::Labels(ref list) => list.len(),
125            NodeListType::Radio(ref list) => list.len(),
126            NodeListType::ElementsByName(ref list) => list.len(),
127        }
128    }
129
130    /// <https://dom.spec.whatwg.org/#dom-nodelist-item>
131    fn Item(&self, index: u32) -> Option<DomRoot<Node>> {
132        match self.list_type {
133            NodeListType::Simple(ref elems) => elems
134                .get(index as usize)
135                .map(|node| DomRoot::from_ref(&**node)),
136            NodeListType::Children(ref list) => list.item(index),
137            NodeListType::Labels(ref list) => list.item(index),
138            NodeListType::Radio(ref list) => list.item(index),
139            NodeListType::ElementsByName(ref list) => list.item(index),
140        }
141    }
142
143    /// <https://dom.spec.whatwg.org/#dom-nodelist-item>
144    fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Node>> {
145        self.Item(index)
146    }
147}
148
149impl NodeList {
150    pub(crate) fn as_children_list(&self) -> &ChildrenList {
151        if let NodeListType::Children(ref list) = self.list_type {
152            list
153        } else {
154            panic!("called as_children_list() on a non-children node list")
155        }
156    }
157
158    pub(crate) fn as_radio_list(&self) -> &RadioList {
159        if let NodeListType::Radio(ref list) = self.list_type {
160            list
161        } else {
162            panic!("called as_radio_list() on a non-radio node list")
163        }
164    }
165
166    pub(crate) fn iter(&self) -> impl Iterator<Item = DomRoot<Node>> + '_ {
167        let len = self.Length();
168        // There is room for optimization here in non-simple cases,
169        // as calling Item repeatedly on a live list can involve redundant work.
170        (0..len).flat_map(move |i| self.Item(i))
171    }
172}
173
174#[derive(JSTraceable, MallocSizeOf)]
175#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
176pub(crate) struct ChildrenList {
177    node: Dom<Node>,
178    cached_children: RefCell<Option<Vec<Dom<Node>>>>,
179}
180
181impl ChildrenList {
182    pub(crate) fn new(node: &Node) -> ChildrenList {
183        ChildrenList {
184            node: Dom::from_ref(node),
185            cached_children: RefCell::new(None),
186        }
187    }
188
189    pub(crate) fn len(&self) -> u32 {
190        self.node.children_count()
191    }
192
193    pub(crate) fn item(&self, index: u32) -> Option<DomRoot<Node>> {
194        self.cached_children
195            .borrow_mut()
196            .get_or_insert_with(|| {
197                self.node
198                    .children()
199                    .map(|child| Dom::from_ref(&*child))
200                    .collect()
201            })
202            .get(index as usize)
203            .map(|child| DomRoot::from_ref(&**child))
204    }
205
206    pub(crate) fn children_changed(&self, mutation: &ChildrenMutation) {
207        match mutation {
208            ChildrenMutation::Append { .. } |
209            ChildrenMutation::Insert { .. } |
210            ChildrenMutation::Prepend { .. } |
211            ChildrenMutation::Replace { .. } |
212            ChildrenMutation::ReplaceAll { .. } => *self.cached_children.borrow_mut() = None,
213            ChildrenMutation::ChangeText => {},
214        }
215    }
216}
217
218// Labels lists: There might be room for performance optimization
219// analogous to the ChildrenMutation case of a children list,
220// in which we can keep information from an older access live
221// if we know nothing has happened that would change it.
222// However, label relationships can happen from further away
223// in the DOM than parent-child relationships, so it's not as simple,
224// and it's possible that tracking label moves would end up no faster
225// than recalculating labels.
226#[derive(JSTraceable, MallocSizeOf)]
227#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
228pub(crate) struct LabelsList {
229    element: Dom<HTMLElement>,
230}
231
232impl LabelsList {
233    pub(crate) fn new(element: &HTMLElement) -> LabelsList {
234        LabelsList {
235            element: Dom::from_ref(element),
236        }
237    }
238
239    pub(crate) fn len(&self) -> u32 {
240        self.element.labels_count()
241    }
242
243    pub(crate) fn item(&self, index: u32) -> Option<DomRoot<Node>> {
244        self.element.label_at(index)
245    }
246}
247
248// Radio node lists: There is room for performance improvement here;
249// a form is already aware of changes to its set of controls,
250// so a radio list can cache and cache-invalidate its contents
251// just by hooking into what the form already knows without a
252// separate mutation observer. FIXME #25482
253#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
254pub(crate) enum RadioListMode {
255    ControlsExceptImageInputs,
256    Images,
257}
258
259#[derive(JSTraceable, MallocSizeOf)]
260#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
261pub(crate) struct RadioList {
262    form: Dom<HTMLFormElement>,
263    mode: RadioListMode,
264    #[no_trace]
265    name: Atom,
266}
267
268impl RadioList {
269    pub(crate) fn new(form: &HTMLFormElement, mode: RadioListMode, name: Atom) -> RadioList {
270        RadioList {
271            form: Dom::from_ref(form),
272            mode,
273            name,
274        }
275    }
276
277    pub(crate) fn len(&self) -> u32 {
278        self.form.count_for_radio_list(self.mode, &self.name)
279    }
280
281    pub(crate) fn item(&self, index: u32) -> Option<DomRoot<Node>> {
282        self.form.nth_for_radio_list(index, self.mode, &self.name)
283    }
284}
285
286#[derive(JSTraceable, MallocSizeOf)]
287#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
288pub(crate) struct ElementsByNameList {
289    document: Dom<Document>,
290    name: DOMString,
291}
292
293impl ElementsByNameList {
294    pub(crate) fn new(document: &Document, name: DOMString) -> ElementsByNameList {
295        ElementsByNameList {
296            document: Dom::from_ref(document),
297            name,
298        }
299    }
300
301    pub(crate) fn len(&self) -> u32 {
302        self.document.elements_by_name_count(&self.name)
303    }
304
305    pub(crate) fn item(&self, index: u32) -> Option<DomRoot<Node>> {
306        self.document
307            .nth_element_by_name(index, &self.name)
308            .map(|n| DomRoot::from_ref(&*n))
309    }
310}