/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
usecrate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace}; usecrate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation, ParsedCaseSensitivity}; usecrate::bloom::BLOOM_HASH_MASK; usecrate::builder::{
relative_selector_list_specificity_and_flags, selector_list_specificity_and_flags,
SelectorBuilder, SelectorFlags, Specificity, SpecificityAndFlags,
}; usecrate::context::QuirksMode; usecrate::sink::Push; usecrate::visitor::SelectorListKind; pubusecrate::visitor::SelectorVisitor; use bitflags::bitflags; use cssparser::match_ignore_ascii_case; use cssparser::parse_nth; use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind}; use cssparser::{CowRcStr, Delimiter, SourceLocation}; use cssparser::{Parser as CssParser, ToCss, Token}; use debug_unreachable::debug_unreachable; use precomputed_hash::PrecomputedHash; use servo_arc::{Arc, ArcUnionBorrow, ThinArc, ThinArcUnion, UniqueArc}; use smallvec::SmallVec; use std::borrow::{Borrow, Cow}; use std::fmt::{self, Debug}; use std::iter::Rev; use std::slice;
#[cfg(feature = "to_shmem")] use to_shmem_derive::ToShmem;
/// A trait that represents a pseudo-element. pubtrait PseudoElement: Sized + ToCss { /// The `SelectorImpl` this pseudo-element is used for. typeImpl: SelectorImpl;
/// Whether the pseudo-element supports a given state selector to the right /// of it. fn accepts_state_pseudo_classes(&self) -> bool { false
}
/// Whether this pseudo-element is valid after a ::slotted(..) pseudo. fn valid_after_slotted(&self) -> bool { false
}
/// Whether this pseudo-element is valid when directly after a ::before/::after pseudo. fn valid_after_before_or_after(&self) -> bool { false
}
/// Whether this pseudo-element is ::before or ::after pseudo element, /// which are treated specially when deciding what can come after them. /// https://drafts.csswg.org/css-pseudo-4/#generated-content fn is_before_or_after(&self) -> bool { false
}
/// The count we contribute to the specificity from this pseudo-element. fn specificity_count(&self) -> u32 { 1
}
/// A trait that represents a pseudo-class. pubtrait NonTSPseudoClass: Sized + ToCss { /// The `SelectorImpl` this pseudo-element is used for. typeImpl: SelectorImpl;
/// Whether this pseudo-class is :active or :hover. fn is_active_or_hover(&self) -> bool;
/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a /// Cow::Owned if `s` had to be converted into ASCII lowercase. fn to_ascii_lowercase(s: &str) -> Cow<'_, str> { iflet Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { letmut string = s.to_owned();
string[first_uppercase..].make_ascii_lowercase();
string.into()
} else {
s.into()
}
}
bitflags! { /// Flags that indicate at which point of parsing a selector are we. #[derive(Copy, Clone)] struct SelectorParsingState: u16 { /// Whether we should avoid adding default namespaces to selectors that /// aren't type or universal selectors. const SKIP_DEFAULT_NAMESPACE = 1 << 0;
/// Whether we've parsed a ::slotted() pseudo-element already. /// /// If so, then we can only parse a subset of pseudo-elements, and /// whatever comes after them if so. const AFTER_SLOTTED = 1 << 1; /// Whether we've parsed a ::part() or element-backed pseudo-element already. /// /// If so, then we can only parse a subset of pseudo-elements, and /// whatever comes after them if so. const AFTER_PART_LIKE = 1 << 2; /// Whether we've parsed a non-element-backed pseudo-element (as in, an /// `Impl::PseudoElement` thus not accounting for `::slotted` or /// `::part`) already. /// /// If so, then other pseudo-elements and most other selectors are /// disallowed. const AFTER_NON_ELEMENT_BACKED_PSEUDO = 1 << 3; /// Whether we've parsed a non-stateful pseudo-element (again, as-in /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are /// disallowed. If this flag is set, `AFTER_NON_ELEMENT_BACKED_PSEUDO` must be set /// as well. const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4; // Whether we've parsed a generated pseudo-element (as in ::before, ::after). // If so then some other pseudo elements are disallowed (e.g. another generated pseudo) // while others allowed (e.g. ::marker). const AFTER_BEFORE_OR_AFTER_PSEUDO = 1 << 5;
/// Whether we are after any of the pseudo-like things. const AFTER_PSEUDO = Self::AFTER_PART_LIKE.bits() | Self::AFTER_SLOTTED.bits() | Self::AFTER_NON_ELEMENT_BACKED_PSEUDO.bits() | Self::AFTER_BEFORE_OR_AFTER_PSEUDO.bits();
/// Whether we've parsed a pseudo-element which is in a pseudo-element tree (i.e. it is a /// descendant pseudo of a pseudo-element root). const IN_PSEUDO_ELEMENT_TREE = 1 << 9;
}
}
macro_rules! with_all_bounds {
(
[ $( $InSelector: tt )* ]
[ $( $CommonBounds: tt )* ]
[ $( $FromStr: tt )* ]
) => { /// This trait allows to define the parser implementation in regards /// of pseudo-classes/elements /// /// NB: We need Clone so that we can derive(Clone) on struct with that /// are parameterized on SelectorImpl. See /// <https://github.com/rust-lang/rust/issues/26925> pubtrait SelectorImpl: Clone + Debug + Sized + 'static { type ExtraMatchingData<'a>: Sized + Default; type AttrValue: $($InSelector)*; type Identifier: $($InSelector)* + PrecomputedHash; type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName> + PrecomputedHash; type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl> + PrecomputedHash; type NamespacePrefix: $($InSelector)* + Default; type BorrowedNamespaceUrl: ?Sized + Eq; type BorrowedLocalName: ?Sized + Eq;
/// This function can return an "Err" pseudo-element in order to support CSS2.1 /// pseudo-elements. fn parse_non_ts_pseudo_class(
&self,
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<<Self::Implas SelectorImpl>::NonTSPseudoClass, ParseError<'i, Self::Error>> {
Err(
location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
/// A selector list is a tagged pointer with either a single selector, or a ThinArc<()> of multiple /// selectors. #[derive(Clone, Eq, Debug, PartialEq)] #[cfg_attr(feature = "to_shmem", derive(ToShmem))] #[cfg_attr(feature = "to_shmem", shmem(no_bounds))] pubstruct SelectorList<Impl: SelectorImpl>( #[cfg_attr(feature = "to_shmem", shmem(field_bound))]
ThinArcUnion<SpecificityAndFlags, Component<Impl>, (), Selector<Impl>>,
);
pubfn from_one(selector: Selector<Impl>) -> Self { #[cfg(debug_assertions)] let selector_repr = unsafe { *(&selector as *const _ as *const usize) }; let list = Self(ThinArcUnion::from_first(selector.into_data())); #[cfg(debug_assertions)]
debug_assert_eq!(
selector_repr, unsafe { *(&list as *const _ as *const usize) }, "We rely on the same bit representation for the single selector variant"
);
list
}
/// Returns the address on the heap of the ThinArc for memory reporting. pubfn thin_arc_heap_ptr(&self) -> *const ::std::os::raw::c_void { matchself.0.borrow() {
ArcUnionBorrow::First(s) => s.with_arc(|a| a.heap_ptr()),
ArcUnionBorrow::Second(s) => s.with_arc(|a| a.heap_ptr()),
}
}
}
/// Uniquely identify a selector based on its components, which is behind ThinArc and /// is therefore stable. #[derive(Clone, Copy, Hash, Eq, PartialEq)] pubstruct SelectorKey(usize);
impl SelectorKey { /// Create a new key based on the given selector. pubfn new<Impl: SelectorImpl>(selector: &Selector<Impl>) -> Self { Self(selector.0.slice().as_ptr() as usize)
}
}
/// Whether or not we're using forgiving parsing mode #[derive(PartialEq)] enum ForgivingParsing { /// Discard the entire selector list upon encountering any invalid selector. /// This is the default behavior for almost all of CSS.
No, /// Ignore invalid selectors, potentially creating an empty selector list. /// /// This is the error recovery mode of :is() and :where()
Yes,
}
/// Flag indicating if we're parsing relative selectors. #[derive(Copy, Clone, PartialEq)] pubenum ParseRelative { /// Expect selectors to start with a combinator, assuming descendant combinator if not present.
ForHas, /// Allow selectors to start with a combinator, prepending a parent selector if so. Do nothing /// otherwise
ForNesting, /// Allow selectors to start with a combinator, prepending a scope selector if so. Do nothing /// otherwise
ForScope, /// Treat as parse error if any selector begins with a combinator.
No,
}
impl<Impl: SelectorImpl> SelectorList<Impl> { /// Returns a selector list with a single `:scope` selector (with specificity) pubfn scope() -> Self { Self::from_one(Selector::scope())
} /// Returns a selector list with a single implicit `:scope` selector (no specificity) pubfn implicit_scope() -> Self { Self::from_one(Selector::implicit_scope())
}
/// Parse a comma-separated list of Selectors. /// <https://drafts.csswg.org/selectors/#grouping> /// /// Return the Selectors or Err if there is an invalid selector. pubfn parse<'i, 't, P>(
parser: &P,
input: &mut CssParser<'i, 't>,
parse_relative: ParseRelative,
) -> Result<Self, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>,
{ Self::parse_with_state(
parser,
input,
SelectorParsingState::empty(),
ForgivingParsing::No,
parse_relative,
)
}
/// Same as `parse`, but disallow parsing of pseudo-elements. pubfn parse_disallow_pseudo<'i, 't, P>(
parser: &P,
input: &mut CssParser<'i, 't>,
parse_relative: ParseRelative,
) -> Result<Self, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>,
{ Self::parse_with_state(
parser,
input,
SelectorParsingState::DISALLOW_PSEUDOS,
ForgivingParsing::No,
parse_relative,
)
}
/// Replaces the parent selector in all the items of the selector list. pubfn replace_parent_selector(&self, parent: &SelectorList<Impl>) -> Self { Self::from_iter( self.slice()
.iter()
.map(|selector| selector.replace_parent_selector(parent)),
)
}
/// Creates a SelectorList from a Vec of selectors. Used in tests. #[allow(dead_code)] pub(crate) fn from_vec(v: Vec<Selector<Impl>>) -> Self {
SelectorList::from_iter(v.into_iter())
}
}
/// Parses one compound selector suitable for nested stuff like :-moz-any, etc. fn parse_inner_compound_selector<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Selector<Impl>, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>, Impl: SelectorImpl,
{
parse_selector(
parser,
input,
state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS,
ParseRelative::No,
)
}
/// Ancestor hashes for the bloom filter. We precompute these and store them /// inline with selectors to optimize cache performance during matching. /// This matters a lot. /// /// We use 4 hashes, which is copied from Gecko, who copied it from WebKit. /// Note that increasing the number of hashes here will adversely affect the /// cache hit when fast-rejecting long lists of Rules with inline hashes. /// /// Because the bloom filter only uses the bottom 24 bits of the hash, we pack /// the fourth hash into the upper bits of the first three hashes in order to /// shrink Rule (whose size matters a lot). This scheme minimizes the runtime /// overhead of the packing for the first three hashes (we just need to mask /// off the upper bits) at the expense of making the fourth somewhat more /// complicated to assemble, because we often bail out before checking all the /// hashes. #[derive(Clone, Debug, Eq, PartialEq)] pubstruct AncestorHashes { pub packed_hashes: [u32; 3],
}
pub(crate) fn collect_selector_hashes<'a, Impl: SelectorImpl, Iter>(
iter: Iter,
quirks_mode: QuirksMode,
hashes: &mut [u32; 4],
len: &mut usize,
create_inner_iterator: fn(&'a Selector<Impl>) -> Iter,
) -> bool where
Iter: Iterator<Item = &'a Component<Impl>>,
{ for component in iter { let hash = match *component {
Component::LocalName(LocalName { ref name, ref lower_name,
}) => { // Only insert the local-name into the filter if it's all // lowercase. Otherwise we would need to test both hashes, and // our data structures aren't really set up for that. if name != lower_name { continue;
}
name.precomputed_hash()
},
Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => {
url.precomputed_hash()
}, // In quirks mode, class and id selectors should match // case-insensitively, so just avoid inserting them into the filter.
Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(),
Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => {
class.precomputed_hash()
},
Component::AttributeInNoNamespace { ref local_name, .. } ifImpl::should_collect_attr_hash(local_name) =>
{ // AttributeInNoNamespace is only used when local_name == // local_name_lower.
local_name.precomputed_hash()
},
Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower,
..
} => { // Only insert the local-name into the filter if it's all // lowercase. Otherwise we would need to test both hashes, and // our data structures aren't really set up for that. if local_name != local_name_lower || !Impl::should_collect_attr_hash(local_name) { continue;
}
local_name.precomputed_hash()
},
Component::AttributeOther(ref selector) => { if selector.local_name != selector.local_name_lower
|| !Impl::should_collect_attr_hash(&selector.local_name)
{ continue;
}
selector.local_name.precomputed_hash()
},
Component::Is(ref list) | Component::Where(ref list) => { // :where and :is OR their selectors, so we can't put any hash // in the filter if there's more than one selector, as that'd // exclude elements that may match one of the other selectors. let slice = list.slice(); if slice.len() == 1
&& !collect_selector_hashes(
create_inner_iterator(&slice[0]),
quirks_mode,
hashes,
len,
create_inner_iterator,
)
{ returnfalse;
} continue;
},
_ => continue,
};
fn collect_ancestor_hashes<Impl: SelectorImpl>( mut iter: SelectorIter<Impl>,
quirks_mode: QuirksMode,
hashes: &mut [u32; 4],
len: &mut usize,
) -> bool { loop { whilelet Some(item) = iter.next() { iflet Component::Is(ref list) | Component::Where(ref list) = item { let slice = list.slice(); if slice.len() == 1
&& !collect_ancestor_hashes(slice[0].iter(), quirks_mode, hashes, len)
{ returnfalse;
}
}
} let Some(c) = iter.next_sequence() else { returntrue;
}; match c { // We got to an ancestor combinator, let collect_selector_hashes take it from there.
Combinator::Child | Combinator::Descendant => break,
Combinator::LaterSibling | Combinator::NextSibling => {
iter.skip_until_ancestor(); break;
}, // Keep scanning the subject for other potential ancestor combinators inside :where() // and :is(). Note that if this is ever changed to stop at the "pseudo-element" // combinator and treat it as a regular ancestor combinator, we will need to fix the way // we compute hashes for revalidation selectors.
Combinator::Part | Combinator::SlotAssignment | Combinator::PseudoElement => {},
}
}
// Now, pack the fourth hash (if it exists) into the upper byte of each of // the other three hashes. if len == 4 { let fourth = hashes[3];
hashes[0] |= (fourth & 0x000000ff) << 24;
hashes[1] |= (fourth & 0x0000ff00) << 16;
hashes[2] |= (fourth & 0x00ff0000) << 8;
}
pub(super) type SelectorData<Impl> = ThinArc<SpecificityAndFlags, Component<Impl>>;
/// Whether a selector may match a featureless host element, and whether it may match other /// elements. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pubenum MatchesFeaturelessHost { /// The selector may match a featureless host, but also a non-featureless element.
Yes, /// The selector is guaranteed to never match a non-featureless host element.
Only, /// The selector never matches a featureless host.
Never,
}
impl MatchesFeaturelessHost { /// Whether we may match. #[inline] pubfn may_match(self) -> bool { return !matches!(self, Self::Never);
}
}
/// A Selector stores a sequence of simple selectors and combinators. The /// iterator classes allow callers to iterate at either the raw sequence level or /// at the level of sequences of simple selectors separated by combinators. Most /// callers want the higher-level iterator. /// /// We store compound selectors internally right-to-left (in matching order). /// Additionally, we invert the order of top-level compound selectors so that /// each one matches left-to-right. This is because matching namespace, local name, /// id, and class are all relatively cheap, whereas matching pseudo-classes might /// be expensive (depending on the pseudo-class). Since authors tend to put the /// pseudo-classes on the right, it's faster to start matching on the left. /// /// This reordering doesn't change the semantics of selector matching, and we /// handle it in to_css to make it invisible to serialization. #[derive(Clone, Eq, PartialEq)] #[cfg_attr(feature = "to_shmem", derive(ToShmem))] #[cfg_attr(feature = "to_shmem", shmem(no_bounds))] #[repr(transparent)] pubstruct Selector<Impl: SelectorImpl>( #[cfg_attr(feature = "to_shmem", shmem(field_bound))] SelectorData<Impl>,
);
/// Whether this selector (pseudo-element part excluded) matches every element. /// /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs #[inline] pubfn is_universal(&self) -> bool { self.iter_raw_match_order().all(|c| {
matches!(
*c,
Component::ExplicitUniversalType
| Component::ExplicitAnyNamespace
| Component::Combinator(Combinator::PseudoElement)
| Component::PseudoElement(..)
)
})
}
/// Whether this selector may match a featureless shadow host, with no combinators to the /// left, and optionally has a pseudo-element to the right. #[inline] pubfn matches_featureless_host(
&self,
scope_matches_featureless_host: bool,
) -> MatchesFeaturelessHost { let flags = self.flags(); if !flags.intersects(SelectorFlags::HAS_HOST | SelectorFlags::HAS_SCOPE) { return MatchesFeaturelessHost::Never;
}
letmut iter = self.iter(); if flags.intersects(SelectorFlags::HAS_PSEUDO) { for _ in &mut iter { // Skip over pseudo-elements
} match iter.next_sequence() {
Some(c) if c.is_pseudo_element() => {},
_ => {
debug_assert!(false, "Pseudo selector without pseudo combinator?"); return MatchesFeaturelessHost::Never;
},
}
}
let compound_matches = crate::matching::compound_matches_featureless_host(
&mut iter,
scope_matches_featureless_host,
); if iter.next_sequence().is_some() { return MatchesFeaturelessHost::Never;
} return compound_matches;
}
/// Returns an iterator over this selector in matching order (right-to-left). /// When a combinator is reached, the iterator will return None, and /// next_sequence() may be called to continue to the next sequence. #[inline] pubfn iter(&self) -> SelectorIter<'_, Impl> {
SelectorIter {
iter: self.iter_raw_match_order(),
next_combinator: None,
}
}
/// Same as `iter()`, but skips `RelativeSelectorAnchor` and its associated combinator. #[inline] pubfn iter_skip_relative_selector_anchor(&self) -> SelectorIter<'_, Impl> { if cfg!(debug_assertions) { letmut selector_iter = self.iter_raw_parse_order_from(0);
assert!(
matches!(
selector_iter.next().unwrap(),
Component::RelativeSelectorAnchor
), "Relative selector does not start with RelativeSelectorAnchor"
);
assert!(
selector_iter.next().unwrap().is_combinator(), "Relative combinator does not exist"
);
}
/// Returns an iterator over this selector in matching order (right-to-left), /// skipping the rightmost |offset| Components. #[inline] pubfn iter_from(&self, offset: usize) -> SelectorIter<'_, Impl> { let iter = self.0.slice()[offset..].iter();
SelectorIter {
iter,
next_combinator: None,
}
}
/// Returns the combinator at index `index` (zero-indexed from the right), /// or panics if the component is not a combinator. #[inline] pubfn combinator_at_match_order(&self, index: usize) -> Combinator { matchself.0.slice()[index] {
Component::Combinator(c) => c, ref other => panic!( "Not a combinator: {:?}, {:?}, index: {}",
other, self, index
),
}
}
/// Returns an iterator over the entire sequence of simple selectors and /// combinators, in matching order (from right to left). #[inline] pubfn iter_raw_match_order(&self) -> slice::Iter<'_, Component<Impl>> { self.0.slice().iter()
}
/// Returns the combinator at index `index` (zero-indexed from the left), /// or panics if the component is not a combinator. #[inline] pubfn combinator_at_parse_order(&self, index: usize) -> Combinator { matchself.0.slice()[self.len() - index - 1] {
Component::Combinator(c) => c, ref other => panic!( "Not a combinator: {:?}, {:?}, index: {}",
other, self, index
),
}
}
/// Returns an iterator over the sequence of simple selectors and /// combinators, in parse order (from left to right), starting from /// `offset`. #[inline] pubfn iter_raw_parse_order_from(
&self,
offset: usize,
) -> Rev<slice::Iter<'_, Component<Impl>>> { self.0.slice()[..self.len() - offset].iter().rev()
}
/// Creates a Selector from a vec of Components, specified in parse order. Used in tests. #[allow(dead_code)] pub(crate) fn from_vec(
vec: Vec<Component<Impl>>,
specificity: u32,
flags: SelectorFlags,
) -> Self { letmut builder = SelectorBuilder::default(); for component in vec.into_iter() { iflet Some(combinator) = component.as_combinator() {
builder.push_combinator(combinator);
} else {
builder.push_simple_selector(component);
}
} let spec = SpecificityAndFlags { specificity, flags };
Selector(builder.build_with_specificity_and_flags(spec, ParseRelative::No))
}
/// Returns count of simple selectors and combinators in the Selector. #[inline] pubfn len(&self) -> usize { self.0.len()
}
/// Returns the address on the heap of the ThinArc for memory reporting. pubfn 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 /// ``` pubfn visit<V>(&self, visitor: &mut V) -> bool where
V: SelectorVisitor<Impl = Impl>,
{ letmut current = self.iter(); letmut combinator = None; loop { if !visitor.visit_complex_selector(combinator) { returnfalse;
}
for selector in &mut current { if !selector.visit(visitor) { returnfalse;
}
}
combinator = current.next_sequence(); if combinator.is_none() { break;
}
}
true
}
/// Parse a selector, without any pseudo-element. #[inline] pubfn parse<'i, 't, P>(
parser: &P,
input: &mut CssParser<'i, 't>,
) -> Result<Self, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>,
{
parse_selector(
parser,
input,
SelectorParsingState::empty(),
ParseRelative::No,
)
}
/// Is the compound starting at the offset the subject compound, or referring to its pseudo-element? pubfn is_rightmost(&self, offset: usize) -> bool { // There can really be only one pseudo-element, and it's not really valid for anything else to // follow it.
offset == 0
|| matches!( self.combinator_at_match_order(offset - 1),
Combinator::PseudoElement
)
}
}
impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> { /// Prepares this iterator to point to the next sequence to the left, /// returning the combinator if the sequence was found. #[inline] pubfn next_sequence(&mutself) -> Option<Combinator> { self.next_combinator.take()
}
/// Skips a sequence of simple selectors and all subsequent sequences until /// a non-pseudo-element ancestor combinator is reached. fn skip_until_ancestor(&mutself) { loop { whileself.next().is_some() {} ifself.next_sequence().is_none_or(|c| c.is_ancestor()) { break;
}
}
}
#[inline] pub(crate) fn matches_for_stateless_pseudo_element(&mutself) -> bool { let first = matchself.next() {
Some(c) => c, // Note that this is the common path that we keep inline: the // pseudo-element not having anything to its right.
None => returntrue,
}; self.matches_for_stateless_pseudo_element_internal(first)
}
#[inline(never)] fn matches_for_stateless_pseudo_element_internal(&mutself, first: &Component<Impl>) -> bool { if !first.matches_for_stateless_pseudo_element() { returnfalse;
} for component inself { // The only other parser-allowed Components in this sequence are // state pseudo-classes, or one of the other things that can contain // them. if !component.matches_for_stateless_pseudo_element() { returnfalse;
}
} true
}
/// Returns remaining count of the simple selectors and combinators in the Selector. #[inline] pubfn selector_length(&self) -> usize { self.iter.len()
}
}
impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> { type Item = &'a Component<Impl>;
impl<'a, Impl: SelectorImpl> fmt::Debug for SelectorIter<'a, Impl> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let iter = self.iter.clone().rev(); for component in iter {
component.to_css(f)?
}
Ok(())
}
}
/// An iterator over all combinators in a selector. Does not traverse selectors within psuedoclasses. struct CombinatorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>); impl<'a, Impl: 'a + SelectorImpl> CombinatorIter<'a, Impl> { fn new(inner: SelectorIter<'a, Impl>) -> Self { letmut result = CombinatorIter(inner);
result.consume_non_combinators();
result
}
impl<'a, Impl: SelectorImpl> Iterator for CombinatorIter<'a, Impl> { type Item = Combinator; fn next(&mutself) -> Option<Self::Item> { let result = self.0.next_sequence(); self.consume_non_combinators();
result
}
}
/// An iterator over all simple selectors belonging to ancestors. struct AncestorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>); impl<'a, Impl: SelectorImpl> Iterator for AncestorIter<'a, Impl> { type Item = &'a Component<Impl>; fn next(&mutself) -> Option<Self::Item> { // Grab the next simple selector in the sequence if available. let next = self.0.next(); if next.is_some() { return next;
} // See if there are more sequences. If so, skip any non-ancestor sequences. if !self.0.next_sequence()?.is_ancestor() { self.0.skip_until_ancestor();
} self.0.next()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "to_shmem", derive(ToShmem))] pubenum Combinator {
Child, // >
Descendant, // space
NextSibling, // +
LaterSibling, // ~ /// A dummy combinator we use to the left of pseudo-elements. /// /// It serializes as the empty string, and acts effectively as a child /// combinator in most cases. If we ever actually start using a child /// combinator for this, we will need to fix up the way hashes are computed /// for revalidation selectors.
PseudoElement, /// Another combinator used for ::slotted(), which represent the jump from /// a node to its assigned slot.
SlotAssignment, /// Another combinator used for `::part()`, which represents the jump from /// the part to the containing shadow host.
Part,
}
impl Combinator { /// Returns true if this combinator is a pseudo-element combinator. #[inline] pubfn is_pseudo_element(&self) -> bool {
matches!(*self, Combinator::PseudoElement)
}
/// Returns true if this combinator is a next- or later-sibling combinator. #[inline] pubfn is_sibling(&self) -> bool {
matches!(*self, Combinator::NextSibling | Combinator::LaterSibling)
}
/// Returns true if this combinator represents a jump to an ancestor. Note that this includes /// combinators like ::part() / ::slotted() and pseudo-elements! #[inline] pubfn is_ancestor(&self) -> bool {
!self.is_sibling()
}
}
/// An enum for the different types of :nth- pseudoclasses #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "to_shmem", derive(ToShmem))] #[cfg_attr(feature = "to_shmem", shmem(no_bounds))] pubenum NthType {
Child,
LastChild,
OnlyChild,
OfType,
LastOfType,
OnlyOfType,
}
/// The properties that comprise an An+B syntax #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "to_shmem", derive(ToShmem))] #[cfg_attr(feature = "to_shmem", shmem(no_bounds))] pubstruct AnPlusB(pub i32, pub i32);
impl AnPlusB { #[inline] pubfn matches_index(&self, i: i32) -> bool { // Is there a non-negative integer n such that An+B=i? match i.checked_sub(self.1) {
None => false,
Some(an) => match an.checked_div(self.0) {
Some(n) => n >= 0 && self.0 * n == an,
None /* a == 0 */ => an == 0,
},
}
}
}
impl ToCss for AnPlusB { /// Serialize <an+b> (part of the CSS Syntax spec). /// <https://drafts.csswg.org/css-syntax-3/#serialize-an-anb-value> #[inline] fn to_css<W>(&self, dest: &mut W) -> fmt::Result where
W: fmt::Write,
{ match (self.0, self.1) {
(0, 0) => dest.write_char('0'),
/// Returns true if this is an edge selector that is not `:*-of-type`` #[inline] pubfn is_simple_edge(&self) -> bool { self.an_plus_b.0 == 0
&& self.an_plus_b.1 == 1
&& !self.ty.is_of_type()
&& !self.ty.is_only()
}
/// Returns the An+B part of the selector #[inline] pubfn nth_data(&self) -> &NthSelectorData {
&self.0.header
}
/// Returns the selector list part of the selector #[inline] pubfn selectors(&self) -> &[Selector<Impl>] { self.0.slice()
}
}
/// Flag indicating where a given relative selector's match would be contained. #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "to_shmem", derive(ToShmem))] pubenum RelativeSelectorMatchHint { /// Within this element's subtree.
InSubtree, /// Within this element's direct children.
InChild, /// This element's next sibling.
InNextSibling, /// Within this element's next sibling's subtree.
InNextSiblingSubtree, /// Within this element's subsequent siblings.
InSibling, /// Across this element's subsequent siblings and their subtrees.
InSiblingSubtree,
}
impl RelativeSelectorMatchHint { /// Create a new relative selector match hint based on its composition. pubfn new(
relative_combinator: Combinator,
has_child_or_descendants: bool,
has_adjacent_or_next_siblings: bool,
) -> Self { match relative_combinator {
Combinator::Descendant => RelativeSelectorMatchHint::InSubtree,
Combinator::Child => { if !has_child_or_descendants {
RelativeSelectorMatchHint::InChild
} else { // Technically, for any composition that consists of child combinators only, // the search space is depth-constrained, but it's probably not worth optimizing for.
RelativeSelectorMatchHint::InSubtree
}
},
Combinator::NextSibling => { if !has_child_or_descendants && !has_adjacent_or_next_siblings {
RelativeSelectorMatchHint::InNextSibling
} elseif !has_child_or_descendants && has_adjacent_or_next_siblings {
RelativeSelectorMatchHint::InSibling
} elseif has_child_or_descendants && !has_adjacent_or_next_siblings { // Match won't cross multiple siblings.
RelativeSelectorMatchHint::InNextSiblingSubtree
} else {
RelativeSelectorMatchHint::InSiblingSubtree
}
},
Combinator::LaterSibling => { if !has_child_or_descendants {
RelativeSelectorMatchHint::InSibling
} else { // Even if the match may not cross multiple siblings, we have to look until // we find a match anyway.
RelativeSelectorMatchHint::InSiblingSubtree
}
},
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => {
debug_assert!(false, "Unexpected relative combinator");
RelativeSelectorMatchHint::InSubtree
},
}
}
/// Is the match traversal direction towards the descendant of this element (As opposed to siblings)? pubfn is_descendant_direction(&self) -> bool {
matches!(*self, Self::InChild | Self::InSubtree)
}
/// Is the match traversal terminated at the next sibling? pubfn is_next_sibling(&self) -> bool {
matches!(*self, Self::InNextSibling | Self::InNextSiblingSubtree)
}
/// Does the match involve matching the subtree? pubfn is_subtree(&self) -> bool {
matches!(
*self, Self::InSubtree | Self::InSiblingSubtree | Self::InNextSiblingSubtree
)
}
}
/// Count of combinators in a given relative selector, not traversing selectors of pseudoclasses. #[derive(Clone, Copy)] pubstruct RelativeSelectorCombinatorCount {
relative_combinator: Combinator, pub child_or_descendants: usize, pub adjacent_or_next_siblings: usize,
}
impl RelativeSelectorCombinatorCount { /// Create a new relative selector combinator count from a given relative selector. pubfn new<Impl: SelectorImpl>(relative_selector: &RelativeSelector<Impl>) -> Self { letmut result = RelativeSelectorCombinatorCount {
relative_combinator: relative_selector.selector.combinator_at_parse_order(1),
child_or_descendants: 0,
adjacent_or_next_siblings: 0,
};
/// Get the match hint based on the current combinator count. pubfn get_match_hint(&self) -> RelativeSelectorMatchHint {
RelativeSelectorMatchHint::new( self.relative_combinator, self.child_or_descendants != 0, self.adjacent_or_next_siblings != 0,
)
}
}
/// Storage for a relative selector. #[derive(Clone, Eq, PartialEq)] #[cfg_attr(feature = "to_shmem", derive(ToShmem))] #[cfg_attr(feature = "to_shmem", shmem(no_bounds))] pubstruct RelativeSelector<Impl: SelectorImpl> { /// Match space constraining hint. pub match_hint: RelativeSelectorMatchHint, /// The selector. Guaranteed to contain `RelativeSelectorAnchor` and the relative combinator in parse order. #[cfg_attr(feature = "to_shmem", shmem(field_bound))] pub selector: Selector<Impl>,
}
bitflags! { /// Composition of combinators in a given selector, not traversing selectors of pseudoclasses. #[derive(Clone, Debug, Eq, PartialEq)] struct CombinatorComposition: u8 { const DESCENDANTS = 1 << 0; const SIBLINGS = 1 << 1;
}
}
impl<Impl: SelectorImpl> RelativeSelector<Impl> { fn from_selector_list(selector_list: SelectorList<Impl>) -> Box<[Self]> {
selector_list
.slice()
.iter()
.map(|selector| { // It's more efficient to keep track of all this during the parse time, but that seems like a lot of special // case handling for what it's worth. if cfg!(debug_assertions) { let relative_selector_anchor = selector.iter_raw_parse_order_from(0).next();
debug_assert!(
relative_selector_anchor.is_some(), "Relative selector is empty"
);
debug_assert!(
matches!(
relative_selector_anchor.unwrap(),
Component::RelativeSelectorAnchor
), "Relative selector anchor is missing"
);
} // Leave a hint for narrowing down the search space when we're matching. let composition = CombinatorComposition::for_relative_selector(&selector); let match_hint = RelativeSelectorMatchHint::new(
selector.combinator_at_parse_order(1),
composition.intersects(CombinatorComposition::DESCENDANTS),
composition.intersects(CombinatorComposition::SIBLINGS),
);
RelativeSelector {
match_hint,
selector: selector.clone(),
}
})
.collect()
}
}
/// A CSS simple selector or combinator. We store both in the same enum for /// optimal packing and cache performance, see [1]. /// /// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973 #[derive(Clone, Eq, PartialEq)] #[cfg_attr(feature = "to_shmem", derive(ToShmem))] #[cfg_attr(feature = "to_shmem", shmem(no_bounds))] pubenum Component<Impl: SelectorImpl> {
LocalName(LocalName<Impl>),
AttributeInNoNamespaceExists { #[cfg_attr(feature = "to_shmem", shmem(field_bound))]
local_name: Impl::LocalName,
local_name_lower: Impl::LocalName,
}, // Used only when local_name is already lowercase.
AttributeInNoNamespace {
local_name: Impl::LocalName,
operator: AttrSelectorOperator, #[cfg_attr(feature = "to_shmem", shmem(field_bound))]
value: Impl::AttrValue,
case_sensitivity: ParsedCaseSensitivity,
}, // Use a Box in the less common cases with more data to keep size_of::<Component>() small.
AttributeOther(Box<AttrSelectorWithOptionalNamespace<Impl>>),
/// Pseudo-classes
Negation(SelectorList<Impl>),
Root,
Empty,
Scope, /// :scope added implicitly into scoped rules (i.e. In `@scope`) not /// explicitly using `:scope` or `&` selectors. /// /// https://drafts.csswg.org/css-cascade-6/#scoped-rules /// /// Unlike the normal `:scope` selector, this does not add any specificity. /// See https://github.com/w3c/csswg-drafts/issues/10196
ImplicitScope,
ParentSelector,
Nth(NthSelectorData),
NthOf(NthOfSelectorData<Impl>),
NonTSPseudoClass(#[cfg_attr(feature = "to_shmem", shmem(field_bound))] Impl::NonTSPseudoClass), /// The ::slotted() pseudo-element: /// /// https://drafts.csswg.org/css-scoping/#slotted-pseudo /// /// The selector here is a compound selector, that is, no combinators. /// /// NOTE(emilio): This should support a list of selectors, but as of this /// writing no other browser does, and that allows them to put ::slotted() /// in the rule hash, so we do that too. /// /// See https://github.com/w3c/csswg-drafts/issues/2158
Slotted(Selector<Impl>), /// The `::part` pseudo-element. /// https://drafts.csswg.org/css-shadow-parts/#part
Part(#[cfg_attr(feature = "to_shmem", shmem(field_bound))] Box<[Impl::Identifier]>), /// The `:host` pseudo-class: /// /// https://drafts.csswg.org/css-scoping/#host-selector /// /// NOTE(emilio): This should support a list of selectors, but as of this /// writing no other browser does, and that allows them to put :host() /// in the rule hash, so we do that too. /// /// 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(SelectorList<Impl>), /// The `:is` pseudo-class. /// /// https://drafts.csswg.org/selectors/#matches-pseudo /// /// Same comment as above re. the argument.
Is(SelectorList<Impl>), /// The `:has` pseudo-class. /// /// https://drafts.csswg.org/selectors/#has-pseudo /// /// Same comment as above re. the argument.
Has(Box<[RelativeSelector<Impl>]>), /// An invalid selector inside :is() / :where().
Invalid(Arc<String>), /// An implementation-dependent pseudo-element selector.
PseudoElement(#[cfg_attr(feature = "to_shmem", shmem(field_bound))] Impl::PseudoElement),
impl<Impl: SelectorImpl> Component<Impl> { /// Returns true if this is a combinator. #[inline] pubfn is_combinator(&self) -> bool {
matches!(*self, Component::Combinator(_))
}
/// Returns true if this is a :host() selector. #[inline] pubfn is_host(&self) -> bool {
matches!(*self, Component::Host(..))
}
/// Returns the value as a combinator if applicable, None otherwise. pubfn as_combinator(&self) -> Option<Combinator> { match *self {
Component::Combinator(c) => Some(c),
_ => None,
}
}
/// Whether a given selector (to the right of a pseudo-element) should match for stateless /// pseudo-elements. Note that generally nothing matches for those, but since we have :not(), /// we still need to traverse nested selector lists. fn matches_for_stateless_pseudo_element(&self) -> bool { match *self {
Component::Negation(ref selectors) => !selectors.slice().iter().all(|selector| {
selector
.iter_raw_match_order()
.all(|c| c.matches_for_stateless_pseudo_element())
}),
Component::Is(ref selectors) | Component::Where(ref selectors) => {
selectors.slice().iter().any(|selector| {
selector
.iter_raw_match_order()
.all(|c| c.matches_for_stateless_pseudo_element())
})
},
_ => false,
}
}
pubfn visit<V>(&self, visitor: &mut V) -> bool where
V: SelectorVisitor<Impl = Impl>,
{ useself::Component::*; if !visitor.visit_simple_selector(self) { returnfalse;
}
// Returns true if this has any selector that requires an index calculation. e.g. // :nth-child, :first-child, etc. For nested selectors, return true only if the // indexed selector is in its subject compound. pubfn has_indexed_selector_in_subject(&self) -> bool { match *self {
Component::NthOf(..) | Component::Nth(..) => returntrue,
Component::Is(ref selectors)
| Component::Where(ref selectors)
| Component::Negation(ref selectors) => { // Check the subject compound. for selector in selectors.slice() { letmut iter = selector.iter(); whilelet Some(c) = iter.next() { if c.has_indexed_selector_in_subject() { returntrue;
}
}
}
},
_ => (),
}; false
}
}
fn serialize_selector_list<'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result where Impl: SelectorImpl,
I: Iterator<Item = &'a Selector<Impl>>,
W: fmt::Write,
{ letmut first = true; for selector in iter { if !first {
dest.write_str(", ")?;
}
first = false;
selector.to_css(dest)?;
}
Ok(())
}
impl<Impl: SelectorImpl> ToCss for Selector<Impl> { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where
W: fmt::Write,
{ // Compound selectors invert the order of their contents, so we need to // undo that during serialization. // // This two-iterator strategy involves walking over the selector twice. // We could do something more clever, but selector serialization probably // isn't hot enough to justify it, and the stringification likely // dominates anyway. // // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), // which we need for |split|. So we split by combinators on a match-order // sequence and then reverse.
letmut combinators_exhausted = false; for compound in compound_selectors {
debug_assert!(!combinators_exhausted);
// https://drafts.csswg.org/cssom/#serializing-selectors let first_compound = match compound.first() {
None => continue,
Some(c) => c,
}; if matches!(
first_compound,
Component::RelativeSelectorAnchor | Component::ImplicitScope
) {
debug_assert!(
compound.len() == 1, "RelativeSelectorAnchor/ImplicitScope should only be a simple selector"
); iflet Some(c) = combinators.next() {
c.to_css_relative(dest)?;
} else { // Direct property declarations in `@scope` does not have // combinators, since its selector is `:implicit-scope`.
debug_assert!(
matches!(first_compound, Component::ImplicitScope), "Only implicit :scope may not have any combinator"
);
} continue;
}
// 1. If there is only one simple selector in the compound selectors // which is a universal selector, append the result of // serializing the universal selector to s. // // Check if `!compound.empty()` first--this can happen if we have // something like `... > ::before`, because we store `>` and `::` // both as combinators internally. // // If we are in this case, after we have serialized the universal // selector, we skip Step 2 and continue with the algorithm. let (can_elide_namespace, first_non_namespace) = match compound[0] {
Component::ExplicitAnyNamespace
| Component::ExplicitNoNamespace
| Component::Namespace(..) => (false, 1),
Component::DefaultNamespace(..) => (true, 1),
_ => (true, 0),
}; letmut perform_step_2 = true; let next_combinator = combinators.next(); if first_non_namespace == compound.len() - 1 { match (next_combinator, &compound[first_non_namespace]) { // We have to be careful here, because if there is a // pseudo element "combinator" there isn't really just // the one simple selector. Technically this compound // selector contains the pseudo element selector as well // -- Combinator::PseudoElement, just like // Combinator::SlotAssignment, don't exist in the // spec.
(Some(Combinator::PseudoElement), _)
| (Some(Combinator::SlotAssignment), _) => (),
(_, &Component::ExplicitUniversalType) => { // Iterate over everything so we serialize the namespace // too. for simple in compound.iter() {
simple.to_css(dest)?;
} // Skip step 2, which is an "otherwise".
perform_step_2 = false;
},
_ => (),
}
}
// 2. Otherwise, for each simple selector in the compound selectors // that is not a universal selector of which the namespace prefix // maps to a namespace that is not the default namespace // serialize the simple selector and append the result to s. // // See https://github.com/w3c/csswg-drafts/issues/1606, which is // proposing to change this to match up with the behavior asserted // in cssom/serialize-namespaced-type-selectors.html, which the // following code tries to match. if perform_step_2 { for simple in compound.iter() { iflet Component::ExplicitUniversalType = *simple { // Can't have a namespace followed by a pseudo-element // selector followed by a universal selector in the same // compound selector, so we don't have to worry about the // real namespace being in a different `compound`. if can_elide_namespace { continue;
}
}
simple.to_css(dest)?;
}
}
// 3. If this is not the last part of the chain of the selector // append a single SPACE (U+0020), followed by the combinator // ">", "+", "~", ">>", "||", as appropriate, followed by another // single SPACE (U+0020) if the combinator was not whitespace, to // s. match next_combinator {
Some(c) => c.to_css(dest)?,
None => combinators_exhausted = true,
};
// 4. If this is the last part of the chain of the selector and // there is a pseudo-element, append "::" followed by the name of // the pseudo-element, to s. // // (we handle this above)
}
/// * `Err(())`: Invalid selector, abort /// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed. /// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`) fn parse_type_selector<'i, 't, P, Impl, S>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
sink: &mut S,
) -> Result<bool, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>, Impl: SelectorImpl,
S: Push<Component<Impl>>,
{ match parse_qualified_name(parser, input, /* in_attr_selector = */ false) {
Err(ParseError {
kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput),
..
})
| Ok(OptionalQName::None(_)) => Ok(false),
Ok(OptionalQName::Some(namespace, local_name)) => { if state.intersects(SelectorParsingState::AFTER_PSEUDO) { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} match namespace {
QNamePrefix::ImplicitAnyNamespace => {},
QNamePrefix::ImplicitDefaultNamespace(url) => {
sink.push(Component::DefaultNamespace(url))
},
QNamePrefix::ExplicitNamespace(prefix, url) => {
sink.push(match parser.default_namespace() {
Some(ref default_url) if url == *default_url => {
Component::DefaultNamespace(url)
},
_ => Component::Namespace(prefix, url),
})
},
QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace),
QNamePrefix::ExplicitAnyNamespace => { match parser.default_namespace() { // Element type selectors that have no namespace // component (no namespace separator) represent elements // without regard to the element's namespace (equivalent // to "*|") unless a default namespace has been declared // for namespaced selectors (e.g. in CSS, in the style // sheet). If a default namespace has been declared, // such selectors will represent only elements in the // default namespace. // -- Selectors § 6.1.1 // So we'll have this act the same as the // QNamePrefix::ImplicitAnyNamespace case.
None => {},
Some(_) => sink.push(Component::ExplicitAnyNamespace),
}
},
QNamePrefix::ImplicitNoNamespace => {
unreachable!() // Not returned with in_attr_selector = false
},
} match local_name {
Some(name) => sink.push(Component::LocalName(LocalName {
lower_name: to_ascii_lowercase(&name).as_ref().into(),
name: name.as_ref().into(),
})),
None => sink.push(Component::ExplicitUniversalType),
}
Ok(true)
},
Err(e) => Err(e),
}
}
#[derive(Debug)] enum QNamePrefix<Impl: SelectorImpl> {
ImplicitNoNamespace, // `foo` in attr selectors
ImplicitAnyNamespace, // `foo` in type selectors, without a default ns
ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns
ExplicitNoNamespace, // `|foo`
ExplicitAnyNamespace, // `*|foo`
ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo`
}
/// * `Err(())`: Invalid selector, abort /// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed, /// but the token is still returned. /// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector fn parse_qualified_name<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
in_attr_selector: bool,
) -> Result<OptionalQName<'i, Impl>, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>, Impl: SelectorImpl,
{ let default_namespace = |local_name| { let namespace = match parser.default_namespace() {
Some(url) => QNamePrefix::ImplicitDefaultNamespace(url),
None => QNamePrefix::ImplicitAnyNamespace,
};
Ok(OptionalQName::Some(namespace, local_name))
};
let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { let location = input.current_source_location(); match input.next_including_whitespace() {
Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)),
Ok(&Token::Ident(ref local_name)) => {
Ok(OptionalQName::Some(namespace, Some(local_name.clone())))
},
Ok(t) if in_attr_selector => { let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone());
Err(location.new_custom_error(e))
},
Ok(t) => Err(location.new_custom_error(
SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()),
)),
Err(e) => Err(e.into()),
}
};
let start = input.state(); match input.next_including_whitespace() {
Ok(Token::Ident(value)) => { let value = value.clone(); let after_ident = input.state(); match input.next_including_whitespace() {
Ok(&Token::Delim('|')) => { let prefix = value.as_ref().into(); let result = parser.namespace_for_prefix(&prefix); let url = result.ok_or(
after_ident
.source_location()
.new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)),
)?;
explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url))
},
_ => {
input.reset(&after_ident); if in_attr_selector {
Ok(OptionalQName::Some(
QNamePrefix::ImplicitNoNamespace,
Some(value),
))
} else {
default_namespace(Some(value))
}
},
}
},
Ok(Token::Delim('*')) => { let after_star = input.state(); match input.next_including_whitespace() {
Ok(&Token::Delim('|')) => {
explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace)
},
_ if !in_attr_selector => {
input.reset(&after_star);
default_namespace(None)
},
result => { let t = result?;
Err(after_star
.source_location()
.new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t.clone())))
},
}
},
Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace),
Ok(t) => { let t = t.clone();
input.reset(&start);
Ok(OptionalQName::None(t))
},
Err(e) => {
input.reset(&start);
Err(e.into())
},
}
}
fn parse_attribute_selector<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
) -> Result<Component<Impl>, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>, Impl: SelectorImpl,
{ let namespace; let local_name;
let value = match input.expect_ident_or_string() {
Ok(t) => t.clone(),
Err(BasicParseError {
kind: BasicParseErrorKind::UnexpectedToken(t),
location,
}) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))),
Err(e) => return Err(e.into()),
};
let attribute_flags = parse_attribute_flags(input)?; let value = value.as_ref().into(); let local_name_lower; let local_name_is_ascii_lowercase; let case_sensitivity;
{ let local_name_lower_cow = to_ascii_lowercase(&local_name);
case_sensitivity =
attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some());
local_name_lower = local_name_lower_cow.as_ref().into();
local_name_is_ascii_lowercase = matches!(local_name_lower_cow, Cow::Borrowed(..));
} let local_name = local_name.as_ref().into(); if namespace.is_some() || !local_name_is_ascii_lowercase {
Ok(Component::AttributeOther(Box::new(
AttrSelectorWithOptionalNamespace {
namespace,
local_name,
local_name_lower,
operation: ParsedAttrSelectorOperation::WithValue {
operator,
case_sensitivity,
value,
},
},
)))
} else {
Ok(Component::AttributeInNoNamespace {
local_name,
operator,
value,
case_sensitivity,
})
}
}
/// An attribute selector can have 's' or 'i' as flags, or no flags at all. enum AttributeFlags { // Matching should be case-sensitive ('s' flag).
CaseSensitive, // Matching should be case-insensitive ('i' flag).
AsciiCaseInsensitive, // No flags. Matching behavior depends on the name of the attribute.
CaseSensitivityDependsOnName,
}
loop { let result = match parse_one_simple_selector(parser, input, *state)? {
None => break,
Some(result) => result,
};
if empty { iflet Some(url) = parser.default_namespace() { // If there was no explicit type selector, but there is a // default namespace, there is an implicit "<defaultns>|*" type // selector. Except for :host() or :not() / :is() / :where(), // where we ignore it. // // https://drafts.csswg.org/css-scoping/#host-element-in-tree: // // When considered within its own shadow trees, the shadow // host is featureless. Only the :host, :host(), and // :host-context() pseudo-classes are allowed to match it. // // https://drafts.csswg.org/selectors-4/#featureless: // // A featureless element does not match any selector at all, // except those it is explicitly defined to match. If a // given selector is allowed to match a featureless element, // it must do so while ignoring the default namespace. // // https://drafts.csswg.org/selectors-4/#matches // // Default namespace declarations do not affect the compound // selector representing the subject of any selector within // a :is() pseudo-class, unless that compound selector // contains an explicit universal selector or type selector. // // (Similar quotes for :where() / :not()) // let ignore_default_ns = state
.intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE)
|| matches!(
result,
SimpleSelectorParseResult::SimpleSelector(Component::Host(..))
); if !ignore_default_ns {
builder.push_simple_selector(Component::DefaultNamespace(url));
}
}
}
empty = false;
match result {
SimpleSelectorParseResult::SimpleSelector(s) => {
builder.push_simple_selector(s);
},
SimpleSelectorParseResult::PartPseudo(part_names) => {
state.insert(SelectorParsingState::AFTER_PART_LIKE);
builder.push_combinator(Combinator::Part);
builder.push_simple_selector(Component::Part(part_names));
},
SimpleSelectorParseResult::SlottedPseudo(selector) => {
state.insert(SelectorParsingState::AFTER_SLOTTED);
builder.push_combinator(Combinator::SlotAssignment);
builder.push_simple_selector(Component::Slotted(selector));
},
SimpleSelectorParseResult::PseudoElement(p) => { if p.parses_as_element_backed() {
state.insert(SelectorParsingState::AFTER_PART_LIKE);
} else {
state.insert(SelectorParsingState::AFTER_NON_ELEMENT_BACKED_PSEUDO); if p.is_before_or_after() {
state.insert(SelectorParsingState::AFTER_BEFORE_OR_AFTER_PSEUDO);
}
} if !p.accepts_state_pseudo_classes() {
state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
} if p.is_in_pseudo_element_tree() {
state.insert(SelectorParsingState::IN_PSEUDO_ELEMENT_TREE);
}
builder.push_combinator(Combinator::PseudoElement);
builder.push_simple_selector(Component::PseudoElement(p));
},
}
}
Ok(empty)
}
fn parse_is_where<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
component: impl FnOnce(SelectorList<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()); // https://drafts.csswg.org/selectors/#matches-pseudo: // // Pseudo-elements cannot be represented by the matches-any // pseudo-class; they are not valid within :is(). // let inner = SelectorList::parse_with_state(
parser,
input,
state
| SelectorParsingState::SKIP_DEFAULT_NAMESPACE
| SelectorParsingState::DISALLOW_PSEUDOS,
ForgivingParsing::Yes,
ParseRelative::No,
)?;
Ok(component(inner))
}
fn parse_has<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Component<Impl>, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>, Impl: SelectorImpl,
{
debug_assert!(parser.parse_has()); if state.intersects(
SelectorParsingState::DISALLOW_RELATIVE_SELECTOR | SelectorParsingState::AFTER_PSEUDO,
) { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} // Nested `:has()` is disallowed, mark it as such. // Note: The spec defines ":has-allowed pseudo-element," but there's no // pseudo-element defined as such at the moment. // https://w3c.github.io/csswg-drafts/selectors-4/#has-allowed-pseudo-element let inner = SelectorList::parse_with_state(
parser,
input,
state
| SelectorParsingState::SKIP_DEFAULT_NAMESPACE
| SelectorParsingState::DISALLOW_PSEUDOS
| SelectorParsingState::DISALLOW_RELATIVE_SELECTOR,
ForgivingParsing::No,
ParseRelative::ForHas,
)?;
Ok(Component::Has(RelativeSelector::from_selector_list(inner)))
}
if state.intersects(
SelectorParsingState::AFTER_NON_ELEMENT_BACKED_PSEUDO | SelectorParsingState::AFTER_SLOTTED,
) { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
let after_part = state.intersects(SelectorParsingState::AFTER_PART_LIKE);
P::parse_non_ts_functional_pseudo_class(parser, name, input, after_part)
.map(Component::NonTSPseudoClass)
}
fn parse_nth_pseudo_class<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
ty: NthType,
) -> Result<Component<Impl>, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>, Impl: SelectorImpl,
{ if !state.allows_tree_structural_pseudo_classes() { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} let (a, b) = parse_nth(input)?; let nth_data = NthSelectorData {
ty,
is_function: true,
an_plus_b: AnPlusB(a, b),
}; if !parser.parse_nth_child_of() || ty.is_of_type() { return Ok(Component::Nth(nth_data));
}
// Try to parse "of <selector-list>". if input.try_parse(|i| i.expect_ident_matching("of")).is_err() { return Ok(Component::Nth(nth_data));
} // Whitespace between "of" and the selector list is optional // https://github.com/w3c/csswg-drafts/issues/8285 let selectors = SelectorList::parse_with_state(
parser,
input,
state
| SelectorParsingState::SKIP_DEFAULT_NAMESPACE
| SelectorParsingState::DISALLOW_PSEUDOS,
ForgivingParsing::No,
ParseRelative::No,
)?;
Ok(Component::NthOf(NthOfSelectorData::new(
&nth_data,
selectors.slice().iter().cloned(),
)))
}
/// Returns whether the name corresponds to a CSS2 pseudo-element that /// can be specified with the single colon syntax (in addition to the /// double-colon syntax, which can be used for all pseudo-elements). pubfn is_css2_pseudo_element(name: &str) -> bool { // ** Do not add to this list! **
match_ignore_ascii_case! { name, "before" | "after" | "first-line" | "first-letter" => true,
_ => false,
}
}
/// Parse a simple selector other than a type selector. /// /// * `Err(())`: Invalid selector, abort /// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed. /// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element fn parse_one_simple_selector<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Option<SimpleSelectorParseResult<Impl>>, ParseError<'i, P::Error>> where
P: Parser<'i, Impl = Impl>, Impl: SelectorImpl,
{ let start = input.state(); let token = match input.next_including_whitespace().map(|t| t.clone()) {
Ok(t) => t,
Err(..) => {
input.reset(&start); return Ok(None);
},
};
Ok(Some(match token {
Token::IDHash(id) => { if state.intersects(SelectorParsingState::AFTER_PSEUDO) { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} let id = Component::ID(id.as_ref().into());
SimpleSelectorParseResult::SimpleSelector(id)
},
Token::Delim(delim) if delim == '.' || (delim == '&' && parser.parse_parent_selector()) => { if state.intersects(SelectorParsingState::AFTER_PSEUDO) { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} let location = input.current_source_location();
SimpleSelectorParseResult::SimpleSelector(if delim == '&' {
Component::ParentSelector
} else { let class = match *input.next_including_whitespace()? {
Token::Ident(ref class) => class, ref t => { let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); return Err(location.new_custom_error(e));
},
};
Component::Class(class.as_ref().into())
})
},
Token::SquareBracketBlock => { if state.intersects(SelectorParsingState::AFTER_PSEUDO) { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;
SimpleSelectorParseResult::SimpleSelector(attr)
},
Token::Colon => { let location = input.current_source_location(); let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() {
Token::Colon => (false, input.next_including_whitespace()?.clone()),
t => (true, t),
}; let (name, is_functional) = match next_token {
Token::Ident(name) => (name, false),
Token::Function(name) => (name, true),
t => { let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t); return Err(input.new_custom_error(e));
},
}; let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); if is_pseudo_element { // Pseudos after pseudo elements are not allowed in some cases: // - Some states will disallow pseudos, such as the interiors of // :has/:is/:where/:not (DISALLOW_PSEUDOS). // - Non-element backed pseudos do not allow other pseudos to follow (AFTER_NON_ELEMENT_BACKED_PSEUDO)... // - ... except ::before and ::after, which allow _some_ pseudos. if state.intersects(SelectorParsingState::DISALLOW_PSEUDOS)
|| (state.intersects(SelectorParsingState::AFTER_NON_ELEMENT_BACKED_PSEUDO)
&& !state.intersects(SelectorParsingState::AFTER_BEFORE_OR_AFTER_PSEUDO))
{ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} let pseudo_element = if is_functional { if P::parse_part(parser) && name.eq_ignore_ascii_case("part") { if !state.allows_part() { return Err(
input.new_custom_error(SelectorParseErrorKind::InvalidState)
);
} let names = input.parse_nested_block(|input| { letmut result = Vec::with_capacity(1);
result.push(input.expect_ident()?.as_ref().into()); while !input.is_exhausted() {
result.push(input.expect_ident()?.as_ref().into());
}
Ok(result.into_boxed_slice())
})?; return Ok(Some(SimpleSelectorParseResult::PartPseudo(names)));
} if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { if !state.allows_slotted() { return Err(
input.new_custom_error(SelectorParseErrorKind::InvalidState)
);
} let selector = input.parse_nested_block(|input| {
parse_inner_compound_selector(parser, input, state)
})?; return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector)));
}
input.parse_nested_block(|input| {
P::parse_functional_pseudo_element(parser, name, input)
})?
} else {
P::parse_pseudo_element(parser, location, name)?
};
if state.intersects(SelectorParsingState::AFTER_BEFORE_OR_AFTER_PSEUDO)
&& !pseudo_element.valid_after_before_or_after()
{ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
if state.allows_tree_structural_pseudo_classes() { // If a descendant pseudo of a pseudo-element root has no other siblings, then :only-child // matches that pseudo. Note that we don't accept other tree structural pseudo classes in // this case (to match other browsers). And the spec mentions only `:only-child` as well. // https://drafts.csswg.org/css-view-transitions-1/#pseudo-root if state.allows_only_child_pseudo_class_only() { if name.eq_ignore_ascii_case("only-child") { return Ok(Component::Nth(NthSelectorData::only( /* of_type = */ false,
)));
} // Other non-functional pseudo classes are not allowed. // FIXME: Perhaps we can refactor this, e.g. distinguish tree-structural pseudo classes // from other non-ts pseudo classes. Otherwise, this special case looks weird. return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));
}
let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; if state.intersects(SelectorParsingState::AFTER_NON_ELEMENT_BACKED_PSEUDO)
&& !pseudo_class.is_user_action_state()
{ return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));
}
Ok(Component::NonTSPseudoClass(pseudo_class))
}
// NB: pub module in order to access the DummyParser #[cfg(test)] pubmod tests { usesuper::*; usecrate::builder::SelectorFlags; usecrate::parser; use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss}; use std::collections::HashMap; use std::fmt;
impl SelectorImpl for DummySelectorImpl { type ExtraMatchingData<'a> = std::marker::PhantomData<&'a ()>; type AttrValue = DummyAttrValue; type Identifier = DummyAtom; type LocalName = DummyAtom; type NamespaceUrl = DummyAtom; type NamespacePrefix = DummyAtom; type BorrowedLocalName = DummyAtom; type BorrowedNamespaceUrl = DummyAtom; type NonTSPseudoClass = PseudoClass; type PseudoElement = PseudoElement;
}
fn parse_ns_relative_expected<'i, 'a>(
input: &'i str,
parser: &DummyParser,
parse_relative: ParseRelative,
expected: Option<&'a str>,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { letmut parser_input = ParserInput::new(input); let result = SelectorList::parse(
parser,
&mut CssParser::new(&mut parser_input),
parse_relative,
); iflet Ok(ref selectors) = result { // We can't assume that the serialized parsed selector will equal // the input; for example, if there is no default namespace, '*|foo' // should serialize to 'foo'.
assert_eq!(
selectors.to_css_string(), match expected {
Some(x) => x,
None => input,
}
);
}
result
}
fn specificity(a: u32, b: u32, c: u32) -> u32 {
a << 20 | b << 10 | c
}
#[test] fn test_ancestor_hashes_in_subject_position() { fn ancestor_hash_count(selector: &str) -> usize { let list = parse(selector).unwrap();
assert_eq!(list.slice().len(), 1); letmut hashes = [0u32; 4]; letmut len = 0;
collect_ancestor_hashes(
list.slice()[0].iter(),
QuirksMode::NoQuirks,
&mut hashes,
&mut len,
);
len
}
// An ancestor combinator inside :is() / :where() in subject position // should still contribute ancestor hashes (bug 2040922).
assert_eq!(ancestor_hash_count(":where(.ancestor > .subject)"), 1);
assert_eq!(ancestor_hash_count(":is(.ancestor .subject)"), 1);
assert_eq!(
ancestor_hash_count(":where(.ancestor > :not(:last-child))"), 1
);
// Real ancestors combine with ancestor combinators nested in a subject // :where().
assert_eq!(
ancestor_hash_count(".real-ancestor :where(.inner-ancestor > .subject)"), 2
);
// :is() / :where() with more than one selector OR their selectors, so no // hash can be collected from them, even when they contain ancestor // combinators.
assert_eq!(ancestor_hash_count(":is(.a, .b) .subject"), 0);
assert_eq!(
ancestor_hash_count(":where(.ancestor > .subject, .other)"), 0
); // But a real ancestor next to a multi-selector subject :where() is still // collected.
assert_eq!(ancestor_hash_count(".real-ancestor :where(.a > .b, .c)"), 1);
// Pseudo-elements match on their originating element, so simple // selectors in front of the pseudo-element combinator are part of the // subject and don't contribute ancestor hashes.
assert_eq!(ancestor_hash_count(".subject::before"), 0);
assert_eq!(ancestor_hash_count(".real-ancestor .subject::before"), 1); // An ancestor combinator nested in a subject :where() is still collected // even when the subject carries a pseudo-element.
assert_eq!(
ancestor_hash_count(":where(.ancestor > .subject)::before"), 1
);
}
#[test] fn test_empty() { letmut input = ParserInput::new(":empty"); let list = SelectorList::parse(
&DummyParser::default(),
&mut CssParser::new(&mut input),
ParseRelative::No,
);
assert!(list.is_ok());
}
let parent = parse(".bar, div .baz").unwrap(); let child = parse("#foo &.bar").unwrap();
assert_eq!(
child.replace_parent_selector(&parent),
parse("#foo :is(.bar, div .baz).bar").unwrap()
);
let has_child = parse("#foo:has(&.bar)").unwrap();
assert_eq!(
has_child.replace_parent_selector(&parent),
parse("#foo:has(:is(.bar, div .baz).bar)").unwrap()
);
let child =
parse_relative_expected("#foo", ParseRelative::ForNesting, Some("& #foo")).unwrap();
assert_eq!(
child.replace_parent_selector(&parent),
parse(":is(.bar, div .baz) #foo").unwrap()
);
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.249Bemerkung:
(vorverarbeitet am 2026-09-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.