diff --git a/components/script/xpath/context.rs b/components/script/xpath/context.rs index df78a9a7bec..fb23f1e14e7 100644 --- a/components/script/xpath/context.rs +++ b/components/script/xpath/context.rs @@ -5,10 +5,14 @@ use std::iter::Enumerate; use std::vec::IntoIter; +use script_bindings::str::DOMString; + use super::Node; use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; /// The context during evaluation of an XPath expression. +#[derive(Debug)] pub(crate) struct EvaluationCtx { /// Where we started at pub(crate) starting_node: DomRoot, @@ -20,7 +24,7 @@ pub(crate) struct EvaluationCtx { pub(crate) predicate_nodes: Option>>, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub(crate) struct PredicateCtx { pub(crate) index: usize, pub(crate) size: usize, @@ -68,6 +72,11 @@ impl EvaluationCtx { size, } } + + /// Resolve a namespace prefix using the context node's document + pub(crate) fn resolve_namespace(&self, prefix: Option<&str>) -> Option { + self.context_node.LookupNamespaceURI(prefix.map(DOMString::from)) + } } /// When evaluating predicates, we need to keep track of the current node being evaluated and diff --git a/components/script/xpath/eval.rs b/components/script/xpath/eval.rs index 4fd70ec1c97..32e5cebfc1f 100644 --- a/components/script/xpath/eval.rs +++ b/components/script/xpath/eval.rs @@ -12,6 +12,7 @@ use super::parser::{ QName as ParserQualName, RelationalOp, StepExpr, UnaryOp, }; use super::{EvaluationCtx, Value}; +use crate::dom::attr::Attr; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId}; use crate::dom::bindings::root::DomRoot; @@ -246,21 +247,29 @@ impl Evaluatable for PathExpr { } } -impl TryFrom<&ParserQualName> for QualName { +pub(crate) struct QualNameConverter<'a> { + qname: &'a ParserQualName, + context: &'a EvaluationCtx, +} + +impl<'a> TryFrom> for QualName { type Error = Error; - fn try_from(qname: &ParserQualName) -> Result { - let qname_as_str = qname.to_string(); - if let Ok((ns, prefix, local)) = validate_and_extract(None, &qname_as_str) { + fn try_from(converter: QualNameConverter<'a>) -> Result { + let qname_as_str = converter.qname.to_string(); + let namespace = converter.context.resolve_namespace(converter.qname.prefix.as_deref()); + + if let Ok((ns, prefix, local)) = validate_and_extract(namespace, &qname_as_str) { Ok(QualName { prefix, ns, local }) } else { Err(Error::InvalidQName { - qname: qname.clone(), + qname: converter.qname.clone(), }) } } } +#[derive(Debug)] pub(crate) enum NameTestComparisonMode { /// Namespaces must match exactly XHtml, @@ -310,17 +319,18 @@ pub(crate) fn element_name_test( } } -fn apply_node_test(test: &NodeTest, node: &Node) -> Result { +fn apply_node_test(context: &EvaluationCtx, test: &NodeTest, node: &Node) -> Result { let result = match test { NodeTest::Name(qname) => { // Convert the unvalidated "parser QualName" into the proper QualName structure - let wanted_name: QualName = qname.try_into()?; - if matches!(node.type_id(), NodeTypeId::Element(_)) { + let wanted_name: QualName = QualNameConverter { qname, context }.try_into()?; + match node.type_id() { + NodeTypeId::Element(_) => { let element = node.downcast::().unwrap(); - let comparison_mode = if node.owner_doc().is_xhtml_document() { + let comparison_mode = if node.owner_doc().is_html_document() { + NameTestComparisonMode::Html + } else { NameTestComparisonMode::XHtml - } else { - NameTestComparisonMode::Html }; let element_qualname = QualName::new( element.prefix().as_ref().cloned(), @@ -328,11 +338,22 @@ fn apply_node_test(test: &NodeTest, node: &Node) -> Result { element.local_name().clone(), ); element_name_test(wanted_name, element_qualname, comparison_mode) - } else { - false + }, + NodeTypeId::Attr => { + let attr = node.downcast::().unwrap(); + let attr_qualname = QualName::new( + attr.prefix().cloned(), + attr.namespace().clone(), + attr.local_name().clone(), + ); + // attributes are always compared with strict namespace matching + let comparison_mode = NameTestComparisonMode::XHtml; + element_name_test(wanted_name, attr_qualname, comparison_mode) + }, + _ => false, } }, - NodeTest::Wildcard => true, + NodeTest::Wildcard => matches!(node.type_id(), NodeTypeId::Element(_)), NodeTest::Kind(kind) => match kind { KindTest::PI(target) => { if NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) == @@ -427,7 +448,7 @@ impl Evaluatable for StepExpr { let filtered_nodes: Vec> = nodes .into_iter() .map(|node| { - apply_node_test(&axis_step.node_test, &node) + apply_node_test(context, &axis_step.node_test, &node) .map(|matches| matches.then_some(node)) }) .collect::, _>>()? @@ -505,6 +526,7 @@ impl Evaluatable for PredicateExpr { let v = match eval_result { Ok(Value::Number(v)) => Ok(predicate_ctx.index == v as usize), + Ok(Value::Boolean(v)) => Ok(v), Ok(v) => Ok(v.boolean()), Err(e) => Err(e), }; diff --git a/components/script/xpath/parser.rs b/components/script/xpath/parser.rs index 2516dcc5478..32350c85eeb 100644 --- a/components/script/xpath/parser.rs +++ b/components/script/xpath/parser.rs @@ -510,7 +510,7 @@ fn union_expr(input: &str) -> IResult<&str, Expr> { fn path_expr(input: &str) -> IResult<&str, Expr> { alt(( // "//" RelativePathExpr - map(pair(tag("//"), relative_path_expr), |(_, rel_path)| { + map(pair(tag("//"), move |i| relative_path_expr(true, i)), |(_, rel_path)| { Expr::Path(PathExpr { is_absolute: true, is_descendant: true, @@ -521,7 +521,7 @@ fn path_expr(input: &str) -> IResult<&str, Expr> { }) }), // "/" RelativePathExpr? - map(pair(char('/'), opt(relative_path_expr)), |(_, rel_path)| { + map(pair(char('/'), opt(move |i| relative_path_expr(false, i))), |(_, rel_path)| { Expr::Path(PathExpr { is_absolute: true, is_descendant: false, @@ -534,16 +534,15 @@ fn path_expr(input: &str) -> IResult<&str, Expr> { }) }), // RelativePathExpr - relative_path_expr, + move |i| relative_path_expr(false, i), ))(input) } -fn relative_path_expr(input: &str) -> IResult<&str, Expr> { - let (input, first) = step_expr(input)?; +fn relative_path_expr(is_descendant: bool, input: &str) -> IResult<&str, Expr> { + let (input, first) = step_expr(is_descendant, input)?; let (input, steps) = many0(pair( - // ("/" | "//") ws(alt((value(true, tag("//")), value(false, char('/'))))), - step_expr, + move |i| step_expr(is_descendant, i), ))(input)?; let mut all_steps = vec![first]; @@ -569,16 +568,16 @@ fn relative_path_expr(input: &str) -> IResult<&str, Expr> { )) } -fn step_expr(input: &str) -> IResult<&str, StepExpr> { +fn step_expr(is_descendant: bool, input: &str) -> IResult<&str, StepExpr> { alt(( map(filter_expr, StepExpr::Filter), - map(axis_step, StepExpr::Axis), + map(|i| axis_step(is_descendant, i), StepExpr::Axis), ))(input) } -fn axis_step(input: &str) -> IResult<&str, AxisStep> { +fn axis_step(is_descendant: bool, input: &str) -> IResult<&str, AxisStep> { let (input, (step, predicates)) = - pair(alt((forward_step, reverse_step)), predicate_list)(input)?; + pair(alt((move |i| forward_step(is_descendant, i), reverse_step)), predicate_list)(input)?; let (axis, node_test) = step; Ok(( @@ -591,8 +590,11 @@ fn axis_step(input: &str) -> IResult<&str, AxisStep> { )) } -fn forward_step(input: &str) -> IResult<&str, (Axis, NodeTest)> { - alt((pair(forward_axis, node_test), abbrev_forward_step))(input) +fn forward_step(is_descendant: bool, input: &str) -> IResult<&str, (Axis, NodeTest)> { + alt(( + pair(forward_axis, node_test), + move |i| abbrev_forward_step(is_descendant, i), + ))(input) } fn forward_axis(input: &str) -> IResult<&str, Axis> { @@ -610,7 +612,7 @@ fn forward_axis(input: &str) -> IResult<&str, Axis> { Ok((input, axis)) } -fn abbrev_forward_step(input: &str) -> IResult<&str, (Axis, NodeTest)> { +fn abbrev_forward_step(is_descendant: bool, input: &str) -> IResult<&str, (Axis, NodeTest)> { let (input, attr) = opt(char('@'))(input)?; let (input, test) = node_test(input)?; @@ -619,6 +621,8 @@ fn abbrev_forward_step(input: &str) -> IResult<&str, (Axis, NodeTest)> { ( if attr.is_some() { Axis::Attribute + } else if is_descendant { + Axis::DescendantOrSelf } else { Axis::Child },