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