/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/node/nodelist.rs
314 строк
10 KB
Simon Sapin
script: Reduce GC-rooting in `NodeList` (#46664)
21 июл 2026, 15:56
Не верифицирован
21 июл 2026, 15:56
51b652e
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::RefCell; use dom_struct::dom_struct; use js::context::{JSContext, NoGC}; use script_bindings::dom::UnrootedDom; use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx}; use stylo_atoms::Atom; use crate::dom::ChildrenMutation; use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::document::Document; use crate::dom::html::htmlelement::HTMLElement; use crate::dom::html::htmlformelement::HTMLFormElement; use crate::dom::node::Node; use crate::dom::window::Window; #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) enum NodeListType { Simple(Vec<Dom<Node>>), Children(ChildrenList), Labels(LabelsList), Radio(RadioList), ElementsByName(ElementsByNameList), } // https://dom.spec.whatwg.org/#interface-nodelist #[dom_struct] pub(crate) struct NodeList { reflector_: Reflector, list_type: NodeListType, } impl NodeList { #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn new_inherited(list_type: NodeListType) -> NodeList { NodeList { reflector_: Reflector::new(), list_type, } } #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn new( cx: &mut JSContext, window: &Window, list_type: NodeListType, ) -> DomRoot<NodeList> { reflect_dom_object_with_cx(Box::new(NodeList::new_inherited(list_type)), window, cx) } pub(crate) fn new_simple_list<T>( cx: &mut JSContext, window: &Window, iter: T, ) -> DomRoot<NodeList> where T: Iterator<Item = DomRoot<Node>>, { NodeList::new( cx, window, NodeListType::Simple(iter.map(|r| Dom::from_ref(&*r)).collect()), ) } pub(crate) fn new_simple_list_slice( cx: &mut JSContext, window: &Window, slice: &[&Node], ) -> DomRoot<NodeList> { NodeList::new( cx, window, NodeListType::Simple(slice.iter().map(|r| Dom::from_ref(*r)).collect()), ) } pub(crate) fn new_child_list( cx: &mut JSContext, window: &Window, node: &Node, ) -> DomRoot<NodeList> { NodeList::new(cx, window, NodeListType::Children(ChildrenList::new(node))) } pub(crate) fn new_labels_list( cx: &mut JSContext, window: &Window, element: &HTMLElement, ) -> DomRoot<NodeList> { NodeList::new(cx, window, NodeListType::Labels(LabelsList::new(element))) } pub(crate) fn new_elements_by_name_list( cx: &mut JSContext, window: &Window, document: &Document, name: DOMString, ) -> DomRoot<NodeList> { NodeList::new( cx, window, NodeListType::ElementsByName(ElementsByNameList::new(document, name)), ) } pub(crate) fn empty(cx: &mut JSContext, window: &Window) -> DomRoot<NodeList> { NodeList::new(cx, window, NodeListType::Simple(vec![])) } } impl NodeListMethods<crate::DomTypeHolder> for NodeList { /// <https://dom.spec.whatwg.org/#dom-nodelist-length> fn Length(&self) -> u32 { match self.list_type { NodeListType::Simple(ref elems) => elems.len() as u32, NodeListType::Children(ref list) => list.len(), NodeListType::Labels(ref list) => list.len(), NodeListType::Radio(ref list) => list.len(), NodeListType::ElementsByName(ref list) => list.len(), } } /// <https://dom.spec.whatwg.org/#dom-nodelist-item> fn Item(&self, no_gc: &NoGC, index: u32) -> Option<DomRoot<Node>> { self.item_unrooted(no_gc, index) .map(|item| item.as_rooted()) } /// <https://dom.spec.whatwg.org/#dom-nodelist-item> fn IndexedGetter(&self, no_gc: &NoGC, index: u32) -> Option<DomRoot<Node>> { self.Item(no_gc, index) } } impl NodeList { pub(crate) fn as_children_list(&self) -> &ChildrenList { if let NodeListType::Children(ref list) = self.list_type { list } else { panic!("called as_children_list() on a non-children node list") } } pub(crate) fn item_unrooted<'a>( &self, no_gc: &'a NoGC, index: u32, ) -> Option<UnrootedDom<'a, Node>> { match self.list_type { NodeListType::Simple(ref elems) => elems .get(index as usize) .map(|node| UnrootedDom::from_dom(node.clone(), no_gc)), NodeListType::Children(ref list) => list.item(no_gc, index), NodeListType::Labels(ref list) => list.item(no_gc, index), NodeListType::Radio(ref list) => list.item(no_gc, index), NodeListType::ElementsByName(ref list) => list.item(no_gc, index), } } pub(crate) fn iter<'a>( &'a self, no_gc: &'a NoGC, ) -> impl Iterator<Item = UnrootedDom<'a, Node>> { let len = self.Length(); // There is room for optimization here in non-simple cases, // as calling Item repeatedly on a live list can involve redundant work. (0..len).flat_map(move |i| self.item_unrooted(no_gc, i)) } } #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) struct ChildrenList { node: Dom<Node>, cached_children: RefCell<Option<Vec<Dom<Node>>>>, } impl ChildrenList { pub(crate) fn new(node: &Node) -> ChildrenList { ChildrenList { node: Dom::from_ref(node), cached_children: RefCell::new(None), } } pub(crate) fn len(&self) -> u32 { self.node.children_count() } pub(crate) fn item<'a>(&self, no_gc: &'a NoGC, index: u32) -> Option<UnrootedDom<'a, Node>> { self.cached_children .borrow_mut() .get_or_insert_with(|| { self.node .children_unrooted(no_gc) .map(|child| (*child).clone()) .collect() }) .get(index as usize) .map(|child| UnrootedDom::from_dom(child.clone(), no_gc)) } pub(crate) fn children_changed(&self, mutation: &ChildrenMutation) { match mutation { ChildrenMutation::Append { .. } | ChildrenMutation::Insert { .. } | ChildrenMutation::Prepend { .. } | ChildrenMutation::Replace { .. } | ChildrenMutation::ReplaceAll { .. } => *self.cached_children.borrow_mut() = None, ChildrenMutation::ChangeText => {}, } } } // Labels lists: There might be room for performance optimization // analogous to the ChildrenMutation case of a children list, // in which we can keep information from an older access live // if we know nothing has happened that would change it. // However, label relationships can happen from further away // in the DOM than parent-child relationships, so it's not as simple, // and it's possible that tracking label moves would end up no faster // than recalculating labels. #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) struct LabelsList { element: Dom<HTMLElement>, } impl LabelsList { pub(crate) fn new(element: &HTMLElement) -> LabelsList { LabelsList { element: Dom::from_ref(element), } } pub(crate) fn len(&self) -> u32 { self.element.labels_count() } pub(crate) fn item<'a>(&self, no_gc: &'a NoGC, index: u32) -> Option<UnrootedDom<'a, Node>> { self.element.label_at(no_gc, index) } } // Radio node lists: There is room for performance improvement here; // a form is already aware of changes to its set of controls, // so a radio list can cache and cache-invalidate its contents // just by hooking into what the form already knows without a // separate mutation observer. FIXME #25482 #[derive(Clone, Copy, JSTraceable, MallocSizeOf)] pub(crate) enum RadioListMode { ControlsExceptImageInputs, Images, } #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) struct RadioList { form: Dom<HTMLFormElement>, mode: RadioListMode, #[no_trace] name: Atom, } impl RadioList { pub(crate) fn new(form: &HTMLFormElement, mode: RadioListMode, name: Atom) -> RadioList { RadioList { form: Dom::from_ref(form), mode, name, } } pub(crate) fn len(&self) -> u32 { self.form.count_for_radio_list(self.mode, &self.name) } pub(crate) fn item<'a>(&self, no_gc: &'a NoGC, index: u32) -> Option<UnrootedDom<'a, Node>> { self.form .nth_for_radio_list(no_gc, index, self.mode, &self.name) } } #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) struct ElementsByNameList { document: Dom<Document>, name: DOMString, } impl ElementsByNameList { pub(crate) fn new(document: &Document, name: DOMString) -> ElementsByNameList { ElementsByNameList { document: Dom::from_ref(document), name, } } pub(crate) fn len(&self) -> u32 { self.document.elements_by_name_count(&self.name) } pub(crate) fn item<'a>(&self, no_gc: &'a NoGC, index: u32) -> Option<UnrootedDom<'a, Node>> { self.document.nth_element_by_name(no_gc, index, &self.name) } }