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