use crate::compat::Feature;
use crate::error::{ParserError, PrinterError};
use crate::parser::ParserFlags;
use crate::printer::Printer;
use crate::properties::custom::TokenList;
use crate::rules::StyleContext;
use crate::stylesheet::{ParserOptions, PrinterOptions};
use crate::targets::{should_compile, Targets};
use crate::traits::{Parse, ParseWithOptions, ToCss};
use crate::values::ident::{CustomIdent, Ident};
use crate::values::string::CSSString;
use crate::vendor_prefix::VendorPrefix;
#[cfg(feature = "visitor")]
use crate::visitor::{Visit, VisitTypes, Visitor};
use crate::{macros::enum_property, values::string::CowArcStr};
use cssparser::*;
use parcel_selectors::parser::{NthType, SelectorParseErrorKind};
use parcel_selectors::{
attr::{AttrSelectorOperator, ParsedAttrSelectorOperation, ParsedCaseSensitivity},
parser::SelectorImpl,
};
use std::collections::HashSet;
use std::fmt;
#[cfg(feature = "serde")]
use crate::serialization::*;
mod private {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Selectors;
#[cfg(feature = "into_owned")]
impl<'any> ::static_self::IntoOwned<'any> for Selectors {
type Owned = Self;
fn into_owned(self) -> Self::Owned {
self
}
}
}
#[cfg(feature = "into_owned")]
fn _assert_into_owned() {
use static_self::IntoOwned;
fn _assert<'any, T: IntoOwned<'any>>() {}
_assert::<SelectorList>();
}
use private::Selectors;
pub type SelectorList<'i> = parcel_selectors::SelectorList<'i, Selectors>;
pub type Selector<'i> = parcel_selectors::parser::Selector<'i, Selectors>;
pub type Component<'i> = parcel_selectors::parser::Component<'i, Selectors>;
pub use parcel_selectors::parser::Combinator;
impl<'i> SelectorImpl<'i> for Selectors {
type AttrValue = CSSString<'i>;
type Identifier = Ident<'i>;
type LocalName = Ident<'i>;
type NamespacePrefix = Ident<'i>;
type NamespaceUrl = CowArcStr<'i>;
type BorrowedNamespaceUrl = CowArcStr<'i>;
type BorrowedLocalName = Ident<'i>;
type NonTSPseudoClass = PseudoClass<'i>;
type PseudoElement = PseudoElement<'i>;
type VendorPrefix = VendorPrefix;
type ExtraMatchingData = ();
fn to_css<W: fmt::Write>(selectors: &SelectorList<'i>, dest: &mut W) -> std::fmt::Result {
let mut printer = Printer::new(dest, PrinterOptions::default());
serialize_selector_list(selectors.0.iter(), &mut printer, None, false).map_err(|_| std::fmt::Error)
}
}
pub(crate) struct SelectorParser<'a, 'o, 'i> {
pub is_nesting_allowed: bool,
pub options: &'a ParserOptions<'o, 'i>,
}
impl<'a, 'o, 'i> parcel_selectors::parser::Parser<'i> for SelectorParser<'a, 'o, 'i> {
type Impl = Selectors;
type Error = ParserError<'i>;
fn parse_non_ts_pseudo_class(
&self,
loc: SourceLocation,
name: CowRcStr<'i>,
) -> Result<PseudoClass<'i>, ParseError<'i, Self::Error>> {
use PseudoClass::*;
let pseudo_class = match_ignore_ascii_case! { &name,
"hover" => Hover,
"active" => Active,
"focus" => Focus,
"focus-visible" => FocusVisible,
"focus-within" => FocusWithin,
"current" => Current,
"past" => Past,
"future" => Future,
"playing" => Playing,
"paused" => Paused,
"seeking" => Seeking,
"buffering" => Buffering,
"stalled" => Stalled,
"muted" => Muted,
"volume-locked" => VolumeLocked,
"fullscreen" => Fullscreen(VendorPrefix::None),
"-webkit-full-screen" => Fullscreen(VendorPrefix::WebKit),
"-moz-full-screen" => Fullscreen(VendorPrefix::Moz),
"-ms-fullscreen" => Fullscreen(VendorPrefix::Ms),
"open" => Open,
"closed" => Closed,
"modal" => Modal,
"picture-in-picture" => PictureInPicture,
"popover-open" => PopoverOpen,
"defined" => Defined,
"any-link" => AnyLink(VendorPrefix::None),
"-webkit-any-link" => AnyLink(VendorPrefix::WebKit),
"-moz-any-link" => AnyLink(VendorPrefix::Moz),
"link" => Link,
"local-link" => LocalLink,
"target" => Target,
"target-within" => TargetWithin,
"visited" => Visited,
"enabled" => Enabled,
"disabled" => Disabled,
"read-only" => ReadOnly(VendorPrefix::None),
"-moz-read-only" => ReadOnly(VendorPrefix::Moz),
"read-write" => ReadWrite(VendorPrefix::None),
"-moz-read-write" => ReadWrite(VendorPrefix::Moz),
"placeholder-shown" => PlaceholderShown(VendorPrefix::None),
"-moz-placeholder-shown" => PlaceholderShown(VendorPrefix::Moz),
"-ms-placeholder-shown" => PlaceholderShown(VendorPrefix::Ms),
"default" => Default,
"checked" => Checked,
"indeterminate" => Indeterminate,
"blank" => Blank,
"valid" => Valid,
"invalid" => Invalid,
"in-range" => InRange,
"out-of-range" => OutOfRange,
"required" => Required,
"optional" => Optional,
"user-valid" => UserValid,
"user-invalid" => UserInvalid,
"autofill" => Autofill(VendorPrefix::None),
"-webkit-autofill" => Autofill(VendorPrefix::WebKit),
"-o-autofill" => Autofill(VendorPrefix::O),
"horizontal" => WebKitScrollbar(WebKitScrollbarPseudoClass::Horizontal),
"vertical" => WebKitScrollbar(WebKitScrollbarPseudoClass::Vertical),
"decrement" => WebKitScrollbar(WebKitScrollbarPseudoClass::Decrement),
"increment" => WebKitScrollbar(WebKitScrollbarPseudoClass::Increment),
"start" => WebKitScrollbar(WebKitScrollbarPseudoClass::Start),
"end" => WebKitScrollbar(WebKitScrollbarPseudoClass::End),
"double-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::DoubleButton),
"single-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::SingleButton),
"no-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::NoButton),
"corner-present" => WebKitScrollbar(WebKitScrollbarPseudoClass::CornerPresent),
"window-inactive" => WebKitScrollbar(WebKitScrollbarPseudoClass::WindowInactive),
_ => {
if !name.starts_with('-') {
self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
}
Custom { name: name.into() }
}
};
Ok(pseudo_class)
}
fn parse_non_ts_functional_pseudo_class<'t>(
&self,
name: CowRcStr<'i>,
parser: &mut cssparser::Parser<'i, 't>,
) -> Result<PseudoClass<'i>, ParseError<'i, Self::Error>> {
use PseudoClass::*;
let pseudo_class = match_ignore_ascii_case! { &name,
"lang" => {
let languages = parser.parse_comma_separated(|parser| {
parser.expect_ident_or_string()
.map(|s| s.into())
.map_err(|e| e.into())
})?;
Lang { languages }
},
"dir" => Dir { direction: Direction::parse(parser)? },
"local" if self.options.css_modules.is_some() => Local { selector: Box::new(Selector::parse(self, parser)?) },
"global" if self.options.css_modules.is_some() => Global { selector: Box::new(Selector::parse(self, parser)?) },
_ => {
if !name.starts_with('-') {
self.options.warn(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
}
let mut args = Vec::new();
TokenList::parse_raw(parser, &mut args, &self.options, 0)?;
CustomFunction {
name: name.into(),
arguments: TokenList(args)
}
},
};
Ok(pseudo_class)
}
fn parse_any_prefix<'t>(&self, name: &str) -> Option<VendorPrefix> {
match_ignore_ascii_case! { &name,
"-webkit-any" => Some(VendorPrefix::WebKit),
"-moz-any" => Some(VendorPrefix::Moz),
_ => None
}
}
fn parse_pseudo_element(
&self,
loc: SourceLocation,
name: CowRcStr<'i>,
) -> Result<PseudoElement<'i>, ParseError<'i, Self::Error>> {
use PseudoElement::*;
let pseudo_element = match_ignore_ascii_case! { &name,
"before" => Before,
"after" => After,
"first-line" => FirstLine,
"first-letter" => FirstLetter,
"cue" => Cue,
"cue-region" => CueRegion,
"selection" => Selection(VendorPrefix::None),
"-moz-selection" => Selection(VendorPrefix::Moz),
"placeholder" => Placeholder(VendorPrefix::None),
"-webkit-input-placeholder" => Placeholder(VendorPrefix::WebKit),
"-moz-placeholder" => Placeholder(VendorPrefix::Moz),
"-ms-input-placeholder" => Placeholder(VendorPrefix::Moz),
"marker" => Marker,
"backdrop" => Backdrop(VendorPrefix::None),
"-webkit-backdrop" => Backdrop(VendorPrefix::WebKit),
"file-selector-button" => FileSelectorButton(VendorPrefix::None),
"-webkit-file-upload-button" => FileSelectorButton(VendorPrefix::WebKit),
"-ms-browse" => FileSelectorButton(VendorPrefix::Ms),
"-webkit-scrollbar" => WebKitScrollbar(WebKitScrollbarPseudoElement::Scrollbar),
"-webkit-scrollbar-button" => WebKitScrollbar(WebKitScrollbarPseudoElement::Button),
"-webkit-scrollbar-track" => WebKitScrollbar(WebKitScrollbarPseudoElement::Track),
"-webkit-scrollbar-track-piece" => WebKitScrollbar(WebKitScrollbarPseudoElement::TrackPiece),
"-webkit-scrollbar-thumb" => WebKitScrollbar(WebKitScrollbarPseudoElement::Thumb),
"-webkit-scrollbar-corner" => WebKitScrollbar(WebKitScrollbarPseudoElement::Corner),
"-webkit-resizer" => WebKitScrollbar(WebKitScrollbarPseudoElement::Resizer),
"view-transition" => ViewTransition,
_ => {
if !name.starts_with('-') {
self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
}
Custom { name: name.into() }
}
};
Ok(pseudo_element)
}
fn parse_functional_pseudo_element<'t>(
&self,
name: CowRcStr<'i>,
arguments: &mut Parser<'i, 't>,
) -> Result<<Self::Impl as SelectorImpl<'i>>::PseudoElement, ParseError<'i, Self::Error>> {
use PseudoElement::*;
let pseudo_element = match_ignore_ascii_case! { &name,
"cue" => CueFunction { selector: Box::new(Selector::parse(self, arguments)?) },
"cue-region" => CueRegionFunction { selector: Box::new(Selector::parse(self, arguments)?) },
"view-transition-group" => ViewTransitionGroup { part_name: ViewTransitionPartName::parse(arguments)? },
"view-transition-image-pair" => ViewTransitionImagePair { part_name: ViewTransitionPartName::parse(arguments)? },
"view-transition-old" => ViewTransitionOld { part_name: ViewTransitionPartName::parse(arguments)? },
"view-transition-new" => ViewTransitionNew { part_name: ViewTransitionPartName::parse(arguments)? },
_ => {
if !name.starts_with('-') {
self.options.warn(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
}
let mut args = Vec::new();
TokenList::parse_raw(arguments, &mut args, &self.options, 0)?;
CustomFunction { name: name.into(), arguments: TokenList(args) }
}
};
Ok(pseudo_element)
}
#[inline]
fn parse_slotted(&self) -> bool {
true
}
#[inline]
fn parse_host(&self) -> bool {
true
}
#[inline]
fn parse_is_and_where(&self) -> bool {
true
}
#[inline]
fn parse_part(&self) -> bool {
true
}
fn default_namespace(&self) -> Option<CowArcStr<'i>> {
None
}
fn namespace_for_prefix(&self, prefix: &Ident<'i>) -> Option<CowArcStr<'i>> {
Some(prefix.0.clone())
}
#[inline]
fn is_nesting_allowed(&self) -> bool {
self.is_nesting_allowed
}
fn deep_combinator_enabled(&self) -> bool {
self.options.flags.contains(ParserFlags::DEEP_SELECTOR_COMBINATOR)
}
}
enum_property! {
#[derive(Eq, Hash)]
pub enum Direction {
Ltr,
Rtl,
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "kind", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum PseudoClass<'i> {
Lang {
#[cfg_attr(feature = "serde", serde(borrow))]
languages: Vec<CowArcStr<'i>>,
},
Dir {
direction: Direction,
},
Hover,
Active,
Focus,
FocusVisible,
FocusWithin,
Current,
Past,
Future,
Playing,
Paused,
Seeking,
Buffering,
Stalled,
Muted,
VolumeLocked,
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
Fullscreen(VendorPrefix),
Open,
Closed,
Modal,
PictureInPicture,
PopoverOpen,
Defined,
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
AnyLink(VendorPrefix),
Link,
LocalLink,
Target,
TargetWithin,
Visited,
Enabled,
Disabled,
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
ReadOnly(VendorPrefix),
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
ReadWrite(VendorPrefix),
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
PlaceholderShown(VendorPrefix),
Default,
Checked,
Indeterminate,
Blank,
Valid,
Invalid,
InRange,
OutOfRange,
Required,
Optional,
UserValid,
UserInvalid,
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
Autofill(VendorPrefix),
Local {
selector: Box<Selector<'i>>,
},
Global {
selector: Box<Selector<'i>>,
},
#[cfg_attr(
feature = "serde",
serde(rename = "webkit-scrollbar", with = "ValueWrapper::<WebKitScrollbarPseudoClass>")
)]
WebKitScrollbar(WebKitScrollbarPseudoClass),
Custom {
name: CowArcStr<'i>,
},
CustomFunction {
name: CowArcStr<'i>,
arguments: TokenList<'i>,
},
}
#[derive(Clone, Eq, PartialEq, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum WebKitScrollbarPseudoClass {
Horizontal,
Vertical,
Decrement,
Increment,
Start,
End,
DoubleButton,
SingleButton,
NoButton,
CornerPresent,
WindowInactive,
}
impl<'i> parcel_selectors::parser::NonTSPseudoClass<'i> for PseudoClass<'i> {
type Impl = Selectors;
fn is_active_or_hover(&self) -> bool {
matches!(*self, PseudoClass::Active | PseudoClass::Hover)
}
fn is_user_action_state(&self) -> bool {
matches!(
*self,
PseudoClass::Active
| PseudoClass::Hover
| PseudoClass::Focus
| PseudoClass::FocusWithin
| PseudoClass::FocusVisible
)
}
fn is_valid_before_webkit_scrollbar(&self) -> bool {
!matches!(*self, PseudoClass::WebKitScrollbar(..))
}
fn is_valid_after_webkit_scrollbar(&self) -> bool {
matches!(
*self,
PseudoClass::WebKitScrollbar(..)
| PseudoClass::Enabled
| PseudoClass::Disabled
| PseudoClass::Hover
| PseudoClass::Active
)
}
}
impl<'i> cssparser::ToCss for PseudoClass<'i> {
fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result
where
W: fmt::Write,
{
let mut s = String::new();
serialize_pseudo_class(self, &mut Printer::new(&mut s, Default::default()), None)
.map_err(|_| std::fmt::Error)?;
write!(dest, "{}", s)
}
}
fn serialize_pseudo_class<'a, 'i, W>(
pseudo_class: &PseudoClass<'i>,
dest: &mut Printer<W>,
context: Option<&StyleContext>,
) -> Result<(), PrinterError>
where
W: fmt::Write,
{
use PseudoClass::*;
match pseudo_class {
Lang { languages: lang } => {
dest.write_str(":lang(")?;
let mut first = true;
for lang in lang {
if first {
first = false;
} else {
dest.delim(',', false)?;
}
serialize_identifier(lang, dest)?;
}
return dest.write_str(")");
}
Dir { direction: dir } => {
dest.write_str(":dir(")?;
dir.to_css(dest)?;
return dest.write_str(")");
}
_ => {}
}
macro_rules! write_prefixed {
($prefix: ident, $val: expr) => {{
dest.write_char(':')?;
let vp = if !dest.vendor_prefix.is_empty() {
(dest.vendor_prefix & *$prefix).or_none()
} else {
*$prefix
};
vp.to_css(dest)?;
dest.write_str($val)
}};
}
macro_rules! pseudo {
($key: ident, $s: literal) => {{
let class = if let Some(pseudo_classes) = &dest.pseudo_classes {
pseudo_classes.$key
} else {
None
};
if let Some(class) = class {
dest.write_char('.')?;
dest.write_ident(class, true)
} else {
dest.write_str($s)
}
}};
}
match pseudo_class {
Hover => pseudo!(hover, ":hover"),
Active => pseudo!(active, ":active"),
Focus => pseudo!(focus, ":focus"),
FocusVisible => pseudo!(focus_visible, ":focus-visible"),
FocusWithin => pseudo!(focus_within, ":focus-within"),
Current => dest.write_str(":current"),
Past => dest.write_str(":past"),
Future => dest.write_str(":future"),
Playing => dest.write_str(":playing"),
Paused => dest.write_str(":paused"),
Seeking => dest.write_str(":seeking"),
Buffering => dest.write_str(":buffering"),
Stalled => dest.write_str(":stalled"),
Muted => dest.write_str(":muted"),
VolumeLocked => dest.write_str(":volume-locked"),
Fullscreen(prefix) => {
dest.write_char(':')?;
let vp = if !dest.vendor_prefix.is_empty() {
(dest.vendor_prefix & *prefix).or_none()
} else {
*prefix
};
vp.to_css(dest)?;
if vp == VendorPrefix::WebKit || vp == VendorPrefix::Moz {
dest.write_str("full-screen")
} else {
dest.write_str("fullscreen")
}
}
Open => dest.write_str(":open"),
Closed => dest.write_str(":closed"),
Modal => dest.write_str(":modal"),
PictureInPicture => dest.write_str(":picture-in-picture"),
PopoverOpen => dest.write_str(":popover-open"),
Defined => dest.write_str(":defined"),
AnyLink(prefix) => write_prefixed!(prefix, "any-link"),
Link => dest.write_str(":link"),
LocalLink => dest.write_str(":local-link"),
Target => dest.write_str(":target"),
TargetWithin => dest.write_str(":target-within"),
Visited => dest.write_str(":visited"),
Enabled => dest.write_str(":enabled"),
Disabled => dest.write_str(":disabled"),
ReadOnly(prefix) => write_prefixed!(prefix, "read-only"),
ReadWrite(prefix) => write_prefixed!(prefix, "read-write"),
PlaceholderShown(prefix) => write_prefixed!(prefix, "placeholder-shown"),
Default => dest.write_str(":default"),
Checked => dest.write_str(":checked"),
Indeterminate => dest.write_str(":indeterminate"),
Blank => dest.write_str(":blank"),
Valid => dest.write_str(":valid"),
Invalid => dest.write_str(":invalid"),
InRange => dest.write_str(":in-range"),
OutOfRange => dest.write_str(":out-of-range"),
Required => dest.write_str(":required"),
Optional => dest.write_str(":optional"),
UserValid => dest.write_str(":user-valid"),
UserInvalid => dest.write_str(":user-invalid"),
Autofill(prefix) => write_prefixed!(prefix, "autofill"),
Local { selector } => serialize_selector(selector, dest, context, false),
Global { selector } => {
let css_module = std::mem::take(&mut dest.css_module);
serialize_selector(selector, dest, context, false)?;
dest.css_module = css_module;
Ok(())
}
WebKitScrollbar(s) => {
use WebKitScrollbarPseudoClass::*;
dest.write_str(match s {
Horizontal => ":horizontal",
Vertical => ":vertical",
Decrement => ":decrement",
Increment => ":increment",
Start => ":start",
End => ":end",
DoubleButton => ":double-button",
SingleButton => ":single-button",
NoButton => ":no-button",
CornerPresent => ":corner-present",
WindowInactive => ":window-inactive",
})
}
Lang { languages: _ } | Dir { direction: _ } => unreachable!(),
Custom { name } => {
dest.write_char(':')?;
return dest.write_str(&name);
}
CustomFunction { name, arguments: args } => {
dest.write_char(':')?;
dest.write_str(name)?;
dest.write_char('(')?;
args.to_css_raw(dest)?;
dest.write_char(')')
}
}
}
impl<'i> PseudoClass<'i> {
pub(crate) fn is_equivalent(&self, other: &PseudoClass<'i>) -> bool {
use PseudoClass::*;
match (self, other) {
(Fullscreen(_), Fullscreen(_))
| (AnyLink(_), AnyLink(_))
| (ReadOnly(_), ReadOnly(_))
| (ReadWrite(_), ReadWrite(_))
| (PlaceholderShown(_), PlaceholderShown(_))
| (Autofill(_), Autofill(_)) => true,
(a, b) => a == b,
}
}
pub(crate) fn get_prefix(&self) -> VendorPrefix {
use PseudoClass::*;
match self {
Fullscreen(p) | AnyLink(p) | ReadOnly(p) | ReadWrite(p) | PlaceholderShown(p) | Autofill(p) => *p,
_ => VendorPrefix::empty(),
}
}
pub(crate) fn get_necessary_prefixes(&mut self, targets: Targets) -> VendorPrefix {
use crate::prefixes::Feature;
use PseudoClass::*;
let (p, feature) = match self {
Fullscreen(p) => (p, Feature::PseudoClassFullscreen),
AnyLink(p) => (p, Feature::PseudoClassAnyLink),
ReadOnly(p) => (p, Feature::PseudoClassReadOnly),
ReadWrite(p) => (p, Feature::PseudoClassReadWrite),
PlaceholderShown(p) => (p, Feature::PseudoClassPlaceholderShown),
Autofill(p) => (p, Feature::PseudoClassAutofill),
_ => return VendorPrefix::empty(),
};
*p = targets.prefixes(*p, feature);
*p
}
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "kind", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum PseudoElement<'i> {
After,
Before,
FirstLine,
FirstLetter,
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
Selection(VendorPrefix),
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
Placeholder(VendorPrefix),
Marker,
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
Backdrop(VendorPrefix),
#[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
FileSelectorButton(VendorPrefix),
#[cfg_attr(
feature = "serde",
serde(rename = "webkit-scrollbar", with = "ValueWrapper::<WebKitScrollbarPseudoElement>")
)]
WebKitScrollbar(WebKitScrollbarPseudoElement),
Cue,
CueRegion,
CueFunction {
selector: Box<Selector<'i>>,
},
CueRegionFunction {
selector: Box<Selector<'i>>,
},
ViewTransition,
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
ViewTransitionGroup {
part_name: ViewTransitionPartName<'i>,
},
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
ViewTransitionImagePair {
part_name: ViewTransitionPartName<'i>,
},
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
ViewTransitionOld {
part_name: ViewTransitionPartName<'i>,
},
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
ViewTransitionNew {
part_name: ViewTransitionPartName<'i>,
},
Custom {
#[cfg_attr(feature = "serde", serde(borrow))]
name: CowArcStr<'i>,
},
CustomFunction {
name: CowArcStr<'i>,
arguments: TokenList<'i>,
},
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum WebKitScrollbarPseudoElement {
Scrollbar,
Button,
Track,
TrackPiece,
Thumb,
Corner,
Resizer,
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum ViewTransitionPartName<'i> {
All,
Name(CustomIdent<'i>),
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'i> serde::Serialize for ViewTransitionPartName<'i> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
ViewTransitionPartName::All => serializer.serialize_str("*"),
ViewTransitionPartName::Name(name) => serializer.serialize_str(&name.0),
}
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'i, 'de: 'i> serde::Deserialize<'de> for ViewTransitionPartName<'i> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = CowArcStr::deserialize(deserializer)?;
if s == "*" {
Ok(ViewTransitionPartName::All)
} else {
Ok(ViewTransitionPartName::Name(CustomIdent(s)))
}
}
}
#[cfg(feature = "jsonschema")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
impl<'a> schemars::JsonSchema for ViewTransitionPartName<'a> {
fn is_referenceable() -> bool {
true
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
str::json_schema(gen)
}
fn schema_name() -> String {
"ViewTransitionPartName".into()
}
}
impl<'i> Parse<'i> for ViewTransitionPartName<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if input.try_parse(|input| input.expect_delim('*')).is_ok() {
return Ok(ViewTransitionPartName::All);
}
Ok(ViewTransitionPartName::Name(CustomIdent::parse(input)?))
}
}
impl<'i> ToCss for ViewTransitionPartName<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
ViewTransitionPartName::All => dest.write_char('*'),
ViewTransitionPartName::Name(name) => name.to_css(dest),
}
}
}
impl<'i> cssparser::ToCss for PseudoElement<'i> {
fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result
where
W: fmt::Write,
{
let mut s = String::new();
serialize_pseudo_element(self, &mut Printer::new(&mut s, Default::default()), None)
.map_err(|_| std::fmt::Error)?;
write!(dest, "{}", s)
}
}
fn serialize_pseudo_element<'a, 'i, W>(
pseudo_element: &PseudoElement,
dest: &mut Printer<W>,
context: Option<&StyleContext>,
) -> Result<(), PrinterError>
where
W: fmt::Write,
{
use PseudoElement::*;
macro_rules! write_prefix {
($prefix: ident) => {{
dest.write_str("::")?;
let vp = if !dest.vendor_prefix.is_empty() {
(dest.vendor_prefix & *$prefix).or_none()
} else {
*$prefix
};
vp.to_css(dest)?;
vp
}};
}
macro_rules! write_prefixed {
($prefix: ident, $val: expr) => {{
write_prefix!($prefix);
dest.write_str($val)
}};
}
match pseudo_element {
After => dest.write_str(":after"),
Before => dest.write_str(":before"),
FirstLine => dest.write_str(":first-line"),
FirstLetter => dest.write_str(":first-letter"),
Marker => dest.write_str("::marker"),
Selection(prefix) => write_prefixed!(prefix, "selection"),
Cue => dest.write_str("::cue"),
CueRegion => dest.write_str("::cue-region"),
CueFunction { selector } => {
dest.write_str("::cue(")?;
serialize_selector(selector, dest, context, false)?;
dest.write_char(')')
}
CueRegionFunction { selector } => {
dest.write_str("::cue-region(")?;
serialize_selector(selector, dest, context, false)?;
dest.write_char(')')
}
Placeholder(prefix) => {
let vp = write_prefix!(prefix);
if vp == VendorPrefix::WebKit || vp == VendorPrefix::Ms {
dest.write_str("input-placeholder")
} else {
dest.write_str("placeholder")
}
}
Backdrop(prefix) => write_prefixed!(prefix, "backdrop"),
FileSelectorButton(prefix) => {
let vp = write_prefix!(prefix);
if vp == VendorPrefix::WebKit {
dest.write_str("file-upload-button")
} else if vp == VendorPrefix::Ms {
dest.write_str("browse")
} else {
dest.write_str("file-selector-button")
}
}
WebKitScrollbar(s) => {
use WebKitScrollbarPseudoElement::*;
dest.write_str(match s {
Scrollbar => "::-webkit-scrollbar",
Button => "::-webkit-scrollbar-button",
Track => "::-webkit-scrollbar-track",
TrackPiece => "::-webkit-scrollbar-track-piece",
Thumb => "::-webkit-scrollbar-thumb",
Corner => "::-webkit-scrollbar-corner",
Resizer => "::-webkit-resizer",
})
}
ViewTransition => dest.write_str("::view-transition"),
ViewTransitionGroup { part_name } => {
dest.write_str("::view-transition-group(")?;
part_name.to_css(dest)?;
dest.write_char(')')
}
ViewTransitionImagePair { part_name } => {
dest.write_str("::view-transition-image-pair(")?;
part_name.to_css(dest)?;
dest.write_char(')')
}
ViewTransitionOld { part_name } => {
dest.write_str("::view-transition-old(")?;
part_name.to_css(dest)?;
dest.write_char(')')
}
ViewTransitionNew { part_name } => {
dest.write_str("::view-transition-new(")?;
part_name.to_css(dest)?;
dest.write_char(')')
}
Custom { name: val } => {
dest.write_str("::")?;
return dest.write_str(val);
}
CustomFunction { name, arguments: args } => {
dest.write_str("::")?;
dest.write_str(name)?;
dest.write_char('(')?;
args.to_css_raw(dest)?;
dest.write_char(')')
}
}
}
impl<'i> parcel_selectors::parser::PseudoElement<'i> for PseudoElement<'i> {
type Impl = Selectors;
fn accepts_state_pseudo_classes(&self) -> bool {
true
}
fn valid_after_slotted(&self) -> bool {
matches!(
*self,
PseudoElement::Before
| PseudoElement::After
| PseudoElement::Marker
| PseudoElement::Placeholder(_)
| PseudoElement::FileSelectorButton(_)
)
}
fn is_webkit_scrollbar(&self) -> bool {
matches!(*self, PseudoElement::WebKitScrollbar(..))
}
fn is_view_transition(&self) -> bool {
matches!(
*self,
PseudoElement::ViewTransitionGroup { .. }
| PseudoElement::ViewTransitionImagePair { .. }
| PseudoElement::ViewTransitionNew { .. }
| PseudoElement::ViewTransitionOld { .. }
)
}
fn is_unknown(&self) -> bool {
matches!(
*self,
PseudoElement::Custom { .. } | PseudoElement::CustomFunction { .. },
)
}
}
impl<'i> PseudoElement<'i> {
pub(crate) fn is_equivalent(&self, other: &PseudoElement<'i>) -> bool {
use PseudoElement::*;
match (self, other) {
(Selection(_), Selection(_))
| (Placeholder(_), Placeholder(_))
| (Backdrop(_), Backdrop(_))
| (FileSelectorButton(_), FileSelectorButton(_)) => true,
(a, b) => a == b,
}
}
pub(crate) fn get_prefix(&self) -> VendorPrefix {
use PseudoElement::*;
match self {
Selection(p) | Placeholder(p) | Backdrop(p) | FileSelectorButton(p) => *p,
_ => VendorPrefix::empty(),
}
}
pub(crate) fn get_necessary_prefixes(&mut self, targets: Targets) -> VendorPrefix {
use crate::prefixes::Feature;
use PseudoElement::*;
let (p, feature) = match self {
Selection(p) => (p, Feature::PseudoElementSelection),
Placeholder(p) => (p, Feature::PseudoElementPlaceholder),
Backdrop(p) => (p, Feature::PseudoElementBackdrop),
FileSelectorButton(p) => (p, Feature::PseudoElementFileSelectorButton),
_ => return VendorPrefix::empty(),
};
*p = targets.prefixes(*p, feature);
*p
}
}
impl<'a, 'i> ToCss for SelectorList<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: fmt::Write,
{
serialize_selector_list(self.0.iter(), dest, dest.context(), false)
}
}
impl ToCss for Combinator {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: fmt::Write,
{
match *self {
Combinator::Child => dest.delim('>', true),
Combinator::Descendant => dest.write_str(" "),
Combinator::NextSibling => dest.delim('+', true),
Combinator::LaterSibling => dest.delim('~', true),
Combinator::Deep => dest.write_str(" /deep/ "),
Combinator::DeepDescendant => {
dest.whitespace()?;
dest.write_str(">>>")?;
dest.whitespace()
}
Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()),
}
}
}
impl<'a, 'i> ToCss for Selector<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: fmt::Write,
{
serialize_selector(self, dest, dest.context(), false)
}
}
fn serialize_selector<'a, 'i, W>(
selector: &Selector<'i>,
dest: &mut Printer<W>,
context: Option<&StyleContext>,
mut is_relative: bool,
) -> Result<(), PrinterError>
where
W: fmt::Write,
{
use parcel_selectors::parser::*;
let mut combinators = selector.iter_raw_match_order().rev().filter_map(|x| x.as_combinator());
let compound_selectors = selector.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev();
let should_compile_nesting = should_compile!(dest.targets, Nesting);
let mut first = true;
let mut combinators_exhausted = false;
for mut compound in compound_selectors {
debug_assert!(!combinators_exhausted);
if is_relative && matches!(compound.get(0), Some(Component::Scope)) {
if let Some(combinator) = combinators.next() {
combinator.to_css(dest)?;
}
compound = &compound[1..];
is_relative = false;
}
if compound.is_empty() {
continue;
}
let has_leading_nesting = first && matches!(compound[0], Component::Nesting);
let first_index = if has_leading_nesting { 1 } else { 0 };
first = false;
let (can_elide_namespace, first_non_namespace) = match compound.get(first_index) {
Some(Component::ExplicitAnyNamespace)
| Some(Component::ExplicitNoNamespace)
| Some(Component::Namespace(..)) => (false, first_index + 1),
Some(Component::DefaultNamespace(..)) => (true, first_index + 1),
_ => (true, first_index),
};
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]) {
(Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (),
(_, &Component::ExplicitUniversalType) => {
let mut iter = compound.iter();
let swap_nesting = has_leading_nesting && should_compile_nesting;
if swap_nesting {
iter.next();
}
for simple in iter {
serialize_component(simple, dest, context)?;
}
if swap_nesting {
serialize_nesting(dest, context, false)?;
}
perform_step_2 = false;
}
_ => (),
}
}
if perform_step_2 {
let mut iter = compound.iter();
if has_leading_nesting && should_compile_nesting && is_type_selector(compound.get(first_non_namespace)) {
let nesting = iter.next().unwrap();
let local = iter.next().unwrap();
serialize_component(local, dest, context)?;
if first_non_namespace > first_index {
let local = iter.next().unwrap();
serialize_component(local, dest, context)?;
}
serialize_component(nesting, dest, context)?;
} else if has_leading_nesting && should_compile_nesting {
iter.next();
serialize_nesting(dest, context, true)?;
}
for simple in iter {
if let Component::ExplicitUniversalType = *simple {
if can_elide_namespace {
continue;
}
}
serialize_component(simple, dest, context)?;
}
}
match next_combinator {
Some(c) => c.to_css(dest)?,
None => combinators_exhausted = true,
};
}
Ok(())
}
fn serialize_component<'a, 'i, W>(
component: &Component,
dest: &mut Printer<W>,
context: Option<&StyleContext>,
) -> Result<(), PrinterError>
where
W: fmt::Write,
{
match component {
Component::Combinator(ref c) => c.to_css(dest),
Component::AttributeInNoNamespace {
ref local_name,
operator,
ref value,
case_sensitivity,
..
} => {
dest.write_char('[')?;
cssparser::ToCss::to_css(local_name, dest)?;
cssparser::ToCss::to_css(operator, dest)?;
if dest.minify {
let mut id = String::new();
serialize_identifier(&value, &mut id)?;
let s = value.to_css_string(Default::default())?;
if id.len() > 0 && id.len() < s.len() {
dest.write_str(&id)?;
} else {
dest.write_str(&s)?;
}
} else {
value.to_css(dest)?;
}
match case_sensitivity {
parcel_selectors::attr::ParsedCaseSensitivity::CaseSensitive
| parcel_selectors::attr::ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}
parcel_selectors::attr::ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
parcel_selectors::attr::ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?,
}
dest.write_char(']')
}
Component::Is(ref list)
| Component::Where(ref list)
| Component::Negation(ref list)
| Component::Any(_, ref list) => {
match *component {
Component::Where(..) => dest.write_str(":where(")?,
Component::Is(ref selectors) => {
if should_unwrap_is(selectors) {
serialize_selector(selectors.first().unwrap(), dest, context, false)?;
return Ok(());
}
let vp = dest.vendor_prefix;
if vp.intersects(VendorPrefix::WebKit | VendorPrefix::Moz) {
dest.write_char(':')?;
vp.to_css(dest)?;
dest.write_str("any(")?;
} else {
dest.write_str(":is(")?;
}
}
Component::Negation(_) => {
dest.write_str(":not(")?;
}
Component::Any(prefix, ..) => {
let vp = dest.vendor_prefix.or(prefix);
if vp.intersects(VendorPrefix::WebKit | VendorPrefix::Moz) {
dest.write_char(':')?;
vp.to_css(dest)?;
dest.write_str("any(")?;
} else {
dest.write_str(":is(")?;
}
}
_ => unreachable!(),
}
serialize_selector_list(list.iter(), dest, context, false)?;
dest.write_str(")")
}
Component::Has(ref list) => {
dest.write_str(":has(")?;
serialize_selector_list(list.iter(), dest, context, true)?;
dest.write_str(")")
}
Component::NonTSPseudoClass(pseudo) => serialize_pseudo_class(pseudo, dest, context),
Component::PseudoElement(pseudo) => serialize_pseudo_element(pseudo, dest, context),
Component::Nesting => serialize_nesting(dest, context, false),
Component::Class(ref class) => {
dest.write_char('.')?;
dest.write_ident(&class.0, true)
}
Component::ID(ref id) => {
dest.write_char('#')?;
dest.write_ident(&id.0, true)
}
Component::Host(selector) => {
dest.write_str(":host")?;
if let Some(ref selector) = *selector {
dest.write_char('(')?;
selector.to_css(dest)?;
dest.write_char(')')?;
}
Ok(())
}
Component::Slotted(ref selector) => {
dest.write_str("::slotted(")?;
selector.to_css(dest)?;
dest.write_char(')')
}
_ => {
cssparser::ToCss::to_css(component, dest)?;
Ok(())
}
}
}
fn should_unwrap_is<'i>(selectors: &Box<[Selector<'i>]>) -> bool {
if selectors.len() == 1 {
let first = selectors.first().unwrap();
if !has_type_selector(first) && is_simple(first) {
return true;
}
}
false
}
fn serialize_nesting<W>(
dest: &mut Printer<W>,
context: Option<&StyleContext>,
first: bool,
) -> Result<(), PrinterError>
where
W: fmt::Write,
{
if let Some(ctx) = context {
if ctx.selectors.0.len() == 1
&& (first || (!has_type_selector(&ctx.selectors.0[0]) && is_simple(&ctx.selectors.0[0])))
{
serialize_selector(ctx.selectors.0.first().unwrap(), dest, ctx.parent, false)
} else {
dest.write_str(":is(")?;
serialize_selector_list(ctx.selectors.0.iter(), dest, ctx.parent, false)?;
dest.write_char(')')
}
} else {
if should_compile!(dest.targets, Nesting) {
dest.write_str(":scope")
} else {
dest.write_char('&')
}
}
}
#[inline]
fn has_type_selector(selector: &Selector) -> bool {
let mut iter = selector.iter_raw_parse_order_from(0);
let first = iter.next();
if is_namespace(first) {
is_type_selector(iter.next())
} else {
is_type_selector(first)
}
}
#[inline]
fn is_simple(selector: &Selector) -> bool {
!selector.iter_raw_match_order().any(|component| component.is_combinator())
}
#[inline]
fn is_type_selector(component: Option<&Component>) -> bool {
matches!(
component,
Some(Component::LocalName(_)) | Some(Component::ExplicitUniversalType)
)
}
#[inline]
fn is_namespace(component: Option<&Component>) -> bool {
matches!(
component,
Some(Component::ExplicitAnyNamespace)
| Some(Component::ExplicitNoNamespace)
| Some(Component::Namespace(..))
| Some(Component::DefaultNamespace(_))
)
}
fn serialize_selector_list<'a, 'i: 'a, I, W>(
iter: I,
dest: &mut Printer<W>,
context: Option<&StyleContext>,
is_relative: bool,
) -> Result<(), PrinterError>
where
I: Iterator<Item = &'a Selector<'i>>,
W: fmt::Write,
{
let mut first = true;
for selector in iter {
if !first {
dest.delim(',', false)?;
}
first = false;
serialize_selector(selector, dest, context, is_relative)?;
}
Ok(())
}
pub(crate) fn is_compatible(selectors: &[Selector], targets: Targets) -> bool {
for selector in selectors {
let iter = selector.iter_raw_match_order();
for component in iter {
let feature = match component {
Component::ID(_) | Component::Class(_) | Component::LocalName(_) => continue,
Component::ExplicitAnyNamespace
| Component::ExplicitNoNamespace
| Component::DefaultNamespace(_)
| Component::Namespace(_, _) => Feature::Namespaces,
Component::ExplicitUniversalType => Feature::Selectors2,
Component::AttributeInNoNamespaceExists { .. } => Feature::Selectors2,
Component::AttributeInNoNamespace {
operator,
case_sensitivity,
..
} => {
if *case_sensitivity != ParsedCaseSensitivity::CaseSensitive {
Feature::CaseInsensitive
} else {
match operator {
AttrSelectorOperator::Equal | AttrSelectorOperator::Includes | AttrSelectorOperator::DashMatch => {
Feature::Selectors2
}
AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {
Feature::Selectors3
}
}
}
}
Component::AttributeOther(attr) => match attr.operation {
ParsedAttrSelectorOperation::Exists => Feature::Selectors2,
ParsedAttrSelectorOperation::WithValue {
operator,
case_sensitivity,
..
} => {
if case_sensitivity != ParsedCaseSensitivity::CaseSensitive {
Feature::CaseInsensitive
} else {
match operator {
AttrSelectorOperator::Equal | AttrSelectorOperator::Includes | AttrSelectorOperator::DashMatch => {
Feature::Selectors2
}
AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {
Feature::Selectors3
}
}
}
}
},
Component::Empty | Component::Root => Feature::Selectors3,
Component::Negation(selectors) => {
if !targets.is_compatible(Feature::Selectors3) || !is_compatible(&*selectors, targets) {
return false;
}
continue;
}
Component::Nth(data) => match data.ty {
NthType::Child if data.a == 0 && data.b == 1 => Feature::Selectors2,
NthType::Col | NthType::LastCol => return false,
_ => Feature::Selectors3,
},
Component::NthOf(n) => {
if !targets.is_compatible(Feature::NthChildOf) || !is_compatible(n.selectors(), targets) {
return false;
}
continue;
}
Component::Is(selectors) => {
if should_unwrap_is(selectors) && is_compatible(selectors, targets) {
continue;
}
Feature::IsSelector
}
Component::Where(_) | Component::Nesting => Feature::IsSelector,
Component::Any(..) => return false,
Component::Has(selectors) => {
if !targets.is_compatible(Feature::HasSelector) || !is_compatible(&*selectors, targets) {
return false;
}
continue;
}
Component::Scope | Component::Host(_) | Component::Slotted(_) => Feature::Shadowdomv1,
Component::Part(_) => Feature::PartPseudo,
Component::NonTSPseudoClass(pseudo) => {
match pseudo {
PseudoClass::Link
| PseudoClass::Visited
| PseudoClass::Active
| PseudoClass::Hover
| PseudoClass::Focus
| PseudoClass::Lang { languages: _ } => Feature::Selectors2,
PseudoClass::Checked | PseudoClass::Disabled | PseudoClass::Enabled | PseudoClass::Target => {
Feature::Selectors3
}
PseudoClass::AnyLink(prefix) if *prefix == VendorPrefix::None => Feature::AnyLink,
PseudoClass::Indeterminate => Feature::IndeterminatePseudo,
PseudoClass::Fullscreen(prefix) if *prefix == VendorPrefix::None => Feature::Fullscreen,
PseudoClass::FocusVisible => Feature::FocusVisible,
PseudoClass::FocusWithin => Feature::FocusWithin,
PseudoClass::Default => Feature::DefaultPseudo,
PseudoClass::Dir { direction: _ } => Feature::DirSelector,
PseudoClass::Optional => Feature::OptionalPseudo,
PseudoClass::PlaceholderShown(prefix) if *prefix == VendorPrefix::None => Feature::PlaceholderShown,
PseudoClass::ReadOnly(prefix) | PseudoClass::ReadWrite(prefix) if *prefix == VendorPrefix::None => {
Feature::ReadOnlyWrite
}
PseudoClass::Valid | PseudoClass::Invalid | PseudoClass::Required => Feature::FormValidation,
PseudoClass::InRange | PseudoClass::OutOfRange => Feature::InOutOfRange,
PseudoClass::Autofill(prefix) if *prefix == VendorPrefix::None => Feature::Autofill,
PseudoClass::Current
| PseudoClass::Past
| PseudoClass::Future
| PseudoClass::Playing
| PseudoClass::Paused
| PseudoClass::Seeking
| PseudoClass::Stalled
| PseudoClass::Buffering
| PseudoClass::Muted
| PseudoClass::VolumeLocked
| PseudoClass::TargetWithin
| PseudoClass::LocalLink
| PseudoClass::Blank
| PseudoClass::UserInvalid
| PseudoClass::UserValid
| PseudoClass::Defined => return false,
PseudoClass::Custom { .. } | _ => return false,
}
}
Component::PseudoElement(pseudo) => match pseudo {
PseudoElement::After | PseudoElement::Before => Feature::Gencontent,
PseudoElement::FirstLine => Feature::FirstLine,
PseudoElement::FirstLetter => Feature::FirstLetter,
PseudoElement::Selection(prefix) if *prefix == VendorPrefix::None => Feature::Selection,
PseudoElement::Placeholder(prefix) if *prefix == VendorPrefix::None => Feature::Placeholder,
PseudoElement::Marker => Feature::MarkerPseudo,
PseudoElement::Backdrop(prefix) if *prefix == VendorPrefix::None => Feature::Dialog,
PseudoElement::Cue => Feature::Cue,
PseudoElement::CueFunction { selector: _ } => Feature::CueFunction,
PseudoElement::Custom { name: _ } | _ => return false,
},
Component::Combinator(combinator) => match combinator {
Combinator::Child | Combinator::NextSibling => Feature::Selectors2,
Combinator::LaterSibling => Feature::Selectors3,
_ => continue,
},
};
if !targets.is_compatible(feature) {
return false;
}
}
}
true
}
pub(crate) fn is_equivalent<'i>(selectors: &[Selector<'i>], other: &[Selector<'i>]) -> bool {
if selectors.len() != other.len() {
return false;
}
for (i, a) in selectors.iter().enumerate() {
let b = &other[i];
if a.len() != b.len() {
return false;
}
for (a, b) in a.iter_raw_match_order().zip(b.iter_raw_match_order()) {
let is_equivalent = match (a, b) {
(Component::NonTSPseudoClass(a_ps), Component::NonTSPseudoClass(b_ps)) => a_ps.is_equivalent(b_ps),
(Component::PseudoElement(a_pe), Component::PseudoElement(b_pe)) => a_pe.is_equivalent(b_pe),
(Component::Any(_, a), Component::Is(b))
| (Component::Is(a), Component::Any(_, b))
| (Component::Any(_, a), Component::Any(_, b))
| (Component::Is(a), Component::Is(b)) => is_equivalent(&*a, &*b),
(a, b) => a == b,
};
if !is_equivalent {
return false;
}
}
}
true
}
pub(crate) fn get_prefix(selectors: &SelectorList) -> VendorPrefix {
let mut prefix = VendorPrefix::empty();
for selector in &selectors.0 {
for component in selector.iter_raw_match_order() {
let p = match component {
Component::NonTSPseudoClass(PseudoClass::Lang { .. })
| Component::NonTSPseudoClass(PseudoClass::Dir { .. })
| Component::Is(..)
| Component::Where(..)
| Component::Has(..)
| Component::Negation(..) => VendorPrefix::None,
Component::Any(prefix, _) => *prefix,
Component::NonTSPseudoClass(pc) => pc.get_prefix(),
Component::PseudoElement(pe) => pe.get_prefix(),
_ => VendorPrefix::empty(),
};
if !p.is_empty() {
let prefix_without_none = prefix - VendorPrefix::None;
if prefix_without_none.is_empty() || prefix_without_none == p {
prefix |= p;
} else {
return VendorPrefix::empty();
}
}
}
}
prefix
}
const RTL_LANGS: &[&str] = &[
"ae", "ar", "arc", "bcc", "bqi", "ckb", "dv", "fa", "glk", "he", "ku", "mzn", "nqo", "pnb", "ps", "sd", "ug",
"ur", "yi",
];
pub(crate) fn downlevel_selectors(selectors: &mut [Selector], targets: Targets) -> VendorPrefix {
let mut necessary_prefixes = VendorPrefix::empty();
for selector in selectors {
for component in selector.iter_mut_raw_match_order() {
necessary_prefixes |= downlevel_component(component, targets);
}
}
necessary_prefixes
}
fn downlevel_component<'i>(component: &mut Component<'i>, targets: Targets) -> VendorPrefix {
match component {
Component::NonTSPseudoClass(pc) => {
match pc {
PseudoClass::Dir { direction: dir } => {
if should_compile!(targets, DirSelector) {
*component = downlevel_dir(*dir, targets);
downlevel_component(component, targets)
} else {
VendorPrefix::empty()
}
}
PseudoClass::Lang { languages: langs } => {
if langs.len() > 1 && should_compile!(targets, LangSelectorList) {
*component = Component::Is(lang_list_to_selectors(&langs));
downlevel_component(component, targets)
} else {
VendorPrefix::empty()
}
}
_ => pc.get_necessary_prefixes(targets),
}
}
Component::PseudoElement(pe) => pe.get_necessary_prefixes(targets),
Component::Is(selectors) => {
let mut necessary_prefixes = downlevel_selectors(&mut **selectors, targets);
if should_compile!(targets, IsSelector)
&& !should_unwrap_is(selectors)
&& selectors.iter().all(|selector| !selector.has_combinator())
{
necessary_prefixes |= targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AnyPseudo)
} else {
necessary_prefixes |= VendorPrefix::None
}
necessary_prefixes
}
Component::Negation(selectors) => {
let mut necessary_prefixes = downlevel_selectors(&mut **selectors, targets);
if selectors.len() > 1 && should_compile!(targets, NotSelectorList) {
*component =
Component::Negation(vec![Selector::from(Component::Is(selectors.clone()))].into_boxed_slice());
if should_compile!(targets, IsSelector) {
necessary_prefixes |= targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AnyPseudo)
} else {
necessary_prefixes |= VendorPrefix::None
}
}
necessary_prefixes
}
Component::Where(selectors) | Component::Any(_, selectors) | Component::Has(selectors) => {
downlevel_selectors(&mut **selectors, targets)
}
_ => VendorPrefix::empty(),
}
}
fn lang_list_to_selectors<'i>(langs: &Vec<CowArcStr<'i>>) -> Box<[Selector<'i>]> {
langs
.iter()
.map(|lang| {
Selector::from(Component::NonTSPseudoClass(PseudoClass::Lang {
languages: vec![lang.clone()],
}))
})
.collect::<Vec<Selector>>()
.into_boxed_slice()
}
fn downlevel_dir<'i>(dir: Direction, targets: Targets) -> Component<'i> {
let langs = RTL_LANGS.iter().map(|lang| (*lang).into()).collect();
if !should_compile!(targets, LangSelectorList) {
let c = Component::NonTSPseudoClass(PseudoClass::Lang { languages: langs });
if dir == Direction::Ltr {
Component::Negation(vec![Selector::from(c)].into_boxed_slice())
} else {
c
}
} else {
if dir == Direction::Ltr {
Component::Negation(lang_list_to_selectors(&langs))
} else {
Component::Is(lang_list_to_selectors(&langs))
}
}
}
pub(crate) fn is_unused(
selectors: &mut std::slice::Iter<Selector>,
unused_symbols: &HashSet<String>,
parent_is_unused: bool,
) -> bool {
if unused_symbols.is_empty() {
return false;
}
selectors.all(|selector| {
for component in selector.iter_raw_match_order() {
match component {
Component::Class(name) | Component::ID(name) => {
if unused_symbols.contains(&name.0.to_string()) {
return true;
}
}
Component::Is(is) | Component::Where(is) | Component::Any(_, is) => {
if is_unused(&mut is.iter(), unused_symbols, parent_is_unused) {
return true;
}
}
Component::Nesting => {
if parent_is_unused {
return true;
}
}
_ => {}
}
}
false
})
}
#[cfg(feature = "visitor")]
#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for SelectorList<'i> {
const CHILD_TYPES: VisitTypes = VisitTypes::SELECTORS;
fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
if visitor.visit_types().contains(VisitTypes::SELECTORS) {
visitor.visit_selector_list(self)
} else {
self.visit_children(visitor)
}
}
fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {
self.0.iter_mut().try_for_each(|selector| Visit::visit(selector, visitor))
}
}
#[cfg(feature = "visitor")]
#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for Selector<'i> {
const CHILD_TYPES: VisitTypes = VisitTypes::SELECTORS;
fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
visitor.visit_selector(self)
}
fn visit_children(&mut self, _visitor: &mut V) -> Result<(), V::Error> {
Ok(())
}
}
impl<'i> ParseWithOptions<'i> for Selector<'i> {
fn parse_with_options<'t>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
Selector::parse(
&SelectorParser {
is_nesting_allowed: true,
options: &options,
},
input,
)
}
}
impl<'i> ParseWithOptions<'i> for SelectorList<'i> {
fn parse_with_options<'t>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
SelectorList::parse(
&SelectorParser {
is_nesting_allowed: true,
options: &options,
},
input,
parcel_selectors::parser::ParseErrorRecovery::DiscardList,
parcel_selectors::parser::NestingRequirement::None,
)
}
}