/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 | | } |