lightningcss/
selector.rs

1//! CSS selectors.
2
3use 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  // Ensure that we provide into_owned
48
49  use static_self::IntoOwned;
50  fn _assert<'any, T: IntoOwned<'any>>() {}
51
52  _assert::<SelectorList>();
53}
54
55use private::Selectors;
56
57/// A list of selectors.
58pub type SelectorList<'i> = parcel_selectors::SelectorList<'i, Selectors>;
59/// A CSS selector, including a list of components.
60pub type Selector<'i> = parcel_selectors::parser::Selector<'i, Selectors>;
61/// An individual component within a selector.
62pub type Component<'i> = parcel_selectors::parser::Component<'i, Selectors>;
63/// A combinator.
64pub 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      // https://drafts.csswg.org/selectors-4/#useraction-pseudos
104      "hover" => Hover,
105      "active" => Active,
106      "focus" => Focus,
107      "focus-visible" => FocusVisible,
108      "focus-within" => FocusWithin,
109
110      // https://drafts.csswg.org/selectors-4/#time-pseudos
111      "current" => Current,
112      "past" => Past,
113      "future" => Future,
114
115      // https://drafts.csswg.org/selectors-4/#resource-pseudos
116      "playing" => Playing,
117      "paused" => Paused,
118      "seeking" => Seeking,
119      "buffering" => Buffering,
120      "stalled" => Stalled,
121      "muted" => Muted,
122      "volume-locked" => VolumeLocked,
123
124      // https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class
125      "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      // https://drafts.csswg.org/selectors/#display-state-pseudos
131      "open" => Open,
132      "closed" => Closed,
133      "modal" => Modal,
134      "picture-in-picture" => PictureInPicture,
135
136      // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open
137      "popover-open" => PopoverOpen,
138
139      // https://drafts.csswg.org/selectors-4/#the-defined-pseudo
140      "defined" => Defined,
141
142      // https://drafts.csswg.org/selectors-4/#location
143      "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      // https://drafts.csswg.org/selectors-4/#input-pseudos
153      "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      // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill
176      "autofill" => Autofill(VendorPrefix::None),
177      "-webkit-autofill" => Autofill(VendorPrefix::WebKit),
178      "-o-autofill" => Autofill(VendorPrefix::O),
179
180      // https://webkit.org/blog/363/styling-scrollbars/
181      "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  /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class.
363  #[derive(Eq, Hash)]
364  pub enum Direction {
365    /// Left to right
366    Ltr,
367    /// Right to left
368    Rtl,
369  }
370}
371
372/// A pseudo class.
373#[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  // https://drafts.csswg.org/selectors-4/#linguistic-pseudos
383  /// The [:lang()](https://drafts.csswg.org/selectors-4/#the-lang-pseudo) pseudo class.
384  Lang {
385    /// A list of language codes.
386    #[cfg_attr(feature = "serde", serde(borrow))]
387    languages: Vec<CowArcStr<'i>>,
388  },
389  /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class.
390  Dir {
391    /// A direction.
392    direction: Direction,
393  },
394
395  // https://drafts.csswg.org/selectors-4/#useraction-pseudos
396  /// The [:hover](https://drafts.csswg.org/selectors-4/#the-hover-pseudo) pseudo class.
397  Hover,
398  /// The [:active](https://drafts.csswg.org/selectors-4/#the-active-pseudo) pseudo class.
399  Active,
400  /// The [:focus](https://drafts.csswg.org/selectors-4/#the-focus-pseudo) pseudo class.
401  Focus,
402  /// The [:focus-visible](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo) pseudo class.
403  FocusVisible,
404  /// The [:focus-within](https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo) pseudo class.
405  FocusWithin,
406
407  // https://drafts.csswg.org/selectors-4/#time-pseudos
408  /// The [:current](https://drafts.csswg.org/selectors-4/#the-current-pseudo) pseudo class.
409  Current,
410  /// The [:past](https://drafts.csswg.org/selectors-4/#the-past-pseudo) pseudo class.
411  Past,
412  /// The [:future](https://drafts.csswg.org/selectors-4/#the-future-pseudo) pseudo class.
413  Future,
414
415  // https://drafts.csswg.org/selectors-4/#resource-pseudos
416  /// The [:playing](https://drafts.csswg.org/selectors-4/#selectordef-playing) pseudo class.
417  Playing,
418  /// The [:paused](https://drafts.csswg.org/selectors-4/#selectordef-paused) pseudo class.
419  Paused,
420  /// The [:seeking](https://drafts.csswg.org/selectors-4/#selectordef-seeking) pseudo class.
421  Seeking,
422  /// The [:buffering](https://drafts.csswg.org/selectors-4/#selectordef-buffering) pseudo class.
423  Buffering,
424  /// The [:stalled](https://drafts.csswg.org/selectors-4/#selectordef-stalled) pseudo class.
425  Stalled,
426  /// The [:muted](https://drafts.csswg.org/selectors-4/#selectordef-muted) pseudo class.
427  Muted,
428  /// The [:volume-locked](https://drafts.csswg.org/selectors-4/#selectordef-volume-locked) pseudo class.
429  VolumeLocked,
430
431  /// The [:fullscreen](https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class) pseudo class.
432  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
433  Fullscreen(VendorPrefix),
434
435  // https://drafts.csswg.org/selectors/#display-state-pseudos
436  /// The [:open](https://drafts.csswg.org/selectors/#selectordef-open) pseudo class.
437  Open,
438  /// The [:closed](https://drafts.csswg.org/selectors/#selectordef-closed) pseudo class.
439  Closed,
440  /// The [:modal](https://drafts.csswg.org/selectors/#modal-state) pseudo class.
441  Modal,
442  /// The [:picture-in-picture](https://drafts.csswg.org/selectors/#pip-state) pseudo class.
443  PictureInPicture,
444
445  // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open
446  /// The [:popover-open](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open) pseudo class.
447  PopoverOpen,
448
449  /// The [:defined](https://drafts.csswg.org/selectors-4/#the-defined-pseudo) pseudo class.
450  Defined,
451
452  // https://drafts.csswg.org/selectors-4/#location
453  /// The [:any-link](https://drafts.csswg.org/selectors-4/#the-any-link-pseudo) pseudo class.
454  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
455  AnyLink(VendorPrefix),
456  /// The [:link](https://drafts.csswg.org/selectors-4/#link-pseudo) pseudo class.
457  Link,
458  /// The [:local-link](https://drafts.csswg.org/selectors-4/#the-local-link-pseudo) pseudo class.
459  LocalLink,
460  /// The [:target](https://drafts.csswg.org/selectors-4/#the-target-pseudo) pseudo class.
461  Target,
462  /// The [:target-within](https://drafts.csswg.org/selectors-4/#the-target-within-pseudo) pseudo class.
463  TargetWithin,
464  /// The [:visited](https://drafts.csswg.org/selectors-4/#visited-pseudo) pseudo class.
465  Visited,
466
467  // https://drafts.csswg.org/selectors-4/#input-pseudos
468  /// The [:enabled](https://drafts.csswg.org/selectors-4/#enabled-pseudo) pseudo class.
469  Enabled,
470  /// The [:disabled](https://drafts.csswg.org/selectors-4/#disabled-pseudo) pseudo class.
471  Disabled,
472  /// The [:read-only](https://drafts.csswg.org/selectors-4/#read-only-pseudo) pseudo class.
473  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
474  ReadOnly(VendorPrefix),
475  /// The [:read-write](https://drafts.csswg.org/selectors-4/#read-write-pseudo) pseudo class.
476  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
477  ReadWrite(VendorPrefix),
478  /// The [:placeholder-shown](https://drafts.csswg.org/selectors-4/#placeholder) pseudo class.
479  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
480  PlaceholderShown(VendorPrefix),
481  /// The [:default](https://drafts.csswg.org/selectors-4/#the-default-pseudo) pseudo class.
482  Default,
483  /// The [:checked](https://drafts.csswg.org/selectors-4/#checked) pseudo class.
484  Checked,
485  /// The [:indeterminate](https://drafts.csswg.org/selectors-4/#indeterminate) pseudo class.
486  Indeterminate,
487  /// The [:blank](https://drafts.csswg.org/selectors-4/#blank) pseudo class.
488  Blank,
489  /// The [:valid](https://drafts.csswg.org/selectors-4/#valid-pseudo) pseudo class.
490  Valid,
491  /// The [:invalid](https://drafts.csswg.org/selectors-4/#invalid-pseudo) pseudo class.
492  Invalid,
493  /// The [:in-range](https://drafts.csswg.org/selectors-4/#in-range-pseudo) pseudo class.
494  InRange,
495  /// The [:out-of-range](https://drafts.csswg.org/selectors-4/#out-of-range-pseudo) pseudo class.
496  OutOfRange,
497  /// The [:required](https://drafts.csswg.org/selectors-4/#required-pseudo) pseudo class.
498  Required,
499  /// The [:optional](https://drafts.csswg.org/selectors-4/#optional-pseudo) pseudo class.
500  Optional,
501  /// The [:user-valid](https://drafts.csswg.org/selectors-4/#user-valid-pseudo) pseudo class.
502  UserValid,
503  /// The [:used-invalid](https://drafts.csswg.org/selectors-4/#user-invalid-pseudo) pseudo class.
504  UserInvalid,
505
506  /// The [:autofill](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill) pseudo class.
507  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
508  Autofill(VendorPrefix),
509
510  // CSS modules
511  /// The CSS modules :local() pseudo class.
512  Local {
513    /// A local selector.
514    selector: Box<Selector<'i>>,
515  },
516  /// The CSS modules :global() pseudo class.
517  Global {
518    /// A global selector.
519    selector: Box<Selector<'i>>,
520  },
521
522  /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class.
523  // https://webkit.org/blog/363/styling-scrollbars/
524  #[cfg_attr(
525    feature = "serde",
526    serde(rename = "webkit-scrollbar", with = "ValueWrapper::<WebKitScrollbarPseudoClass>")
527  )]
528  WebKitScrollbar(WebKitScrollbarPseudoClass),
529  /// An unknown pseudo class.
530  Custom {
531    /// The pseudo class name.
532    name: CowArcStr<'i>,
533  },
534  /// An unknown functional pseudo class.
535  CustomFunction {
536    /// The pseudo class name.
537    name: CowArcStr<'i>,
538    /// The arguments of the pseudo class function.
539    arguments: TokenList<'i>,
540  },
541}
542
543/// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class.
544#[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
554  Horizontal,
555  /// :vertical
556  Vertical,
557  /// :decrement
558  Decrement,
559  /// :increment
560  Increment,
561  /// :start
562  Start,
563  /// :end
564  End,
565  /// :double-button
566  DoubleButton,
567  /// :single-button
568  SingleButton,
569  /// :no-button
570  NoButton,
571  /// :corner-present
572  CornerPresent,
573  /// :window-inactive
574  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    // https://github.com/WebKit/WebKit/blob/02fbf9b7aa435edb96cbf563a8d4dcf1aa73b4b3/Source/WebCore/css/parser/CSSSelectorParser.cpp#L285
601    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      // If the printer has a vendor prefix override, use that.
659      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    // https://drafts.csswg.org/selectors-4/#useraction-pseudos
688    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    // https://drafts.csswg.org/selectors-4/#time-pseudos
695    Current => dest.write_str(":current"),
696    Past => dest.write_str(":past"),
697    Future => dest.write_str(":future"),
698
699    // https://drafts.csswg.org/selectors-4/#resource-pseudos
700    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    // https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class
709    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    // https://drafts.csswg.org/selectors/#display-state-pseudos
725    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    // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open
731    PopoverOpen => dest.write_str(":popover-open"),
732
733    // https://drafts.csswg.org/selectors-4/#the-defined-pseudo
734    Defined => dest.write_str(":defined"),
735
736    // https://drafts.csswg.org/selectors-4/#location
737    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    // https://drafts.csswg.org/selectors-4/#input-pseudos
745    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    // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill
764    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    // https://webkit.org/blog/363/styling-scrollbars/
775    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/// A pseudo element.
848#[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  /// The [::after](https://drafts.csswg.org/css-pseudo-4/#selectordef-after) pseudo element.
858  After,
859  /// The [::before](https://drafts.csswg.org/css-pseudo-4/#selectordef-before) pseudo element.
860  Before,
861  /// The [::first-line](https://drafts.csswg.org/css-pseudo-4/#first-line-pseudo) pseudo element.
862  FirstLine,
863  /// The [::first-letter](https://drafts.csswg.org/css-pseudo-4/#first-letter-pseudo) pseudo element.
864  FirstLetter,
865  /// The [::selection](https://drafts.csswg.org/css-pseudo-4/#selectordef-selection) pseudo element.
866  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
867  Selection(VendorPrefix),
868  /// The [::placeholder](https://drafts.csswg.org/css-pseudo-4/#placeholder-pseudo) pseudo element.
869  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
870  Placeholder(VendorPrefix),
871  ///  The [::marker](https://drafts.csswg.org/css-pseudo-4/#marker-pseudo) pseudo element.
872  Marker,
873  /// The [::backdrop](https://fullscreen.spec.whatwg.org/#::backdrop-pseudo-element) pseudo element.
874  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
875  Backdrop(VendorPrefix),
876  /// The [::file-selector-button](https://drafts.csswg.org/css-pseudo-4/#file-selector-button-pseudo) pseudo element.
877  #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
878  FileSelectorButton(VendorPrefix),
879  /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element.
880  #[cfg_attr(
881    feature = "serde",
882    serde(rename = "webkit-scrollbar", with = "ValueWrapper::<WebKitScrollbarPseudoElement>")
883  )]
884  WebKitScrollbar(WebKitScrollbarPseudoElement),
885  /// The [::cue](https://w3c.github.io/webvtt/#the-cue-pseudo-element) pseudo element.
886  Cue,
887  /// The [::cue-region](https://w3c.github.io/webvtt/#the-cue-region-pseudo-element) pseudo element.
888  CueRegion,
889  /// The [::cue()](https://w3c.github.io/webvtt/#cue-selector) functional pseudo element.
890  CueFunction {
891    /// The selector argument.
892    selector: Box<Selector<'i>>,
893  },
894  /// The [::cue-region()](https://w3c.github.io/webvtt/#cue-region-selector) functional pseudo element.
895  CueRegionFunction {
896    /// The selector argument.
897    selector: Box<Selector<'i>>,
898  },
899  /// The [::view-transition](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition) pseudo element.
900  ViewTransition,
901  /// The [::view-transition-group()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-group-pt-name-selector) functional pseudo element.
902  #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
903  ViewTransitionGroup {
904    /// A part name selector.
905    part_name: ViewTransitionPartName<'i>,
906  },
907  /// The [::view-transition-image-pair()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-image-pair-pt-name-selector) functional pseudo element.
908  #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
909  ViewTransitionImagePair {
910    /// A part name selector.
911    part_name: ViewTransitionPartName<'i>,
912  },
913  /// The [::view-transition-old()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-old-pt-name-selector) functional pseudo element.
914  #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
915  ViewTransitionOld {
916    /// A part name selector.
917    part_name: ViewTransitionPartName<'i>,
918  },
919  /// The [::view-transition-new()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-new-pt-name-selector) functional pseudo element.
920  #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
921  ViewTransitionNew {
922    /// A part name selector.
923    part_name: ViewTransitionPartName<'i>,
924  },
925  /// An unknown pseudo element.
926  Custom {
927    /// The name of the pseudo element.
928    #[cfg_attr(feature = "serde", serde(borrow))]
929    name: CowArcStr<'i>,
930  },
931  /// An unknown functional pseudo element.
932  CustomFunction {
933    ///The name of the pseudo element.
934    name: CowArcStr<'i>,
935    /// The arguments of the pseudo element function.
936    arguments: TokenList<'i>,
937  },
938}
939
940/// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element.
941#[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  /// ::-webkit-scrollbar
951  Scrollbar,
952  /// ::-webkit-scrollbar-button
953  Button,
954  /// ::-webkit-scrollbar-track
955  Track,
956  /// ::-webkit-scrollbar-track-piece
957  TrackPiece,
958  /// ::-webkit-scrollbar-thumb
959  Thumb,
960  /// ::-webkit-scrollbar-corner
961  Corner,
962  /// ::-webkit-resizer
963  Resizer,
964}
965
966/// A [view transition part name](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#typedef-pt-name-selector).
967#[derive(PartialEq, Eq, Clone, Debug, Hash)]
968#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
969pub enum ViewTransitionPartName<'i> {
970  /// *
971  All,
972  /// <custom-ident>
973  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      // If the printer has a vendor prefix override, use that.
1070      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    // CSS2 pseudo elements support a single colon syntax in addition
1089    // to the more correct double colon for other pseudo elements.
1090    // We use that here because it's supported everywhere and is shorter.
1091    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    // Be lenient.
1180    true
1181  }
1182
1183  fn valid_after_slotted(&self) -> bool {
1184    // ::slotted() should support all tree-abiding pseudo-elements, see
1185    // https://drafts.csswg.org/css-scoping/#slotted-pseudo
1186    // https://drafts.csswg.org/css-pseudo-4/#treelike
1187    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
1285// Copied from the selectors crate and modified to override to_css implementation.
1286impl<'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  // Compound selectors invert the order of their contents, so we need to
1306  // undo that during serialization.
1307  //
1308  // This two-iterator strategy involves walking over the selector twice.
1309  // We could do something more clever, but selector serialization probably
1310  // isn't hot enough to justify it, and the stringification likely
1311  // dominates anyway.
1312  //
1313  // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(),
1314  // which we need for |split|. So we split by combinators on a match-order
1315  // sequence and then reverse.
1316
1317  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    // Skip implicit :scope in relative selectors (e.g. :has(:scope > foo) -> :has(> foo))
1327    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    // https://drafts.csswg.org/cssom/#serializing-selectors
1336    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    // 1. If there is only one simple selector in the compound selectors
1345    //    which is a universal selector, append the result of
1346    //    serializing the universal selector to s.
1347    //
1348    // Check if `!compound.empty()` first--this can happen if we have
1349    // something like `... > ::before`, because we store `>` and `::`
1350    // both as combinators internally.
1351    //
1352    // If we are in this case, after we have serialized the universal
1353    // selector, we skip Step 2 and continue with the algorithm.
1354    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        // We have to be careful here, because if there is a
1366        // pseudo element "combinator" there isn't really just
1367        // the one simple selector. Technically this compound
1368        // selector contains the pseudo element selector as well
1369        // -- Combinator::PseudoElement, just like
1370        // Combinator::SlotAssignment, don't exist in the
1371        // spec.
1372        (Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (),
1373        (_, &Component::ExplicitUniversalType) => {
1374          // Iterate over everything so we serialize the namespace
1375          // too.
1376          let mut iter = compound.iter();
1377          let swap_nesting = has_leading_nesting && should_compile_nesting;
1378          if swap_nesting {
1379            // Swap nesting and type selector (e.g. &div -> div&).
1380            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          // Skip step 2, which is an "otherwise".
1392          perform_step_2 = false;
1393        }
1394        _ => (),
1395      }
1396    }
1397
1398    // 2. Otherwise, for each simple selector in the compound selectors
1399    //    that is not a universal selector of which the namespace prefix
1400    //    maps to a namespace that is not the default namespace
1401    //    serialize the simple selector and append the result to s.
1402    //
1403    // See https://github.com/w3c/csswg-drafts/issues/1606, which is
1404    // proposing to change this to match up with the behavior asserted
1405    // in cssom/serialize-namespaced-type-selectors.html, which the
1406    // following code tries to match.
1407    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        // Swap nesting and type selector (e.g. &div -> div&).
1411        // This ensures that the compiled selector is valid. e.g. (div.foo is valid, .foodiv is not).
1412        let nesting = iter.next().unwrap();
1413        let local = iter.next().unwrap();
1414        serialize_component(local, dest, context)?;
1415
1416        // Also check the next item in case of namespaces.
1417        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        // Nesting selector may serialize differently if it is leading, due to type selectors.
1425        iter.next();
1426        serialize_nesting(dest, context, true)?;
1427      }
1428
1429      for simple in iter {
1430        if let Component::ExplicitUniversalType = *simple {
1431          // Can't have a namespace followed by a pseudo-element
1432          // selector followed by a universal selector in the same
1433          // compound selector, so we don't have to worry about the
1434          // real namespace being in a different `compound`.
1435          if can_elide_namespace {
1436            continue;
1437          }
1438        }
1439        serialize_component(simple, dest, context)?;
1440      }
1441    }
1442
1443    // 3. If this is not the last part of the chain of the selector
1444    //    append a single SPACE (U+0020), followed by the combinator
1445    //    ">", "+", "~", ">>", "||", as appropriate, followed by another
1446    //    single SPACE (U+0020) if the combinator was not whitespace, to
1447    //    s.
1448    match next_combinator {
1449      Some(c) => c.to_css(dest)?,
1450      None => combinators_exhausted = true,
1451    };
1452
1453    // 4. If this is the last part of the chain of the selector and
1454    //    there is a pseudo-element, append "::" followed by the name of
1455    //    the pseudo-element, to s.
1456    //
1457    // (we handle this above)
1458  }
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        // Serialize as both an identifier and a string and choose the shorter one.
1486        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 there's only one simple selector, serialize it directly.
1516          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 there's only one simple selector, just serialize it directly.
1606    // Otherwise, use an :is() pseudo class.
1607    // Type selectors are only allowed at the start of a compound selector,
1608    // so use :is() if that is not the case.
1609    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 there is no context, we are at the root if nesting is supported. This is equivalent to :scope.
1620    // Otherwise, if nesting is supported, serialize the nesting selector directly.
1621    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  // For input:checked the component vector is
1632  // [input, :checked] so we have to check it using matching order.
1633  //
1634  // This both happens for input:checked and is(input:checked)
1635  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          // :not() selector list is not forgiving.
1748          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        // These support forgiving selector lists, so no need to check nested selectors.
1767        Component::Is(selectors) => {
1768          // ... except if we are going to unwrap them.
1769          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            // Experimental, no browser support.
1824            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
1874/// Returns whether two selector lists are equivalent, i.e. the same minus any vendor prefix differences.
1875pub(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
1906/// Returns the vendor prefix (if any) used in the given selector list.
1907/// If multiple vendor prefixes are seen, this is invalid, and an empty result is returned.
1908pub(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        // Return none rather than empty for these so that we call downlevel_selectors.
1914        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        // Allow none to be mixed with a prefix.
1928        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
1946/// Downlevels the given selectors to be compatible with the given browser targets.
1947/// Returns the necessary vendor prefixes.
1948pub(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          // :lang() with multiple languages is not supported everywhere.
1973          // compile this to :is(:lang(a), :lang(b)) etc.
1974          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      // Convert :is to :-webkit-any/:-moz-any if needed.
1989      // All selectors must be simple, no combinators are supported.
1990      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      // Downlevel :not(.a, .b) -> :not(:is(.a, .b)) if not list is unsupported.
2005      // We need to use :is() / :-webkit-any() rather than :not(.a):not(.b) to ensure the specificity is equivalent.
2006      // https://drafts.csswg.org/selectors/#specificity-rules
2007      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  // Convert :dir to :lang. If supported, use a list of languages in a single :lang,
2041  // otherwise, use :is/:not, which may be further downleveled to e.g. :-webkit-any.
2042  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
2059/// Determines whether a selector list contains only unused selectors.
2060/// A selector is considered unused if it contains a class or id component that exists in the set of unused symbols.
2061pub(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
2096/// Returns whether the selector has any class or id components.
2097pub(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}