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