aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom
diff options
context:
space:
mode:
authorVille Lindholm <ville@lindholm.dev>2024-12-08 04:01:50 +0200
committerGitHub <noreply@github.com>2024-12-08 02:01:50 +0000
commitbc7fe41a02b044b52a54c7347f347557a4bd0e8d (patch)
tree328133ab7eea5f406965c24a3fd2c6cc4858c55c /components/script/dom
parent264c0f972fd1a732ee1d1490d78a1d26a65c6f5f (diff)
downloadservo-bc7fe41a02b044b52a54c7347f347557a4bd0e8d.tar.gz
servo-bc7fe41a02b044b52a54c7347f347557a4bd0e8d.zip
Add XPath parser/evaluator (#34463)
* Add XPath parser/evaluator Signed-off-by: Ville Lindholm <ville@lindholm.dev> * Correctly annotate XPathEvaluator IDL Signed-off-by: Ville Lindholm <ville@lindholm.dev> * [PR review]: have bindings pass in `can_gc` Signed-off-by: Ville Lindholm <ville@lindholm.dev> * [PR review]: add docstrings Signed-off-by: Ville Lindholm <ville@lindholm.dev> * [PR review]: implement PartialEq for Value for readability Signed-off-by: Ville Lindholm <ville@lindholm.dev> * [PR review]: add docstrings for CoreFunctions Signed-off-by: Ville Lindholm <ville@lindholm.dev> * [PR review]: simplify node test code Signed-off-by: Ville Lindholm <ville@lindholm.dev> * [PR review]: add unit tests for string handling xpath functions Signed-off-by: Ville Lindholm <ville@lindholm.dev> * put xpath features behind dom.xpath.enabled pref Signed-off-by: Ville Lindholm <ville@lindholm.dev> * [PR review] remove rstest and insta dev-deps Signed-off-by: Ville Lindholm <ville@lindholm.dev> * update wpt test expectations Signed-off-by: Ville Lindholm <ville@lindholm.dev> * [PR review]: tweak metadata files Signed-off-by: Ville Lindholm <ville@lindholm.dev> * update wpt test expectations AGAIN Signed-off-by: Ville Lindholm <ville@lindholm.dev> --------- Signed-off-by: Ville Lindholm <ville@lindholm.dev>
Diffstat (limited to 'components/script/dom')
-rw-r--r--components/script/dom/bindings/codegen/Bindings.conf10
-rw-r--r--components/script/dom/document.rs62
-rw-r--r--components/script/dom/mod.rs3
-rw-r--r--components/script/dom/node.rs19
-rw-r--r--components/script/dom/webidls/XPathEvaluator.webidl30
-rw-r--r--components/script/dom/webidls/XPathExpression.webidl14
-rw-r--r--components/script/dom/webidls/XPathNSResolver.webidl9
-rw-r--r--components/script/dom/webidls/XPathResult.webidl29
-rw-r--r--components/script/dom/xpathevaluator.rs110
-rw-r--r--components/script/dom/xpathexpression.rs77
-rw-r--r--components/script/dom/xpathresult.rs261
11 files changed, 617 insertions, 7 deletions
diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf
index a1f3de7e46a..0a15dde41d7 100644
--- a/components/script/dom/bindings/codegen/Bindings.conf
+++ b/components/script/dom/bindings/codegen/Bindings.conf
@@ -84,7 +84,7 @@ DOMInterfaces = {
},
'Document': {
- 'canGc': ['Close', 'CreateElement', 'CreateElementNS', 'ImportNode', 'SetTitle', 'Write', 'Writeln', 'CreateEvent', 'CreateRange', 'Open', 'Open_', 'CreateComment', 'CreateAttribute', 'CreateAttributeNS', 'CreateDocumentFragment', 'CreateTextNode', 'CreateCDATASection', 'CreateProcessingInstruction', 'Prepend', 'Append', 'ReplaceChildren', 'SetBgColor', 'SetFgColor', 'Fonts', 'ElementFromPoint', 'ElementsFromPoint', 'ExitFullscreen'],
+ 'canGc': ['Close', 'CreateElement', 'CreateElementNS', 'ImportNode', 'SetTitle', 'Write', 'Writeln', 'CreateEvent', 'CreateRange', 'Open', 'Open_', 'CreateComment', 'CreateAttribute', 'CreateAttributeNS', 'CreateDocumentFragment', 'CreateTextNode', 'CreateCDATASection', 'CreateProcessingInstruction', 'Prepend', 'Append', 'ReplaceChildren', 'SetBgColor', 'SetFgColor', 'Fonts', 'ElementFromPoint', 'ElementsFromPoint', 'ExitFullscreen', 'CreateExpression', 'CreateNSResolver', 'Evaluate'],
},
'DocumentFragment': {
@@ -488,6 +488,14 @@ DOMInterfaces = {
'canGc': ['Abort', 'GetResponseXML', 'Response', 'Send'],
},
+'XPathEvaluator': {
+ 'canGc': ['CreateExpression', 'Evaluate'],
+},
+
+'XPathExpression': {
+ 'canGc': ['Evaluate'],
+},
+
'XRBoundedReferenceSpace': {
'canGc': ['BoundsGeometry'],
},
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 7021cbe62d5..99bbedf9bb1 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -71,6 +71,7 @@ use uuid::Uuid;
use webgpu::swapchain::WebGPUContextId;
use webrender_api::units::DeviceIntRect;
+use super::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMethods;
use crate::animation_timeline::AnimationTimeline;
use crate::animations::Animations;
use crate::document_loader::{DocumentLoader, LoadType};
@@ -95,6 +96,7 @@ use crate::dom::bindings::codegen::Bindings::TouchBinding::TouchMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::{
FrameRequestCallback, ScrollBehavior, WindowMethods,
};
+use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
use crate::dom::bindings::codegen::UnionTypes::{NodeOrString, StringOrElementCreationOptions};
use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
@@ -180,6 +182,7 @@ use crate::dom::webgpu::gpucanvascontext::GPUCanvasContext;
use crate::dom::wheelevent::WheelEvent;
use crate::dom::window::{ReflowReason, Window};
use crate::dom::windowproxy::WindowProxy;
+use crate::dom::xpathevaluator::XPathEvaluator;
use crate::fetch::FetchCanceller;
use crate::network_listener::{NetworkListener, PreInvoke};
use crate::realms::{enter_realm, AlreadyInRealm, InRealm};
@@ -682,6 +685,12 @@ impl Document {
self.is_html_document
}
+ pub fn is_xhtml_document(&self) -> bool {
+ self.content_type.type_() == mime::APPLICATION &&
+ self.content_type.subtype().as_str() == "xhtml" &&
+ self.content_type.suffix() == Some(mime::XML)
+ }
+
pub fn set_https_state(&self, https_state: HttpsState) {
self.https_state.set(https_state);
}
@@ -4519,11 +4528,7 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
local_name.make_ascii_lowercase();
}
- let is_xhtml = self.content_type.type_() == mime::APPLICATION &&
- self.content_type.subtype().as_str() == "xhtml" &&
- self.content_type.suffix() == Some(mime::XML);
-
- let ns = if self.is_html_document || is_xhtml {
+ let ns = if self.is_html_document || self.is_xhtml_document() {
ns!(html)
} else {
ns!()
@@ -5613,6 +5618,53 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
fn VisibilityState(&self) -> DocumentVisibilityState {
self.visibility_state.get()
}
+
+ fn CreateExpression(
+ &self,
+ expression: DOMString,
+ resolver: Option<Rc<XPathNSResolver>>,
+ can_gc: CanGc,
+ ) -> Fallible<DomRoot<super::types::XPathExpression>> {
+ let global = self.global();
+ let window = global.as_window();
+ let evaluator = XPathEvaluator::new(window, None, can_gc);
+ XPathEvaluatorMethods::<crate::DomTypeHolder>::CreateExpression(
+ &*evaluator,
+ expression,
+ resolver,
+ can_gc,
+ )
+ }
+
+ fn CreateNSResolver(&self, node_resolver: &Node, can_gc: CanGc) -> DomRoot<Node> {
+ let global = self.global();
+ let window = global.as_window();
+ let evaluator = XPathEvaluator::new(window, None, can_gc);
+ XPathEvaluatorMethods::<crate::DomTypeHolder>::CreateNSResolver(&*evaluator, node_resolver)
+ }
+
+ fn Evaluate(
+ &self,
+ expression: DOMString,
+ context_node: &Node,
+ resolver: Option<Rc<XPathNSResolver>>,
+ type_: u16,
+ result: Option<&super::types::XPathResult>,
+ can_gc: CanGc,
+ ) -> Fallible<DomRoot<super::types::XPathResult>> {
+ let global = self.global();
+ let window = global.as_window();
+ let evaluator = XPathEvaluator::new(window, None, can_gc);
+ XPathEvaluatorMethods::<crate::DomTypeHolder>::Evaluate(
+ &*evaluator,
+ expression,
+ context_node,
+ resolver,
+ type_,
+ result,
+ can_gc,
+ )
+ }
}
fn update_with_current_instant(marker: &Cell<Option<CrossProcessInstant>>) {
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index 3253cf6b39d..52a8559628a 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -601,4 +601,7 @@ pub mod xmlhttprequest;
pub mod xmlhttprequesteventtarget;
pub mod xmlhttprequestupload;
pub mod xmlserializer;
+pub mod xpathevaluator;
+pub mod xpathexpression;
+pub mod xpathresult;
pub use self::webgl_extensions::ext::*;
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index 3c7df12b633..b8e3531508e 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -10,7 +10,7 @@ use std::default::Default;
use std::ops::Range;
use std::slice::from_ref;
use std::sync::Arc as StdArc;
-use std::{cmp, iter};
+use std::{cmp, fmt, iter};
use app_units::Au;
use base::id::{BrowsingContextId, PipelineId};
@@ -168,6 +168,23 @@ pub struct Node {
layout_data: DomRefCell<Option<Box<GenericLayoutData>>>,
}
+impl fmt::Debug for Node {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if matches!(self.type_id(), NodeTypeId::Element(_)) {
+ let el = self.downcast::<Element>().unwrap();
+ el.fmt(f)
+ } else {
+ write!(f, "[Node({:?})]", self.type_id())
+ }
+ }
+}
+
+impl fmt::Debug for DomRoot<Node> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ (**self).fmt(f)
+ }
+}
+
/// Flags for node items
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
pub struct NodeFlags(u16);
diff --git a/components/script/dom/webidls/XPathEvaluator.webidl b/components/script/dom/webidls/XPathEvaluator.webidl
new file mode 100644
index 00000000000..8586b19232b
--- /dev/null
+++ b/components/script/dom/webidls/XPathEvaluator.webidl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://dom.spec.whatwg.org/#mixin-xpathevaluatorbase
+interface mixin XPathEvaluatorBase {
+ [NewObject, Throws, Pref="dom.xpath.enabled"] XPathExpression createExpression(
+ DOMString expression,
+ optional XPathNSResolver? resolver = null
+ );
+ Node createNSResolver(Node nodeResolver); // legacy
+ // XPathResult.ANY_TYPE = 0
+ [Throws, Pref="dom.xpath.enabled"] XPathResult evaluate(
+ DOMString expression,
+ Node contextNode,
+ optional XPathNSResolver? resolver = null,
+ optional unsigned short type = 0,
+ optional XPathResult? result = null
+ );
+};
+
+Document includes XPathEvaluatorBase;
+
+// https://dom.spec.whatwg.org/#interface-xpathevaluator
+[Exposed=Window, Pref="dom.xpath.enabled"]
+interface XPathEvaluator {
+ constructor();
+};
+
+XPathEvaluator includes XPathEvaluatorBase;
diff --git a/components/script/dom/webidls/XPathExpression.webidl b/components/script/dom/webidls/XPathExpression.webidl
new file mode 100644
index 00000000000..bf5ba2b83aa
--- /dev/null
+++ b/components/script/dom/webidls/XPathExpression.webidl
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://dom.spec.whatwg.org/#interface-xpathexpression
+[Exposed=Window, Pref="dom.xpath.enabled"]
+interface XPathExpression {
+ // XPathResult.ANY_TYPE = 0
+ [Throws] XPathResult evaluate(
+ Node contextNode,
+ optional unsigned short type = 0,
+ optional XPathResult? result = null
+ );
+};
diff --git a/components/script/dom/webidls/XPathNSResolver.webidl b/components/script/dom/webidls/XPathNSResolver.webidl
new file mode 100644
index 00000000000..099f6afd105
--- /dev/null
+++ b/components/script/dom/webidls/XPathNSResolver.webidl
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://dom.spec.whatwg.org/#mixin-xpathevaluatorbase
+[Exposed=Window]
+callback interface XPathNSResolver {
+ DOMString? lookupNamespaceURI(DOMString? prefix);
+};
diff --git a/components/script/dom/webidls/XPathResult.webidl b/components/script/dom/webidls/XPathResult.webidl
new file mode 100644
index 00000000000..25e87e3024a
--- /dev/null
+++ b/components/script/dom/webidls/XPathResult.webidl
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://dom.spec.whatwg.org/#interface-xpathresult
+[Exposed=Window, Pref="dom.xpath.enabled"]
+interface XPathResult {
+ const unsigned short ANY_TYPE = 0;
+ const unsigned short NUMBER_TYPE = 1;
+ const unsigned short STRING_TYPE = 2;
+ const unsigned short BOOLEAN_TYPE = 3;
+ const unsigned short UNORDERED_NODE_ITERATOR_TYPE = 4;
+ const unsigned short ORDERED_NODE_ITERATOR_TYPE = 5;
+ const unsigned short UNORDERED_NODE_SNAPSHOT_TYPE = 6;
+ const unsigned short ORDERED_NODE_SNAPSHOT_TYPE = 7;
+ const unsigned short ANY_UNORDERED_NODE_TYPE = 8;
+ const unsigned short FIRST_ORDERED_NODE_TYPE = 9;
+
+ readonly attribute unsigned short resultType;
+ [Throws] readonly attribute unrestricted double numberValue;
+ [Throws] readonly attribute DOMString stringValue;
+ [Throws] readonly attribute boolean booleanValue;
+ [Throws] readonly attribute Node? singleNodeValue;
+ [Throws] readonly attribute boolean invalidIteratorState;
+ [Throws] readonly attribute unsigned long snapshotLength;
+
+ [Throws] Node? iterateNext();
+ [Throws] Node? snapshotItem(unsigned long index);
+};
diff --git a/components/script/dom/xpathevaluator.rs b/components/script/dom/xpathevaluator.rs
new file mode 100644
index 00000000000..967f7ef6076
--- /dev/null
+++ b/components/script/dom/xpathevaluator.rs
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::HandleObject;
+
+use super::bindings::error::Error;
+use crate::dom::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMethods;
+use crate::dom::bindings::codegen::Bindings::XPathExpressionBinding::XPathExpression_Binding::XPathExpressionMethods;
+use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
+use crate::dom::bindings::error::Fallible;
+use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
+use crate::dom::bindings::root::{Dom, DomRoot};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::node::Node;
+use crate::dom::window::Window;
+use crate::dom::xpathexpression::XPathExpression;
+use crate::dom::xpathresult::XPathResult;
+use crate::script_runtime::CanGc;
+
+#[dom_struct]
+pub struct XPathEvaluator {
+ reflector_: Reflector,
+ window: Dom<Window>,
+}
+
+impl XPathEvaluator {
+ fn new_inherited(window: &Window) -> XPathEvaluator {
+ XPathEvaluator {
+ reflector_: Reflector::new(),
+ window: Dom::from_ref(window),
+ }
+ }
+
+ pub fn new(
+ window: &Window,
+ proto: Option<HandleObject>,
+ can_gc: CanGc,
+ ) -> DomRoot<XPathEvaluator> {
+ reflect_dom_object_with_proto(
+ Box::new(XPathEvaluator::new_inherited(window)),
+ window,
+ proto,
+ can_gc,
+ )
+ }
+}
+
+impl XPathEvaluatorMethods<crate::DomTypeHolder> for XPathEvaluator {
+ /// <https://dom.spec.whatwg.org/#dom-xpathevaluator-xpathevaluator>
+ fn Constructor(
+ window: &Window,
+ proto: Option<HandleObject>,
+ can_gc: CanGc,
+ ) -> DomRoot<XPathEvaluator> {
+ XPathEvaluator::new(window, proto, can_gc)
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathevaluatorbase-createexpression>
+ fn CreateExpression(
+ &self,
+ expression: DOMString,
+ _resolver: Option<Rc<XPathNSResolver>>,
+ can_gc: CanGc,
+ ) -> Fallible<DomRoot<XPathExpression>> {
+ let global = self.global();
+ let window = global.as_window();
+ // NB: this function is *not* Fallible according to the spec, so we swallow any parsing errors and
+ // just pass a None as the expression... it's not great.
+ let parsed_expression = crate::xpath::parse(&expression).map_err(|_e| Error::Syntax)?;
+ Ok(XPathExpression::new(
+ window,
+ None,
+ can_gc,
+ parsed_expression,
+ ))
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathevaluatorbase-creatensresolver>
+ fn CreateNSResolver(&self, node_resolver: &Node) -> DomRoot<Node> {
+ // Legacy: the spec tells us to just return `node_resolver` as-is
+ DomRoot::from_ref(node_resolver)
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathevaluatorbase-evaluate>
+ fn Evaluate(
+ &self,
+ expression_str: DOMString,
+ context_node: &Node,
+ _resolver: Option<Rc<XPathNSResolver>>,
+ result_type: u16,
+ result: Option<&XPathResult>,
+ can_gc: CanGc,
+ ) -> Fallible<DomRoot<XPathResult>> {
+ let global = self.global();
+ let window = global.as_window();
+ let parsed_expression = crate::xpath::parse(&expression_str).map_err(|_| Error::Syntax)?;
+ let expression = XPathExpression::new(window, None, can_gc, parsed_expression);
+ XPathExpressionMethods::<crate::DomTypeHolder>::Evaluate(
+ &*expression,
+ context_node,
+ result_type,
+ result,
+ can_gc,
+ )
+ }
+}
diff --git a/components/script/dom/xpathexpression.rs b/components/script/dom/xpathexpression.rs
new file mode 100644
index 00000000000..16e898c2e72
--- /dev/null
+++ b/components/script/dom/xpathexpression.rs
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use dom_struct::dom_struct;
+use js::rust::HandleObject;
+
+use crate::dom::bindings::codegen::Bindings::XPathExpressionBinding::XPathExpressionMethods;
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
+use crate::dom::bindings::root::{Dom, DomRoot};
+use crate::dom::node::Node;
+use crate::dom::window::Window;
+use crate::dom::xpathresult::{XPathResult, XPathResultType};
+use crate::script_runtime::CanGc;
+use crate::xpath::{evaluate_parsed_xpath, Expr};
+
+#[dom_struct]
+pub struct XPathExpression {
+ reflector_: Reflector,
+ window: Dom<Window>,
+ #[no_trace]
+ parsed_expression: Expr,
+}
+
+impl XPathExpression {
+ fn new_inherited(window: &Window, parsed_expression: Expr) -> XPathExpression {
+ XPathExpression {
+ reflector_: Reflector::new(),
+ window: Dom::from_ref(window),
+ parsed_expression,
+ }
+ }
+
+ pub fn new(
+ window: &Window,
+ proto: Option<HandleObject>,
+ can_gc: CanGc,
+ parsed_expression: Expr,
+ ) -> DomRoot<XPathExpression> {
+ reflect_dom_object_with_proto(
+ Box::new(XPathExpression::new_inherited(window, parsed_expression)),
+ window,
+ proto,
+ can_gc,
+ )
+ }
+}
+
+impl XPathExpressionMethods<crate::DomTypeHolder> for XPathExpression {
+ /// <https://dom.spec.whatwg.org/#dom-xpathexpression-evaluate>
+ fn Evaluate(
+ &self,
+ context_node: &Node,
+ result_type_num: u16,
+ _result: Option<&XPathResult>,
+ can_gc: CanGc,
+ ) -> Fallible<DomRoot<XPathResult>> {
+ let result_type = XPathResultType::try_from(result_type_num)
+ .map_err(|()| Error::Type("Invalid XPath result type".to_string()))?;
+
+ let global = self.global();
+ let window = global.as_window();
+
+ let result_value = evaluate_parsed_xpath(&self.parsed_expression, context_node)
+ .map_err(|_e| Error::Operation)?;
+
+ // TODO(vlindhol): support putting results into mutable `_result` as per the spec
+ Ok(XPathResult::new(
+ window,
+ None,
+ can_gc,
+ result_type,
+ result_value.into(),
+ ))
+ }
+}
diff --git a/components/script/dom/xpathresult.rs b/components/script/dom/xpathresult.rs
new file mode 100644
index 00000000000..93905b05ff7
--- /dev/null
+++ b/components/script/dom/xpathresult.rs
@@ -0,0 +1,261 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::cell::Cell;
+
+use dom_struct::dom_struct;
+use js::rust::HandleObject;
+
+use crate::dom::bindings::codegen::Bindings::XPathResultBinding::{
+ XPathResultConstants, XPathResultMethods,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
+use crate::dom::bindings::root::{Dom, DomRoot};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::node::Node;
+use crate::dom::window::Window;
+use crate::script_runtime::CanGc;
+use crate::xpath::{NodesetHelpers, Value};
+
+#[repr(u16)]
+#[derive(Clone, Copy, Debug, Eq, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
+pub enum XPathResultType {
+ Any = XPathResultConstants::ANY_TYPE,
+ Number = XPathResultConstants::NUMBER_TYPE,
+ String = XPathResultConstants::STRING_TYPE,
+ Boolean = XPathResultConstants::BOOLEAN_TYPE,
+ UnorderedNodeIterator = XPathResultConstants::UNORDERED_NODE_ITERATOR_TYPE,
+ OrderedNodeIterator = XPathResultConstants::ORDERED_NODE_ITERATOR_TYPE,
+ UnorderedNodeSnapshot = XPathResultConstants::UNORDERED_NODE_SNAPSHOT_TYPE,
+ OrderedNodeSnapshot = XPathResultConstants::ORDERED_NODE_SNAPSHOT_TYPE,
+ AnyUnorderedNode = XPathResultConstants::ANY_UNORDERED_NODE_TYPE,
+ FirstOrderedNode = XPathResultConstants::FIRST_ORDERED_NODE_TYPE,
+}
+
+impl TryFrom<u16> for XPathResultType {
+ type Error = ();
+
+ fn try_from(value: u16) -> Result<Self, Self::Error> {
+ match value {
+ XPathResultConstants::ANY_TYPE => Ok(Self::Any),
+ XPathResultConstants::NUMBER_TYPE => Ok(Self::Number),
+ XPathResultConstants::STRING_TYPE => Ok(Self::String),
+ XPathResultConstants::BOOLEAN_TYPE => Ok(Self::Boolean),
+ XPathResultConstants::UNORDERED_NODE_ITERATOR_TYPE => Ok(Self::UnorderedNodeIterator),
+ XPathResultConstants::ORDERED_NODE_ITERATOR_TYPE => Ok(Self::OrderedNodeIterator),
+ XPathResultConstants::UNORDERED_NODE_SNAPSHOT_TYPE => Ok(Self::UnorderedNodeSnapshot),
+ XPathResultConstants::ORDERED_NODE_SNAPSHOT_TYPE => Ok(Self::OrderedNodeSnapshot),
+ XPathResultConstants::ANY_UNORDERED_NODE_TYPE => Ok(Self::AnyUnorderedNode),
+ XPathResultConstants::FIRST_ORDERED_NODE_TYPE => Ok(Self::FirstOrderedNode),
+ _ => Err(()),
+ }
+ }
+}
+
+#[derive(JSTraceable, MallocSizeOf)]
+pub enum XPathResultValue {
+ Boolean(bool),
+ /// A IEEE-754 double-precision floating point number
+ Number(f64),
+ String(DOMString),
+ /// A collection of unique nodes
+ Nodeset(Vec<DomRoot<Node>>),
+}
+
+impl From<Value> for XPathResultValue {
+ fn from(value: Value) -> Self {
+ match value {
+ Value::Boolean(b) => XPathResultValue::Boolean(b),
+ Value::Number(n) => XPathResultValue::Number(n),
+ Value::String(s) => XPathResultValue::String(s.into()),
+ Value::Nodeset(nodes) => {
+ // Put the evaluation result into (unique) document order. This also re-roots them
+ // so that we are sure we can hold them for the lifetime of this XPathResult.
+ let rooted_nodes = nodes.document_order_unique();
+ XPathResultValue::Nodeset(rooted_nodes)
+ },
+ }
+ }
+}
+
+#[dom_struct]
+pub struct XPathResult {
+ reflector_: Reflector,
+ window: Dom<Window>,
+ result_type: XPathResultType,
+ value: XPathResultValue,
+ iterator_invalid: Cell<bool>,
+ iterator_pos: Cell<usize>,
+}
+
+impl XPathResult {
+ fn new_inherited(
+ window: &Window,
+ result_type: XPathResultType,
+ value: XPathResultValue,
+ ) -> XPathResult {
+ // TODO(vlindhol): if the wanted result type is AnyUnorderedNode | FirstOrderedNode,
+ // we could drop all nodes except one to save memory.
+ let inferred_result_type = if result_type == XPathResultType::Any {
+ match value {
+ XPathResultValue::Boolean(_) => XPathResultType::Boolean,
+ XPathResultValue::Number(_) => XPathResultType::Number,
+ XPathResultValue::String(_) => XPathResultType::String,
+ XPathResultValue::Nodeset(_) => XPathResultType::UnorderedNodeIterator,
+ }
+ } else {
+ result_type
+ };
+
+ XPathResult {
+ reflector_: Reflector::new(),
+ window: Dom::from_ref(window),
+ result_type: inferred_result_type,
+ iterator_invalid: Cell::new(false),
+ iterator_pos: Cell::new(0),
+ value,
+ }
+ }
+
+ /// NB: Blindly trusts `result_type` and constructs an object regardless of the contents
+ /// of `value`. The exception is `XPathResultType::Any`, for which we look at the value
+ /// to determine the type.
+ pub fn new(
+ window: &Window,
+ proto: Option<HandleObject>,
+ can_gc: CanGc,
+ result_type: XPathResultType,
+ value: XPathResultValue,
+ ) -> DomRoot<XPathResult> {
+ reflect_dom_object_with_proto(
+ Box::new(XPathResult::new_inherited(window, result_type, value)),
+ window,
+ proto,
+ can_gc,
+ )
+ }
+}
+
+impl XPathResultMethods<crate::DomTypeHolder> for XPathResult {
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-resulttype>
+ fn ResultType(&self) -> u16 {
+ self.result_type as u16
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-numbervalue>
+ fn GetNumberValue(&self) -> Fallible<f64> {
+ match (&self.value, self.result_type) {
+ (XPathResultValue::Number(n), XPathResultType::Number) => Ok(*n),
+ _ => Err(Error::Type(
+ "Can't get number value for non-number XPathResult".to_string(),
+ )),
+ }
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-stringvalue>
+ fn GetStringValue(&self) -> Fallible<DOMString> {
+ match (&self.value, self.result_type) {
+ (XPathResultValue::String(s), XPathResultType::String) => Ok(s.clone()),
+ _ => Err(Error::Type(
+ "Can't get string value for non-string XPathResult".to_string(),
+ )),
+ }
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-booleanvalue>
+ fn GetBooleanValue(&self) -> Fallible<bool> {
+ match (&self.value, self.result_type) {
+ (XPathResultValue::Boolean(b), XPathResultType::Boolean) => Ok(*b),
+ _ => Err(Error::Type(
+ "Can't get boolean value for non-boolean XPathResult".to_string(),
+ )),
+ }
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-iteratenext>
+ fn IterateNext(&self) -> Fallible<Option<DomRoot<Node>>> {
+ // TODO(vlindhol): actually set `iterator_invalid` somewhere
+ if self.iterator_invalid.get() {
+ return Err(Error::Range(
+ "Invalidated iterator for XPathResult, the DOM has mutated.".to_string(),
+ ));
+ }
+
+ match (&self.value, self.result_type) {
+ (
+ XPathResultValue::Nodeset(nodes),
+ XPathResultType::OrderedNodeIterator | XPathResultType::UnorderedNodeIterator,
+ ) => {
+ let pos = self.iterator_pos.get();
+ if pos >= nodes.len() {
+ Ok(None)
+ } else {
+ let node = nodes[pos].clone();
+ self.iterator_pos.set(pos + 1);
+ Ok(Some(node))
+ }
+ },
+ _ => Err(Error::Type(
+ "Can't iterate on XPathResult that is not a node-set".to_string(),
+ )),
+ }
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-invaliditeratorstate>
+ fn GetInvalidIteratorState(&self) -> Fallible<bool> {
+ let is_iterator_invalid = self.iterator_invalid.get();
+ if is_iterator_invalid ||
+ matches!(
+ self.result_type,
+ XPathResultType::OrderedNodeIterator | XPathResultType::UnorderedNodeIterator
+ )
+ {
+ Ok(is_iterator_invalid)
+ } else {
+ Err(Error::Type(
+ "Can't iterate on XPathResult that is not a node-set".to_string(),
+ ))
+ }
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-snapshotlength>
+ fn GetSnapshotLength(&self) -> Fallible<u32> {
+ match (&self.value, self.result_type) {
+ (
+ XPathResultValue::Nodeset(nodes),
+ XPathResultType::OrderedNodeSnapshot | XPathResultType::UnorderedNodeSnapshot,
+ ) => Ok(nodes.len() as u32),
+ _ => Err(Error::Type(
+ "Can't get snapshot length of XPathResult that is not a snapshot".to_string(),
+ )),
+ }
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-snapshotitem>
+ fn SnapshotItem(&self, index: u32) -> Fallible<Option<DomRoot<Node>>> {
+ match (&self.value, self.result_type) {
+ (
+ XPathResultValue::Nodeset(nodes),
+ XPathResultType::OrderedNodeSnapshot | XPathResultType::UnorderedNodeSnapshot,
+ ) => Ok(nodes.get(index as usize).cloned()),
+ _ => Err(Error::Type(
+ "Can't get snapshot item of XPathResult that is not a snapshot".to_string(),
+ )),
+ }
+ }
+
+ /// <https://dom.spec.whatwg.org/#dom-xpathresult-singlenodevalue>
+ fn GetSingleNodeValue(&self) -> Fallible<Option<DomRoot<Node>>> {
+ match (&self.value, self.result_type) {
+ (
+ XPathResultValue::Nodeset(nodes),
+ XPathResultType::AnyUnorderedNode | XPathResultType::FirstOrderedNode,
+ ) => Ok(nodes.first().cloned()),
+ _ => Err(Error::Type(
+ "Getting single value requires result type 'any unordered node' or 'first ordered node'".to_string(),
+ )),
+ }
+ }
+}