/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/xpath.rs
397 строк
12 KB
Gae24
script: Don't store rooted values inside `XPathResultValue` (#46690)
21 июл 2026, 19:18
Не верифицирован
21 июл 2026, 19:18
9bc80d2
Код
Авторство
О чём код?
/* 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/. */ //! Bindings to the `xpath` crate use std::cell::Ref; use std::cmp::Ordering; use std::fmt::Debug; use std::rc::Rc; use html5ever::{LocalName, Namespace, Prefix}; use js::context::JSContext; use script_bindings::callback::ExceptionHandling; use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods; use script_bindings::codegen::GenericBindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; use script_bindings::root::Dom; use script_bindings::str::DOMString; use style::Atom; use style::dom::OpaqueNode; use crate::dom::attr::Attr; use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; use crate::dom::comment::Comment; use crate::dom::document::Document; use crate::dom::element::Element; use crate::dom::element::attributes::storage::AttributeStorage; use crate::dom::iterators::{PrecedingNodeIterator, ShadowIncluding}; use crate::dom::node::{Node, NodeTraits}; use crate::dom::processinginstruction::ProcessingInstruction; use crate::dom::text::Text; pub(crate) type Value = xpath::Value<XPathWrapper<DomRoot<Node>>>; /// Wrapper type that allows us to define xpath traits on the relevant types, /// since they're not defined in `script`. #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct XPathWrapper<T>(pub T); pub(crate) struct XPathImplementation; impl xpath::Dom for XPathImplementation { type Context = JSContext; type Node = XPathWrapper<DomRoot<Node>>; type NamespaceResolver = XPathWrapper<Rc<XPathNSResolver>>; } impl xpath::Node for XPathWrapper<DomRoot<Node>> { type Context = JSContext; type ProcessingInstruction = XPathWrapper<DomRoot<ProcessingInstruction>>; type Document = XPathWrapper<DomRoot<Document>>; type Attribute = XPathWrapper<DomRoot<Attr>>; type Element = XPathWrapper<DomRoot<Element>>; /// A opaque handle to a node with the sole purpose of comparing one node with another. type Opaque = OpaqueNode; fn is_comment(&self) -> bool { self.0.is::<Comment>() } fn is_text(&self) -> bool { self.0.is::<Text>() } fn text_content(&self) -> String { self.0.GetTextContent().unwrap_or_default().into() } fn language(&self) -> Option<String> { self.0.get_lang() } fn parent(&self) -> Option<Self> { // The parent of an attribute node is its owner, see // https://www.w3.org/TR/1999/REC-xpath-19991116/#attribute-nodes if let Some(attribute) = self.0.downcast::<Attr>() { return attribute .GetOwnerElement() .map(DomRoot::upcast) .map(XPathWrapper); } self.0.GetParentNode().map(XPathWrapper) } fn children(&self) -> impl Iterator<Item = Self> { self.0.children().map(XPathWrapper) } fn compare_tree_order(&self, other: &Self) -> Ordering { if self == other { Ordering::Equal } else if self.0.is_before(&other.0) { Ordering::Less } else { Ordering::Greater } } fn traverse_preorder(&self) -> impl Iterator<Item = Self> { self.0 .traverse_preorder(ShadowIncluding::No) .map(XPathWrapper) } fn inclusive_ancestors(&self) -> impl Iterator<Item = Self> { self.0 .inclusive_ancestors(ShadowIncluding::No) .map(XPathWrapper) } fn preceding_nodes(&self) -> impl Iterator<Item = Self> { PrecedingNodeIteratorWithoutAncestors::new(&self.0).map(XPathWrapper) } fn following_nodes(&self) -> impl Iterator<Item = Self> { let owner_document = self.0.owner_document(); let next_non_descendant_node = self .0 .following_nodes(owner_document.upcast(), ShadowIncluding::No) .next_skipping_children(); let following_nodes = next_non_descendant_node .clone() .map(|node| node.following_nodes(owner_document.upcast(), ShadowIncluding::No)) .into_iter() .flatten(); next_non_descendant_node .into_iter() .chain(following_nodes) .map(XPathWrapper) } fn preceding_siblings(&self) -> impl Iterator<Item = Self> { self.0.preceding_siblings().map(XPathWrapper) } fn following_siblings(&self) -> impl Iterator<Item = Self> { self.0.following_siblings().map(XPathWrapper) } fn owner_document(&self) -> Self::Document { XPathWrapper(self.0.owner_document()) } fn to_opaque(&self) -> Self::Opaque { self.0.to_opaque() } fn as_processing_instruction(&self) -> Option<Self::ProcessingInstruction> { self.0 .downcast::<ProcessingInstruction>() .map(DomRoot::from_ref) .map(XPathWrapper) } fn as_attribute(&self) -> Option<Self::Attribute> { self.0 .downcast::<Attr>() .map(DomRoot::from_ref) .map(XPathWrapper) } fn as_element(&self) -> Option<Self::Element> { self.0 .downcast::<Element>() .map(DomRoot::from_ref) .map(XPathWrapper) } fn get_root_node(&self) -> Self { XPathWrapper(self.0.GetRootNode(&GetRootNodeOptions::empty())) } } impl xpath::Document for XPathWrapper<DomRoot<Document>> { type Node = XPathWrapper<DomRoot<Node>>; fn get_elements_with_id( &self, cx: &mut JSContext, id: &str, ) -> impl Iterator<Item = XPathWrapper<DomRoot<Element>>> { struct ElementIterator<'a> { elements: Ref<'a, [Dom<Element>]>, position: usize, } impl<'a> Iterator for ElementIterator<'a> { type Item = XPathWrapper<DomRoot<Element>>; fn next(&mut self) -> Option<Self::Item> { let element = self.elements.get(self.position)?; self.position += 1; Some(element.as_rooted().into()) } } ElementIterator { elements: self.0.get_elements_with_id(cx, &Atom::from(id)), position: 0, } } } impl xpath::Element for XPathWrapper<DomRoot<Element>> { type Context = JSContext; type Node = XPathWrapper<DomRoot<Node>>; type Attribute = XPathWrapper<DomRoot<Attr>>; fn as_node(&self) -> Self::Node { DomRoot::from_ref(self.0.upcast::<Node>()).into() } fn attributes(&self, cx: &mut JSContext) -> impl Iterator<Item = Self::Attribute> { struct AttributeIterator<'a> { attributes: &'a AttributeStorage, position: usize, } impl<'a> Iterator for AttributeIterator<'a> { type Item = XPathWrapper<DomRoot<Attr>>; fn next(&mut self) -> Option<Self::Item> { let entries = self.attributes.borrow(); let entry = entries.get(self.position)?; self.position += 1; Some(DomRoot::from_ref(entry.as_attr().unwrap()).into()) } fn size_hint(&self) -> (usize, Option<usize>) { let exact_length = self.attributes.borrow().len() - self.position; (exact_length, Some(exact_length)) } } // XPath needs full DOM attribute nodes. AttributeIterator { attributes: self.0.dom_attrs(cx), position: 0, } } fn prefix(&self) -> Option<Prefix> { self.0.prefix().clone() } fn namespace(&self) -> Namespace { self.0.namespace().clone() } fn local_name(&self) -> LocalName { self.0.local_name().clone() } fn is_html_element_in_html_document(&self) -> bool { self.0.is_html_element() && self.0.owner_document().is_html_document() } } impl xpath::Attribute for XPathWrapper<DomRoot<Attr>> { type Node = XPathWrapper<DomRoot<Node>>; fn as_node(&self) -> Self::Node { XPathWrapper(DomRoot::from_ref(self.0.upcast::<Node>())) } fn prefix(&self) -> Option<Prefix> { self.0.prefix().cloned() } fn namespace(&self) -> Namespace { self.0.namespace().clone() } fn local_name(&self) -> LocalName { self.0.local_name().clone() } } impl xpath::NamespaceResolver for XPathWrapper<Rc<XPathNSResolver>> { type Context = JSContext; fn resolve_namespace_prefix(&self, cx: &mut JSContext, prefix: &str) -> Option<String> { self.0 .LookupNamespaceURI__(cx, Some(DOMString::from(prefix)), ExceptionHandling::Report) .ok() .flatten() .map(String::from) } } impl xpath::ProcessingInstruction for XPathWrapper<DomRoot<ProcessingInstruction>> { fn target(&self) -> String { self.0.target().to_owned().into() } } impl<T> From<T> for XPathWrapper<T> { fn from(value: T) -> Self { Self(value) } } pub(crate) fn parse_expression( cx: &mut JSContext, expression: &str, resolver: Option<Rc<XPathNSResolver>>, is_in_html_document: bool, ) -> Fallible<xpath::Expression> { xpath::parse( cx, expression, resolver.map(XPathWrapper), is_in_html_document, ) .map_err(|error| match error { xpath::ParserError::FailedToResolveNamespacePrefix => Error::Namespace(None), _ => Error::Syntax(Some(format!("Failed to parse XPath expression: {error:?}"))), }) } enum PrecedingNodeIteratorWithoutAncestors { Done, NotDone { current: DomRoot<Node>, /// When we're currently walking over the subtree of a node in reverse tree order /// then this is the iterator for doing that. subtree_iterator: Option<PrecedingNodeIterator>, }, } /// Returns the previous element (in tree order) that is not an ancestor of `node`. fn previous_non_ancestor_node(node: &Node) -> Option<DomRoot<Node>> { let mut current = DomRoot::from_ref(node); loop { if let Some(previous_sibling) = current.GetPreviousSibling() { return Some(previous_sibling); } current = current.GetParentNode()?; } } impl PrecedingNodeIteratorWithoutAncestors { fn new(node: &Node) -> Self { let Some(current) = previous_non_ancestor_node(node) else { return Self::Done; }; Self::NotDone { subtree_iterator: current .descending_last_children() .last() .map(|node| node.preceding_nodes(¤t)), current, } } } impl Iterator for PrecedingNodeIteratorWithoutAncestors { type Item = DomRoot<Node>; fn next(&mut self) -> Option<Self::Item> { let Self::NotDone { current, subtree_iterator, } = self else { return None; }; if let Some(next_node) = subtree_iterator .as_mut() .and_then(|iterator| iterator.next()) { return Some(next_node); } // Our current subtree is exhausted. Return the root of the subtree and move on to the next one // in inverse tree order. let Some(next_subtree) = previous_non_ancestor_node(current) else { *self = Self::Done; return None; }; *current = next_subtree; *subtree_iterator = current .descending_last_children() .last() .map(|node| node.preceding_nodes(current)); self.next() } }