1use crate::compat::Feature;
4use crate::error::{ParserError, PrinterError};
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 std::collections::HashSet;
25use std::fmt;
26
27#[cfg(feature = "serde")]
28use crate::serialization::*;
29
30mod private {
31 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
32 #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
33 pub struct Selectors;
34
35 #[cfg(feature = "into_owned")]
36 impl<'any> ::static_self::IntoOwned<'any> for Selectors {
37 type Owned = Self;
38
39 fn into_owned(self) -> Self::Owned {
40 self
41 }
42 }
43}
44
45#[cfg(feature = "into_owned")]
46fn _assert_into_owned() {
47 use static_self::IntoOwned;
50 fn _assert<'any, T: IntoOwned<'any>>() {}
51
52 _assert::<SelectorList>();
53}
54
55use private::Selectors;
56
57pub type SelectorList<'i> = parcel_selectors::SelectorList<'i, Selectors>;
59pub type Selector<'i> = parcel_selectors::parser::Selector<'i, Selectors>;
61pub type Component<'i> = parcel_selectors::parser::Component<'i, Selectors>;
63pub use parcel_selectors::parser::Combinator;
65
66impl<'i> SelectorImpl<'i> for Selectors {
67 type AttrValue = CSSString<'i>;
68 type Identifier = Ident<'i>;
69 type LocalName = Ident<'i>;
70 type NamespacePrefix = Ident<'i>;
71 type NamespaceUrl = CowArcStr<'i>;
72 type BorrowedNamespaceUrl = CowArcStr<'i>;
73 type BorrowedLocalName = Ident<'i>;
74
75 type NonTSPseudoClass = PseudoClass<'i>;
76 type PseudoElement = PseudoElement<'i>;
77 type VendorPrefix = VendorPrefix;
78
79 type ExtraMatchingData = ();
80
81 fn to_css<W: fmt::Write>(selectors: &SelectorList<'i>, dest: &mut W) -> std::fmt::Result {
82 let mut printer = Printer::new(dest, PrinterOptions::default());
83 serialize_selector_list(selectors.0.iter(), &mut printer, None, false).map_err(|_| std::fmt::Error)
84 }
85}
86
87pub(crate) struct SelectorParser<'a, 'o, 'i> {
88 pub is_nesting_allowed: bool,
89 pub options: &'a ParserOptions<'o, 'i>,
90}
91
92impl<'a, 'o, 'i> parcel_selectors::parser::Parser<'i> for SelectorParser<'a, 'o, 'i> {
93 type Impl = Selectors;
94 type Error = ParserError<'i>;
95
96 fn parse_non_ts_pseudo_class(
97 &self,
98 loc: SourceLocation,
99 name: CowRcStr<'i>,
100 ) -> Result<PseudoClass<'i>, ParseError<'i, Self::Error>> {
101 use PseudoClass::*;
102 let pseudo_class = match_ignore_ascii_case! { &name,
103 "hover" => Hover,
105 "active" => Active,
106 "focus" => Focus,
107 "focus-visible" => FocusVisible,
108 "focus-within" => FocusWithin,
109
110 "current" => Current,
112 "past" => Past,
113 "future" => Future,
114
115 "playing" => Playing,
117 "paused" => Paused,
118 "seeking" => Seeking,
119 "buffering" => Buffering,
120 "stalled" => Stalled,
121 "muted" => Muted,
122 "volume-locked" => VolumeLocked,
123
124 "fullscreen" => Fullscreen(VendorPrefix::None),
126 "-webkit-full-screen" => Fullscreen(VendorPrefix::WebKit),
127 "-moz-full-screen" => Fullscreen(VendorPrefix::Moz),
128 "-ms-fullscreen" => Fullscreen(VendorPrefix::Ms),
129
130 "open" => Open,
132 "closed" => Closed,
133 "modal" => Modal,
134 "picture-in-picture" => PictureInPicture,
135
136 "popover-open" => PopoverOpen,
138
139 "defined" => Defined,
141
142 "any-link" => AnyLink(VendorPrefix::None),
144 "-webkit-any-link" => AnyLink(VendorPrefix::WebKit),
145 "-moz-any-link" => AnyLink(VendorPrefix::Moz),
146 "link" => Link,
147 "local-link" => LocalLink,
148 "target" => Target,
149 "target-within" => TargetWithin,
150 "visited" => Visited,
151
152 "enabled" => Enabled,
154 "disabled" => Disabled,
155 "read-only" => ReadOnly(VendorPrefix::None),
156 "-moz-read-only" => ReadOnly(VendorPrefix::Moz),
157 "read-write" => ReadWrite(VendorPrefix::None),
158 "-moz-read-write" => ReadWrite(VendorPrefix::Moz),
159 "placeholder-shown" => PlaceholderShown(VendorPrefix::None),
160 "-moz-placeholder-shown" => PlaceholderShown(VendorPrefix::Moz),
161 "-ms-placeholder-shown" => PlaceholderShown(VendorPrefix::Ms),
162 "default" => Default,
163 "checked" => Checked,
164 "indeterminate" => Indeterminate,
165 "blank" => Blank,
166 "valid" => Valid,
167 "invalid" => Invalid,
168 "in-range" => InRange,
169 "out-of-range" => OutOfRange,
170 "required" => Required,
171 "optional" => Optional,
172 "user-valid" => UserValid,
173 "user-invalid" => UserInvalid,
174
175 "autofill" => Autofill(VendorPrefix::None),
177 "-webkit-autofill" => Autofill(VendorPrefix::WebKit),
178 "-o-autofill" => Autofill(VendorPrefix::O),
179
180 "horizontal" => WebKitScrollbar(WebKitScrollbarPseudoClass::Horizontal),
182 "vertical" => WebKitScrollbar(WebKitScrollbarPseudoClass::Vertical),
183 "decrement" => WebKitScrollbar(WebKitScrollbarPseudoClass::Decrement),
184 "increment" => WebKitScrollbar(WebKitScrollbarPseudoClass::Increment),
185 "start" => WebKitScrollbar(WebKitScrollbarPseudoClass::Start),
186 "end" => WebKitScrollbar(WebKitScrollbarPseudoClass::End),
187 "double-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::DoubleButton),
188 "single-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::SingleButton),
189 "no-button" => WebKitScrollbar(WebKitScrollbarPseudoClass::NoButton),
190 "corner-present" => WebKitScrollbar(WebKitScrollbarPseudoClass::CornerPresent),
191 "window-inactive" => WebKitScrollbar(WebKitScrollbarPseudoClass::WindowInactive),
192
193 "local" | "global" if self.options.css_modules.is_some() => {
194 return Err(loc.new_custom_error(SelectorParseErrorKind::AmbiguousCssModuleClass(name.clone())))
195 },
196
197 _ => {
198 if !name.starts_with('-') {
199 self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
200 }
201 Custom { name: name.into() }
202 }
203 };
204
205 Ok(pseudo_class)
206 }
207
208 fn parse_non_ts_functional_pseudo_class<'t>(
209 &self,
210 name: CowRcStr<'i>,
211 parser: &mut cssparser::Parser<'i, 't>,
212 ) -> Result<PseudoClass<'i>, ParseError<'i, Self::Error>> {
213 use PseudoClass::*;
214 let pseudo_class = match_ignore_ascii_case! { &name,
215 "lang" => {
216 let languages = parser.parse_comma_separated(|parser| {
217 parser.expect_ident_or_string()
218 .map(|s| s.into())
219 .map_err(|e| e.into())
220 })?;
221 Lang { languages }
222 },
223 "dir" => Dir { direction: Direction::parse(parser)? },
224 "local" if self.options.css_modules.is_some() => Local { selector: Box::new(Selector::parse(self, parser)?) },
225 "global" if self.options.css_modules.is_some() => Global { selector: Box::new(Selector::parse(self, parser)?) },
226 _ => {
227 if !name.starts_with('-') {
228 self.options.warn(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
229 }
230 let mut args = Vec::new();
231 TokenList::parse_raw(parser, &mut args, &self.options, 0)?;
232 CustomFunction {
233 name: name.into(),
234 arguments: TokenList(args)
235 }
236 },
237 };
238
239 Ok(pseudo_class)
240 }
241
242 fn parse_any_prefix<'t>(&self, name: &str) -> Option<VendorPrefix> {
243 match_ignore_ascii_case! { &name,
244 "-webkit-any" => Some(VendorPrefix::WebKit),
245 "-moz-any" => Some(VendorPrefix::Moz),
246 _ => None
247 }
248 }
249
250 fn parse_pseudo_element(
251 &self,
252 loc: SourceLocation,
253 name: CowRcStr<'i>,
254 ) -> Result<PseudoElement<'i>, ParseError<'i, Self::Error>> {
255 use PseudoElement::*;
256 let pseudo_element = match_ignore_ascii_case! { &name,
257 "before" => Before,
258 "after" => After,
259 "first-line" => FirstLine,
260 "first-letter" => FirstLetter,
261 "cue" => Cue,
262 "cue-region" => CueRegion,
263 "selection" => Selection(VendorPrefix::None),
264 "-moz-selection" => Selection(VendorPrefix::Moz),
265 "placeholder" => Placeholder(VendorPrefix::None),
266 "-webkit-input-placeholder" => Placeholder(VendorPrefix::WebKit),
267 "-moz-placeholder" => Placeholder(VendorPrefix::Moz),
268 "-ms-input-placeholder" => Placeholder(VendorPrefix::Moz),
269 "marker" => Marker,
270 "backdrop" => Backdrop(VendorPrefix::None),
271 "-webkit-backdrop" => Backdrop(VendorPrefix::WebKit),
272 "file-selector-button" => FileSelectorButton(VendorPrefix::None),
273 "-webkit-file-upload-button" => FileSelectorButton(VendorPrefix::WebKit),
274 "-ms-browse" => FileSelectorButton(VendorPrefix::Ms),
275
276 "-webkit-scrollbar" => WebKitScrollbar(WebKitScrollbarPseudoElement::Scrollbar),
277 "-webkit-scrollbar-button" => WebKitScrollbar(WebKitScrollbarPseudoElement::Button),
278 "-webkit-scrollbar-track" => WebKitScrollbar(WebKitScrollbarPseudoElement::Track),
279 "-webkit-scrollbar-track-piece" => WebKitScrollbar(WebKitScrollbarPseudoElement::TrackPiece),
280 "-webkit-scrollbar-thumb" => WebKitScrollbar(WebKitScrollbarPseudoElement::Thumb),
281 "-webkit-scrollbar-corner" => WebKitScrollbar(WebKitScrollbarPseudoElement::Corner),
282 "-webkit-resizer" => WebKitScrollbar(WebKitScrollbarPseudoElement::Resizer),
283
284 "view-transition" => ViewTransition,
285
286 _ => {
287 if !name.starts_with('-') {
288 self.options.warn(loc.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
289 }
290 Custom { name: name.into() }
291 }
292 };
293
294 Ok(pseudo_element)
295 }
296
297 fn parse_functional_pseudo_element<'t>(
298 &self,
299 name: CowRcStr<'i>,
300 arguments: &mut Parser<'i, 't>,
301 ) -> Result<<Self::Impl as SelectorImpl<'i>>::PseudoElement, ParseError<'i, Self::Error>> {
302 use PseudoElement::*;
303 let pseudo_element = match_ignore_ascii_case! { &name,
304 "cue" => CueFunction { selector: Box::new(Selector::parse(self, arguments)?) },
305 "cue-region" => CueRegionFunction { selector: Box::new(Selector::parse(self, arguments)?) },
306 "view-transition-group" => ViewTransitionGroup { part_name: ViewTransitionPartName::parse(arguments)? },
307 "view-transition-image-pair" => ViewTransitionImagePair { part_name: ViewTransitionPartName::parse(arguments)? },
308 "view-transition-old" => ViewTransitionOld { part_name: ViewTransitionPartName::parse(arguments)? },
309 "view-transition-new" => ViewTransitionNew { part_name: ViewTransitionPartName::parse(arguments)? },
310 _ => {
311 if !name.starts_with('-') {
312 self.options.warn(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())));
313 }
314 let mut args = Vec::new();
315 TokenList::parse_raw(arguments, &mut args, &self.options, 0)?;
316 CustomFunction { name: name.into(), arguments: TokenList(args) }
317 }
318 };
319
320 Ok(pseudo_element)
321 }
322
323 #[inline]
324 fn parse_slotted(&self) -> bool {
325 true
326 }
327
328 #[inline]
329 fn parse_host(&self) -> bool {
330 true
331 }
332
333 #[inline]
334 fn parse_is_and_where(&self) -> bool {
335 true
336 }
337
338 #[inline]
339 fn parse_part(&self) -> bool {
340 true
341 }
342
343 fn default_namespace(&self) -> Option<CowArcStr<'i>> {
344 None
345 }
346
347 fn namespace_for_prefix(&self, prefix: &Ident<'i>) -> Option<CowArcStr<'i>> {
348 Some(prefix.0.clone())
349 }
350
351 #[inline]
352 fn is_nesting_allowed(&self) -> bool {
353 self.is_nesting_allowed
354 }
355
356 fn deep_combinator_enabled(&self) -> bool {
357 self.options.flags.contains(ParserFlags::DEEP_SELECTOR_COMBINATOR)
358 }
359}
360
361enum_property! {
362 #[derive(Eq, Hash)]
364 pub enum Direction {
365 Ltr,
367 Rtl,
369 }
370}
371
372#[derive(Clone, PartialEq, Eq, Hash)]
374#[cfg_attr(
375 feature = "serde",
376 derive(serde::Serialize, serde::Deserialize),
377 serde(tag = "kind", rename_all = "kebab-case")
378)]
379#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
380#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
381pub enum PseudoClass<'i> {
382 Lang {
385 #[cfg_attr(feature = "serde", serde(borrow))]
387 languages: Vec<CowArcStr<'i>>,
388 },
389 Dir {
391 direction: Direction,
393 },
394
395 Hover,
398 Active,
400 Focus,
402 FocusVisible,
404 FocusWithin,
406
407 Current,
410 Past,
412 Future,
414
415 Playing,
418 Paused,
420 Seeking,
422 Buffering,
424 Stalled,
426 Muted,
428 VolumeLocked,
430
431 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
433 Fullscreen(VendorPrefix),
434
435 Open,
438 Closed,
440 Modal,
442 PictureInPicture,
444
445 PopoverOpen,
448
449 Defined,
451
452 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
455 AnyLink(VendorPrefix),
456 Link,
458 LocalLink,
460 Target,
462 TargetWithin,
464 Visited,
466
467 Enabled,
470 Disabled,
472 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
474 ReadOnly(VendorPrefix),
475 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
477 ReadWrite(VendorPrefix),
478 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
480 PlaceholderShown(VendorPrefix),
481 Default,
483 Checked,
485 Indeterminate,
487 Blank,
489 Valid,
491 Invalid,
493 InRange,
495 OutOfRange,
497 Required,
499 Optional,
501 UserValid,
503 UserInvalid,
505
506 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
508 Autofill(VendorPrefix),
509
510 Local {
513 selector: Box<Selector<'i>>,
515 },
516 Global {
518 selector: Box<Selector<'i>>,
520 },
521
522 #[cfg_attr(
525 feature = "serde",
526 serde(rename = "webkit-scrollbar", with = "ValueWrapper::<WebKitScrollbarPseudoClass>")
527 )]
528 WebKitScrollbar(WebKitScrollbarPseudoClass),
529 Custom {
531 name: CowArcStr<'i>,
533 },
534 CustomFunction {
536 name: CowArcStr<'i>,
538 arguments: TokenList<'i>,
540 },
541}
542
543#[derive(Clone, Eq, PartialEq, Hash)]
545#[cfg_attr(
546 feature = "serde",
547 derive(serde::Serialize, serde::Deserialize),
548 serde(rename_all = "kebab-case")
549)]
550#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
551#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
552pub enum WebKitScrollbarPseudoClass {
553 Horizontal,
555 Vertical,
557 Decrement,
559 Increment,
561 Start,
563 End,
565 DoubleButton,
567 SingleButton,
569 NoButton,
571 CornerPresent,
573 WindowInactive,
575}
576
577impl<'i> parcel_selectors::parser::NonTSPseudoClass<'i> for PseudoClass<'i> {
578 type Impl = Selectors;
579
580 fn is_active_or_hover(&self) -> bool {
581 matches!(*self, PseudoClass::Active | PseudoClass::Hover)
582 }
583
584 fn is_user_action_state(&self) -> bool {
585 matches!(
586 *self,
587 PseudoClass::Active
588 | PseudoClass::Hover
589 | PseudoClass::Focus
590 | PseudoClass::FocusWithin
591 | PseudoClass::FocusVisible
592 )
593 }
594
595 fn is_valid_before_webkit_scrollbar(&self) -> bool {
596 !matches!(*self, PseudoClass::WebKitScrollbar(..))
597 }
598
599 fn is_valid_after_webkit_scrollbar(&self) -> bool {
600 matches!(
602 *self,
603 PseudoClass::WebKitScrollbar(..)
604 | PseudoClass::Enabled
605 | PseudoClass::Disabled
606 | PseudoClass::Hover
607 | PseudoClass::Active
608 )
609 }
610}
611
612impl<'i> cssparser::ToCss for PseudoClass<'i> {
613 fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result
614 where
615 W: fmt::Write,
616 {
617 let mut s = String::new();
618 serialize_pseudo_class(self, &mut Printer::new(&mut s, Default::default()), None)
619 .map_err(|_| std::fmt::Error)?;
620 write!(dest, "{}", s)
621 }
622}
623
624fn serialize_pseudo_class<'a, 'i, W>(
625 pseudo_class: &PseudoClass<'i>,
626 dest: &mut Printer<W>,
627 context: Option<&StyleContext>,
628) -> Result<(), PrinterError>
629where
630 W: fmt::Write,
631{
632 use PseudoClass::*;
633 match pseudo_class {
634 Lang { languages: lang } => {
635 dest.write_str(":lang(")?;
636 let mut first = true;
637 for lang in lang {
638 if first {
639 first = false;
640 } else {
641 dest.delim(',', false)?;
642 }
643 serialize_identifier(lang, dest)?;
644 }
645 return dest.write_str(")");
646 }
647 Dir { direction: dir } => {
648 dest.write_str(":dir(")?;
649 dir.to_css(dest)?;
650 return dest.write_str(")");
651 }
652 _ => {}
653 }
654
655 macro_rules! write_prefixed {
656 ($prefix: ident, $val: expr) => {{
657 dest.write_char(':')?;
658 let vp = if !dest.vendor_prefix.is_empty() {
660 (dest.vendor_prefix & *$prefix).or_none()
661 } else {
662 *$prefix
663 };
664 vp.to_css(dest)?;
665 dest.write_str($val)
666 }};
667 }
668
669 macro_rules! pseudo {
670 ($key: ident, $s: literal) => {{
671 let class = if let Some(pseudo_classes) = &dest.pseudo_classes {
672 pseudo_classes.$key
673 } else {
674 None
675 };
676
677 if let Some(class) = class {
678 dest.write_char('.')?;
679 dest.write_ident(class, true)
680 } else {
681 dest.write_str($s)
682 }
683 }};
684 }
685
686 match pseudo_class {
687 Hover => pseudo!(hover, ":hover"),
689 Active => pseudo!(active, ":active"),
690 Focus => pseudo!(focus, ":focus"),
691 FocusVisible => pseudo!(focus_visible, ":focus-visible"),
692 FocusWithin => pseudo!(focus_within, ":focus-within"),
693
694 Current => dest.write_str(":current"),
696 Past => dest.write_str(":past"),
697 Future => dest.write_str(":future"),
698
699 Playing => dest.write_str(":playing"),
701 Paused => dest.write_str(":paused"),
702 Seeking => dest.write_str(":seeking"),
703 Buffering => dest.write_str(":buffering"),
704 Stalled => dest.write_str(":stalled"),
705 Muted => dest.write_str(":muted"),
706 VolumeLocked => dest.write_str(":volume-locked"),
707
708 Fullscreen(prefix) => {
710 dest.write_char(':')?;
711 let vp = if !dest.vendor_prefix.is_empty() {
712 (dest.vendor_prefix & *prefix).or_none()
713 } else {
714 *prefix
715 };
716 vp.to_css(dest)?;
717 if vp == VendorPrefix::WebKit || vp == VendorPrefix::Moz {
718 dest.write_str("full-screen")
719 } else {
720 dest.write_str("fullscreen")
721 }
722 }
723
724 Open => dest.write_str(":open"),
726 Closed => dest.write_str(":closed"),
727 Modal => dest.write_str(":modal"),
728 PictureInPicture => dest.write_str(":picture-in-picture"),
729
730 PopoverOpen => dest.write_str(":popover-open"),
732
733 Defined => dest.write_str(":defined"),
735
736 AnyLink(prefix) => write_prefixed!(prefix, "any-link"),
738 Link => dest.write_str(":link"),
739 LocalLink => dest.write_str(":local-link"),
740 Target => dest.write_str(":target"),
741 TargetWithin => dest.write_str(":target-within"),
742 Visited => dest.write_str(":visited"),
743
744 Enabled => dest.write_str(":enabled"),
746 Disabled => dest.write_str(":disabled"),
747 ReadOnly(prefix) => write_prefixed!(prefix, "read-only"),
748 ReadWrite(prefix) => write_prefixed!(prefix, "read-write"),
749 PlaceholderShown(prefix) => write_prefixed!(prefix, "placeholder-shown"),
750 Default => dest.write_str(":default"),
751 Checked => dest.write_str(":checked"),
752 Indeterminate => dest.write_str(":indeterminate"),
753 Blank => dest.write_str(":blank"),
754 Valid => dest.write_str(":valid"),
755 Invalid => dest.write_str(":invalid"),
756 InRange => dest.write_str(":in-range"),
757 OutOfRange => dest.write_str(":out-of-range"),
758 Required => dest.write_str(":required"),
759 Optional => dest.write_str(":optional"),
760 UserValid => dest.write_str(":user-valid"),
761 UserInvalid => dest.write_str(":user-invalid"),
762
763 Autofill(prefix) => write_prefixed!(prefix, "autofill"),
765
766 Local { selector } => serialize_selector(selector, dest, context, false),
767 Global { selector } => {
768 let css_module = std::mem::take(&mut dest.css_module);
769 serialize_selector(selector, dest, context, false)?;
770 dest.css_module = css_module;
771 Ok(())
772 }
773
774 WebKitScrollbar(s) => {
776 use WebKitScrollbarPseudoClass::*;
777 dest.write_str(match s {
778 Horizontal => ":horizontal",
779 Vertical => ":vertical",
780 Decrement => ":decrement",
781 Increment => ":increment",
782 Start => ":start",
783 End => ":end",
784 DoubleButton => ":double-button",
785 SingleButton => ":single-button",
786 NoButton => ":no-button",
787 CornerPresent => ":corner-present",
788 WindowInactive => ":window-inactive",
789 })
790 }
791
792 Lang { languages: _ } | Dir { direction: _ } => unreachable!(),
793 Custom { name } => {
794 dest.write_char(':')?;
795 return dest.write_str(&name);
796 }
797 CustomFunction { name, arguments: args } => {
798 dest.write_char(':')?;
799 dest.write_str(name)?;
800 dest.write_char('(')?;
801 args.to_css_raw(dest)?;
802 dest.write_char(')')
803 }
804 }
805}
806
807impl<'i> PseudoClass<'i> {
808 pub(crate) fn is_equivalent(&self, other: &PseudoClass<'i>) -> bool {
809 use PseudoClass::*;
810 match (self, other) {
811 (Fullscreen(_), Fullscreen(_))
812 | (AnyLink(_), AnyLink(_))
813 | (ReadOnly(_), ReadOnly(_))
814 | (ReadWrite(_), ReadWrite(_))
815 | (PlaceholderShown(_), PlaceholderShown(_))
816 | (Autofill(_), Autofill(_)) => true,
817 (a, b) => a == b,
818 }
819 }
820
821 pub(crate) fn get_prefix(&self) -> VendorPrefix {
822 use PseudoClass::*;
823 match self {
824 Fullscreen(p) | AnyLink(p) | ReadOnly(p) | ReadWrite(p) | PlaceholderShown(p) | Autofill(p) => *p,
825 _ => VendorPrefix::empty(),
826 }
827 }
828
829 pub(crate) fn get_necessary_prefixes(&mut self, targets: Targets) -> VendorPrefix {
830 use crate::prefixes::Feature;
831 use PseudoClass::*;
832 let (p, feature) = match self {
833 Fullscreen(p) => (p, Feature::PseudoClassFullscreen),
834 AnyLink(p) => (p, Feature::PseudoClassAnyLink),
835 ReadOnly(p) => (p, Feature::PseudoClassReadOnly),
836 ReadWrite(p) => (p, Feature::PseudoClassReadWrite),
837 PlaceholderShown(p) => (p, Feature::PseudoClassPlaceholderShown),
838 Autofill(p) => (p, Feature::PseudoClassAutofill),
839 _ => return VendorPrefix::empty(),
840 };
841
842 *p = targets.prefixes(*p, feature);
843 *p
844 }
845}
846
847#[derive(PartialEq, Eq, Hash, Clone, Debug)]
849#[cfg_attr(
850 feature = "serde",
851 derive(serde::Serialize, serde::Deserialize),
852 serde(tag = "kind", rename_all = "kebab-case")
853)]
854#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
855#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
856pub enum PseudoElement<'i> {
857 After,
859 Before,
861 FirstLine,
863 FirstLetter,
865 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
867 Selection(VendorPrefix),
868 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
870 Placeholder(VendorPrefix),
871 Marker,
873 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
875 Backdrop(VendorPrefix),
876 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
878 FileSelectorButton(VendorPrefix),
879 #[cfg_attr(
881 feature = "serde",
882 serde(rename = "webkit-scrollbar", with = "ValueWrapper::<WebKitScrollbarPseudoElement>")
883 )]
884 WebKitScrollbar(WebKitScrollbarPseudoElement),
885 Cue,
887 CueRegion,
889 CueFunction {
891 selector: Box<Selector<'i>>,
893 },
894 CueRegionFunction {
896 selector: Box<Selector<'i>>,
898 },
899 ViewTransition,
901 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
903 ViewTransitionGroup {
904 part_name: ViewTransitionPartName<'i>,
906 },
907 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
909 ViewTransitionImagePair {
910 part_name: ViewTransitionPartName<'i>,
912 },
913 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
915 ViewTransitionOld {
916 part_name: ViewTransitionPartName<'i>,
918 },
919 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
921 ViewTransitionNew {
922 part_name: ViewTransitionPartName<'i>,
924 },
925 Custom {
927 #[cfg_attr(feature = "serde", serde(borrow))]
929 name: CowArcStr<'i>,
930 },
931 CustomFunction {
933 name: CowArcStr<'i>,
935 arguments: TokenList<'i>,
937 },
938}
939
940#[derive(PartialEq, Eq, Clone, Debug, Hash)]
942#[cfg_attr(
943 feature = "serde",
944 derive(serde::Serialize, serde::Deserialize),
945 serde(rename_all = "kebab-case")
946)]
947#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
948#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
949pub enum WebKitScrollbarPseudoElement {
950 Scrollbar,
952 Button,
954 Track,
956 TrackPiece,
958 Thumb,
960 Corner,
962 Resizer,
964}
965
966#[derive(PartialEq, Eq, Clone, Debug, Hash)]
968#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
969pub enum ViewTransitionPartName<'i> {
970 All,
972 Name(CustomIdent<'i>),
974}
975
976#[cfg(feature = "serde")]
977#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
978impl<'i> serde::Serialize for ViewTransitionPartName<'i> {
979 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
980 where
981 S: serde::Serializer,
982 {
983 match self {
984 ViewTransitionPartName::All => serializer.serialize_str("*"),
985 ViewTransitionPartName::Name(name) => serializer.serialize_str(&name.0),
986 }
987 }
988}
989
990#[cfg(feature = "serde")]
991#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
992impl<'i, 'de: 'i> serde::Deserialize<'de> for ViewTransitionPartName<'i> {
993 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
994 where
995 D: serde::Deserializer<'de>,
996 {
997 let s = CowArcStr::deserialize(deserializer)?;
998 if s == "*" {
999 Ok(ViewTransitionPartName::All)
1000 } else {
1001 Ok(ViewTransitionPartName::Name(CustomIdent(s)))
1002 }
1003 }
1004}
1005
1006#[cfg(feature = "jsonschema")]
1007#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
1008impl<'a> schemars::JsonSchema for ViewTransitionPartName<'a> {
1009 fn is_referenceable() -> bool {
1010 true
1011 }
1012
1013 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1014 str::json_schema(gen)
1015 }
1016
1017 fn schema_name() -> String {
1018 "ViewTransitionPartName".into()
1019 }
1020}
1021
1022impl<'i> Parse<'i> for ViewTransitionPartName<'i> {
1023 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1024 if input.try_parse(|input| input.expect_delim('*')).is_ok() {
1025 return Ok(ViewTransitionPartName::All);
1026 }
1027
1028 Ok(ViewTransitionPartName::Name(CustomIdent::parse(input)?))
1029 }
1030}
1031
1032impl<'i> ToCss for ViewTransitionPartName<'i> {
1033 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1034 where
1035 W: std::fmt::Write,
1036 {
1037 match self {
1038 ViewTransitionPartName::All => dest.write_char('*'),
1039 ViewTransitionPartName::Name(name) => name.to_css(dest),
1040 }
1041 }
1042}
1043
1044impl<'i> cssparser::ToCss for PseudoElement<'i> {
1045 fn to_css<W>(&self, dest: &mut W) -> std::fmt::Result
1046 where
1047 W: fmt::Write,
1048 {
1049 let mut s = String::new();
1050 serialize_pseudo_element(self, &mut Printer::new(&mut s, Default::default()), None)
1051 .map_err(|_| std::fmt::Error)?;
1052 write!(dest, "{}", s)
1053 }
1054}
1055
1056fn serialize_pseudo_element<'a, 'i, W>(
1057 pseudo_element: &PseudoElement,
1058 dest: &mut Printer<W>,
1059 context: Option<&StyleContext>,
1060) -> Result<(), PrinterError>
1061where
1062 W: fmt::Write,
1063{
1064 use PseudoElement::*;
1065
1066 macro_rules! write_prefix {
1067 ($prefix: ident) => {{
1068 dest.write_str("::")?;
1069 let vp = if !dest.vendor_prefix.is_empty() {
1071 (dest.vendor_prefix & *$prefix).or_none()
1072 } else {
1073 *$prefix
1074 };
1075 vp.to_css(dest)?;
1076 vp
1077 }};
1078 }
1079
1080 macro_rules! write_prefixed {
1081 ($prefix: ident, $val: expr) => {{
1082 write_prefix!($prefix);
1083 dest.write_str($val)
1084 }};
1085 }
1086
1087 match pseudo_element {
1088 After => dest.write_str(":after"),
1092 Before => dest.write_str(":before"),
1093 FirstLine => dest.write_str(":first-line"),
1094 FirstLetter => dest.write_str(":first-letter"),
1095 Marker => dest.write_str("::marker"),
1096 Selection(prefix) => write_prefixed!(prefix, "selection"),
1097 Cue => dest.write_str("::cue"),
1098 CueRegion => dest.write_str("::cue-region"),
1099 CueFunction { selector } => {
1100 dest.write_str("::cue(")?;
1101 serialize_selector(selector, dest, context, false)?;
1102 dest.write_char(')')
1103 }
1104 CueRegionFunction { selector } => {
1105 dest.write_str("::cue-region(")?;
1106 serialize_selector(selector, dest, context, false)?;
1107 dest.write_char(')')
1108 }
1109 Placeholder(prefix) => {
1110 let vp = write_prefix!(prefix);
1111 if vp == VendorPrefix::WebKit || vp == VendorPrefix::Ms {
1112 dest.write_str("input-placeholder")
1113 } else {
1114 dest.write_str("placeholder")
1115 }
1116 }
1117 Backdrop(prefix) => write_prefixed!(prefix, "backdrop"),
1118 FileSelectorButton(prefix) => {
1119 let vp = write_prefix!(prefix);
1120 if vp == VendorPrefix::WebKit {
1121 dest.write_str("file-upload-button")
1122 } else if vp == VendorPrefix::Ms {
1123 dest.write_str("browse")
1124 } else {
1125 dest.write_str("file-selector-button")
1126 }
1127 }
1128 WebKitScrollbar(s) => {
1129 use WebKitScrollbarPseudoElement::*;
1130 dest.write_str(match s {
1131 Scrollbar => "::-webkit-scrollbar",
1132 Button => "::-webkit-scrollbar-button",
1133 Track => "::-webkit-scrollbar-track",
1134 TrackPiece => "::-webkit-scrollbar-track-piece",
1135 Thumb => "::-webkit-scrollbar-thumb",
1136 Corner => "::-webkit-scrollbar-corner",
1137 Resizer => "::-webkit-resizer",
1138 })
1139 }
1140 ViewTransition => dest.write_str("::view-transition"),
1141 ViewTransitionGroup { part_name } => {
1142 dest.write_str("::view-transition-group(")?;
1143 part_name.to_css(dest)?;
1144 dest.write_char(')')
1145 }
1146 ViewTransitionImagePair { part_name } => {
1147 dest.write_str("::view-transition-image-pair(")?;
1148 part_name.to_css(dest)?;
1149 dest.write_char(')')
1150 }
1151 ViewTransitionOld { part_name } => {
1152 dest.write_str("::view-transition-old(")?;
1153 part_name.to_css(dest)?;
1154 dest.write_char(')')
1155 }
1156 ViewTransitionNew { part_name } => {
1157 dest.write_str("::view-transition-new(")?;
1158 part_name.to_css(dest)?;
1159 dest.write_char(')')
1160 }
1161 Custom { name: val } => {
1162 dest.write_str("::")?;
1163 return dest.write_str(val);
1164 }
1165 CustomFunction { name, arguments: args } => {
1166 dest.write_str("::")?;
1167 dest.write_str(name)?;
1168 dest.write_char('(')?;
1169 args.to_css_raw(dest)?;
1170 dest.write_char(')')
1171 }
1172 }
1173}
1174
1175impl<'i> parcel_selectors::parser::PseudoElement<'i> for PseudoElement<'i> {
1176 type Impl = Selectors;
1177
1178 fn accepts_state_pseudo_classes(&self) -> bool {
1179 true
1181 }
1182
1183 fn valid_after_slotted(&self) -> bool {
1184 matches!(
1188 *self,
1189 PseudoElement::Before
1190 | PseudoElement::After
1191 | PseudoElement::Marker
1192 | PseudoElement::Placeholder(_)
1193 | PseudoElement::FileSelectorButton(_)
1194 )
1195 }
1196
1197 fn is_webkit_scrollbar(&self) -> bool {
1198 matches!(*self, PseudoElement::WebKitScrollbar(..))
1199 }
1200
1201 fn is_view_transition(&self) -> bool {
1202 matches!(
1203 *self,
1204 PseudoElement::ViewTransitionGroup { .. }
1205 | PseudoElement::ViewTransitionImagePair { .. }
1206 | PseudoElement::ViewTransitionNew { .. }
1207 | PseudoElement::ViewTransitionOld { .. }
1208 )
1209 }
1210
1211 fn is_unknown(&self) -> bool {
1212 matches!(
1213 *self,
1214 PseudoElement::Custom { .. } | PseudoElement::CustomFunction { .. },
1215 )
1216 }
1217}
1218
1219impl<'i> PseudoElement<'i> {
1220 pub(crate) fn is_equivalent(&self, other: &PseudoElement<'i>) -> bool {
1221 use PseudoElement::*;
1222 match (self, other) {
1223 (Selection(_), Selection(_))
1224 | (Placeholder(_), Placeholder(_))
1225 | (Backdrop(_), Backdrop(_))
1226 | (FileSelectorButton(_), FileSelectorButton(_)) => true,
1227 (a, b) => a == b,
1228 }
1229 }
1230
1231 pub(crate) fn get_prefix(&self) -> VendorPrefix {
1232 use PseudoElement::*;
1233 match self {
1234 Selection(p) | Placeholder(p) | Backdrop(p) | FileSelectorButton(p) => *p,
1235 _ => VendorPrefix::empty(),
1236 }
1237 }
1238
1239 pub(crate) fn get_necessary_prefixes(&mut self, targets: Targets) -> VendorPrefix {
1240 use crate::prefixes::Feature;
1241 use PseudoElement::*;
1242 let (p, feature) = match self {
1243 Selection(p) => (p, Feature::PseudoElementSelection),
1244 Placeholder(p) => (p, Feature::PseudoElementPlaceholder),
1245 Backdrop(p) => (p, Feature::PseudoElementBackdrop),
1246 FileSelectorButton(p) => (p, Feature::PseudoElementFileSelectorButton),
1247 _ => return VendorPrefix::empty(),
1248 };
1249
1250 *p = targets.prefixes(*p, feature);
1251 *p
1252 }
1253}
1254
1255impl<'a, 'i> ToCss for SelectorList<'i> {
1256 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1257 where
1258 W: fmt::Write,
1259 {
1260 serialize_selector_list(self.0.iter(), dest, dest.context(), false)
1261 }
1262}
1263
1264impl ToCss for Combinator {
1265 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1266 where
1267 W: fmt::Write,
1268 {
1269 match *self {
1270 Combinator::Child => dest.delim('>', true),
1271 Combinator::Descendant => dest.write_str(" "),
1272 Combinator::NextSibling => dest.delim('+', true),
1273 Combinator::LaterSibling => dest.delim('~', true),
1274 Combinator::Deep => dest.write_str(" /deep/ "),
1275 Combinator::DeepDescendant => {
1276 dest.whitespace()?;
1277 dest.write_str(">>>")?;
1278 dest.whitespace()
1279 }
1280 Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()),
1281 }
1282 }
1283}
1284
1285impl<'a, 'i> ToCss for Selector<'i> {
1287 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1288 where
1289 W: fmt::Write,
1290 {
1291 serialize_selector(self, dest, dest.context(), false)
1292 }
1293}
1294
1295fn serialize_selector<'a, 'i, W>(
1296 selector: &Selector<'i>,
1297 dest: &mut Printer<W>,
1298 context: Option<&StyleContext>,
1299 mut is_relative: bool,
1300) -> Result<(), PrinterError>
1301where
1302 W: fmt::Write,
1303{
1304 use parcel_selectors::parser::*;
1305 let mut combinators = selector.iter_raw_match_order().rev().filter_map(|x| x.as_combinator());
1318 let compound_selectors = selector.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev();
1319 let should_compile_nesting = should_compile!(dest.targets, Nesting);
1320
1321 let mut first = true;
1322 let mut combinators_exhausted = false;
1323 for mut compound in compound_selectors {
1324 debug_assert!(!combinators_exhausted);
1325
1326 if is_relative && matches!(compound.get(0), Some(Component::Scope)) {
1328 if let Some(combinator) = combinators.next() {
1329 combinator.to_css(dest)?;
1330 }
1331 compound = &compound[1..];
1332 is_relative = false;
1333 }
1334
1335 if compound.is_empty() {
1337 continue;
1338 }
1339
1340 let has_leading_nesting = first && matches!(compound[0], Component::Nesting);
1341 let first_index = if has_leading_nesting { 1 } else { 0 };
1342 first = false;
1343
1344 let (can_elide_namespace, first_non_namespace) = match compound.get(first_index) {
1355 Some(Component::ExplicitAnyNamespace)
1356 | Some(Component::ExplicitNoNamespace)
1357 | Some(Component::Namespace(..)) => (false, first_index + 1),
1358 Some(Component::DefaultNamespace(..)) => (true, first_index + 1),
1359 _ => (true, first_index),
1360 };
1361 let mut perform_step_2 = true;
1362 let next_combinator = combinators.next();
1363 if first_non_namespace == compound.len() - 1 {
1364 match (next_combinator, &compound[first_non_namespace]) {
1365 (Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (),
1373 (_, &Component::ExplicitUniversalType) => {
1374 let mut iter = compound.iter();
1377 let swap_nesting = has_leading_nesting && should_compile_nesting;
1378 if swap_nesting {
1379 iter.next();
1381 }
1382
1383 for simple in iter {
1384 serialize_component(simple, dest, context)?;
1385 }
1386
1387 if swap_nesting {
1388 serialize_nesting(dest, context, false)?;
1389 }
1390
1391 perform_step_2 = false;
1393 }
1394 _ => (),
1395 }
1396 }
1397
1398 if perform_step_2 {
1408 let mut iter = compound.iter();
1409 if has_leading_nesting && should_compile_nesting && is_type_selector(compound.get(first_non_namespace)) {
1410 let nesting = iter.next().unwrap();
1413 let local = iter.next().unwrap();
1414 serialize_component(local, dest, context)?;
1415
1416 if first_non_namespace > first_index {
1418 let local = iter.next().unwrap();
1419 serialize_component(local, dest, context)?;
1420 }
1421
1422 serialize_component(nesting, dest, context)?;
1423 } else if has_leading_nesting && should_compile_nesting {
1424 iter.next();
1426 serialize_nesting(dest, context, true)?;
1427 }
1428
1429 for simple in iter {
1430 if let Component::ExplicitUniversalType = *simple {
1431 if can_elide_namespace {
1436 continue;
1437 }
1438 }
1439 serialize_component(simple, dest, context)?;
1440 }
1441 }
1442
1443 match next_combinator {
1449 Some(c) => c.to_css(dest)?,
1450 None => combinators_exhausted = true,
1451 };
1452
1453 }
1459
1460 Ok(())
1461}
1462
1463fn serialize_component<'a, 'i, W>(
1464 component: &Component,
1465 dest: &mut Printer<W>,
1466 context: Option<&StyleContext>,
1467) -> Result<(), PrinterError>
1468where
1469 W: fmt::Write,
1470{
1471 match component {
1472 Component::Combinator(ref c) => c.to_css(dest),
1473 Component::AttributeInNoNamespace {
1474 ref local_name,
1475 operator,
1476 ref value,
1477 case_sensitivity,
1478 ..
1479 } => {
1480 dest.write_char('[')?;
1481 cssparser::ToCss::to_css(local_name, dest)?;
1482 cssparser::ToCss::to_css(operator, dest)?;
1483
1484 if dest.minify {
1485 let mut id = String::new();
1487 serialize_identifier(&value, &mut id)?;
1488
1489 let s = value.to_css_string(Default::default())?;
1490
1491 if id.len() > 0 && id.len() < s.len() {
1492 dest.write_str(&id)?;
1493 } else {
1494 dest.write_str(&s)?;
1495 }
1496 } else {
1497 value.to_css(dest)?;
1498 }
1499
1500 match case_sensitivity {
1501 parcel_selectors::attr::ParsedCaseSensitivity::CaseSensitive
1502 | parcel_selectors::attr::ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}
1503 parcel_selectors::attr::ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
1504 parcel_selectors::attr::ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?,
1505 }
1506 dest.write_char(']')
1507 }
1508 Component::Is(ref list)
1509 | Component::Where(ref list)
1510 | Component::Negation(ref list)
1511 | Component::Any(_, ref list) => {
1512 match *component {
1513 Component::Where(..) => dest.write_str(":where(")?,
1514 Component::Is(ref selectors) => {
1515 if should_unwrap_is(selectors) {
1517 serialize_selector(selectors.first().unwrap(), dest, context, false)?;
1518 return Ok(());
1519 }
1520
1521 let vp = dest.vendor_prefix;
1522 if vp.intersects(VendorPrefix::WebKit | VendorPrefix::Moz) {
1523 dest.write_char(':')?;
1524 vp.to_css(dest)?;
1525 dest.write_str("any(")?;
1526 } else {
1527 dest.write_str(":is(")?;
1528 }
1529 }
1530 Component::Negation(_) => {
1531 dest.write_str(":not(")?;
1532 }
1533 Component::Any(prefix, ..) => {
1534 let vp = dest.vendor_prefix.or(prefix);
1535 if vp.intersects(VendorPrefix::WebKit | VendorPrefix::Moz) {
1536 dest.write_char(':')?;
1537 vp.to_css(dest)?;
1538 dest.write_str("any(")?;
1539 } else {
1540 dest.write_str(":is(")?;
1541 }
1542 }
1543 _ => unreachable!(),
1544 }
1545 serialize_selector_list(list.iter(), dest, context, false)?;
1546 dest.write_str(")")
1547 }
1548 Component::Has(ref list) => {
1549 dest.write_str(":has(")?;
1550 serialize_selector_list(list.iter(), dest, context, true)?;
1551 dest.write_str(")")
1552 }
1553 Component::NonTSPseudoClass(pseudo) => serialize_pseudo_class(pseudo, dest, context),
1554 Component::PseudoElement(pseudo) => serialize_pseudo_element(pseudo, dest, context),
1555 Component::Nesting => serialize_nesting(dest, context, false),
1556 Component::Class(ref class) => {
1557 dest.write_char('.')?;
1558 dest.write_ident(&class.0, true)
1559 }
1560 Component::ID(ref id) => {
1561 dest.write_char('#')?;
1562 dest.write_ident(&id.0, true)
1563 }
1564 Component::Host(selector) => {
1565 dest.write_str(":host")?;
1566 if let Some(ref selector) = *selector {
1567 dest.write_char('(')?;
1568 selector.to_css(dest)?;
1569 dest.write_char(')')?;
1570 }
1571 Ok(())
1572 }
1573 Component::Slotted(ref selector) => {
1574 dest.write_str("::slotted(")?;
1575 selector.to_css(dest)?;
1576 dest.write_char(')')
1577 }
1578 _ => {
1579 cssparser::ToCss::to_css(component, dest)?;
1580 Ok(())
1581 }
1582 }
1583}
1584
1585fn should_unwrap_is<'i>(selectors: &Box<[Selector<'i>]>) -> bool {
1586 if selectors.len() == 1 {
1587 let first = selectors.first().unwrap();
1588 if !has_type_selector(first) && is_simple(first) {
1589 return true;
1590 }
1591 }
1592
1593 false
1594}
1595
1596fn serialize_nesting<W>(
1597 dest: &mut Printer<W>,
1598 context: Option<&StyleContext>,
1599 first: bool,
1600) -> Result<(), PrinterError>
1601where
1602 W: fmt::Write,
1603{
1604 if let Some(ctx) = context {
1605 if ctx.selectors.0.len() == 1
1610 && (first || (!has_type_selector(&ctx.selectors.0[0]) && is_simple(&ctx.selectors.0[0])))
1611 {
1612 serialize_selector(ctx.selectors.0.first().unwrap(), dest, ctx.parent, false)
1613 } else {
1614 dest.write_str(":is(")?;
1615 serialize_selector_list(ctx.selectors.0.iter(), dest, ctx.parent, false)?;
1616 dest.write_char(')')
1617 }
1618 } else {
1619 if should_compile!(dest.targets, Nesting) {
1622 dest.write_str(":scope")
1623 } else {
1624 dest.write_char('&')
1625 }
1626 }
1627}
1628
1629#[inline]
1630fn has_type_selector(selector: &Selector) -> bool {
1631 let mut iter = selector.iter_raw_match_order();
1636 let first = iter.next();
1637
1638 if is_namespace(first) {
1639 is_type_selector(iter.next())
1640 } else {
1641 is_type_selector(first)
1642 }
1643}
1644
1645#[inline]
1646fn is_simple(selector: &Selector) -> bool {
1647 !selector.iter_raw_match_order().any(|component| component.is_combinator())
1648}
1649
1650#[inline]
1651fn is_type_selector(component: Option<&Component>) -> bool {
1652 matches!(
1653 component,
1654 Some(Component::LocalName(_)) | Some(Component::ExplicitUniversalType)
1655 )
1656}
1657
1658#[inline]
1659fn is_namespace(component: Option<&Component>) -> bool {
1660 matches!(
1661 component,
1662 Some(Component::ExplicitAnyNamespace)
1663 | Some(Component::ExplicitNoNamespace)
1664 | Some(Component::Namespace(..))
1665 | Some(Component::DefaultNamespace(_))
1666 )
1667}
1668
1669fn serialize_selector_list<'a, 'i: 'a, I, W>(
1670 iter: I,
1671 dest: &mut Printer<W>,
1672 context: Option<&StyleContext>,
1673 is_relative: bool,
1674) -> Result<(), PrinterError>
1675where
1676 I: Iterator<Item = &'a Selector<'i>>,
1677 W: fmt::Write,
1678{
1679 let mut first = true;
1680 for selector in iter {
1681 if !first {
1682 dest.delim(',', false)?;
1683 }
1684 first = false;
1685 serialize_selector(selector, dest, context, is_relative)?;
1686 }
1687 Ok(())
1688}
1689
1690pub(crate) fn is_compatible(selectors: &[Selector], targets: Targets) -> bool {
1691 for selector in selectors {
1692 let iter = selector.iter_raw_match_order();
1693 for component in iter {
1694 let feature = match component {
1695 Component::ID(_) | Component::Class(_) | Component::LocalName(_) => continue,
1696
1697 Component::ExplicitAnyNamespace
1698 | Component::ExplicitNoNamespace
1699 | Component::DefaultNamespace(_)
1700 | Component::Namespace(_, _) => Feature::Namespaces,
1701
1702 Component::ExplicitUniversalType => Feature::Selectors2,
1703
1704 Component::AttributeInNoNamespaceExists { .. } => Feature::Selectors2,
1705 Component::AttributeInNoNamespace {
1706 operator,
1707 case_sensitivity,
1708 ..
1709 } => {
1710 if *case_sensitivity != ParsedCaseSensitivity::CaseSensitive {
1711 Feature::CaseInsensitive
1712 } else {
1713 match operator {
1714 AttrSelectorOperator::Equal | AttrSelectorOperator::Includes | AttrSelectorOperator::DashMatch => {
1715 Feature::Selectors2
1716 }
1717 AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {
1718 Feature::Selectors3
1719 }
1720 }
1721 }
1722 }
1723 Component::AttributeOther(attr) => match attr.operation {
1724 ParsedAttrSelectorOperation::Exists => Feature::Selectors2,
1725 ParsedAttrSelectorOperation::WithValue {
1726 operator,
1727 case_sensitivity,
1728 ..
1729 } => {
1730 if case_sensitivity != ParsedCaseSensitivity::CaseSensitive {
1731 Feature::CaseInsensitive
1732 } else {
1733 match operator {
1734 AttrSelectorOperator::Equal | AttrSelectorOperator::Includes | AttrSelectorOperator::DashMatch => {
1735 Feature::Selectors2
1736 }
1737 AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => {
1738 Feature::Selectors3
1739 }
1740 }
1741 }
1742 }
1743 },
1744
1745 Component::Empty | Component::Root => Feature::Selectors3,
1746 Component::Negation(selectors) => {
1747 if !targets.is_compatible(Feature::Selectors3) || !is_compatible(&*selectors, targets) {
1749 return false;
1750 }
1751 continue;
1752 }
1753
1754 Component::Nth(data) => match data.ty {
1755 NthType::Child if data.a == 0 && data.b == 1 => Feature::Selectors2,
1756 NthType::Col | NthType::LastCol => return false,
1757 _ => Feature::Selectors3,
1758 },
1759 Component::NthOf(n) => {
1760 if !targets.is_compatible(Feature::NthChildOf) || !is_compatible(n.selectors(), targets) {
1761 return false;
1762 }
1763 continue;
1764 }
1765
1766 Component::Is(selectors) => {
1768 if should_unwrap_is(selectors) && is_compatible(selectors, targets) {
1770 continue;
1771 }
1772
1773 Feature::IsSelector
1774 }
1775 Component::Where(_) | Component::Nesting => Feature::IsSelector,
1776 Component::Any(..) => return false,
1777 Component::Has(selectors) => {
1778 if !targets.is_compatible(Feature::HasSelector) || !is_compatible(&*selectors, targets) {
1779 return false;
1780 }
1781 continue;
1782 }
1783
1784 Component::Scope | Component::Host(_) | Component::Slotted(_) => Feature::Shadowdomv1,
1785
1786 Component::Part(_) => Feature::PartPseudo,
1787
1788 Component::NonTSPseudoClass(pseudo) => {
1789 match pseudo {
1790 PseudoClass::Link
1791 | PseudoClass::Visited
1792 | PseudoClass::Active
1793 | PseudoClass::Hover
1794 | PseudoClass::Focus
1795 | PseudoClass::Lang { languages: _ } => Feature::Selectors2,
1796
1797 PseudoClass::Checked | PseudoClass::Disabled | PseudoClass::Enabled | PseudoClass::Target => {
1798 Feature::Selectors3
1799 }
1800
1801 PseudoClass::AnyLink(prefix) if *prefix == VendorPrefix::None => Feature::AnyLink,
1802 PseudoClass::Indeterminate => Feature::IndeterminatePseudo,
1803
1804 PseudoClass::Fullscreen(prefix) if *prefix == VendorPrefix::None => Feature::Fullscreen,
1805
1806 PseudoClass::FocusVisible => Feature::FocusVisible,
1807 PseudoClass::FocusWithin => Feature::FocusWithin,
1808 PseudoClass::Default => Feature::DefaultPseudo,
1809 PseudoClass::Dir { direction: _ } => Feature::DirSelector,
1810 PseudoClass::Optional => Feature::OptionalPseudo,
1811 PseudoClass::PlaceholderShown(prefix) if *prefix == VendorPrefix::None => Feature::PlaceholderShown,
1812
1813 PseudoClass::ReadOnly(prefix) | PseudoClass::ReadWrite(prefix) if *prefix == VendorPrefix::None => {
1814 Feature::ReadOnlyWrite
1815 }
1816
1817 PseudoClass::Valid | PseudoClass::Invalid | PseudoClass::Required => Feature::FormValidation,
1818
1819 PseudoClass::InRange | PseudoClass::OutOfRange => Feature::InOutOfRange,
1820
1821 PseudoClass::Autofill(prefix) if *prefix == VendorPrefix::None => Feature::Autofill,
1822
1823 PseudoClass::Current
1825 | PseudoClass::Past
1826 | PseudoClass::Future
1827 | PseudoClass::Playing
1828 | PseudoClass::Paused
1829 | PseudoClass::Seeking
1830 | PseudoClass::Stalled
1831 | PseudoClass::Buffering
1832 | PseudoClass::Muted
1833 | PseudoClass::VolumeLocked
1834 | PseudoClass::TargetWithin
1835 | PseudoClass::LocalLink
1836 | PseudoClass::Blank
1837 | PseudoClass::UserInvalid
1838 | PseudoClass::UserValid
1839 | PseudoClass::Defined => return false,
1840
1841 PseudoClass::Custom { .. } | _ => return false,
1842 }
1843 }
1844
1845 Component::PseudoElement(pseudo) => match pseudo {
1846 PseudoElement::After | PseudoElement::Before => Feature::Gencontent,
1847 PseudoElement::FirstLine => Feature::FirstLine,
1848 PseudoElement::FirstLetter => Feature::FirstLetter,
1849 PseudoElement::Selection(prefix) if *prefix == VendorPrefix::None => Feature::Selection,
1850 PseudoElement::Placeholder(prefix) if *prefix == VendorPrefix::None => Feature::Placeholder,
1851 PseudoElement::Marker => Feature::MarkerPseudo,
1852 PseudoElement::Backdrop(prefix) if *prefix == VendorPrefix::None => Feature::Dialog,
1853 PseudoElement::Cue => Feature::Cue,
1854 PseudoElement::CueFunction { selector: _ } => Feature::CueFunction,
1855 PseudoElement::Custom { name: _ } | _ => return false,
1856 },
1857
1858 Component::Combinator(combinator) => match combinator {
1859 Combinator::Child | Combinator::NextSibling => Feature::Selectors2,
1860 Combinator::LaterSibling => Feature::Selectors3,
1861 _ => continue,
1862 },
1863 };
1864
1865 if !targets.is_compatible(feature) {
1866 return false;
1867 }
1868 }
1869 }
1870
1871 true
1872}
1873
1874pub(crate) fn is_equivalent<'i>(selectors: &[Selector<'i>], other: &[Selector<'i>]) -> bool {
1876 if selectors.len() != other.len() {
1877 return false;
1878 }
1879
1880 for (i, a) in selectors.iter().enumerate() {
1881 let b = &other[i];
1882 if a.len() != b.len() {
1883 return false;
1884 }
1885
1886 for (a, b) in a.iter_raw_match_order().zip(b.iter_raw_match_order()) {
1887 let is_equivalent = match (a, b) {
1888 (Component::NonTSPseudoClass(a_ps), Component::NonTSPseudoClass(b_ps)) => a_ps.is_equivalent(b_ps),
1889 (Component::PseudoElement(a_pe), Component::PseudoElement(b_pe)) => a_pe.is_equivalent(b_pe),
1890 (Component::Any(_, a), Component::Is(b))
1891 | (Component::Is(a), Component::Any(_, b))
1892 | (Component::Any(_, a), Component::Any(_, b))
1893 | (Component::Is(a), Component::Is(b)) => is_equivalent(&*a, &*b),
1894 (a, b) => a == b,
1895 };
1896
1897 if !is_equivalent {
1898 return false;
1899 }
1900 }
1901 }
1902
1903 true
1904}
1905
1906pub(crate) fn get_prefix(selectors: &SelectorList) -> VendorPrefix {
1909 let mut prefix = VendorPrefix::empty();
1910 for selector in &selectors.0 {
1911 for component in selector.iter_raw_match_order() {
1912 let p = match component {
1913 Component::NonTSPseudoClass(PseudoClass::Lang { .. })
1915 | Component::NonTSPseudoClass(PseudoClass::Dir { .. })
1916 | Component::Is(..)
1917 | Component::Where(..)
1918 | Component::Has(..)
1919 | Component::Negation(..) => VendorPrefix::None,
1920 Component::Any(prefix, _) => *prefix,
1921 Component::NonTSPseudoClass(pc) => pc.get_prefix(),
1922 Component::PseudoElement(pe) => pe.get_prefix(),
1923 _ => VendorPrefix::empty(),
1924 };
1925
1926 if !p.is_empty() {
1927 let prefix_without_none = prefix - VendorPrefix::None;
1929 if prefix_without_none.is_empty() || prefix_without_none == p {
1930 prefix |= p;
1931 } else {
1932 return VendorPrefix::empty();
1933 }
1934 }
1935 }
1936 }
1937
1938 prefix
1939}
1940
1941const RTL_LANGS: &[&str] = &[
1942 "ae", "ar", "arc", "bcc", "bqi", "ckb", "dv", "fa", "glk", "he", "ku", "mzn", "nqo", "pnb", "ps", "sd", "ug",
1943 "ur", "yi",
1944];
1945
1946pub(crate) fn downlevel_selectors(selectors: &mut [Selector], targets: Targets) -> VendorPrefix {
1949 let mut necessary_prefixes = VendorPrefix::empty();
1950 for selector in selectors {
1951 for component in selector.iter_mut_raw_match_order() {
1952 necessary_prefixes |= downlevel_component(component, targets);
1953 }
1954 }
1955
1956 necessary_prefixes
1957}
1958
1959fn downlevel_component<'i>(component: &mut Component<'i>, targets: Targets) -> VendorPrefix {
1960 match component {
1961 Component::NonTSPseudoClass(pc) => {
1962 match pc {
1963 PseudoClass::Dir { direction: dir } => {
1964 if should_compile!(targets, DirSelector) {
1965 *component = downlevel_dir(*dir, targets);
1966 downlevel_component(component, targets)
1967 } else {
1968 VendorPrefix::empty()
1969 }
1970 }
1971 PseudoClass::Lang { languages: langs } => {
1972 if langs.len() > 1 && should_compile!(targets, LangSelectorList) {
1975 *component = Component::Is(lang_list_to_selectors(&langs));
1976 downlevel_component(component, targets)
1977 } else {
1978 VendorPrefix::empty()
1979 }
1980 }
1981 _ => pc.get_necessary_prefixes(targets),
1982 }
1983 }
1984 Component::PseudoElement(pe) => pe.get_necessary_prefixes(targets),
1985 Component::Is(selectors) => {
1986 let mut necessary_prefixes = downlevel_selectors(&mut **selectors, targets);
1987
1988 if should_compile!(targets, IsSelector)
1991 && !should_unwrap_is(selectors)
1992 && selectors.iter().all(|selector| !selector.has_combinator())
1993 {
1994 necessary_prefixes |= targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AnyPseudo)
1995 } else {
1996 necessary_prefixes |= VendorPrefix::None
1997 }
1998
1999 necessary_prefixes
2000 }
2001 Component::Negation(selectors) => {
2002 let mut necessary_prefixes = downlevel_selectors(&mut **selectors, targets);
2003
2004 if selectors.len() > 1 && should_compile!(targets, NotSelectorList) {
2008 *component =
2009 Component::Negation(vec![Selector::from(Component::Is(selectors.clone()))].into_boxed_slice());
2010
2011 if should_compile!(targets, IsSelector) {
2012 necessary_prefixes |= targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::AnyPseudo)
2013 } else {
2014 necessary_prefixes |= VendorPrefix::None
2015 }
2016 }
2017
2018 necessary_prefixes
2019 }
2020 Component::Where(selectors) | Component::Any(_, selectors) | Component::Has(selectors) => {
2021 downlevel_selectors(&mut **selectors, targets)
2022 }
2023 _ => VendorPrefix::empty(),
2024 }
2025}
2026
2027fn lang_list_to_selectors<'i>(langs: &Vec<CowArcStr<'i>>) -> Box<[Selector<'i>]> {
2028 langs
2029 .iter()
2030 .map(|lang| {
2031 Selector::from(Component::NonTSPseudoClass(PseudoClass::Lang {
2032 languages: vec![lang.clone()],
2033 }))
2034 })
2035 .collect::<Vec<Selector>>()
2036 .into_boxed_slice()
2037}
2038
2039fn downlevel_dir<'i>(dir: Direction, targets: Targets) -> Component<'i> {
2040 let langs = RTL_LANGS.iter().map(|lang| (*lang).into()).collect();
2043 if !should_compile!(targets, LangSelectorList) {
2044 let c = Component::NonTSPseudoClass(PseudoClass::Lang { languages: langs });
2045 if dir == Direction::Ltr {
2046 Component::Negation(vec![Selector::from(c)].into_boxed_slice())
2047 } else {
2048 c
2049 }
2050 } else {
2051 if dir == Direction::Ltr {
2052 Component::Negation(lang_list_to_selectors(&langs))
2053 } else {
2054 Component::Is(lang_list_to_selectors(&langs))
2055 }
2056 }
2057}
2058
2059pub(crate) fn is_unused(
2062 selectors: &mut std::slice::Iter<Selector>,
2063 unused_symbols: &HashSet<String>,
2064 parent_is_unused: bool,
2065) -> bool {
2066 if unused_symbols.is_empty() {
2067 return false;
2068 }
2069
2070 selectors.all(|selector| {
2071 for component in selector.iter_raw_match_order() {
2072 match component {
2073 Component::Class(name) | Component::ID(name) => {
2074 if unused_symbols.contains(&name.0.to_string()) {
2075 return true;
2076 }
2077 }
2078 Component::Is(is) | Component::Where(is) | Component::Any(_, is) => {
2079 if is_unused(&mut is.iter(), unused_symbols, parent_is_unused) {
2080 return true;
2081 }
2082 }
2083 Component::Nesting => {
2084 if parent_is_unused {
2085 return true;
2086 }
2087 }
2088 _ => {}
2089 }
2090 }
2091
2092 false
2093 })
2094}
2095
2096pub(crate) fn is_pure_css_modules_selector(selector: &Selector) -> bool {
2098 use parcel_selectors::parser::Component;
2099 selector.iter_raw_match_order().any(|c| match c {
2100 Component::Class(_) | Component::ID(_) => true,
2101 Component::Is(s) | Component::Where(s) | Component::Has(s) | Component::Any(_, s) | Component::Negation(s) => {
2102 s.iter().any(is_pure_css_modules_selector)
2103 }
2104 Component::NthOf(nth) => nth.selectors().iter().any(is_pure_css_modules_selector),
2105 Component::Slotted(s) => is_pure_css_modules_selector(&s),
2106 Component::Host(s) => s.as_ref().map(is_pure_css_modules_selector).unwrap_or(false),
2107 Component::NonTSPseudoClass(pc) => match pc {
2108 PseudoClass::Local { selector } => is_pure_css_modules_selector(&*selector),
2109 _ => false,
2110 },
2111 _ => false,
2112 })
2113}
2114
2115#[cfg(feature = "visitor")]
2116#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
2117impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for SelectorList<'i> {
2118 const CHILD_TYPES: VisitTypes = VisitTypes::SELECTORS;
2119
2120 fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
2121 if visitor.visit_types().contains(VisitTypes::SELECTORS) {
2122 visitor.visit_selector_list(self)
2123 } else {
2124 self.visit_children(visitor)
2125 }
2126 }
2127
2128 fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {
2129 self.0.iter_mut().try_for_each(|selector| Visit::visit(selector, visitor))
2130 }
2131}
2132
2133#[cfg(feature = "visitor")]
2134#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
2135impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for Selector<'i> {
2136 const CHILD_TYPES: VisitTypes = VisitTypes::SELECTORS;
2137
2138 fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
2139 visitor.visit_selector(self)
2140 }
2141
2142 fn visit_children(&mut self, _visitor: &mut V) -> Result<(), V::Error> {
2143 Ok(())
2144 }
2145}
2146
2147impl<'i> ParseWithOptions<'i> for Selector<'i> {
2148 fn parse_with_options<'t>(
2149 input: &mut Parser<'i, 't>,
2150 options: &ParserOptions<'_, 'i>,
2151 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
2152 Selector::parse(
2153 &SelectorParser {
2154 is_nesting_allowed: true,
2155 options: &options,
2156 },
2157 input,
2158 )
2159 }
2160}
2161
2162impl<'i> ParseWithOptions<'i> for SelectorList<'i> {
2163 fn parse_with_options<'t>(
2164 input: &mut Parser<'i, 't>,
2165 options: &ParserOptions<'_, 'i>,
2166 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
2167 SelectorList::parse(
2168 &SelectorParser {
2169 is_nesting_allowed: true,
2170 options: &options,
2171 },
2172 input,
2173 parcel_selectors::parser::ParseErrorRecovery::DiscardList,
2174 parcel_selectors::parser::NestingRequirement::None,
2175 )
2176 }
2177}