1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::{JSContext, NoGC};
9use script_bindings::cell::DomRefCell;
10use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
11use script_bindings::domstring::TracedDOMString;
12use script_bindings::reflector::{Reflector, reflect_dom_object};
13
14use crate::dom::bindings::codegen::Bindings::XPathResultBinding::{
15 XPathResultConstants, XPathResultMethods,
16};
17use crate::dom::bindings::error::{Error, Fallible};
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::root::{Dom, DomRoot};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::node::Node;
22use crate::dom::window::Window;
23use crate::xpath::Value;
24
25#[repr(u16)]
26#[derive(Clone, Copy, Debug, Eq, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
27pub(crate) enum XPathResultType {
28 Any = XPathResultConstants::ANY_TYPE,
29 Number = XPathResultConstants::NUMBER_TYPE,
30 String = XPathResultConstants::STRING_TYPE,
31 Boolean = XPathResultConstants::BOOLEAN_TYPE,
32 UnorderedNodeIterator = XPathResultConstants::UNORDERED_NODE_ITERATOR_TYPE,
33 OrderedNodeIterator = XPathResultConstants::ORDERED_NODE_ITERATOR_TYPE,
34 UnorderedNodeSnapshot = XPathResultConstants::UNORDERED_NODE_SNAPSHOT_TYPE,
35 OrderedNodeSnapshot = XPathResultConstants::ORDERED_NODE_SNAPSHOT_TYPE,
36 AnyUnorderedNode = XPathResultConstants::ANY_UNORDERED_NODE_TYPE,
37 FirstOrderedNode = XPathResultConstants::FIRST_ORDERED_NODE_TYPE,
38}
39
40impl TryFrom<u16> for XPathResultType {
41 type Error = ();
42
43 fn try_from(value: u16) -> Result<Self, Self::Error> {
44 match value {
45 XPathResultConstants::ANY_TYPE => Ok(Self::Any),
46 XPathResultConstants::NUMBER_TYPE => Ok(Self::Number),
47 XPathResultConstants::STRING_TYPE => Ok(Self::String),
48 XPathResultConstants::BOOLEAN_TYPE => Ok(Self::Boolean),
49 XPathResultConstants::UNORDERED_NODE_ITERATOR_TYPE => Ok(Self::UnorderedNodeIterator),
50 XPathResultConstants::ORDERED_NODE_ITERATOR_TYPE => Ok(Self::OrderedNodeIterator),
51 XPathResultConstants::UNORDERED_NODE_SNAPSHOT_TYPE => Ok(Self::UnorderedNodeSnapshot),
52 XPathResultConstants::ORDERED_NODE_SNAPSHOT_TYPE => Ok(Self::OrderedNodeSnapshot),
53 XPathResultConstants::ANY_UNORDERED_NODE_TYPE => Ok(Self::AnyUnorderedNode),
54 XPathResultConstants::FIRST_ORDERED_NODE_TYPE => Ok(Self::FirstOrderedNode),
55 _ => Err(()),
56 }
57 }
58}
59
60#[derive(Debug, JSTraceable, MallocSizeOf)]
61#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
62enum XPathResultValue {
63 Boolean(bool),
64 Number(f64),
66 String(TracedDOMString),
67 Nodeset(Vec<Dom<Node>>),
69}
70
71impl From<Value> for XPathResultValue {
72 fn from(value: Value) -> Self {
73 match value {
74 Value::Boolean(b) => XPathResultValue::Boolean(b),
75 Value::Number(n) => XPathResultValue::Number(n),
76 Value::String(s) => XPathResultValue::String(s.into()),
77 Value::NodeSet(nodes) => XPathResultValue::Nodeset(
78 nodes
79 .into_iter()
80 .map(|xpath_node| xpath_node.0.as_traced())
81 .collect(),
82 ),
83 }
84 }
85}
86
87#[dom_struct]
88pub(crate) struct XPathResult {
89 reflector_: Reflector,
90 window: Dom<Window>,
91 version: Cell<u64>,
94 result_type: Cell<XPathResultType>,
95 value: DomRefCell<XPathResultValue>,
96 iterator_pos: Cell<usize>,
97}
98
99impl XPathResult {
100 fn new_inherited(window: &Window, result_type: XPathResultType, value: Value) -> XPathResult {
101 XPathResult {
102 reflector_: Reflector::new(),
103 window: Dom::from_ref(window),
104 version: Cell::new(
105 window
106 .Document()
107 .upcast::<Node>()
108 .inclusive_descendants_version(),
109 ),
110 result_type: Cell::new(result_type),
111 iterator_pos: Cell::new(0),
112 value: DomRefCell::new(value.into()),
113 }
114 }
115
116 fn document_changed_since_creation(&self) -> bool {
117 let current_document_version = self
118 .window
119 .Document()
120 .upcast::<Node>()
121 .inclusive_descendants_version();
122 current_document_version != self.version.get()
123 }
124
125 pub(crate) fn new(
129 cx: &mut JSContext,
130 window: &Window,
131 result_type: XPathResultType,
132 value: Value,
133 ) -> DomRoot<XPathResult> {
134 reflect_dom_object(
135 cx,
136 Box::new(XPathResult::new_inherited(window, result_type, value)),
137 window,
138 )
139 }
140
141 pub(crate) fn reinitialize_with(
142 &self,
143 no_gc: &NoGC,
144 result_type: XPathResultType,
145 value: Value,
146 ) {
147 self.result_type.set(result_type);
148 *self.value.safe_borrow_mut(no_gc) = value.into();
149 self.version.set(
150 self.window
151 .Document()
152 .upcast::<Node>()
153 .inclusive_descendants_version(),
154 );
155 self.iterator_pos.set(0);
156 }
157}
158
159impl XPathResultMethods<crate::DomTypeHolder> for XPathResult {
160 fn ResultType(&self) -> u16 {
162 self.result_type.get() as u16
163 }
164
165 fn GetNumberValue(&self) -> Fallible<f64> {
167 match (&*self.value.borrow(), self.result_type.get()) {
168 (XPathResultValue::Number(n), XPathResultType::Number) => Ok(*n),
169 _ => Err(Error::Type(
170 c"Can't get number value for non-number XPathResult".to_owned(),
171 )),
172 }
173 }
174
175 fn GetStringValue(&self) -> Fallible<DOMString> {
177 match (&*self.value.borrow(), self.result_type.get()) {
178 (XPathResultValue::String(s), XPathResultType::String) => Ok(s.clone().root()),
179 _ => Err(Error::Type(
180 c"Can't get string value for non-string XPathResult".to_owned(),
181 )),
182 }
183 }
184
185 fn GetBooleanValue(&self) -> Fallible<bool> {
187 match (&*self.value.borrow(), self.result_type.get()) {
188 (XPathResultValue::Boolean(b), XPathResultType::Boolean) => Ok(*b),
189 _ => Err(Error::Type(
190 c"Can't get boolean value for non-boolean XPathResult".to_owned(),
191 )),
192 }
193 }
194
195 fn IterateNext(&self) -> Fallible<Option<DomRoot<Node>>> {
197 if !matches!(
198 self.result_type.get(),
199 XPathResultType::OrderedNodeIterator | XPathResultType::UnorderedNodeIterator
200 ) {
201 return Err(Error::Type(c"Result is not an iterator".into()));
202 }
203
204 if self.document_changed_since_creation() {
205 return Err(Error::InvalidState(None));
206 }
207
208 let XPathResultValue::Nodeset(nodes) = &*self.value.borrow() else {
209 return Err(Error::Type(
210 c"Can't iterate on XPathResult that is not a node-set".to_owned(),
211 ));
212 };
213
214 let position = self.iterator_pos.get();
215 if position >= nodes.len() {
216 Ok(None)
217 } else {
218 let node = nodes[position].as_rooted();
219 self.iterator_pos.set(position + 1);
220 Ok(Some(node))
221 }
222 }
223
224 fn InvalidIteratorState(&self) -> bool {
226 let is_iterable = matches!(
227 self.result_type.get(),
228 XPathResultType::OrderedNodeIterator | XPathResultType::UnorderedNodeIterator
229 );
230
231 is_iterable && self.document_changed_since_creation()
232 }
233
234 fn GetSnapshotLength(&self) -> Fallible<u32> {
236 match (&*self.value.borrow(), self.result_type.get()) {
237 (
238 XPathResultValue::Nodeset(nodes),
239 XPathResultType::OrderedNodeSnapshot | XPathResultType::UnorderedNodeSnapshot,
240 ) => Ok(nodes.len() as u32),
241 _ => Err(Error::Type(
242 c"Can't get snapshot length of XPathResult that is not a snapshot".to_owned(),
243 )),
244 }
245 }
246
247 fn SnapshotItem(&self, index: u32) -> Fallible<Option<DomRoot<Node>>> {
249 match (&*self.value.borrow(), self.result_type.get()) {
250 (
251 XPathResultValue::Nodeset(nodes),
252 XPathResultType::OrderedNodeSnapshot | XPathResultType::UnorderedNodeSnapshot,
253 ) => Ok(nodes.get(index as usize).map(|node| node.as_rooted())),
254 _ => Err(Error::Type(
255 c"Can't get snapshot item of XPathResult that is not a snapshot".to_owned(),
256 )),
257 }
258 }
259
260 fn GetSingleNodeValue(&self) -> Fallible<Option<DomRoot<Node>>> {
262 match (&*self.value.borrow(), self.result_type.get()) {
263 (
264 XPathResultValue::Nodeset(nodes),
265 XPathResultType::AnyUnorderedNode | XPathResultType::FirstOrderedNode,
266 ) => Ok(nodes.first().map(|node| node.as_rooted())),
267 _ => Err(Error::Type(
268 c"Getting single value requires result type 'any unordered node' or 'first ordered node'".to_owned(),
269 )),
270 }
271 }
272}