Skip to main content

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