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>
This commit is contained in:
Ville Lindholm 2024-12-08 04:01:50 +02:00 committed by GitHub
parent 264c0f972f
commit bc7fe41a02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 6426 additions and 314 deletions

View file

@ -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'],
},

View file

@ -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>>) {

View file

@ -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::*;

View file

@ -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);

View file

@ -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;

View file

@ -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
);
};

View file

@ -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);
};

View file

@ -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);
};

View file

@ -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,
)
}
}

View file

@ -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(),
))
}
}

View file

@ -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(),
)),
}
}
}