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