Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace};
use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation, ParsedCaseSensitivity};
use crate::bloom::BLOOM_HASH_MASK;
use crate::builder::{
relative_selector_list_specificity_and_flags, selector_list_specificity_and_flags,
SelectorBuilder, SelectorFlags, Specificity, SpecificityAndFlags,
};
use crate::context::QuirksMode;
use crate::sink::Push;
use crate::visitor::SelectorListKind;
pub use crate::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;
/// A trait that represents a pseudo-element.
pub trait PseudoElement: Sized + ToCss {
/// The `SelectorImpl` this pseudo-element is used for.
type Impl: 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
}
}
/// A trait that represents a pseudo-class.
pub trait NonTSPseudoClass: Sized + ToCss {
/// The `SelectorImpl` this pseudo-element is used for.
type Impl: SelectorImpl;
/// Whether this pseudo-class is :active or :hover.
fn is_active_or_hover(&self) -> bool;
/// Whether this pseudo-class belongs to:
///
fn is_user_action_state(&self) -> bool;
fn visit<V>(&self, _visitor: &mut V) -> bool
where
V: SelectorVisitor<Impl = Self::Impl>,
{
true
}
}
/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a
/// Cow::Owned if `s` had to be converted into ASCII lowercase.
fn to_ascii_lowercase(s: &str) -> Cow<str> {
if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {
let mut 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: u8 {
/// 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 explicitly disallow combinators.
const DISALLOW_COMBINATORS = 1 << 5;
/// Whether we explicitly disallow pseudo-element-like things.
const DISALLOW_PSEUDOS = 1 << 6;
/// Whether we explicitly disallow relative selectors (i.e. `:has()`).
const DISALLOW_RELATIVE_SELECTOR = 1 << 7;
}
}
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)
}
#[inline]
fn allows_slotted(self) -> bool {
!self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)
}
#[inline]
fn allows_part(self) -> bool {
!self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)
}
#[inline]
fn allows_non_functional_pseudo_classes(self) -> bool {
!self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
}
#[inline]
fn allows_tree_structural_pseudo_classes(self) -> bool {
!self.intersects(Self::AFTER_PSEUDO)
}
#[inline]
fn allows_combinators(self) -> bool {
!self.intersects(Self::DISALLOW_COMBINATORS)
}
}
pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
#[derive(Clone, Debug, PartialEq)]
pub enum SelectorParseErrorKind<'i> {
NoQualifiedNameInAttributeSelector(Token<'i>),
EmptySelector,
DanglingCombinator,
NonCompoundSelector,
NonPseudoElementAfterSlotted,
InvalidPseudoElementAfterSlotted,
InvalidPseudoElementInsideWhere,
InvalidState,
UnexpectedTokenInAttributeSelector(Token<'i>),
PseudoElementExpectedColon(Token<'i>),
PseudoElementExpectedIdent(Token<'i>),
NoIdentForPseudo(Token<'i>),
UnsupportedPseudoClassOrElement(CowRcStr<'i>),
UnexpectedIdent(CowRcStr<'i>),
ExpectedNamespace(CowRcStr<'i>),
ExpectedBarInAttr(Token<'i>),
BadValueInAttr(Token<'i>),
InvalidQualNameInAttr(Token<'i>),
ExplicitNamespaceUnexpectedToken(Token<'i>),
ClassNeedsIdent(Token<'i>),
}
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
pub trait 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;
/// non tree-structural pseudo-classes
type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass<Impl = Self>;
/// pseudo-elements
type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>;
/// Whether attribute hashes should be collected for filtering
/// purposes.
fn should_collect_attr_hash(_name: &Self::LocalName) -> bool {
false
}
}
}
}
macro_rules! with_bounds {
( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => {
with_all_bounds! {
[$($CommonBounds)* + $($FromStr)* + ToCss]
[$($CommonBounds)*]
[$($FromStr)*]
}
}
}
with_bounds! {
[Clone + Eq]
[for<'a> From<&'a str>]
}
pub trait Parser<'i> {
type Impl: SelectorImpl;
type Error: 'i + From<SelectorParseErrorKind<'i>>;
/// Whether to parse the `::slotted()` pseudo-element.
fn parse_slotted(&self) -> bool {
false
}
/// Whether to parse the `::part()` pseudo-element.
fn parse_part(&self) -> bool {
false
}
/// Whether to parse the selector list of nth-child() or nth-last-child().
fn parse_nth_child_of(&self) -> bool {
false
}
/// Whether to parse the `:where` pseudo-class.
fn parse_is_and_where(&self) -> bool {
false
}
/// Whether to parse the :has pseudo-class.
fn parse_has(&self) -> bool {
false
}
/// Whether to parse the '&' delimiter as a parent selector.
fn parse_parent_selector(&self) -> bool {
false
}
/// Whether the given function name is an alias for the `:is()` function.
fn is_is_alias(&self, _name: &str) -> bool {
false
}
/// Whether to parse the `:host` pseudo-class.
fn parse_host(&self) -> bool {
false
}
/// Whether to allow forgiving selector-list parsing.
fn allow_forgiving_selectors(&self) -> bool {
true
}
/// 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::Impl as SelectorImpl>::NonTSPseudoClass, ParseError<'i, Self::Error>> {
Err(
location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
fn parse_non_ts_functional_pseudo_class<'t>(
&self,
name: CowRcStr<'i>,
parser: &mut CssParser<'i, 't>,
_after_part: bool,
) -> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ParseError<'i, Self::Error>> {
Err(
parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
fn parse_pseudo_element(
&self,
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<<Self::Impl as SelectorImpl>::PseudoElement, ParseError<'i, Self::Error>> {
Err(
location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
fn parse_functional_pseudo_element<'t>(
&self,
name: CowRcStr<'i>,
arguments: &mut CssParser<'i, 't>,
) -> Result<<Self::Impl as SelectorImpl>::PseudoElement, ParseError<'i, Self::Error>> {
Err(
arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
fn default_namespace(&self) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> {
None
}
fn namespace_for_prefix(
&self,
_prefix: &<Self::Impl as SelectorImpl>::NamespacePrefix,
) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> {
None
}
}
/// A selector list is a tagged pointer with either a single selector, or a ThinArc<()> of multiple
/// selectors.
#[derive(Clone, Eq, Debug, PartialEq, ToShmem)]
#[shmem(no_bounds)]
pub struct SelectorList<Impl: SelectorImpl>(
#[shmem(field_bound)] ThinArcUnion<SpecificityAndFlags, Component<Impl>, (), Selector<Impl>>,
);
impl<Impl: SelectorImpl> SelectorList<Impl> {
pub fn 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
}
pub fn from_iter(mut iter: impl ExactSizeIterator<Item = Selector<Impl>>) -> Self {
if iter.len() == 1 {
Self::from_one(iter.next().unwrap())
} else {
Self(ThinArcUnion::from_second(ThinArc::from_header_and_iter(
(),
iter,
)))
}
}
#[inline]
pub fn slice(&self) -> &[Selector<Impl>] {
match self.0.borrow() {
ArcUnionBorrow::First(..) => {
// SAFETY: see from_one.
let selector: &Selector<Impl> = unsafe { std::mem::transmute(self) };
std::slice::from_ref(selector)
},
ArcUnionBorrow::Second(list) => list.get().slice(),
}
}
#[inline]
pub fn len(&self) -> usize {
match self.0.borrow() {
ArcUnionBorrow::First(..) => 1,
ArcUnionBorrow::Second(list) => list.len(),
}
}
/// Returns the address on the heap of the ThinArc for memory reporting.
pub fn thin_arc_heap_ptr(&self) -> *const ::std::os::raw::c_void {
match self.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)]
pub struct SelectorKey(usize);
impl SelectorKey {
/// Create a new key based on the given selector.
pub fn 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)]
pub enum 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 ParseRelative {
#[inline]
pub(crate) fn needs_implicit_parent_selector(self) -> bool {
matches!(self, Self::ForNesting)
}
}
impl<Impl: SelectorImpl> SelectorList<Impl> {
/// Returns a selector list with a single `&`
pub fn ampersand() -> Self {
Self::from_one(Selector::ampersand())
}
/// Parse a comma-separated list of Selectors.
///
/// Return the Selectors or Err if there is an invalid selector.
pub fn 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,
)
}
pub fn parse_forgiving<'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::Yes,
parse_relative,
)
}
#[inline]
fn parse_with_state<'i, 't, P>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
recovery: ForgivingParsing,
parse_relative: ParseRelative,
) -> Result<Self, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
{
let mut values = SmallVec::<[_; 4]>::new();
let forgiving = recovery == ForgivingParsing::Yes && parser.allow_forgiving_selectors();
loop {
let selector = input.parse_until_before(Delimiter::Comma, |input| {
let start = input.position();
let mut selector = parse_selector(parser, input, state, parse_relative);
if forgiving && (selector.is_err() || input.expect_exhausted().is_err()) {
input.expect_no_error_token()?;
selector = Ok(Selector::new_invalid(input.slice_from(start)));
}
selector
})?;
values.push(selector);
match input.next() {
Ok(&Token::Comma) => {},
Ok(_) => unreachable!(),
Err(_) => break,
}
}
Ok(Self::from_iter(values.into_iter()))
}
/// Replaces the parent selector in all the items of the selector list.
pub fn 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)]
pub struct 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, .. }
if Impl::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,
)
{
return false;
}
continue;
},
_ => continue,
};
hashes[*len] = hash & BLOOM_HASH_MASK;
*len += 1;
if *len == hashes.len() {
return false;
}
}
true
}
fn collect_ancestor_hashes<Impl: SelectorImpl>(
iter: SelectorIter<Impl>,
quirks_mode: QuirksMode,
hashes: &mut [u32; 4],
len: &mut usize,
) {
collect_selector_hashes(AncestorIter::new(iter), quirks_mode, hashes, len, |s| {
AncestorIter(s.iter())
});
}
impl AncestorHashes {
pub fn new<Impl: SelectorImpl>(selector: &Selector<Impl>, quirks_mode: QuirksMode) -> Self {
// Compute ancestor hashes for the bloom filter.
let mut hashes = [0u32; 4];
let mut len = 0;
collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len);
debug_assert!(len <= 4);
// 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;
}
AncestorHashes {
packed_hashes: [hashes[0], hashes[1], hashes[2]],
}
}
/// Returns the fourth hash, reassembled from parts.
pub fn fourth_hash(&self) -> u32 {
((self.packed_hashes[0] & 0xff000000) >> 24) |
((self.packed_hashes[1] & 0xff000000) >> 16) |
((self.packed_hashes[2] & 0xff000000) >> 8)
}
}
#[inline]
pub fn namespace_empty_string<Impl: SelectorImpl>() -> Impl::NamespaceUrl {
// Rust type’s default, not default namespace
Impl::NamespaceUrl::default()
}
type SelectorData<Impl> = ThinArc<SpecificityAndFlags, Component<Impl>>;
/// 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, ToShmem)]
#[shmem(no_bounds)]
#[repr(transparent)]
pub struct Selector<Impl: SelectorImpl>(#[shmem(field_bound)] SelectorData<Impl>);
impl<Impl: SelectorImpl> Selector<Impl> {
/// See Arc::mark_as_intentionally_leaked
pub fn mark_as_intentionally_leaked(&self) {
self.0.mark_as_intentionally_leaked()
}
fn ampersand() -> Self {
Self(ThinArc::from_header_and_iter(
SpecificityAndFlags {
specificity: 0,
flags: SelectorFlags::HAS_PARENT,
},
std::iter::once(Component::ParentSelector),
))
}
#[inline]
pub fn specificity(&self) -> u32 {
self.0.header.specificity
}
#[inline]
pub(crate) fn flags(&self) -> SelectorFlags {
self.0.header.flags
}
#[inline]
pub fn has_pseudo_element(&self) -> bool {
self.flags().intersects(SelectorFlags::HAS_PSEUDO)
}
#[inline]
pub fn has_parent_selector(&self) -> bool {
self.flags().intersects(SelectorFlags::HAS_PARENT)
}
#[inline]
pub fn is_slotted(&self) -> bool {
self.flags().intersects(SelectorFlags::HAS_SLOTTED)
}
#[inline]
pub fn is_part(&self) -> bool {
self.flags().intersects(SelectorFlags::HAS_PART)
}
#[inline]
pub fn parts(&self) -> Option<&[Impl::Identifier]> {
if !self.is_part() {
return None;
}
let mut iter = self.iter();
if self.has_pseudo_element() {
// Skip the pseudo-element.
for _ in &mut iter {}
let combinator = iter.next_sequence()?;
debug_assert_eq!(combinator, Combinator::PseudoElement);
}
for component in iter {
if let Component::Part(ref part) = *component {
return Some(part);
}
}
debug_assert!(false, "is_part() lied somehow?");
None
}
#[inline]
pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> {
if !self.has_pseudo_element() {
return None;
}
for component in self.iter() {
if let Component::PseudoElement(ref pseudo) = *component {
return Some(pseudo);
}
}
debug_assert!(false, "has_pseudo_element lied!");
None
}
/// Whether this selector (pseudo-element part excluded) matches every element.
///
/// Used for "pre-computed" pseudo-elements in components/style/stylist.rs
#[inline]
pub fn 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]
pub fn 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]
pub fn iter_skip_relative_selector_anchor(&self) -> SelectorIter<Impl> {
if cfg!(debug_assertions) {
let mut 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"
);
}
SelectorIter {
iter: self.0.slice()[..self.len() - 2].iter(),
next_combinator: None,
}
}
/// Whether this selector is a featureless :host selector, with no combinators to the left, and
/// optionally has a pseudo-element to the right.
#[inline]
pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool {
let flags = self.flags();
flags.intersects(SelectorFlags::HAS_HOST) &&
!flags.intersects(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT)
}
/// Returns an iterator over this selector in matching order (right-to-left),
/// skipping the rightmost |offset| Components.
#[inline]
pub fn 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]
pub fn combinator_at_match_order(&self, index: usize) -> Combinator {
match self.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]
pub fn 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]
pub fn combinator_at_parse_order(&self, index: usize) -> Combinator {
match self.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]
pub fn 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 {
let mut builder = SelectorBuilder::default();
for component in vec.into_iter() {
if let 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))
}
#[inline]
fn into_data(self) -> SelectorData<Impl> {
self.0
}
pub fn replace_parent_selector(&self, parent: &SelectorList<Impl>) -> Self {
let parent_specificity_and_flags =
selector_list_specificity_and_flags(parent.slice().iter());
let mut specificity = Specificity::from(self.specificity());
let mut flags = self.flags() - SelectorFlags::HAS_PARENT;
fn replace_parent_on_selector_list<Impl: SelectorImpl>(
orig: &[Selector<Impl>],
parent: &SelectorList<Impl>,
specificity: &mut Specificity,
flags: &mut SelectorFlags,
propagate_specificity: bool,
flags_to_propagate: SelectorFlags,
) -> Option<SelectorList<Impl>> {
if !orig.iter().any(|s| s.has_parent_selector()) {
return None;
}
let result = SelectorList::from_iter(orig.iter().map(|s| {
s.replace_parent_selector(parent)
}));
let result_specificity_and_flags =
selector_list_specificity_and_flags(result.slice().iter());
if propagate_specificity {
*specificity += Specificity::from(
result_specificity_and_flags.specificity -
selector_list_specificity_and_flags(orig.iter()).specificity,
);
}
flags.insert(
result_specificity_and_flags
.flags
.intersection(flags_to_propagate),
);
Some(result)
}
fn replace_parent_on_relative_selector_list<Impl: SelectorImpl>(
orig: &[RelativeSelector<Impl>],
parent: &SelectorList<Impl>,
specificity: &mut Specificity,
flags: &mut SelectorFlags,
flags_to_propagate: SelectorFlags,
) -> Vec<RelativeSelector<Impl>> {
let mut any = false;
let result = orig
.iter()
.map(|s| {
if !s.selector.has_parent_selector() {
return s.clone();
}
any = true;
RelativeSelector {
match_hint: s.match_hint,
selector: s.selector.replace_parent_selector(parent),
}
})
.collect();
if !any {
return result;
}
let result_specificity_and_flags =
relative_selector_list_specificity_and_flags(&result);
flags.insert(
result_specificity_and_flags
.flags
.intersection(flags_to_propagate),
);
*specificity += Specificity::from(
result_specificity_and_flags.specificity -
relative_selector_list_specificity_and_flags(orig).specificity,
);
result
}
fn replace_parent_on_selector<Impl: SelectorImpl>(
orig: &Selector<Impl>,
parent: &SelectorList<Impl>,
specificity: &mut Specificity,
flags: &mut SelectorFlags,
flags_to_propagate: SelectorFlags,
) -> Selector<Impl> {
let new_selector = orig.replace_parent_selector(parent);
*specificity += Specificity::from(new_selector.specificity() - orig.specificity());
flags.insert(new_selector.flags().intersection(flags_to_propagate));
new_selector
}
if !self.has_parent_selector() {
return self.clone();
}
let iter = self.iter_raw_match_order().map(|component| {
use self::Component::*;
match *component {
LocalName(..) |
ID(..) |
Class(..) |
AttributeInNoNamespaceExists { .. } |
AttributeInNoNamespace { .. } |
AttributeOther(..) |
ExplicitUniversalType |
ExplicitAnyNamespace |
ExplicitNoNamespace |
DefaultNamespace(..) |
Namespace(..) |
Root |
Empty |
Scope |
Nth(..) |
NonTSPseudoClass(..) |
PseudoElement(..) |
Combinator(..) |
Host(None) |
Part(..) |
Invalid(..) |
RelativeSelectorAnchor => component.clone(),
ParentSelector => {
specificity += Specificity::from(parent_specificity_and_flags.specificity);
flags.insert(
parent_specificity_and_flags
.flags
.intersection(SelectorFlags::for_nesting()),
);
Is(parent.clone())
},
Negation(ref selectors) => {
Negation(
replace_parent_on_selector_list(
selectors.slice(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ true,
SelectorFlags::for_nesting(),
)
.unwrap_or_else(|| selectors.clone()),
)
},
Is(ref selectors) => {
Is(replace_parent_on_selector_list(
selectors.slice(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ true,
SelectorFlags::for_nesting(),
)
.unwrap_or_else(|| selectors.clone()))
},
Where(ref selectors) => {
Where(
replace_parent_on_selector_list(
selectors.slice(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ false,
SelectorFlags::for_nesting(),
)
.unwrap_or_else(|| selectors.clone()),
)
},
Has(ref selectors) => Has(replace_parent_on_relative_selector_list(
selectors,
parent,
&mut specificity,
&mut flags,
SelectorFlags::for_nesting(),
)
.into_boxed_slice()),
Host(Some(ref selector)) => Host(Some(replace_parent_on_selector(
selector,
parent,
&mut specificity,
&mut flags,
SelectorFlags::for_nesting() - SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
))),
NthOf(ref data) => {
let selectors = replace_parent_on_selector_list(
data.selectors(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ true,
SelectorFlags::for_nesting(),
);
NthOf(match selectors {
Some(s) => {
NthOfSelectorData::new(data.nth_data(), s.slice().iter().cloned())
},
None => data.clone(),
})
},
Slotted(ref selector) => Slotted(replace_parent_on_selector(
selector,
parent,
&mut specificity,
&mut flags,
SelectorFlags::for_nesting(),
)),
}
});
let mut items = UniqueArc::from_header_and_iter(Default::default(), iter);
*items.header_mut() = SpecificityAndFlags {
specificity: specificity.into(),
flags,
};
Selector(items.shareable())
}
/// Returns count of simple selectors and combinators in the Selector.
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
/// Returns the address on the heap of the ThinArc for memory reporting.
pub fn thin_arc_heap_ptr(&self) -> *const ::std::os::raw::c_void {
self.0.heap_ptr()
}
/// Traverse selector components inside `self`.
///
/// Implementations of this method should call `SelectorVisitor` methods
/// or other impls of `Visit` as appropriate based on the fields of `Self`.
///
/// A return value of `false` indicates terminating the traversal.
/// It should be propagated with an early return.
/// On the contrary, `true` indicates that all fields of `self` have been traversed:
///
/// ```rust,ignore
/// if !visitor.visit_simple_selector(&self.some_simple_selector) {
/// return false;
/// }
/// if !self.some_component.visit(visitor) {
/// return false;
/// }
/// true
/// ```
pub fn visit<V>(&self, visitor: &mut V) -> bool
where
V: SelectorVisitor<Impl = Impl>,
{
let mut current = self.iter();
let mut combinator = None;
loop {
if !visitor.visit_complex_selector(combinator) {
return false;
}
for selector in &mut current {
if !selector.visit(visitor) {
return false;
}
}
combinator = current.next_sequence();
if combinator.is_none() {
break;
}
}
true
}
/// Parse a selector, without any pseudo-element.
#[inline]
pub fn 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,
)
}
pub fn new_invalid(s: &str) -> Self {
fn check_for_parent(input: &mut CssParser, has_parent: &mut bool) {
while let Ok(t) = input.next() {
match *t {
Token::Function(_) |
Token::ParenthesisBlock |
Token::CurlyBracketBlock |
Token::SquareBracketBlock => {
let _ = input.parse_nested_block(
|i| -> Result<(), ParseError<'_, BasicParseError>> {
check_for_parent(i, has_parent);
Ok(())
},
);
},
Token::Delim('&') => {
*has_parent = true;
},
_ => {},
}
if *has_parent {
break;
}
}
}
let mut has_parent = false;
{
let mut parser = cssparser::ParserInput::new(s);
let mut parser = CssParser::new(&mut parser);
check_for_parent(&mut parser, &mut has_parent);
}
Self(ThinArc::from_header_and_iter(
SpecificityAndFlags {
specificity: 0,
flags: if has_parent {
SelectorFlags::HAS_PARENT
} else {
SelectorFlags::empty()
},
},
std::iter::once(Component::Invalid(Arc::new(String::from(s.trim())))),
))
}
/// Is the compound starting at the offset the subject compound, or referring to its pseudo-element?
pub fn 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)
}
}
#[derive(Clone)]
pub struct SelectorIter<'a, Impl: 'a + SelectorImpl> {
iter: slice::Iter<'a, Component<Impl>>,
next_combinator: Option<Combinator>,
}
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]
pub fn next_sequence(&mut self) -> Option<Combinator> {
self.next_combinator.take()
}
/// Whether this selector is a featureless host selector, with no
/// combinators to the left.
#[inline]
pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
self.selector_length() > 0 &&
self.all(|component| component.matches_featureless_host()) &&
self.next_sequence().is_none()
}
#[inline]
pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool {
let first = match self.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 => return true,
};
self.matches_for_stateless_pseudo_element_internal(first)
}
#[inline(never)]
fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<Impl>) -> bool {
if !first.matches_for_stateless_pseudo_element() {
return false;
}
for component in self {
// 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() {
return false;
}
}
true
}
/// Returns remaining count of the simple selectors and combinators in the Selector.
#[inline]
pub fn selector_length(&self) -> usize {
self.iter.len()
}
}
impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> {
type Item = &'a Component<Impl>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
debug_assert!(
self.next_combinator.is_none(),
"You should call next_sequence!"
);
match *self.iter.next()? {
Component::Combinator(c) => {
self.next_combinator = Some(c);
None
},
ref x => Some(x),
}
}
}
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 {
let mut result = CombinatorIter(inner);
result.consume_non_combinators();
result
}
fn consume_non_combinators(&mut self) {
while self.0.next().is_some() {}
}
}
impl<'a, Impl: SelectorImpl> Iterator for CombinatorIter<'a, Impl> {
type Item = Combinator;
fn next(&mut self) -> 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 {
let mut 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(&mut self) {
loop {
while self.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.
if self.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(&mut self) -> 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 let 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, ToShmem)]
pub enum 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]
pub fn 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]
pub fn is_pseudo_element(&self) -> bool {
matches!(*self, Combinator::PseudoElement)
}
/// Returns true if this combinator is a next- or later-sibling combinator.
#[inline]
pub fn is_sibling(&self) -> bool {
matches!(*self, Combinator::NextSibling | Combinator::LaterSibling)
}
}
/// An enum for the different types of :nth- pseudoclasses
#[derive(Copy, Clone, Eq, PartialEq, ToShmem)]
#[shmem(no_bounds)]
pub enum NthType {
Child,
LastChild,
OnlyChild,
OfType,
LastOfType,
OnlyOfType,
}
impl NthType {
pub fn is_only(self) -> bool {
self == Self::OnlyChild || self == Self::OnlyOfType
}
pub fn is_of_type(self) -> bool {
self == Self::OfType || self == Self::LastOfType || self == Self::OnlyOfType
}
pub fn is_from_end(self) -> bool {
self == Self::LastChild || self == Self::LastOfType
}
}
/// The properties that comprise an :nth- pseudoclass as of Selectors 3 (e.g.,
/// nth-child(An+B)).
#[derive(Copy, Clone, Eq, PartialEq, ToShmem)]
#[shmem(no_bounds)]
pub struct NthSelectorData {
pub ty: NthType,
pub is_function: bool,
pub a: i32,
pub b: i32,
}
impl NthSelectorData {
/// Returns selector data for :only-{child,of-type}
#[inline]
pub const fn only(of_type: bool) -> Self {
Self {
ty: if of_type {
NthType::OnlyOfType
} else {
NthType::OnlyChild
},
is_function: false,
a: 0,
b: 1,
}
}
/// Returns selector data for :first-{child,of-type}
#[inline]
pub const fn first(of_type: bool) -> Self {
Self {
ty: if of_type {
NthType::OfType
} else {
NthType::Child
},
is_function: false,
a: 0,
b: 1,
}
}
/// Returns selector data for :last-{child,of-type}
#[inline]
pub const fn last(of_type: bool) -> Self {
Self {
ty: if of_type {
NthType::LastOfType
} else {
NthType::LastChild
},
is_function: false,
a: 0,
b: 1,
}
}
/// Returns true if this is an edge selector that is not `:*-of-type``
#[inline]
pub fn is_simple_edge(&self) -> bool {
self.a == 0 && self.b == 1 && !self.ty.is_of_type()
}
/// Writes the beginning of the selector.
#[inline]
fn write_start<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
dest.write_str(match self.ty {
NthType::Child if self.is_function => ":nth-child(",
NthType::Child => ":first-child",
NthType::LastChild if self.is_function => ":nth-last-child(",
NthType::LastChild => ":last-child",
NthType::OfType if self.is_function => ":nth-of-type(",
NthType::OfType => ":first-of-type",
NthType::LastOfType if self.is_function => ":nth-last-of-type(",
NthType::LastOfType => ":last-of-type",
NthType::OnlyChild => ":only-child",
NthType::OnlyOfType => ":only-of-type",
})
}
/// Serialize <an+b> (part of the CSS Syntax spec, but currently only used here).
#[inline]
fn write_affine<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match (self.a, self.b) {
(0, 0) => dest.write_char('0'),
(1, 0) => dest.write_char('n'),
(-1, 0) => dest.write_str("-n"),
(_, 0) => write!(dest, "{}n", self.a),
(0, _) => write!(dest, "{}", self.b),
(1, _) => write!(dest, "n{:+}", self.b),
(-1, _) => write!(dest, "-n{:+}", self.b),
(_, _) => write!(dest, "{}n{:+}", self.a, self.b),
}
}
}
/// The properties that comprise an :nth- pseudoclass as of Selectors 4 (e.g.,
/// nth-child(An+B [of S]?)).
#[derive(Clone, Eq, PartialEq, ToShmem)]
#[shmem(no_bounds)]
pub struct NthOfSelectorData<Impl: SelectorImpl>(
#[shmem(field_bound)] ThinArc<NthSelectorData, Selector<Impl>>,
);
impl<Impl: SelectorImpl> NthOfSelectorData<Impl> {
/// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S])
#[inline]
pub fn new<I>(nth_data: &NthSelectorData, selectors: I) -> Self
where
I: Iterator<Item = Selector<Impl>> + ExactSizeIterator,
{
Self(ThinArc::from_header_and_iter(*nth_data, selectors))
}
/// Returns the An+B part of the selector
#[inline]
pub fn nth_data(&self) -> &NthSelectorData {
&self.0.header
}
/// Returns the selector list part of the selector
#[inline]
pub fn selectors(&self) -> &[Selector<Impl>] {
self.0.slice()
}
}
/// Flag indicating where a given relative selector's match would be contained.
#[derive(Clone, Copy, Eq, PartialEq, ToShmem)]
pub enum 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.
pub fn 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
} else if !has_child_or_descendants && has_adjacent_or_next_siblings {
RelativeSelectorMatchHint::InSibling
} else if 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)?
pub fn is_descendant_direction(&self) -> bool {
matches!(*self, Self::InChild | Self::InSubtree)
}
/// Is the match traversal terminated at the next sibling?
pub fn is_next_sibling(&self) -> bool {
matches!(*self, Self::InNextSibling | Self::InNextSiblingSubtree)
}
/// Does the match involve matching the subtree?
pub fn 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)]
pub struct 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.
pub fn new<Impl: SelectorImpl>(relative_selector: &RelativeSelector<Impl>) -> Self {
let mut result = RelativeSelectorCombinatorCount {
relative_combinator: relative_selector.selector.combinator_at_parse_order(1),
child_or_descendants: 0,
adjacent_or_next_siblings: 0,
};
for combinator in CombinatorIter::new(
relative_selector
.selector
.iter_skip_relative_selector_anchor(),
) {
match combinator {
Combinator::Descendant | Combinator::Child => {
result.child_or_descendants += 1;
},
Combinator::NextSibling | Combinator::LaterSibling => {
result.adjacent_or_next_siblings += 1;
},
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => {
continue
},
};
}
result
}
/// Get the match hint based on the current combinator count.
pub fn 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, ToShmem)]
#[shmem(no_bounds)]
pub struct RelativeSelector<Impl: SelectorImpl> {
/// Match space constraining hint.
pub match_hint: RelativeSelectorMatchHint,
/// The selector. Guaranteed to contain `RelativeSelectorAnchor` and the relative combinator in parse order.
#[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 CombinatorComposition {
fn for_relative_selector<Impl: SelectorImpl>(inner_selector: &Selector<Impl>) -> Self {
let mut result = CombinatorComposition::empty();
for combinator in CombinatorIter::new(inner_selector.iter_skip_relative_selector_anchor()) {
match combinator {
Combinator::Descendant | Combinator::Child => {
result.insert(Self::DESCENDANTS);
},
Combinator::NextSibling | Combinator::LaterSibling => {
result.insert(Self::SIBLINGS);
},
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => {
continue
},
};
if result.is_all() {
break;
}
}
return result;
}
}
impl<Impl: SelectorImpl> RelativeSelector<Impl> {
fn from_selector_list(selector_list: SelectorList<Impl>) -> Box<[Self]> {
let vec: Vec<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();
vec.into_boxed_slice()
}
}
/// A CSS simple selector or combinator. We store both in the same enum for
/// optimal packing and cache performance, see [1].
///
#[derive(Clone, Eq, PartialEq, ToShmem)]
#[shmem(no_bounds)]
pub enum Component<Impl: SelectorImpl> {
LocalName(LocalName<Impl>),
ID(#[shmem(field_bound)] Impl::Identifier),
Class(#[shmem(field_bound)] Impl::Identifier),
AttributeInNoNamespaceExists {
#[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,
#[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>>),
ExplicitUniversalType,
ExplicitAnyNamespace,
ExplicitNoNamespace,
DefaultNamespace(#[shmem(field_bound)] Impl::NamespaceUrl),
Namespace(
#[shmem(field_bound)] Impl::NamespacePrefix,
#[shmem(field_bound)] Impl::NamespaceUrl,
),
/// Pseudo-classes
Negation(SelectorList<Impl>),
Root,
Empty,
Scope,
ParentSelector,
Nth(NthSelectorData),
NthOf(NthOfSelectorData<Impl>),
NonTSPseudoClass(#[shmem(field_bound)] Impl::NonTSPseudoClass),
/// The ::slotted() pseudo-element:
///
///
/// 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.
///
Slotted(Selector<Impl>),
/// The `::part` pseudo-element.
Part(#[shmem(field_bound)] Box<[Impl::Identifier]>),
/// The `:host` pseudo-class:
///
///
/// 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.
///
Host(Option<Selector<Impl>>),
/// The `:where` pseudo-class.
///
///
/// 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.
///
///
/// Same comment as above re. the argument.
Is(SelectorList<Impl>),
/// The `:has` pseudo-class.
///
///
/// 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(#[shmem(field_bound)] Impl::PseudoElement),
Combinator(Combinator),
/// Used only for relative selectors, which starts with a combinator
/// (With an implied descendant combinator if not specified).
///
RelativeSelectorAnchor,
}
impl<Impl: SelectorImpl> Component<Impl> {
/// Returns true if this is a combinator.
#[inline]
pub fn is_combinator(&self) -> bool {
matches!(*self, Component::Combinator(_))
}
/// Returns true if this is a :host() selector.
#[inline]
pub fn is_host(&self) -> bool {
matches!(*self, Component::Host(..))
}
/// Returns true if this is a :host() selector.
#[inline]
pub fn matches_featureless_host(&self) -> bool {
match *self {
Component::Host(..) => true,
Component::Where(ref l) | Component::Is(ref l) => {
// TODO(emilio): For now we use .all() rather than .any(), because not doing so
// brings up a fair amount of extra complexity (we can't make the decision on
// whether to walk out statically).
l.slice()
.iter()
.all(|i| i.is_featureless_host_selector_or_pseudo_element())
},
_ => false,
}
}
/// Returns the value as a combinator if applicable, None otherwise.
pub fn 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,
}
}
pub fn visit<V>(&self, visitor: &mut V) -> bool
where
V: SelectorVisitor<Impl = Impl>,
{
use self::Component::*;
if !visitor.visit_simple_selector(self) {
return false;
}
match *self {
Slotted(ref selector) => {
if !selector.visit(visitor) {
return false;
}
},
Host(Some(ref selector)) => {
if !selector.visit(visitor) {
return false;
}
},
AttributeInNoNamespaceExists {
ref local_name,
ref local_name_lower,
} => {
if !visitor.visit_attribute_selector(
&NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
local_name,
local_name_lower,
) {
return false;
}
},
AttributeInNoNamespace { ref local_name, .. } => {
if !visitor.visit_attribute_selector(
&NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
local_name,
local_name,
) {
return false;
}
},
AttributeOther(ref attr_selector) => {
let empty_string;
let namespace = match attr_selector.namespace() {
Some(ns) => ns,
None => {
empty_string = crate::parser::namespace_empty_string::<Impl>();
NamespaceConstraint::Specific(&empty_string)
},
};
if !visitor.visit_attribute_selector(
&namespace,
&attr_selector.local_name,
&attr_selector.local_name_lower,
) {
return false;
}
},
NonTSPseudoClass(ref pseudo_class) => {
if !pseudo_class.visit(visitor) {
return false;
}
},
Negation(ref list) | Is(ref list) | Where(ref list) => {
let list_kind = SelectorListKind::from_component(self);
debug_assert!(!list_kind.is_empty());
if !visitor.visit_selector_list(list_kind, list.slice()) {
return false;
}
},
NthOf(ref nth_of_data) => {
if !visitor.visit_selector_list(SelectorListKind::NTH_OF, nth_of_data.selectors()) {
return false;
}
},
Has(ref list) => {
if !visitor.visit_relative_selector_list(list) {
return false;
}
},
_ => {},
}
true
}
// 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.
pub fn has_indexed_selector_in_subject(&self) -> bool {
match *self {
Component::NthOf(..) | Component::Nth(..) => return true,
Component::Is(ref selectors) |
Component::Where(ref selectors) |
Component::Negation(ref selectors) => {
// Check the subject compound.
for selector in selectors.slice() {
let mut iter = selector.iter();
while let Some(c) = iter.next() {
if c.has_indexed_selector_in_subject() {
return true;
}
}
}
},
_ => (),
};
false
}
}
#[derive(Clone, Eq, PartialEq, ToShmem)]
#[shmem(no_bounds)]
pub struct LocalName<Impl: SelectorImpl> {
#[shmem(field_bound)]
pub name: Impl::LocalName,
pub lower_name: Impl::LocalName,
}
impl<Impl: SelectorImpl> Debug for Selector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Selector(")?;
self.to_css(f)?;
write!(
f,
", specificity = {:#x}, flags = {:?})",
self.specificity(),
self.flags()
)
}
}
impl<Impl: SelectorImpl> Debug for Component<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_css(f)
}
}
impl<Impl: SelectorImpl> Debug for AttrSelectorWithOptionalNamespace<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_css(f)
}
}
impl<Impl: SelectorImpl> Debug for LocalName<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_css(f)
}
}
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,
{
let mut first = true;
for selector in iter {
if !first {
dest.write_str(", ")?;
}
first = false;
selector.to_css(dest)?;
}
Ok(())
}
impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
serialize_selector_list(self.slice().iter(), dest)
}
}
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.
let mut combinators = self
.iter_raw_match_order()
.rev()
.filter_map(|x| x.as_combinator());
let compound_selectors = self
.iter_raw_match_order()
.as_slice()
.split(|x| x.is_combinator())
.rev();
let mut combinators_exhausted = false;
for compound in compound_selectors {
debug_assert!(!combinators_exhausted);
if compound.is_empty() {
continue;
}
if let Component::RelativeSelectorAnchor = compound.first().unwrap() {
debug_assert!(
compound.len() == 1,
"RelativeLeft 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),
};
let mut 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.
//
// 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() {
if let 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)
}
Ok(())
}
}
impl Combinator {
fn to_css_internal<W>(&self, dest: &mut W, prefix_space: bool) -> fmt::Result
where
W: fmt::Write,
{
if matches!(
*self,
Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment
) {
return Ok(());
}
if prefix_space {
dest.write_char(' ')?;
}
match *self {
Combinator::Child => dest.write_str("> "),
Combinator::Descendant => Ok(()),
Combinator::NextSibling => dest.write_str("+ "),
Combinator::LaterSibling => dest.write_str("~ "),
Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => unsafe {
debug_unreachable!("Already handled")
},
}
}
fn to_css_relative<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
self.to_css_internal(dest, false)
}
}
impl ToCss for Combinator {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
self.to_css_internal(dest, true)
}
}
impl<Impl: SelectorImpl> ToCss for Component<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
use self::Component::*;
match *self {
Combinator(ref c) => c.to_css(dest),
Slotted(ref selector) => {
dest.write_str("::slotted(")?;
selector.to_css(dest)?;
dest.write_char(')')
},
Part(ref part_names) => {
dest.write_str("::part(")?;
for (i, name) in part_names.iter().enumerate() {
if i != 0 {
dest.write_char(' ')?;
}
name.to_css(dest)?;
}
dest.write_char(')')
},
PseudoElement(ref p) => p.to_css(dest),
ID(ref s) => {
dest.write_char('#')?;
s.to_css(dest)
},
Class(ref s) => {
dest.write_char('.')?;
s.to_css(dest)
},
LocalName(ref s) => s.to_css(dest),
ExplicitUniversalType => dest.write_char('*'),
DefaultNamespace(_) => Ok(()),
ExplicitNoNamespace => dest.write_char('|'),
ExplicitAnyNamespace => dest.write_str("*|"),
Namespace(ref prefix, _) => {
prefix.to_css(dest)?;
dest.write_char('|')
},
AttributeInNoNamespaceExists { ref local_name, .. } => {
dest.write_char('[')?;
local_name.to_css(dest)?;
dest.write_char(']')
},
AttributeInNoNamespace {
ref local_name,
operator,
ref value,
case_sensitivity,
..
} => {
dest.write_char('[')?;
local_name.to_css(dest)?;
operator.to_css(dest)?;
value.to_css(dest)?;
match case_sensitivity {
ParsedCaseSensitivity::CaseSensitive |
ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {},
ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?,
}
dest.write_char(']')
},
AttributeOther(ref attr_selector) => attr_selector.to_css(dest),
// Pseudo-classes
Root => dest.write_str(":root"),
Empty => dest.write_str(":empty"),
Scope => dest.write_str(":scope"),
ParentSelector => dest.write_char('&'),
Host(ref selector) => {
dest.write_str(":host")?;
if let Some(ref selector) = *selector {
dest.write_char('(')?;
selector.to_css(dest)?;
dest.write_char(')')?;
}
Ok(())
},
Nth(ref nth_data) => {
nth_data.write_start(dest)?;
if nth_data.is_function {
nth_data.write_affine(dest)?;
dest.write_char(')')?;
}
Ok(())
},
NthOf(ref nth_of_data) => {
let nth_data = nth_of_data.nth_data();
nth_data.write_start(dest)?;
debug_assert!(
nth_data.is_function,
"A selector must be a function to hold An+B notation"
);
nth_data.write_affine(dest)?;
debug_assert!(
matches!(nth_data.ty, NthType::Child | NthType::LastChild),
"Only :nth-child or :nth-last-child can be of a selector list"
);
debug_assert!(
!nth_of_data.selectors().is_empty(),
"The selector list should not be empty"
);
dest.write_str(" of ")?;
serialize_selector_list(nth_of_data.selectors().iter(), dest)?;
dest.write_char(')')
},
Is(ref list) | Where(ref list) | Negation(ref list) => {
match *self {
Where(..) => dest.write_str(":where(")?,
Is(..) => dest.write_str(":is(")?,
Negation(..) => dest.write_str(":not(")?,
_ => unreachable!(),
}
serialize_selector_list(list.slice().iter(), dest)?;
dest.write_str(")")
},
Has(ref list) => {
dest.write_str(":has(")?;
let mut first = true;
for RelativeSelector { ref selector, .. } in list.iter() {
if !first {
dest.write_str(", ")?;
}
first = false;
selector.to_css(dest)?;
}
dest.write_str(")")
},
NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
Invalid(ref css) => dest.write_str(css),
RelativeSelectorAnchor => Ok(()),
}
}
}
impl<Impl: SelectorImpl> ToCss for AttrSelectorWithOptionalNamespace<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
dest.write_char('[')?;
match self.namespace {
Some(NamespaceConstraint::Specific((ref prefix, _))) => {
prefix.to_css(dest)?;
dest.write_char('|')?
},
Some(NamespaceConstraint::Any) => dest.write_str("*|")?,
None => {},
}
self.local_name.to_css(dest)?;
match self.operation {
ParsedAttrSelectorOperation::Exists => {},
ParsedAttrSelectorOperation::WithValue {
operator,
case_sensitivity,
ref value,
} => {
operator.to_css(dest)?;
value.to_css(dest)?;
match case_sensitivity {
ParsedCaseSensitivity::CaseSensitive |
ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {},
ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?,
}
},
}
dest.write_char(']')
}
}
impl<Impl: SelectorImpl> ToCss for LocalName<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
self.name.to_css(dest)
}
}
/// Build up a Selector.
/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
///
/// `Err` means invalid selector.
fn parse_selector<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
mut state: SelectorParsingState,
parse_relative: ParseRelative,
) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
let mut builder = SelectorBuilder::default();
// Helps rewind less, but also simplifies dealing with relative combinators below.
input.skip_whitespace();
if parse_relative != ParseRelative::No {
let combinator = try_parse_combinator::<P, Impl>(input);
match parse_relative {
ParseRelative::ForHas => {
builder.push_simple_selector(Component::RelativeSelectorAnchor);
// Do we see a combinator? If so, push that. Otherwise, push a descendant
// combinator.
builder.push_combinator(combinator.unwrap_or(Combinator::Descendant));
},
ParseRelative::ForNesting | ParseRelative::ForScope => {
if let Ok(combinator) = combinator {
let selector = match parse_relative {
ParseRelative::ForHas | ParseRelative::No => unreachable!(),
ParseRelative::ForNesting => Component::ParentSelector,
ParseRelative::ForScope => Component::Scope,
};
builder.push_simple_selector(selector);
builder.push_combinator(combinator);
}
},
ParseRelative::No => unreachable!(),
}
}
'outer_loop: loop {
// Parse a sequence of simple selectors.
let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?;
if empty {
return Err(input.new_custom_error(if builder.has_combinators() {
SelectorParseErrorKind::DanglingCombinator
} else {
SelectorParseErrorKind::EmptySelector
}));
}
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
debug_assert!(state.intersects(
SelectorParsingState::AFTER_PSEUDO_ELEMENT |
SelectorParsingState::AFTER_SLOTTED |
SelectorParsingState::AFTER_PART
));
break;
}
let combinator = if let Ok(c) = try_parse_combinator::<P, Impl>(input) {
c
} else {
break 'outer_loop;
};
if !state.allows_combinators() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
builder.push_combinator(combinator);
}
return Ok(Selector(builder.build(parse_relative)));
}
fn try_parse_combinator<'i, 't, P, Impl>(input: &mut CssParser<'i, 't>) -> Result<Combinator, ()> {
let mut any_whitespace = false;
loop {
let before_this_token = input.state();
match input.next_including_whitespace() {
Err(_e) => return Err(()),
Ok(&Token::WhiteSpace(_)) => any_whitespace = true,
Ok(&Token::Delim('>')) => {
return Ok(Combinator::Child);
},
Ok(&Token::Delim('+')) => {
return Ok(Combinator::NextSibling);
},
Ok(&Token::Delim('~')) => {
return Ok(Combinator::LaterSibling);
},
Ok(_) => {
input.reset(&before_this_token);
if any_whitespace {
return Ok(Combinator::Descendant);
} else {
return Err(());
}
},
}
}
}
/// * `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 SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(Component<Impl>),
PseudoElement(Impl::PseudoElement),
SlottedPseudo(Selector<Impl>),
PartPseudo(Box<[Impl::Identifier]>),
}
#[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`
}
enum OptionalQName<'i, Impl: SelectorImpl> {
Some(QNamePrefix<Impl>, Option<CowRcStr<'i>>),
None(Token<'i>),
}
/// * `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;
input.skip_whitespace();
match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? {
OptionalQName::None(t) => {
return Err(input.new_custom_error(
SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t),
));
},
OptionalQName::Some(_, None) => unreachable!(),
OptionalQName::Some(ns, Some(ln)) => {
local_name = ln;
namespace = match ns {
QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None,
QNamePrefix::ExplicitNamespace(prefix, url) => {
Some(NamespaceConstraint::Specific((prefix, url)))
},
QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any),
QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => {
unreachable!() // Not returned with in_attr_selector = true
},
}
},
}
let location = input.current_source_location();
let operator = match input.next() {
// [foo]
Err(_) => {
let local_name_lower = to_ascii_lowercase(&local_name).as_ref().into();
let local_name = local_name.as_ref().into();
if let Some(namespace) = namespace {
return Ok(Component::AttributeOther(Box::new(
AttrSelectorWithOptionalNamespace {
namespace: Some(namespace),
local_name,
local_name_lower,
operation: ParsedAttrSelectorOperation::Exists,
},
)));
} else {
return Ok(Component::AttributeInNoNamespaceExists {
local_name,
local_name_lower,
});
}
},
// [foo=bar]
Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal,
// [foo~=bar]
Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes,
// [foo|=bar]
Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch,
// [foo^=bar]
Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix,
// [foo*=bar]
Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring,
// [foo$=bar]
Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix,
Ok(t) => {
return Err(location.new_custom_error(
SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone()),
));
},
};
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,
}
impl AttributeFlags {
fn to_case_sensitivity(self, local_name: &str, have_namespace: bool) -> ParsedCaseSensitivity {
match self {
AttributeFlags::CaseSensitive => ParsedCaseSensitivity::ExplicitCaseSensitive,
AttributeFlags::AsciiCaseInsensitive => ParsedCaseSensitivity::AsciiCaseInsensitive,
AttributeFlags::CaseSensitivityDependsOnName => {
if !have_namespace &&
include!(concat!(
env!("OUT_DIR"),
"/ascii_case_insensitive_html_attributes.rs"
))
.contains(local_name)
{
ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument
} else {
ParsedCaseSensitivity::CaseSensitive
}
},
}
}
}
fn parse_attribute_flags<'i, 't>(
input: &mut CssParser<'i, 't>,
) -> Result<AttributeFlags, BasicParseError<'i>> {
let location = input.current_source_location();
let token = match input.next() {
Ok(t) => t,
Err(..) => {
// Selectors spec says language-defined; HTML says it depends on the
// exact attribute name.
return Ok(AttributeFlags::CaseSensitivityDependsOnName);
},
};
let ident = match *token {
Token::Ident(ref i) => i,
ref other => return Err(location.new_basic_unexpected_token_error(other.clone())),
};
Ok(match_ignore_ascii_case! {
ident,
"i" => AttributeFlags::AsciiCaseInsensitive,
"s" => AttributeFlags::CaseSensitive,
_ => return Err(location.new_basic_unexpected_token_error(token.clone())),
})
}
/// Level 3: Parse **one** simple_selector. (Though we might insert a second
/// implied "<defaultns>|*" type selector.)
fn parse_negation<'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,
{
let list = SelectorList::parse_with_state(
parser,
input,
state |
SelectorParsingState::SKIP_DEFAULT_NAMESPACE |
SelectorParsingState::DISALLOW_PSEUDOS,
ForgivingParsing::No,
ParseRelative::No,
)?;
Ok(Component::Negation(list))
}
/// simple_selector_sequence
/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
/// | [ HASH | class | attrib | pseudo | negation ]+
///
/// `Err(())` means invalid selector.
/// `Ok(true)` is an empty selector
fn parse_compound_selector<'i, 't, P, Impl>(
parser: &P,
state: &mut SelectorParsingState,
input: &mut CssParser<'i, 't>,
builder: &mut SelectorBuilder<Impl>,
) -> Result<bool, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
input.skip_whitespace();
let mut empty = true;
if parse_type_selector(parser, input, *state, builder)? {
empty = false;
}
loop {
let result = match parse_one_simple_selector(parser, input, *state)? {
None => break,
Some(result) => result,
};
if empty {
if let 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.
//
//
// 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.
//
//
// 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.
//
//
// 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);
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) => {
state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
if !p.accepts_state_pseudo_classes() {
state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
}
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());
//
// 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) {
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.
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)))
}
fn parse_functional_pseudo_class<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
name: CowRcStr<'i>,
state: SelectorParsingState,
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
match_ignore_ascii_case! { &name,
"nth-child" => return parse_nth_pseudo_class(parser, input, state, NthType::Child),
"nth-of-type" => return parse_nth_pseudo_class(parser, input, state, NthType::OfType),
"nth-last-child" => return parse_nth_pseudo_class(parser, input, state, NthType::LastChild),
"nth-last-of-type" => return parse_nth_pseudo_class(parser, input, state, NthType::LastOfType),
"is" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Is),
"where" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Where),
"has" if parser.parse_has() => return parse_has(parser, input, state),
"host" => {
if !state.allows_tree_structural_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?)));
},
"not" => {
return parse_negation(parser, input, state)
},
_ => {}
}
if parser.parse_is_and_where() && parser.is_is_alias(&name) {
return parse_is_where(parser, input, state, Component::Is);
}
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
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| {
let mut 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_SLOTTED) &&
!pseudo_element.valid_after_slotted()
{
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
SimpleSelectorParseResult::PseudoElement(pseudo_element)
} else {
let pseudo_class = if is_functional {
input.parse_nested_block(|input| {
parse_functional_pseudo_class(parser, input, name, state)
})?
} else {
parse_simple_pseudo_class(parser, location, name, state)?
};
SimpleSelectorParseResult::SimpleSelector(pseudo_class)
}
},
_ => {
input.reset(&start);
return Ok(None);
},
}))
}
fn parse_simple_pseudo_class<'i, P, Impl>(
parser: &P,
location: SourceLocation,
name: CowRcStr<'i>,
state: SelectorParsingState,
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
if !state.allows_non_functional_pseudo_classes() {
return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));
}
if state.allows_tree_structural_pseudo_classes() {
match_ignore_ascii_case! { &name,
"first-child" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ false))),
"last-child" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ false))),
"only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))),
"root" => return Ok(Component::Root),
"empty" => return Ok(Component::Empty),
"scope" => return Ok(Component::Scope),
"host" if P::parse_host(parser) => return Ok(Component::Host(None)),
"first-of-type" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ true))),
"last-of-type" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ true))),
"only-of-type" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ true))),
_ => {},
}
}
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)]
pub mod tests {
use super::*;
use crate::builder::SelectorFlags;
use crate::parser;
use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss};
use std::collections::HashMap;
use std::fmt;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PseudoClass {
Hover,
Active,
Lang(String),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PseudoElement {
Before,
After,
Highlight(String),
}
impl parser::PseudoElement for PseudoElement {
type Impl = DummySelectorImpl;
fn accepts_state_pseudo_classes(&self) -> bool {
true
}
fn valid_after_slotted(&self) -> bool {
true
}
}
impl parser::NonTSPseudoClass for PseudoClass {
type Impl = DummySelectorImpl;
#[inline]
fn is_active_or_hover(&self) -> bool {
matches!(*self, PseudoClass::Active | PseudoClass::Hover)
}
#[inline]
fn is_user_action_state(&self) -> bool {
self.is_active_or_hover()
}
}
impl ToCss for PseudoClass {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
match *self {
PseudoClass::Hover => dest.write_str(":hover"),
PseudoClass::Active => dest.write_str(":active"),
PseudoClass::Lang(ref lang) => {
dest.write_str(":lang(")?;
serialize_identifier(lang, dest)?;
dest.write_char(')')
},
}
}
}
impl ToCss for PseudoElement {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
match *self {
PseudoElement::Before => dest.write_str("::before"),
PseudoElement::After => dest.write_str("::after"),
PseudoElement::Highlight(ref name) => {
dest.write_str("::highlight(")?;
serialize_identifier(&name, dest)?;
dest.write_char(')')
},
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DummySelectorImpl;
#[derive(Default)]
pub struct DummyParser {
default_ns: Option<DummyAtom>,
ns_prefixes: HashMap<DummyAtom, DummyAtom>,
}
impl DummyParser {
fn default_with_namespace(default_ns: DummyAtom) -> DummyParser {
DummyParser {
default_ns: Some(default_ns),
ns_prefixes: Default::default(),
}
}
}
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;
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct DummyAttrValue(String);
impl ToCss for DummyAttrValue {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
use std::fmt::Write;
dest.write_char('"')?;
write!(cssparser::CssStringWriter::new(dest), "{}", &self.0)?;
dest.write_char('"')
}
}
impl<'a> From<&'a str> for DummyAttrValue {
fn from(string: &'a str) -> Self {
Self(string.into())
}
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct DummyAtom(String);
impl ToCss for DummyAtom {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
serialize_identifier(&self.0, dest)
}
}
impl From<String> for DummyAtom {
fn from(string: String) -> Self {
DummyAtom(string)
}
}
impl<'a> From<&'a str> for DummyAtom {
fn from(string: &'a str) -> Self {
DummyAtom(string.into())
}
}
impl PrecomputedHash for DummyAtom {
fn precomputed_hash(&self) -> u32 {
self.0.as_ptr() as u32
}
}
impl<'i> Parser<'i> for DummyParser {
type Impl = DummySelectorImpl;
type Error = SelectorParseErrorKind<'i>;
fn parse_slotted(&self) -> bool {
true
}
fn parse_nth_child_of(&self) -> bool {
true
}
fn parse_is_and_where(&self) -> bool {
true
}
fn parse_has(&self) -> bool {
true
}
fn parse_parent_selector(&self) -> bool {
true
}
fn parse_part(&self) -> bool {
true
}
fn parse_non_ts_pseudo_class(
&self,
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<PseudoClass, SelectorParseError<'i>> {
match_ignore_ascii_case! { &name,
"hover" => return Ok(PseudoClass::Hover),
"active" => return Ok(PseudoClass::Active),
_ => {}
}
Err(
location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
fn parse_non_ts_functional_pseudo_class<'t>(
&self,
name: CowRcStr<'i>,
parser: &mut CssParser<'i, 't>,
after_part: bool,
) -> Result<PseudoClass, SelectorParseError<'i>> {
match_ignore_ascii_case! { &name,
"lang" if !after_part => {
let lang = parser.expect_ident_or_string()?.as_ref().to_owned();
return Ok(PseudoClass::Lang(lang));
},
_ => {}
}
Err(
parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
fn parse_pseudo_element(
&self,
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<PseudoElement, SelectorParseError<'i>> {
match_ignore_ascii_case! { &name,
"before" => return Ok(PseudoElement::Before),
"after" => return Ok(PseudoElement::After),
_ => {}
}
Err(
location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
fn parse_functional_pseudo_element<'t>(
&self,
name: CowRcStr<'i>,
parser: &mut CssParser<'i, 't>,
) -> Result<PseudoElement, SelectorParseError<'i>> {
match_ignore_ascii_case! {&name,
"highlight" => return Ok(PseudoElement::Highlight(parser.expect_ident()?.as_ref().to_owned())),
_ => {}
}
Err(
parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
name,
)),
)
}
fn default_namespace(&self) -> Option<DummyAtom> {
self.default_ns.clone()
}
fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option<DummyAtom> {
self.ns_prefixes.get(prefix).cloned()
}
}
fn parse<'i>(
input: &'i str,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> {
parse_relative(input, ParseRelative::No)
}
fn parse_relative<'i>(
input: &'i str,
parse_relative: ParseRelative,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> {
parse_ns_relative(input, &DummyParser::default(), parse_relative)
}
fn parse_expected<'i, 'a>(
input: &'i str,
expected: Option<&'a str>,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> {
parse_ns_expected(input, &DummyParser::default(), expected)
}
fn parse_relative_expected<'i, 'a>(
input: &'i str,
parse_relative: ParseRelative,
expected: Option<&'a str>,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> {
parse_ns_relative_expected(input, &DummyParser::default(), parse_relative, expected)
}
fn parse_ns<'i>(
input: &'i str,
parser: &DummyParser,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> {
parse_ns_relative(input, parser, ParseRelative::No)
}
fn parse_ns_relative<'i>(
input: &'i str,
parser: &DummyParser,
parse_relative: ParseRelative,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> {
parse_ns_relative_expected(input, parser, parse_relative, None)
}
fn parse_ns_expected<'i, 'a>(
input: &'i str,
parser: &DummyParser,
expected: Option<&'a str>,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> {
parse_ns_relative_expected(input, parser, ParseRelative::No, expected)
}
fn parse_ns_relative_expected<'i, 'a>(
input: &'i str,
parser: &DummyParser,
parse_relative: ParseRelative,
expected: Option<&'a str>,
) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> {
let mut parser_input = ParserInput::new(input);
let result = SelectorList::parse(
parser,
&mut CssParser::new(&mut parser_input),
parse_relative,
);
if let 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() {
let mut input = ParserInput::new(":empty");
let list = SelectorList::parse(
&DummyParser::default(),
&mut CssParser::new(&mut input),
ParseRelative::No,
);
assert!(list.is_ok());
}
const MATHML: &str = "http://www.w3.org/1998/Math/MathML";
const SVG: &str = "http://www.w3.org/2000/svg";
#[test]
fn test_parsing() {
assert!(parse("").is_err());
assert!(parse(":lang(4)").is_err());
assert!(parse(":lang(en US)").is_err());
assert_eq!(
parse("EeÉ"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::LocalName(LocalName {
name: DummyAtom::from("EeÉ"),
lower_name: DummyAtom::from("eeÉ"),
})],
specificity(0, 0, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse("|e"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::ExplicitNoNamespace,
Component::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e"),
}),
],
specificity(0, 0, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
// When the default namespace is not set, *| should be elided.
assert_eq!(
parse_expected("*|e", Some("e")),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e"),
})],
specificity(0, 0, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
// When the default namespace is set, *| should _not_ be elided (as foo
// is no longer equivalent to *|foo--the former is only for foo in the
// default namespace).
assert_eq!(
parse_ns(
"*|e",
&DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org"))
),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::ExplicitAnyNamespace,
Component::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e"),
}),
],
specificity(0, 0, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse("*"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::ExplicitUniversalType],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse("|*"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::ExplicitNoNamespace,
Component::ExplicitUniversalType,
],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse_expected("*|*", Some("*")),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::ExplicitUniversalType],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse_ns(
"*|*",
&DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org"))
),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::ExplicitAnyNamespace,
Component::ExplicitUniversalType,
],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse(".foo:lang(en-US)"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::Class(DummyAtom::from("foo")),
Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())),
],
specificity(0, 2, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse("#bar"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::ID(DummyAtom::from("bar"))],
specificity(1, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse("e.foo#bar"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e"),
}),
Component::Class(DummyAtom::from("foo")),
Component::ID(DummyAtom::from("bar")),
],
specificity(1, 1, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse("e.foo #bar"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e"),
}),
Component::Class(DummyAtom::from("foo")),
Component::Combinator(Combinator::Descendant),
Component::ID(DummyAtom::from("bar")),
],
specificity(1, 1, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
// Default namespace does not apply to attribute selectors
let mut parser = DummyParser::default();
assert_eq!(
parse_ns("[Foo]", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::AttributeInNoNamespaceExists {
local_name: DummyAtom::from("Foo"),
local_name_lower: DummyAtom::from("foo"),
}],
specificity(0, 1, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert!(parse_ns("svg|circle", &parser).is_err());
parser
.ns_prefixes
.insert(DummyAtom("svg".into()), DummyAtom(SVG.into()));
assert_eq!(
parse_ns("svg|circle", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::Namespace(DummyAtom("svg".into()), SVG.into()),
Component::LocalName(LocalName {
name: DummyAtom::from("circle"),
lower_name: DummyAtom::from("circle"),
}),
],
specificity(0, 0, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse_ns("svg|*", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::Namespace(DummyAtom("svg".into()), SVG.into()),
Component::ExplicitUniversalType,
],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
// Default namespace does not apply to attribute selectors
// but it does apply to implicit type selectors
parser.default_ns = Some(MATHML.into());
assert_eq!(
parse_ns("[Foo]", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::AttributeInNoNamespaceExists {
local_name: DummyAtom::from("Foo"),
local_name_lower: DummyAtom::from("foo"),
},
],
specificity(0, 1, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
// Default namespace does apply to type selectors
assert_eq!(
parse_ns("e", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e"),
}),
],
specificity(0, 0, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse_ns("*", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::ExplicitUniversalType,
],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse_ns("*|*", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::ExplicitAnyNamespace,
Component::ExplicitUniversalType,
],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
// Default namespace applies to universal and type selectors inside :not and :matches,
// but not otherwise.
assert_eq!(
parse_ns(":not(.cl)", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::Negation(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::Class(DummyAtom::from("cl"))],
specificity(0, 1, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)])),
],
specificity(0, 1, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse_ns(":not(*)", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::Negation(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::ExplicitUniversalType,
],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]),),
],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse_ns(":not(e)", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::Negation(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(MATHML.into()),
Component::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e"),
}),
],
specificity(0, 0, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)])),
],
specificity(0, 0, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse("[attr|=\"foo\"]"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::AttributeInNoNamespace {
local_name: DummyAtom::from("attr"),
operator: AttrSelectorOperator::DashMatch,
value: DummyAttrValue::from("foo"),
case_sensitivity: ParsedCaseSensitivity::CaseSensitive,
}],
specificity(0, 1, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse("::before"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::Combinator(Combinator::PseudoElement),
Component::PseudoElement(PseudoElement::Before),
],
specificity(0, 0, 1),
SelectorFlags::HAS_PSEUDO,
)]))
);
assert_eq!(
parse("::before:hover"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::Combinator(Combinator::PseudoElement),
Component::PseudoElement(PseudoElement::Before),
Component::NonTSPseudoClass(PseudoClass::Hover),
],
specificity(0, 1, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT | SelectorFlags::HAS_PSEUDO,
)]))
);
assert_eq!(
parse("::before:hover:hover"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::Combinator(Combinator::PseudoElement),
Component::PseudoElement(PseudoElement::Before),
Component::NonTSPseudoClass(PseudoClass::Hover),
Component::NonTSPseudoClass(PseudoClass::Hover),
],
specificity(0, 2, 1),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT | SelectorFlags::HAS_PSEUDO,
)]))
);
assert!(parse("::before:hover:lang(foo)").is_err());
assert!(parse("::before:hover .foo").is_err());
assert!(parse("::before .foo").is_err());
assert!(parse("::before ~ bar").is_err());
assert!(parse("::before:active").is_ok());
assert!(parse(":: before").is_err());
assert_eq!(
parse("div ::after"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::LocalName(LocalName {
name: DummyAtom::from("div"),
lower_name: DummyAtom::from("div"),
}),
Component::Combinator(Combinator::Descendant),
Component::Combinator(Combinator::PseudoElement),
Component::PseudoElement(PseudoElement::After),
],
specificity(0, 0, 2),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT | SelectorFlags::HAS_PSEUDO,
)]))
);
assert_eq!(
parse("#d1 > .ok"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::ID(DummyAtom::from("d1")),
Component::Combinator(Combinator::Child),
Component::Class(DummyAtom::from("ok")),
],
(1 << 20) + (1 << 10) + (0 << 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
parser.default_ns = None;
assert!(parse(":not(#provel.old)").is_ok());
assert!(parse(":not(#provel > old)").is_ok());
assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok());
assert_eq!(
parse_ns(":not(*)", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::Negation(SelectorList::from_vec(vec![
Selector::from_vec(
vec![Component::ExplicitUniversalType],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)
]))],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert_eq!(
parse_ns(":not(|*)", &parser),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::Negation(SelectorList::from_vec(vec![
Selector::from_vec(
vec![
Component::ExplicitNoNamespace,
Component::ExplicitUniversalType,
],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)
]))],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
// *| should be elided if there is no default namespace.
assert_eq!(
parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::Negation(SelectorList::from_vec(vec![
Selector::from_vec(
vec![Component::ExplicitUniversalType],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)
]))],
specificity(0, 0, 0),
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
)]))
);
assert!(parse("::highlight(foo)").is_ok());
assert!(parse("::slotted()").is_err());
assert!(parse("::slotted(div)").is_ok());
assert!(parse("::slotted(div).foo").is_err());
assert!(parse("::slotted(div + bar)").is_err());
assert!(parse("::slotted(div) + foo").is_err());
assert!(parse("::part()").is_err());
assert!(parse("::part(42)").is_err());
assert!(parse("::part(foo bar)").is_ok());
assert!(parse("::part(foo):hover").is_ok());
assert!(parse("::part(foo) + bar").is_err());
assert!(parse("div ::slotted(div)").is_ok());
assert!(parse("div + slot::slotted(div)").is_ok());
assert!(parse("div + slot::slotted(div.foo)").is_ok());
assert!(parse("slot::slotted(div,foo)::first-line").is_err());
assert!(parse("::slotted(div)::before").is_ok());
assert!(parse("slot::slotted(div,foo)").is_err());
assert!(parse("foo:where()").is_ok());
assert!(parse("foo:where(div, foo, .bar baz)").is_ok());
assert!(parse("foo:where(::before)").is_ok());
}
#[test]
fn parent_selector() {
assert!(parse("foo &").is_ok());
assert_eq!(
parse("#foo &.bar"),
Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::ID(DummyAtom::from("foo")),
Component::Combinator(Combinator::Descendant),
Component::ParentSelector,
Component::Class(DummyAtom::from("bar")),
],
(1 << 20) + (1 << 10) + (0 << 0),
SelectorFlags::HAS_PARENT | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT
)]))
);
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()
);
let child =
parse_relative_expected("+ #foo", ParseRelative::ForNesting, Some("& + #foo")).unwrap();
assert_eq!(child, parse("& + #foo").unwrap());
}
#[test]
fn test_pseudo_iter() {
let list = parse("q::before").unwrap();
let selector = &list.slice()[0];
assert!(!selector.is_universal());
let mut iter = selector.iter();
assert_eq!(
iter.next(),
Some(&Component::PseudoElement(PseudoElement::Before))
);
assert_eq!(iter.next(), None);
let combinator = iter.next_sequence();
assert_eq!(combinator, Some(Combinator::PseudoElement));
assert!(matches!(iter.next(), Some(&Component::LocalName(..))));
assert_eq!(iter.next(), None);
assert_eq!(iter.next_sequence(), None);
}
#[test]
fn test_universal() {
let list = parse_ns(
"*|*::before",
&DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")),
)
.unwrap();
let selector = &list.slice()[0];
assert!(selector.is_universal());
}
#[test]
fn test_empty_pseudo_iter() {
let list = parse("::before").unwrap();
let selector = &list.slice()[0];
assert!(selector.is_universal());
let mut iter = selector.iter();
assert_eq!(
iter.next(),
Some(&Component::PseudoElement(PseudoElement::Before))
);
assert_eq!(iter.next(), None);
assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement));
assert_eq!(iter.next(), None);
assert_eq!(iter.next_sequence(), None);
}
struct TestVisitor {
seen: Vec<String>,
}
impl SelectorVisitor for TestVisitor {
type Impl = DummySelectorImpl;
fn visit_simple_selector(&mut self, s: &Component<DummySelectorImpl>) -> bool {
let mut dest = String::new();
s.to_css(&mut dest).unwrap();
self.seen.push(dest);
true
}
}
#[test]
fn visitor() {
let mut test_visitor = TestVisitor { seen: vec![] };
parse(":not(:hover) ~ label").unwrap().slice()[0].visit(&mut test_visitor);
assert!(test_visitor.seen.contains(&":hover".into()));
let mut test_visitor = TestVisitor { seen: vec![] };
parse("::before:hover").unwrap().slice()[0].visit(&mut test_visitor);
assert!(test_visitor.seen.contains(&":hover".into()));
}
}