Skip to main content

script/
xpath.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
5//! Bindings to the `xpath` crate
6
7use std::cell::Ref;
8use std::cmp::Ordering;
9use std::fmt::Debug;
10use std::rc::Rc;
11
12use html5ever::{LocalName, Namespace, Prefix};
13use script_bindings::callback::ExceptionHandling;
14use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
15use script_bindings::codegen::GenericBindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
16use script_bindings::root::Dom;
17use script_bindings::str::DOMString;
18use style::Atom;
19use style::dom::OpaqueNode;
20
21use crate::dom::attr::Attr;
22use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
23use crate::dom::bindings::error::{Error, Fallible};
24use crate::dom::bindings::inheritance::Castable;
25use crate::dom::bindings::root::DomRoot;
26use crate::dom::comment::Comment;
27use crate::dom::document::Document;
28use crate::dom::element::Element;
29use crate::dom::element::attributes::storage::AttributeStorage;
30use crate::dom::node::{Node, NodeTraits, PrecedingNodeIterator, ShadowIncluding};
31use crate::dom::processinginstruction::ProcessingInstruction;
32use crate::dom::text::Text;
33
34pub(crate) type Value = xpath::Value<XPathWrapper<DomRoot<Node>>>;
35
36/// Wrapper type that allows us to define xpath traits on the relevant types,
37/// since they're not defined in `script`.
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub(crate) struct XPathWrapper<T>(pub T);
40
41pub(crate) struct XPathImplementation;
42
43impl xpath::Dom for XPathImplementation {
44    type Node = XPathWrapper<DomRoot<Node>>;
45    type NamespaceResolver = XPathWrapper<Rc<XPathNSResolver>>;
46}
47
48impl xpath::Node for XPathWrapper<DomRoot<Node>> {
49    type ProcessingInstruction = XPathWrapper<DomRoot<ProcessingInstruction>>;
50    type Document = XPathWrapper<DomRoot<Document>>;
51    type Attribute = XPathWrapper<DomRoot<Attr>>;
52    type Element = XPathWrapper<DomRoot<Element>>;
53    /// A opaque handle to a node with the sole purpose of comparing one node with another.
54    type Opaque = OpaqueNode;
55
56    fn is_comment(&self) -> bool {
57        self.0.is::<Comment>()
58    }
59
60    fn is_text(&self) -> bool {
61        self.0.is::<Text>()
62    }
63
64    fn text_content(&self) -> String {
65        self.0.GetTextContent().unwrap_or_default().into()
66    }
67
68    #[expect(unsafe_code)]
69    fn language(&self) -> Option<String> {
70        let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
71        let cx = &mut cx;
72
73        self.0.get_lang(cx)
74    }
75
76    fn parent(&self) -> Option<Self> {
77        // The parent of an attribute node is its owner, see
78        // https://www.w3.org/TR/1999/REC-xpath-19991116/#attribute-nodes
79        if let Some(attribute) = self.0.downcast::<Attr>() {
80            return attribute
81                .GetOwnerElement()
82                .map(DomRoot::upcast)
83                .map(XPathWrapper);
84        }
85
86        self.0.GetParentNode().map(XPathWrapper)
87    }
88
89    fn children(&self) -> impl Iterator<Item = Self> {
90        self.0.children().map(XPathWrapper)
91    }
92
93    fn compare_tree_order(&self, other: &Self) -> Ordering {
94        if self == other {
95            Ordering::Equal
96        } else if self.0.is_before(&other.0) {
97            Ordering::Less
98        } else {
99            Ordering::Greater
100        }
101    }
102
103    fn traverse_preorder(&self) -> impl Iterator<Item = Self> {
104        self.0
105            .traverse_preorder(ShadowIncluding::No)
106            .map(XPathWrapper)
107    }
108
109    fn inclusive_ancestors(&self) -> impl Iterator<Item = Self> {
110        self.0
111            .inclusive_ancestors(ShadowIncluding::No)
112            .map(XPathWrapper)
113    }
114
115    fn preceding_nodes(&self) -> impl Iterator<Item = Self> {
116        PrecedingNodeIteratorWithoutAncestors::new(&self.0).map(XPathWrapper)
117    }
118
119    fn following_nodes(&self) -> impl Iterator<Item = Self> {
120        let owner_document = self.0.owner_document();
121        let next_non_descendant_node = self
122            .0
123            .following_nodes(owner_document.upcast())
124            .next_skipping_children();
125        let following_nodes = next_non_descendant_node
126            .clone()
127            .map(|node| node.following_nodes(owner_document.upcast()))
128            .into_iter()
129            .flatten();
130        next_non_descendant_node
131            .into_iter()
132            .chain(following_nodes)
133            .map(XPathWrapper)
134    }
135
136    fn preceding_siblings(&self) -> impl Iterator<Item = Self> {
137        self.0.preceding_siblings().map(XPathWrapper)
138    }
139
140    fn following_siblings(&self) -> impl Iterator<Item = Self> {
141        self.0.following_siblings().map(XPathWrapper)
142    }
143
144    fn owner_document(&self) -> Self::Document {
145        XPathWrapper(self.0.owner_document())
146    }
147
148    fn to_opaque(&self) -> Self::Opaque {
149        self.0.to_opaque()
150    }
151
152    fn as_processing_instruction(&self) -> Option<Self::ProcessingInstruction> {
153        self.0
154            .downcast::<ProcessingInstruction>()
155            .map(DomRoot::from_ref)
156            .map(XPathWrapper)
157    }
158
159    fn as_attribute(&self) -> Option<Self::Attribute> {
160        self.0
161            .downcast::<Attr>()
162            .map(DomRoot::from_ref)
163            .map(XPathWrapper)
164    }
165
166    fn as_element(&self) -> Option<Self::Element> {
167        self.0
168            .downcast::<Element>()
169            .map(DomRoot::from_ref)
170            .map(XPathWrapper)
171    }
172
173    fn get_root_node(&self) -> Self {
174        XPathWrapper(self.0.GetRootNode(&GetRootNodeOptions::empty()))
175    }
176}
177
178impl xpath::Document for XPathWrapper<DomRoot<Document>> {
179    type Node = XPathWrapper<DomRoot<Node>>;
180
181    fn get_elements_with_id(
182        &self,
183        id: &str,
184    ) -> impl Iterator<Item = XPathWrapper<DomRoot<Element>>> {
185        struct ElementIterator<'a> {
186            elements: Ref<'a, [Dom<Element>]>,
187            position: usize,
188        }
189
190        impl<'a> Iterator for ElementIterator<'a> {
191            type Item = XPathWrapper<DomRoot<Element>>;
192
193            fn next(&mut self) -> Option<Self::Item> {
194                let element = self.elements.get(self.position)?;
195                self.position += 1;
196                Some(element.as_rooted().into())
197            }
198        }
199
200        ElementIterator {
201            elements: self.0.get_elements_with_id(&Atom::from(id)),
202            position: 0,
203        }
204    }
205}
206
207impl xpath::Element for XPathWrapper<DomRoot<Element>> {
208    type Node = XPathWrapper<DomRoot<Node>>;
209    type Attribute = XPathWrapper<DomRoot<Attr>>;
210
211    fn as_node(&self) -> Self::Node {
212        DomRoot::from_ref(self.0.upcast::<Node>()).into()
213    }
214
215    #[expect(unsafe_code)]
216    fn attributes(&self) -> impl Iterator<Item = Self::Attribute> {
217        let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
218        let cx = &mut cx;
219
220        struct AttributeIterator<'a> {
221            attributes: &'a AttributeStorage,
222            position: usize,
223        }
224
225        impl<'a> Iterator for AttributeIterator<'a> {
226            type Item = XPathWrapper<DomRoot<Attr>>;
227
228            fn next(&mut self) -> Option<Self::Item> {
229                let entries = self.attributes.borrow();
230                let entry = entries.get(self.position)?;
231                self.position += 1;
232                Some(DomRoot::from_ref(entry.as_attr().unwrap()).into())
233            }
234
235            fn size_hint(&self) -> (usize, Option<usize>) {
236                let exact_length = self.attributes.borrow().len() - self.position;
237                (exact_length, Some(exact_length))
238            }
239        }
240
241        // XPath needs full DOM attribute nodes.
242        AttributeIterator {
243            attributes: self.0.dom_attrs(cx),
244            position: 0,
245        }
246    }
247
248    fn prefix(&self) -> Option<Prefix> {
249        self.0.prefix().clone()
250    }
251
252    fn namespace(&self) -> Namespace {
253        self.0.namespace().clone()
254    }
255
256    fn local_name(&self) -> LocalName {
257        self.0.local_name().clone()
258    }
259
260    fn is_html_element_in_html_document(&self) -> bool {
261        self.0.is_html_element() && self.0.owner_document().is_html_document()
262    }
263}
264
265impl xpath::Attribute for XPathWrapper<DomRoot<Attr>> {
266    type Node = XPathWrapper<DomRoot<Node>>;
267
268    fn as_node(&self) -> Self::Node {
269        XPathWrapper(DomRoot::from_ref(self.0.upcast::<Node>()))
270    }
271
272    fn prefix(&self) -> Option<Prefix> {
273        self.0.prefix().cloned()
274    }
275
276    fn namespace(&self) -> Namespace {
277        self.0.namespace().clone()
278    }
279
280    fn local_name(&self) -> LocalName {
281        self.0.local_name().clone()
282    }
283}
284
285impl xpath::NamespaceResolver for XPathWrapper<Rc<XPathNSResolver>> {
286    #[expect(unsafe_code)]
287    fn resolve_namespace_prefix(&self, prefix: &str) -> Option<String> {
288        let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
289        let cx = &mut cx;
290
291        self.0
292            .LookupNamespaceURI__(cx, Some(DOMString::from(prefix)), ExceptionHandling::Report)
293            .ok()
294            .flatten()
295            .map(String::from)
296    }
297}
298
299impl xpath::ProcessingInstruction for XPathWrapper<DomRoot<ProcessingInstruction>> {
300    fn target(&self) -> String {
301        self.0.target().to_owned().into()
302    }
303}
304
305impl<T> From<T> for XPathWrapper<T> {
306    fn from(value: T) -> Self {
307        Self(value)
308    }
309}
310
311impl<T> XPathWrapper<T> {
312    pub(crate) fn into_inner(self) -> T {
313        self.0
314    }
315}
316
317pub(crate) fn parse_expression(
318    expression: &str,
319    resolver: Option<Rc<XPathNSResolver>>,
320    is_in_html_document: bool,
321) -> Fallible<xpath::Expression> {
322    xpath::parse(expression, resolver.map(XPathWrapper), is_in_html_document).map_err(|error| {
323        match error {
324            xpath::ParserError::FailedToResolveNamespacePrefix => Error::Namespace(None),
325            _ => Error::Syntax(Some(format!("Failed to parse XPath expression: {error:?}"))),
326        }
327    })
328}
329
330enum PrecedingNodeIteratorWithoutAncestors {
331    Done,
332    NotDone {
333        current: DomRoot<Node>,
334        /// When we're currently walking over the subtree of a node in reverse tree order
335        /// then this is the iterator for doing that.
336        subtree_iterator: Option<PrecedingNodeIterator>,
337    },
338}
339
340/// Returns the previous element (in tree order) that is not an ancestor of `node`.
341fn previous_non_ancestor_node(node: &Node) -> Option<DomRoot<Node>> {
342    let mut current = DomRoot::from_ref(node);
343    loop {
344        if let Some(previous_sibling) = current.GetPreviousSibling() {
345            return Some(previous_sibling);
346        }
347
348        current = current.GetParentNode()?;
349    }
350}
351
352impl PrecedingNodeIteratorWithoutAncestors {
353    fn new(node: &Node) -> Self {
354        let Some(current) = previous_non_ancestor_node(node) else {
355            return Self::Done;
356        };
357
358        Self::NotDone {
359            subtree_iterator: current
360                .descending_last_children()
361                .last()
362                .map(|node| node.preceding_nodes(&current)),
363            current,
364        }
365    }
366}
367
368impl Iterator for PrecedingNodeIteratorWithoutAncestors {
369    type Item = DomRoot<Node>;
370
371    fn next(&mut self) -> Option<Self::Item> {
372        let Self::NotDone {
373            current,
374            subtree_iterator,
375        } = self
376        else {
377            return None;
378        };
379
380        if let Some(next_node) = subtree_iterator
381            .as_mut()
382            .and_then(|iterator| iterator.next())
383        {
384            return Some(next_node);
385        }
386
387        // Our current subtree is exhausted. Return the root of the subtree and move on to the next one
388        // in inverse tree order.
389        let Some(next_subtree) = previous_non_ancestor_node(current) else {
390            *self = Self::Done;
391            return None;
392        };
393
394        *current = next_subtree;
395        *subtree_iterator = current
396            .descending_last_children()
397            .last()
398            .map(|node| node.preceding_nodes(current));
399
400        self.next()
401    }
402}