1use crate::compat::Feature;
4use crate::error::{ParserError, PrinterError, SelectorError};
5use crate::parser::ParserFlags;
6use crate::printer::Printer;
7use crate::properties::custom::TokenList;
8use crate::rules::StyleContext;
9use crate::stylesheet::{ParserOptions, PrinterOptions};
10use crate::targets::{should_compile, Targets};
11use crate::traits::{Parse, ParseWithOptions, ToCss};
12use crate::values::ident::{CustomIdent, Ident};
13use crate::values::string::CSSString;
14use crate::vendor_prefix::VendorPrefix;
15#[cfg(feature = "visitor")]
16use crate::visitor::{Visit, VisitTypes, Visitor};
17use crate::{macros::enum_property, values::string::CowArcStr};
18use cssparser::*;
19use parcel_selectors::parser::{NthType, SelectorParseErrorKind};
20use parcel_selectors::{
21 attr::{AttrSelectorOperator, ParsedAttrSelectorOperation, ParsedCaseSensitivity},
22 parser::SelectorImpl,
23};
24use smallvec::SmallVec;
25use std::collections::HashSet;
26use std::fmt;
27
28#[cfg(feature = "serde")]
29use crate::serialization::*;
30
31mod private {
32 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
33 #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
34 pub struct Selectors;
35
36 #[cfg(feature = "into_owned")]
37 impl<'any> ::static_self::IntoOwned<'any> for Selectors {
38 type Owned = Self;
39
40 fn into_owned(self) -> Self::Owned {
41 self
42 }
43 }
44}
45
46#[cfg(feature = "into_owned")]
47fn _assert_into_owned() {
48 use static_self::IntoOwned;
51 fn _assert<'any, T: IntoOwned<'any>>() {}
52
53 _assert::<SelectorList>();
54}
55
56use private::Selectors;
57
58pub type SelectorList<'i> = parcel_selectors::SelectorList<'i, Selectors>;
60pub type Selector<'i> = parcel_selectors::parser::Selector<'i, Selectors>;
62pub type Component<'i> = parcel_selectors::parser::Component<'i, Selectors>;
64pub use parcel_selectors::parser::Combinator;
66
67impl<'i> SelectorImpl<'i> for Selectors {
68 type AttrValue = CSSString<'i>;
69 type Identifier = Ident<'i>;
70 type LocalName = Ident<'i>;
71 type NamespacePrefix = Ident<'i>;
72 type NamespaceUrl = CowArcStr<'i>;
73 type BorrowedNamespaceUrl = CowArcStr<'i>;
74 type BorrowedLocalName = Ident<'i>;
75
76 type NonTSPseudoClass = PseudoClass<'i>;
77 type PseudoElement = PseudoElement<'i>;
78 type VendorPrefix = VendorPrefix;
79
80 type ExtraMatchingData = ();
81
82 fn to_css<W: fmt::Write>(selectors: &SelectorList<'i>, dest: &mut W) -> std::fmt::Result {
83 let mut printer = Printer::new(dest, PrinterOptions::default());
84 serialize_selector_list(selectors.0.iter(), &mut printer, None, false).map_err(|_| std::fmt::Error)
85 }
86}
87
88pub(crate) struct SelectorParser<'a, 'i> {
89 pub is_nesting_allowed: bool,
90 pub options: &'a ParserOptions<'i>,
91}
92
93impl<'a, 'i> parcel_selectors::parser::Parser<'i> for SelectorParser<'a, 'i> {
94 type Impl = Selectors;
95 type Error = ParserError<'i>;
96
97 fn parse_non_ts_pseudo_class(
98 &self,
99 loc: SourceLocation,
100 name: CowRcStr<'i>,
101 ) -> Result<PseudoClass<'i>, ParseError<'i, Self::Error>> {
102 use PseudoClass::*;
103 let scroll_navigation_controls = self.options.flags.contains(ParserFlags::SCROLL_NAVIGATION_CONTROLS);
104 let pseudo_class = match_ignore_ascii_case! { &name,
105 "hover" => Hover,
107 "active" => Active,
108 "focus" => Focus,
109 "focus-visible" => FocusVisible,
110 "focus-within" => FocusWithin,
111
112 "current" => Current,
114 "past" => Past,
115 "future" => Future,
116
117 "playing" => Playing,
119 "paused" => Paused,
120 "seeking" => Seeking,
121 "buffering" => Buffering,
122 "stalled" => Stalled,
123 "muted" => Muted,
124 "volume-locked" => VolumeLocked,
125
126 "fullscreen" => Fullscreen(VendorPrefix::None),
128 "-webkit-full-screen" => Fullscreen(VendorPrefix::WebKit),
129 "-moz-full-screen" => Fullscreen(VendorPrefix::Moz),
130 "-ms-fullscreen" => Fullscreen(VendorPrefix::Ms),
131
132 "open" => Open,
134 "closed" => Closed,
135 "modal" => Modal,
136 "picture-in-picture" => PictureInPicture,
137
138 "popover-open" => PopoverOpen,
140
141 "defined" => Defined,
143
144 "any-link" => AnyLink(VendorPrefix::None),
146 "-webkit-any-link" => AnyLink(VendorPrefix::WebKit),
147 "-moz-any-link" => AnyLink(VendorPrefix::Moz),
148 "link" => Link,
149 "local-link" => LocalLink,
150 "target" => Target,
151 "target-within" => TargetWithin,
152
153 "target-current" if scroll_navigation_controls => TargetCurrent,
155 "target-before" if scroll_navigation_controls => TargetBefore,
156 "target-after" if scroll_navigation_controls => TargetAfter,
157
158 "visited" => Visited,
159
160 "enabled" => Enabled,
162 "disabled" => Disabled,
163 "read-only" => ReadOnly(VendorPrefix::None),
164 "-moz-read-only" => ReadOnly(VendorPrefix::Moz),
165 "read-write" => ReadWrite(VendorPrefix::None),
166 "-moz-read-write" => ReadWrite(VendorPrefix::Moz),
167 "placeholder-shown" => PlaceholderShown(VendorPrefix::None),
168 "-moz-placeholder" => PlaceholderShown(VendorPrefix::Moz),
169 "-ms-input-placeholder" => PlaceholderShown(VendorPrefix::Ms),
170 "default" => Default,
171 "checked" => Checked,
172 "indeterminate" => Indeterminate,
173 "blank" => Blank,
174 "valid" => Valid,
175 "invalid" => Invalid,
176 "in-range" => InRange,
177 "out-of-range" => OutOfRange,
178 "required" => Required,
179 "optional" => Optional,
180 "user-valid" => UserValid,
181 "user-invalid" => UserInvalid,
182
183 "autofill" => Autofill(VendorPrefix::None),
185 "-webkit-autofill" => Autofill(VendorPrefix::WebKit),
186 "-o-autofill" => Autofill(VendorPrefix::O),
187
188 "active-view-transition" => ActiveViewTransition,
190
191 "horizontal" => WebKitScrollbar(WebKitScrollbarPseudoClass::Horizontal),
193 "vertical" => WebKitScrollbar(WebKitScrollbarPseudoClass::Vertical),
194 "decrement" => WebKitScrollbar(WebKitScrollbarPseudoClass::Decrement),
195 "increment" => WebKitScrollbar(WebKitScrollbarPseudoClass::Increment),
196 "start" => WebKitScrollbar(WebKitScrollbarPseudoClass::Start),
197 "end" => WebKitScrollbar(WebKitScrollbarPseudoClass::End),
198 "double-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::DoubleButton),
199 "single-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::SingleButton),
200 "no-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::NoButton),
201 "corner-present" => WebKitScrollbar(WebKitScrollbarPseudoClass::CornerPresent),
202 "window-inactive" => WebKitScrollbar(WebKitScrollbarPseudoClass::WindowInactive),
203
204 "local" | "global" if self.options.css_modules.is_some() => {
205 return Err(loc.new_custom_error(SelectorParseErrorKind::AmbiguousCssModuleClass(name.clone())))
206 },
207
208 _ => {
209 if !name.starts_with('-') {
210 self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name.clone())));
211 }
212 Custom { name: name.into() }
213 }
214 };
215
216 Ok(pseudo_class)
217 }
218
219 fn parse_non_ts_functional_pseudo_class<'t>(
220 &self,
221 name: CowRcStr<'i>,
222 parser: &mut cssparser::Parser<'i, 't>,
223 ) -> Result<PseudoClass<'i>, ParseError<'i, Self::Error>> {
224 use PseudoClass::*;
225 let pseudo_class = match_ignore_ascii_case! { &name,
226 "lang" => {
227 let languages = parser.parse_comma_separated(|parser| {
228 parser.expect_ident_or_string()
229 .map(|s| s.into())
230 .map_err(|e| e.into())
231 })?;
232 Lang { languages }
233 },
234 "dir" => Dir { direction: Direction::parse(parser)? },
235 "active-view-transition-type" => {
237 let kind = Parse::parse(parser)?;
238 ActiveViewTransitionType { kind }
239 },
240 "state" => {
241 let state = CustomIdent::parse(parser)?;
242 State { state }
243 },
244 "local" if self.options.css_modules.is_some() => Local { selector: Box::new(Selector::parse(self, parser)?) },
245 "global" if self.options.css_modules.is_some() => Global { selector: Box::new(Selector::parse(self, parser)?) },
246 _ => {
247 if !name.starts_with('-') {
248 self.options.warn(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name.clone())));
249 }
250 let mut args = Vec::new();
251 TokenList::parse_raw(parser, &mut args, &self.options, 0)?;
252 CustomFunction {
253 name: name.into(),
254 arguments: TokenList(args)
255 }
256 },
257 };
258
259 Ok(pseudo_class)
260 }
261
262 fn parse_any_prefix<'t>(&self, name: &str) -> Option<VendorPrefix> {
263 match_ignore_ascii_case! { &name,
264 "-webkit-any" => Some(VendorPrefix::WebKit),
265 "-moz-any" => Some(VendorPrefix::Moz),
266 _ => None
267 }
268 }
269
270 fn parse_pseudo_element(
271 &self,
272 loc: SourceLocation,
273 name: CowRcStr<'i>,
274 ) -> Result<PseudoElement<'i>, ParseError<'i, Self::Error>> {
275 use PseudoElement::*;
276 let pseudo_element = match_ignore_ascii_case! { &name,
277 "before" => Before,
278 "after" => After,
279 "first-line" => FirstLine,
280 "first-letter" => FirstLetter,
281 "details-content" => DetailsContent,
282 "target-text" => TargetText,
283 "search-text" => SearchText,
284 "cue" => Cue,
285 "cue-region" => CueRegion,
286 "selection" => Selection(VendorPrefix::None),
287 "-moz-selection" => Selection(VendorPrefix::Moz),
288 "placeholder" => Placeholder(VendorPrefix::None),
289 "-webkit-input-placeholder" => Placeholder(VendorPrefix::WebKit),
290 "-moz-placeholder" => Placeholder(VendorPrefix::Moz),
291 "-ms-input-placeholder" => Placeholder(VendorPrefix::Moz),
292 "marker" => Marker,
293 "backdrop" => Backdrop(VendorPrefix::None),
294 "-webkit-backdrop" => Backdrop(VendorPrefix::WebKit),
295 "file-selector-button" => FileSelectorButton(VendorPrefix::None),
296 "-webkit-file-upload-button" => FileSelectorButton(VendorPrefix::WebKit),
297 "-ms-browse" => FileSelectorButton(VendorPrefix::Ms),
298
299 "-webkit-scrollbar" => WebKitScrollbar(WebKitScrollbarPseudoElement::Scrollbar),
300 "-webkit-scrollbar-button" => WebKitScrollbar(WebKitScrollbarPseudoElement::Button),
301 "-webkit-scrollbar-track" => WebKitScrollbar(WebKitScrollbarPseudoElement::Track),
302 "-webkit-scrollbar-track-piece" => WebKitScrollbar(WebKitScrollbarPseudoElement::TrackPiece),
303 "-webkit-scrollbar-thumb" => WebKitScrollbar(WebKitScrollbarPseudoElement::Thumb),
304 "-webkit-scrollbar-corner" => WebKitScrollbar(WebKitScrollbarPseudoElement::Corner),
305 "-webkit-resizer" => WebKitScrollbar(WebKitScrollbarPseudoElement::Resizer),
306
307 "picker-icon" => PickerIcon,
308 "checkmark" => Checkmark,
309
310 "view-transition" => ViewTransition,
311
312 "grammar-error" => GrammarError,
313 "spelling-error" => SpellingError,
314
315 _ => {
316 if !name.starts_with('-') {
317 self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name.clone())));
318 }
319 Custom { name: name.into() }
320 }
321 };
322
323 Ok(pseudo_element)
324 }
325
326 fn parse_functional_pseudo_element<'t>(
327 &self,
328 name: CowRcStr<'i>,
329 arguments: &mut Parser<'i, 't>,
330 ) -> Result<<Self::Impl as SelectorImpl<'i>>::PseudoElement, ParseError<'i, Self::Error>> {
331 use PseudoElement::*;
332 let pseudo_element = match_ignore_ascii_case! { &name,
333 "cue" => CueFunction { selector: Box::new(Selector::parse(self, arguments)?) },
334 "cue-region" => CueRegionFunction { selector: Box::new(Selector::parse(self, arguments)?) },
335 "highlight" => HighlightFunction { name: CustomIdent::parse(arguments)? },
336 "picker" => PickerFunction { identifier: Ident::parse(arguments)? },
337 "view-transition-group" => ViewTransitionGroup { part: ViewTransitionPartSelector::parse(arguments)? },
338 "view-transition-image-pair" => ViewTransitionImagePair { part: ViewTransitionPartSelector::parse(arguments)? },
339 "view-transition-old" => ViewTransitionOld { part: ViewTransitionPartSelector::parse(arguments)? },
340 "view-transition-new" => ViewTransitionNew { part: ViewTransitionPartSelector::parse(arguments)? },
341 _ => {
342 if !name.starts_with('-') {
343 self.options.warn(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name.clone())));
344 }
345 let mut args = Vec::new();
346 TokenList::parse_raw(arguments, &mut args, &self.options, 0)?;
347 CustomFunction { name: name.into(), arguments: TokenList(args) }
348 }
349 };
350
351 Ok(pseudo_element)
352 }
353
354 #[inline]
355 fn parse_slotted(&self) -> bool {
356 true
357 }
358
359 #[inline]
360 fn parse_host(&self) -> bool {
361 true
362 }
363
364 #[inline]
365 fn parse_is_and_where(&self) -> bool {
366 true
367 }
368
369 #[inline]
370 fn parse_part(&self) -> bool {
371 true
372 }
373
374 fn default_namespace(&self) -> Option<CowArcStr<'i>> {
375 None
376 }
377
378 fn namespace_for_prefix(&self, prefix: &Ident<'i>) -> Option<CowArcStr<'i>> {
379 Some(prefix.0.clone())
380 }
381
382 #[inline]
383 fn is_nesting_allowed(&self) -> bool {
384 self.is_nesting_allowed
385 }
386
387 fn deep_combinator_enabled(&self) -> bool {
388 self.options.flags.contains(ParserFlags::DEEP_SELECTOR_COMBINATOR)
389 }
390}
391
392enum_property! {
393 #[derive(Eq, Hash)]
395 pub enum Direction {
396 Ltr,
398 Rtl,
400 }
401}
402
403#[derive(Clone, PartialEq, Eq, Hash)]
405#[cfg_attr(
406 feature = "serde",
407 derive(serde::Serialize, serde::Deserialize),
408 serde(tag = "kind", rename_all = "kebab-case")
409)]
410#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
411#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
412pub enum PseudoClass<'i> {
413 Lang {
416 #[cfg_attr(feature = "serde", serde(borrow))]
418 languages: Vec<CowArcStr<'i>>,
419 },
420 Dir {
422 direction: Direction,
424 },
425
426 Hover,
429 Active,
431 Focus,
433 FocusVisible,
435 FocusWithin,
437
438 Current,
441 Past,
443 Future,
445
446 Playing,
449 Paused,
451 Seeking,
453 Buffering,
455 Stalled,
457 Muted,
459 VolumeLocked,
461
462 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
464 Fullscreen(VendorPrefix),
465
466 Open,
469 Closed,
471 Modal,
473 PictureInPicture,
475
476 PopoverOpen,
479
480 Defined,
482
483 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
486 AnyLink(VendorPrefix),
487 Link,
489 LocalLink,
491 Target,
493
494 TargetCurrent,
496 TargetBefore,
498 TargetAfter,
500 TargetWithin,
502 Visited,
504
505 Enabled,
508 Disabled,
510 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
512 ReadOnly(VendorPrefix),
513 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
515 ReadWrite(VendorPrefix),
516 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
518 PlaceholderShown(VendorPrefix),
519 Default,
521 Checked,
523 Indeterminate,
525 Blank,
527 Valid,
529 Invalid,
531 InRange,
533 OutOfRange,
535 Required,
537 Optional,
539 UserValid,
541 UserInvalid,
543
544 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
546 Autofill(VendorPrefix),
547
548 ActiveViewTransition,
550 ActiveViewTransitionType {
552 #[cfg_attr(feature = "serde", serde(rename = "type"))]
554 kind: SmallVec<[CustomIdent<'i>; 1]>,
555 },
556
557 State {
559 state: CustomIdent<'i>,
561 },
562
563 Local {
566 selector: Box<Selector<'i>>,
568 },
569 Global {
571 selector: Box<Selector<'i>>,
573 },
574
575 #[cfg_attr(
578 feature = "serde",
579 serde(rename = "webkit-scrollbar", with = "ValueWrapper::<WebKitScrollbarPseudoClass>")
580 )]
581 WebKitScrollbar(WebKitScrollbarPseudoClass),
582 Custom {
584 name: CowArcStr<'i>,
586 },
587 CustomFunction {
589 name: CowArcStr<'i>,
591 arguments: TokenList<'i>,
593 },
594}
595
596#[derive(Clone, Eq, PartialEq, Hash)]
598#[cfg_attr(
599 feature = "serde",
600 derive(serde::Serialize, serde::Deserialize),
601 serde(rename_all = "kebab-case")
602)]
603#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
604#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
605pub enum WebKitScrollbarPseudoClass {
606 Horizontal,
608 Vertical,
610 Decrement,
612 Increment,
614 Start,
616 End,
618 DoubleButton,
620 SingleButton,
622 NoButton,
624 CornerPresent,
626 WindowInactive,
628}
629
630impl<'i> parcel_selectors::parser::NonTSPseudoClass<'i> for PseudoClass<'i> {
631 type Impl = Selectors;
632
633 fn is_active_or_hover(&self) -> bool {
634 matches!(*self, PseudoClass::Active | PseudoClass::Hover)
635 }
636
637 fn is_user_action_state(&self) -> bool {
638 matches!(
639 *self,
640 PseudoClass::Active
641 | PseudoClass::Hover
642 | PseudoClass::Focus
643 | PseudoClass::FocusWithin
644 | PseudoClass::FocusVisible
645 )
646 }
647
648 fn is_valid_before_webkit_scrollbar(&self) -> bool {
649 !matches!(*self, PseudoClass::WebKitScrollbar(..))
650 }
651
652 fn is_valid_after_webkit_scrollbar(&self) -> bool {
653 matches!(
655 *self,
656 PseudoClass::WebKitScrollbar(..)
657 | PseudoClass::Enabled
658 | PseudoClass::Disabled
659 | PseudoClass::Hover
660 | PseudoClass::Active
661 )
662 }
663}
664
665impl<'i> cssparser::ToCss for PseudoClass<'i> {
666 fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result
667 where
668 W: fmt::Write,
669 {
670 let mut s = String::new();
671 serialize_pseudo_class(self, &mut Printer::new(&mut s, Default::default()), None)
672 .map_err(|_| std::fmt::Error)?;
673 write!(dest, "{}", s)
674 }
675}
676
677fn serialize_pseudo_class<'a, 'i, W>(
678 pseudo_class: &PseudoClass<'i>,
679 dest: &mut Printer<W>,
680 context: Option<&StyleContext>,
681) -> Result<(), PrinterError>
682where
683 W: fmt::Write,
684{
685 use PseudoClass::*;
686 match pseudo_class {
687 Lang { languages: lang } => {
688 dest.write_str(":lang(")?;
689 let mut first = true;
690 for lang in lang {
691 if first {
692 first = false;
693 } else {
694 dest.delim(',', false)?;
695 }
696 serialize_identifier(lang, dest)?;
697 }
698 return dest.write_str(")");
699 }
700 Dir { direction: dir } => {
701 dest.write_str(":dir(")?;
702 dir.to_css(dest)?;
703 return dest.write_str(")");
704 }
705 State { state } => {
706 dest.write_str(":state(")?;
707 state.to_css(dest)?;
708 return dest.write_str(")");
709 }
710 _ => {}
711 }
712
713 macro_rules! write_prefixed {
714 ($prefix: ident, $val: expr) => {{
715 dest.write_char(':')?;
716 let vp = if !dest.vendor_prefix.is_empty() {
718 (dest.vendor_prefix & *$prefix).or_none()
719 } else {
720 *$prefix
721 };
722 vp.to_css(dest)?;
723 dest.write_str($val)
724 }};
725 }
726
727 macro_rules! pseudo {
728 ($key: ident, $s: literal) => {{
729 let class = if let Some(pseudo_classes) = &dest.pseudo_classes {
730 pseudo_classes.$key
731 } else {
732 None
733 };
734
735 if let Some(class) = class {
736 dest.write_char('.')?;
737 dest.write_ident(class, true)
738 } else {
739 dest.write_str($s)
740 }
741 }};
742 }
743
744 match pseudo_class {
745 Hover => pseudo!(hover, ":hover"),
747 Active => pseudo!(active, ":active"),
748 Focus => pseudo!(focus, ":focus"),
749 FocusVisible => pseudo!(focus_visible, ":focus-visible"),
750 FocusWithin => pseudo!(focus_within, ":focus-within"),
751
752 Current => dest.write_str(":current"),
754 Past => dest.write_str(":past"),
755 Future => dest.write_str(":future"),
756
757 Playing => dest.write_str(":playing"),
759 Paused => dest.write_str(":paused"),
760 Seeking => dest.write_str(":seeking"),
761 Buffering => dest.write_str(":buffering"),
762 Stalled => dest.write_str(":stalled"),
763 Muted => dest.write_str(":muted"),
764 VolumeLocked => dest.write_str(":volume-locked"),
765
766 Fullscreen(prefix) => {
768 dest.write_char(':')?;
769 let vp = if !dest.vendor_prefix.is_empty() {
770 (dest.vendor_prefix & *prefix).or_none()
771 } else {
772 *prefix
773 };
774 vp.to_css(dest)?;
775 if vp == VendorPrefix::WebKit || vp == VendorPrefix::Moz {
776 dest.write_str("full-screen")
777 } else {
778 dest.write_str("fullscreen")
779 }
780 }
781
782 Open => dest.write_str(":open"),
784 Closed => dest.write_str(":closed"),
785 Modal => dest.write_str(":modal"),
786 PictureInPicture => dest.write_str(":picture-in-picture"),
787
788 PopoverOpen => dest.write_str(":popover-open"),
790
791 Defined => dest.write_str(":defined"),
793
794 AnyLink(prefix) => write_prefixed!(prefix, "any-link"),
796 Link => dest.write_str(":link"),
797 LocalLink => dest.write_str(":local-link"),
798 Target => dest.write_str(":target"),
799 TargetWithin => dest.write_str(":target-within"),
800
801 TargetCurrent => dest.write_str(":target-current"),
803 TargetBefore => dest.write_str(":target-before"),
804 TargetAfter => dest.write_str(":target-after"),
805
806 Visited => dest.write_str(":visited"),
807
808 Enabled => dest.write_str(":enabled"),
810 Disabled => dest.write_str(":disabled"),
811 ReadOnly(prefix) => write_prefixed!(prefix, "read-only"),
812 ReadWrite(prefix) => write_prefixed!(prefix, "read-write"),
813 PlaceholderShown(prefix) => write_prefixed!(prefix, "placeholder-shown"),
814 Default => dest.write_str(":default"),
815 Checked => dest.write_str(":checked"),
816 Indeterminate => dest.write_str(":indeterminate"),
817 Blank => dest.write_str(":blank"),
818 Valid => dest.write_str(":valid"),
819 Invalid => dest.write_str(":invalid"),
820 InRange => dest.write_str(":in-range"),
821 OutOfRange => dest.write_str(":out-of-range"),
822 Required => dest.write_str(":required"),
823 Optional => dest.write_str(":optional"),
824 UserValid => dest.write_str(":user-valid"),
825 UserInvalid => dest.write_str(":user-invalid"),
826
827 Autofill(prefix) => write_prefixed!(prefix, "autofill"),
829
830 ActiveViewTransition => dest.write_str(":active-view-transition"),
831 ActiveViewTransitionType { kind } => {
832 dest.write_str(":active-view-transition-type(")?;
833 kind.to_css(dest)?;
834 dest.write_char(')')
835 }
836
837 Local { selector } => serialize_selector(selector, dest, context, false),
838 Global { selector } => {
839 let css_module = std::mem::take(&mut dest.css_module);
840 serialize_selector(selector, dest, context, false)?;
841 dest.css_module = css_module;
842 Ok(())
843 }
844
845 WebKitScrollbar(s) => {
847 use WebKitScrollbarPseudoClass::*;
848 dest.write_str(match s {
849 Horizontal => ":horizontal",
850 Vertical => ":vertical",
851 Decrement => ":decrement",
852 Increment => ":increment",
853 Start => ":start",
854 End => ":end",
855 DoubleButton => ":double-button",
856 SingleButton => ":single-button",
857 NoButton => ":no-button",
858 CornerPresent => ":corner-present",
859 WindowInactive => ":window-inactive",
860 })
861 }
862
863 Lang { languages: _ } | Dir { direction: _ } | State { .. } => unreachable!(),
864 Custom { name } => {
865 dest.write_char(':')?;
866 return dest.write_str(&name);
867 }
868 CustomFunction { name, arguments: args } => {
869 dest.write_char(':')?;
870 dest.write_str(name)?;
871 dest.write_char('(')?;
872 args.to_css_raw(dest)?;
873 dest.write_char(')')
874 }
875 }
876}
877
878impl<'i> PseudoClass<'i> {
879 pub(crate) fn is_equivalent(&self, other: &PseudoClass<'i>) -> bool {
880 use PseudoClass::*;
881 match (self, other) {
882 (Fullscreen(_), Fullscreen(_))
883 | (AnyLink(_), AnyLink(_))
884 | (ReadOnly(_), ReadOnly(_))
885 | (ReadWrite(_), ReadWrite(_))
886 | (PlaceholderShown(_), PlaceholderShown(_))
887 | (Autofill(_), Autofill(_)) => true,
888 (a, b) => a == b,
889 }
890 }
891
892 pub(crate) fn get_prefix(&self) -> VendorPrefix {
893 use PseudoClass::*;
894 match self {
895 Fullscreen(p) | AnyLink(p) | ReadOnly(p) | ReadWrite(p) | PlaceholderShown(p) | Autofill(p) => *p,
896 _ => VendorPrefix::empty(),
897 }
898 }
899
900 pub(crate) fn get_necessary_prefixes(&mut self, targets: Targets) -> VendorPrefix {
901 use crate::prefixes::Feature;
902 use PseudoClass::*;
903 let (p, feature) = match self {
904 Fullscreen(p) => (p, Feature::PseudoClassFullscreen),
905 AnyLink(p) => (p, Feature::PseudoClassAnyLink),
906 ReadOnly(p) => (p, Feature::PseudoClassReadOnly),
907 ReadWrite(p) => (p, Feature::PseudoClassReadWrite),
908 PlaceholderShown(p) => (p, Feature::PseudoClassPlaceholderShown),
909 Autofill(p) => (p, Feature::PseudoClassAutofill),
910 _ => return VendorPrefix::empty(),
911 };
912
913 *p = targets.prefixes(*p, feature);
914 *p
915 }
916}
917
918#[derive(PartialEq, Eq, Hash, Clone, Debug)]
920#[cfg_attr(
921 feature = "serde",
922 derive(serde::Serialize, serde::Deserialize),
923 serde(tag = "kind", rename_all = "kebab-case")
924)]
925#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
926#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
927pub enum PseudoElement<'i> {
928 After,
930 Before,
932 FirstLine,
934 FirstLetter,
936 DetailsContent,
938 TargetText,
940 SearchText,
942 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
944 Selection(VendorPrefix),
945 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
947 Placeholder(VendorPrefix),
948 HighlightFunction {
950 name: CustomIdent<'i>,
952 },
953 Marker,
955 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
957 Backdrop(VendorPrefix),
958 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
960 FileSelectorButton(VendorPrefix),
961 #[cfg_attr(
963 feature = "serde",
964 serde(rename = "webkit-scrollbar", with = "ValueWrapper::<WebKitScrollbarPseudoElement>")
965 )]
966 WebKitScrollbar(WebKitScrollbarPseudoElement),
967 Cue,
969 CueRegion,
971 CueFunction {
973 selector: Box<Selector<'i>>,
975 },
976 CueRegionFunction {
978 selector: Box<Selector<'i>>,
980 },
981 ViewTransition,
983 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
985 ViewTransitionGroup {
986 part: ViewTransitionPartSelector<'i>,
988 },
989 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
991 ViewTransitionImagePair {
992 part: ViewTransitionPartSelector<'i>,
994 },
995 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
997 ViewTransitionOld {
998 part: ViewTransitionPartSelector<'i>,
1000 },
1001 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1003 ViewTransitionNew {
1004 part: ViewTransitionPartSelector<'i>,
1006 },
1007 PickerFunction {
1009 identifier: Ident<'i>,
1011 },
1012 PickerIcon,
1014 Checkmark,
1016 GrammarError,
1018 SpellingError,
1020 Custom {
1022 #[cfg_attr(feature = "serde", serde(borrow))]
1024 name: CowArcStr<'i>,
1025 },
1026 CustomFunction {
1028 name: CowArcStr<'i>,
1030 arguments: TokenList<'i>,
1032 },
1033}
1034
1035#[derive(PartialEq, Eq, Clone, Debug, Hash)]
1037#[cfg_attr(
1038 feature = "serde",
1039 derive(serde::Serialize, serde::Deserialize),
1040 serde(rename_all = "kebab-case")
1041)]
1042#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1043#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1044pub enum WebKitScrollbarPseudoElement {
1045 Scrollbar,
1047 Button,
1049 Track,
1051 TrackPiece,
1053 Thumb,
1055 Corner,
1057 Resizer,
1059}
1060
1061#[derive(PartialEq, Eq, Clone, Debug, Hash)]
1063#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1064#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1065pub enum ViewTransitionPartName<'i> {
1066 #[cfg_attr(feature = "serde", serde(rename = "*"))]
1068 All,
1069 #[cfg_attr(feature = "serde", serde(borrow, untagged))]
1071 Name(CustomIdent<'i>),
1072}
1073
1074#[cfg(feature = "jsonschema")]
1075#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
1076impl<'a> schemars::JsonSchema for ViewTransitionPartName<'a> {
1077 fn is_referenceable() -> bool {
1078 true
1079 }
1080
1081 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1082 str::json_schema(gen)
1083 }
1084
1085 fn schema_name() -> String {
1086 "ViewTransitionPartName".into()
1087 }
1088}
1089
1090impl<'i> Parse<'i> for ViewTransitionPartName<'i> {
1091 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1092 if input.try_parse(|input| input.expect_delim('*')).is_ok() {
1093 return Ok(ViewTransitionPartName::All);
1094 }
1095
1096 Ok(ViewTransitionPartName::Name(CustomIdent::parse(input)?))
1097 }
1098}
1099
1100impl<'i> ToCss for ViewTransitionPartName<'i> {
1101 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1102 where
1103 W: std::fmt::Write,
1104 {
1105 match self {
1106 ViewTransitionPartName::All => dest.write_char('*'),
1107 ViewTransitionPartName::Name(name) => name.to_css(dest),
1108 }
1109 }
1110}
1111
1112#[derive(PartialEq, Eq, Clone, Debug, Hash)]
1114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1115#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1116#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1117pub struct ViewTransitionPartSelector<'i> {
1118 #[cfg_attr(feature = "serde", serde(borrow))]
1120 name: Option<ViewTransitionPartName<'i>>,
1121 classes: Vec<CustomIdent<'i>>,
1123}
1124
1125impl<'i> Parse<'i> for ViewTransitionPartSelector<'i> {
1126 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1127 input.skip_whitespace();
1128 let name = input.try_parse(ViewTransitionPartName::parse).ok();
1129 let mut classes = Vec::new();
1130 while let Ok(token) = input.next_including_whitespace() {
1131 if matches!(token, Token::Delim('.')) {
1132 match input.next_including_whitespace() {
1133 Ok(Token::Ident(id)) => classes.push(CustomIdent(id.into())),
1134 _ => return Err(input.new_custom_error(ParserError::SelectorError(SelectorError::InvalidState))),
1135 }
1136 } else {
1137 break;
1138 }
1139 }
1140
1141 if !input.is_exhausted() || (name.is_none() && classes.is_empty()) {
1142 return Err(input.new_custom_error(ParserError::SelectorError(SelectorError::InvalidState)));
1143 }
1144
1145 Ok(ViewTransitionPartSelector { name, classes })
1146 }
1147}
1148
1149impl<'i> ToCss for ViewTransitionPartSelector<'i> {
1150 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1151 where
1152 W: std::fmt::Write,
1153 {
1154 if let Some(name) = &self.name {
1155 name.to_css(dest)?;
1156 }
1157 for class in &self.classes {
1158 dest.write_char('.')?;
1159 class.to_css(dest)?;
1160 }
1161 Ok(())
1162 }
1163}
1164
1165impl<'i> cssparser::ToCss for PseudoElement<'i> {
1166 fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result
1167 where
1168 W: fmt::Write,
1169 {
1170 let mut s = String::new();
1171 serialize_pseudo_element(self, &mut Printer::new(&mut s, Default::default()), None)
1172 .map_err(|_| std::fmt::Error)?;
1173 write!(dest, "{}", s)
1174 }
1175}
1176
1177fn serialize_pseudo_element<'a, 'i, W>(
1178 pseudo_element: &PseudoElement,
1179 dest: &mut Printer<W>,
1180 context: Option<&StyleContext>,
1181) -> Result<(), PrinterError>
1182where
1183 W: fmt::Write,
1184{
1185 use PseudoElement::*;
1186
1187 macro_rules! write_prefix {
1188 ($prefix: ident) => {{
1189 dest.write_str("::")?;
1190 let vp = if !dest.vendor_prefix.is_empty() {
1192 (dest.vendor_prefix & *$prefix).or_none()
1193 } else {
1194 *$prefix
1195 };
1196 vp.to_css(dest)?;
1197 vp
1198 }};
1199 }
1200
1201 macro_rules! write_prefixed {
1202 ($prefix: ident, $val: expr) => {{
1203 write_prefix!($prefix);
1204 dest.write_str($val)
1205 }};
1206 }
1207
1208 match pseudo_element {
1209 After => dest.write_str(":after"),
1213 Before => dest.write_str(":before"),
1214 FirstLine => dest.write_str(":first-line"),
1215 FirstLetter => dest.write_str(":first-letter"),
1216 DetailsContent => dest.write_str("::details-content"),
1217 TargetText => dest.write_str("::target-text"),
1218 SearchText => dest.write_str("::search-text"),
1219 HighlightFunction { name } => {
1220 dest.write_str("::highlight(")?;
1221 name.to_css(dest)?;
1222 dest.write_char(')')
1223 }
1224 Marker => dest.write_str("::marker"),
1225 Selection(prefix) => write_prefixed!(prefix, "selection"),
1226 Cue => dest.write_str("::cue"),
1227 CueRegion => dest.write_str("::cue-region"),
1228 CueFunction { selector } => {
1229 dest.write_str("::cue(")?;
1230 serialize_selector(selector, dest, context, false)?;
1231 dest.write_char(')')
1232 }
1233 CueRegionFunction { selector } => {
1234 dest.write_str("::cue-region(")?;
1235 serialize_selector(selector, dest, context, false)?;
1236 dest.write_char(')')
1237 }
1238 Placeholder(prefix) => {
1239 let vp = write_prefix!(prefix);
1240 if vp == VendorPrefix::WebKit || vp == VendorPrefix::Ms {
1241 dest.write_str("input-placeholder")
1242 } else {
1243 dest.write_str("placeholder")
1244 }
1245 }
1246 Backdrop(prefix) => write_prefixed!(prefix, "backdrop"),
1247 FileSelectorButton(prefix) => {
1248 let vp = write_prefix!(prefix);
1249 if vp == VendorPrefix::WebKit {
1250 dest.write_str("file-upload-button")
1251 } else if vp == VendorPrefix::Ms {
1252 dest.write_str("browse")
1253 } else {
1254 dest.write_str("file-selector-button")
1255 }
1256 }
1257 WebKitScrollbar(s) => {
1258 use WebKitScrollbarPseudoElement::*;
1259 dest.write_str(match s {
1260 Scrollbar => "::-webkit-scrollbar",
1261 Button => "::-webkit-scrollbar-button",
1262 Track => "::-webkit-scrollbar-track",
1263 TrackPiece => "::-webkit-scrollbar-track-piece",
1264 Thumb => "::-webkit-scrollbar-thumb",
1265 Corner => "::-webkit-scrollbar-corner",
1266 Resizer => "::-webkit-resizer",
1267 })
1268 }
1269 ViewTransition => dest.write_str("::view-transition"),
1270 ViewTransitionGroup { part } => {
1271 dest.write_str("::view-transition-group(")?;
1272 part.to_css(dest)?;
1273 dest.write_char(')')
1274 }
1275 ViewTransitionImagePair { part } => {
1276 dest.write_str("::view-transition-image-pair(")?;
1277 part.to_css(dest)?;
1278 dest.write_char(')')
1279 }
1280 ViewTransitionOld { part } => {
1281 dest.write_str("::view-transition-old(")?;
1282 part.to_css(dest)?;
1283 dest.write_char(')')
1284 }
1285 ViewTransitionNew { part } => {
1286 dest.write_str("::view-transition-new(")?;
1287 part.to_css(dest)?;
1288 dest.write_char(')')
1289 }
1290 PickerFunction { identifier } => {
1291 dest.write_str("::picker(")?;
1292 identifier.to_css(dest)?;
1293 dest.write_char(')')
1294 }
1295 PickerIcon => dest.write_str("::picker-icon"),
1296 Checkmark => dest.write_str("::checkmark"),
1297 GrammarError => dest.write_str("::grammar-error"),
1298 SpellingError => dest.write_str("::spelling-error"),
1299 Custom { name: val } => {
1300 dest.write_str("::")?;
1301 return dest.write_str(val);
1302 }
1303 CustomFunction { name, arguments: args } => {
1304 dest.write_str("::")?;
1305 dest.write_str(name)?;
1306 dest.write_char('(')?;
1307 args.to_css_raw(dest)?;
1308 dest.write_char(')')
1309 }
1310 }
1311}
1312
1313impl<'i> parcel_selectors::parser::PseudoElement<'i> for PseudoElement<'i> {
1314 type Impl = Selectors;
1315
1316 fn accepts_state_pseudo_classes(&self) -> bool {
1317 true
1319 }
1320
1321 fn valid_after_slotted(&self) -> bool {
1322 matches!(
1326 *self,
1327 PseudoElement::Before
1328 | PseudoElement::After
1329 | PseudoElement::Marker
1330 | PseudoElement::Placeholder(_)
1331 | PseudoElement::FileSelectorButton(_)
1332 )
1333 }
1334
1335 fn is_webkit_scrollbar(&self) -> bool {
1336 matches!(*self, PseudoElement::WebKitScrollbar(..))
1337 }
1338
1339 fn is_view_transition(&self) -> bool {
1340 matches!(
1341 *self,
1342 PseudoElement::ViewTransitionGroup { .. }
1343 | PseudoElement::ViewTransitionImagePair { .. }
1344 | PseudoElement::ViewTransitionNew { .. }
1345 | PseudoElement::ViewTransitionOld { .. }
1346 )
1347 }
1348
1349 fn is_unknown(&self) -> bool {
1350 matches!(
1351 *self,
1352 PseudoElement::Custom { .. } | PseudoElement::CustomFunction { .. },
1353 )
1354 }
1355}
1356
1357impl<'i> PseudoElement<'i> {
1358 pub(crate) fn is_equivalent(&self, other: &PseudoElement<'i>) -> bool {
1359 use PseudoElement::*;
1360 match (self, other) {
1361 (Selection(_), Selection(_))
1362 | (Placeholder(_), Placeholder(_))
1363 | (Backdrop(_), Backdrop(_))
1364 | (FileSelectorButton(_), FileSelectorButton(_)) => true,
1365 (a, b) => a == b,
1366 }
1367 }
1368
1369 pub(crate) fn get_prefix(&self) -> VendorPrefix {
1370 use PseudoElement::*;
1371 match self {
1372 Selection(p) | Placeholder(p) | Backdrop(p) | FileSelectorButton(p) => *p,
1373 _ => VendorPrefix::empty(),
1374 }
1375 }
1376
1377 pub(crate) fn get_necessary_prefixes(&mut self, targets: Targets) -> VendorPrefix {
1378 use crate::prefixes::Feature;
1379 use PseudoElement::*;
1380 let (p, feature) = match self {
1381 Selection(p) => (p, Feature::PseudoElementSelection),
1382 Placeholder(p) => (p, Feature::PseudoElementPlaceholder),
1383 Backdrop(p) => (p, Feature::PseudoElementBackdrop),
1384 FileSelectorButton(p) => (p, Feature::PseudoElementFileSelectorButton),
1385 _ => return VendorPrefix::empty(),
1386 };
1387
1388 *p = targets.prefixes(*p, feature);
1389 *p
1390 }
1391}
1392
1393impl<'a, 'i> ToCss for SelectorList<'i> {
1394 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1395 where
1396 W: fmt::Write,
1397 {
1398 serialize_selector_list(self.0.iter(), dest, dest.context(), false)
1399 }
1400}
1401
1402impl ToCss for Combinator {
1403 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1404 where
1405 W: fmt::Write,
1406 {
1407 match *self {
1408 Combinator::Child => dest.delim('>', true),
1409 Combinator::Descendant => dest.write_str(" "),
1410 Combinator::NextSibling => dest.delim('+', true),
1411 Combinator::LaterSibling => dest.delim('~', true),
1412 Combinator::Deep => dest.write_str(" /deep/ "),
1413 Combinator::DeepDescendant => {
1414 dest.whitespace()?;
1415 dest.write_str(">>>")?;
1416 dest.whitespace()
1417 }
1418 Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()),
1419 }
1420 }
1421}
1422
1423impl<'a, 'i> ToCss for Selector<'i> {
1425 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1426 where
1427 W: fmt::Write,
1428 {
1429 serialize_selector(self, dest, dest.context(), false)
1430 }
1431}
1432
1433fn serialize_selector<'a, 'i, W>(
1434 selector: &Selector<'i>,
1435 dest: &mut Printer<W>,
1436 context: Option<&StyleContext>,
1437 mut is_relative: bool,
1438) -> Result<(), PrinterError>
1439where
1440 W: fmt::Write,
1441{
1442 use parcel_selectors::parser::*;
1443 let mut combinators = selector.iter_raw_match_order().rev().filter_map(|x| x.as_combinator());
1456 let compound_selectors = selector.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev();
1457 let should_compile_nesting = should_compile!(dest.targets.current, Nesting);
1458
1459 let mut first = true;
1460 let mut combinators_exhausted = false;
1461 for mut compound in compound_selectors {
1462 debug_assert!(!combinators_exhausted);
1463
1464 if is_relative && matches!(compound.get(0), Some(Component::Scope)) {
1466 if let Some(combinator) = combinators.next() {
1467 combinator.to_css(dest)?;
1468 }
1469 compound = &compound[1..];
1470 is_relative = false;
1471 }
1472
1473 if compound.is_empty() {
1475 continue;
1476 }
1477
1478 let has_leading_nesting = first && matches!(compound[0], Component::Nesting);
1479 let first_index = if has_leading_nesting { 1 } else { 0 };
1480 first = false;
1481
1482 let (can_elide_namespace, first_non_namespace) = match compound.get(first_index) {
1493 Some(Component::ExplicitAnyNamespace)
1494 | Some(Component::ExplicitNoNamespace)
1495 | Some(Component::Namespace(..)) => (false, first_index + 1),
1496 Some(Component::DefaultNamespace(..)) => (true, first_index + 1),
1497 _ => (true, first_index),
1498 };
1499 let mut perform_step_2 = true;
1500 let next_combinator = combinators.next();
1501 if first_non_namespace == compound.len() - 1 {
1502 match (next_combinator, &compound[first_non_namespace]) {
1503 (Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (),
1511 (_, &Component::ExplicitUniversalType) => {
1512 let mut iter = compound.iter();
1515 let swap_nesting = has_leading_nesting && should_compile_nesting;
1516 if swap_nesting {
1517 iter.next();
1519 }
1520
1521 for simple in iter {
1522 serialize_component(simple, dest, context)?;
1523 }
1524
1525 if swap_nesting {
1526 serialize_nesting(dest, context, false)?;
1527 }
1528
1529 perform_step_2 = false;
1531 }
1532 _ => (),
1533 }
1534 }
1535
1536 if perform_step_2 {
1546 let mut iter = compound.iter();
1547 if has_leading_nesting && should_compile_nesting && is_type_selector(compound.get(first_non_namespace)) {
1548 let nesting = iter.next().unwrap();
1551 let local = iter.next().unwrap();
1552 serialize_component(local, dest, context)?;
1553
1554 if first_non_namespace > first_index {
1556 let local = iter.next().unwrap();
1557 serialize_component(local, dest, context)?;
1558 }
1559
1560 serialize_component(nesting, dest, context)?;
1561 } else if has_leading_nesting && should_compile_nesting {
1562 iter.next();
1564 serialize_nesting(dest, context, true)?;
1565 }
1566
1567 for simple in iter {
1568 if let Component::ExplicitUniversalType = *simple {
1569 if can_elide_namespace {
1574 continue;
1575 }
1576 }
1577 serialize_component(simple, dest, context)?;
1578 }
1579 }
1580
1581 match next_combinator {
1587 Some(c) => c.to_css(dest)?,
1588 None => combinators_exhausted = true,
1589 };
1590
1591 }
1597
1598 Ok(())
1599}
1600
1601fn serialize_component<'a, 'i, W>(
1602 component: &Component,
1603 dest: &mut Printer<W>,
1604 context: Option<&StyleContext>,
1605) -> Result<(), PrinterError>
1606where
1607 W: fmt::Write,
1608{
1609 match component {
1610 Component::Combinator(ref c) => c.to_css(dest),
1611 Component::AttributeInNoNamespace {
1612 ref local_name,
1613 operator,
1614 ref value,
1615 case_sensitivity,
1616 ..
1617 } => {
1618 dest.write_char('[')?;
1619 cssparser::ToCss::to_css(local_name, dest)?;
1620 cssparser::ToCss::to_css(operator, dest)?;
1621
1622 if dest.minify {
1623 let mut id = String::new();
1625 serialize_identifier(&value, &mut id)?;
1626
1627 let s = value.to_css_string(Default::default())?;
1628
1629 if id.len() > 0 && id.len() < s.len() {
1630 dest.write_str(&id)?;
1631 } else {
1632 dest.write_str(&s)?;
1633 }
1634 } else {
1635 value.to_css(dest)?;
1636 }
1637
1638 match case_sensitivity {
1639 parcel_selectors::attr::ParsedCaseSensitivity::CaseSensitive
1640 | parcel_selectors::attr::ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}
1641 parcel_selectors::attr::ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
1642 parcel_selectors::attr::ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?,
1643 }
1644 dest.write_char(']')
1645 }
1646 Component::Is(ref list)
1647 | Component::Where(ref list)
1648 | Component::Negation(ref list)
1649 | Component::Any(_, ref list) => {
1650 match *component {
1651 Component::Where(..) => dest.write_str(":where(")?,
1652 Component::Is(ref selectors) => {
1653 if should_unwrap_is(selectors) {
1655 serialize_selector(selectors.first().unwrap(), dest, context, false)?;
1656 return Ok(());
1657 }
1658
1659 let vp = dest.vendor_prefix;
1660 if vp.intersects(VendorPrefix::WebKit | VendorPrefix::Moz) {
1661 dest.write_char(':')?;
1662 vp.to_css(dest)?;
1663 dest.write_str("any(")?;
1664 } else {
1665 dest.write_str(":is(")?;
1666 }
1667 }
1668 Component::Negation(_) => {
1669 dest.write_str(":not(")?;
1670 }
1671 Component::Any(prefix, ..) => {
1672 let vp = dest.vendor_prefix.or(prefix);
1673 if vp.intersects(VendorPrefix::WebKit | VendorPrefix::Moz) {
1674 dest.write_char(':')?;
1675 vp.to_css(dest)?;
1676 dest.write_str("any(")?;
1677 } else {
1678 dest.write_str(":is(")?;
1679 }
1680 }
1681 _ => unreachable!(),
1682 }
1683 serialize_selector_list(list.iter(), dest, context, false)?;
1684 dest.write_str(")")
1685 }
1686 Component::Has(ref list) => {
1687 dest.write_str(":has(")?;
1688 serialize_selector_list(list.iter(), dest, context, true)?;
1689 dest.write_str(")")
1690 }
1691 Component::NonTSPseudoClass(pseudo) => serialize_pseudo_class(pseudo, dest, context),
1692 Component::PseudoElement(pseudo) => serialize_pseudo_element(pseudo, dest, context),
1693 Component::Nesting => serialize_nesting(dest, context, false),
1694 Component::Class(ref class) => {
1695 dest.write_char('.')?;
1696 dest.write_ident(&class.0, true)
1697 }
1698 Component::ID(ref id) => {
1699 dest.write_char('#')?;
1700 dest.write_ident(&id.0, true)
1701 }
1702 Component::Host(selector) => {
1703 dest.write_str(":host")?;
1704 if let Some(ref selector) = *selector {
1705 dest.write_char('(')?;
1706 selector.to_css(dest)?;
1707 dest.write_char(')')?;
1708 }
1709 Ok(())
1710 }
1711 Component::Slotted(ref selector) => {
1712 dest.write_str("::slotted(")?;
1713 selector.to_css(dest)?;
1714 dest.write_char(')')
1715 }
1716 Component::NthOf(ref nth_of_data) => {
1717 let nth_data = nth_of_data.nth_data();
1718 nth_data.write_start(dest, true)?;
1719 nth_data.write_affine(dest)?;
1720 dest.write_str(" of ")?;
1721 serialize_selector_list(nth_of_data.selectors().iter(), dest, context, true)?;
1722 dest.write_char(')')
1723 }
1724 _ => {
1725 cssparser::ToCss::to_css(component, dest)?;
1726 Ok(())
1727 }
1728 }
1729}
1730
1731fn should_unwrap_is<'i>(selectors: &Box<[Selector<'i>]>) -> bool {
1732 if selectors.len() == 1 {
1733 let first = selectors.first().unwrap();
1734 if !has_type_selector(first) && is_simple(first) {
1735 return true;
1736 }
1737 }
1738
1739 false
1740}
1741
1742fn serialize_nesting<W>(
1743 dest: &mut Printer<W>,
1744 context: Option<&StyleContext>,
1745 first: bool,
1746) -> Result<(), PrinterError>
1747where
1748 W: fmt::Write,
1749{
1750 if let Some(ctx) = context {
1751 if ctx.selectors.0.len() == 1
1756 && (first || (!has_type_selector(&ctx.selectors.0[0]) && is_simple(&ctx.selectors.0[0])))
1757 {
1758 serialize_selector(ctx.selectors.0.first().unwrap(), dest, ctx.parent, false)
1759 } else {
1760 dest.write_str(":is(")?;
1761 serialize_selector_list(ctx.selectors.0.iter(), dest, ctx.parent, false)?;
1762 dest.write_char(')')
1763 }
1764 } else {
1765 if should_compile!(dest.targets.current, Nesting) {
1768 dest.write_str(":scope")
1769 } else {
1770 dest.write_char('&')
1771 }
1772 }
1773}
1774
1775#[inline]
1776fn has_type_selector(selector: &Selector) -> bool {
1777 let mut iter = selector.iter_raw_match_order();
1782 let first = iter.next();
1783
1784 if is_namespace(first) {
1785 is_type_selector(iter.next())
1786 } else {
1787 is_type_selector(first)
1788 }
1789}
1790
1791#[inline]
1792fn is_simple(selector: &Selector) -> bool {
1793 !selector.iter_raw_match_order().any(|component| component.is_combinator())
1794}
1795
1796#[inline]
1797fn is_type_selector(component: Option<&Component>) -> bool {
1798 matches!(
1799 component,
1800 Some(Component::LocalName(_)) | Some(Component::ExplicitUniversalType)
1801 )
1802}
1803
1804#[inline]
1805fn is_namespace(component: Option<&Component>) -> bool {
1806 matches!(
1807 component,
1808 Some(Component::ExplicitAnyNamespace)
1809 | Some(Component::ExplicitNoNamespace)
1810 | Some(Component::Namespace(..))
1811 | Some(Component::DefaultNamespace(_))
1812 )
1813}
1814
1815fn serialize_selector_list<'a, 'i: 'a, I, W>(
1816 iter: I,
1817 dest: &mut Printer<W>,
1818 context: Option<&StyleContext>,
1819 is_relative: bool,
1820) -> Result<(), PrinterError>
1821where
1822 I: Iterator<Item = &'a Selector<'i>>,
1823 W: fmt::Write,
1824{
1825 let mut first = true;
1826 for selector in iter {
1827 if !first {
1828 dest.delim(',', false)?;
1829 }
1830 first = false;
1831 serialize_selector(selector, dest, context, is_relative)?;
1832 }
1833 Ok(())
1834}
1835
1836pub(crate) fn is_compatible(selectors: &[Selector], targets: Targets) -> bool {
1837 for selector in selectors {
1838 let iter = selector.iter_raw_match_order();
1839 for component in iter {
1840 let feature = match component {
1841 Component::ID(_) | Component::Class(_) | Component::LocalName(_) => continue,
1842
1843 Component::ExplicitAnyNamespace
1844 | Component::ExplicitNoNamespace
1845 | Component::DefaultNamespace(_)
1846 | Component::Namespace(_, _) => Feature::Namespaces,
1847
1848 Component::ExplicitUniversalType => Feature::Selectors2,
1849
1850 Component::AttributeInNoNamespaceExists { .. } => Feature::Selectors2,
1851 Component::AttributeInNoNamespace {
1852 operator,
1853 case_sensitivity,
1854 ..
1855 } => {
1856 if *case_sensitivity != ParsedCaseSensitivity::CaseSensitive {
1857 Feature::CaseInsensitive
1858 } else {
1859 match operator {
1860 AttrSelectorOperator::Equal | AttrSelectorOperator::Includes | AttrSelectorOperator::DashMatch => {
1861 Feature::Selectors2
1862 }
1863 AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {
1864 Feature::Selectors3
1865 }
1866 }
1867 }
1868 }
1869 Component::AttributeOther(attr) => match attr.operation {
1870 ParsedAttrSelectorOperation::Exists => Feature::Selectors2,
1871 ParsedAttrSelectorOperation::WithValue {
1872 operator,
1873 case_sensitivity,
1874 ..
1875 } => {
1876 if case_sensitivity != ParsedCaseSensitivity::CaseSensitive {
1877 Feature::CaseInsensitive
1878 } else {
1879 match operator {
1880 AttrSelectorOperator::Equal | AttrSelectorOperator::Includes | AttrSelectorOperator::DashMatch => {
1881 Feature::Selectors2
1882 }
1883 AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {
1884 Feature::Selectors3
1885 }
1886 }
1887 }
1888 }
1889 },
1890
1891 Component::Empty | Component::Root => Feature::Selectors3,
1892 Component::Negation(selectors) => {
1893 if !targets.is_compatible(Feature::Selectors3) || !is_compatible(&*selectors, targets) {
1895 return false;
1896 }
1897 continue;
1898 }
1899
1900 Component::Nth(data) => match data.ty {
1901 NthType::Child if data.a == 0 && data.b == 1 => Feature::Selectors2,
1902 NthType::Col | NthType::LastCol => return false,
1903 _ => Feature::Selectors3,
1904 },
1905 Component::NthOf(n) => {
1906 if !targets.is_compatible(Feature::NthChildOf) || !is_compatible(n.selectors(), targets) {
1907 return false;
1908 }
1909 continue;
1910 }
1911
1912 Component::Is(selectors) => {
1914 if should_unwrap_is(selectors) && is_compatible(selectors, targets) {
1916 continue;
1917 }
1918
1919 Feature::IsSelector
1920 }
1921 Component::Where(_) | Component::Nesting => Feature::IsSelector,
1922 Component::Any(..) => return false,
1923 Component::Has(selectors) => {
1924 if !targets.is_compatible(Feature::HasSelector) || !is_compatible(&*selectors, targets) {
1925 return false;
1926 }
1927 continue;
1928 }
1929
1930 Component::Scope | Component::Host(_) | Component::Slotted(_) => Feature::Shadowdomv1,
1931
1932 Component::Part(_) => Feature::PartPseudo,
1933
1934 Component::NonTSPseudoClass(pseudo) => {
1935 match pseudo {
1936 PseudoClass::Link
1937 | PseudoClass::Visited
1938 | PseudoClass::Active
1939 | PseudoClass::Hover
1940 | PseudoClass::Focus
1941 | PseudoClass::Lang { languages: _ } => Feature::Selectors2,
1942
1943 PseudoClass::Checked | PseudoClass::Disabled | PseudoClass::Enabled | PseudoClass::Target => {
1944 Feature::Selectors3
1945 }
1946
1947 PseudoClass::AnyLink(prefix) if *prefix == VendorPrefix::None => Feature::AnyLink,
1948 PseudoClass::Indeterminate => Feature::IndeterminatePseudo,
1949
1950 PseudoClass::Fullscreen(prefix) if *prefix == VendorPrefix::None => Feature::Fullscreen,
1951
1952 PseudoClass::FocusVisible => Feature::FocusVisible,
1953 PseudoClass::FocusWithin => Feature::FocusWithin,
1954 PseudoClass::Default => Feature::DefaultPseudo,
1955 PseudoClass::Dir { direction: _ } => Feature::DirSelector,
1956 PseudoClass::Optional => Feature::OptionalPseudo,
1957 PseudoClass::PlaceholderShown(prefix) if *prefix == VendorPrefix::None => Feature::PlaceholderShown,
1958
1959 PseudoClass::TargetCurrent => Feature::TargetCurrent,
1960 PseudoClass::TargetBefore | PseudoClass::TargetAfter => Feature::TargetBeforeAfter,
1961
1962 PseudoClass::ReadOnly(prefix) | PseudoClass::ReadWrite(prefix) if *prefix == VendorPrefix::None => {
1963 Feature::ReadOnlyWrite
1964 }
1965
1966 PseudoClass::Valid | PseudoClass::Invalid | PseudoClass::Required => Feature::FormValidation,
1967
1968 PseudoClass::InRange | PseudoClass::OutOfRange => Feature::InOutOfRange,
1969
1970 PseudoClass::Autofill(prefix) if *prefix == VendorPrefix::None => Feature::Autofill,
1971
1972 PseudoClass::State { .. } => Feature::StatePseudoClass,
1973
1974 PseudoClass::Current
1976 | PseudoClass::Past
1977 | PseudoClass::Future
1978 | PseudoClass::Playing
1979 | PseudoClass::Paused
1980 | PseudoClass::Seeking
1981 | PseudoClass::Stalled
1982 | PseudoClass::Buffering
1983 | PseudoClass::Muted
1984 | PseudoClass::VolumeLocked
1985 | PseudoClass::TargetWithin
1986 | PseudoClass::LocalLink
1987 | PseudoClass::Blank
1988 | PseudoClass::UserInvalid
1989 | PseudoClass::UserValid
1990 | PseudoClass::Defined
1991 | PseudoClass::ActiveViewTransition
1992 | PseudoClass::ActiveViewTransitionType { .. } => return false,
1993
1994 PseudoClass::Custom { .. } | _ => return false,
1995 }
1996 }
1997
1998 Component::PseudoElement(pseudo) => match pseudo {
1999 PseudoElement::After | PseudoElement::Before => Feature::Gencontent,
2000 PseudoElement::FirstLine => Feature::FirstLine,
2001 PseudoElement::FirstLetter => Feature::FirstLetter,
2002 PseudoElement::DetailsContent => Feature::DetailsContent,
2003 PseudoElement::TargetText => Feature::TargetText,
2004 PseudoElement::SearchText => Feature::SearchText,
2005 PseudoElement::Selection(prefix) if *prefix == VendorPrefix::None => Feature::Selection,
2006 PseudoElement::Placeholder(prefix) if *prefix == VendorPrefix::None => Feature::Placeholder,
2007 PseudoElement::HighlightFunction { name: _ } => Feature::Highlight,
2008 PseudoElement::Marker => Feature::MarkerPseudo,
2009 PseudoElement::Backdrop(prefix) if *prefix == VendorPrefix::None => Feature::Dialog,
2010 PseudoElement::Cue => Feature::Cue,
2011 PseudoElement::CueFunction { selector: _ } => Feature::CueFunction,
2012 PseudoElement::ViewTransition
2013 | PseudoElement::ViewTransitionNew { .. }
2014 | PseudoElement::ViewTransitionOld { .. }
2015 | PseudoElement::ViewTransitionGroup { .. }
2016 | PseudoElement::ViewTransitionImagePair { .. } => Feature::ViewTransition,
2017 PseudoElement::PickerFunction { identifier: _ } => Feature::Picker,
2018 PseudoElement::PickerIcon => Feature::PickerIcon,
2019 PseudoElement::Checkmark => Feature::Checkmark,
2020 PseudoElement::GrammarError => Feature::GrammarError,
2021 PseudoElement::SpellingError => Feature::SpellingError,
2022 PseudoElement::Custom { name: _ } | _ => return false,
2023 },
2024
2025 Component::Combinator(combinator) => match combinator {
2026 Combinator::Child | Combinator::NextSibling => Feature::Selectors2,
2027 Combinator::LaterSibling => Feature::Selectors3,
2028 _ => continue,
2029 },
2030 };
2031
2032 if !targets.is_compatible(feature) {
2033 return false;
2034 }
2035 }
2036 }
2037
2038 true
2039}
2040
2041pub(crate) fn is_equivalent<'i>(selectors: &[Selector<'i>], other: &[Selector<'i>]) -> bool {
2043 if selectors.len() != other.len() {
2044 return false;
2045 }
2046
2047 for (i, a) in selectors.iter().enumerate() {
2048 let b = &other[i];
2049 if a.len() != b.len() {
2050 return false;
2051 }
2052
2053 for (a, b) in a.iter_raw_match_order().zip(b.iter_raw_match_order()) {
2054 let is_equivalent = match (a, b) {
2055 (Component::NonTSPseudoClass(a_ps), Component::NonTSPseudoClass(b_ps)) => a_ps.is_equivalent(b_ps),
2056 (Component::PseudoElement(a_pe), Component::PseudoElement(b_pe)) => a_pe.is_equivalent(b_pe),
2057 (Component::Any(_, a), Component::Is(b))
2058 | (Component::Is(a), Component::Any(_, b))
2059 | (Component::Any(_, a), Component::Any(_, b))
2060 | (Component::Is(a), Component::Is(b)) => is_equivalent(&*a, &*b),
2061 (a, b) => a == b,
2062 };
2063
2064 if !is_equivalent {
2065 return false;
2066 }
2067 }
2068 }
2069
2070 true
2071}
2072
2073pub(crate) fn get_prefix(selectors: &SelectorList) -> VendorPrefix {
2076 let mut prefix = VendorPrefix::empty();
2077 for selector in &selectors.0 {
2078 for component in selector.iter_raw_match_order() {
2079 let p = match component {
2080 Component::NonTSPseudoClass(PseudoClass::Lang { .. })
2082 | Component::NonTSPseudoClass(PseudoClass::Dir { .. })
2083 | Component::Is(..)
2084 | Component::Where(..)
2085 | Component::Has(..)
2086 | Component::Negation(..) => VendorPrefix::None,
2087 Component::Any(prefix, _) => *prefix,
2088 Component::NonTSPseudoClass(pc) => pc.get_prefix(),
2089 Component::PseudoElement(pe) => pe.get_prefix(),
2090 _ => VendorPrefix::empty(),
2091 };
2092
2093 if !p.is_empty() {
2094 let prefix_without_none = prefix - VendorPrefix::None;
2096 if prefix_without_none.is_empty() || prefix_without_none == p {
2097 prefix |= p;
2098 } else {
2099 return VendorPrefix::empty();
2100 }
2101 }
2102 }
2103 }
2104
2105 prefix
2106}
2107
2108const RTL_LANGS: &[&str] = &[
2109 "ae", "ar", "arc", "bcc", "bqi", "ckb", "dv", "fa", "glk", "he", "ku", "mzn", "nqo", "pnb", "ps", "sd", "ug",
2110 "ur", "yi",
2111];
2112
2113pub(crate) fn downlevel_selectors(selectors: &mut [Selector], targets: Targets) -> VendorPrefix {
2116 let mut necessary_prefixes = VendorPrefix::empty();
2117 for selector in selectors {
2118 for component in selector.iter_mut_raw_match_order() {
2119 necessary_prefixes |= downlevel_component(component, targets);
2120 }
2121 }
2122
2123 necessary_prefixes
2124}
2125
2126fn downlevel_component<'i>(component: &mut Component<'i>, targets: Targets) -> VendorPrefix {
2127 match component {
2128 Component::NonTSPseudoClass(pc) => {
2129 match pc {
2130 PseudoClass::Dir { direction: dir } => {
2131 if should_compile!(targets, DirSelector) {
2132 *component = downlevel_dir(*dir, targets);
2133 downlevel_component(component, targets)
2134 } else {
2135 VendorPrefix::empty()
2136 }
2137 }
2138 PseudoClass::Lang { languages: langs } => {
2139 if langs.len() > 1 && should_compile!(targets, LangSelectorList) {
2142 *component = Component::Is(lang_list_to_selectors(&langs));
2143 downlevel_component(component, targets)
2144 } else {
2145 VendorPrefix::empty()
2146 }
2147 }
2148 _ => pc.get_necessary_prefixes(targets),
2149 }
2150 }
2151 Component::PseudoElement(pe) => pe.get_necessary_prefixes(targets),
2152 Component::Is(selectors) => {
2153 let mut necessary_prefixes = downlevel_selectors(&mut **selectors, targets);
2154
2155 if should_compile!(targets, IsSelector)
2158 && !should_unwrap_is(selectors)
2159 && selectors.iter().all(|selector| !selector.has_combinator())
2160 {
2161 necessary_prefixes |= targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AnyPseudo)
2162 } else {
2163 necessary_prefixes |= VendorPrefix::None
2164 }
2165
2166 necessary_prefixes
2167 }
2168 Component::Negation(selectors) => {
2169 let mut necessary_prefixes = downlevel_selectors(&mut **selectors, targets);
2170
2171 if selectors.len() > 1 && should_compile!(targets, NotSelectorList) {
2175 *component =
2176 Component::Negation(vec![Selector::from(Component::Is(selectors.clone()))].into_boxed_slice());
2177
2178 if should_compile!(targets, IsSelector) {
2179 necessary_prefixes |= targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AnyPseudo)
2180 } else {
2181 necessary_prefixes |= VendorPrefix::None
2182 }
2183 }
2184
2185 necessary_prefixes
2186 }
2187 Component::Where(selectors) | Component::Any(_, selectors) | Component::Has(selectors) => {
2188 downlevel_selectors(&mut **selectors, targets)
2189 }
2190 _ => VendorPrefix::empty(),
2191 }
2192}
2193
2194fn lang_list_to_selectors<'i>(langs: &Vec<CowArcStr<'i>>) -> Box<[Selector<'i>]> {
2195 langs
2196 .iter()
2197 .map(|lang| {
2198 Selector::from(Component::NonTSPseudoClass(PseudoClass::Lang {
2199 languages: vec![lang.clone()],
2200 }))
2201 })
2202 .collect::<Vec<Selector>>()
2203 .into_boxed_slice()
2204}
2205
2206fn downlevel_dir<'i>(dir: Direction, targets: Targets) -> Component<'i> {
2207 let langs = RTL_LANGS.iter().map(|lang| (*lang).into()).collect();
2210 if !should_compile!(targets, LangSelectorList) {
2211 let c = Component::NonTSPseudoClass(PseudoClass::Lang { languages: langs });
2212 if dir == Direction::Ltr {
2213 Component::Negation(vec![Selector::from(c)].into_boxed_slice())
2214 } else {
2215 c
2216 }
2217 } else {
2218 if dir == Direction::Ltr {
2219 Component::Negation(lang_list_to_selectors(&langs))
2220 } else {
2221 Component::Is(lang_list_to_selectors(&langs))
2222 }
2223 }
2224}
2225
2226pub(crate) fn is_unused(
2229 selectors: &mut std::slice::Iter<Selector>,
2230 unused_symbols: &HashSet<String>,
2231 parent_is_unused: bool,
2232) -> bool {
2233 if unused_symbols.is_empty() {
2234 return false;
2235 }
2236
2237 selectors.all(|selector| {
2238 for component in selector.iter_raw_match_order() {
2239 match component {
2240 Component::Class(name) | Component::ID(name) => {
2241 if unused_symbols.contains(&name.0.to_string()) {
2242 return true;
2243 }
2244 }
2245 Component::Is(is) | Component::Where(is) | Component::Any(_, is) => {
2246 if is_unused(&mut is.iter(), unused_symbols, parent_is_unused) {
2247 return true;
2248 }
2249 }
2250 Component::Nesting => {
2251 if parent_is_unused {
2252 return true;
2253 }
2254 }
2255 _ => {}
2256 }
2257 }
2258
2259 false
2260 })
2261}
2262
2263pub(crate) fn is_pure_css_modules_selector(selector: &Selector) -> bool {
2265 use parcel_selectors::parser::Component;
2266 selector.iter_raw_match_order().any(|c| match c {
2267 Component::Class(_) | Component::ID(_) => true,
2268 Component::Is(s) | Component::Where(s) | Component::Has(s) | Component::Any(_, s) | Component::Negation(s) => {
2269 s.iter().any(is_pure_css_modules_selector)
2270 }
2271 Component::NthOf(nth) => nth.selectors().iter().any(is_pure_css_modules_selector),
2272 Component::Slotted(s) => is_pure_css_modules_selector(&s),
2273 Component::Host(s) => s.as_ref().map(is_pure_css_modules_selector).unwrap_or(false),
2274 Component::NonTSPseudoClass(pc) => match pc {
2275 PseudoClass::Local { selector } => is_pure_css_modules_selector(&*selector),
2276 _ => false,
2277 },
2278 _ => false,
2279 })
2280}
2281
2282#[cfg(feature = "visitor")]
2283#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
2284impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for SelectorList<'i> {
2285 const CHILD_TYPES: VisitTypes = VisitTypes::SELECTORS;
2286
2287 fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
2288 if visitor.visit_types().contains(VisitTypes::SELECTORS) {
2289 visitor.visit_selector_list(self)
2290 } else {
2291 self.visit_children(visitor)
2292 }
2293 }
2294
2295 fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {
2296 self.0.iter_mut().try_for_each(|selector| Visit::visit(selector, visitor))
2297 }
2298}
2299
2300#[cfg(feature = "visitor")]
2301#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
2302impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for Selector<'i> {
2303 const CHILD_TYPES: VisitTypes = VisitTypes::SELECTORS;
2304
2305 fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
2306 visitor.visit_selector(self)
2307 }
2308
2309 fn visit_children(&mut self, _visitor: &mut V) -> Result<(), V::Error> {
2310 Ok(())
2311 }
2312}
2313
2314impl<'i> ParseWithOptions<'i> for Selector<'i> {
2315 fn parse_with_options<'t>(
2316 input: &mut Parser<'i, 't>,
2317 options: &ParserOptions<'i>,
2318 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
2319 Selector::parse(
2320 &SelectorParser {
2321 is_nesting_allowed: true,
2322 options: &options,
2323 },
2324 input,
2325 )
2326 }
2327}
2328
2329impl<'i> ParseWithOptions<'i> for SelectorList<'i> {
2330 fn parse_with_options<'t>(
2331 input: &mut Parser<'i, 't>,
2332 options: &ParserOptions<'i>,
2333 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
2334 SelectorList::parse(
2335 &SelectorParser {
2336 is_nesting_allowed: true,
2337 options: &options,
2338 },
2339 input,
2340 parcel_selectors::parser::ParseErrorRecovery::DiscardList,
2341 parcel_selectors::parser::NestingRequirement::None,
2342 )
2343 }
2344}