/* 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 cssparser::parse_nth; use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind}; use cssparser::{CowRcStr, Delimiter, SourceLocation}; use cssparser::{Parser as CssParser, ToCss, Token}; 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; use bitflags::bitflags; use cssparser::match_ignore_ascii_case; use debug_unreachable::debug_unreachable;
#[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
}
/// 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() 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 = 1 << 2; /// Whether we've parsed a 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_PSEUDO_ELEMENT = 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_PSEUDO_ELEMENT` must be set /// as well. const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
/// Whether we are after any of the pseudo-like things. const AFTER_PSEUDO = Self::AFTER_PART.bits() | Self::AFTER_SLOTTED.bits() | Self::AFTER_PSEUDO_ELEMENT.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 << 8;
}
}
impl SelectorParsingState { #[inline] fn allows_pseudos(self) -> bool { // NOTE(emilio): We allow pseudos after ::part and such.
!self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS)
}
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,
};
// 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;
}
type SelectorData<Impl> = ThinArc<SpecificityAndFlags, Component<Impl>>;
bitflags! { /// What kind of selectors potentially matching featureless shawdow host are present. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pubstruct FeaturelessHostMatches: u8 { /// This selector matches featureless shadow host via `:host`. const FOR_HOST = 1 << 0; /// This selector matches featureless shadow host via `:scope`. /// Featureless match applies only if we're: /// 1) In a scoping context, AND /// 2) The scope is a shadow host. const FOR_SCOPE = 1 << 1;
}
}
/// 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(..)
)
})
}
/// 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"
);
}
/// Whether this selector matches a featureless shadow host, with no combinators to the left, and /// optionally has a pseudo-element to the right. #[inline] pubfn matches_featureless_host_selector_or_pseudo_element(&self) -> FeaturelessHostMatches { let flags = self.flags();
letmut result = FeaturelessHostMatches::empty(); if flags.intersects(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT) { return result;
} if flags.intersects(SelectorFlags::HAS_HOST) {
result.insert(FeaturelessHostMatches::FOR_HOST);
} if flags.intersects(SelectorFlags::HAS_SCOPE) {
result.insert(FeaturelessHostMatches::FOR_SCOPE);
}
result
}
/// 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()
}
/// Whether this selector is a featureless selector matching the shadow host, with no /// combinators to the left. #[inline] pub(crate) fn is_featureless_host_selector(&mutself) -> FeaturelessHostMatches { ifself.selector_length() == 0 { return FeaturelessHostMatches::empty();
} letmut result = FeaturelessHostMatches::empty(); whilelet Some(c) = self.next() { let component_matches = c.matches_featureless_host(); if component_matches.is_empty() { return FeaturelessHostMatches::empty();
}
result.insert(component_matches);
} ifself.next_sequence().is_some() {
FeaturelessHostMatches::empty()
} else {
result
}
}
#[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: 'a + SelectorImpl> AncestorIter<'a, Impl> { /// Creates an AncestorIter. The passed-in iterator is assumed to point to /// the beginning of the child sequence, which will be skipped. fn new(inner: SelectorIter<'a, Impl>) -> Self { letmut result = AncestorIter(inner);
result.skip_until_ancestor();
result
}
/// 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.0.next().is_some() {} // If this is ever changed to stop at the "pseudo-element" // combinator, we will need to fix the way we compute hashes for // revalidation selectors. ifself.0.next_sequence().map_or(true, |x| {
matches!(x, Combinator::Child | Combinator::Descendant)
}) { break;
}
}
}
}
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. iflet Some(combinator) = self.0.next_sequence() { if !matches!(combinator, Combinator::Child | Combinator::Descendant) { self.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 child or descendant combinator. #[inline] pubfn is_ancestor(&self) -> bool {
matches!(
*self,
Combinator::Child |
Combinator::Descendant |
Combinator::PseudoElement |
Combinator::SlotAssignment
)
}
/// 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)
}
}
/// 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,
}
/// Returns true if this is an edge selector that is not `:*-of-type`` #[inline] pubfn is_simple_edge(&self) -> bool { self.a == 0 && self.b == 1 && !self.ty.is_of_type() && !self.ty.is_only()
}
/// Serialize <an+b> (part of the CSS Syntax spec, but currently only used here). /// <https://drafts.csswg.org/css-syntax-3/#serialize-an-anb-value> #[inline] fn write_affine<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result { match (self.a, self.b) {
(0, 0) => dest.write_char('0'),
/// 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 if this component can match a featureless shadow host, and if so, /// via which selector. #[inline] pubfn matches_featureless_host(&self) -> FeaturelessHostMatches { match *self {
Component::Host(..) => FeaturelessHostMatches::FOR_HOST,
Component::Scope | Component::ImplicitScope => FeaturelessHostMatches::FOR_SCOPE,
Component::Where(ref l) | Component::Is(ref l) => {
debug_assert!(l.len() > 0, "Zero length selector?"); // TODO(emilio): For now we require that everything in logical combination can match // the featureless shadow host, because not doing so brings up a fair amount of extra // complexity (we can't make the decision on whether to walk out statically). letmut result = FeaturelessHostMatches::empty(); for i in l.slice() { if !result.insert_not_empty(
i.matches_featureless_host_selector_or_pseudo_element()
) { return FeaturelessHostMatches::empty();
}
}
result
},
_ => FeaturelessHostMatches::empty(),
}
}
/// 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"
);
combinators.next().unwrap().to_css_relative(dest)?; 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));
}
}
}
if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT | SelectorParsingState::AFTER_SLOTTED) { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
let after_part = state.intersects(SelectorParsingState::AFTER_PART);
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,
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). fn 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 { if !state.allows_pseudos() { 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.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_PSEUDO_ELEMENT) &&
!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_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.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.