script/dom/
xpathexpression.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
5use dom_struct::dom_struct;
6use js::rust::HandleObject;
7use script_bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId};
8use xpath::{Expression, evaluate_parsed_xpath};
9
10use crate::dom::bindings::codegen::Bindings::XPathExpressionBinding::XPathExpressionMethods;
11use crate::dom::bindings::error::{Error, Fallible};
12use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
13use crate::dom::bindings::root::{Dom, DomRoot};
14use crate::dom::node::Node;
15use crate::dom::window::Window;
16use crate::dom::xpathresult::{XPathResult, XPathResultType};
17use crate::script_runtime::CanGc;
18use crate::xpath::{Value, XPathImplementation};
19
20#[dom_struct]
21pub(crate) struct XPathExpression {
22    reflector_: Reflector,
23    window: Dom<Window>,
24    #[no_trace]
25    parsed_expression: Expression,
26}
27
28impl XPathExpression {
29    fn new_inherited(window: &Window, parsed_expression: Expression) -> XPathExpression {
30        XPathExpression {
31            reflector_: Reflector::new(),
32            window: Dom::from_ref(window),
33            parsed_expression,
34        }
35    }
36
37    pub(crate) fn new(
38        window: &Window,
39        proto: Option<HandleObject>,
40        can_gc: CanGc,
41        parsed_expression: Expression,
42    ) -> DomRoot<XPathExpression> {
43        reflect_dom_object_with_proto(
44            Box::new(XPathExpression::new_inherited(window, parsed_expression)),
45            window,
46            proto,
47            can_gc,
48        )
49    }
50
51    pub(crate) fn evaluate_internal(
52        &self,
53        context_node: &Node,
54        result_type_num: u16,
55        result: Option<&XPathResult>,
56        can_gc: CanGc,
57    ) -> Fallible<DomRoot<XPathResult>> {
58        let is_allowed_context_node_type = matches!(
59            context_node.type_id(),
60            NodeTypeId::Attr |
61                NodeTypeId::CharacterData(
62                    CharacterDataTypeId::Comment |
63                        CharacterDataTypeId::Text(_) |
64                        CharacterDataTypeId::ProcessingInstruction
65                ) |
66                NodeTypeId::Document(_) |
67                NodeTypeId::Element(_)
68        );
69        if !is_allowed_context_node_type {
70            return Err(Error::NotSupported);
71        }
72
73        let result_type = XPathResultType::try_from(result_type_num)
74            .map_err(|()| Error::Type("Invalid XPath result type".to_string()))?;
75
76        let global = self.global();
77        let window = global.as_window();
78
79        let result_value = evaluate_parsed_xpath::<XPathImplementation>(
80            &self.parsed_expression,
81            DomRoot::from_ref(context_node).into(),
82        )
83        .map_err(|_| Error::Operation)?;
84
85        // Cast the result to the type we wanted
86        let result_value: Value = match result_type {
87            XPathResultType::Boolean => result_value.convert_to_boolean().into(),
88            XPathResultType::Number => result_value.convert_to_number().into(),
89            XPathResultType::String => result_value.convert_to_string().into(),
90            _ => result_value,
91        };
92
93        // TODO: if the wanted result type is AnyUnorderedNode | FirstOrderedNode,
94        // we could drop all nodes except one to save memory.
95        let inferred_result_type = if result_type == XPathResultType::Any {
96            match result_value {
97                Value::Boolean(_) => XPathResultType::Boolean,
98                Value::Number(_) => XPathResultType::Number,
99                Value::String(_) => XPathResultType::String,
100                Value::NodeSet(_) => XPathResultType::UnorderedNodeIterator,
101            }
102        } else {
103            result_type
104        };
105
106        if let Some(result) = result {
107            // According to https://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathEvaluator-evaluate, reusing
108            // the provided result object is optional. We choose to do it here because thats what other browsers do.
109            result.reinitialize_with(inferred_result_type, result_value.into());
110            Ok(DomRoot::from_ref(result))
111        } else {
112            Ok(XPathResult::new(
113                window,
114                None,
115                can_gc,
116                inferred_result_type,
117                result_value.into(),
118            ))
119        }
120    }
121}
122
123impl XPathExpressionMethods<crate::DomTypeHolder> for XPathExpression {
124    /// <https://dom.spec.whatwg.org/#dom-xpathexpression-evaluate>
125    fn Evaluate(
126        &self,
127        context_node: &Node,
128        result_type_num: u16,
129        result: Option<&XPathResult>,
130        can_gc: CanGc,
131    ) -> Fallible<DomRoot<XPathResult>> {
132        self.evaluate_internal(context_node, result_type_num, result, can_gc)
133    }
134}