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 js::context::JSContext;
14use script_bindings::callback::ExceptionHandling;
15use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
16use script_bindings::codegen::GenericBindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
17use script_bindings::root::Dom;
18use script_bindings::str::DOMString;
19use style::Atom;
20use style::dom::OpaqueNode;
21
22use crate::dom::attr::Attr;
23use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
24use crate::dom::bindings::error::{Error, Fallible};
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::root::DomRoot;
27use crate::dom::comment::Comment;
28use crate::dom::document::Document;
29use crate::dom::element::Element;
30use crate::dom::element::attributes::storage::AttributeStorage;
31use crate::dom::iterators::{PrecedingNodeIterator, ShadowIncluding};
32use crate::dom::node::{Node, NodeTraits};
33use crate::dom::processinginstruction::ProcessingInstruction;
34use crate::dom::text::Text;
35
36pub(crate) type Value = xpath::Value<XPathWrapper<DomRoot<Node>>>;
37
38/// Wrapper type that allows us to define xpath traits on the relevant types,
39/// since they're not defined in `script`.
40#[derive(Clone, Debug, Eq, PartialEq)]
41pub(crate) struct XPathWrapper<T>(pub T);
42
43pub(crate) struct XPathImplementation;
44
45impl xpath::Dom for XPathImplementation {
46    type Context = JSContext;
47    type Node = XPathWrapper<DomRoot<Node>>;
48    type NamespaceResolver = XPathWrapper<Rc<XPathNSResolver>>;
49}
50
51impl xpath::Node for XPathWrapper<DomRoot<Node>> {
52    type Context = JSContext;
53    type ProcessingInstruction = XPathWrapper<DomRoot<ProcessingInstruction>>;
54    type Document = XPathWrapper<DomRoot<Document>>;
55    type Attribute = XPathWrapper<DomRoot<Attr>>;
56    type Element = XPathWrapper<DomRoot<Element>>;
57    /// A opaque handle to a node with the sole purpose of comparing one node with another.
58    type Opaque = OpaqueNode;
59
60    fn is_comment(&self) -> bool {
61        self.0.is::<Comment>()
62    }
63
64    fn is_text(&self) -> bool {
65        self.0.is::<Text>()
66    }
67
68    fn text_content(&self) -> String {
69        self.0.GetTextContent().unwrap_or_default().into()
70    }
71
72    fn language(&self) -> Option<String> {
73        self.0.get_lang()
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(), ShadowIncluding::No)
124            .next_skipping_children();
125        let following_nodes = next_non_descendant_node
126            .clone()
127            .map(|node| node.following_nodes(owner_document.upcast(), ShadowIncluding::No))
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        cx: &mut JSContext,
184        id: &str,
185    ) -> impl Iterator<Item = XPathWrapper<DomRoot<Element>>> {
186        struct ElementIterator<'a> {
187            elements: Ref<'a, [Dom<Element>]>,
188            position: usize,
189        }
190
191        impl<'a> Iterator for ElementIterator<'a> {
192            type Item = XPathWrapper<DomRoot<Element>>;
193
194            fn next(&mut self) -> Option<Self::Item> {
195                let element = self.elements.get(self.position)?;
196                self.position += 1;
197                Some(element.as_rooted().into())
198            }
199        }
200
201        ElementIterator {
202            elements: self.0.get_elements_with_id(cx, &Atom::from(id)),
203            position: 0,
204        }
205    }
206}
207
208impl xpath::Element for XPathWrapper<DomRoot<Element>> {
209    type Context = JSContext;
210    type Node = XPathWrapper<DomRoot<Node>>;
211    type Attribute = XPathWrapper<DomRoot<Attr>>;
212
213    fn as_node(&self) -> Self::Node {
214        DomRoot::from_ref(self.0.upcast::<Node>()).into()
215    }
216
217    fn attributes(&self, cx: &mut JSContext) -> impl Iterator<Item = Self::Attribute> {
218        struct AttributeIterator<'a> {
219            attributes: &'a AttributeStorage,
220            position: usize,
221        }
222
223        impl<'a> Iterator for AttributeIterator<'a> {
224            type Item = XPathWrapper<DomRoot<Attr>>;
225
226            fn next(&mut self) -> Option<Self::Item> {
227                let entries = self.attributes.borrow();
228                let entry = entries.get(self.position)?;
229                self.position += 1;
230                Some(DomRoot::from_ref(entry.as_attr().unwrap()).into())
231            }
232
233            fn size_hint(&self) -> (usize, Option<usize>) {
234                let exact_length = self.attributes.borrow().len() - self.position;
235                (exact_length, Some(exact_length))
236            }
237        }
238
239        // XPath needs full DOM attribute nodes.
240        AttributeIterator {
241            attributes: self.0.dom_attrs(cx),
242            position: 0,
243        }
244    }
245
246    fn prefix(&self) -> Option<Prefix> {
247        self.0.prefix().clone()
248    }
249
250    fn namespace(&self) -> Namespace {
251        self.0.namespace().clone()
252    }
253
254    fn local_name(&self) -> LocalName {
255        self.0.local_name().clone()
256    }
257
258    fn is_html_element_in_html_document(&self) -> bool {
259        self.0.is_html_element() && self.0.owner_document().is_html_document()
260    }
261}
262
263impl xpath::Attribute for XPathWrapper<DomRoot<Attr>> {
264    type Node = XPathWrapper<DomRoot<Node>>;
265
266    fn as_node(&self) -> Self::Node {
267        XPathWrapper(DomRoot::from_ref(self.0.upcast::<Node>()))
268    }
269
270    fn prefix(&self) -> Option<Prefix> {
271        self.0.prefix().cloned()
272    }
273
274    fn namespace(&self) -> Namespace {
275        self.0.namespace().clone()
276    }
277
278    fn local_name(&self) -> LocalName {
279        self.0.local_name().clone()
280    }
281}
282
283impl xpath::NamespaceResolver for XPathWrapper<Rc<XPathNSResolver>> {
284    type Context = JSContext;
285
286    fn resolve_namespace_prefix(&self, cx: &mut JSContext, prefix: &str) -> Option<String> {
287        self.0
288            .LookupNamespaceURI__(cx, Some(DOMString::from(prefix)), ExceptionHandling::Report)
289            .ok()
290            .flatten()
291            .map(String::from)
292    }
293}
294
295impl xpath::ProcessingInstruction for XPathWrapper<DomRoot<ProcessingInstruction>> {
296    fn target(&self) -> String {
297        self.0.target().to_owned().into()
298    }
299}
300
301impl<T> From<T> for XPathWrapper<T> {
302    fn from(value: T) -> Self {
303        Self(value)
304    }
305}
306
307impl<T> XPathWrapper<T> {
308    pub(crate) fn into_inner(self) -> T {
309        self.0
310    }
311}
312
313pub(crate) fn parse_expression(
314    cx: &mut JSContext,
315    expression: &str,
316    resolver: Option<Rc<XPathNSResolver>>,
317    is_in_html_document: bool,
318) -> Fallible<xpath::Expression> {
319    xpath::parse(
320        cx,
321        expression,
322        resolver.map(XPathWrapper),
323        is_in_html_document,
324    )
325    .map_err(|error| match error {
326        xpath::ParserError::FailedToResolveNamespacePrefix => Error::Namespace(None),
327        _ => Error::Syntax(Some(format!("Failed to parse XPath expression: {error:?}"))),
328    })
329}
330
331enum PrecedingNodeIteratorWithoutAncestors {
332    Done,
333    NotDone {
334        current: DomRoot<Node>,
335        /// When we're currently walking over the subtree of a node in reverse tree order
336        /// then this is the iterator for doing that.
337        subtree_iterator: Option<PrecedingNodeIterator>,
338    },
339}
340
341/// Returns the previous element (in tree order) that is not an ancestor of `node`.
342fn previous_non_ancestor_node(node: &Node) -> Option<DomRoot<Node>> {
343    let mut current = DomRoot::from_ref(node);
344    loop {
345        if let Some(previous_sibling) = current.GetPreviousSibling() {
346            return Some(previous_sibling);
347        }
348
349        current = current.GetParentNode()?;
350    }
351}
352
353impl PrecedingNodeIteratorWithoutAncestors {
354    fn new(node: &Node) -> Self {
355        let Some(current) = previous_non_ancestor_node(node) else {
356            return Self::Done;
357        };
358
359        Self::NotDone {
360            subtree_iterator: current
361                .descending_last_children()
362                .last()
363                .map(|node| node.preceding_nodes(&current)),
364            current,
365        }
366    }
367}
368
369impl Iterator for PrecedingNodeIteratorWithoutAncestors {
370    type Item = DomRoot<Node>;
371
372    fn next(&mut self) -> Option<Self::Item> {
373        let Self::NotDone {
374            current,
375            subtree_iterator,
376        } = self
377        else {
378            return None;
379        };
380
381        if let Some(next_node) = subtree_iterator
382            .as_mut()
383            .and_then(|iterator| iterator.next())
384        {
385            return Some(next_node);
386        }
387
388        // Our current subtree is exhausted. Return the root of the subtree and move on to the next one
389        // in inverse tree order.
390        let Some(next_subtree) = previous_non_ancestor_node(current) else {
391            *self = Self::Done;
392            return None;
393        };
394
395        *current = next_subtree;
396        *subtree_iterator = current
397            .descending_last_children()
398            .last()
399            .map(|node| node.preceding_nodes(current));
400
401        self.next()
402    }
403}