Coverage Report

Created: 2026-10-28 07:08

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