//! CSS selectors. use crate::compat::Feature; use crate::error::{ParserError, PrinterError, SelectorError}; 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 smallvec::SmallVec; 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() { // Ensure that we provide into_owned use static_self::IntoOwned; fn _assert<'any, T: IntoOwned<'any>>() {} _assert::(); } use private::Selectors; /// A list of selectors. pub type SelectorList<'i> = parcel_selectors::SelectorList<'i, Selectors>; /// A CSS selector, including a list of components. pub type Selector<'i> = parcel_selectors::parser::Selector<'i, Selectors>; /// An individual component within a selector. pub type Component<'i> = parcel_selectors::parser::Component<'i, Selectors>; /// A combinator. 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(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, ParseError<'i, Self::Error>> { use PseudoClass::*; let pseudo_class = match_ignore_ascii_case! { &name, // https://drafts.csswg.org/selectors-4/#useraction-pseudos "hover" => Hover, "active" => Active, "focus" => Focus, "focus-visible" => FocusVisible, "focus-within" => FocusWithin, // https://drafts.csswg.org/selectors-4/#time-pseudos "current" => Current, "past" => Past, "future" => Future, // https://drafts.csswg.org/selectors-4/#resource-pseudos "playing" => Playing, "paused" => Paused, "seeking" => Seeking, "buffering" => Buffering, "stalled" => Stalled, "muted" => Muted, "volume-locked" => VolumeLocked, // https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class "fullscreen" => Fullscreen(VendorPrefix::None), "-webkit-full-screen" => Fullscreen(VendorPrefix::WebKit), "-moz-full-screen" => Fullscreen(VendorPrefix::Moz), "-ms-fullscreen" => Fullscreen(VendorPrefix::Ms), // https://drafts.csswg.org/selectors/#display-state-pseudos "open" => Open, "closed" => Closed, "modal" => Modal, "picture-in-picture" => PictureInPicture, // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open "popover-open" => PopoverOpen, // https://drafts.csswg.org/selectors-4/#the-defined-pseudo "defined" => Defined, // https://drafts.csswg.org/selectors-4/#location "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, // https://drafts.csswg.org/selectors-4/#input-pseudos "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" => PlaceholderShown(VendorPrefix::Moz), "-ms-input-placeholder" => 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, // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill "autofill" => Autofill(VendorPrefix::None), "-webkit-autofill" => Autofill(VendorPrefix::WebKit), "-o-autofill" => Autofill(VendorPrefix::O), // https://drafts.csswg.org/css-view-transitions-2/#pseudo-classes-for-selective-vt "active-view-transition" => ActiveViewTransition, // https://webkit.org/blog/363/styling-scrollbars/ "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), "local" | "global" if self.options.css_modules.is_some() => { return Err(loc.new_custom_error(SelectorParseErrorKind::AmbiguousCssModuleClass(name.clone()))) }, _ => { if !name.starts_with('-') { self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(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, 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)? }, // https://drafts.csswg.org/css-view-transitions-2/#the-active-view-transition-type-pseudo "active-view-transition-type" => { let kind = Parse::parse(parser)?; ActiveViewTransitionType { kind } }, "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::UnsupportedPseudoClass(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 { 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, ParseError<'i, Self::Error>> { use PseudoElement::*; let pseudo_element = match_ignore_ascii_case! { &name, "before" => Before, "after" => After, "first-line" => FirstLine, "first-letter" => FirstLetter, "details-content" => DetailsContent, "target-text" => TargetText, "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), "picker-icon" => PickerIcon, "checkmark" => Checkmark, "view-transition" => ViewTransition, _ => { if !name.starts_with('-') { self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(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<>::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)?) }, "picker" => PickerFunction { identifier: Ident::parse(arguments)? }, "view-transition-group" => ViewTransitionGroup { part: ViewTransitionPartSelector::parse(arguments)? }, "view-transition-image-pair" => ViewTransitionImagePair { part: ViewTransitionPartSelector::parse(arguments)? }, "view-transition-old" => ViewTransitionOld { part: ViewTransitionPartSelector::parse(arguments)? }, "view-transition-new" => ViewTransitionNew { part: ViewTransitionPartSelector::parse(arguments)? }, _ => { if !name.starts_with('-') { self.options.warn(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(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> { None } fn namespace_for_prefix(&self, prefix: &Ident<'i>) -> Option> { 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! { /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class. #[derive(Eq, Hash)] pub enum Direction { /// Left to right Ltr, /// Right to left Rtl, } } /// A pseudo class. #[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> { // https://drafts.csswg.org/selectors-4/#linguistic-pseudos /// The [:lang()](https://drafts.csswg.org/selectors-4/#the-lang-pseudo) pseudo class. Lang { /// A list of language codes. #[cfg_attr(feature = "serde", serde(borrow))] languages: Vec>, }, /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class. Dir { /// A direction. direction: Direction, }, // https://drafts.csswg.org/selectors-4/#useraction-pseudos /// The [:hover](https://drafts.csswg.org/selectors-4/#the-hover-pseudo) pseudo class. Hover, /// The [:active](https://drafts.csswg.org/selectors-4/#the-active-pseudo) pseudo class. Active, /// The [:focus](https://drafts.csswg.org/selectors-4/#the-focus-pseudo) pseudo class. Focus, /// The [:focus-visible](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo) pseudo class. FocusVisible, /// The [:focus-within](https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo) pseudo class. FocusWithin, // https://drafts.csswg.org/selectors-4/#time-pseudos /// The [:current](https://drafts.csswg.org/selectors-4/#the-current-pseudo) pseudo class. Current, /// The [:past](https://drafts.csswg.org/selectors-4/#the-past-pseudo) pseudo class. Past, /// The [:future](https://drafts.csswg.org/selectors-4/#the-future-pseudo) pseudo class. Future, // https://drafts.csswg.org/selectors-4/#resource-pseudos /// The [:playing](https://drafts.csswg.org/selectors-4/#selectordef-playing) pseudo class. Playing, /// The [:paused](https://drafts.csswg.org/selectors-4/#selectordef-paused) pseudo class. Paused, /// The [:seeking](https://drafts.csswg.org/selectors-4/#selectordef-seeking) pseudo class. Seeking, /// The [:buffering](https://drafts.csswg.org/selectors-4/#selectordef-buffering) pseudo class. Buffering, /// The [:stalled](https://drafts.csswg.org/selectors-4/#selectordef-stalled) pseudo class. Stalled, /// The [:muted](https://drafts.csswg.org/selectors-4/#selectordef-muted) pseudo class. Muted, /// The [:volume-locked](https://drafts.csswg.org/selectors-4/#selectordef-volume-locked) pseudo class. VolumeLocked, /// The [:fullscreen](https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class) pseudo class. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] Fullscreen(VendorPrefix), // https://drafts.csswg.org/selectors/#display-state-pseudos /// The [:open](https://drafts.csswg.org/selectors/#selectordef-open) pseudo class. Open, /// The [:closed](https://drafts.csswg.org/selectors/#selectordef-closed) pseudo class. Closed, /// The [:modal](https://drafts.csswg.org/selectors/#modal-state) pseudo class. Modal, /// The [:picture-in-picture](https://drafts.csswg.org/selectors/#pip-state) pseudo class. PictureInPicture, // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open /// The [:popover-open](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open) pseudo class. PopoverOpen, /// The [:defined](https://drafts.csswg.org/selectors-4/#the-defined-pseudo) pseudo class. Defined, // https://drafts.csswg.org/selectors-4/#location /// The [:any-link](https://drafts.csswg.org/selectors-4/#the-any-link-pseudo) pseudo class. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] AnyLink(VendorPrefix), /// The [:link](https://drafts.csswg.org/selectors-4/#link-pseudo) pseudo class. Link, /// The [:local-link](https://drafts.csswg.org/selectors-4/#the-local-link-pseudo) pseudo class. LocalLink, /// The [:target](https://drafts.csswg.org/selectors-4/#the-target-pseudo) pseudo class. Target, /// The [:target-within](https://drafts.csswg.org/selectors-4/#the-target-within-pseudo) pseudo class. TargetWithin, /// The [:visited](https://drafts.csswg.org/selectors-4/#visited-pseudo) pseudo class. Visited, // https://drafts.csswg.org/selectors-4/#input-pseudos /// The [:enabled](https://drafts.csswg.org/selectors-4/#enabled-pseudo) pseudo class. Enabled, /// The [:disabled](https://drafts.csswg.org/selectors-4/#disabled-pseudo) pseudo class. Disabled, /// The [:read-only](https://drafts.csswg.org/selectors-4/#read-only-pseudo) pseudo class. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] ReadOnly(VendorPrefix), /// The [:read-write](https://drafts.csswg.org/selectors-4/#read-write-pseudo) pseudo class. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] ReadWrite(VendorPrefix), /// The [:placeholder-shown](https://drafts.csswg.org/selectors-4/#placeholder) pseudo class. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] PlaceholderShown(VendorPrefix), /// The [:default](https://drafts.csswg.org/selectors-4/#the-default-pseudo) pseudo class. Default, /// The [:checked](https://drafts.csswg.org/selectors-4/#checked) pseudo class. Checked, /// The [:indeterminate](https://drafts.csswg.org/selectors-4/#indeterminate) pseudo class. Indeterminate, /// The [:blank](https://drafts.csswg.org/selectors-4/#blank) pseudo class. Blank, /// The [:valid](https://drafts.csswg.org/selectors-4/#valid-pseudo) pseudo class. Valid, /// The [:invalid](https://drafts.csswg.org/selectors-4/#invalid-pseudo) pseudo class. Invalid, /// The [:in-range](https://drafts.csswg.org/selectors-4/#in-range-pseudo) pseudo class. InRange, /// The [:out-of-range](https://drafts.csswg.org/selectors-4/#out-of-range-pseudo) pseudo class. OutOfRange, /// The [:required](https://drafts.csswg.org/selectors-4/#required-pseudo) pseudo class. Required, /// The [:optional](https://drafts.csswg.org/selectors-4/#optional-pseudo) pseudo class. Optional, /// The [:user-valid](https://drafts.csswg.org/selectors-4/#user-valid-pseudo) pseudo class. UserValid, /// The [:used-invalid](https://drafts.csswg.org/selectors-4/#user-invalid-pseudo) pseudo class. UserInvalid, /// The [:autofill](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill) pseudo class. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] Autofill(VendorPrefix), /// The [:active-view-transition](https://drafts.csswg.org/css-view-transitions-2/#the-active-view-transition-pseudo) pseudo class. ActiveViewTransition, /// The [:active-view-transition-type()](https://drafts.csswg.org/css-view-transitions-2/#the-active-view-transition-type-pseudo) pseudo class. ActiveViewTransitionType { /// A view transition type. #[cfg_attr(feature = "serde", serde(rename = "type"))] kind: SmallVec<[CustomIdent<'i>; 1]>, }, // CSS modules /// The CSS modules :local() pseudo class. Local { /// A local selector. selector: Box>, }, /// The CSS modules :global() pseudo class. Global { /// A global selector. selector: Box>, }, /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class. // https://webkit.org/blog/363/styling-scrollbars/ #[cfg_attr( feature = "serde", serde(rename = "webkit-scrollbar", with = "ValueWrapper::") )] WebKitScrollbar(WebKitScrollbarPseudoClass), /// An unknown pseudo class. Custom { /// The pseudo class name. name: CowArcStr<'i>, }, /// An unknown functional pseudo class. CustomFunction { /// The pseudo class name. name: CowArcStr<'i>, /// The arguments of the pseudo class function. arguments: TokenList<'i>, }, } /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class. #[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 Horizontal, /// :vertical Vertical, /// :decrement Decrement, /// :increment Increment, /// :start Start, /// :end End, /// :double-button DoubleButton, /// :single-button SingleButton, /// :no-button NoButton, /// :corner-present CornerPresent, /// :window-inactive 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 { // https://github.com/WebKit/WebKit/blob/02fbf9b7aa435edb96cbf563a8d4dcf1aa73b4b3/Source/WebCore/css/parser/CSSSelectorParser.cpp#L285 matches!( *self, PseudoClass::WebKitScrollbar(..) | PseudoClass::Enabled | PseudoClass::Disabled | PseudoClass::Hover | PseudoClass::Active ) } } impl<'i> cssparser::ToCss for PseudoClass<'i> { fn to_css(&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, 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(':')?; // If the printer has a vendor prefix override, use that. 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 { // https://drafts.csswg.org/selectors-4/#useraction-pseudos Hover => pseudo!(hover, ":hover"), Active => pseudo!(active, ":active"), Focus => pseudo!(focus, ":focus"), FocusVisible => pseudo!(focus_visible, ":focus-visible"), FocusWithin => pseudo!(focus_within, ":focus-within"), // https://drafts.csswg.org/selectors-4/#time-pseudos Current => dest.write_str(":current"), Past => dest.write_str(":past"), Future => dest.write_str(":future"), // https://drafts.csswg.org/selectors-4/#resource-pseudos 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"), // https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class 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") } } // https://drafts.csswg.org/selectors/#display-state-pseudos Open => dest.write_str(":open"), Closed => dest.write_str(":closed"), Modal => dest.write_str(":modal"), PictureInPicture => dest.write_str(":picture-in-picture"), // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open PopoverOpen => dest.write_str(":popover-open"), // https://drafts.csswg.org/selectors-4/#the-defined-pseudo Defined => dest.write_str(":defined"), // https://drafts.csswg.org/selectors-4/#location 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"), // https://drafts.csswg.org/selectors-4/#input-pseudos 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"), // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill Autofill(prefix) => write_prefixed!(prefix, "autofill"), ActiveViewTransition => dest.write_str(":active-view-transition"), ActiveViewTransitionType { kind } => { dest.write_str(":active-view-transition-type(")?; kind.to_css(dest)?; dest.write_char(')') } 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(()) } // https://webkit.org/blog/363/styling-scrollbars/ 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 } } /// A pseudo element. #[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> { /// The [::after](https://drafts.csswg.org/css-pseudo-4/#selectordef-after) pseudo element. After, /// The [::before](https://drafts.csswg.org/css-pseudo-4/#selectordef-before) pseudo element. Before, /// The [::first-line](https://drafts.csswg.org/css-pseudo-4/#first-line-pseudo) pseudo element. FirstLine, /// The [::first-letter](https://drafts.csswg.org/css-pseudo-4/#first-letter-pseudo) pseudo element. FirstLetter, /// The [::details-content](https://drafts.csswg.org/css-pseudo-4/#details-content-pseudo) DetailsContent, /// The [::target-text](https://drafts.csswg.org/css-pseudo-4/#selectordef-target-text) TargetText, /// The [::selection](https://drafts.csswg.org/css-pseudo-4/#selectordef-selection) pseudo element. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] Selection(VendorPrefix), /// The [::placeholder](https://drafts.csswg.org/css-pseudo-4/#placeholder-pseudo) pseudo element. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] Placeholder(VendorPrefix), /// The [::marker](https://drafts.csswg.org/css-pseudo-4/#marker-pseudo) pseudo element. Marker, /// The [::backdrop](https://fullscreen.spec.whatwg.org/#::backdrop-pseudo-element) pseudo element. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] Backdrop(VendorPrefix), /// The [::file-selector-button](https://drafts.csswg.org/css-pseudo-4/#file-selector-button-pseudo) pseudo element. #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))] FileSelectorButton(VendorPrefix), /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element. #[cfg_attr( feature = "serde", serde(rename = "webkit-scrollbar", with = "ValueWrapper::") )] WebKitScrollbar(WebKitScrollbarPseudoElement), /// The [::cue](https://w3c.github.io/webvtt/#the-cue-pseudo-element) pseudo element. Cue, /// The [::cue-region](https://w3c.github.io/webvtt/#the-cue-region-pseudo-element) pseudo element. CueRegion, /// The [::cue()](https://w3c.github.io/webvtt/#cue-selector) functional pseudo element. CueFunction { /// The selector argument. selector: Box>, }, /// The [::cue-region()](https://w3c.github.io/webvtt/#cue-region-selector) functional pseudo element. CueRegionFunction { /// The selector argument. selector: Box>, }, /// The [::view-transition](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition) pseudo element. ViewTransition, /// The [::view-transition-group()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-group-pt-name-selector) functional pseudo element. #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] ViewTransitionGroup { /// A part name selector. part: ViewTransitionPartSelector<'i>, }, /// The [::view-transition-image-pair()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-image-pair-pt-name-selector) functional pseudo element. #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] ViewTransitionImagePair { /// A part name selector. part: ViewTransitionPartSelector<'i>, }, /// The [::view-transition-old()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-old-pt-name-selector) functional pseudo element. #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] ViewTransitionOld { /// A part name selector. part: ViewTransitionPartSelector<'i>, }, /// The [::view-transition-new()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-new-pt-name-selector) functional pseudo element. #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] ViewTransitionNew { /// A part name selector. part: ViewTransitionPartSelector<'i>, }, /// The [::picker()](https://drafts.csswg.org/css-forms-1/#the-picker-pseudo-element) functional pseudo element. PickerFunction { /// A form control identifier. identifier: Ident<'i>, }, /// The [::picker-icon](https://drafts.csswg.org/css-forms-1/#picker-opener-icon-the-picker-icon-pseudo-element) pseudo element. PickerIcon, /// The [::checkmark](https://drafts.csswg.org/css-forms-1/#styling-checkmarks-the-checkmark-pseudo-element) pseudo element. Checkmark, /// An unknown pseudo element. Custom { /// The name of the pseudo element. #[cfg_attr(feature = "serde", serde(borrow))] name: CowArcStr<'i>, }, /// An unknown functional pseudo element. CustomFunction { ///The name of the pseudo element. name: CowArcStr<'i>, /// The arguments of the pseudo element function. arguments: TokenList<'i>, }, } /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element. #[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 { /// ::-webkit-scrollbar Scrollbar, /// ::-webkit-scrollbar-button Button, /// ::-webkit-scrollbar-track Track, /// ::-webkit-scrollbar-track-piece TrackPiece, /// ::-webkit-scrollbar-thumb Thumb, /// ::-webkit-scrollbar-corner Corner, /// ::-webkit-resizer Resizer, } /// A [view transition part name](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#typedef-pt-name-selector). #[derive(PartialEq, Eq, Clone, Debug, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum ViewTransitionPartName<'i> { /// * #[cfg_attr(feature = "serde", serde(rename = "*"))] All, /// #[cfg_attr(feature = "serde", serde(borrow, untagged))] Name(CustomIdent<'i>), } #[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>> { 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(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { ViewTransitionPartName::All => dest.write_char('*'), ViewTransitionPartName::Name(name) => name.to_css(dest), } } } /// A [view transition part selector](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#typedef-pt-name-selector). #[derive(PartialEq, Eq, Clone, Debug, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub struct ViewTransitionPartSelector<'i> { /// The view transition part name. #[cfg_attr(feature = "serde", serde(borrow))] name: Option>, /// A list of view transition classes. classes: Vec>, } impl<'i> Parse<'i> for ViewTransitionPartSelector<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { input.skip_whitespace(); let name = input.try_parse(ViewTransitionPartName::parse).ok(); let mut classes = Vec::new(); while let Ok(token) = input.next_including_whitespace() { if matches!(token, Token::Delim('.')) { match input.next_including_whitespace() { Ok(Token::Ident(id)) => classes.push(CustomIdent(id.into())), _ => return Err(input.new_custom_error(ParserError::SelectorError(SelectorError::InvalidState))), } } else { return Err(input.new_custom_error(ParserError::SelectorError(SelectorError::InvalidState))); } } Ok(ViewTransitionPartSelector { name, classes }) } } impl<'i> ToCss for ViewTransitionPartSelector<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { if let Some(name) = &self.name { name.to_css(dest)?; } for class in &self.classes { dest.write_char('.')?; class.to_css(dest)?; } Ok(()) } } impl<'i> cssparser::ToCss for PseudoElement<'i> { fn to_css(&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, context: Option<&StyleContext>, ) -> Result<(), PrinterError> where W: fmt::Write, { use PseudoElement::*; macro_rules! write_prefix { ($prefix: ident) => {{ dest.write_str("::")?; // If the printer has a vendor prefix override, use that. 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 { // CSS2 pseudo elements support a single colon syntax in addition // to the more correct double colon for other pseudo elements. // We use that here because it's supported everywhere and is shorter. After => dest.write_str(":after"), Before => dest.write_str(":before"), FirstLine => dest.write_str(":first-line"), FirstLetter => dest.write_str(":first-letter"), DetailsContent => dest.write_str("::details-content"), TargetText => dest.write_str("::target-text"), 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 } => { dest.write_str("::view-transition-group(")?; part.to_css(dest)?; dest.write_char(')') } ViewTransitionImagePair { part } => { dest.write_str("::view-transition-image-pair(")?; part.to_css(dest)?; dest.write_char(')') } ViewTransitionOld { part } => { dest.write_str("::view-transition-old(")?; part.to_css(dest)?; dest.write_char(')') } ViewTransitionNew { part } => { dest.write_str("::view-transition-new(")?; part.to_css(dest)?; dest.write_char(')') } PickerFunction { identifier } => { dest.write_str("::picker(")?; identifier.to_css(dest)?; dest.write_char(')') } PickerIcon => dest.write_str("::picker-icon"), Checkmark => dest.write_str("::checkmark"), 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 { // Be lenient. true } fn valid_after_slotted(&self) -> bool { // ::slotted() should support all tree-abiding pseudo-elements, see // https://drafts.csswg.org/css-scoping/#slotted-pseudo // https://drafts.csswg.org/css-pseudo-4/#treelike 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(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: fmt::Write, { serialize_selector_list(self.0.iter(), dest, dest.context(), false) } } impl ToCss for Combinator { fn to_css(&self, dest: &mut Printer) -> 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(()), } } } // Copied from the selectors crate and modified to override to_css implementation. impl<'a, 'i> ToCss for Selector<'i> { fn to_css(&self, dest: &mut Printer) -> 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, context: Option<&StyleContext>, mut is_relative: bool, ) -> Result<(), PrinterError> where W: fmt::Write, { use parcel_selectors::parser::*; // 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 = 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.current, Nesting); let mut first = true; let mut combinators_exhausted = false; for mut compound in compound_selectors { debug_assert!(!combinators_exhausted); // Skip implicit :scope in relative selectors (e.g. :has(:scope > foo) -> :has(> foo)) 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; } // https://drafts.csswg.org/cssom/#serializing-selectors 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; // 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.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]) { // 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. let mut iter = compound.iter(); let swap_nesting = has_leading_nesting && should_compile_nesting; if swap_nesting { // Swap nesting and type selector (e.g. &div -> div&). iter.next(); } for simple in iter { serialize_component(simple, dest, context)?; } if swap_nesting { serialize_nesting(dest, context, false)?; } // Skip step 2, which is an "otherwise". perform_step_2 = false; } _ => (), } } // 2. Otherwise, for each simple selector in the compound selectors // that is not a universal selector of which the namespace prefix // maps to a namespace that is not the default namespace // serialize the simple selector and append the result to s. // // See https://github.com/w3c/csswg-drafts/issues/1606, which is // proposing to change this to match up with the behavior asserted // in cssom/serialize-namespaced-type-selectors.html, which the // following code tries to match. if perform_step_2 { let mut iter = compound.iter(); if has_leading_nesting && should_compile_nesting && is_type_selector(compound.get(first_non_namespace)) { // Swap nesting and type selector (e.g. &div -> div&). // This ensures that the compiled selector is valid. e.g. (div.foo is valid, .foodiv is not). let nesting = iter.next().unwrap(); let local = iter.next().unwrap(); serialize_component(local, dest, context)?; // Also check the next item in case of namespaces. 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 { // Nesting selector may serialize differently if it is leading, due to type selectors. iter.next(); serialize_nesting(dest, context, true)?; } for simple in 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; } } serialize_component(simple, dest, context)?; } } // 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(()) } fn serialize_component<'a, 'i, W>( component: &Component, dest: &mut Printer, 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 { // Serialize as both an identifier and a string and choose the shorter one. 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 there's only one simple selector, serialize it directly. 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(')') } Component::NthOf(ref nth_of_data) => { let nth_data = nth_of_data.nth_data(); nth_data.write_start(dest, true)?; nth_data.write_affine(dest)?; dest.write_str(" of ")?; serialize_selector_list(nth_of_data.selectors().iter(), dest, context, true)?; 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( dest: &mut Printer, context: Option<&StyleContext>, first: bool, ) -> Result<(), PrinterError> where W: fmt::Write, { if let Some(ctx) = context { // If there's only one simple selector, just serialize it directly. // Otherwise, use an :is() pseudo class. // Type selectors are only allowed at the start of a compound selector, // so use :is() if that is not the case. 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 there is no context, we are at the root if nesting is supported. This is equivalent to :scope. // Otherwise, if nesting is supported, serialize the nesting selector directly. if should_compile!(dest.targets.current, Nesting) { dest.write_str(":scope") } else { dest.write_char('&') } } } #[inline] fn has_type_selector(selector: &Selector) -> bool { // For input:checked the component vector is // [input, :checked] so we have to check it using matching order. // // This both happens for input:checked and is(input:checked) let mut iter = selector.iter_raw_match_order(); 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, context: Option<&StyleContext>, is_relative: bool, ) -> Result<(), PrinterError> where I: Iterator>, 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) => { // :not() selector list is not forgiving. 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; } // These support forgiving selector lists, so no need to check nested selectors. Component::Is(selectors) => { // ... except if we are going to unwrap them. 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, // Experimental, no browser support. 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 | PseudoClass::ActiveViewTransition | PseudoClass::ActiveViewTransitionType { .. } => 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::DetailsContent => Feature::DetailsContent, PseudoElement::TargetText => Feature::TargetText, 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::ViewTransition | PseudoElement::ViewTransitionNew { .. } | PseudoElement::ViewTransitionOld { .. } | PseudoElement::ViewTransitionGroup { .. } | PseudoElement::ViewTransitionImagePair { .. } => Feature::ViewTransition, PseudoElement::PickerFunction { identifier: _ } => Feature::Picker, PseudoElement::PickerIcon => Feature::PickerIcon, PseudoElement::Checkmark => Feature::Checkmark, 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 } /// Returns whether two selector lists are equivalent, i.e. the same minus any vendor prefix differences. 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 } /// Returns the vendor prefix (if any) used in the given selector list. /// If multiple vendor prefixes are seen, this is invalid, and an empty result is returned. 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 { // Return none rather than empty for these so that we call downlevel_selectors. 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() { // Allow none to be mixed with a prefix. 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", ]; /// Downlevels the given selectors to be compatible with the given browser targets. /// Returns the necessary vendor prefixes. 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 } => { // :lang() with multiple languages is not supported everywhere. // compile this to :is(:lang(a), :lang(b)) etc. 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); // Convert :is to :-webkit-any/:-moz-any if needed. // All selectors must be simple, no combinators are supported. 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); // Downlevel :not(.a, .b) -> :not(:is(.a, .b)) if not list is unsupported. // We need to use :is() / :-webkit-any() rather than :not(.a):not(.b) to ensure the specificity is equivalent. // https://drafts.csswg.org/selectors/#specificity-rules 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>) -> Box<[Selector<'i>]> { langs .iter() .map(|lang| { Selector::from(Component::NonTSPseudoClass(PseudoClass::Lang { languages: vec![lang.clone()], })) }) .collect::>() .into_boxed_slice() } fn downlevel_dir<'i>(dir: Direction, targets: Targets) -> Component<'i> { // Convert :dir to :lang. If supported, use a list of languages in a single :lang, // otherwise, use :is/:not, which may be further downleveled to e.g. :-webkit-any. 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)) } } } /// Determines whether a selector list contains only unused selectors. /// A selector is considered unused if it contains a class or id component that exists in the set of unused symbols. pub(crate) fn is_unused( selectors: &mut std::slice::Iter, unused_symbols: &HashSet, 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 }) } /// Returns whether the selector has any class or id components. pub(crate) fn is_pure_css_modules_selector(selector: &Selector) -> bool { use parcel_selectors::parser::Component; selector.iter_raw_match_order().any(|c| match c { Component::Class(_) | Component::ID(_) => true, Component::Is(s) | Component::Where(s) | Component::Has(s) | Component::Any(_, s) | Component::Negation(s) => { s.iter().any(is_pure_css_modules_selector) } Component::NthOf(nth) => nth.selectors().iter().any(is_pure_css_modules_selector), Component::Slotted(s) => is_pure_css_modules_selector(&s), Component::Host(s) => s.as_ref().map(is_pure_css_modules_selector).unwrap_or(false), Component::NonTSPseudoClass(pc) => match pc { PseudoClass::Local { selector } => is_pure_css_modules_selector(&*selector), _ => false, }, _ => 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>> { 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>> { SelectorList::parse( &SelectorParser { is_nesting_allowed: true, options: &options, }, input, parcel_selectors::parser::ParseErrorRecovery::DiscardList, parcel_selectors::parser::NestingRequirement::None, ) } }