aboutsummaryrefslogtreecommitdiffstats
path: root/components/selectors/parser.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/selectors/parser.rs')
-rw-r--r--components/selectors/parser.rs363
1 files changed, 219 insertions, 144 deletions
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs
index 0f7cb9c6055..0a3dd43ac3c 100644
--- a/components/selectors/parser.rs
+++ b/components/selectors/parser.rs
@@ -9,7 +9,7 @@ use crate::bloom::BLOOM_HASH_MASK;
use crate::builder::{SelectorBuilder, SelectorFlags, SpecificityAndFlags};
use crate::context::QuirksMode;
use crate::sink::Push;
-pub use crate::visitor::{SelectorVisitor, Visit};
+pub use crate::visitor::SelectorVisitor;
use cssparser::{parse_nth, serialize_identifier};
use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind};
use cssparser::{CowRcStr, Delimiter, SourceLocation};
@@ -55,6 +55,13 @@ pub trait NonTSPseudoClass: Sized + ToCss {
/// Whether this pseudo-class has zero specificity.
fn has_zero_specificity(&self) -> bool;
+
+ fn visit<V>(&self, _visitor: &mut V) -> bool
+ where
+ V: SelectorVisitor<Impl = Self::Impl>,
+ {
+ true
+ }
}
/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a
@@ -147,6 +154,7 @@ pub enum SelectorParseErrorKind<'i> {
NonCompoundSelector,
NonPseudoElementAfterSlotted,
InvalidPseudoElementAfterSlotted,
+ InvalidPseudoElementInsideWhere,
InvalidState,
UnexpectedTokenInAttributeSelector(Token<'i>),
PseudoElementExpectedColon(Token<'i>),
@@ -226,6 +234,11 @@ pub trait Parser<'i> {
false
}
+ /// Whether to parse the `:where` pseudo-class.
+ fn parse_is_and_where(&self) -> bool {
+ false
+ }
+
/// Whether to parse the `:host` pseudo-class.
fn parse_host(&self) -> bool {
false
@@ -441,128 +454,6 @@ impl AncestorHashes {
}
}
-impl<Impl: SelectorImpl> Visit for Selector<Impl>
-where
- Impl::NonTSPseudoClass: Visit<Impl = Impl>,
-{
- type Impl = Impl;
-
- fn visit<V>(&self, visitor: &mut V) -> bool
- where
- V: SelectorVisitor<Impl = Impl>,
- {
- let mut current = self.iter();
- let mut combinator = None;
- loop {
- if !visitor.visit_complex_selector(combinator) {
- return false;
- }
-
- for selector in &mut current {
- if !selector.visit(visitor) {
- return false;
- }
- }
-
- combinator = current.next_sequence();
- if combinator.is_none() {
- break;
- }
- }
-
- true
- }
-}
-
-impl<Impl: SelectorImpl> Visit for Component<Impl>
-where
- Impl::NonTSPseudoClass: Visit<Impl = Impl>,
-{
- type Impl = Impl;
-
- fn visit<V>(&self, visitor: &mut V) -> bool
- where
- V: SelectorVisitor<Impl = Impl>,
- {
- use self::Component::*;
- if !visitor.visit_simple_selector(self) {
- return false;
- }
-
- match *self {
- Slotted(ref selector) => {
- if !selector.visit(visitor) {
- return false;
- }
- },
- Host(Some(ref selector)) => {
- if !selector.visit(visitor) {
- return false;
- }
- },
- Negation(ref negated) => {
- for component in negated.iter() {
- if !component.visit(visitor) {
- return false;
- }
- }
- },
-
- AttributeInNoNamespaceExists {
- ref local_name,
- ref local_name_lower,
- } => {
- if !visitor.visit_attribute_selector(
- &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
- local_name,
- local_name_lower,
- ) {
- return false;
- }
- },
- AttributeInNoNamespace {
- ref local_name,
- never_matches,
- ..
- } if !never_matches => {
- if !visitor.visit_attribute_selector(
- &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
- local_name,
- local_name,
- ) {
- return false;
- }
- },
- AttributeOther(ref attr_selector) if !attr_selector.never_matches => {
- let empty_string;
- let namespace = match attr_selector.namespace() {
- Some(ns) => ns,
- None => {
- empty_string = crate::parser::namespace_empty_string::<Impl>();
- NamespaceConstraint::Specific(&empty_string)
- },
- };
- if !visitor.visit_attribute_selector(
- &namespace,
- &attr_selector.local_name,
- &attr_selector.local_name_lower,
- ) {
- return false;
- }
- },
-
- NonTSPseudoClass(ref pseudo_class) => {
- if !pseudo_class.visit(visitor) {
- return false;
- }
- },
- _ => {},
- }
-
- true
- }
-}
-
pub fn namespace_empty_string<Impl: SelectorImpl>() -> Impl::NamespaceUrl {
// Rust type’s default, not default namespace
Impl::NamespaceUrl::default()
@@ -781,6 +672,50 @@ impl<Impl: SelectorImpl> Selector<Impl> {
pub fn thin_arc_heap_ptr(&self) -> *const ::std::os::raw::c_void {
self.0.heap_ptr()
}
+
+ /// Traverse selector components inside `self`.
+ ///
+ /// Implementations of this method should call `SelectorVisitor` methods
+ /// or other impls of `Visit` as appropriate based on the fields of `Self`.
+ ///
+ /// A return value of `false` indicates terminating the traversal.
+ /// It should be propagated with an early return.
+ /// On the contrary, `true` indicates that all fields of `self` have been traversed:
+ ///
+ /// ```rust,ignore
+ /// if !visitor.visit_simple_selector(&self.some_simple_selector) {
+ /// return false;
+ /// }
+ /// if !self.some_component.visit(visitor) {
+ /// return false;
+ /// }
+ /// true
+ /// ```
+ pub fn visit<V>(&self, visitor: &mut V) -> bool
+ where
+ V: SelectorVisitor<Impl = Impl>,
+ {
+ let mut current = self.iter();
+ let mut combinator = None;
+ loop {
+ if !visitor.visit_complex_selector(combinator) {
+ return false;
+ }
+
+ for selector in &mut current {
+ if !selector.visit(visitor) {
+ return false;
+ }
+ }
+
+ combinator = current.next_sequence();
+ if combinator.is_none() {
+ break;
+ }
+ }
+
+ true
+ }
}
#[derive(Clone)]
@@ -1027,6 +962,20 @@ pub enum Component<Impl: SelectorImpl> {
///
/// See https://github.com/w3c/csswg-drafts/issues/2158
Host(Option<Selector<Impl>>),
+ /// The `:where` pseudo-class.
+ ///
+ /// https://drafts.csswg.org/selectors/#zero-matches
+ ///
+ /// The inner argument is conceptually a SelectorList, but we move the
+ /// selectors to the heap to keep Component small.
+ Where(Box<[Selector<Impl>]>),
+ /// The `:is` pseudo-class.
+ ///
+ /// https://drafts.csswg.org/selectors/#matches-pseudo
+ ///
+ /// Same comment as above re. the argument.
+ Is(Box<[Selector<Impl>]>),
+ /// An implementation-dependent pseudo-element selector.
PseudoElement(#[shmem(field_bound)] Impl::PseudoElement),
}
@@ -1080,6 +1029,94 @@ impl<Impl: SelectorImpl> Component<Impl> {
_ => None,
}
}
+
+ pub fn visit<V>(&self, visitor: &mut V) -> bool
+ where
+ V: SelectorVisitor<Impl = Impl>,
+ {
+ use self::Component::*;
+ if !visitor.visit_simple_selector(self) {
+ return false;
+ }
+
+ match *self {
+ Slotted(ref selector) => {
+ if !selector.visit(visitor) {
+ return false;
+ }
+ },
+ Host(Some(ref selector)) => {
+ if !selector.visit(visitor) {
+ return false;
+ }
+ },
+ Negation(ref negated) => {
+ for component in negated.iter() {
+ if !component.visit(visitor) {
+ return false;
+ }
+ }
+ },
+
+ AttributeInNoNamespaceExists {
+ ref local_name,
+ ref local_name_lower,
+ } => {
+ if !visitor.visit_attribute_selector(
+ &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
+ local_name,
+ local_name_lower,
+ ) {
+ return false;
+ }
+ },
+ AttributeInNoNamespace {
+ ref local_name,
+ never_matches,
+ ..
+ } if !never_matches => {
+ if !visitor.visit_attribute_selector(
+ &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
+ local_name,
+ local_name,
+ ) {
+ return false;
+ }
+ },
+ AttributeOther(ref attr_selector) if !attr_selector.never_matches => {
+ let empty_string;
+ let namespace = match attr_selector.namespace() {
+ Some(ns) => ns,
+ None => {
+ empty_string = crate::parser::namespace_empty_string::<Impl>();
+ NamespaceConstraint::Specific(&empty_string)
+ },
+ };
+ if !visitor.visit_attribute_selector(
+ &namespace,
+ &attr_selector.local_name,
+ &attr_selector.local_name_lower,
+ ) {
+ return false;
+ }
+ },
+
+ NonTSPseudoClass(ref pseudo_class) => {
+ if !pseudo_class.visit(visitor) {
+ return false;
+ }
+ },
+
+ Is(ref list) | Where(ref list) => {
+ if !visitor.visit_selector_list(&list) {
+ return false;
+ }
+ },
+ _ => {},
+ }
+
+ true
+ }
}
#[derive(Clone, Eq, PartialEq, ToShmem)]
@@ -1114,21 +1151,29 @@ impl<Impl: SelectorImpl> Debug for LocalName<Impl> {
}
}
+fn serialize_selector_list<'a, Impl, I, W>(mut iter: I, dest: &mut W) -> fmt::Result
+where
+ Impl: SelectorImpl,
+ I: Iterator<Item = &'a Selector<Impl>>,
+ W: fmt::Write,
+{
+ let first = iter
+ .next()
+ .expect("Empty SelectorList, should contain at least one selector");
+ first.to_css(dest)?;
+ for selector in iter {
+ dest.write_str(", ")?;
+ selector.to_css(dest)?;
+ }
+ Ok(())
+}
+
impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
- let mut iter = self.0.iter();
- let first = iter
- .next()
- .expect("Empty SelectorList, should contain at least one selector");
- first.to_css(dest)?;
- for selector in iter {
- dest.write_str(", ")?;
- selector.to_css(dest)?;
- }
- Ok(())
+ serialize_selector_list(self.0.iter(), dest)
}
}
@@ -1401,6 +1446,15 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
write_affine(dest, a, b)?;
dest.write_char(')')
},
+ Is(ref list) | Where(ref list) => {
+ match *self {
+ Where(..) => dest.write_str(":where(")?,
+ Is(..) => dest.write_str(":is(")?,
+ _ => unreachable!(),
+ }
+ serialize_selector_list(list.iter(), dest)?;
+ dest.write_str(")")
+ },
NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
}
}
@@ -2086,6 +2140,28 @@ where
}
}
+fn parse_is_or_where<'i, 't, P, Impl>(
+ parser: &P,
+ input: &mut CssParser<'i, 't>,
+ component: impl FnOnce(Box<[Selector<Impl>]>) -> Component<Impl>,
+) -> Result<Component<Impl>, ParseError<'i, P::Error>>
+where
+ P: Parser<'i, Impl = Impl>,
+ Impl: SelectorImpl,
+{
+ debug_assert!(parser.parse_is_and_where());
+ let inner = SelectorList::parse(parser, input)?;
+ // https://drafts.csswg.org/selectors/#matches-pseudo:
+ //
+ // Pseudo-elements cannot be represented by the matches-any
+ // pseudo-class; they are not valid within :is().
+ //
+ if inner.0.iter().any(|i| i.has_pseudo_element()) {
+ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidPseudoElementInsideWhere));
+ }
+ Ok(component(inner.0.into_vec().into_boxed_slice()))
+}
+
fn parse_functional_pseudo_class<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
@@ -2105,6 +2181,8 @@ where
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
"nth-last-child" => return Ok(parse_nth_pseudo_class(input, Component::NthLastChild)?),
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
+ "is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, Component::Is),
+ "where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, Component::Where),
"host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
"not" => {
if state.intersects(SelectorParsingState::INSIDE_NEGATION) {
@@ -2396,17 +2474,6 @@ pub mod tests {
}
}
- impl Visit for PseudoClass {
- type Impl = DummySelectorImpl;
-
- fn visit<V>(&self, _visitor: &mut V) -> bool
- where
- V: SelectorVisitor<Impl = Self::Impl>,
- {
- true
- }
- }
-
#[derive(Clone, Debug, PartialEq)]
pub struct DummySelectorImpl;
@@ -2469,6 +2536,10 @@ pub mod tests {
true
}
+ fn parse_is_and_where(&self) -> bool {
+ true
+ }
+
fn parse_part(&self) -> bool {
true
}
@@ -3100,6 +3171,10 @@ pub mod tests {
assert!(parse("slot::slotted(div,foo)::first-line").is_err());
assert!(parse("::slotted(div)::before").is_ok());
assert!(parse("slot::slotted(div,foo)").is_err());
+
+ assert!(parse("foo:where()").is_err());
+ assert!(parse("foo:where(div, foo, .bar baz)").is_ok());
+ assert!(parse("foo:where(::before)").is_err());
}
#[test]