/rust/registry/src/index.crates.io-1949cf8c6b5b557f/parcel_selectors-0.28.3/parser.rs
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | use crate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace}; |
6 | | use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation}; |
7 | | use crate::attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE}; |
8 | | use crate::bloom::BLOOM_HASH_MASK; |
9 | | use crate::builder::{SelectorBuilder, SelectorFlags, SpecificityAndFlags}; |
10 | | use crate::context::QuirksMode; |
11 | | use crate::sink::Push; |
12 | | pub use crate::visitor::SelectorVisitor; |
13 | | use cssparser::parse_nth; |
14 | | use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind}; |
15 | | use cssparser::{CowRcStr, Delimiter, SourceLocation}; |
16 | | use cssparser::{Parser as CssParser, ToCss, Token}; |
17 | | use precomputed_hash::PrecomputedHash; |
18 | | use smallvec::{smallvec, SmallVec}; |
19 | | use std::borrow::Borrow; |
20 | | use std::fmt::{self, Debug}; |
21 | | use std::iter::Rev; |
22 | | use std::slice; |
23 | | |
24 | | /// A trait that represents a pseudo-element. |
25 | | pub trait PseudoElement<'i>: Sized + ToCss { |
26 | | /// The `SelectorImpl` this pseudo-element is used for. |
27 | | type Impl: SelectorImpl<'i>; |
28 | | |
29 | | /// Whether the pseudo-element supports a given state selector to the right |
30 | | /// of it. |
31 | 0 | fn accepts_state_pseudo_classes(&self) -> bool { |
32 | 0 | false |
33 | 0 | } |
34 | | |
35 | | /// Whether this pseudo-element is valid after a ::slotted(..) pseudo. |
36 | 0 | fn valid_after_slotted(&self) -> bool { |
37 | 0 | false |
38 | 0 | } |
39 | | |
40 | 0 | fn is_webkit_scrollbar(&self) -> bool { |
41 | 0 | false |
42 | 0 | } |
43 | | |
44 | 0 | fn is_view_transition(&self) -> bool { |
45 | 0 | false |
46 | 0 | } |
47 | | |
48 | 0 | fn is_unknown(&self) -> bool { |
49 | 0 | false |
50 | 0 | } |
51 | | } |
52 | | |
53 | | /// A trait that represents a pseudo-class. |
54 | | pub trait NonTSPseudoClass<'i>: Sized + ToCss { |
55 | | /// The `SelectorImpl` this pseudo-element is used for. |
56 | | type Impl: SelectorImpl<'i>; |
57 | | |
58 | | /// Whether this pseudo-class is :active or :hover. |
59 | | fn is_active_or_hover(&self) -> bool; |
60 | | |
61 | | /// Whether this pseudo-class belongs to: |
62 | | /// |
63 | | /// https://drafts.csswg.org/selectors-4/#useraction-pseudos |
64 | | fn is_user_action_state(&self) -> bool; |
65 | | |
66 | 0 | fn is_valid_before_webkit_scrollbar(&self) -> bool { |
67 | 0 | true |
68 | 0 | } |
69 | | |
70 | 0 | fn is_valid_after_webkit_scrollbar(&self) -> bool { |
71 | 0 | false |
72 | 0 | } |
73 | | |
74 | 0 | fn visit<V>(&self, _visitor: &mut V) -> bool |
75 | 0 | where |
76 | 0 | V: SelectorVisitor<'i, Impl = Self::Impl>, |
77 | | { |
78 | 0 | true |
79 | 0 | } |
80 | | } |
81 | | |
82 | | /// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a |
83 | | /// Cow::Owned if `s` had to be converted into ASCII lowercase. |
84 | 2.01M | fn to_ascii_lowercase<'i>(s: CowRcStr<'i>) -> CowRcStr<'i> { |
85 | 27.8M | if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { |
86 | 455k | let mut string = s.to_string(); |
87 | 455k | string[first_uppercase..].make_ascii_lowercase(); |
88 | 455k | string.into() |
89 | | } else { |
90 | 1.56M | s |
91 | | } |
92 | 2.01M | } |
93 | | |
94 | | bitflags! { |
95 | | /// Flags that indicate at which point of parsing a selector are we. |
96 | | #[derive(PartialEq, Eq, Clone, Copy)] |
97 | | struct SelectorParsingState: u16 { |
98 | | /// Whether we should avoid adding default namespaces to selectors that |
99 | | /// aren't type or universal selectors. |
100 | | const SKIP_DEFAULT_NAMESPACE = 1 << 0; |
101 | | |
102 | | /// Whether we've parsed a ::slotted() pseudo-element already. |
103 | | /// |
104 | | /// If so, then we can only parse a subset of pseudo-elements, and |
105 | | /// whatever comes after them if so. |
106 | | const AFTER_SLOTTED = 1 << 1; |
107 | | /// Whether we've parsed a ::part() pseudo-element already. |
108 | | /// |
109 | | /// If so, then we can only parse a subset of pseudo-elements, and |
110 | | /// whatever comes after them if so. |
111 | | const AFTER_PART = 1 << 2; |
112 | | /// Whether we've parsed a pseudo-element (as in, an |
113 | | /// `Impl::PseudoElement` thus not accounting for `::slotted` or |
114 | | /// `::part`) already. |
115 | | /// |
116 | | /// If so, then other pseudo-elements and most other selectors are |
117 | | /// disallowed. |
118 | | const AFTER_PSEUDO_ELEMENT = 1 << 3; |
119 | | /// Whether we've parsed a non-stateful pseudo-element (again, as-in |
120 | | /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are |
121 | | /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set |
122 | | /// as well. |
123 | | const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4; |
124 | | |
125 | | /// Whether we are after any of the pseudo-like things. |
126 | | const AFTER_PSEUDO = Self::AFTER_PART.bits() | Self::AFTER_SLOTTED.bits() | Self::AFTER_PSEUDO_ELEMENT.bits(); |
127 | | |
128 | | /// Whether we explicitly disallow combinators. |
129 | | const DISALLOW_COMBINATORS = 1 << 5; |
130 | | |
131 | | /// Whether we explicitly disallow pseudo-element-like things. |
132 | | const DISALLOW_PSEUDOS = 1 << 6; |
133 | | |
134 | | /// Whether we have seen a nesting selector. |
135 | | const AFTER_NESTING = 1 << 7; |
136 | | |
137 | | const AFTER_WEBKIT_SCROLLBAR = 1 << 8; |
138 | | const AFTER_VIEW_TRANSITION = 1 << 9; |
139 | | const AFTER_UNKNOWN_PSEUDO_ELEMENT = 1 << 10; |
140 | | } |
141 | | } |
142 | | |
143 | | impl SelectorParsingState { |
144 | | #[inline] |
145 | 65.8k | fn allows_pseudos(self) -> bool { |
146 | | // NOTE(emilio): We allow pseudos after ::part and such. |
147 | 65.8k | !self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS) |
148 | 65.8k | } <parcel_selectors::parser::SelectorParsingState>::allows_pseudos Line | Count | Source | 145 | 65.8k | fn allows_pseudos(self) -> bool { | 146 | | // NOTE(emilio): We allow pseudos after ::part and such. | 147 | 65.8k | !self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS) | 148 | 65.8k | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_pseudos |
149 | | |
150 | | #[inline] |
151 | 0 | fn allows_slotted(self) -> bool { |
152 | 0 | !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) |
153 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_slotted Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_slotted |
154 | | |
155 | | #[inline] |
156 | 0 | fn allows_part(self) -> bool { |
157 | 0 | !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) |
158 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_part Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_part |
159 | | |
160 | | // TODO(emilio): Maybe some of these should be allowed, but this gets us on |
161 | | // the safe side for now, matching previous behavior. Gotta be careful with |
162 | | // the ones like :-moz-any, which allow nested selectors but don't carry the |
163 | | // state, and so on. |
164 | | #[inline] |
165 | 3.85k | fn allows_custom_functional_pseudo_classes(self) -> bool { |
166 | 3.85k | !self.intersects(Self::AFTER_PSEUDO) |
167 | 3.85k | } <parcel_selectors::parser::SelectorParsingState>::allows_custom_functional_pseudo_classes Line | Count | Source | 165 | 3.85k | fn allows_custom_functional_pseudo_classes(self) -> bool { | 166 | 3.85k | !self.intersects(Self::AFTER_PSEUDO) | 167 | 3.85k | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_custom_functional_pseudo_classes |
168 | | |
169 | | #[inline] |
170 | 289k | fn allows_non_functional_pseudo_classes(self) -> bool { |
171 | 289k | !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT) |
172 | 289k | } <parcel_selectors::parser::SelectorParsingState>::allows_non_functional_pseudo_classes Line | Count | Source | 170 | 289k | fn allows_non_functional_pseudo_classes(self) -> bool { | 171 | 289k | !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT) | 172 | 289k | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_non_functional_pseudo_classes |
173 | | |
174 | | #[inline] |
175 | 289k | fn allows_tree_structural_pseudo_classes(self) -> bool { |
176 | 289k | !self.intersects(Self::AFTER_PSEUDO) |
177 | 289k | } <parcel_selectors::parser::SelectorParsingState>::allows_tree_structural_pseudo_classes Line | Count | Source | 175 | 289k | fn allows_tree_structural_pseudo_classes(self) -> bool { | 176 | 289k | !self.intersects(Self::AFTER_PSEUDO) | 177 | 289k | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_tree_structural_pseudo_classes |
178 | | |
179 | | #[inline] |
180 | 581k | fn allows_combinators(self) -> bool { |
181 | 581k | !self.intersects(Self::DISALLOW_COMBINATORS) |
182 | 581k | } <parcel_selectors::parser::SelectorParsingState>::allows_combinators Line | Count | Source | 180 | 581k | fn allows_combinators(self) -> bool { | 181 | 581k | !self.intersects(Self::DISALLOW_COMBINATORS) | 182 | 581k | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorParsingState>::allows_combinators |
183 | | } |
184 | | |
185 | | pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>; |
186 | | |
187 | | #[derive(Clone, Debug, PartialEq)] |
188 | | pub enum SelectorParseErrorKind<'i> { |
189 | | NoQualifiedNameInAttributeSelector(Token<'i>), |
190 | | EmptySelector, |
191 | | DanglingCombinator, |
192 | | InvalidPseudoClassBeforeWebKitScrollbar, |
193 | | InvalidPseudoClassAfterWebKitScrollbar, |
194 | | InvalidPseudoClassAfterPseudoElement, |
195 | | InvalidState, |
196 | | MissingNestingSelector, |
197 | | MissingNestingPrefix, |
198 | | UnexpectedTokenInAttributeSelector(Token<'i>), |
199 | | PseudoElementExpectedIdent(Token<'i>), |
200 | | UnsupportedPseudoElement(CowRcStr<'i>), |
201 | | UnsupportedPseudoClass(CowRcStr<'i>), |
202 | | AmbiguousCssModuleClass(CowRcStr<'i>), |
203 | | UnexpectedIdent(CowRcStr<'i>), |
204 | | ExpectedNamespace(CowRcStr<'i>), |
205 | | ExpectedBarInAttr(Token<'i>), |
206 | | BadValueInAttr(Token<'i>), |
207 | | InvalidQualNameInAttr(Token<'i>), |
208 | | ExplicitNamespaceUnexpectedToken(Token<'i>), |
209 | | ClassNeedsIdent(Token<'i>), |
210 | | UnexpectedSelectorAfterPseudoElement(Token<'i>), |
211 | | } |
212 | | |
213 | | macro_rules! with_all_bounds { |
214 | | ( |
215 | | [ $( $InSelector: tt )* ] |
216 | | [ $( $CommonBounds: tt )* ] |
217 | | [ $( $FromStr: tt )* ] |
218 | | ) => { |
219 | | /// This trait allows to define the parser implementation in regards |
220 | | /// of pseudo-classes/elements |
221 | | /// |
222 | | /// NB: We need Clone so that we can derive(Clone) on struct with that |
223 | | /// are parameterized on SelectorImpl. See |
224 | | /// <https://github.com/rust-lang/rust/issues/26925> |
225 | | pub trait SelectorImpl<'i>: Clone + Debug + Sized + 'static { |
226 | | type ExtraMatchingData: Sized + Default + 'static; |
227 | | type AttrValue: $($InSelector)*; |
228 | | type Identifier: $($InSelector)*; |
229 | | type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>; |
230 | | type NamespaceUrl: $($CommonBounds)* + $($FromStr)* + Default + Borrow<Self::BorrowedNamespaceUrl>; |
231 | | type NamespacePrefix: $($InSelector)* + Default; |
232 | | type BorrowedNamespaceUrl: ?Sized + Eq; |
233 | | type BorrowedLocalName: ?Sized + Eq; |
234 | | |
235 | | /// non tree-structural pseudo-classes |
236 | | /// (see: https://drafts.csswg.org/selectors/#structural-pseudos) |
237 | | type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass<'i, Impl = Self>; |
238 | | type VendorPrefix: Sized + Eq + $($CommonBounds)* + ToCss; |
239 | | |
240 | | /// pseudo-elements |
241 | | type PseudoElement: $($CommonBounds)* + PseudoElement<'i, Impl = Self>; |
242 | | |
243 | 0 | fn to_css<W: fmt::Write>(selectors: &SelectorList<'i, Self>, dest: &mut W) -> fmt::Result { |
244 | 0 | serialize_selector_list(selectors.0.iter(), dest) |
245 | 0 | } |
246 | | } |
247 | | } |
248 | | } |
249 | | |
250 | | macro_rules! with_bounds { |
251 | | ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => { |
252 | | with_all_bounds! { |
253 | | [$($CommonBounds)* + $($FromStr)* + ToCss] |
254 | | [$($CommonBounds)*] |
255 | | [$($FromStr)*] |
256 | | } |
257 | | } |
258 | | } |
259 | | |
260 | | #[cfg(feature = "serde")] |
261 | | with_bounds! { |
262 | | [Clone + PartialEq + Eq + std::hash::Hash] |
263 | | [From<CowRcStr<'i>> + From<std::borrow::Cow<'i, str>> + AsRef<str>] |
264 | | } |
265 | | |
266 | | #[cfg(not(feature = "serde"))] |
267 | | with_bounds! { |
268 | | [Clone + PartialEq + Eq + std::hash::Hash] |
269 | | [From<CowRcStr<'i>>] |
270 | | } |
271 | | |
272 | | pub trait Parser<'i> { |
273 | | type Impl: SelectorImpl<'i>; |
274 | | type Error: 'i + From<SelectorParseErrorKind<'i>>; |
275 | | |
276 | | /// Whether to parse the `::slotted()` pseudo-element. |
277 | 0 | fn parse_slotted(&self) -> bool { |
278 | 0 | false |
279 | 0 | } |
280 | | |
281 | | /// Whether to parse the `::part()` pseudo-element. |
282 | 0 | fn parse_part(&self) -> bool { |
283 | 0 | false |
284 | 0 | } |
285 | | |
286 | | /// Whether to parse the `:where` pseudo-class. |
287 | 0 | fn parse_is_and_where(&self) -> bool { |
288 | 0 | false |
289 | 0 | } |
290 | | |
291 | | /// The error recovery that selector lists inside :is() and :where() have. |
292 | 3.62k | fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { |
293 | 3.62k | ParseErrorRecovery::IgnoreInvalidSelector |
294 | 3.62k | } <lightningcss::selector::SelectorParser as parcel_selectors::parser::Parser>::is_and_where_error_recovery Line | Count | Source | 292 | 3.62k | fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { | 293 | 3.62k | ParseErrorRecovery::IgnoreInvalidSelector | 294 | 3.62k | } |
Unexecuted instantiation: <_ as parcel_selectors::parser::Parser>::is_and_where_error_recovery |
295 | | |
296 | | /// Whether the given function name is an alias for the `:is()` function. |
297 | 0 | fn parse_any_prefix(&self, _name: &str) -> Option<<Self::Impl as SelectorImpl<'i>>::VendorPrefix> { |
298 | 0 | None |
299 | 0 | } |
300 | | |
301 | | /// Whether to parse the `:host` pseudo-class. |
302 | 0 | fn parse_host(&self) -> bool { |
303 | 0 | false |
304 | 0 | } |
305 | | |
306 | | /// Parses non-tree-structural pseudo-classes. Tree structural pseudo-classes, |
307 | | /// like `:first-child`, are built into this library. |
308 | | /// |
309 | | /// This function can return an "Err" pseudo-element in order to support CSS2.1 |
310 | | /// pseudo-elements. |
311 | 0 | fn parse_non_ts_pseudo_class( |
312 | 0 | &self, |
313 | 0 | location: SourceLocation, |
314 | 0 | name: CowRcStr<'i>, |
315 | 0 | ) -> Result<<Self::Impl as SelectorImpl<'i>>::NonTSPseudoClass, ParseError<'i, Self::Error>> { |
316 | 0 | Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name))) |
317 | 0 | } |
318 | | |
319 | 0 | fn parse_non_ts_functional_pseudo_class<'t>( |
320 | 0 | &self, |
321 | 0 | name: CowRcStr<'i>, |
322 | 0 | arguments: &mut CssParser<'i, 't>, |
323 | 0 | ) -> Result<<Self::Impl as SelectorImpl<'i>>::NonTSPseudoClass, ParseError<'i, Self::Error>> { |
324 | 0 | Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name))) |
325 | 0 | } |
326 | | |
327 | 0 | fn parse_pseudo_element( |
328 | 0 | &self, |
329 | 0 | location: SourceLocation, |
330 | 0 | name: CowRcStr<'i>, |
331 | 0 | ) -> Result<<Self::Impl as SelectorImpl<'i>>::PseudoElement, ParseError<'i, Self::Error>> { |
332 | 0 | Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name))) |
333 | 0 | } |
334 | | |
335 | 0 | fn parse_functional_pseudo_element<'t>( |
336 | 0 | &self, |
337 | 0 | name: CowRcStr<'i>, |
338 | 0 | arguments: &mut CssParser<'i, 't>, |
339 | 0 | ) -> Result<<Self::Impl as SelectorImpl<'i>>::PseudoElement, ParseError<'i, Self::Error>> { |
340 | 0 | Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name))) |
341 | 0 | } |
342 | | |
343 | 0 | fn default_namespace(&self) -> Option<<Self::Impl as SelectorImpl<'i>>::NamespaceUrl> { |
344 | 0 | None |
345 | 0 | } |
346 | | |
347 | 0 | fn namespace_for_prefix( |
348 | 0 | &self, |
349 | 0 | _prefix: &<Self::Impl as SelectorImpl<'i>>::NamespacePrefix, |
350 | 0 | ) -> Option<<Self::Impl as SelectorImpl<'i>>::NamespaceUrl> { |
351 | 0 | None |
352 | 0 | } |
353 | | |
354 | 0 | fn is_nesting_allowed(&self) -> bool { |
355 | 0 | false |
356 | 0 | } |
357 | | |
358 | 0 | fn deep_combinator_enabled(&self) -> bool { |
359 | 0 | false |
360 | 0 | } |
361 | | } |
362 | | |
363 | | #[derive(Clone, Debug, PartialEq, Eq, Hash)] |
364 | | #[cfg_attr( |
365 | | feature = "serde", |
366 | | derive(serde::Serialize, serde::Deserialize), |
367 | | serde(bound( |
368 | | serialize = "Impl::NonTSPseudoClass: serde::Serialize, Impl::PseudoElement: serde::Serialize, Impl::VendorPrefix: serde::Serialize", |
369 | | deserialize = "Impl::NonTSPseudoClass: serde::Deserialize<'de>, Impl::PseudoElement: serde::Deserialize<'de>, Impl::VendorPrefix: serde::Deserialize<'de>" |
370 | | )) |
371 | | )] |
372 | | #[cfg_attr( |
373 | | feature = "jsonschema", |
374 | | derive(schemars::JsonSchema), |
375 | | schemars( |
376 | | rename = "SelectorList", |
377 | | bound = "Impl: schemars::JsonSchema, Impl::NonTSPseudoClass: schemars::JsonSchema, Impl::PseudoElement: schemars::JsonSchema, Impl::VendorPrefix: schemars::JsonSchema" |
378 | | ) |
379 | | )] |
380 | | pub struct SelectorList<'i, Impl: SelectorImpl<'i>>( |
381 | | #[cfg_attr(feature = "serde", serde(borrow))] pub SmallVec<[Selector<'i, Impl>; 1]>, |
382 | | ); |
383 | | |
384 | | #[cfg(feature = "into_owned")] |
385 | | impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for SelectorList<'i, Impl> |
386 | | where |
387 | | Impl: static_self::IntoOwned<'any, Owned = NewSel>, |
388 | | NewSel: SelectorImpl<'any>, |
389 | | Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>, |
390 | | { |
391 | | type Owned = SelectorList<'any, NewSel>; |
392 | | |
393 | | fn into_owned(self) -> Self::Owned { |
394 | | SelectorList(self.0.into_owned()) |
395 | | } |
396 | | } |
397 | | |
398 | | /// How to treat invalid selectors in a selector list. |
399 | | pub enum ParseErrorRecovery { |
400 | | /// Discard the entire selector list, this is the default behavior for |
401 | | /// almost all of CSS. |
402 | | DiscardList, |
403 | | /// Ignore invalid selectors, potentially creating an empty selector list. |
404 | | /// |
405 | | /// This is the error recovery mode of :is() and :where() |
406 | | IgnoreInvalidSelector, |
407 | | } |
408 | | |
409 | | #[derive(Eq, PartialEq, Clone, Copy)] |
410 | | pub enum NestingRequirement { |
411 | | None, |
412 | | Prefixed, |
413 | | Contained, |
414 | | Implicit, |
415 | | } |
416 | | |
417 | | impl<'i, Impl: SelectorImpl<'i>> SelectorList<'i, Impl> { |
418 | | /// Parse a comma-separated list of Selectors. |
419 | | /// <https://drafts.csswg.org/selectors/#grouping> |
420 | | /// |
421 | | /// Return the Selectors or Err if there is an invalid selector. |
422 | 307k | pub fn parse<'t, P>( |
423 | 307k | parser: &P, |
424 | 307k | input: &mut CssParser<'i, 't>, |
425 | 307k | error_recovery: ParseErrorRecovery, |
426 | 307k | nesting_requirement: NestingRequirement, |
427 | 307k | ) -> Result<Self, ParseError<'i, P::Error>> |
428 | 307k | where |
429 | 307k | P: Parser<'i, Impl = Impl>, |
430 | | { |
431 | 307k | Self::parse_with_state( |
432 | 307k | parser, |
433 | 307k | input, |
434 | 307k | &mut SelectorParsingState::empty(), |
435 | 307k | error_recovery, |
436 | 307k | nesting_requirement, |
437 | | ) |
438 | 307k | } <parcel_selectors::parser::SelectorList<lightningcss::selector::private::Selectors>>::parse::<lightningcss::selector::SelectorParser> Line | Count | Source | 422 | 307k | pub fn parse<'t, P>( | 423 | 307k | parser: &P, | 424 | 307k | input: &mut CssParser<'i, 't>, | 425 | 307k | error_recovery: ParseErrorRecovery, | 426 | 307k | nesting_requirement: NestingRequirement, | 427 | 307k | ) -> Result<Self, ParseError<'i, P::Error>> | 428 | 307k | where | 429 | 307k | P: Parser<'i, Impl = Impl>, | 430 | | { | 431 | 307k | Self::parse_with_state( | 432 | 307k | parser, | 433 | 307k | input, | 434 | 307k | &mut SelectorParsingState::empty(), | 435 | 307k | error_recovery, | 436 | 307k | nesting_requirement, | 437 | | ) | 438 | 307k | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorList<_>>::parse::<_> |
439 | | |
440 | | #[inline] |
441 | 311k | fn parse_with_state<'t, P>( |
442 | 311k | parser: &P, |
443 | 311k | input: &mut CssParser<'i, 't>, |
444 | 311k | state: &mut SelectorParsingState, |
445 | 311k | recovery: ParseErrorRecovery, |
446 | 311k | nesting_requirement: NestingRequirement, |
447 | 311k | ) -> Result<Self, ParseError<'i, P::Error>> |
448 | 311k | where |
449 | 311k | P: Parser<'i, Impl = Impl>, |
450 | | { |
451 | 311k | let original_state = *state; |
452 | 311k | let mut values = SmallVec::new(); |
453 | | loop { |
454 | 361k | let selector = input.parse_until_before(Delimiter::Comma, |input| { |
455 | 361k | let mut selector_state = original_state; |
456 | 361k | let result = parse_selector(parser, input, &mut selector_state, nesting_requirement); |
457 | 361k | if selector_state.contains(SelectorParsingState::AFTER_NESTING) { |
458 | 17.9k | state.insert(SelectorParsingState::AFTER_NESTING) |
459 | 343k | } |
460 | 361k | result |
461 | 361k | }); <parcel_selectors::parser::SelectorList<lightningcss::selector::private::Selectors>>::parse_with_state::<lightningcss::selector::SelectorParser>::{closure#0}Line | Count | Source | 454 | 361k | let selector = input.parse_until_before(Delimiter::Comma, |input| { | 455 | 361k | let mut selector_state = original_state; | 456 | 361k | let result = parse_selector(parser, input, &mut selector_state, nesting_requirement); | 457 | 361k | if selector_state.contains(SelectorParsingState::AFTER_NESTING) { | 458 | 17.9k | state.insert(SelectorParsingState::AFTER_NESTING) | 459 | 343k | } | 460 | 361k | result | 461 | 361k | }); |
Unexecuted instantiation: <parcel_selectors::parser::SelectorList<_>>::parse_with_state::<_>::{closure#0} |
462 | | |
463 | 361k | let was_ok = selector.is_ok(); |
464 | 361k | match selector { |
465 | 156k | Ok(selector) => values.push(selector), |
466 | 204k | Err(err) => match recovery { |
467 | 194k | ParseErrorRecovery::DiscardList => return Err(err), |
468 | 9.55k | ParseErrorRecovery::IgnoreInvalidSelector => {} |
469 | | }, |
470 | | } |
471 | | |
472 | | loop { |
473 | 166k | match input.next() { |
474 | 116k | Err(_) => return Ok(SelectorList(values)), |
475 | 49.7k | Ok(&Token::Comma) => break, |
476 | | Ok(_) => { |
477 | 0 | debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); |
478 | | } |
479 | | } |
480 | | } |
481 | | } |
482 | 311k | } <parcel_selectors::parser::SelectorList<lightningcss::selector::private::Selectors>>::parse_with_state::<lightningcss::selector::SelectorParser> Line | Count | Source | 441 | 311k | fn parse_with_state<'t, P>( | 442 | 311k | parser: &P, | 443 | 311k | input: &mut CssParser<'i, 't>, | 444 | 311k | state: &mut SelectorParsingState, | 445 | 311k | recovery: ParseErrorRecovery, | 446 | 311k | nesting_requirement: NestingRequirement, | 447 | 311k | ) -> Result<Self, ParseError<'i, P::Error>> | 448 | 311k | where | 449 | 311k | P: Parser<'i, Impl = Impl>, | 450 | | { | 451 | 311k | let original_state = *state; | 452 | 311k | let mut values = SmallVec::new(); | 453 | | loop { | 454 | 361k | let selector = input.parse_until_before(Delimiter::Comma, |input| { | 455 | | let mut selector_state = original_state; | 456 | | let result = parse_selector(parser, input, &mut selector_state, nesting_requirement); | 457 | | if selector_state.contains(SelectorParsingState::AFTER_NESTING) { | 458 | | state.insert(SelectorParsingState::AFTER_NESTING) | 459 | | } | 460 | | result | 461 | | }); | 462 | | | 463 | 361k | let was_ok = selector.is_ok(); | 464 | 361k | match selector { | 465 | 156k | Ok(selector) => values.push(selector), | 466 | 204k | Err(err) => match recovery { | 467 | 194k | ParseErrorRecovery::DiscardList => return Err(err), | 468 | 9.55k | ParseErrorRecovery::IgnoreInvalidSelector => {} | 469 | | }, | 470 | | } | 471 | | | 472 | | loop { | 473 | 166k | match input.next() { | 474 | 116k | Err(_) => return Ok(SelectorList(values)), | 475 | 49.7k | Ok(&Token::Comma) => break, | 476 | | Ok(_) => { | 477 | 0 | debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); | 478 | | } | 479 | | } | 480 | | } | 481 | | } | 482 | 311k | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorList<_>>::parse_with_state::<_> |
483 | | |
484 | 1.40M | pub fn parse_relative<'t, P>( |
485 | 1.40M | parser: &P, |
486 | 1.40M | input: &mut CssParser<'i, 't>, |
487 | 1.40M | error_recovery: ParseErrorRecovery, |
488 | 1.40M | nesting_requirement: NestingRequirement, |
489 | 1.40M | ) -> Result<Self, ParseError<'i, P::Error>> |
490 | 1.40M | where |
491 | 1.40M | P: Parser<'i, Impl = Impl>, |
492 | | { |
493 | 1.40M | Self::parse_relative_with_state( |
494 | 1.40M | parser, |
495 | 1.40M | input, |
496 | 1.40M | &mut SelectorParsingState::empty(), |
497 | 1.40M | error_recovery, |
498 | 1.40M | nesting_requirement, |
499 | | ) |
500 | 1.40M | } <parcel_selectors::parser::SelectorList<lightningcss::selector::private::Selectors>>::parse_relative::<lightningcss::selector::SelectorParser> Line | Count | Source | 484 | 1.40M | pub fn parse_relative<'t, P>( | 485 | 1.40M | parser: &P, | 486 | 1.40M | input: &mut CssParser<'i, 't>, | 487 | 1.40M | error_recovery: ParseErrorRecovery, | 488 | 1.40M | nesting_requirement: NestingRequirement, | 489 | 1.40M | ) -> Result<Self, ParseError<'i, P::Error>> | 490 | 1.40M | where | 491 | 1.40M | P: Parser<'i, Impl = Impl>, | 492 | | { | 493 | 1.40M | Self::parse_relative_with_state( | 494 | 1.40M | parser, | 495 | 1.40M | input, | 496 | 1.40M | &mut SelectorParsingState::empty(), | 497 | 1.40M | error_recovery, | 498 | 1.40M | nesting_requirement, | 499 | | ) | 500 | 1.40M | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorList<_>>::parse_relative::<_> |
501 | | |
502 | | #[inline] |
503 | 1.40M | fn parse_relative_with_state<'t, P>( |
504 | 1.40M | parser: &P, |
505 | 1.40M | input: &mut CssParser<'i, 't>, |
506 | 1.40M | state: &mut SelectorParsingState, |
507 | 1.40M | recovery: ParseErrorRecovery, |
508 | 1.40M | nesting_requirement: NestingRequirement, |
509 | 1.40M | ) -> Result<Self, ParseError<'i, P::Error>> |
510 | 1.40M | where |
511 | 1.40M | P: Parser<'i, Impl = Impl>, |
512 | | { |
513 | 1.40M | let original_state = *state; |
514 | 1.40M | let mut values = SmallVec::new(); |
515 | | loop { |
516 | 1.56M | let selector = input.parse_until_before(Delimiter::Comma, |input| { |
517 | 1.56M | let mut selector_state = original_state; |
518 | 1.56M | let result = parse_relative_selector(parser, input, &mut selector_state, nesting_requirement); |
519 | 1.56M | if selector_state.contains(SelectorParsingState::AFTER_NESTING) { |
520 | 20.3k | state.insert(SelectorParsingState::AFTER_NESTING) |
521 | 1.54M | } |
522 | 1.56M | result |
523 | 1.56M | }); <parcel_selectors::parser::SelectorList<lightningcss::selector::private::Selectors>>::parse_relative_with_state::<lightningcss::selector::SelectorParser>::{closure#0}Line | Count | Source | 516 | 1.56M | let selector = input.parse_until_before(Delimiter::Comma, |input| { | 517 | 1.56M | let mut selector_state = original_state; | 518 | 1.56M | let result = parse_relative_selector(parser, input, &mut selector_state, nesting_requirement); | 519 | 1.56M | if selector_state.contains(SelectorParsingState::AFTER_NESTING) { | 520 | 20.3k | state.insert(SelectorParsingState::AFTER_NESTING) | 521 | 1.54M | } | 522 | 1.56M | result | 523 | 1.56M | }); |
Unexecuted instantiation: <parcel_selectors::parser::SelectorList<_>>::parse_relative_with_state::<_>::{closure#0} |
524 | | |
525 | 1.56M | let was_ok = selector.is_ok(); |
526 | 1.56M | match selector { |
527 | 1.20M | Ok(selector) => values.push(selector), |
528 | 369k | Err(err) => match recovery { |
529 | 368k | ParseErrorRecovery::DiscardList => return Err(err), |
530 | 169 | ParseErrorRecovery::IgnoreInvalidSelector => {} |
531 | | }, |
532 | | } |
533 | | |
534 | | loop { |
535 | 1.20M | match input.next() { |
536 | 1.03M | Err(_) => return Ok(SelectorList(values)), |
537 | 162k | Ok(&Token::Comma) => break, |
538 | | Ok(_) => { |
539 | 0 | debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); |
540 | | } |
541 | | } |
542 | | } |
543 | | } |
544 | 1.40M | } <parcel_selectors::parser::SelectorList<lightningcss::selector::private::Selectors>>::parse_relative_with_state::<lightningcss::selector::SelectorParser> Line | Count | Source | 503 | 1.40M | fn parse_relative_with_state<'t, P>( | 504 | 1.40M | parser: &P, | 505 | 1.40M | input: &mut CssParser<'i, 't>, | 506 | 1.40M | state: &mut SelectorParsingState, | 507 | 1.40M | recovery: ParseErrorRecovery, | 508 | 1.40M | nesting_requirement: NestingRequirement, | 509 | 1.40M | ) -> Result<Self, ParseError<'i, P::Error>> | 510 | 1.40M | where | 511 | 1.40M | P: Parser<'i, Impl = Impl>, | 512 | | { | 513 | 1.40M | let original_state = *state; | 514 | 1.40M | let mut values = SmallVec::new(); | 515 | | loop { | 516 | 1.56M | let selector = input.parse_until_before(Delimiter::Comma, |input| { | 517 | | let mut selector_state = original_state; | 518 | | let result = parse_relative_selector(parser, input, &mut selector_state, nesting_requirement); | 519 | | if selector_state.contains(SelectorParsingState::AFTER_NESTING) { | 520 | | state.insert(SelectorParsingState::AFTER_NESTING) | 521 | | } | 522 | | result | 523 | | }); | 524 | | | 525 | 1.56M | let was_ok = selector.is_ok(); | 526 | 1.56M | match selector { | 527 | 1.20M | Ok(selector) => values.push(selector), | 528 | 369k | Err(err) => match recovery { | 529 | 368k | ParseErrorRecovery::DiscardList => return Err(err), | 530 | 169 | ParseErrorRecovery::IgnoreInvalidSelector => {} | 531 | | }, | 532 | | } | 533 | | | 534 | | loop { | 535 | 1.20M | match input.next() { | 536 | 1.03M | Err(_) => return Ok(SelectorList(values)), | 537 | 162k | Ok(&Token::Comma) => break, | 538 | | Ok(_) => { | 539 | 0 | debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); | 540 | | } | 541 | | } | 542 | | } | 543 | | } | 544 | 1.40M | } |
Unexecuted instantiation: <parcel_selectors::parser::SelectorList<_>>::parse_relative_with_state::<_> |
545 | | |
546 | | /// Creates a new SelectorList. |
547 | 0 | pub fn new(v: SmallVec<[Selector<'i, Impl>; 1]>) -> Self { |
548 | 0 | SelectorList(v) |
549 | 0 | } |
550 | | |
551 | | /// Creates a SelectorList from a Vec of selectors. Used in tests. |
552 | 0 | pub fn from_vec(v: Vec<Selector<'i, Impl>>) -> Self { |
553 | 0 | SelectorList(SmallVec::from_vec(v)) |
554 | 0 | } |
555 | | } |
556 | | |
557 | | impl<'i, Impl: SelectorImpl<'i>> From<Selector<'i, Impl>> for SelectorList<'i, Impl> { |
558 | 0 | fn from(selector: Selector<'i, Impl>) -> Self { |
559 | 0 | SelectorList(smallvec![selector]) |
560 | 0 | } |
561 | | } |
562 | | |
563 | | impl<'i, Impl: SelectorImpl<'i>> From<Component<'i, Impl>> for SelectorList<'i, Impl> { |
564 | 0 | fn from(component: Component<'i, Impl>) -> Self { |
565 | 0 | SelectorList::from(Selector::from(component)) |
566 | 0 | } |
567 | | } |
568 | | |
569 | | /// Parses one compound selector suitable for nested stuff like :-moz-any, etc. |
570 | 1 | fn parse_inner_compound_selector<'i, 't, P, Impl>( |
571 | 1 | parser: &P, |
572 | 1 | input: &mut CssParser<'i, 't>, |
573 | 1 | state: &mut SelectorParsingState, |
574 | 1 | ) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>> |
575 | 1 | where |
576 | 1 | P: Parser<'i, Impl = Impl>, |
577 | 1 | Impl: SelectorImpl<'i>, |
578 | | { |
579 | 1 | let mut child_state = |
580 | 1 | *state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS; |
581 | 1 | let result = parse_selector(parser, input, &mut child_state, NestingRequirement::None)?; |
582 | 1 | if child_state.contains(SelectorParsingState::AFTER_NESTING) { |
583 | 0 | state.insert(SelectorParsingState::AFTER_NESTING) |
584 | 1 | } |
585 | 1 | Ok(result) |
586 | 1 | } parcel_selectors::parser::parse_inner_compound_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 570 | 1 | fn parse_inner_compound_selector<'i, 't, P, Impl>( | 571 | 1 | parser: &P, | 572 | 1 | input: &mut CssParser<'i, 't>, | 573 | 1 | state: &mut SelectorParsingState, | 574 | 1 | ) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>> | 575 | 1 | where | 576 | 1 | P: Parser<'i, Impl = Impl>, | 577 | 1 | Impl: SelectorImpl<'i>, | 578 | | { | 579 | 1 | let mut child_state = | 580 | 1 | *state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS; | 581 | 1 | let result = parse_selector(parser, input, &mut child_state, NestingRequirement::None)?; | 582 | 1 | if child_state.contains(SelectorParsingState::AFTER_NESTING) { | 583 | 0 | state.insert(SelectorParsingState::AFTER_NESTING) | 584 | 1 | } | 585 | 1 | Ok(result) | 586 | 1 | } |
Unexecuted instantiation: parcel_selectors::parser::parse_inner_compound_selector::<_, _> |
587 | | |
588 | | /// Ancestor hashes for the bloom filter. We precompute these and store them |
589 | | /// inline with selectors to optimize cache performance during matching. |
590 | | /// This matters a lot. |
591 | | /// |
592 | | /// We use 4 hashes, which is copied from Gecko, who copied it from WebKit. |
593 | | /// Note that increasing the number of hashes here will adversely affect the |
594 | | /// cache hit when fast-rejecting long lists of Rules with inline hashes. |
595 | | /// |
596 | | /// Because the bloom filter only uses the bottom 24 bits of the hash, we pack |
597 | | /// the fourth hash into the upper bits of the first three hashes in order to |
598 | | /// shrink Rule (whose size matters a lot). This scheme minimizes the runtime |
599 | | /// overhead of the packing for the first three hashes (we just need to mask |
600 | | /// off the upper bits) at the expense of making the fourth somewhat more |
601 | | /// complicated to assemble, because we often bail out before checking all the |
602 | | /// hashes. |
603 | | #[derive(Clone, Debug, Eq, PartialEq)] |
604 | | pub struct AncestorHashes { |
605 | | pub packed_hashes: [u32; 3], |
606 | | } |
607 | | |
608 | 0 | fn collect_ancestor_hashes<'i, Impl: SelectorImpl<'i>>( |
609 | 0 | iter: SelectorIter<'_, 'i, Impl>, |
610 | 0 | quirks_mode: QuirksMode, |
611 | 0 | hashes: &mut [u32; 4], |
612 | 0 | len: &mut usize, |
613 | 0 | ) -> bool |
614 | 0 | where |
615 | 0 | Impl::Identifier: PrecomputedHash, |
616 | 0 | Impl::LocalName: PrecomputedHash, |
617 | 0 | Impl::NamespaceUrl: PrecomputedHash, |
618 | | { |
619 | 0 | for component in AncestorIter::new(iter) { |
620 | 0 | let hash = match *component { |
621 | | Component::LocalName(LocalName { |
622 | 0 | ref name, |
623 | 0 | ref lower_name, |
624 | | }) => { |
625 | | // Only insert the local-name into the filter if it's all |
626 | | // lowercase. Otherwise we would need to test both hashes, and |
627 | | // our data structures aren't really set up for that. |
628 | 0 | if name != lower_name { |
629 | 0 | continue; |
630 | 0 | } |
631 | 0 | name.precomputed_hash() |
632 | | } |
633 | 0 | Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => url.precomputed_hash(), |
634 | | // In quirks mode, class and id selectors should match |
635 | | // case-insensitively, so just avoid inserting them into the filter. |
636 | 0 | Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(), |
637 | 0 | Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => class.precomputed_hash(), |
638 | 0 | Component::Is(ref list) | Component::Where(ref list) => { |
639 | | // :where and :is OR their selectors, so we can't put any hash |
640 | | // in the filter if there's more than one selector, as that'd |
641 | | // exclude elements that may match one of the other selectors. |
642 | 0 | if list.len() == 1 && !collect_ancestor_hashes(list[0].iter(), quirks_mode, hashes, len) { |
643 | 0 | return false; |
644 | 0 | } |
645 | 0 | continue; |
646 | | } |
647 | 0 | _ => continue, |
648 | | }; |
649 | | |
650 | 0 | hashes[*len] = hash & BLOOM_HASH_MASK; |
651 | 0 | *len += 1; |
652 | 0 | if *len == hashes.len() { |
653 | 0 | return false; |
654 | 0 | } |
655 | | } |
656 | 0 | true |
657 | 0 | } |
658 | | |
659 | | impl AncestorHashes { |
660 | 0 | pub fn new<'i, Impl: SelectorImpl<'i>>(selector: &Selector<'i, Impl>, quirks_mode: QuirksMode) -> Self |
661 | 0 | where |
662 | 0 | Impl::Identifier: PrecomputedHash, |
663 | 0 | Impl::LocalName: PrecomputedHash, |
664 | 0 | Impl::NamespaceUrl: PrecomputedHash, |
665 | | { |
666 | | // Compute ancestor hashes for the bloom filter. |
667 | 0 | let mut hashes = [0u32; 4]; |
668 | 0 | let mut len = 0; |
669 | 0 | collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len); |
670 | 0 | debug_assert!(len <= 4); |
671 | | |
672 | | // Now, pack the fourth hash (if it exists) into the upper byte of each of |
673 | | // the other three hashes. |
674 | 0 | if len == 4 { |
675 | 0 | let fourth = hashes[3]; |
676 | 0 | hashes[0] |= (fourth & 0x000000ff) << 24; |
677 | 0 | hashes[1] |= (fourth & 0x0000ff00) << 16; |
678 | 0 | hashes[2] |= (fourth & 0x00ff0000) << 8; |
679 | 0 | } |
680 | | |
681 | 0 | AncestorHashes { |
682 | 0 | packed_hashes: [hashes[0], hashes[1], hashes[2]], |
683 | 0 | } |
684 | 0 | } |
685 | | |
686 | | /// Returns the fourth hash, reassembled from parts. |
687 | 0 | pub fn fourth_hash(&self) -> u32 { |
688 | 0 | ((self.packed_hashes[0] & 0xff000000) >> 24) |
689 | 0 | | ((self.packed_hashes[1] & 0xff000000) >> 16) |
690 | 0 | | ((self.packed_hashes[2] & 0xff000000) >> 8) |
691 | 0 | } |
692 | | } |
693 | | |
694 | 0 | pub fn namespace_empty_string<'i, Impl: SelectorImpl<'i>>() -> Impl::NamespaceUrl { |
695 | | // Rust type’s default, not default namespace |
696 | 0 | Impl::NamespaceUrl::default() |
697 | 0 | } |
698 | | |
699 | | /// A Selector stores a sequence of simple selectors and combinators. The |
700 | | /// iterator classes allow callers to iterate at either the raw sequence level or |
701 | | /// at the level of sequences of simple selectors separated by combinators. Most |
702 | | /// callers want the higher-level iterator. |
703 | | /// |
704 | | /// We store compound selectors internally right-to-left (in matching order). |
705 | | /// Additionally, we invert the order of top-level compound selectors so that |
706 | | /// each one matches left-to-right. This is because matching namespace, local name, |
707 | | /// id, and class are all relatively cheap, whereas matching pseudo-classes might |
708 | | /// be expensive (depending on the pseudo-class). Since authors tend to put the |
709 | | /// pseudo-classes on the right, it's faster to start matching on the left. |
710 | | /// |
711 | | /// This reordering doesn't change the semantics of selector matching, and we |
712 | | /// handle it in to_css to make it invisible to serialization. |
713 | | #[derive(Clone, PartialEq, Eq, Hash)] |
714 | | pub struct Selector<'i, Impl: SelectorImpl<'i>>(SpecificityAndFlags, Vec<Component<'i, Impl>>); |
715 | | |
716 | | #[cfg(feature = "into_owned")] |
717 | | impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for Selector<'i, Impl> |
718 | | where |
719 | | Impl: static_self::IntoOwned<'any, Owned = NewSel>, |
720 | | NewSel: SelectorImpl<'any>, |
721 | | Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>, |
722 | | { |
723 | | type Owned = Selector<'any, NewSel>; |
724 | | |
725 | | fn into_owned(self) -> Self::Owned { |
726 | | Selector(self.0, self.1.into_owned()) |
727 | | } |
728 | | } |
729 | | |
730 | | impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { |
731 | | #[inline] |
732 | 24.6k | pub fn specificity(&self) -> u32 { |
733 | 24.6k | self.0.specificity() |
734 | 24.6k | } <parcel_selectors::parser::Selector<lightningcss::selector::private::Selectors>>::specificity Line | Count | Source | 732 | 24.6k | pub fn specificity(&self) -> u32 { | 733 | 24.6k | self.0.specificity() | 734 | 24.6k | } |
Unexecuted instantiation: <parcel_selectors::parser::Selector<_>>::specificity |
735 | | |
736 | | #[inline] |
737 | 0 | pub fn has_pseudo_element(&self) -> bool { |
738 | 0 | self.0.has_pseudo_element() |
739 | 0 | } |
740 | | |
741 | | #[inline] |
742 | 0 | pub fn is_slotted(&self) -> bool { |
743 | 0 | self.0.is_slotted() |
744 | 0 | } |
745 | | |
746 | | #[inline] |
747 | 0 | pub fn is_part(&self) -> bool { |
748 | 0 | self.0.is_part() |
749 | 0 | } |
750 | | |
751 | | #[inline] |
752 | 0 | pub fn append(&mut self, component: Component<'i, Impl>) { |
753 | 0 | let index = self |
754 | 0 | .1 |
755 | 0 | .iter() |
756 | 0 | .position(|c| matches!(*c, Component::Combinator(..) | Component::PseudoElement(..))) |
757 | 0 | .unwrap_or(self.1.len()); |
758 | 0 | self.1.insert(index, component); |
759 | 0 | } |
760 | | |
761 | | #[inline] |
762 | 0 | pub fn parts(&self) -> Option<&[Impl::Identifier]> { |
763 | 0 | if !self.is_part() { |
764 | 0 | return None; |
765 | 0 | } |
766 | | |
767 | 0 | let mut iter = self.iter(); |
768 | 0 | if self.has_pseudo_element() { |
769 | | // Skip the pseudo-element. |
770 | 0 | for _ in &mut iter {} |
771 | | |
772 | 0 | let combinator = iter.next_sequence()?; |
773 | 0 | debug_assert_eq!(combinator, Combinator::PseudoElement); |
774 | 0 | } |
775 | | |
776 | 0 | for component in iter { |
777 | 0 | if let Component::Part(ref part) = *component { |
778 | 0 | return Some(part); |
779 | 0 | } |
780 | | } |
781 | | |
782 | 0 | debug_assert!(false, "is_part() lied somehow?"); |
783 | 0 | None |
784 | 0 | } |
785 | | |
786 | | #[inline] |
787 | 0 | pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { |
788 | 0 | if !self.has_pseudo_element() { |
789 | 0 | return None; |
790 | 0 | } |
791 | | |
792 | 0 | for component in self.iter() { |
793 | 0 | if let Component::PseudoElement(ref pseudo) = *component { |
794 | 0 | return Some(pseudo); |
795 | 0 | } |
796 | | } |
797 | | |
798 | 0 | debug_assert!(false, "has_pseudo_element lied!"); |
799 | 0 | None |
800 | 0 | } |
801 | | |
802 | | /// Whether this selector (pseudo-element part excluded) matches every element. |
803 | | /// |
804 | | /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs |
805 | | #[inline] |
806 | 0 | pub fn is_universal(&self) -> bool { |
807 | 0 | self.iter_raw_match_order().all(|c| { |
808 | 0 | matches!( |
809 | 0 | *c, |
810 | | Component::ExplicitUniversalType |
811 | | | Component::ExplicitAnyNamespace |
812 | | | Component::Combinator(Combinator::PseudoElement) |
813 | | | Component::PseudoElement(..) |
814 | | ) |
815 | 0 | }) |
816 | 0 | } |
817 | | |
818 | | #[inline] |
819 | 0 | pub fn has_combinator(&self) -> bool { |
820 | 0 | self |
821 | 0 | .iter_raw_match_order() |
822 | 0 | .any(|c| matches!(*c, Component::Combinator(combinator) if combinator.is_tree_combinator())) Unexecuted instantiation: <parcel_selectors::parser::Selector<lightningcss::selector::private::Selectors>>::has_combinator::{closure#0}Unexecuted instantiation: <parcel_selectors::parser::Selector<_>>::has_combinator::{closure#0} |
823 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::Selector<lightningcss::selector::private::Selectors>>::has_combinator Unexecuted instantiation: <parcel_selectors::parser::Selector<_>>::has_combinator |
824 | | |
825 | | /// Returns an iterator over this selector in matching order (right-to-left). |
826 | | /// When a combinator is reached, the iterator will return None, and |
827 | | /// next_sequence() may be called to continue to the next sequence. |
828 | | #[inline] |
829 | 0 | pub fn iter(&self) -> SelectorIter<'_, 'i, Impl> { |
830 | 0 | SelectorIter { |
831 | 0 | iter: self.iter_raw_match_order(), |
832 | 0 | next_combinator: None, |
833 | 0 | } |
834 | 0 | } |
835 | | |
836 | | /// Whether this selector is a featureless :host selector, with no |
837 | | /// combinators to the left, and optionally has a pseudo-element to the |
838 | | /// right. |
839 | | #[inline] |
840 | 0 | pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool { |
841 | 0 | let mut iter = self.iter(); |
842 | 0 | if !self.has_pseudo_element() { |
843 | 0 | return iter.is_featureless_host_selector(); |
844 | 0 | } |
845 | | |
846 | | // Skip the pseudo-element. |
847 | 0 | for _ in &mut iter {} |
848 | | |
849 | 0 | match iter.next_sequence() { |
850 | 0 | None => return false, |
851 | 0 | Some(combinator) => { |
852 | 0 | debug_assert_eq!(combinator, Combinator::PseudoElement); |
853 | | } |
854 | | } |
855 | | |
856 | 0 | iter.is_featureless_host_selector() |
857 | 0 | } |
858 | | |
859 | | /// Returns an iterator over this selector in matching order (right-to-left), |
860 | | /// skipping the rightmost |offset| Components. |
861 | | #[inline] |
862 | 0 | pub fn iter_from(&self, offset: usize) -> SelectorIter<'_, 'i, Impl> { |
863 | 0 | let iter = self.1[offset..].iter(); |
864 | 0 | SelectorIter { |
865 | 0 | iter, |
866 | 0 | next_combinator: None, |
867 | 0 | } |
868 | 0 | } |
869 | | |
870 | | /// Returns the combinator at index `index` (zero-indexed from the right), |
871 | | /// or panics if the component is not a combinator. |
872 | | #[inline] |
873 | 0 | pub fn combinator_at_match_order(&self, index: usize) -> Combinator { |
874 | 0 | match self.1[index] { |
875 | 0 | Component::Combinator(c) => c, |
876 | 0 | ref other => panic!("Not a combinator: {:?}, {:?}, index: {}", other, self, index), |
877 | | } |
878 | 0 | } |
879 | | |
880 | | /// Returns an iterator over the entire sequence of simple selectors and |
881 | | /// combinators, in matching order (from right to left). |
882 | | #[inline] |
883 | 708k | pub fn iter_raw_match_order(&self) -> slice::Iter<'_, Component<'i, Impl>> { |
884 | 708k | self.1.iter() |
885 | 708k | } <parcel_selectors::parser::Selector<lightningcss::selector::private::Selectors>>::iter_raw_match_order Line | Count | Source | 883 | 708k | pub fn iter_raw_match_order(&self) -> slice::Iter<'_, Component<'i, Impl>> { | 884 | 708k | self.1.iter() | 885 | 708k | } |
Unexecuted instantiation: <parcel_selectors::parser::Selector<_>>::iter_raw_match_order |
886 | | |
887 | | #[inline] |
888 | 0 | pub fn iter_mut_raw_match_order(&mut self) -> slice::IterMut<'_, Component<'i, Impl>> { |
889 | 0 | self.1.iter_mut() |
890 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::Selector<lightningcss::selector::private::Selectors>>::iter_mut_raw_match_order Unexecuted instantiation: <parcel_selectors::parser::Selector<_>>::iter_mut_raw_match_order |
891 | | |
892 | | /// Returns the combinator at index `index` (zero-indexed from the left), |
893 | | /// or panics if the component is not a combinator. |
894 | | #[inline] |
895 | 0 | pub fn combinator_at_parse_order(&self, index: usize) -> Combinator { |
896 | 0 | match self.1[self.len() - index - 1] { |
897 | 0 | Component::Combinator(c) => c, |
898 | 0 | ref other => panic!("Not a combinator: {:?}, {:?}, index: {}", other, self, index), |
899 | | } |
900 | 0 | } |
901 | | |
902 | | /// Returns an iterator over the sequence of simple selectors and |
903 | | /// combinators, in parse order (from left to right), starting from |
904 | | /// `offset`. |
905 | | #[inline] |
906 | 0 | pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev<slice::Iter<'_, Component<'i, Impl>>> { |
907 | 0 | self.1[..self.len() - offset].iter().rev() |
908 | 0 | } |
909 | | |
910 | | /// Creates a Selector from a vec of Components, specified in parse order. Used in tests. |
911 | | #[allow(unused)] |
912 | 0 | pub(crate) fn from_vec(vec: Vec<Component<'i, Impl>>, specificity: u32, flags: SelectorFlags) -> Self { |
913 | 0 | let mut builder = SelectorBuilder::default(); |
914 | 0 | for component in vec.into_iter() { |
915 | 0 | if let Some(combinator) = component.as_combinator() { |
916 | 0 | builder.push_combinator(combinator); |
917 | 0 | } else { |
918 | 0 | builder.push_simple_selector(component); |
919 | 0 | } |
920 | | } |
921 | 0 | let spec = SpecificityAndFlags { specificity, flags }; |
922 | 0 | let (spec, components) = builder.build_with_specificity_and_flags(spec); |
923 | 0 | Selector(spec, components) |
924 | 0 | } |
925 | | |
926 | | #[cfg(feature = "serde")] |
927 | | #[inline] |
928 | | pub(crate) fn new(spec: SpecificityAndFlags, components: Vec<Component<'i, Impl>>) -> Self { |
929 | | Selector(spec, components) |
930 | | } |
931 | | |
932 | | /// Returns count of simple selectors and combinators in the Selector. |
933 | | #[inline] |
934 | 0 | pub fn len(&self) -> usize { |
935 | 0 | self.1.len() |
936 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::Selector<lightningcss::selector::private::Selectors>>::len Unexecuted instantiation: <parcel_selectors::parser::Selector<_>>::len |
937 | | |
938 | | /// Traverse selector components inside `self`. |
939 | | /// |
940 | | /// Implementations of this method should call `SelectorVisitor` methods |
941 | | /// or other impls of `Visit` as appropriate based on the fields of `Self`. |
942 | | /// |
943 | | /// A return value of `false` indicates terminating the traversal. |
944 | | /// It should be propagated with an early return. |
945 | | /// On the contrary, `true` indicates that all fields of `self` have been traversed: |
946 | | /// |
947 | | /// ```rust,ignore |
948 | | /// if !visitor.visit_simple_selector(&self.some_simple_selector) { |
949 | | /// return false; |
950 | | /// } |
951 | | /// if !self.some_component.visit(visitor) { |
952 | | /// return false; |
953 | | /// } |
954 | | /// true |
955 | | /// ``` |
956 | 0 | pub fn visit<V>(&self, visitor: &mut V) -> bool |
957 | 0 | where |
958 | 0 | V: SelectorVisitor<'i, Impl = Impl>, |
959 | | { |
960 | 0 | let mut current = self.iter(); |
961 | 0 | let mut combinator = None; |
962 | | loop { |
963 | 0 | if !visitor.visit_complex_selector(combinator) { |
964 | 0 | return false; |
965 | 0 | } |
966 | | |
967 | 0 | for selector in &mut current { |
968 | 0 | if !selector.visit(visitor) { |
969 | 0 | return false; |
970 | 0 | } |
971 | | } |
972 | | |
973 | 0 | combinator = current.next_sequence(); |
974 | 0 | if combinator.is_none() { |
975 | 0 | break; |
976 | 0 | } |
977 | | } |
978 | | |
979 | 0 | true |
980 | 0 | } |
981 | | } |
982 | | |
983 | | impl<'i, Impl: SelectorImpl<'i>> From<Component<'i, Impl>> for Selector<'i, Impl> { |
984 | 0 | fn from(component: Component<'i, Impl>) -> Self { |
985 | 0 | let mut builder = SelectorBuilder::default(); |
986 | 0 | if let Some(combinator) = component.as_combinator() { |
987 | 0 | builder.push_combinator(combinator); |
988 | 0 | } else { |
989 | 0 | builder.push_simple_selector(component); |
990 | 0 | } |
991 | 0 | let (spec, components) = builder.build(false, false, false); |
992 | 0 | Selector(spec, components) |
993 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::Selector<lightningcss::selector::private::Selectors> as core::convert::From<parcel_selectors::parser::Component<lightningcss::selector::private::Selectors>>>::from Unexecuted instantiation: <parcel_selectors::parser::Selector<_> as core::convert::From<parcel_selectors::parser::Component<_>>>::from |
994 | | } |
995 | | |
996 | | impl<'i, Impl: SelectorImpl<'i>> From<Vec<Component<'i, Impl>>> for Selector<'i, Impl> { |
997 | 0 | fn from(vec: Vec<Component<'i, Impl>>) -> Self { |
998 | 0 | let mut builder = SelectorBuilder::default(); |
999 | 0 | for component in vec.into_iter() { |
1000 | 0 | if let Some(combinator) = component.as_combinator() { |
1001 | 0 | builder.push_combinator(combinator); |
1002 | 0 | } else { |
1003 | 0 | builder.push_simple_selector(component); |
1004 | 0 | } |
1005 | | } |
1006 | 0 | let (spec, components) = builder.build(false, false, false); |
1007 | 0 | Selector(spec, components) |
1008 | 0 | } |
1009 | | } |
1010 | | |
1011 | | #[derive(Clone)] |
1012 | | pub struct SelectorIter<'a, 'i, Impl: SelectorImpl<'i>> { |
1013 | | iter: slice::Iter<'a, Component<'i, Impl>>, |
1014 | | next_combinator: Option<Combinator>, |
1015 | | } |
1016 | | |
1017 | | impl<'a, 'i, Impl: 'a + SelectorImpl<'i>> SelectorIter<'a, 'i, Impl> { |
1018 | | /// Prepares this iterator to point to the next sequence to the left, |
1019 | | /// returning the combinator if the sequence was found. |
1020 | | #[inline] |
1021 | 0 | pub fn next_sequence(&mut self) -> Option<Combinator> { |
1022 | 0 | self.next_combinator.take() |
1023 | 0 | } |
1024 | | |
1025 | | /// Whether this selector is a featureless host selector, with no |
1026 | | /// combinators to the left. |
1027 | | #[inline] |
1028 | 0 | pub(crate) fn is_featureless_host_selector(&mut self) -> bool { |
1029 | 0 | self.selector_length() > 0 |
1030 | 0 | && self.all(|component| matches!(*component, Component::Host(..))) |
1031 | 0 | && self.next_sequence().is_none() |
1032 | 0 | } |
1033 | | |
1034 | | #[inline] |
1035 | 0 | pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool { |
1036 | 0 | let first = match self.next() { |
1037 | 0 | Some(c) => c, |
1038 | | // Note that this is the common path that we keep inline: the |
1039 | | // pseudo-element not having anything to its right. |
1040 | 0 | None => return true, |
1041 | | }; |
1042 | 0 | self.matches_for_stateless_pseudo_element_internal(first) |
1043 | 0 | } |
1044 | | |
1045 | | #[inline(never)] |
1046 | 0 | fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<'i, Impl>) -> bool { |
1047 | 0 | if !first.matches_for_stateless_pseudo_element() { |
1048 | 0 | return false; |
1049 | 0 | } |
1050 | 0 | for component in self { |
1051 | | // The only other parser-allowed Components in this sequence are |
1052 | | // state pseudo-classes, or one of the other things that can contain |
1053 | | // them. |
1054 | 0 | if !component.matches_for_stateless_pseudo_element() { |
1055 | 0 | return false; |
1056 | 0 | } |
1057 | | } |
1058 | 0 | true |
1059 | 0 | } |
1060 | | |
1061 | | /// Returns remaining count of the simple selectors and combinators in the Selector. |
1062 | | #[inline] |
1063 | 0 | pub fn selector_length(&self) -> usize { |
1064 | 0 | self.iter.len() |
1065 | 0 | } |
1066 | | } |
1067 | | |
1068 | | impl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for SelectorIter<'a, 'i, Impl> { |
1069 | | type Item = &'a Component<'i, Impl>; |
1070 | | |
1071 | | #[inline] |
1072 | 0 | fn next(&mut self) -> Option<Self::Item> { |
1073 | 0 | debug_assert!(self.next_combinator.is_none(), "You should call next_sequence!"); |
1074 | 0 | match *self.iter.next()? { |
1075 | 0 | Component::Combinator(c) => { |
1076 | 0 | self.next_combinator = Some(c); |
1077 | 0 | None |
1078 | | } |
1079 | 0 | ref x => Some(x), |
1080 | | } |
1081 | 0 | } |
1082 | | } |
1083 | | |
1084 | | impl<'a, 'i, Impl: SelectorImpl<'i>> fmt::Debug for SelectorIter<'a, 'i, Impl> { |
1085 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1086 | 0 | let iter = self.iter.clone().rev(); |
1087 | 0 | for component in iter { |
1088 | 0 | component.to_css(f)? |
1089 | | } |
1090 | 0 | Ok(()) |
1091 | 0 | } |
1092 | | } |
1093 | | |
1094 | | /// An iterator over all simple selectors belonging to ancestors. |
1095 | | struct AncestorIter<'a, 'i, Impl: SelectorImpl<'i>>(SelectorIter<'a, 'i, Impl>); |
1096 | | impl<'a, 'i, Impl: 'a + SelectorImpl<'i>> AncestorIter<'a, 'i, Impl> { |
1097 | | /// Creates an AncestorIter. The passed-in iterator is assumed to point to |
1098 | | /// the beginning of the child sequence, which will be skipped. |
1099 | 0 | fn new(inner: SelectorIter<'a, 'i, Impl>) -> Self { |
1100 | 0 | let mut result = AncestorIter(inner); |
1101 | 0 | result.skip_until_ancestor(); |
1102 | 0 | result |
1103 | 0 | } |
1104 | | |
1105 | | /// Skips a sequence of simple selectors and all subsequent sequences until |
1106 | | /// a non-pseudo-element ancestor combinator is reached. |
1107 | 0 | fn skip_until_ancestor(&mut self) { |
1108 | | loop { |
1109 | 0 | while self.0.next().is_some() {} |
1110 | | // If this is ever changed to stop at the "pseudo-element" |
1111 | | // combinator, we will need to fix the way we compute hashes for |
1112 | | // revalidation selectors. |
1113 | 0 | if self |
1114 | 0 | .0 |
1115 | 0 | .next_sequence() |
1116 | 0 | .map_or(true, |x| matches!(x, Combinator::Child | Combinator::Descendant)) |
1117 | | { |
1118 | 0 | break; |
1119 | 0 | } |
1120 | | } |
1121 | 0 | } |
1122 | | } |
1123 | | |
1124 | | impl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for AncestorIter<'a, 'i, Impl> { |
1125 | | type Item = &'a Component<'i, Impl>; |
1126 | 0 | fn next(&mut self) -> Option<Self::Item> { |
1127 | | // Grab the next simple selector in the sequence if available. |
1128 | 0 | let next = self.0.next(); |
1129 | 0 | if next.is_some() { |
1130 | 0 | return next; |
1131 | 0 | } |
1132 | | |
1133 | | // See if there are more sequences. If so, skip any non-ancestor sequences. |
1134 | 0 | if let Some(combinator) = self.0.next_sequence() { |
1135 | 0 | if !matches!(combinator, Combinator::Child | Combinator::Descendant) { |
1136 | 0 | self.skip_until_ancestor(); |
1137 | 0 | } |
1138 | 0 | } |
1139 | | |
1140 | 0 | self.0.next() |
1141 | 0 | } |
1142 | | } |
1143 | | |
1144 | | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] |
1145 | | #[cfg_attr( |
1146 | | feature = "serde", |
1147 | | derive(serde::Serialize, serde::Deserialize), |
1148 | | serde(rename_all = "kebab-case") |
1149 | | )] |
1150 | | #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] |
1151 | | #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] |
1152 | | pub enum Combinator { |
1153 | | Child, // > |
1154 | | Descendant, // space |
1155 | | NextSibling, // + |
1156 | | LaterSibling, // ~ |
1157 | | /// A dummy combinator we use to the left of pseudo-elements. |
1158 | | /// |
1159 | | /// It serializes as the empty string, and acts effectively as a child |
1160 | | /// combinator in most cases. If we ever actually start using a child |
1161 | | /// combinator for this, we will need to fix up the way hashes are computed |
1162 | | /// for revalidation selectors. |
1163 | | PseudoElement, |
1164 | | /// Another combinator used for ::slotted(), which represent the jump from |
1165 | | /// a node to its assigned slot. |
1166 | | SlotAssignment, |
1167 | | /// Another combinator used for `::part()`, which represents the jump from |
1168 | | /// the part to the containing shadow host. |
1169 | | Part, |
1170 | | |
1171 | | /// Non-standard Vue >>> combinator. |
1172 | | /// https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors |
1173 | | DeepDescendant, |
1174 | | /// Non-standard /deep/ combinator. |
1175 | | /// Appeared in early versions of the css-scoping-1 specification: |
1176 | | /// https://www.w3.org/TR/2014/WD-css-scoping-1-20140403/#deep-combinator |
1177 | | /// And still supported as an alias for >>> by Vue. |
1178 | | Deep, |
1179 | | } |
1180 | | |
1181 | | impl Combinator { |
1182 | | /// Returns true if this combinator is a child or descendant combinator. |
1183 | | #[inline] |
1184 | 0 | pub fn is_ancestor(&self) -> bool { |
1185 | 0 | matches!( |
1186 | 0 | *self, |
1187 | | Combinator::Child | Combinator::Descendant | Combinator::PseudoElement | Combinator::SlotAssignment |
1188 | | ) |
1189 | 0 | } |
1190 | | |
1191 | | /// Returns true if this combinator is a pseudo-element combinator. |
1192 | | #[inline] |
1193 | 0 | pub fn is_pseudo_element(&self) -> bool { |
1194 | 0 | matches!(*self, Combinator::PseudoElement) |
1195 | 0 | } |
1196 | | |
1197 | | /// Returns true if this combinator is a next- or later-sibling combinator. |
1198 | | #[inline] |
1199 | 0 | pub fn is_sibling(&self) -> bool { |
1200 | 0 | matches!(*self, Combinator::NextSibling | Combinator::LaterSibling) |
1201 | 0 | } |
1202 | | |
1203 | | #[inline] |
1204 | 0 | pub fn is_tree_combinator(&self) -> bool { |
1205 | 0 | matches!( |
1206 | 0 | *self, |
1207 | | Combinator::Child | Combinator::Descendant | Combinator::NextSibling | Combinator::LaterSibling |
1208 | | ) |
1209 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::Combinator>::is_tree_combinator Unexecuted instantiation: <parcel_selectors::parser::Combinator>::is_tree_combinator |
1210 | | } |
1211 | | |
1212 | | /// An enum for the different types of :nth- pseudoclasses |
1213 | | #[derive(Copy, Clone, Eq, PartialEq, Hash)] |
1214 | | #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] |
1215 | | pub enum NthType { |
1216 | | Child, |
1217 | | LastChild, |
1218 | | OnlyChild, |
1219 | | OfType, |
1220 | | LastOfType, |
1221 | | OnlyOfType, |
1222 | | Col, |
1223 | | LastCol, |
1224 | | } |
1225 | | |
1226 | | impl NthType { |
1227 | 0 | pub fn is_only(self) -> bool { |
1228 | 0 | self == Self::OnlyChild || self == Self::OnlyOfType |
1229 | 0 | } |
1230 | | |
1231 | 0 | pub fn is_of_type(self) -> bool { |
1232 | 0 | self == Self::OfType || self == Self::LastOfType || self == Self::OnlyOfType |
1233 | 0 | } |
1234 | | |
1235 | 0 | pub fn is_from_end(self) -> bool { |
1236 | 0 | self == Self::LastChild || self == Self::LastOfType || self == Self::LastCol |
1237 | 0 | } |
1238 | | |
1239 | 0 | pub fn allows_of_selector(self) -> bool { |
1240 | 0 | self == Self::Child || self == Self::LastChild |
1241 | 0 | } |
1242 | | } |
1243 | | |
1244 | | /// The properties that comprise an :nth- pseudoclass as of Selectors 3 (e.g., |
1245 | | /// nth-child(An+B)). |
1246 | | /// https://www.w3.org/TR/selectors-3/#nth-child-pseudo |
1247 | | #[derive(Copy, Clone, Eq, PartialEq, Hash)] |
1248 | | #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] |
1249 | | pub struct NthSelectorData { |
1250 | | pub ty: NthType, |
1251 | | pub is_function: bool, |
1252 | | pub a: i32, |
1253 | | pub b: i32, |
1254 | | } |
1255 | | |
1256 | | impl NthSelectorData { |
1257 | | /// Returns selector data for :only-{child,of-type} |
1258 | | #[inline] |
1259 | 11 | pub const fn only(of_type: bool) -> Self { |
1260 | | Self { |
1261 | 11 | ty: if of_type { |
1262 | 10 | NthType::OnlyOfType |
1263 | | } else { |
1264 | 1 | NthType::OnlyChild |
1265 | | }, |
1266 | | is_function: false, |
1267 | | a: 0, |
1268 | | b: 1, |
1269 | | } |
1270 | 11 | } <parcel_selectors::parser::NthSelectorData>::only Line | Count | Source | 1259 | 11 | pub const fn only(of_type: bool) -> Self { | 1260 | | Self { | 1261 | 11 | ty: if of_type { | 1262 | 10 | NthType::OnlyOfType | 1263 | | } else { | 1264 | 1 | NthType::OnlyChild | 1265 | | }, | 1266 | | is_function: false, | 1267 | | a: 0, | 1268 | | b: 1, | 1269 | | } | 1270 | 11 | } |
Unexecuted instantiation: <parcel_selectors::parser::NthSelectorData>::only |
1271 | | |
1272 | | /// Returns selector data for :first-{child,of-type} |
1273 | | #[inline] |
1274 | 2 | pub const fn first(of_type: bool) -> Self { |
1275 | | Self { |
1276 | 2 | ty: if of_type { NthType::OfType } else { NthType::Child }, |
1277 | | is_function: false, |
1278 | | a: 0, |
1279 | | b: 1, |
1280 | | } |
1281 | 2 | } <parcel_selectors::parser::NthSelectorData>::first Line | Count | Source | 1274 | 2 | pub const fn first(of_type: bool) -> Self { | 1275 | | Self { | 1276 | 2 | ty: if of_type { NthType::OfType } else { NthType::Child }, | 1277 | | is_function: false, | 1278 | | a: 0, | 1279 | | b: 1, | 1280 | | } | 1281 | 2 | } |
Unexecuted instantiation: <parcel_selectors::parser::NthSelectorData>::first |
1282 | | |
1283 | | /// Returns selector data for :last-{child,of-type} |
1284 | | #[inline] |
1285 | 2 | pub const fn last(of_type: bool) -> Self { |
1286 | | Self { |
1287 | 2 | ty: if of_type { |
1288 | 1 | NthType::LastOfType |
1289 | | } else { |
1290 | 1 | NthType::LastChild |
1291 | | }, |
1292 | | is_function: false, |
1293 | | a: 0, |
1294 | | b: 1, |
1295 | | } |
1296 | 2 | } <parcel_selectors::parser::NthSelectorData>::last Line | Count | Source | 1285 | 2 | pub const fn last(of_type: bool) -> Self { | 1286 | | Self { | 1287 | 2 | ty: if of_type { | 1288 | 1 | NthType::LastOfType | 1289 | | } else { | 1290 | 1 | NthType::LastChild | 1291 | | }, | 1292 | | is_function: false, | 1293 | | a: 0, | 1294 | | b: 1, | 1295 | | } | 1296 | 2 | } |
Unexecuted instantiation: <parcel_selectors::parser::NthSelectorData>::last |
1297 | | |
1298 | | #[inline] |
1299 | 0 | pub fn is_function(&self) -> bool { |
1300 | 0 | self.a != 0 || self.b != 1 |
1301 | 0 | } |
1302 | | |
1303 | | /// Writes the beginning of the selector. |
1304 | | #[inline] |
1305 | 0 | pub fn write_start<W: fmt::Write>(&self, dest: &mut W, is_function: bool) -> fmt::Result { |
1306 | 0 | dest.write_str(match self.ty { |
1307 | 0 | NthType::Child if is_function => ":nth-child(", |
1308 | 0 | NthType::Child => ":first-child", |
1309 | 0 | NthType::LastChild if is_function => ":nth-last-child(", |
1310 | 0 | NthType::LastChild => ":last-child", |
1311 | 0 | NthType::OfType if is_function => ":nth-of-type(", |
1312 | 0 | NthType::OfType => ":first-of-type", |
1313 | 0 | NthType::LastOfType if is_function => ":nth-last-of-type(", |
1314 | 0 | NthType::LastOfType => ":last-of-type", |
1315 | 0 | NthType::OnlyChild => ":only-child", |
1316 | 0 | NthType::OnlyOfType => ":only-of-type", |
1317 | 0 | NthType::Col => ":nth-col(", |
1318 | 0 | NthType::LastCol => ":nth-last-col(", |
1319 | | }) |
1320 | 0 | } |
1321 | | |
1322 | | /// Serialize <an+b> (part of the CSS Syntax spec, but currently only used here). |
1323 | | /// <https://drafts.csswg.org/css-syntax-3/#serialize-an-anb-value> |
1324 | | #[inline] |
1325 | 0 | pub fn write_affine<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result { |
1326 | 0 | match (self.a, self.b) { |
1327 | 0 | (0, 0) => dest.write_char('0'), |
1328 | | |
1329 | 0 | (1, 0) => dest.write_char('n'), |
1330 | 0 | (-1, 0) => dest.write_str("-n"), |
1331 | 0 | (_, 0) => write!(dest, "{}n", self.a), |
1332 | | |
1333 | 0 | (2, 1) => dest.write_str("odd"), |
1334 | | |
1335 | 0 | (0, _) => write!(dest, "{}", self.b), |
1336 | 0 | (1, _) => write!(dest, "n{:+}", self.b), |
1337 | 0 | (-1, _) => write!(dest, "-n{:+}", self.b), |
1338 | 0 | (_, _) => write!(dest, "{}n{:+}", self.a, self.b), |
1339 | | } |
1340 | 0 | } |
1341 | | } |
1342 | | |
1343 | | /// The properties that comprise an :nth- pseudoclass as of Selectors 4 (e.g., |
1344 | | /// nth-child(An+B [of S]?)). |
1345 | | /// https://www.w3.org/TR/selectors-4/#nth-child-pseudo |
1346 | | #[derive(Clone, PartialEq, Eq, Hash)] |
1347 | | pub struct NthOfSelectorData<'i, Impl: SelectorImpl<'i>>(NthSelectorData, Box<[Selector<'i, Impl>]>); |
1348 | | |
1349 | | #[cfg(feature = "into_owned")] |
1350 | | impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for NthOfSelectorData<'i, Impl> |
1351 | | where |
1352 | | Impl: static_self::IntoOwned<'any, Owned = NewSel>, |
1353 | | NewSel: SelectorImpl<'any>, |
1354 | | Component<'i, Impl>: static_self::IntoOwned<'any, Owned = Component<'any, NewSel>>, |
1355 | | { |
1356 | | type Owned = NthOfSelectorData<'any, NewSel>; |
1357 | | |
1358 | | fn into_owned(self) -> Self::Owned { |
1359 | | NthOfSelectorData(self.0, self.1.into_owned()) |
1360 | | } |
1361 | | } |
1362 | | |
1363 | | impl<'i, Impl: SelectorImpl<'i>> NthOfSelectorData<'i, Impl> { |
1364 | | /// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S]) |
1365 | | #[inline] |
1366 | 0 | pub fn new(nth_data: NthSelectorData, selectors: Box<[Selector<'i, Impl>]>) -> Self { |
1367 | 0 | Self(nth_data, selectors) |
1368 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::NthOfSelectorData<lightningcss::selector::private::Selectors>>::new Unexecuted instantiation: <parcel_selectors::parser::NthOfSelectorData<_>>::new |
1369 | | |
1370 | | /// Returns the An+B part of the selector |
1371 | | #[inline] |
1372 | 0 | pub fn nth_data(&self) -> &NthSelectorData { |
1373 | 0 | &self.0 |
1374 | 0 | } |
1375 | | |
1376 | | /// Returns the selector list part of the selector |
1377 | | #[inline] |
1378 | 0 | pub fn selectors(&self) -> &[Selector<'i, Impl>] { |
1379 | 0 | &*self.1 |
1380 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::NthOfSelectorData<lightningcss::selector::private::Selectors>>::selectors Unexecuted instantiation: <parcel_selectors::parser::NthOfSelectorData<_>>::selectors |
1381 | | |
1382 | 0 | pub fn clone_selectors(&self) -> Box<[Selector<'i, Impl>]> { |
1383 | 0 | self.1.clone() |
1384 | 0 | } |
1385 | | } |
1386 | | |
1387 | | /// A CSS simple selector or combinator. We store both in the same enum for |
1388 | | /// optimal packing and cache performance, see [1]. |
1389 | | /// |
1390 | | /// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973 |
1391 | | #[derive(Clone, PartialEq, Eq, Hash)] |
1392 | | pub enum Component<'i, Impl: SelectorImpl<'i>> { |
1393 | | Combinator(Combinator), |
1394 | | |
1395 | | ExplicitAnyNamespace, |
1396 | | ExplicitNoNamespace, |
1397 | | DefaultNamespace(Impl::NamespaceUrl), |
1398 | | Namespace(Impl::NamespacePrefix, Impl::NamespaceUrl), |
1399 | | |
1400 | | ExplicitUniversalType, |
1401 | | LocalName(LocalName<'i, Impl>), |
1402 | | |
1403 | | ID(Impl::Identifier), |
1404 | | Class(Impl::Identifier), |
1405 | | |
1406 | | AttributeInNoNamespaceExists { |
1407 | | local_name: Impl::LocalName, |
1408 | | local_name_lower: Impl::LocalName, |
1409 | | }, |
1410 | | // Used only when local_name is already lowercase. |
1411 | | AttributeInNoNamespace { |
1412 | | local_name: Impl::LocalName, |
1413 | | operator: AttrSelectorOperator, |
1414 | | value: Impl::AttrValue, |
1415 | | case_sensitivity: ParsedCaseSensitivity, |
1416 | | never_matches: bool, |
1417 | | }, |
1418 | | // Use a Box in the less common cases with more data to keep size_of::<Component>() small. |
1419 | | AttributeOther(Box<AttrSelectorWithOptionalNamespace<'i, Impl>>), |
1420 | | |
1421 | | /// Pseudo-classes |
1422 | | Negation(Box<[Selector<'i, Impl>]>), |
1423 | | Root, |
1424 | | Empty, |
1425 | | Scope, |
1426 | | Nth(NthSelectorData), |
1427 | | NthOf(NthOfSelectorData<'i, Impl>), |
1428 | | NonTSPseudoClass(Impl::NonTSPseudoClass), |
1429 | | /// The ::slotted() pseudo-element: |
1430 | | /// |
1431 | | /// https://drafts.csswg.org/css-scoping/#slotted-pseudo |
1432 | | /// |
1433 | | /// The selector here is a compound selector, that is, no combinators. |
1434 | | /// |
1435 | | /// NOTE(emilio): This should support a list of selectors, but as of this |
1436 | | /// writing no other browser does, and that allows them to put ::slotted() |
1437 | | /// in the rule hash, so we do that too. |
1438 | | /// |
1439 | | /// See https://github.com/w3c/csswg-drafts/issues/2158 |
1440 | | Slotted(Selector<'i, Impl>), |
1441 | | /// The `::part` pseudo-element. |
1442 | | /// https://drafts.csswg.org/css-shadow-parts/#part |
1443 | | Part(Box<[Impl::Identifier]>), |
1444 | | /// The `:host` pseudo-class: |
1445 | | /// |
1446 | | /// https://drafts.csswg.org/css-scoping/#host-selector |
1447 | | /// |
1448 | | /// NOTE(emilio): This should support a list of selectors, but as of this |
1449 | | /// writing no other browser does, and that allows them to put :host() |
1450 | | /// in the rule hash, so we do that too. |
1451 | | /// |
1452 | | /// See https://github.com/w3c/csswg-drafts/issues/2158 |
1453 | | Host(Option<Selector<'i, Impl>>), |
1454 | | /// The `:where` pseudo-class. |
1455 | | /// |
1456 | | /// https://drafts.csswg.org/selectors/#zero-matches |
1457 | | /// |
1458 | | /// The inner argument is conceptually a SelectorList, but we move the |
1459 | | /// selectors to the heap to keep Component small. |
1460 | | Where(Box<[Selector<'i, Impl>]>), |
1461 | | /// The `:is` pseudo-class. |
1462 | | /// |
1463 | | /// https://drafts.csswg.org/selectors/#matches-pseudo |
1464 | | /// |
1465 | | /// Same comment as above re. the argument. |
1466 | | Is(Box<[Selector<'i, Impl>]>), |
1467 | | Any(Impl::VendorPrefix, Box<[Selector<'i, Impl>]>), |
1468 | | /// The `:has` pseudo-class. |
1469 | | /// |
1470 | | /// https://www.w3.org/TR/selectors/#relational |
1471 | | Has(Box<[Selector<'i, Impl>]>), |
1472 | | /// An implementation-dependent pseudo-element selector. |
1473 | | PseudoElement(Impl::PseudoElement), |
1474 | | /// A nesting selector: |
1475 | | /// |
1476 | | /// https://drafts.csswg.org/css-nesting-1/#nest-selector |
1477 | | /// |
1478 | | /// NOTE: This is a lightningcss addition. |
1479 | | Nesting, |
1480 | | } |
1481 | | |
1482 | | #[cfg(feature = "into_owned")] |
1483 | | impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for Component<'i, Impl> |
1484 | | where |
1485 | | Impl: static_self::IntoOwned<'any, Owned = NewSel>, |
1486 | | NewSel: SelectorImpl<'any>, |
1487 | | Impl::NamespaceUrl: static_self::IntoOwned<'any, Owned = NewSel::NamespaceUrl>, |
1488 | | Impl::NamespacePrefix: static_self::IntoOwned<'any, Owned = NewSel::NamespacePrefix>, |
1489 | | Impl::Identifier: static_self::IntoOwned<'any, Owned = NewSel::Identifier>, |
1490 | | Impl::LocalName: static_self::IntoOwned<'any, Owned = NewSel::LocalName>, |
1491 | | Impl::AttrValue: static_self::IntoOwned<'any, Owned = NewSel::AttrValue>, |
1492 | | Impl::NonTSPseudoClass: static_self::IntoOwned<'any, Owned = NewSel::NonTSPseudoClass>, |
1493 | | Impl::PseudoElement: static_self::IntoOwned<'any, Owned = NewSel::PseudoElement>, |
1494 | | Impl::VendorPrefix: static_self::IntoOwned<'any, Owned = NewSel::VendorPrefix>, |
1495 | | { |
1496 | | type Owned = Component<'any, NewSel>; |
1497 | | |
1498 | | fn into_owned(self) -> Self::Owned { |
1499 | | match self { |
1500 | | Component::Combinator(c) => Component::Combinator(c.into_owned()), |
1501 | | Component::ExplicitAnyNamespace => Component::ExplicitAnyNamespace, |
1502 | | Component::ExplicitNoNamespace => Component::ExplicitNoNamespace, |
1503 | | Component::DefaultNamespace(c) => Component::DefaultNamespace(c.into_owned()), |
1504 | | Component::Namespace(a, b) => Component::Namespace(a.into_owned(), b.into_owned()), |
1505 | | Component::ExplicitUniversalType => Component::ExplicitUniversalType, |
1506 | | Component::LocalName(c) => Component::LocalName(c.into_owned()), |
1507 | | Component::ID(c) => Component::ID(c.into_owned()), |
1508 | | Component::Class(c) => Component::Class(c.into_owned()), |
1509 | | Component::AttributeInNoNamespaceExists { |
1510 | | local_name, |
1511 | | local_name_lower, |
1512 | | } => Component::AttributeInNoNamespaceExists { |
1513 | | local_name: local_name.into_owned(), |
1514 | | local_name_lower: local_name_lower.into_owned(), |
1515 | | }, |
1516 | | Component::AttributeInNoNamespace { |
1517 | | local_name, |
1518 | | operator, |
1519 | | value, |
1520 | | case_sensitivity, |
1521 | | never_matches, |
1522 | | } => { |
1523 | | let value = value.into_owned(); |
1524 | | Component::AttributeInNoNamespace { |
1525 | | local_name: local_name.into_owned(), |
1526 | | operator, |
1527 | | value, |
1528 | | case_sensitivity, |
1529 | | never_matches, |
1530 | | } |
1531 | | } |
1532 | | Component::AttributeOther(c) => Component::AttributeOther(c.into_owned()), |
1533 | | Component::Negation(c) => Component::Negation(c.into_owned()), |
1534 | | Component::Root => Component::Root, |
1535 | | Component::Empty => Component::Empty, |
1536 | | Component::Scope => Component::Scope, |
1537 | | Component::Nth(c) => Component::Nth(c.into_owned()), |
1538 | | Component::NthOf(c) => Component::NthOf(c.into_owned()), |
1539 | | Component::NonTSPseudoClass(c) => Component::NonTSPseudoClass(c.into_owned()), |
1540 | | Component::Slotted(c) => Component::Slotted(c.into_owned()), |
1541 | | Component::Part(c) => Component::Part(c.into_owned()), |
1542 | | Component::Host(c) => Component::Host(c.into_owned()), |
1543 | | Component::Where(c) => Component::Where(c.into_owned()), |
1544 | | Component::Is(c) => Component::Is(c.into_owned()), |
1545 | | Component::Any(a, b) => Component::Any(a.into_owned(), b.into_owned()), |
1546 | | Component::Has(c) => Component::Has(c.into_owned()), |
1547 | | Component::PseudoElement(c) => Component::PseudoElement(c.into_owned()), |
1548 | | Component::Nesting => Component::Nesting, |
1549 | | } |
1550 | | } |
1551 | | } |
1552 | | |
1553 | | impl<'i, Impl: SelectorImpl<'i>> Component<'i, Impl> { |
1554 | | /// Returns true if this is a combinator. |
1555 | 2.91M | pub fn is_combinator(&self) -> bool { |
1556 | 2.91M | matches!(*self, Component::Combinator(_)) |
1557 | 2.91M | } <parcel_selectors::parser::Component<lightningcss::selector::private::Selectors>>::is_combinator Line | Count | Source | 1555 | 2.91M | pub fn is_combinator(&self) -> bool { | 1556 | 2.91M | matches!(*self, Component::Combinator(_)) | 1557 | 2.91M | } |
Unexecuted instantiation: <parcel_selectors::parser::Component<_>>::is_combinator |
1558 | | |
1559 | | /// Returns the value as a combinator if applicable, None otherwise. |
1560 | 0 | pub fn as_combinator(&self) -> Option<Combinator> { |
1561 | 0 | match *self { |
1562 | 0 | Component::Combinator(c) => Some(c), |
1563 | 0 | _ => None, |
1564 | | } |
1565 | 0 | } Unexecuted instantiation: <parcel_selectors::parser::Component<lightningcss::selector::private::Selectors>>::as_combinator Unexecuted instantiation: <parcel_selectors::parser::Component<_>>::as_combinator |
1566 | | |
1567 | | /// Whether this component is valid after a pseudo-element. Only intended |
1568 | | /// for sanity-checking. |
1569 | 0 | pub fn maybe_allowed_after_pseudo_element(&self) -> bool { |
1570 | 0 | match *self { |
1571 | 0 | Component::NonTSPseudoClass(..) => true, |
1572 | 0 | Component::Negation(ref selectors) | Component::Is(ref selectors) | Component::Where(ref selectors) => { |
1573 | 0 | selectors |
1574 | 0 | .iter() |
1575 | 0 | .all(|selector| selector.iter_raw_match_order().all(|c| c.maybe_allowed_after_pseudo_element())) |
1576 | | } |
1577 | 0 | _ => false, |
1578 | | } |
1579 | 0 | } |
1580 | | |
1581 | | /// Whether a given selector should match for stateless pseudo-elements. |
1582 | | /// |
1583 | | /// This is a bit subtle: Only selectors that return true in |
1584 | | /// `maybe_allowed_after_pseudo_element` should end up here, and |
1585 | | /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after |
1586 | | /// all). |
1587 | 0 | fn matches_for_stateless_pseudo_element(&self) -> bool { |
1588 | 0 | debug_assert!( |
1589 | 0 | self.maybe_allowed_after_pseudo_element(), |
1590 | | "Someone messed up pseudo-element parsing: {:?}", |
1591 | | *self |
1592 | | ); |
1593 | 0 | match *self { |
1594 | 0 | Component::Negation(ref selectors) => !selectors.iter().all(|selector| { |
1595 | 0 | selector |
1596 | 0 | .iter_raw_match_order() |
1597 | 0 | .all(|c| c.matches_for_stateless_pseudo_element()) |
1598 | 0 | }), |
1599 | 0 | Component::Is(ref selectors) | Component::Where(ref selectors) => selectors.iter().any(|selector| { |
1600 | 0 | selector |
1601 | 0 | .iter_raw_match_order() |
1602 | 0 | .all(|c| c.matches_for_stateless_pseudo_element()) |
1603 | 0 | }), |
1604 | 0 | _ => false, |
1605 | | } |
1606 | 0 | } |
1607 | | |
1608 | 0 | pub fn visit<V>(&self, visitor: &mut V) -> bool |
1609 | 0 | where |
1610 | 0 | V: SelectorVisitor<'i, Impl = Impl>, |
1611 | | { |
1612 | | use self::Component::*; |
1613 | 0 | if !visitor.visit_simple_selector(self) { |
1614 | 0 | return false; |
1615 | 0 | } |
1616 | | |
1617 | 0 | match *self { |
1618 | 0 | Slotted(ref selector) => { |
1619 | 0 | if !selector.visit(visitor) { |
1620 | 0 | return false; |
1621 | 0 | } |
1622 | | } |
1623 | 0 | Host(Some(ref selector)) => { |
1624 | 0 | if !selector.visit(visitor) { |
1625 | 0 | return false; |
1626 | 0 | } |
1627 | | } |
1628 | | AttributeInNoNamespaceExists { |
1629 | 0 | ref local_name, |
1630 | 0 | ref local_name_lower, |
1631 | | } => { |
1632 | 0 | if !visitor.visit_attribute_selector( |
1633 | 0 | &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()), |
1634 | 0 | local_name, |
1635 | 0 | local_name_lower, |
1636 | 0 | ) { |
1637 | 0 | return false; |
1638 | 0 | } |
1639 | | } |
1640 | | AttributeInNoNamespace { |
1641 | 0 | ref local_name, |
1642 | 0 | never_matches, |
1643 | | .. |
1644 | 0 | } if !never_matches => { |
1645 | 0 | if !visitor.visit_attribute_selector( |
1646 | 0 | &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()), |
1647 | 0 | local_name, |
1648 | 0 | local_name, |
1649 | 0 | ) { |
1650 | 0 | return false; |
1651 | 0 | } |
1652 | | } |
1653 | 0 | AttributeOther(ref attr_selector) if !attr_selector.never_matches => { |
1654 | | let empty_string; |
1655 | 0 | let namespace = match attr_selector.namespace() { |
1656 | 0 | Some(ns) => ns, |
1657 | | None => { |
1658 | 0 | empty_string = crate::parser::namespace_empty_string::<Impl>(); |
1659 | 0 | NamespaceConstraint::Specific(&empty_string) |
1660 | | } |
1661 | | }; |
1662 | 0 | if !visitor.visit_attribute_selector( |
1663 | 0 | &namespace, |
1664 | 0 | &attr_selector.local_name, |
1665 | 0 | &attr_selector.local_name_lower, |
1666 | 0 | ) { |
1667 | 0 | return false; |
1668 | 0 | } |
1669 | | } |
1670 | | |
1671 | 0 | NonTSPseudoClass(ref pseudo_class) => { |
1672 | 0 | if !pseudo_class.visit(visitor) { |
1673 | 0 | return false; |
1674 | 0 | } |
1675 | | } |
1676 | | |
1677 | 0 | Negation(ref list) | Is(ref list) | Where(ref list) => { |
1678 | 0 | if !visitor.visit_selector_list(&list) { |
1679 | 0 | return false; |
1680 | 0 | } |
1681 | | } |
1682 | 0 | NthOf(ref nth_of_data) => { |
1683 | 0 | if !visitor.visit_selector_list(nth_of_data.selectors()) { |
1684 | 0 | return false; |
1685 | 0 | } |
1686 | | } |
1687 | 0 | _ => {} |
1688 | | } |
1689 | | |
1690 | 0 | true |
1691 | 0 | } |
1692 | | } |
1693 | | |
1694 | | #[derive(Clone, Eq, PartialEq, Hash)] |
1695 | | pub struct LocalName<'i, Impl: SelectorImpl<'i>> { |
1696 | | pub name: Impl::LocalName, |
1697 | | pub lower_name: Impl::LocalName, |
1698 | | } |
1699 | | |
1700 | | #[cfg(feature = "into_owned")] |
1701 | | impl<'any, 'i, Impl: SelectorImpl<'i>, NewSel> static_self::IntoOwned<'any> for LocalName<'i, Impl> |
1702 | | where |
1703 | | Impl: static_self::IntoOwned<'any, Owned = NewSel>, |
1704 | | NewSel: SelectorImpl<'any>, |
1705 | | Impl::LocalName: static_self::IntoOwned<'any, Owned = NewSel::LocalName>, |
1706 | | { |
1707 | | type Owned = LocalName<'any, NewSel>; |
1708 | | |
1709 | | fn into_owned(self) -> Self::Owned { |
1710 | | LocalName { |
1711 | | name: self.name.into_owned(), |
1712 | | lower_name: self.lower_name.into_owned(), |
1713 | | } |
1714 | | } |
1715 | | } |
1716 | | |
1717 | | impl<'i, Impl: SelectorImpl<'i>> Debug for Selector<'i, Impl> { |
1718 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1719 | 0 | f.write_str("Selector(")?; |
1720 | 0 | self.to_css(f)?; |
1721 | 0 | write!(f, ", specificity = 0x{:x})", self.specificity()) |
1722 | 0 | } |
1723 | | } |
1724 | | |
1725 | | impl<'i, Impl: SelectorImpl<'i>> Debug for Component<'i, Impl> { |
1726 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1727 | 0 | self.to_css(f) |
1728 | 0 | } |
1729 | | } |
1730 | | impl<'i, Impl: SelectorImpl<'i>> Debug for AttrSelectorWithOptionalNamespace<'i, Impl> { |
1731 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1732 | 0 | self.to_css(f) |
1733 | 0 | } |
1734 | | } |
1735 | | impl<'i, Impl: SelectorImpl<'i>> Debug for LocalName<'i, Impl> { |
1736 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1737 | 0 | self.to_css(f) |
1738 | 0 | } |
1739 | | } |
1740 | | |
1741 | | #[cfg(feature = "serde")] |
1742 | | impl<'i, Impl: SelectorImpl<'i>> serde::Serialize for LocalName<'i, Impl> |
1743 | | where |
1744 | | Impl::LocalName: serde::Serialize, |
1745 | | { |
1746 | | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
1747 | | where |
1748 | | S: serde::Serializer, |
1749 | | { |
1750 | | self.name.serialize(serializer) |
1751 | | } |
1752 | | } |
1753 | | |
1754 | | #[cfg(feature = "serde")] |
1755 | | impl<'i, 'de: 'i, Impl: SelectorImpl<'i>> serde::Deserialize<'de> for LocalName<'i, Impl> |
1756 | | where |
1757 | | Impl::LocalName: serde::Deserialize<'de>, |
1758 | | { |
1759 | | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
1760 | | where |
1761 | | D: serde::Deserializer<'de>, |
1762 | | { |
1763 | | let name = Impl::LocalName::deserialize(deserializer)?; |
1764 | | let lower_name = to_ascii_lowercase(name.as_ref().to_string().into()).into(); |
1765 | | Ok(LocalName { name, lower_name }) |
1766 | | } |
1767 | | } |
1768 | | |
1769 | 0 | fn serialize_selector_list<'a, 'i: 'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result |
1770 | 0 | where |
1771 | 0 | Impl: SelectorImpl<'i>, |
1772 | 0 | I: Iterator<Item = &'a Selector<'i, Impl>>, |
1773 | 0 | W: fmt::Write, |
1774 | | { |
1775 | 0 | let mut first = true; |
1776 | 0 | for selector in iter { |
1777 | 0 | if !first { |
1778 | 0 | dest.write_str(", ")?; |
1779 | 0 | } |
1780 | 0 | first = false; |
1781 | 0 | selector.to_css(dest)?; |
1782 | | } |
1783 | 0 | Ok(()) |
1784 | 0 | } |
1785 | | |
1786 | | impl<'i, Impl: SelectorImpl<'i>> ToCss for SelectorList<'i, Impl> { |
1787 | 0 | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
1788 | 0 | where |
1789 | 0 | W: fmt::Write, |
1790 | | { |
1791 | 0 | serialize_selector_list(self.0.iter(), dest) |
1792 | 0 | } |
1793 | | } |
1794 | | |
1795 | | impl<'i, Impl: SelectorImpl<'i>> fmt::Display for SelectorList<'i, Impl> { |
1796 | 0 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { |
1797 | 0 | Impl::to_css(self, f) |
1798 | 0 | } |
1799 | | } |
1800 | | |
1801 | | impl<'i, Impl: SelectorImpl<'i>> ToCss for Selector<'i, Impl> { |
1802 | 0 | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
1803 | 0 | where |
1804 | 0 | W: fmt::Write, |
1805 | | { |
1806 | | // Compound selectors invert the order of their contents, so we need to |
1807 | | // undo that during serialization. |
1808 | | // |
1809 | | // This two-iterator strategy involves walking over the selector twice. |
1810 | | // We could do something more clever, but selector serialization probably |
1811 | | // isn't hot enough to justify it, and the stringification likely |
1812 | | // dominates anyway. |
1813 | | // |
1814 | | // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), |
1815 | | // which we need for |split|. So we split by combinators on a match-order |
1816 | | // sequence and then reverse. |
1817 | | |
1818 | 0 | let mut combinators = self.iter_raw_match_order().rev().filter_map(|x| x.as_combinator()); |
1819 | 0 | let compound_selectors = self.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev(); |
1820 | | |
1821 | 0 | let mut combinators_exhausted = false; |
1822 | 0 | for compound in compound_selectors { |
1823 | 0 | debug_assert!(!combinators_exhausted); |
1824 | | |
1825 | | // https://drafts.csswg.org/cssom/#serializing-selectors |
1826 | 0 | if compound.is_empty() { |
1827 | 0 | continue; |
1828 | 0 | } |
1829 | | |
1830 | | // 1. If there is only one simple selector in the compound selectors |
1831 | | // which is a universal selector, append the result of |
1832 | | // serializing the universal selector to s. |
1833 | | // |
1834 | | // Check if `!compound.empty()` first--this can happen if we have |
1835 | | // something like `... > ::before`, because we store `>` and `::` |
1836 | | // both as combinators internally. |
1837 | | // |
1838 | | // If we are in this case, after we have serialized the universal |
1839 | | // selector, we skip Step 2 and continue with the algorithm. |
1840 | 0 | let (can_elide_namespace, first_non_namespace) = match compound[0] { |
1841 | 0 | Component::ExplicitAnyNamespace | Component::ExplicitNoNamespace | Component::Namespace(..) => (false, 1), |
1842 | 0 | Component::DefaultNamespace(..) => (true, 1), |
1843 | 0 | _ => (true, 0), |
1844 | | }; |
1845 | 0 | let mut perform_step_2 = true; |
1846 | 0 | let next_combinator = combinators.next(); |
1847 | 0 | if first_non_namespace == compound.len() - 1 { |
1848 | 0 | match (next_combinator, &compound[first_non_namespace]) { |
1849 | | // We have to be careful here, because if there is a |
1850 | | // pseudo element "combinator" there isn't really just |
1851 | | // the one simple selector. Technically this compound |
1852 | | // selector contains the pseudo element selector as well |
1853 | | // -- Combinator::PseudoElement, just like |
1854 | | // Combinator::SlotAssignment, don't exist in the |
1855 | | // spec. |
1856 | 0 | (Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (), |
1857 | | (_, &Component::ExplicitUniversalType) => { |
1858 | | // Iterate over everything so we serialize the namespace |
1859 | | // too. |
1860 | 0 | for simple in compound.iter() { |
1861 | 0 | simple.to_css(dest)?; |
1862 | | } |
1863 | | // Skip step 2, which is an "otherwise". |
1864 | 0 | perform_step_2 = false; |
1865 | | } |
1866 | 0 | _ => (), |
1867 | | } |
1868 | 0 | } |
1869 | | |
1870 | | // 2. Otherwise, for each simple selector in the compound selectors |
1871 | | // that is not a universal selector of which the namespace prefix |
1872 | | // maps to a namespace that is not the default namespace |
1873 | | // serialize the simple selector and append the result to s. |
1874 | | // |
1875 | | // See https://github.com/w3c/csswg-drafts/issues/1606, which is |
1876 | | // proposing to change this to match up with the behavior asserted |
1877 | | // in cssom/serialize-namespaced-type-selectors.html, which the |
1878 | | // following code tries to match. |
1879 | 0 | if perform_step_2 { |
1880 | 0 | for simple in compound.iter() { |
1881 | 0 | if let Component::ExplicitUniversalType = *simple { |
1882 | | // Can't have a namespace followed by a pseudo-element |
1883 | | // selector followed by a universal selector in the same |
1884 | | // compound selector, so we don't have to worry about the |
1885 | | // real namespace being in a different `compound`. |
1886 | 0 | if can_elide_namespace { |
1887 | 0 | continue; |
1888 | 0 | } |
1889 | 0 | } |
1890 | 0 | simple.to_css(dest)?; |
1891 | | } |
1892 | 0 | } |
1893 | | |
1894 | | // 3. If this is not the last part of the chain of the selector |
1895 | | // append a single SPACE (U+0020), followed by the combinator |
1896 | | // ">", "+", "~", ">>", "||", as appropriate, followed by another |
1897 | | // single SPACE (U+0020) if the combinator was not whitespace, to |
1898 | | // s. |
1899 | 0 | match next_combinator { |
1900 | 0 | Some(c) => c.to_css(dest)?, |
1901 | 0 | None => combinators_exhausted = true, |
1902 | | }; |
1903 | | |
1904 | | // 4. If this is the last part of the chain of the selector and |
1905 | | // there is a pseudo-element, append "::" followed by the name of |
1906 | | // the pseudo-element, to s. |
1907 | | // |
1908 | | // (we handle this above) |
1909 | | } |
1910 | | |
1911 | 0 | Ok(()) |
1912 | 0 | } |
1913 | | } |
1914 | | |
1915 | | impl ToCss for Combinator { |
1916 | 0 | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
1917 | 0 | where |
1918 | 0 | W: fmt::Write, |
1919 | | { |
1920 | 0 | match *self { |
1921 | 0 | Combinator::Child => dest.write_str(" > "), |
1922 | 0 | Combinator::Descendant => dest.write_str(" "), |
1923 | 0 | Combinator::NextSibling => dest.write_str(" + "), |
1924 | 0 | Combinator::LaterSibling => dest.write_str(" ~ "), |
1925 | 0 | Combinator::DeepDescendant => dest.write_str(" >>> "), |
1926 | 0 | Combinator::Deep => dest.write_str(" /deep/ "), |
1927 | 0 | Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()), |
1928 | | } |
1929 | 0 | } |
1930 | | } |
1931 | | |
1932 | | impl<'i, Impl: SelectorImpl<'i>> ToCss for Component<'i, Impl> { |
1933 | 0 | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
1934 | 0 | where |
1935 | 0 | W: fmt::Write, |
1936 | | { |
1937 | | use self::Component::*; |
1938 | | |
1939 | 0 | match *self { |
1940 | 0 | Combinator(ref c) => c.to_css(dest), |
1941 | 0 | Slotted(ref selector) => { |
1942 | 0 | dest.write_str("::slotted(")?; |
1943 | 0 | selector.to_css(dest)?; |
1944 | 0 | dest.write_char(')') |
1945 | | } |
1946 | 0 | Part(ref part_names) => { |
1947 | 0 | dest.write_str("::part(")?; |
1948 | 0 | for (i, name) in part_names.iter().enumerate() { |
1949 | 0 | if i != 0 { |
1950 | 0 | dest.write_char(' ')?; |
1951 | 0 | } |
1952 | 0 | name.to_css(dest)?; |
1953 | | } |
1954 | 0 | dest.write_char(')') |
1955 | | } |
1956 | 0 | PseudoElement(ref p) => p.to_css(dest), |
1957 | 0 | ID(ref s) => { |
1958 | 0 | dest.write_char('#')?; |
1959 | 0 | s.to_css(dest) |
1960 | | } |
1961 | 0 | Class(ref s) => { |
1962 | 0 | dest.write_char('.')?; |
1963 | 0 | s.to_css(dest) |
1964 | | } |
1965 | 0 | LocalName(ref s) => s.to_css(dest), |
1966 | 0 | ExplicitUniversalType => dest.write_char('*'), |
1967 | | |
1968 | 0 | DefaultNamespace(_) => Ok(()), |
1969 | 0 | ExplicitNoNamespace => dest.write_char('|'), |
1970 | 0 | ExplicitAnyNamespace => dest.write_str("*|"), |
1971 | 0 | Namespace(ref prefix, _) => { |
1972 | 0 | prefix.to_css(dest)?; |
1973 | 0 | dest.write_char('|') |
1974 | | } |
1975 | | |
1976 | 0 | AttributeInNoNamespaceExists { ref local_name, .. } => { |
1977 | 0 | dest.write_char('[')?; |
1978 | 0 | local_name.to_css(dest)?; |
1979 | 0 | dest.write_char(']') |
1980 | | } |
1981 | | AttributeInNoNamespace { |
1982 | 0 | ref local_name, |
1983 | 0 | operator, |
1984 | 0 | ref value, |
1985 | 0 | case_sensitivity, |
1986 | | .. |
1987 | | } => { |
1988 | 0 | dest.write_char('[')?; |
1989 | 0 | local_name.to_css(dest)?; |
1990 | 0 | operator.to_css(dest)?; |
1991 | 0 | value.to_css(dest)?; |
1992 | 0 | match case_sensitivity { |
1993 | | ParsedCaseSensitivity::CaseSensitive |
1994 | 0 | | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {} |
1995 | 0 | ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, |
1996 | 0 | ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, |
1997 | | } |
1998 | 0 | dest.write_char(']') |
1999 | | } |
2000 | 0 | AttributeOther(ref attr_selector) => attr_selector.to_css(dest), |
2001 | | |
2002 | | // Pseudo-classes |
2003 | 0 | Root => dest.write_str(":root"), |
2004 | 0 | Empty => dest.write_str(":empty"), |
2005 | 0 | Scope => dest.write_str(":scope"), |
2006 | 0 | Host(ref selector) => { |
2007 | 0 | dest.write_str(":host")?; |
2008 | 0 | if let Some(ref selector) = *selector { |
2009 | 0 | dest.write_char('(')?; |
2010 | 0 | selector.to_css(dest)?; |
2011 | 0 | dest.write_char(')')?; |
2012 | 0 | } |
2013 | 0 | Ok(()) |
2014 | | } |
2015 | 0 | Nth(ref nth_data) => { |
2016 | 0 | nth_data.write_start(dest, nth_data.is_function())?; |
2017 | 0 | if nth_data.is_function() { |
2018 | 0 | nth_data.write_affine(dest)?; |
2019 | 0 | dest.write_char(')')?; |
2020 | 0 | } |
2021 | 0 | Ok(()) |
2022 | | } |
2023 | 0 | NthOf(ref nth_of_data) => { |
2024 | 0 | let nth_data = nth_of_data.nth_data(); |
2025 | 0 | nth_data.write_start(dest, true)?; |
2026 | 0 | debug_assert!( |
2027 | 0 | nth_data.is_function, |
2028 | | "A selector must be a function to hold An+B notation" |
2029 | | ); |
2030 | 0 | nth_data.write_affine(dest)?; |
2031 | 0 | debug_assert!( |
2032 | 0 | matches!(nth_data.ty, NthType::Child | NthType::LastChild), |
2033 | | "Only :nth-child or :nth-last-child can be of a selector list" |
2034 | | ); |
2035 | 0 | debug_assert!( |
2036 | 0 | !nth_of_data.selectors().is_empty(), |
2037 | | "The selector list should not be empty" |
2038 | | ); |
2039 | 0 | dest.write_str(" of ")?; |
2040 | 0 | serialize_selector_list(nth_of_data.selectors().iter(), dest)?; |
2041 | 0 | dest.write_char(')') |
2042 | | } |
2043 | 0 | Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) | Any(_, ref list) => { |
2044 | 0 | match *self { |
2045 | 0 | Where(..) => dest.write_str(":where(")?, |
2046 | 0 | Is(..) => dest.write_str(":is(")?, |
2047 | 0 | Negation(..) => dest.write_str(":not(")?, |
2048 | 0 | Has(..) => dest.write_str(":has(")?, |
2049 | 0 | Any(ref prefix, _) => { |
2050 | 0 | dest.write_char(':')?; |
2051 | 0 | prefix.to_css(dest)?; |
2052 | 0 | dest.write_str("any(")?; |
2053 | | } |
2054 | 0 | _ => unreachable!(), |
2055 | | } |
2056 | 0 | serialize_selector_list(list.iter(), dest)?; |
2057 | 0 | dest.write_str(")") |
2058 | | } |
2059 | 0 | NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), |
2060 | 0 | Nesting => dest.write_char('&'), |
2061 | | } |
2062 | 0 | } |
2063 | | } |
2064 | | |
2065 | | impl<'i, Impl: SelectorImpl<'i>> ToCss for AttrSelectorWithOptionalNamespace<'i, Impl> { |
2066 | 0 | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
2067 | 0 | where |
2068 | 0 | W: fmt::Write, |
2069 | | { |
2070 | 0 | dest.write_char('[')?; |
2071 | 0 | match self.namespace { |
2072 | 0 | Some(NamespaceConstraint::Specific((ref prefix, _))) => { |
2073 | 0 | prefix.to_css(dest)?; |
2074 | 0 | dest.write_char('|')? |
2075 | | } |
2076 | 0 | Some(NamespaceConstraint::Any) => dest.write_str("*|")?, |
2077 | 0 | None => {} |
2078 | | } |
2079 | 0 | self.local_name.to_css(dest)?; |
2080 | 0 | match self.operation { |
2081 | 0 | ParsedAttrSelectorOperation::Exists => {} |
2082 | | ParsedAttrSelectorOperation::WithValue { |
2083 | 0 | operator, |
2084 | 0 | case_sensitivity, |
2085 | 0 | ref expected_value, |
2086 | | } => { |
2087 | 0 | operator.to_css(dest)?; |
2088 | 0 | expected_value.to_css(dest)?; |
2089 | 0 | match case_sensitivity { |
2090 | | ParsedCaseSensitivity::CaseSensitive |
2091 | 0 | | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {} |
2092 | 0 | ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, |
2093 | 0 | ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, |
2094 | | } |
2095 | | } |
2096 | | } |
2097 | 0 | dest.write_char(']') |
2098 | 0 | } |
2099 | | } |
2100 | | |
2101 | | impl<'i, Impl: SelectorImpl<'i>> ToCss for LocalName<'i, Impl> { |
2102 | 0 | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
2103 | 0 | where |
2104 | 0 | W: fmt::Write, |
2105 | | { |
2106 | 0 | self.name.to_css(dest) |
2107 | 0 | } |
2108 | | } |
2109 | | |
2110 | | /// Build up a Selector. |
2111 | | /// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; |
2112 | | /// |
2113 | | /// `Err` means invalid selector. |
2114 | 1.90M | fn parse_selector<'i, 't, P, Impl>( |
2115 | 1.90M | parser: &P, |
2116 | 1.90M | input: &mut CssParser<'i, 't>, |
2117 | 1.90M | state: &mut SelectorParsingState, |
2118 | 1.90M | nesting_requirement: NestingRequirement, |
2119 | 1.90M | ) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>> |
2120 | 1.90M | where |
2121 | 1.90M | P: Parser<'i, Impl = Impl>, |
2122 | 1.90M | Impl: SelectorImpl<'i>, |
2123 | | { |
2124 | 1.90M | if nesting_requirement == NestingRequirement::Prefixed { |
2125 | 0 | let state = input.state(); |
2126 | 0 | if !input.expect_delim('&').is_ok() { |
2127 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingPrefix)); |
2128 | 0 | } |
2129 | 0 | input.reset(&state); |
2130 | 1.90M | } |
2131 | | |
2132 | 1.90M | let mut builder = SelectorBuilder::default(); |
2133 | | |
2134 | | 'outer_loop: loop { |
2135 | | // Parse a sequence of simple selectors. |
2136 | 2.48M | let empty = parse_compound_selector(parser, state, input, &mut builder)?; |
2137 | 2.31M | if empty { |
2138 | 165k | return Err(input.new_custom_error(if builder.has_combinators() { |
2139 | 40.6k | SelectorParseErrorKind::DanglingCombinator |
2140 | | } else { |
2141 | 124k | SelectorParseErrorKind::EmptySelector |
2142 | | })); |
2143 | 2.14M | } |
2144 | | |
2145 | 2.14M | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { |
2146 | | // Input should be exhausted here. |
2147 | 96 | let source_location = input.current_source_location(); |
2148 | 96 | if let Ok(next) = input.next() { |
2149 | 48 | let next = next.clone(); |
2150 | 48 | return Err( |
2151 | 48 | source_location.new_custom_error(SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(next)), |
2152 | 48 | ); |
2153 | 48 | } |
2154 | 48 | break; |
2155 | 2.14M | } |
2156 | | |
2157 | | // Parse a combinator. |
2158 | | let combinator; |
2159 | 2.14M | let mut any_whitespace = false; |
2160 | | loop { |
2161 | 3.01M | let before_this_token = input.state(); |
2162 | 3.01M | match input.next_including_whitespace() { |
2163 | 1.35M | Err(_e) => break 'outer_loop, |
2164 | 866k | Ok(&Token::WhiteSpace(_)) => any_whitespace = true, |
2165 | | Ok(&Token::Delim('>')) => { |
2166 | 22.0k | if parser.deep_combinator_enabled() |
2167 | 0 | && input |
2168 | 0 | .try_parse(|input| { |
2169 | 0 | input.expect_delim('>')?; |
2170 | 0 | input.expect_delim('>') |
2171 | 0 | }) Unexecuted instantiation: parcel_selectors::parser::parse_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#0}Unexecuted instantiation: parcel_selectors::parser::parse_selector::<_, _>::{closure#0} |
2172 | 0 | .is_ok() |
2173 | 0 | { |
2174 | 0 | combinator = Combinator::DeepDescendant; |
2175 | 22.0k | } else { |
2176 | 22.0k | combinator = Combinator::Child; |
2177 | 22.0k | } |
2178 | 22.0k | break; |
2179 | | } |
2180 | | Ok(&Token::Delim('+')) => { |
2181 | 9.24k | combinator = Combinator::NextSibling; |
2182 | 9.24k | break; |
2183 | | } |
2184 | | Ok(&Token::Delim('~')) => { |
2185 | 30.1k | combinator = Combinator::LaterSibling; |
2186 | 30.1k | break; |
2187 | | } |
2188 | 7.46k | Ok(&Token::Delim('/')) if parser.deep_combinator_enabled() => { |
2189 | 0 | if input |
2190 | 0 | .try_parse(|input| { |
2191 | 0 | input.expect_ident_matching("deep")?; |
2192 | 0 | input.expect_delim('/') |
2193 | 0 | }) Unexecuted instantiation: parcel_selectors::parser::parse_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#1}Unexecuted instantiation: parcel_selectors::parser::parse_selector::<_, _>::{closure#1} |
2194 | 0 | .is_ok() |
2195 | | { |
2196 | 0 | combinator = Combinator::Deep; |
2197 | 0 | break; |
2198 | | } else { |
2199 | 0 | break 'outer_loop; |
2200 | | } |
2201 | | } |
2202 | | Ok(_) => { |
2203 | 729k | input.reset(&before_this_token); |
2204 | 729k | if any_whitespace { |
2205 | 520k | combinator = Combinator::Descendant; |
2206 | 520k | break; |
2207 | | } else { |
2208 | 208k | break 'outer_loop; |
2209 | | } |
2210 | | } |
2211 | | } |
2212 | | } |
2213 | | |
2214 | 581k | if !state.allows_combinators() { |
2215 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
2216 | 581k | } |
2217 | | |
2218 | 581k | builder.push_combinator(combinator); |
2219 | | } |
2220 | | |
2221 | 1.56M | if !state.contains(SelectorParsingState::AFTER_NESTING) { |
2222 | 1.53M | match nesting_requirement { |
2223 | 1.33M | NestingRequirement::Implicit => { |
2224 | 1.33M | builder.add_nesting_prefix(); |
2225 | 1.33M | } |
2226 | | NestingRequirement::Contained | NestingRequirement::Prefixed => { |
2227 | 72 | return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingSelector)); |
2228 | | } |
2229 | 198k | _ => {} |
2230 | | } |
2231 | 35.5k | } |
2232 | | |
2233 | 1.56M | let has_pseudo_element = state |
2234 | 1.56M | .intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT | SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT); |
2235 | 1.56M | let slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED); |
2236 | 1.56M | let part = state.intersects(SelectorParsingState::AFTER_PART); |
2237 | 1.56M | let (spec, components) = builder.build(has_pseudo_element, slotted, part); |
2238 | 1.56M | Ok(Selector(spec, components)) |
2239 | 1.90M | } parcel_selectors::parser::parse_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2114 | 1.90M | fn parse_selector<'i, 't, P, Impl>( | 2115 | 1.90M | parser: &P, | 2116 | 1.90M | input: &mut CssParser<'i, 't>, | 2117 | 1.90M | state: &mut SelectorParsingState, | 2118 | 1.90M | nesting_requirement: NestingRequirement, | 2119 | 1.90M | ) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>> | 2120 | 1.90M | where | 2121 | 1.90M | P: Parser<'i, Impl = Impl>, | 2122 | 1.90M | Impl: SelectorImpl<'i>, | 2123 | | { | 2124 | 1.90M | if nesting_requirement == NestingRequirement::Prefixed { | 2125 | 0 | let state = input.state(); | 2126 | 0 | if !input.expect_delim('&').is_ok() { | 2127 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingPrefix)); | 2128 | 0 | } | 2129 | 0 | input.reset(&state); | 2130 | 1.90M | } | 2131 | | | 2132 | 1.90M | let mut builder = SelectorBuilder::default(); | 2133 | | | 2134 | | 'outer_loop: loop { | 2135 | | // Parse a sequence of simple selectors. | 2136 | 2.48M | let empty = parse_compound_selector(parser, state, input, &mut builder)?; | 2137 | 2.31M | if empty { | 2138 | 165k | return Err(input.new_custom_error(if builder.has_combinators() { | 2139 | 40.6k | SelectorParseErrorKind::DanglingCombinator | 2140 | | } else { | 2141 | 124k | SelectorParseErrorKind::EmptySelector | 2142 | | })); | 2143 | 2.14M | } | 2144 | | | 2145 | 2.14M | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { | 2146 | | // Input should be exhausted here. | 2147 | 96 | let source_location = input.current_source_location(); | 2148 | 96 | if let Ok(next) = input.next() { | 2149 | 48 | let next = next.clone(); | 2150 | 48 | return Err( | 2151 | 48 | source_location.new_custom_error(SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(next)), | 2152 | 48 | ); | 2153 | 48 | } | 2154 | 48 | break; | 2155 | 2.14M | } | 2156 | | | 2157 | | // Parse a combinator. | 2158 | | let combinator; | 2159 | 2.14M | let mut any_whitespace = false; | 2160 | | loop { | 2161 | 3.01M | let before_this_token = input.state(); | 2162 | 3.01M | match input.next_including_whitespace() { | 2163 | 1.35M | Err(_e) => break 'outer_loop, | 2164 | 866k | Ok(&Token::WhiteSpace(_)) => any_whitespace = true, | 2165 | | Ok(&Token::Delim('>')) => { | 2166 | 22.0k | if parser.deep_combinator_enabled() | 2167 | 0 | && input | 2168 | 0 | .try_parse(|input| { | 2169 | | input.expect_delim('>')?; | 2170 | | input.expect_delim('>') | 2171 | | }) | 2172 | 0 | .is_ok() | 2173 | 0 | { | 2174 | 0 | combinator = Combinator::DeepDescendant; | 2175 | 22.0k | } else { | 2176 | 22.0k | combinator = Combinator::Child; | 2177 | 22.0k | } | 2178 | 22.0k | break; | 2179 | | } | 2180 | | Ok(&Token::Delim('+')) => { | 2181 | 9.24k | combinator = Combinator::NextSibling; | 2182 | 9.24k | break; | 2183 | | } | 2184 | | Ok(&Token::Delim('~')) => { | 2185 | 30.1k | combinator = Combinator::LaterSibling; | 2186 | 30.1k | break; | 2187 | | } | 2188 | 7.46k | Ok(&Token::Delim('/')) if parser.deep_combinator_enabled() => { | 2189 | 0 | if input | 2190 | 0 | .try_parse(|input| { | 2191 | | input.expect_ident_matching("deep")?; | 2192 | | input.expect_delim('/') | 2193 | | }) | 2194 | 0 | .is_ok() | 2195 | | { | 2196 | 0 | combinator = Combinator::Deep; | 2197 | 0 | break; | 2198 | | } else { | 2199 | 0 | break 'outer_loop; | 2200 | | } | 2201 | | } | 2202 | | Ok(_) => { | 2203 | 729k | input.reset(&before_this_token); | 2204 | 729k | if any_whitespace { | 2205 | 520k | combinator = Combinator::Descendant; | 2206 | 520k | break; | 2207 | | } else { | 2208 | 208k | break 'outer_loop; | 2209 | | } | 2210 | | } | 2211 | | } | 2212 | | } | 2213 | | | 2214 | 581k | if !state.allows_combinators() { | 2215 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 2216 | 581k | } | 2217 | | | 2218 | 581k | builder.push_combinator(combinator); | 2219 | | } | 2220 | | | 2221 | 1.56M | if !state.contains(SelectorParsingState::AFTER_NESTING) { | 2222 | 1.53M | match nesting_requirement { | 2223 | 1.33M | NestingRequirement::Implicit => { | 2224 | 1.33M | builder.add_nesting_prefix(); | 2225 | 1.33M | } | 2226 | | NestingRequirement::Contained | NestingRequirement::Prefixed => { | 2227 | 72 | return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingSelector)); | 2228 | | } | 2229 | 198k | _ => {} | 2230 | | } | 2231 | 35.5k | } | 2232 | | | 2233 | 1.56M | let has_pseudo_element = state | 2234 | 1.56M | .intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT | SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT); | 2235 | 1.56M | let slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED); | 2236 | 1.56M | let part = state.intersects(SelectorParsingState::AFTER_PART); | 2237 | 1.56M | let (spec, components) = builder.build(has_pseudo_element, slotted, part); | 2238 | 1.56M | Ok(Selector(spec, components)) | 2239 | 1.90M | } |
Unexecuted instantiation: parcel_selectors::parser::parse_selector::<_, _> |
2240 | | |
2241 | | impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { |
2242 | | /// Parse a selector, without any pseudo-element. |
2243 | | #[inline] |
2244 | 315 | pub fn parse<'t, P>(parser: &P, input: &mut CssParser<'i, 't>) -> Result<Self, ParseError<'i, P::Error>> |
2245 | 315 | where |
2246 | 315 | P: Parser<'i, Impl = Impl>, |
2247 | | { |
2248 | 315 | parse_selector( |
2249 | 315 | parser, |
2250 | 315 | input, |
2251 | 315 | &mut SelectorParsingState::empty(), |
2252 | 315 | NestingRequirement::None, |
2253 | | ) |
2254 | 315 | } <parcel_selectors::parser::Selector<lightningcss::selector::private::Selectors>>::parse::<lightningcss::selector::SelectorParser> Line | Count | Source | 2244 | 315 | pub fn parse<'t, P>(parser: &P, input: &mut CssParser<'i, 't>) -> Result<Self, ParseError<'i, P::Error>> | 2245 | 315 | where | 2246 | 315 | P: Parser<'i, Impl = Impl>, | 2247 | | { | 2248 | 315 | parse_selector( | 2249 | 315 | parser, | 2250 | 315 | input, | 2251 | 315 | &mut SelectorParsingState::empty(), | 2252 | 315 | NestingRequirement::None, | 2253 | | ) | 2254 | 315 | } |
Unexecuted instantiation: <parcel_selectors::parser::Selector<_>>::parse::<_> |
2255 | | } |
2256 | | |
2257 | 1.56M | fn parse_relative_selector<'i, 't, P, Impl>( |
2258 | 1.56M | parser: &P, |
2259 | 1.56M | input: &mut CssParser<'i, 't>, |
2260 | 1.56M | state: &mut SelectorParsingState, |
2261 | 1.56M | mut nesting_requirement: NestingRequirement, |
2262 | 1.56M | ) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>> |
2263 | 1.56M | where |
2264 | 1.56M | P: Parser<'i, Impl = Impl>, |
2265 | 1.56M | Impl: SelectorImpl<'i>, |
2266 | | { |
2267 | | // https://www.w3.org/TR/selectors-4/#parse-relative-selector |
2268 | 1.56M | let s = input.state(); |
2269 | 1.56M | let combinator = match input.next()? { |
2270 | 779 | Token::Delim('>') => Some(Combinator::Child), |
2271 | 1.20k | Token::Delim('+') => Some(Combinator::NextSibling), |
2272 | 368 | Token::Delim('~') => Some(Combinator::LaterSibling), |
2273 | | _ => { |
2274 | 1.53M | input.reset(&s); |
2275 | 1.53M | None |
2276 | | } |
2277 | | }; |
2278 | | |
2279 | 1.54M | let scope = if nesting_requirement == NestingRequirement::Implicit { |
2280 | 1.54M | Component::Nesting |
2281 | | } else { |
2282 | 124 | Component::Scope |
2283 | | }; |
2284 | | |
2285 | 1.54M | if combinator.is_some() { |
2286 | 2.34k | nesting_requirement = NestingRequirement::None; |
2287 | 1.53M | } |
2288 | | |
2289 | 1.54M | let mut selector = parse_selector(parser, input, state, nesting_requirement)?; |
2290 | 1.35M | if let Some(combinator) = combinator { |
2291 | 1.55k | // https://www.w3.org/TR/selectors/#absolutizing |
2292 | 1.55k | selector.1.push(Component::Combinator(combinator)); |
2293 | 1.55k | selector.1.push(scope); |
2294 | 1.35M | } |
2295 | | |
2296 | 1.35M | Ok(selector) |
2297 | 1.56M | } parcel_selectors::parser::parse_relative_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2257 | 1.56M | fn parse_relative_selector<'i, 't, P, Impl>( | 2258 | 1.56M | parser: &P, | 2259 | 1.56M | input: &mut CssParser<'i, 't>, | 2260 | 1.56M | state: &mut SelectorParsingState, | 2261 | 1.56M | mut nesting_requirement: NestingRequirement, | 2262 | 1.56M | ) -> Result<Selector<'i, Impl>, ParseError<'i, P::Error>> | 2263 | 1.56M | where | 2264 | 1.56M | P: Parser<'i, Impl = Impl>, | 2265 | 1.56M | Impl: SelectorImpl<'i>, | 2266 | | { | 2267 | | // https://www.w3.org/TR/selectors-4/#parse-relative-selector | 2268 | 1.56M | let s = input.state(); | 2269 | 1.56M | let combinator = match input.next()? { | 2270 | 779 | Token::Delim('>') => Some(Combinator::Child), | 2271 | 1.20k | Token::Delim('+') => Some(Combinator::NextSibling), | 2272 | 368 | Token::Delim('~') => Some(Combinator::LaterSibling), | 2273 | | _ => { | 2274 | 1.53M | input.reset(&s); | 2275 | 1.53M | None | 2276 | | } | 2277 | | }; | 2278 | | | 2279 | 1.54M | let scope = if nesting_requirement == NestingRequirement::Implicit { | 2280 | 1.54M | Component::Nesting | 2281 | | } else { | 2282 | 124 | Component::Scope | 2283 | | }; | 2284 | | | 2285 | 1.54M | if combinator.is_some() { | 2286 | 2.34k | nesting_requirement = NestingRequirement::None; | 2287 | 1.53M | } | 2288 | | | 2289 | 1.54M | let mut selector = parse_selector(parser, input, state, nesting_requirement)?; | 2290 | 1.35M | if let Some(combinator) = combinator { | 2291 | 1.55k | // https://www.w3.org/TR/selectors/#absolutizing | 2292 | 1.55k | selector.1.push(Component::Combinator(combinator)); | 2293 | 1.55k | selector.1.push(scope); | 2294 | 1.35M | } | 2295 | | | 2296 | 1.35M | Ok(selector) | 2297 | 1.56M | } |
Unexecuted instantiation: parcel_selectors::parser::parse_relative_selector::<_, _> |
2298 | | |
2299 | | /// * `Err(())`: Invalid selector, abort |
2300 | | /// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed. |
2301 | | /// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`) |
2302 | 2.48M | fn parse_type_selector<'i, 't, P, Impl, S>( |
2303 | 2.48M | parser: &P, |
2304 | 2.48M | input: &mut CssParser<'i, 't>, |
2305 | 2.48M | state: SelectorParsingState, |
2306 | 2.48M | sink: &mut S, |
2307 | 2.48M | ) -> Result<bool, ParseError<'i, P::Error>> |
2308 | 2.48M | where |
2309 | 2.48M | P: Parser<'i, Impl = Impl>, |
2310 | 2.48M | Impl: SelectorImpl<'i>, |
2311 | 2.48M | S: Push<Component<'i, Impl>>, |
2312 | | { |
2313 | 2.48M | match parse_qualified_name(parser, input, /* in_attr_selector = */ false) { |
2314 | | Err(ParseError { |
2315 | | kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), |
2316 | | .. |
2317 | | }) |
2318 | 446k | | Ok(OptionalQName::None(_)) => Ok(false), |
2319 | 2.02M | Ok(OptionalQName::Some(namespace, local_name)) => { |
2320 | 2.02M | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { |
2321 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
2322 | 2.02M | } |
2323 | 2.02M | match namespace { |
2324 | 1.99M | QNamePrefix::ImplicitAnyNamespace => {} |
2325 | 0 | QNamePrefix::ImplicitDefaultNamespace(url) => sink.push(Component::DefaultNamespace(url)), |
2326 | 7.57k | QNamePrefix::ExplicitNamespace(prefix, url) => sink.push(match parser.default_namespace() { |
2327 | 0 | Some(ref default_url) if url == *default_url => Component::DefaultNamespace(url), |
2328 | 7.57k | _ => Component::Namespace(prefix, url), |
2329 | | }), |
2330 | 21.2k | QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace), |
2331 | | QNamePrefix::ExplicitAnyNamespace => { |
2332 | | // Element type selectors that have no namespace |
2333 | | // component (no namespace separator) represent elements |
2334 | | // without regard to the element's namespace (equivalent |
2335 | | // to "*|") unless a default namespace has been declared |
2336 | | // for namespaced selectors (e.g. in CSS, in the style |
2337 | | // sheet). If a default namespace has been declared, |
2338 | | // such selectors will represent only elements in the |
2339 | | // default namespace. |
2340 | | // -- Selectors § 6.1.1 |
2341 | | // So we'll have this act the same as the |
2342 | | // QNamePrefix::ImplicitAnyNamespace case. |
2343 | | // For lightning css this logic was removed, should be handled when matching. |
2344 | 44 | sink.push(Component::ExplicitAnyNamespace) |
2345 | | } |
2346 | | QNamePrefix::ImplicitNoNamespace => { |
2347 | 0 | unreachable!() // Not returned with in_attr_selector = false |
2348 | | } |
2349 | | } |
2350 | 2.02M | match local_name { |
2351 | 2.01M | Some(name) => sink.push(Component::LocalName(LocalName { |
2352 | 2.01M | lower_name: to_ascii_lowercase(name.clone()).into(), |
2353 | 2.01M | name: name.into(), |
2354 | 2.01M | })), |
2355 | 10.3k | None => sink.push(Component::ExplicitUniversalType), |
2356 | | } |
2357 | 2.02M | Ok(true) |
2358 | | } |
2359 | 9.58k | Err(e) => Err(e), |
2360 | | } |
2361 | 2.48M | } parcel_selectors::parser::parse_type_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors, parcel_selectors::builder::SelectorBuilder<lightningcss::selector::private::Selectors>> Line | Count | Source | 2302 | 2.48M | fn parse_type_selector<'i, 't, P, Impl, S>( | 2303 | 2.48M | parser: &P, | 2304 | 2.48M | input: &mut CssParser<'i, 't>, | 2305 | 2.48M | state: SelectorParsingState, | 2306 | 2.48M | sink: &mut S, | 2307 | 2.48M | ) -> Result<bool, ParseError<'i, P::Error>> | 2308 | 2.48M | where | 2309 | 2.48M | P: Parser<'i, Impl = Impl>, | 2310 | 2.48M | Impl: SelectorImpl<'i>, | 2311 | 2.48M | S: Push<Component<'i, Impl>>, | 2312 | | { | 2313 | 2.48M | match parse_qualified_name(parser, input, /* in_attr_selector = */ false) { | 2314 | | Err(ParseError { | 2315 | | kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), | 2316 | | .. | 2317 | | }) | 2318 | 446k | | Ok(OptionalQName::None(_)) => Ok(false), | 2319 | 2.02M | Ok(OptionalQName::Some(namespace, local_name)) => { | 2320 | 2.02M | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { | 2321 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 2322 | 2.02M | } | 2323 | 2.02M | match namespace { | 2324 | 1.99M | QNamePrefix::ImplicitAnyNamespace => {} | 2325 | 0 | QNamePrefix::ImplicitDefaultNamespace(url) => sink.push(Component::DefaultNamespace(url)), | 2326 | 7.57k | QNamePrefix::ExplicitNamespace(prefix, url) => sink.push(match parser.default_namespace() { | 2327 | 0 | Some(ref default_url) if url == *default_url => Component::DefaultNamespace(url), | 2328 | 7.57k | _ => Component::Namespace(prefix, url), | 2329 | | }), | 2330 | 21.2k | QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace), | 2331 | | QNamePrefix::ExplicitAnyNamespace => { | 2332 | | // Element type selectors that have no namespace | 2333 | | // component (no namespace separator) represent elements | 2334 | | // without regard to the element's namespace (equivalent | 2335 | | // to "*|") unless a default namespace has been declared | 2336 | | // for namespaced selectors (e.g. in CSS, in the style | 2337 | | // sheet). If a default namespace has been declared, | 2338 | | // such selectors will represent only elements in the | 2339 | | // default namespace. | 2340 | | // -- Selectors § 6.1.1 | 2341 | | // So we'll have this act the same as the | 2342 | | // QNamePrefix::ImplicitAnyNamespace case. | 2343 | | // For lightning css this logic was removed, should be handled when matching. | 2344 | 44 | sink.push(Component::ExplicitAnyNamespace) | 2345 | | } | 2346 | | QNamePrefix::ImplicitNoNamespace => { | 2347 | 0 | unreachable!() // Not returned with in_attr_selector = false | 2348 | | } | 2349 | | } | 2350 | 2.02M | match local_name { | 2351 | 2.01M | Some(name) => sink.push(Component::LocalName(LocalName { | 2352 | 2.01M | lower_name: to_ascii_lowercase(name.clone()).into(), | 2353 | 2.01M | name: name.into(), | 2354 | 2.01M | })), | 2355 | 10.3k | None => sink.push(Component::ExplicitUniversalType), | 2356 | | } | 2357 | 2.02M | Ok(true) | 2358 | | } | 2359 | 9.58k | Err(e) => Err(e), | 2360 | | } | 2361 | 2.48M | } |
Unexecuted instantiation: parcel_selectors::parser::parse_type_selector::<_, _, _> |
2362 | | |
2363 | | #[derive(Debug)] |
2364 | | enum SimpleSelectorParseResult<'i, Impl: SelectorImpl<'i>> { |
2365 | | SimpleSelector(Component<'i, Impl>), |
2366 | | PseudoElement(Impl::PseudoElement), |
2367 | | SlottedPseudo(Selector<'i, Impl>), |
2368 | | PartPseudo(Box<[Impl::Identifier]>), |
2369 | | } |
2370 | | |
2371 | | #[derive(Debug)] |
2372 | | enum QNamePrefix<'i, Impl: SelectorImpl<'i>> { |
2373 | | ImplicitNoNamespace, // `foo` in attr selectors |
2374 | | ImplicitAnyNamespace, // `foo` in type selectors, without a default ns |
2375 | | ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns |
2376 | | ExplicitNoNamespace, // `|foo` |
2377 | | ExplicitAnyNamespace, // `*|foo` |
2378 | | ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo` |
2379 | | } |
2380 | | |
2381 | | enum OptionalQName<'i, Impl: SelectorImpl<'i>> { |
2382 | | Some(QNamePrefix<'i, Impl>, Option<CowRcStr<'i>>), |
2383 | | None(Token<'i>), |
2384 | | } |
2385 | | |
2386 | | /// * `Err(())`: Invalid selector, abort |
2387 | | /// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed, |
2388 | | /// but the token is still returned. |
2389 | | /// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector |
2390 | 2.48M | fn parse_qualified_name<'i, 't, P, Impl>( |
2391 | 2.48M | parser: &P, |
2392 | 2.48M | input: &mut CssParser<'i, 't>, |
2393 | 2.48M | in_attr_selector: bool, |
2394 | 2.48M | ) -> Result<OptionalQName<'i, Impl>, ParseError<'i, P::Error>> |
2395 | 2.48M | where |
2396 | 2.48M | P: Parser<'i, Impl = Impl>, |
2397 | 2.48M | Impl: SelectorImpl<'i>, |
2398 | | { |
2399 | 2.48M | let default_namespace = |local_name| { |
2400 | 1.99M | let namespace = match parser.default_namespace() { |
2401 | 0 | Some(url) => QNamePrefix::ImplicitDefaultNamespace(url), |
2402 | 1.99M | None => QNamePrefix::ImplicitAnyNamespace, |
2403 | | }; |
2404 | 1.99M | Ok(OptionalQName::Some(namespace, local_name)) |
2405 | 1.99M | }; parcel_selectors::parser::parse_qualified_name::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#0}Line | Count | Source | 2399 | 1.99M | let default_namespace = |local_name| { | 2400 | 1.99M | let namespace = match parser.default_namespace() { | 2401 | 0 | Some(url) => QNamePrefix::ImplicitDefaultNamespace(url), | 2402 | 1.99M | None => QNamePrefix::ImplicitAnyNamespace, | 2403 | | }; | 2404 | 1.99M | Ok(OptionalQName::Some(namespace, local_name)) | 2405 | 1.99M | }; |
Unexecuted instantiation: parcel_selectors::parser::parse_qualified_name::<_, _>::{closure#0} |
2406 | | |
2407 | 2.48M | let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { |
2408 | 57.0k | let location = input.current_source_location(); |
2409 | 57.0k | match input.next_including_whitespace() { |
2410 | 12 | Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)), |
2411 | 28.8k | Ok(&Token::Ident(ref local_name)) => Ok(OptionalQName::Some(namespace, Some(local_name.clone()))), |
2412 | 3 | Ok(t) if in_attr_selector => { |
2413 | 3 | let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone()); |
2414 | 3 | Err(location.new_custom_error(e)) |
2415 | | } |
2416 | 9.58k | Ok(t) => Err(location.new_custom_error(SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()))), |
2417 | 18.5k | Err(e) => Err(e.into()), |
2418 | | } |
2419 | 57.0k | }; parcel_selectors::parser::parse_qualified_name::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#1}Line | Count | Source | 2407 | 57.0k | let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { | 2408 | 57.0k | let location = input.current_source_location(); | 2409 | 57.0k | match input.next_including_whitespace() { | 2410 | 12 | Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)), | 2411 | 28.8k | Ok(&Token::Ident(ref local_name)) => Ok(OptionalQName::Some(namespace, Some(local_name.clone()))), | 2412 | 3 | Ok(t) if in_attr_selector => { | 2413 | 3 | let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone()); | 2414 | 3 | Err(location.new_custom_error(e)) | 2415 | | } | 2416 | 9.58k | Ok(t) => Err(location.new_custom_error(SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()))), | 2417 | 18.5k | Err(e) => Err(e.into()), | 2418 | | } | 2419 | 57.0k | }; |
Unexecuted instantiation: parcel_selectors::parser::parse_qualified_name::<_, _>::{closure#1} |
2420 | | |
2421 | 2.48M | let start = input.state(); |
2422 | | // FIXME: remove clone() when lifetimes are non-lexical |
2423 | 2.48M | match input.next_including_whitespace().map(|t| t.clone()) {parcel_selectors::parser::parse_qualified_name::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#2}Line | Count | Source | 2423 | 2.47M | match input.next_including_whitespace().map(|t| t.clone()) { |
Unexecuted instantiation: parcel_selectors::parser::parse_qualified_name::<_, _>::{closure#2} |
2424 | 1.99M | Ok(Token::Ident(value)) => { |
2425 | 1.99M | let after_ident = input.state(); |
2426 | 1.99M | match input.next_including_whitespace() { |
2427 | | Ok(&Token::Delim('|')) => { |
2428 | 7.99k | let prefix = value.clone().into(); |
2429 | 7.99k | let result = parser.namespace_for_prefix(&prefix); |
2430 | 7.99k | let url = result.ok_or( |
2431 | 7.99k | after_ident |
2432 | 7.99k | .source_location() |
2433 | 7.99k | .new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)), |
2434 | 0 | )?; |
2435 | 7.99k | explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url)) |
2436 | | } |
2437 | | _ => { |
2438 | 1.98M | input.reset(&after_ident); |
2439 | 1.98M | if in_attr_selector { |
2440 | 717 | Ok(OptionalQName::Some(QNamePrefix::ImplicitNoNamespace, Some(value))) |
2441 | | } else { |
2442 | 1.98M | default_namespace(Some(value)) |
2443 | | } |
2444 | | } |
2445 | | } |
2446 | | } |
2447 | | Ok(Token::Delim('*')) => { |
2448 | 10.4k | let after_star = input.state(); |
2449 | | // FIXME: remove clone() when lifetimes are non-lexical |
2450 | 10.4k | match input.next_including_whitespace().map(|t| t.clone()) {parcel_selectors::parser::parse_qualified_name::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#3}Line | Count | Source | 2450 | 2.74k | match input.next_including_whitespace().map(|t| t.clone()) { |
Unexecuted instantiation: parcel_selectors::parser::parse_qualified_name::<_, _>::{closure#3} |
2451 | 49 | Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace), |
2452 | 10.3k | result => { |
2453 | 10.3k | input.reset(&after_star); |
2454 | 10.3k | if in_attr_selector { |
2455 | 6 | match result { |
2456 | 6 | Ok(t) => Err( |
2457 | 6 | after_star |
2458 | 6 | .source_location() |
2459 | 6 | .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t)), |
2460 | 6 | ), |
2461 | 0 | Err(e) => Err(e.into()), |
2462 | | } |
2463 | | } else { |
2464 | 10.3k | default_namespace(None) |
2465 | | } |
2466 | | } |
2467 | | } |
2468 | | } |
2469 | 48.9k | Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace), |
2470 | 414k | Ok(t) => { |
2471 | 414k | input.reset(&start); |
2472 | 414k | Ok(OptionalQName::None(t)) |
2473 | | } |
2474 | 14.0k | Err(e) => { |
2475 | 14.0k | input.reset(&start); |
2476 | 14.0k | Err(e.into()) |
2477 | | } |
2478 | | } |
2479 | 2.48M | } parcel_selectors::parser::parse_qualified_name::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2390 | 2.48M | fn parse_qualified_name<'i, 't, P, Impl>( | 2391 | 2.48M | parser: &P, | 2392 | 2.48M | input: &mut CssParser<'i, 't>, | 2393 | 2.48M | in_attr_selector: bool, | 2394 | 2.48M | ) -> Result<OptionalQName<'i, Impl>, ParseError<'i, P::Error>> | 2395 | 2.48M | where | 2396 | 2.48M | P: Parser<'i, Impl = Impl>, | 2397 | 2.48M | Impl: SelectorImpl<'i>, | 2398 | | { | 2399 | 2.48M | let default_namespace = |local_name| { | 2400 | | let namespace = match parser.default_namespace() { | 2401 | | Some(url) => QNamePrefix::ImplicitDefaultNamespace(url), | 2402 | | None => QNamePrefix::ImplicitAnyNamespace, | 2403 | | }; | 2404 | | Ok(OptionalQName::Some(namespace, local_name)) | 2405 | | }; | 2406 | | | 2407 | 2.48M | let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { | 2408 | | let location = input.current_source_location(); | 2409 | | match input.next_including_whitespace() { | 2410 | | Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)), | 2411 | | Ok(&Token::Ident(ref local_name)) => Ok(OptionalQName::Some(namespace, Some(local_name.clone()))), | 2412 | | Ok(t) if in_attr_selector => { | 2413 | | let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone()); | 2414 | | Err(location.new_custom_error(e)) | 2415 | | } | 2416 | | Ok(t) => Err(location.new_custom_error(SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()))), | 2417 | | Err(e) => Err(e.into()), | 2418 | | } | 2419 | | }; | 2420 | | | 2421 | 2.48M | let start = input.state(); | 2422 | | // FIXME: remove clone() when lifetimes are non-lexical | 2423 | 2.48M | match input.next_including_whitespace().map(|t| t.clone()) { | 2424 | 1.99M | Ok(Token::Ident(value)) => { | 2425 | 1.99M | let after_ident = input.state(); | 2426 | 1.99M | match input.next_including_whitespace() { | 2427 | | Ok(&Token::Delim('|')) => { | 2428 | 7.99k | let prefix = value.clone().into(); | 2429 | 7.99k | let result = parser.namespace_for_prefix(&prefix); | 2430 | 7.99k | let url = result.ok_or( | 2431 | 7.99k | after_ident | 2432 | 7.99k | .source_location() | 2433 | 7.99k | .new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)), | 2434 | 0 | )?; | 2435 | 7.99k | explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url)) | 2436 | | } | 2437 | | _ => { | 2438 | 1.98M | input.reset(&after_ident); | 2439 | 1.98M | if in_attr_selector { | 2440 | 717 | Ok(OptionalQName::Some(QNamePrefix::ImplicitNoNamespace, Some(value))) | 2441 | | } else { | 2442 | 1.98M | default_namespace(Some(value)) | 2443 | | } | 2444 | | } | 2445 | | } | 2446 | | } | 2447 | | Ok(Token::Delim('*')) => { | 2448 | 10.4k | let after_star = input.state(); | 2449 | | // FIXME: remove clone() when lifetimes are non-lexical | 2450 | 10.4k | match input.next_including_whitespace().map(|t| t.clone()) { | 2451 | 49 | Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace), | 2452 | 10.3k | result => { | 2453 | 10.3k | input.reset(&after_star); | 2454 | 10.3k | if in_attr_selector { | 2455 | 6 | match result { | 2456 | 6 | Ok(t) => Err( | 2457 | 6 | after_star | 2458 | 6 | .source_location() | 2459 | 6 | .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t)), | 2460 | 6 | ), | 2461 | 0 | Err(e) => Err(e.into()), | 2462 | | } | 2463 | | } else { | 2464 | 10.3k | default_namespace(None) | 2465 | | } | 2466 | | } | 2467 | | } | 2468 | | } | 2469 | 48.9k | Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace), | 2470 | 414k | Ok(t) => { | 2471 | 414k | input.reset(&start); | 2472 | 414k | Ok(OptionalQName::None(t)) | 2473 | | } | 2474 | 14.0k | Err(e) => { | 2475 | 14.0k | input.reset(&start); | 2476 | 14.0k | Err(e.into()) | 2477 | | } | 2478 | | } | 2479 | 2.48M | } |
Unexecuted instantiation: parcel_selectors::parser::parse_qualified_name::<_, _> |
2480 | | |
2481 | 1.49k | fn parse_attribute_selector<'i, 't, P, Impl>( |
2482 | 1.49k | parser: &P, |
2483 | 1.49k | input: &mut CssParser<'i, 't>, |
2484 | 1.49k | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> |
2485 | 1.49k | where |
2486 | 1.49k | P: Parser<'i, Impl = Impl>, |
2487 | 1.49k | Impl: SelectorImpl<'i>, |
2488 | | { |
2489 | | let namespace; |
2490 | | let local_name; |
2491 | | |
2492 | 1.49k | input.skip_whitespace(); |
2493 | | |
2494 | 1.49k | match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? { |
2495 | 725 | OptionalQName::None(t) => { |
2496 | 725 | return Err(input.new_custom_error(SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t))); |
2497 | | } |
2498 | 0 | OptionalQName::Some(_, None) => unreachable!(), |
2499 | 733 | OptionalQName::Some(ns, Some(ln)) => { |
2500 | 733 | local_name = ln; |
2501 | 733 | namespace = match ns { |
2502 | 718 | QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None, |
2503 | 15 | QNamePrefix::ExplicitNamespace(prefix, url) => Some(NamespaceConstraint::Specific((prefix, url))), |
2504 | 0 | QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any), |
2505 | | QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => { |
2506 | 0 | unreachable!() // Not returned with in_attr_selector = true |
2507 | | } |
2508 | | } |
2509 | | } |
2510 | | } |
2511 | | |
2512 | 733 | let location = input.current_source_location(); |
2513 | 733 | let operator = match input.next() { |
2514 | | // [foo] |
2515 | | Err(_) => { |
2516 | 105 | let local_name_lower = to_ascii_lowercase(local_name.clone()).into(); |
2517 | 105 | let local_name = local_name.into(); |
2518 | 105 | if let Some(namespace) = namespace { |
2519 | 8 | return Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace { |
2520 | 8 | namespace: Some(namespace), |
2521 | 8 | local_name, |
2522 | 8 | local_name_lower, |
2523 | 8 | operation: ParsedAttrSelectorOperation::Exists, |
2524 | 8 | never_matches: false, |
2525 | 8 | }))); |
2526 | | } else { |
2527 | 97 | return Ok(Component::AttributeInNoNamespaceExists { |
2528 | 97 | local_name, |
2529 | 97 | local_name_lower, |
2530 | 97 | }); |
2531 | | } |
2532 | | } |
2533 | | |
2534 | | // [foo=bar] |
2535 | 219 | Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal, |
2536 | | // [foo~=bar] |
2537 | 36 | Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes, |
2538 | | // [foo|=bar] |
2539 | 8 | Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch, |
2540 | | // [foo^=bar] |
2541 | 1 | Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix, |
2542 | | // [foo*=bar] |
2543 | 11 | Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring, |
2544 | | // [foo$=bar] |
2545 | 3 | Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix, |
2546 | 350 | Ok(t) => { |
2547 | 350 | return Err( |
2548 | 350 | location.new_custom_error(SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone())), |
2549 | 350 | ); |
2550 | | } |
2551 | | }; |
2552 | | |
2553 | 278 | let value = match input.expect_ident_or_string() { |
2554 | 263 | Ok(t) => t.clone(), |
2555 | | Err(BasicParseError { |
2556 | 12 | kind: BasicParseErrorKind::UnexpectedToken(t), |
2557 | 12 | location, |
2558 | 12 | }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))), |
2559 | 3 | Err(e) => return Err(e.into()), |
2560 | | }; |
2561 | 263 | let never_matches = match operator { |
2562 | 214 | AttrSelectorOperator::Equal | AttrSelectorOperator::DashMatch => false, |
2563 | | |
2564 | 36 | AttrSelectorOperator::Includes => value.is_empty() || value.contains(SELECTOR_WHITESPACE), |
2565 | | |
2566 | | AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => { |
2567 | 13 | value.is_empty() |
2568 | | } |
2569 | | }; |
2570 | | |
2571 | 263 | let attribute_flags = parse_attribute_flags(input)?; |
2572 | | |
2573 | 187 | let value = value.into(); |
2574 | | let case_sensitivity; |
2575 | | // copied from to_ascii_lowercase function, so we can know whether it is already lower case. |
2576 | 187 | let (local_name_lower_cow, local_name_is_ascii_lowercase) = |
2577 | 8.98k | if let Some(first_uppercase) = local_name.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {parcel_selectors::parser::parse_attribute_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#0}Line | Count | Source | 2577 | 8.98k | if let Some(first_uppercase) = local_name.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { |
Unexecuted instantiation: parcel_selectors::parser::parse_attribute_selector::<_, _>::{closure#0} |
2578 | 135 | let mut string = local_name.to_string(); |
2579 | 135 | string[first_uppercase..].make_ascii_lowercase(); |
2580 | 135 | (string.into(), false) |
2581 | | } else { |
2582 | 52 | (local_name.clone(), true) |
2583 | | }; |
2584 | 187 | case_sensitivity = attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some()); |
2585 | 187 | let local_name_lower = local_name_lower_cow.into(); |
2586 | 187 | let local_name = local_name.into(); |
2587 | 187 | if namespace.is_some() || !local_name_is_ascii_lowercase { |
2588 | 136 | Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace { |
2589 | 136 | namespace, |
2590 | 136 | local_name, |
2591 | 136 | local_name_lower, |
2592 | 136 | never_matches, |
2593 | 136 | operation: ParsedAttrSelectorOperation::WithValue { |
2594 | 136 | operator, |
2595 | 136 | case_sensitivity, |
2596 | 136 | expected_value: value, |
2597 | 136 | }, |
2598 | 136 | }))) |
2599 | | } else { |
2600 | 51 | Ok(Component::AttributeInNoNamespace { |
2601 | 51 | local_name, |
2602 | 51 | operator, |
2603 | 51 | value, |
2604 | 51 | case_sensitivity, |
2605 | 51 | never_matches, |
2606 | 51 | }) |
2607 | | } |
2608 | 1.49k | } parcel_selectors::parser::parse_attribute_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2481 | 1.49k | fn parse_attribute_selector<'i, 't, P, Impl>( | 2482 | 1.49k | parser: &P, | 2483 | 1.49k | input: &mut CssParser<'i, 't>, | 2484 | 1.49k | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> | 2485 | 1.49k | where | 2486 | 1.49k | P: Parser<'i, Impl = Impl>, | 2487 | 1.49k | Impl: SelectorImpl<'i>, | 2488 | | { | 2489 | | let namespace; | 2490 | | let local_name; | 2491 | | | 2492 | 1.49k | input.skip_whitespace(); | 2493 | | | 2494 | 1.49k | match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? { | 2495 | 725 | OptionalQName::None(t) => { | 2496 | 725 | return Err(input.new_custom_error(SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t))); | 2497 | | } | 2498 | 0 | OptionalQName::Some(_, None) => unreachable!(), | 2499 | 733 | OptionalQName::Some(ns, Some(ln)) => { | 2500 | 733 | local_name = ln; | 2501 | 733 | namespace = match ns { | 2502 | 718 | QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None, | 2503 | 15 | QNamePrefix::ExplicitNamespace(prefix, url) => Some(NamespaceConstraint::Specific((prefix, url))), | 2504 | 0 | QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any), | 2505 | | QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => { | 2506 | 0 | unreachable!() // Not returned with in_attr_selector = true | 2507 | | } | 2508 | | } | 2509 | | } | 2510 | | } | 2511 | | | 2512 | 733 | let location = input.current_source_location(); | 2513 | 733 | let operator = match input.next() { | 2514 | | // [foo] | 2515 | | Err(_) => { | 2516 | 105 | let local_name_lower = to_ascii_lowercase(local_name.clone()).into(); | 2517 | 105 | let local_name = local_name.into(); | 2518 | 105 | if let Some(namespace) = namespace { | 2519 | 8 | return Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace { | 2520 | 8 | namespace: Some(namespace), | 2521 | 8 | local_name, | 2522 | 8 | local_name_lower, | 2523 | 8 | operation: ParsedAttrSelectorOperation::Exists, | 2524 | 8 | never_matches: false, | 2525 | 8 | }))); | 2526 | | } else { | 2527 | 97 | return Ok(Component::AttributeInNoNamespaceExists { | 2528 | 97 | local_name, | 2529 | 97 | local_name_lower, | 2530 | 97 | }); | 2531 | | } | 2532 | | } | 2533 | | | 2534 | | // [foo=bar] | 2535 | 219 | Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal, | 2536 | | // [foo~=bar] | 2537 | 36 | Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes, | 2538 | | // [foo|=bar] | 2539 | 8 | Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch, | 2540 | | // [foo^=bar] | 2541 | 1 | Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix, | 2542 | | // [foo*=bar] | 2543 | 11 | Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring, | 2544 | | // [foo$=bar] | 2545 | 3 | Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix, | 2546 | 350 | Ok(t) => { | 2547 | 350 | return Err( | 2548 | 350 | location.new_custom_error(SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone())), | 2549 | 350 | ); | 2550 | | } | 2551 | | }; | 2552 | | | 2553 | 278 | let value = match input.expect_ident_or_string() { | 2554 | 263 | Ok(t) => t.clone(), | 2555 | | Err(BasicParseError { | 2556 | 12 | kind: BasicParseErrorKind::UnexpectedToken(t), | 2557 | 12 | location, | 2558 | 12 | }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))), | 2559 | 3 | Err(e) => return Err(e.into()), | 2560 | | }; | 2561 | 263 | let never_matches = match operator { | 2562 | 214 | AttrSelectorOperator::Equal | AttrSelectorOperator::DashMatch => false, | 2563 | | | 2564 | 36 | AttrSelectorOperator::Includes => value.is_empty() || value.contains(SELECTOR_WHITESPACE), | 2565 | | | 2566 | | AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => { | 2567 | 13 | value.is_empty() | 2568 | | } | 2569 | | }; | 2570 | | | 2571 | 263 | let attribute_flags = parse_attribute_flags(input)?; | 2572 | | | 2573 | 187 | let value = value.into(); | 2574 | | let case_sensitivity; | 2575 | | // copied from to_ascii_lowercase function, so we can know whether it is already lower case. | 2576 | 187 | let (local_name_lower_cow, local_name_is_ascii_lowercase) = | 2577 | 187 | if let Some(first_uppercase) = local_name.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { | 2578 | 135 | let mut string = local_name.to_string(); | 2579 | 135 | string[first_uppercase..].make_ascii_lowercase(); | 2580 | 135 | (string.into(), false) | 2581 | | } else { | 2582 | 52 | (local_name.clone(), true) | 2583 | | }; | 2584 | 187 | case_sensitivity = attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some()); | 2585 | 187 | let local_name_lower = local_name_lower_cow.into(); | 2586 | 187 | let local_name = local_name.into(); | 2587 | 187 | if namespace.is_some() || !local_name_is_ascii_lowercase { | 2588 | 136 | Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace { | 2589 | 136 | namespace, | 2590 | 136 | local_name, | 2591 | 136 | local_name_lower, | 2592 | 136 | never_matches, | 2593 | 136 | operation: ParsedAttrSelectorOperation::WithValue { | 2594 | 136 | operator, | 2595 | 136 | case_sensitivity, | 2596 | 136 | expected_value: value, | 2597 | 136 | }, | 2598 | 136 | }))) | 2599 | | } else { | 2600 | 51 | Ok(Component::AttributeInNoNamespace { | 2601 | 51 | local_name, | 2602 | 51 | operator, | 2603 | 51 | value, | 2604 | 51 | case_sensitivity, | 2605 | 51 | never_matches, | 2606 | 51 | }) | 2607 | | } | 2608 | 1.49k | } |
Unexecuted instantiation: parcel_selectors::parser::parse_attribute_selector::<_, _> |
2609 | | |
2610 | | /// An attribute selector can have 's' or 'i' as flags, or no flags at all. |
2611 | | enum AttributeFlags { |
2612 | | // Matching should be case-sensitive ('s' flag). |
2613 | | CaseSensitive, |
2614 | | // Matching should be case-insensitive ('i' flag). |
2615 | | AsciiCaseInsensitive, |
2616 | | // No flags. Matching behavior depends on the name of the attribute. |
2617 | | CaseSensitivityDependsOnName, |
2618 | | } |
2619 | | |
2620 | | impl AttributeFlags { |
2621 | 187 | fn to_case_sensitivity(self, local_name: &str, have_namespace: bool) -> ParsedCaseSensitivity { |
2622 | 187 | match self { |
2623 | 0 | AttributeFlags::CaseSensitive => ParsedCaseSensitivity::ExplicitCaseSensitive, |
2624 | 0 | AttributeFlags::AsciiCaseInsensitive => ParsedCaseSensitivity::AsciiCaseInsensitive, |
2625 | | AttributeFlags::CaseSensitivityDependsOnName => { |
2626 | 187 | if !have_namespace |
2627 | 186 | && include!(concat!(env!("OUT_DIR"), "/ascii_case_insensitive_html_attributes.rs")).contains(local_name) |
2628 | | { |
2629 | 0 | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument |
2630 | | } else { |
2631 | 187 | ParsedCaseSensitivity::CaseSensitive |
2632 | | } |
2633 | | } |
2634 | | } |
2635 | 187 | } |
2636 | | } |
2637 | | |
2638 | 263 | fn parse_attribute_flags<'i, 't>(input: &mut CssParser<'i, 't>) -> Result<AttributeFlags, BasicParseError<'i>> { |
2639 | 263 | let location = input.current_source_location(); |
2640 | 263 | let token = match input.next() { |
2641 | 76 | Ok(t) => t, |
2642 | | Err(..) => { |
2643 | | // Selectors spec says language-defined; HTML says it depends on the |
2644 | | // exact attribute name. |
2645 | 187 | return Ok(AttributeFlags::CaseSensitivityDependsOnName); |
2646 | | } |
2647 | | }; |
2648 | | |
2649 | 76 | let ident = match *token { |
2650 | 12 | Token::Ident(ref i) => i, |
2651 | 64 | ref other => return Err(location.new_basic_unexpected_token_error(other.clone())), |
2652 | | }; |
2653 | | |
2654 | 12 | Ok(match_ignore_ascii_case! { |
2655 | 12 | ident, |
2656 | 12 | "i" => AttributeFlags::AsciiCaseInsensitive, |
2657 | 12 | "s" => AttributeFlags::CaseSensitive, |
2658 | 12 | _ => return Err(location.new_basic_unexpected_token_error(token.clone())), |
2659 | | }) |
2660 | 263 | } |
2661 | | |
2662 | | /// Level 3: Parse **one** simple_selector. (Though we might insert a second |
2663 | | /// implied "<defaultns>|*" type selector.) |
2664 | 0 | fn parse_negation<'i, 't, P, Impl>( |
2665 | 0 | parser: &P, |
2666 | 0 | input: &mut CssParser<'i, 't>, |
2667 | 0 | state: &mut SelectorParsingState, |
2668 | 0 | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> |
2669 | 0 | where |
2670 | 0 | P: Parser<'i, Impl = Impl>, |
2671 | 0 | Impl: SelectorImpl<'i>, |
2672 | | { |
2673 | 0 | let mut child_state = |
2674 | 0 | *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; |
2675 | 0 | let list = SelectorList::parse_with_state( |
2676 | 0 | parser, |
2677 | 0 | input, |
2678 | 0 | &mut child_state, |
2679 | 0 | ParseErrorRecovery::DiscardList, |
2680 | 0 | NestingRequirement::None, |
2681 | 0 | )?; |
2682 | | |
2683 | 0 | if child_state.contains(SelectorParsingState::AFTER_NESTING) { |
2684 | 0 | state.insert(SelectorParsingState::AFTER_NESTING) |
2685 | 0 | } |
2686 | | |
2687 | 0 | Ok(Component::Negation(list.0.into_vec().into_boxed_slice())) |
2688 | 0 | } Unexecuted instantiation: parcel_selectors::parser::parse_negation::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Unexecuted instantiation: parcel_selectors::parser::parse_negation::<_, _> |
2689 | | |
2690 | | /// simple_selector_sequence |
2691 | | /// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* |
2692 | | /// | [ HASH | class | attrib | pseudo | negation ]+ |
2693 | | /// |
2694 | | /// `Err(())` means invalid selector. |
2695 | | /// `Ok(true)` is an empty selector |
2696 | 2.48M | fn parse_compound_selector<'i, 't, P, Impl>( |
2697 | 2.48M | parser: &P, |
2698 | 2.48M | state: &mut SelectorParsingState, |
2699 | 2.48M | input: &mut CssParser<'i, 't>, |
2700 | 2.48M | builder: &mut SelectorBuilder<'i, Impl>, |
2701 | 2.48M | ) -> Result<bool, ParseError<'i, P::Error>> |
2702 | 2.48M | where |
2703 | 2.48M | P: Parser<'i, Impl = Impl>, |
2704 | 2.48M | Impl: SelectorImpl<'i>, |
2705 | | { |
2706 | 2.48M | input.skip_whitespace(); |
2707 | | |
2708 | 2.48M | let mut empty = true; |
2709 | 2.48M | if parser.is_nesting_allowed() && input.try_parse(|input| input.expect_delim('&')).is_ok() {parcel_selectors::parser::parse_compound_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#0}Line | Count | Source | 2709 | 2.48M | if parser.is_nesting_allowed() && input.try_parse(|input| input.expect_delim('&')).is_ok() { |
Unexecuted instantiation: parcel_selectors::parser::parse_compound_selector::<_, _>::{closure#0} |
2710 | 2.61k | state.insert(SelectorParsingState::AFTER_NESTING); |
2711 | 2.61k | builder.push_simple_selector(Component::Nesting); |
2712 | 2.61k | empty = false; |
2713 | 2.48M | } |
2714 | | |
2715 | 2.48M | if parse_type_selector(parser, input, *state, builder)? { |
2716 | 2.02M | empty = false; |
2717 | 2.02M | } |
2718 | | |
2719 | | loop { |
2720 | 3.32M | let result = match parse_one_simple_selector(parser, input, state)? { |
2721 | 2.31M | None => break, |
2722 | 853k | Some(result) => result, |
2723 | | }; |
2724 | | |
2725 | 853k | if empty { |
2726 | 214k | if let Some(url) = parser.default_namespace() { |
2727 | | // If there was no explicit type selector, but there is a |
2728 | | // default namespace, there is an implicit "<defaultns>|*" type |
2729 | | // selector. Except for :host() or :not() / :is() / :where(), |
2730 | | // where we ignore it. |
2731 | | // |
2732 | | // https://drafts.csswg.org/css-scoping/#host-element-in-tree: |
2733 | | // |
2734 | | // When considered within its own shadow trees, the shadow |
2735 | | // host is featureless. Only the :host, :host(), and |
2736 | | // :host-context() pseudo-classes are allowed to match it. |
2737 | | // |
2738 | | // https://drafts.csswg.org/selectors-4/#featureless: |
2739 | | // |
2740 | | // A featureless element does not match any selector at all, |
2741 | | // except those it is explicitly defined to match. If a |
2742 | | // given selector is allowed to match a featureless element, |
2743 | | // it must do so while ignoring the default namespace. |
2744 | | // |
2745 | | // https://drafts.csswg.org/selectors-4/#matches |
2746 | | // |
2747 | | // Default namespace declarations do not affect the compound |
2748 | | // selector representing the subject of any selector within |
2749 | | // a :is() pseudo-class, unless that compound selector |
2750 | | // contains an explicit universal selector or type selector. |
2751 | | // |
2752 | | // (Similar quotes for :where() / :not()) |
2753 | | // |
2754 | 0 | let ignore_default_ns = state.intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) |
2755 | 0 | || matches!(result, SimpleSelectorParseResult::SimpleSelector(Component::Host(..))); |
2756 | 0 | if !ignore_default_ns { |
2757 | 0 | builder.push_simple_selector(Component::DefaultNamespace(url)); |
2758 | 0 | } |
2759 | 214k | } |
2760 | 639k | } |
2761 | | |
2762 | 853k | empty = false; |
2763 | | |
2764 | 853k | match result { |
2765 | 788k | SimpleSelectorParseResult::SimpleSelector(s) => { |
2766 | 788k | builder.push_simple_selector(s); |
2767 | 788k | } |
2768 | 0 | SimpleSelectorParseResult::PartPseudo(part_names) => { |
2769 | 0 | state.insert(SelectorParsingState::AFTER_PART); |
2770 | 0 | builder.push_combinator(Combinator::Part); |
2771 | 0 | builder.push_simple_selector(Component::Part(part_names)); |
2772 | 0 | } |
2773 | 0 | SimpleSelectorParseResult::SlottedPseudo(selector) => { |
2774 | 0 | state.insert(SelectorParsingState::AFTER_SLOTTED); |
2775 | 0 | builder.push_combinator(Combinator::SlotAssignment); |
2776 | 0 | builder.push_simple_selector(Component::Slotted(selector)); |
2777 | 0 | } |
2778 | 65.6k | SimpleSelectorParseResult::PseudoElement(p) => { |
2779 | 65.6k | if !p.is_unknown() { |
2780 | 135 | state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); |
2781 | 135 | builder.push_combinator(Combinator::PseudoElement); |
2782 | 65.5k | } else { |
2783 | 65.5k | state.insert(SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT); |
2784 | 65.5k | } |
2785 | 65.6k | if !p.accepts_state_pseudo_classes() { |
2786 | 0 | state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); |
2787 | 65.6k | } |
2788 | 65.6k | if p.is_webkit_scrollbar() { |
2789 | 2 | state.insert(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR); |
2790 | 65.6k | } |
2791 | 65.6k | if p.is_view_transition() { |
2792 | 0 | state.insert(SelectorParsingState::AFTER_VIEW_TRANSITION); |
2793 | 65.6k | } |
2794 | 65.6k | builder.push_simple_selector(Component::PseudoElement(p)); |
2795 | | } |
2796 | | } |
2797 | | } |
2798 | 2.31M | Ok(empty) |
2799 | 2.48M | } parcel_selectors::parser::parse_compound_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2696 | 2.48M | fn parse_compound_selector<'i, 't, P, Impl>( | 2697 | 2.48M | parser: &P, | 2698 | 2.48M | state: &mut SelectorParsingState, | 2699 | 2.48M | input: &mut CssParser<'i, 't>, | 2700 | 2.48M | builder: &mut SelectorBuilder<'i, Impl>, | 2701 | 2.48M | ) -> Result<bool, ParseError<'i, P::Error>> | 2702 | 2.48M | where | 2703 | 2.48M | P: Parser<'i, Impl = Impl>, | 2704 | 2.48M | Impl: SelectorImpl<'i>, | 2705 | | { | 2706 | 2.48M | input.skip_whitespace(); | 2707 | | | 2708 | 2.48M | let mut empty = true; | 2709 | 2.48M | if parser.is_nesting_allowed() && input.try_parse(|input| input.expect_delim('&')).is_ok() { | 2710 | 2.61k | state.insert(SelectorParsingState::AFTER_NESTING); | 2711 | 2.61k | builder.push_simple_selector(Component::Nesting); | 2712 | 2.61k | empty = false; | 2713 | 2.48M | } | 2714 | | | 2715 | 2.48M | if parse_type_selector(parser, input, *state, builder)? { | 2716 | 2.02M | empty = false; | 2717 | 2.02M | } | 2718 | | | 2719 | | loop { | 2720 | 3.32M | let result = match parse_one_simple_selector(parser, input, state)? { | 2721 | 2.31M | None => break, | 2722 | 853k | Some(result) => result, | 2723 | | }; | 2724 | | | 2725 | 853k | if empty { | 2726 | 214k | if let Some(url) = parser.default_namespace() { | 2727 | | // If there was no explicit type selector, but there is a | 2728 | | // default namespace, there is an implicit "<defaultns>|*" type | 2729 | | // selector. Except for :host() or :not() / :is() / :where(), | 2730 | | // where we ignore it. | 2731 | | // | 2732 | | // https://drafts.csswg.org/css-scoping/#host-element-in-tree: | 2733 | | // | 2734 | | // When considered within its own shadow trees, the shadow | 2735 | | // host is featureless. Only the :host, :host(), and | 2736 | | // :host-context() pseudo-classes are allowed to match it. | 2737 | | // | 2738 | | // https://drafts.csswg.org/selectors-4/#featureless: | 2739 | | // | 2740 | | // A featureless element does not match any selector at all, | 2741 | | // except those it is explicitly defined to match. If a | 2742 | | // given selector is allowed to match a featureless element, | 2743 | | // it must do so while ignoring the default namespace. | 2744 | | // | 2745 | | // https://drafts.csswg.org/selectors-4/#matches | 2746 | | // | 2747 | | // Default namespace declarations do not affect the compound | 2748 | | // selector representing the subject of any selector within | 2749 | | // a :is() pseudo-class, unless that compound selector | 2750 | | // contains an explicit universal selector or type selector. | 2751 | | // | 2752 | | // (Similar quotes for :where() / :not()) | 2753 | | // | 2754 | 0 | let ignore_default_ns = state.intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) | 2755 | 0 | || matches!(result, SimpleSelectorParseResult::SimpleSelector(Component::Host(..))); | 2756 | 0 | if !ignore_default_ns { | 2757 | 0 | builder.push_simple_selector(Component::DefaultNamespace(url)); | 2758 | 0 | } | 2759 | 214k | } | 2760 | 639k | } | 2761 | | | 2762 | 853k | empty = false; | 2763 | | | 2764 | 853k | match result { | 2765 | 788k | SimpleSelectorParseResult::SimpleSelector(s) => { | 2766 | 788k | builder.push_simple_selector(s); | 2767 | 788k | } | 2768 | 0 | SimpleSelectorParseResult::PartPseudo(part_names) => { | 2769 | 0 | state.insert(SelectorParsingState::AFTER_PART); | 2770 | 0 | builder.push_combinator(Combinator::Part); | 2771 | 0 | builder.push_simple_selector(Component::Part(part_names)); | 2772 | 0 | } | 2773 | 0 | SimpleSelectorParseResult::SlottedPseudo(selector) => { | 2774 | 0 | state.insert(SelectorParsingState::AFTER_SLOTTED); | 2775 | 0 | builder.push_combinator(Combinator::SlotAssignment); | 2776 | 0 | builder.push_simple_selector(Component::Slotted(selector)); | 2777 | 0 | } | 2778 | 65.6k | SimpleSelectorParseResult::PseudoElement(p) => { | 2779 | 65.6k | if !p.is_unknown() { | 2780 | 135 | state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); | 2781 | 135 | builder.push_combinator(Combinator::PseudoElement); | 2782 | 65.5k | } else { | 2783 | 65.5k | state.insert(SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT); | 2784 | 65.5k | } | 2785 | 65.6k | if !p.accepts_state_pseudo_classes() { | 2786 | 0 | state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); | 2787 | 65.6k | } | 2788 | 65.6k | if p.is_webkit_scrollbar() { | 2789 | 2 | state.insert(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR); | 2790 | 65.6k | } | 2791 | 65.6k | if p.is_view_transition() { | 2792 | 0 | state.insert(SelectorParsingState::AFTER_VIEW_TRANSITION); | 2793 | 65.6k | } | 2794 | 65.6k | builder.push_simple_selector(Component::PseudoElement(p)); | 2795 | | } | 2796 | | } | 2797 | | } | 2798 | 2.31M | Ok(empty) | 2799 | 2.48M | } |
Unexecuted instantiation: parcel_selectors::parser::parse_compound_selector::<_, _> |
2800 | | |
2801 | 3.61k | fn parse_is_or_where<'i, 't, P, Impl>( |
2802 | 3.61k | parser: &P, |
2803 | 3.61k | input: &mut CssParser<'i, 't>, |
2804 | 3.61k | state: &mut SelectorParsingState, |
2805 | 3.61k | component: impl FnOnce(Box<[Selector<'i, Impl>]>) -> Component<'i, Impl>, |
2806 | 3.61k | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> |
2807 | 3.61k | where |
2808 | 3.61k | P: Parser<'i, Impl = Impl>, |
2809 | 3.61k | Impl: SelectorImpl<'i>, |
2810 | | { |
2811 | 3.61k | debug_assert!(parser.parse_is_and_where()); |
2812 | | // https://drafts.csswg.org/selectors/#matches-pseudo: |
2813 | | // |
2814 | | // Pseudo-elements cannot be represented by the matches-any |
2815 | | // pseudo-class; they are not valid within :is(). |
2816 | | // |
2817 | 3.61k | let mut child_state = |
2818 | 3.61k | *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; |
2819 | 3.61k | let inner = SelectorList::parse_with_state( |
2820 | 3.61k | parser, |
2821 | 3.61k | input, |
2822 | 3.61k | &mut child_state, |
2823 | 3.61k | parser.is_and_where_error_recovery(), |
2824 | 3.61k | NestingRequirement::None, |
2825 | 0 | )?; |
2826 | 3.61k | if child_state.contains(SelectorParsingState::AFTER_NESTING) { |
2827 | 2.02k | state.insert(SelectorParsingState::AFTER_NESTING) |
2828 | 1.58k | } |
2829 | 3.61k | Ok(component(inner.0.into_vec().into_boxed_slice())) |
2830 | 3.61k | } parcel_selectors::parser::parse_is_or_where::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors, parcel_selectors::parser::parse_functional_pseudo_class<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#0}>Line | Count | Source | 2801 | 2.82k | fn parse_is_or_where<'i, 't, P, Impl>( | 2802 | 2.82k | parser: &P, | 2803 | 2.82k | input: &mut CssParser<'i, 't>, | 2804 | 2.82k | state: &mut SelectorParsingState, | 2805 | 2.82k | component: impl FnOnce(Box<[Selector<'i, Impl>]>) -> Component<'i, Impl>, | 2806 | 2.82k | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> | 2807 | 2.82k | where | 2808 | 2.82k | P: Parser<'i, Impl = Impl>, | 2809 | 2.82k | Impl: SelectorImpl<'i>, | 2810 | | { | 2811 | 2.82k | debug_assert!(parser.parse_is_and_where()); | 2812 | | // https://drafts.csswg.org/selectors/#matches-pseudo: | 2813 | | // | 2814 | | // Pseudo-elements cannot be represented by the matches-any | 2815 | | // pseudo-class; they are not valid within :is(). | 2816 | | // | 2817 | 2.82k | let mut child_state = | 2818 | 2.82k | *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; | 2819 | 2.82k | let inner = SelectorList::parse_with_state( | 2820 | 2.82k | parser, | 2821 | 2.82k | input, | 2822 | 2.82k | &mut child_state, | 2823 | 2.82k | parser.is_and_where_error_recovery(), | 2824 | 2.82k | NestingRequirement::None, | 2825 | 0 | )?; | 2826 | 2.82k | if child_state.contains(SelectorParsingState::AFTER_NESTING) { | 2827 | 1.45k | state.insert(SelectorParsingState::AFTER_NESTING) | 2828 | 1.37k | } | 2829 | 2.82k | Ok(component(inner.0.into_vec().into_boxed_slice())) | 2830 | 2.82k | } |
parcel_selectors::parser::parse_is_or_where::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors, parcel_selectors::parser::Component<lightningcss::selector::private::Selectors>::Is> Line | Count | Source | 2801 | 672 | fn parse_is_or_where<'i, 't, P, Impl>( | 2802 | 672 | parser: &P, | 2803 | 672 | input: &mut CssParser<'i, 't>, | 2804 | 672 | state: &mut SelectorParsingState, | 2805 | 672 | component: impl FnOnce(Box<[Selector<'i, Impl>]>) -> Component<'i, Impl>, | 2806 | 672 | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> | 2807 | 672 | where | 2808 | 672 | P: Parser<'i, Impl = Impl>, | 2809 | 672 | Impl: SelectorImpl<'i>, | 2810 | | { | 2811 | 672 | debug_assert!(parser.parse_is_and_where()); | 2812 | | // https://drafts.csswg.org/selectors/#matches-pseudo: | 2813 | | // | 2814 | | // Pseudo-elements cannot be represented by the matches-any | 2815 | | // pseudo-class; they are not valid within :is(). | 2816 | | // | 2817 | 672 | let mut child_state = | 2818 | 672 | *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; | 2819 | 672 | let inner = SelectorList::parse_with_state( | 2820 | 672 | parser, | 2821 | 672 | input, | 2822 | 672 | &mut child_state, | 2823 | 672 | parser.is_and_where_error_recovery(), | 2824 | 672 | NestingRequirement::None, | 2825 | 0 | )?; | 2826 | 672 | if child_state.contains(SelectorParsingState::AFTER_NESTING) { | 2827 | 561 | state.insert(SelectorParsingState::AFTER_NESTING) | 2828 | 111 | } | 2829 | 672 | Ok(component(inner.0.into_vec().into_boxed_slice())) | 2830 | 672 | } |
parcel_selectors::parser::parse_is_or_where::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors, parcel_selectors::parser::Component<lightningcss::selector::private::Selectors>::Where> Line | Count | Source | 2801 | 113 | fn parse_is_or_where<'i, 't, P, Impl>( | 2802 | 113 | parser: &P, | 2803 | 113 | input: &mut CssParser<'i, 't>, | 2804 | 113 | state: &mut SelectorParsingState, | 2805 | 113 | component: impl FnOnce(Box<[Selector<'i, Impl>]>) -> Component<'i, Impl>, | 2806 | 113 | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> | 2807 | 113 | where | 2808 | 113 | P: Parser<'i, Impl = Impl>, | 2809 | 113 | Impl: SelectorImpl<'i>, | 2810 | | { | 2811 | 113 | debug_assert!(parser.parse_is_and_where()); | 2812 | | // https://drafts.csswg.org/selectors/#matches-pseudo: | 2813 | | // | 2814 | | // Pseudo-elements cannot be represented by the matches-any | 2815 | | // pseudo-class; they are not valid within :is(). | 2816 | | // | 2817 | 113 | let mut child_state = | 2818 | 113 | *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; | 2819 | 113 | let inner = SelectorList::parse_with_state( | 2820 | 113 | parser, | 2821 | 113 | input, | 2822 | 113 | &mut child_state, | 2823 | 113 | parser.is_and_where_error_recovery(), | 2824 | 113 | NestingRequirement::None, | 2825 | 0 | )?; | 2826 | 113 | if child_state.contains(SelectorParsingState::AFTER_NESTING) { | 2827 | 11 | state.insert(SelectorParsingState::AFTER_NESTING) | 2828 | 102 | } | 2829 | 113 | Ok(component(inner.0.into_vec().into_boxed_slice())) | 2830 | 113 | } |
Unexecuted instantiation: parcel_selectors::parser::parse_is_or_where::<_, _, _> |
2831 | | |
2832 | 16 | fn parse_has<'i, 't, P, Impl>( |
2833 | 16 | parser: &P, |
2834 | 16 | input: &mut CssParser<'i, 't>, |
2835 | 16 | state: &mut SelectorParsingState, |
2836 | 16 | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> |
2837 | 16 | where |
2838 | 16 | P: Parser<'i, Impl = Impl>, |
2839 | 16 | Impl: SelectorImpl<'i>, |
2840 | | { |
2841 | 16 | let mut child_state = *state; |
2842 | 16 | let inner = SelectorList::parse_relative_with_state( |
2843 | 16 | parser, |
2844 | 16 | input, |
2845 | 16 | &mut child_state, |
2846 | 16 | parser.is_and_where_error_recovery(), |
2847 | 16 | NestingRequirement::None, |
2848 | 0 | )?; |
2849 | 16 | if child_state.contains(SelectorParsingState::AFTER_NESTING) { |
2850 | 0 | state.insert(SelectorParsingState::AFTER_NESTING) |
2851 | 16 | } |
2852 | 16 | Ok(Component::Has(inner.0.into_vec().into_boxed_slice())) |
2853 | 16 | } parcel_selectors::parser::parse_has::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2832 | 16 | fn parse_has<'i, 't, P, Impl>( | 2833 | 16 | parser: &P, | 2834 | 16 | input: &mut CssParser<'i, 't>, | 2835 | 16 | state: &mut SelectorParsingState, | 2836 | 16 | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> | 2837 | 16 | where | 2838 | 16 | P: Parser<'i, Impl = Impl>, | 2839 | 16 | Impl: SelectorImpl<'i>, | 2840 | | { | 2841 | 16 | let mut child_state = *state; | 2842 | 16 | let inner = SelectorList::parse_relative_with_state( | 2843 | 16 | parser, | 2844 | 16 | input, | 2845 | 16 | &mut child_state, | 2846 | 16 | parser.is_and_where_error_recovery(), | 2847 | 16 | NestingRequirement::None, | 2848 | 0 | )?; | 2849 | 16 | if child_state.contains(SelectorParsingState::AFTER_NESTING) { | 2850 | 0 | state.insert(SelectorParsingState::AFTER_NESTING) | 2851 | 16 | } | 2852 | 16 | Ok(Component::Has(inner.0.into_vec().into_boxed_slice())) | 2853 | 16 | } |
Unexecuted instantiation: parcel_selectors::parser::parse_has::<_, _> |
2854 | | |
2855 | 7.48k | fn parse_functional_pseudo_class<'i, 't, P, Impl>( |
2856 | 7.48k | parser: &P, |
2857 | 7.48k | input: &mut CssParser<'i, 't>, |
2858 | 7.48k | name: CowRcStr<'i>, |
2859 | 7.48k | state: &mut SelectorParsingState, |
2860 | 7.48k | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> |
2861 | 7.48k | where |
2862 | 7.48k | P: Parser<'i, Impl = Impl>, |
2863 | 7.48k | Impl: SelectorImpl<'i>, |
2864 | | { |
2865 | 7.48k | match_ignore_ascii_case! { &name, |
2866 | 7.48k | "nth-child" => return parse_nth_pseudo_class(parser, input, *state, NthType::Child), |
2867 | 7.48k | "nth-of-type" => return parse_nth_pseudo_class(parser, input, *state, NthType::OfType), |
2868 | 7.48k | "nth-last-child" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastChild), |
2869 | 7.48k | "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastOfType), |
2870 | 7.48k | "nth-col" => return parse_nth_pseudo_class(parser, input, *state, NthType::Col), |
2871 | 7.48k | "nth-last-col" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastCol), |
2872 | 7.48k | "is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Is), |
2873 | 6.81k | "where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where), |
2874 | 6.69k | "has" => return parse_has(parser, input, state), |
2875 | 6.68k | "host" => { |
2876 | 1 | if !state.allows_tree_structural_pseudo_classes() { |
2877 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
2878 | 1 | } |
2879 | 1 | return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))); |
2880 | | }, |
2881 | 6.68k | "not" => { |
2882 | 0 | return parse_negation(parser, input, state) |
2883 | | }, |
2884 | 6.68k | _ => {} |
2885 | | } |
2886 | | |
2887 | 6.68k | if let Some(prefix) = parser.parse_any_prefix(&name) { |
2888 | 2.82k | return parse_is_or_where(parser, input, state, |selectors| Component::Any(prefix, selectors)); parcel_selectors::parser::parse_functional_pseudo_class::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#0}Line | Count | Source | 2888 | 2.82k | return parse_is_or_where(parser, input, state, |selectors| Component::Any(prefix, selectors)); |
Unexecuted instantiation: parcel_selectors::parser::parse_functional_pseudo_class::<_, _>::{closure#0} |
2889 | 3.85k | } |
2890 | | |
2891 | 3.85k | if !state.allows_custom_functional_pseudo_classes() { |
2892 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
2893 | 3.85k | } |
2894 | | |
2895 | 3.85k | P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) |
2896 | 7.48k | } parcel_selectors::parser::parse_functional_pseudo_class::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2855 | 7.48k | fn parse_functional_pseudo_class<'i, 't, P, Impl>( | 2856 | 7.48k | parser: &P, | 2857 | 7.48k | input: &mut CssParser<'i, 't>, | 2858 | 7.48k | name: CowRcStr<'i>, | 2859 | 7.48k | state: &mut SelectorParsingState, | 2860 | 7.48k | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> | 2861 | 7.48k | where | 2862 | 7.48k | P: Parser<'i, Impl = Impl>, | 2863 | 7.48k | Impl: SelectorImpl<'i>, | 2864 | | { | 2865 | 7.48k | match_ignore_ascii_case! { &name, | 2866 | 7.48k | "nth-child" => return parse_nth_pseudo_class(parser, input, *state, NthType::Child), | 2867 | 7.48k | "nth-of-type" => return parse_nth_pseudo_class(parser, input, *state, NthType::OfType), | 2868 | 7.48k | "nth-last-child" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastChild), | 2869 | 7.48k | "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastOfType), | 2870 | 7.48k | "nth-col" => return parse_nth_pseudo_class(parser, input, *state, NthType::Col), | 2871 | 7.48k | "nth-last-col" => return parse_nth_pseudo_class(parser, input, *state, NthType::LastCol), | 2872 | 7.48k | "is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Is), | 2873 | 6.81k | "where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where), | 2874 | 6.69k | "has" => return parse_has(parser, input, state), | 2875 | 6.68k | "host" => { | 2876 | 1 | if !state.allows_tree_structural_pseudo_classes() { | 2877 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 2878 | 1 | } | 2879 | 1 | return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))); | 2880 | | }, | 2881 | 6.68k | "not" => { | 2882 | 0 | return parse_negation(parser, input, state) | 2883 | | }, | 2884 | 6.68k | _ => {} | 2885 | | } | 2886 | | | 2887 | 6.68k | if let Some(prefix) = parser.parse_any_prefix(&name) { | 2888 | 2.82k | return parse_is_or_where(parser, input, state, |selectors| Component::Any(prefix, selectors)); | 2889 | 3.85k | } | 2890 | | | 2891 | 3.85k | if !state.allows_custom_functional_pseudo_classes() { | 2892 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 2893 | 3.85k | } | 2894 | | | 2895 | 3.85k | P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) | 2896 | 7.48k | } |
Unexecuted instantiation: parcel_selectors::parser::parse_functional_pseudo_class::<_, _> |
2897 | | |
2898 | 5 | fn parse_nth_pseudo_class<'i, 't, P, Impl>( |
2899 | 5 | parser: &P, |
2900 | 5 | input: &mut CssParser<'i, 't>, |
2901 | 5 | state: SelectorParsingState, |
2902 | 5 | ty: NthType, |
2903 | 5 | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> |
2904 | 5 | where |
2905 | 5 | P: Parser<'i, Impl = Impl>, |
2906 | 5 | Impl: SelectorImpl<'i>, |
2907 | | { |
2908 | 5 | if !state.allows_tree_structural_pseudo_classes() { |
2909 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
2910 | 5 | } |
2911 | 5 | let (a, b) = parse_nth(input)?; |
2912 | 0 | let nth_data = NthSelectorData { |
2913 | 0 | ty, |
2914 | 0 | is_function: true, |
2915 | 0 | a, |
2916 | 0 | b, |
2917 | 0 | }; |
2918 | 0 | if !ty.allows_of_selector() { |
2919 | 0 | return Ok(Component::Nth(nth_data)); |
2920 | 0 | } |
2921 | | |
2922 | | // Try to parse "of <selector-list>". |
2923 | 0 | if input.try_parse(|i| i.expect_ident_matching("of")).is_err() {Unexecuted instantiation: parcel_selectors::parser::parse_nth_pseudo_class::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#0}Unexecuted instantiation: parcel_selectors::parser::parse_nth_pseudo_class::<_, _>::{closure#0} |
2924 | 0 | return Ok(Component::Nth(nth_data)); |
2925 | 0 | } |
2926 | | // Whitespace between "of" and the selector list is optional |
2927 | | // https://github.com/w3c/csswg-drafts/issues/8285 |
2928 | 0 | let mut child_state = |
2929 | 0 | state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; |
2930 | 0 | let selectors = SelectorList::parse_with_state( |
2931 | 0 | parser, |
2932 | 0 | input, |
2933 | 0 | &mut child_state, |
2934 | 0 | ParseErrorRecovery::IgnoreInvalidSelector, |
2935 | 0 | NestingRequirement::None, |
2936 | 0 | )?; |
2937 | 0 | Ok(Component::NthOf(NthOfSelectorData::new( |
2938 | 0 | nth_data, |
2939 | 0 | selectors.0.into_vec().into_boxed_slice(), |
2940 | 0 | ))) |
2941 | 5 | } parcel_selectors::parser::parse_nth_pseudo_class::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2898 | 5 | fn parse_nth_pseudo_class<'i, 't, P, Impl>( | 2899 | 5 | parser: &P, | 2900 | 5 | input: &mut CssParser<'i, 't>, | 2901 | 5 | state: SelectorParsingState, | 2902 | 5 | ty: NthType, | 2903 | 5 | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> | 2904 | 5 | where | 2905 | 5 | P: Parser<'i, Impl = Impl>, | 2906 | 5 | Impl: SelectorImpl<'i>, | 2907 | | { | 2908 | 5 | if !state.allows_tree_structural_pseudo_classes() { | 2909 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 2910 | 5 | } | 2911 | 5 | let (a, b) = parse_nth(input)?; | 2912 | 0 | let nth_data = NthSelectorData { | 2913 | 0 | ty, | 2914 | 0 | is_function: true, | 2915 | 0 | a, | 2916 | 0 | b, | 2917 | 0 | }; | 2918 | 0 | if !ty.allows_of_selector() { | 2919 | 0 | return Ok(Component::Nth(nth_data)); | 2920 | 0 | } | 2921 | | | 2922 | | // Try to parse "of <selector-list>". | 2923 | 0 | if input.try_parse(|i| i.expect_ident_matching("of")).is_err() { | 2924 | 0 | return Ok(Component::Nth(nth_data)); | 2925 | 0 | } | 2926 | | // Whitespace between "of" and the selector list is optional | 2927 | | // https://github.com/w3c/csswg-drafts/issues/8285 | 2928 | 0 | let mut child_state = | 2929 | 0 | state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; | 2930 | 0 | let selectors = SelectorList::parse_with_state( | 2931 | 0 | parser, | 2932 | 0 | input, | 2933 | 0 | &mut child_state, | 2934 | 0 | ParseErrorRecovery::IgnoreInvalidSelector, | 2935 | 0 | NestingRequirement::None, | 2936 | 0 | )?; | 2937 | 0 | Ok(Component::NthOf(NthOfSelectorData::new( | 2938 | 0 | nth_data, | 2939 | 0 | selectors.0.into_vec().into_boxed_slice(), | 2940 | 0 | ))) | 2941 | 5 | } |
Unexecuted instantiation: parcel_selectors::parser::parse_nth_pseudo_class::<_, _> |
2942 | | |
2943 | | /// Returns whether the name corresponds to a CSS2 pseudo-element that |
2944 | | /// can be specified with the single colon syntax (in addition to the |
2945 | | /// double-colon syntax, which can be used for all pseudo-elements). |
2946 | 297k | fn is_css2_pseudo_element(name: &str) -> bool { |
2947 | | // ** Do not add to this list! ** |
2948 | 297k | match_ignore_ascii_case! { name, |
2949 | 297k | "before" | "after" | "first-line" | "first-letter" => true, |
2950 | 297k | _ => false, |
2951 | | } |
2952 | 297k | } |
2953 | | |
2954 | | /// Parse a simple selector other than a type selector. |
2955 | | /// |
2956 | | /// * `Err(())`: Invalid selector, abort |
2957 | | /// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed. |
2958 | | /// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element |
2959 | 3.32M | fn parse_one_simple_selector<'i, 't, P, Impl>( |
2960 | 3.32M | parser: &P, |
2961 | 3.32M | input: &mut CssParser<'i, 't>, |
2962 | 3.32M | state: &mut SelectorParsingState, |
2963 | 3.32M | ) -> Result<Option<SimpleSelectorParseResult<'i, Impl>>, ParseError<'i, P::Error>> |
2964 | 3.32M | where |
2965 | 3.32M | P: Parser<'i, Impl = Impl>, |
2966 | 3.32M | Impl: SelectorImpl<'i>, |
2967 | | { |
2968 | 3.32M | let start = input.state(); |
2969 | 3.32M | let token_location = input.current_source_location(); |
2970 | 3.32M | let token = match input.next_including_whitespace().map(|t| t.clone()) {parcel_selectors::parser::parse_one_simple_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#0}Line | Count | Source | 2970 | 2.26M | let token = match input.next_including_whitespace().map(|t| t.clone()) { |
Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<_, _>::{closure#0} |
2971 | 2.26M | Ok(t) => t, |
2972 | | Err(..) => { |
2973 | 1.06M | input.reset(&start); |
2974 | 1.06M | return Ok(None); |
2975 | | } |
2976 | | }; |
2977 | | |
2978 | 54.8k | Ok(Some(match token { |
2979 | 20.8k | Token::IDHash(id) => { |
2980 | 20.8k | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { |
2981 | 2 | return Err(token_location.new_custom_error( |
2982 | 2 | SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::IDHash(id)), |
2983 | 2 | )); |
2984 | 20.8k | } |
2985 | 20.8k | let id = Component::ID(id.into()); |
2986 | 20.8k | SimpleSelectorParseResult::SimpleSelector(id) |
2987 | | } |
2988 | | Token::Delim('.') => { |
2989 | 456k | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { |
2990 | 15 | return Err(token_location.new_custom_error( |
2991 | 15 | SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::Delim('.')), |
2992 | 15 | )); |
2993 | 456k | } |
2994 | 456k | let location = input.current_source_location(); |
2995 | 456k | let class = match *input.next_including_whitespace()? { |
2996 | 416k | Token::Ident(ref class) => class.clone(), |
2997 | 31.1k | ref t => { |
2998 | 31.1k | let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); |
2999 | 31.1k | return Err(location.new_custom_error(e)); |
3000 | | } |
3001 | | }; |
3002 | 416k | let class = Component::Class(class.into()); |
3003 | 416k | SimpleSelectorParseResult::SimpleSelector(class) |
3004 | | } |
3005 | | Token::SquareBracketBlock => { |
3006 | 1.50k | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { |
3007 | 1 | return Err(token_location.new_custom_error( |
3008 | 1 | SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::SquareBracketBlock), |
3009 | 1 | )); |
3010 | 1.49k | } |
3011 | 1.49k | let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; parcel_selectors::parser::parse_one_simple_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#1}Line | Count | Source | 3011 | 1.49k | let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; |
Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<_, _>::{closure#1} |
3012 | 292 | SimpleSelectorParseResult::SimpleSelector(attr) |
3013 | | } |
3014 | | Token::Colon => { |
3015 | 481k | let location = input.current_source_location(); |
3016 | 481k | let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() { |
3017 | 71.4k | Token::Colon => (false, input.next_including_whitespace()?.clone()), |
3018 | 403k | t => (true, t), |
3019 | | }; |
3020 | 473k | let (name, is_functional) = match next_token { |
3021 | 355k | Token::Ident(name) => (name, false), |
3022 | 7.67k | Token::Function(name) => (name, true), |
3023 | 110k | t => { |
3024 | 110k | let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t); |
3025 | 110k | return Err(input.new_custom_error(e)); |
3026 | | } |
3027 | | }; |
3028 | 362k | let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); |
3029 | 362k | if is_pseudo_element { |
3030 | 65.8k | if !state.allows_pseudos() { |
3031 | 112 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
3032 | 65.7k | } |
3033 | 65.7k | let pseudo_element = if is_functional { |
3034 | 189 | if P::parse_part(parser) && name.eq_ignore_ascii_case("part") { |
3035 | 0 | if !state.allows_part() { |
3036 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
3037 | 0 | } |
3038 | 0 | let names = input.parse_nested_block(|input| { |
3039 | 0 | let mut result = Vec::with_capacity(1); |
3040 | 0 | result.push(input.expect_ident_cloned()?.into()); |
3041 | 0 | while !input.is_exhausted() { |
3042 | 0 | result.push(input.expect_ident_cloned()?.into()); |
3043 | | } |
3044 | 0 | Ok(result.into_boxed_slice()) |
3045 | 0 | })?; Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#2}Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<_, _>::{closure#2} |
3046 | 0 | return Ok(Some(SimpleSelectorParseResult::PartPseudo(names))); |
3047 | 189 | } |
3048 | 189 | if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { |
3049 | 0 | if !state.allows_slotted() { |
3050 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
3051 | 0 | } |
3052 | 0 | let selector = |
3053 | 0 | input.parse_nested_block(|input| parse_inner_compound_selector(parser, input, state))?; Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#3}Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<_, _>::{closure#3} |
3054 | 0 | return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); |
3055 | 189 | } |
3056 | 189 | input.parse_nested_block(|input| P::parse_functional_pseudo_element(parser, name, input))? parcel_selectors::parser::parse_one_simple_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#4}Line | Count | Source | 3056 | 189 | input.parse_nested_block(|input| P::parse_functional_pseudo_element(parser, name, input))? |
Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<_, _>::{closure#4} |
3057 | | } else { |
3058 | 65.5k | P::parse_pseudo_element(parser, location, name)? |
3059 | | }; |
3060 | | |
3061 | 65.6k | if state.intersects(SelectorParsingState::AFTER_SLOTTED) && !pseudo_element.valid_after_slotted() { |
3062 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); |
3063 | 65.6k | } |
3064 | 65.6k | SimpleSelectorParseResult::PseudoElement(pseudo_element) |
3065 | | } else { |
3066 | 297k | let pseudo_class = if is_functional { |
3067 | 7.48k | input.parse_nested_block(|input| parse_functional_pseudo_class(parser, input, name, state))? parcel_selectors::parser::parse_one_simple_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors>::{closure#5}Line | Count | Source | 3067 | 7.48k | input.parse_nested_block(|input| parse_functional_pseudo_class(parser, input, name, state))? |
Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<_, _>::{closure#5} |
3068 | | } else { |
3069 | 289k | parse_simple_pseudo_class(parser, location, name, *state)? |
3070 | | }; |
3071 | 295k | SimpleSelectorParseResult::SimpleSelector(pseudo_class) |
3072 | | } |
3073 | | } |
3074 | 54.8k | Token::Delim('&') if parser.is_nesting_allowed() => { |
3075 | 54.8k | *state |= SelectorParsingState::AFTER_NESTING; |
3076 | 54.8k | SimpleSelectorParseResult::SimpleSelector(Component::Nesting) |
3077 | | } |
3078 | | _ => { |
3079 | 1.25M | input.reset(&start); |
3080 | 1.25M | return Ok(None); |
3081 | | } |
3082 | | })) |
3083 | 3.32M | } parcel_selectors::parser::parse_one_simple_selector::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 2959 | 3.32M | fn parse_one_simple_selector<'i, 't, P, Impl>( | 2960 | 3.32M | parser: &P, | 2961 | 3.32M | input: &mut CssParser<'i, 't>, | 2962 | 3.32M | state: &mut SelectorParsingState, | 2963 | 3.32M | ) -> Result<Option<SimpleSelectorParseResult<'i, Impl>>, ParseError<'i, P::Error>> | 2964 | 3.32M | where | 2965 | 3.32M | P: Parser<'i, Impl = Impl>, | 2966 | 3.32M | Impl: SelectorImpl<'i>, | 2967 | | { | 2968 | 3.32M | let start = input.state(); | 2969 | 3.32M | let token_location = input.current_source_location(); | 2970 | 3.32M | let token = match input.next_including_whitespace().map(|t| t.clone()) { | 2971 | 2.26M | Ok(t) => t, | 2972 | | Err(..) => { | 2973 | 1.06M | input.reset(&start); | 2974 | 1.06M | return Ok(None); | 2975 | | } | 2976 | | }; | 2977 | | | 2978 | 54.8k | Ok(Some(match token { | 2979 | 20.8k | Token::IDHash(id) => { | 2980 | 20.8k | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { | 2981 | 2 | return Err(token_location.new_custom_error( | 2982 | 2 | SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::IDHash(id)), | 2983 | 2 | )); | 2984 | 20.8k | } | 2985 | 20.8k | let id = Component::ID(id.into()); | 2986 | 20.8k | SimpleSelectorParseResult::SimpleSelector(id) | 2987 | | } | 2988 | | Token::Delim('.') => { | 2989 | 456k | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { | 2990 | 15 | return Err(token_location.new_custom_error( | 2991 | 15 | SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::Delim('.')), | 2992 | 15 | )); | 2993 | 456k | } | 2994 | 456k | let location = input.current_source_location(); | 2995 | 456k | let class = match *input.next_including_whitespace()? { | 2996 | 416k | Token::Ident(ref class) => class.clone(), | 2997 | 31.1k | ref t => { | 2998 | 31.1k | let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); | 2999 | 31.1k | return Err(location.new_custom_error(e)); | 3000 | | } | 3001 | | }; | 3002 | 416k | let class = Component::Class(class.into()); | 3003 | 416k | SimpleSelectorParseResult::SimpleSelector(class) | 3004 | | } | 3005 | | Token::SquareBracketBlock => { | 3006 | 1.50k | if state.intersects(SelectorParsingState::AFTER_PSEUDO) { | 3007 | 1 | return Err(token_location.new_custom_error( | 3008 | 1 | SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::SquareBracketBlock), | 3009 | 1 | )); | 3010 | 1.49k | } | 3011 | 1.49k | let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; | 3012 | 292 | SimpleSelectorParseResult::SimpleSelector(attr) | 3013 | | } | 3014 | | Token::Colon => { | 3015 | 481k | let location = input.current_source_location(); | 3016 | 481k | let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() { | 3017 | 71.4k | Token::Colon => (false, input.next_including_whitespace()?.clone()), | 3018 | 403k | t => (true, t), | 3019 | | }; | 3020 | 473k | let (name, is_functional) = match next_token { | 3021 | 355k | Token::Ident(name) => (name, false), | 3022 | 7.67k | Token::Function(name) => (name, true), | 3023 | 110k | t => { | 3024 | 110k | let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t); | 3025 | 110k | return Err(input.new_custom_error(e)); | 3026 | | } | 3027 | | }; | 3028 | 362k | let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); | 3029 | 362k | if is_pseudo_element { | 3030 | 65.8k | if !state.allows_pseudos() { | 3031 | 112 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 3032 | 65.7k | } | 3033 | 65.7k | let pseudo_element = if is_functional { | 3034 | 189 | if P::parse_part(parser) && name.eq_ignore_ascii_case("part") { | 3035 | 0 | if !state.allows_part() { | 3036 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 3037 | 0 | } | 3038 | 0 | let names = input.parse_nested_block(|input| { | 3039 | | let mut result = Vec::with_capacity(1); | 3040 | | result.push(input.expect_ident_cloned()?.into()); | 3041 | | while !input.is_exhausted() { | 3042 | | result.push(input.expect_ident_cloned()?.into()); | 3043 | | } | 3044 | | Ok(result.into_boxed_slice()) | 3045 | 0 | })?; | 3046 | 0 | return Ok(Some(SimpleSelectorParseResult::PartPseudo(names))); | 3047 | 189 | } | 3048 | 189 | if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { | 3049 | 0 | if !state.allows_slotted() { | 3050 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 3051 | 0 | } | 3052 | 0 | let selector = | 3053 | 0 | input.parse_nested_block(|input| parse_inner_compound_selector(parser, input, state))?; | 3054 | 0 | return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); | 3055 | 189 | } | 3056 | 189 | input.parse_nested_block(|input| P::parse_functional_pseudo_element(parser, name, input))? | 3057 | | } else { | 3058 | 65.5k | P::parse_pseudo_element(parser, location, name)? | 3059 | | }; | 3060 | | | 3061 | 65.6k | if state.intersects(SelectorParsingState::AFTER_SLOTTED) && !pseudo_element.valid_after_slotted() { | 3062 | 0 | return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); | 3063 | 65.6k | } | 3064 | 65.6k | SimpleSelectorParseResult::PseudoElement(pseudo_element) | 3065 | | } else { | 3066 | 297k | let pseudo_class = if is_functional { | 3067 | 7.48k | input.parse_nested_block(|input| parse_functional_pseudo_class(parser, input, name, state))? | 3068 | | } else { | 3069 | 289k | parse_simple_pseudo_class(parser, location, name, *state)? | 3070 | | }; | 3071 | 295k | SimpleSelectorParseResult::SimpleSelector(pseudo_class) | 3072 | | } | 3073 | | } | 3074 | 54.8k | Token::Delim('&') if parser.is_nesting_allowed() => { | 3075 | 54.8k | *state |= SelectorParsingState::AFTER_NESTING; | 3076 | 54.8k | SimpleSelectorParseResult::SimpleSelector(Component::Nesting) | 3077 | | } | 3078 | | _ => { | 3079 | 1.25M | input.reset(&start); | 3080 | 1.25M | return Ok(None); | 3081 | | } | 3082 | | })) | 3083 | 3.32M | } |
Unexecuted instantiation: parcel_selectors::parser::parse_one_simple_selector::<_, _> |
3084 | | |
3085 | 289k | fn parse_simple_pseudo_class<'i, P, Impl>( |
3086 | 289k | parser: &P, |
3087 | 289k | location: SourceLocation, |
3088 | 289k | name: CowRcStr<'i>, |
3089 | 289k | state: SelectorParsingState, |
3090 | 289k | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> |
3091 | 289k | where |
3092 | 289k | P: Parser<'i, Impl = Impl>, |
3093 | 289k | Impl: SelectorImpl<'i>, |
3094 | | { |
3095 | 289k | if !state.allows_non_functional_pseudo_classes() { |
3096 | 0 | return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); |
3097 | 289k | } |
3098 | | |
3099 | 289k | if state.allows_tree_structural_pseudo_classes() { |
3100 | 289k | match_ignore_ascii_case! { &name, |
3101 | 289k | "first-child" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ false))), |
3102 | 289k | "last-child" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ false))), |
3103 | 289k | "only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))), |
3104 | 289k | "root" => return Ok(Component::Root), |
3105 | 289k | "empty" => return Ok(Component::Empty), |
3106 | 289k | "scope" => return Ok(Component::Scope), |
3107 | 289k | "host" if P::parse_host(parser) => return Ok(Component::Host(None)), |
3108 | 289k | "first-of-type" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ true))), |
3109 | 289k | "last-of-type" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ true))), |
3110 | 289k | "only-of-type" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ true))), |
3111 | 289k | _ => {}, |
3112 | | } |
3113 | 18 | } |
3114 | | |
3115 | | // The view-transition pseudo elements accept the :only-child pseudo class. |
3116 | | // https://w3c.github.io/csswg-drafts/css-view-transitions-1/#pseudo-root |
3117 | 289k | if state.intersects(SelectorParsingState::AFTER_VIEW_TRANSITION) { |
3118 | 0 | match_ignore_ascii_case! { &name, |
3119 | 0 | "only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))), |
3120 | 0 | _ => {} |
3121 | | } |
3122 | 289k | } |
3123 | | |
3124 | 289k | let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; |
3125 | 289k | if state.intersects(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR) { |
3126 | 0 | if !pseudo_class.is_valid_after_webkit_scrollbar() { |
3127 | 0 | return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar)); |
3128 | 0 | } |
3129 | 289k | } else if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) { |
3130 | 18 | if !pseudo_class.is_user_action_state() { |
3131 | 18 | return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement)); |
3132 | 0 | } |
3133 | 289k | } else if !pseudo_class.is_valid_before_webkit_scrollbar() { |
3134 | 83 | return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar)); |
3135 | 289k | } |
3136 | 289k | Ok(Component::NonTSPseudoClass(pseudo_class)) |
3137 | 289k | } parcel_selectors::parser::parse_simple_pseudo_class::<lightningcss::selector::SelectorParser, lightningcss::selector::private::Selectors> Line | Count | Source | 3085 | 289k | fn parse_simple_pseudo_class<'i, P, Impl>( | 3086 | 289k | parser: &P, | 3087 | 289k | location: SourceLocation, | 3088 | 289k | name: CowRcStr<'i>, | 3089 | 289k | state: SelectorParsingState, | 3090 | 289k | ) -> Result<Component<'i, Impl>, ParseError<'i, P::Error>> | 3091 | 289k | where | 3092 | 289k | P: Parser<'i, Impl = Impl>, | 3093 | 289k | Impl: SelectorImpl<'i>, | 3094 | | { | 3095 | 289k | if !state.allows_non_functional_pseudo_classes() { | 3096 | 0 | return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); | 3097 | 289k | } | 3098 | | | 3099 | 289k | if state.allows_tree_structural_pseudo_classes() { | 3100 | 289k | match_ignore_ascii_case! { &name, | 3101 | 289k | "first-child" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ false))), | 3102 | 289k | "last-child" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ false))), | 3103 | 289k | "only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))), | 3104 | 289k | "root" => return Ok(Component::Root), | 3105 | 289k | "empty" => return Ok(Component::Empty), | 3106 | 289k | "scope" => return Ok(Component::Scope), | 3107 | 289k | "host" if P::parse_host(parser) => return Ok(Component::Host(None)), | 3108 | 289k | "first-of-type" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ true))), | 3109 | 289k | "last-of-type" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ true))), | 3110 | 289k | "only-of-type" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ true))), | 3111 | 289k | _ => {}, | 3112 | | } | 3113 | 18 | } | 3114 | | | 3115 | | // The view-transition pseudo elements accept the :only-child pseudo class. | 3116 | | // https://w3c.github.io/csswg-drafts/css-view-transitions-1/#pseudo-root | 3117 | 289k | if state.intersects(SelectorParsingState::AFTER_VIEW_TRANSITION) { | 3118 | 0 | match_ignore_ascii_case! { &name, | 3119 | 0 | "only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))), | 3120 | 0 | _ => {} | 3121 | | } | 3122 | 289k | } | 3123 | | | 3124 | 289k | let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; | 3125 | 289k | if state.intersects(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR) { | 3126 | 0 | if !pseudo_class.is_valid_after_webkit_scrollbar() { | 3127 | 0 | return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar)); | 3128 | 0 | } | 3129 | 289k | } else if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) { | 3130 | 18 | if !pseudo_class.is_user_action_state() { | 3131 | 18 | return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement)); | 3132 | 0 | } | 3133 | 289k | } else if !pseudo_class.is_valid_before_webkit_scrollbar() { | 3134 | 83 | return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar)); | 3135 | 289k | } | 3136 | 289k | Ok(Component::NonTSPseudoClass(pseudo_class)) | 3137 | 289k | } |
Unexecuted instantiation: parcel_selectors::parser::parse_simple_pseudo_class::<_, _> |
3138 | | |
3139 | | // NB: pub module in order to access the DummyParser |
3140 | | #[cfg(test)] |
3141 | | pub mod tests { |
3142 | | use super::*; |
3143 | | use crate::builder::SelectorFlags; |
3144 | | use crate::parser; |
3145 | | use cssparser::{serialize_identifier, serialize_string, Parser as CssParser, ParserInput, ToCss}; |
3146 | | use std::collections::HashMap; |
3147 | | use std::fmt; |
3148 | | |
3149 | | #[derive(Clone, Debug, Eq, PartialEq, Hash)] |
3150 | | pub enum PseudoClass { |
3151 | | Hover, |
3152 | | Active, |
3153 | | Lang(String), |
3154 | | } |
3155 | | |
3156 | | #[derive(Clone, Debug, Eq, PartialEq, Hash)] |
3157 | | pub enum PseudoElement { |
3158 | | Before, |
3159 | | After, |
3160 | | } |
3161 | | |
3162 | | impl<'i> parser::PseudoElement<'i> for PseudoElement { |
3163 | | type Impl = DummySelectorImpl; |
3164 | | |
3165 | | fn accepts_state_pseudo_classes(&self) -> bool { |
3166 | | true |
3167 | | } |
3168 | | |
3169 | | fn valid_after_slotted(&self) -> bool { |
3170 | | true |
3171 | | } |
3172 | | } |
3173 | | |
3174 | | impl<'i> parser::NonTSPseudoClass<'i> for PseudoClass { |
3175 | | type Impl = DummySelectorImpl; |
3176 | | |
3177 | | #[inline] |
3178 | | fn is_active_or_hover(&self) -> bool { |
3179 | | matches!(*self, PseudoClass::Active | PseudoClass::Hover) |
3180 | | } |
3181 | | |
3182 | | #[inline] |
3183 | | fn is_user_action_state(&self) -> bool { |
3184 | | self.is_active_or_hover() |
3185 | | } |
3186 | | } |
3187 | | |
3188 | | impl ToCss for PseudoClass { |
3189 | | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
3190 | | where |
3191 | | W: fmt::Write, |
3192 | | { |
3193 | | match *self { |
3194 | | PseudoClass::Hover => dest.write_str(":hover"), |
3195 | | PseudoClass::Active => dest.write_str(":active"), |
3196 | | PseudoClass::Lang(ref lang) => { |
3197 | | dest.write_str(":lang(")?; |
3198 | | serialize_identifier(lang, dest)?; |
3199 | | dest.write_char(')') |
3200 | | } |
3201 | | } |
3202 | | } |
3203 | | } |
3204 | | |
3205 | | impl ToCss for PseudoElement { |
3206 | | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
3207 | | where |
3208 | | W: fmt::Write, |
3209 | | { |
3210 | | match *self { |
3211 | | PseudoElement::Before => dest.write_str("::before"), |
3212 | | PseudoElement::After => dest.write_str("::after"), |
3213 | | } |
3214 | | } |
3215 | | } |
3216 | | |
3217 | | #[derive(Clone, Debug, PartialEq)] |
3218 | | pub struct DummySelectorImpl; |
3219 | | |
3220 | | #[derive(Default)] |
3221 | | pub struct DummyParser { |
3222 | | default_ns: Option<DummyAtom>, |
3223 | | ns_prefixes: HashMap<DummyAtom, DummyAtom>, |
3224 | | } |
3225 | | |
3226 | | impl DummyParser { |
3227 | | fn default_with_namespace(default_ns: DummyAtom) -> DummyParser { |
3228 | | DummyParser { |
3229 | | default_ns: Some(default_ns), |
3230 | | ns_prefixes: Default::default(), |
3231 | | } |
3232 | | } |
3233 | | } |
3234 | | |
3235 | | impl<'i> SelectorImpl<'i> for DummySelectorImpl { |
3236 | | type ExtraMatchingData = (); |
3237 | | type AttrValue = DummyAttrValue; |
3238 | | type Identifier = DummyAtom; |
3239 | | type LocalName = DummyAtom; |
3240 | | type NamespaceUrl = DummyAtom; |
3241 | | type NamespacePrefix = DummyAtom; |
3242 | | type BorrowedLocalName = DummyAtom; |
3243 | | type BorrowedNamespaceUrl = DummyAtom; |
3244 | | type NonTSPseudoClass = PseudoClass; |
3245 | | type PseudoElement = PseudoElement; |
3246 | | type VendorPrefix = u8; |
3247 | | } |
3248 | | |
3249 | | #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] |
3250 | | pub struct DummyAttrValue(String); |
3251 | | |
3252 | | impl ToCss for DummyAttrValue { |
3253 | | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
3254 | | where |
3255 | | W: fmt::Write, |
3256 | | { |
3257 | | serialize_string(&self.0, dest) |
3258 | | } |
3259 | | } |
3260 | | |
3261 | | impl AsRef<str> for DummyAttrValue { |
3262 | | fn as_ref(&self) -> &str { |
3263 | | self.0.as_ref() |
3264 | | } |
3265 | | } |
3266 | | |
3267 | | impl<'a> From<&'a str> for DummyAttrValue { |
3268 | | fn from(string: &'a str) -> Self { |
3269 | | Self(string.into()) |
3270 | | } |
3271 | | } |
3272 | | |
3273 | | impl<'a> From<std::borrow::Cow<'a, str>> for DummyAttrValue { |
3274 | | fn from(string: std::borrow::Cow<'a, str>) -> Self { |
3275 | | Self(string.to_string()) |
3276 | | } |
3277 | | } |
3278 | | |
3279 | | impl<'a> From<CowRcStr<'a>> for DummyAttrValue { |
3280 | | fn from(string: CowRcStr<'a>) -> Self { |
3281 | | Self(string.to_string()) |
3282 | | } |
3283 | | } |
3284 | | |
3285 | | #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] |
3286 | | pub struct DummyAtom(String); |
3287 | | |
3288 | | impl ToCss for DummyAtom { |
3289 | | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
3290 | | where |
3291 | | W: fmt::Write, |
3292 | | { |
3293 | | serialize_identifier(&self.0, dest) |
3294 | | } |
3295 | | } |
3296 | | |
3297 | | impl From<String> for DummyAtom { |
3298 | | fn from(string: String) -> Self { |
3299 | | DummyAtom(string) |
3300 | | } |
3301 | | } |
3302 | | |
3303 | | impl<'a> From<&'a str> for DummyAtom { |
3304 | | fn from(string: &'a str) -> Self { |
3305 | | DummyAtom(string.into()) |
3306 | | } |
3307 | | } |
3308 | | |
3309 | | impl<'a> From<CowRcStr<'a>> for DummyAtom { |
3310 | | fn from(string: CowRcStr<'a>) -> Self { |
3311 | | DummyAtom(string.to_string()) |
3312 | | } |
3313 | | } |
3314 | | |
3315 | | impl AsRef<str> for DummyAtom { |
3316 | | fn as_ref(&self) -> &str { |
3317 | | self.0.as_ref() |
3318 | | } |
3319 | | } |
3320 | | |
3321 | | impl<'a> From<std::borrow::Cow<'a, str>> for DummyAtom { |
3322 | | fn from(string: std::borrow::Cow<'a, str>) -> Self { |
3323 | | Self(string.to_string()) |
3324 | | } |
3325 | | } |
3326 | | |
3327 | | impl<'i> Parser<'i> for DummyParser { |
3328 | | type Impl = DummySelectorImpl; |
3329 | | type Error = SelectorParseErrorKind<'i>; |
3330 | | |
3331 | | fn parse_slotted(&self) -> bool { |
3332 | | true |
3333 | | } |
3334 | | |
3335 | | fn parse_is_and_where(&self) -> bool { |
3336 | | true |
3337 | | } |
3338 | | |
3339 | | fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { |
3340 | | ParseErrorRecovery::DiscardList |
3341 | | } |
3342 | | |
3343 | | fn parse_part(&self) -> bool { |
3344 | | true |
3345 | | } |
3346 | | |
3347 | | fn parse_non_ts_pseudo_class( |
3348 | | &self, |
3349 | | location: SourceLocation, |
3350 | | name: CowRcStr<'i>, |
3351 | | ) -> Result<PseudoClass, SelectorParseError<'i>> { |
3352 | | match_ignore_ascii_case! { &name, |
3353 | | "hover" => return Ok(PseudoClass::Hover), |
3354 | | "active" => return Ok(PseudoClass::Active), |
3355 | | _ => {} |
3356 | | } |
3357 | | Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name))) |
3358 | | } |
3359 | | |
3360 | | fn parse_non_ts_functional_pseudo_class<'t>( |
3361 | | &self, |
3362 | | name: CowRcStr<'i>, |
3363 | | parser: &mut CssParser<'i, 't>, |
3364 | | ) -> Result<PseudoClass, SelectorParseError<'i>> { |
3365 | | match_ignore_ascii_case! { &name, |
3366 | | "lang" => { |
3367 | | let lang = parser.expect_ident_or_string()?.as_ref().to_owned(); |
3368 | | return Ok(PseudoClass::Lang(lang)); |
3369 | | }, |
3370 | | _ => {} |
3371 | | } |
3372 | | Err(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name))) |
3373 | | } |
3374 | | |
3375 | | fn parse_pseudo_element( |
3376 | | &self, |
3377 | | location: SourceLocation, |
3378 | | name: CowRcStr<'i>, |
3379 | | ) -> Result<PseudoElement, SelectorParseError<'i>> { |
3380 | | match_ignore_ascii_case! { &name, |
3381 | | "before" => return Ok(PseudoElement::Before), |
3382 | | "after" => return Ok(PseudoElement::After), |
3383 | | _ => {} |
3384 | | } |
3385 | | Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name))) |
3386 | | } |
3387 | | |
3388 | | fn default_namespace(&self) -> Option<DummyAtom> { |
3389 | | self.default_ns.clone() |
3390 | | } |
3391 | | |
3392 | | fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option<DummyAtom> { |
3393 | | self.ns_prefixes.get(prefix).cloned() |
3394 | | } |
3395 | | } |
3396 | | |
3397 | | fn parse<'i>(input: &'i str) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> { |
3398 | | parse_ns(input, &DummyParser::default()) |
3399 | | } |
3400 | | |
3401 | | // fn parse_expected<'i, 'a>( |
3402 | | // input: &'i str, |
3403 | | // expected: Option<&'a str>, |
3404 | | // ) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> { |
3405 | | // parse_ns_expected(input, &DummyParser::default(), expected) |
3406 | | // } |
3407 | | |
3408 | | fn parse_ns<'i>( |
3409 | | input: &'i str, |
3410 | | parser: &DummyParser, |
3411 | | ) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> { |
3412 | | parse_ns_expected(input, parser, None) |
3413 | | } |
3414 | | |
3415 | | fn parse_ns_expected<'i, 'a>( |
3416 | | input: &'i str, |
3417 | | parser: &DummyParser, |
3418 | | expected: Option<&'a str>, |
3419 | | ) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> { |
3420 | | let mut parser_input = ParserInput::new(input); |
3421 | | let result = SelectorList::parse( |
3422 | | parser, |
3423 | | &mut CssParser::new(&mut parser_input), |
3424 | | ParseErrorRecovery::DiscardList, |
3425 | | NestingRequirement::None, |
3426 | | ); |
3427 | | if let Ok(ref selectors) = result { |
3428 | | assert_eq!(selectors.0.len(), 1); |
3429 | | // We can't assume that the serialized parsed selector will equal |
3430 | | // the input; for example, if there is no default namespace, '*|foo' |
3431 | | // should serialize to 'foo'. |
3432 | | assert_eq!( |
3433 | | selectors.0[0].to_css_string(), |
3434 | | match expected { |
3435 | | Some(x) => x, |
3436 | | None => input, |
3437 | | } |
3438 | | ); |
3439 | | } |
3440 | | result |
3441 | | } |
3442 | | |
3443 | | fn specificity(a: u32, b: u32, c: u32) -> u32 { |
3444 | | a << 20 | b << 10 | c |
3445 | | } |
3446 | | |
3447 | | #[test] |
3448 | | fn test_empty() { |
3449 | | let mut input = ParserInput::new(":empty"); |
3450 | | let list = SelectorList::parse( |
3451 | | &DummyParser::default(), |
3452 | | &mut CssParser::new(&mut input), |
3453 | | ParseErrorRecovery::DiscardList, |
3454 | | NestingRequirement::None, |
3455 | | ); |
3456 | | assert!(list.is_ok()); |
3457 | | } |
3458 | | |
3459 | | const MATHML: &str = "http://www.w3.org/1998/Math/MathML"; |
3460 | | const SVG: &str = "http://www.w3.org/2000/svg"; |
3461 | | |
3462 | | #[test] |
3463 | | fn test_parsing() { |
3464 | | assert!(parse("").is_err()); |
3465 | | assert!(parse(":lang(4)").is_err()); |
3466 | | assert!(parse(":lang(en US)").is_err()); |
3467 | | assert_eq!( |
3468 | | parse("EeÉ"), |
3469 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3470 | | vec![Component::LocalName(LocalName { |
3471 | | name: DummyAtom::from("EeÉ"), |
3472 | | lower_name: DummyAtom::from("eeÉ"), |
3473 | | })], |
3474 | | specificity(0, 0, 1), |
3475 | | Default::default(), |
3476 | | )])) |
3477 | | ); |
3478 | | assert_eq!( |
3479 | | parse("|e"), |
3480 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3481 | | vec![ |
3482 | | Component::ExplicitNoNamespace, |
3483 | | Component::LocalName(LocalName { |
3484 | | name: DummyAtom::from("e"), |
3485 | | lower_name: DummyAtom::from("e"), |
3486 | | }), |
3487 | | ], |
3488 | | specificity(0, 0, 1), |
3489 | | Default::default(), |
3490 | | )])) |
3491 | | ); |
3492 | | // When the default namespace is not set, *| should be elided. |
3493 | | // https://github.com/servo/servo/pull/17537 |
3494 | | // assert_eq!( |
3495 | | // parse_expected("*|e", Some("e")), |
3496 | | // Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3497 | | // vec![Component::LocalName(LocalName { |
3498 | | // name: DummyAtom::from("e"), |
3499 | | // lower_name: DummyAtom::from("e"), |
3500 | | // })], |
3501 | | // specificity(0, 0, 1), |
3502 | | // Default::default(), |
3503 | | // )])) |
3504 | | // ); |
3505 | | // When the default namespace is set, *| should _not_ be elided (as foo |
3506 | | // is no longer equivalent to *|foo--the former is only for foo in the |
3507 | | // default namespace). |
3508 | | // https://github.com/servo/servo/issues/16020 |
3509 | | // assert_eq!( |
3510 | | // parse_ns( |
3511 | | // "*|e", |
3512 | | // &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) |
3513 | | // ), |
3514 | | // Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3515 | | // vec![ |
3516 | | // Component::ExplicitAnyNamespace, |
3517 | | // Component::LocalName(LocalName { |
3518 | | // name: DummyAtom::from("e"), |
3519 | | // lower_name: DummyAtom::from("e"), |
3520 | | // }), |
3521 | | // ], |
3522 | | // specificity(0, 0, 1), |
3523 | | // Default::default(), |
3524 | | // )])) |
3525 | | // ); |
3526 | | assert_eq!( |
3527 | | parse("*"), |
3528 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3529 | | vec![Component::ExplicitUniversalType], |
3530 | | specificity(0, 0, 0), |
3531 | | Default::default(), |
3532 | | )])) |
3533 | | ); |
3534 | | assert_eq!( |
3535 | | parse("|*"), |
3536 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3537 | | vec![Component::ExplicitNoNamespace, Component::ExplicitUniversalType,], |
3538 | | specificity(0, 0, 0), |
3539 | | Default::default(), |
3540 | | )])) |
3541 | | ); |
3542 | | // assert_eq!( |
3543 | | // parse_expected("*|*", Some("*")), |
3544 | | // Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3545 | | // vec![Component::ExplicitUniversalType], |
3546 | | // specificity(0, 0, 0), |
3547 | | // Default::default(), |
3548 | | // )])) |
3549 | | // ); |
3550 | | assert_eq!( |
3551 | | parse_ns( |
3552 | | "*|*", |
3553 | | &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) |
3554 | | ), |
3555 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3556 | | vec![Component::ExplicitAnyNamespace, Component::ExplicitUniversalType,], |
3557 | | specificity(0, 0, 0), |
3558 | | Default::default(), |
3559 | | )])) |
3560 | | ); |
3561 | | assert_eq!( |
3562 | | parse(".foo:lang(en-US)"), |
3563 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3564 | | vec![ |
3565 | | Component::Class(DummyAtom::from("foo")), |
3566 | | Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())), |
3567 | | ], |
3568 | | specificity(0, 2, 0), |
3569 | | Default::default(), |
3570 | | )])) |
3571 | | ); |
3572 | | assert_eq!( |
3573 | | parse("#bar"), |
3574 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3575 | | vec![Component::ID(DummyAtom::from("bar"))], |
3576 | | specificity(1, 0, 0), |
3577 | | Default::default(), |
3578 | | )])) |
3579 | | ); |
3580 | | assert_eq!( |
3581 | | parse("e.foo#bar"), |
3582 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3583 | | vec![ |
3584 | | Component::LocalName(LocalName { |
3585 | | name: DummyAtom::from("e"), |
3586 | | lower_name: DummyAtom::from("e"), |
3587 | | }), |
3588 | | Component::Class(DummyAtom::from("foo")), |
3589 | | Component::ID(DummyAtom::from("bar")), |
3590 | | ], |
3591 | | specificity(1, 1, 1), |
3592 | | Default::default(), |
3593 | | )])) |
3594 | | ); |
3595 | | assert_eq!( |
3596 | | parse("e.foo #bar"), |
3597 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3598 | | vec![ |
3599 | | Component::LocalName(LocalName { |
3600 | | name: DummyAtom::from("e"), |
3601 | | lower_name: DummyAtom::from("e"), |
3602 | | }), |
3603 | | Component::Class(DummyAtom::from("foo")), |
3604 | | Component::Combinator(Combinator::Descendant), |
3605 | | Component::ID(DummyAtom::from("bar")), |
3606 | | ], |
3607 | | specificity(1, 1, 1), |
3608 | | Default::default(), |
3609 | | )])) |
3610 | | ); |
3611 | | // Default namespace does not apply to attribute selectors |
3612 | | // https://github.com/mozilla/servo/pull/1652 |
3613 | | let mut parser = DummyParser::default(); |
3614 | | assert_eq!( |
3615 | | parse_ns("[Foo]", &parser), |
3616 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3617 | | vec![Component::AttributeInNoNamespaceExists { |
3618 | | local_name: DummyAtom::from("Foo"), |
3619 | | local_name_lower: DummyAtom::from("foo"), |
3620 | | }], |
3621 | | specificity(0, 1, 0), |
3622 | | Default::default(), |
3623 | | )])) |
3624 | | ); |
3625 | | assert!(parse_ns("svg|circle", &parser).is_err()); |
3626 | | parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); |
3627 | | assert_eq!( |
3628 | | parse_ns("svg|circle", &parser), |
3629 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3630 | | vec![ |
3631 | | Component::Namespace(DummyAtom("svg".into()), SVG.into()), |
3632 | | Component::LocalName(LocalName { |
3633 | | name: DummyAtom::from("circle"), |
3634 | | lower_name: DummyAtom::from("circle"), |
3635 | | }), |
3636 | | ], |
3637 | | specificity(0, 0, 1), |
3638 | | Default::default(), |
3639 | | )])) |
3640 | | ); |
3641 | | assert_eq!( |
3642 | | parse_ns("svg|*", &parser), |
3643 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3644 | | vec![ |
3645 | | Component::Namespace(DummyAtom("svg".into()), SVG.into()), |
3646 | | Component::ExplicitUniversalType, |
3647 | | ], |
3648 | | specificity(0, 0, 0), |
3649 | | Default::default(), |
3650 | | )])) |
3651 | | ); |
3652 | | // Default namespace does not apply to attribute selectors |
3653 | | // https://github.com/mozilla/servo/pull/1652 |
3654 | | // but it does apply to implicit type selectors |
3655 | | // https://github.com/servo/rust-selectors/pull/82 |
3656 | | parser.default_ns = Some(MATHML.into()); |
3657 | | assert_eq!( |
3658 | | parse_ns("[Foo]", &parser), |
3659 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3660 | | vec![ |
3661 | | Component::DefaultNamespace(MATHML.into()), |
3662 | | Component::AttributeInNoNamespaceExists { |
3663 | | local_name: DummyAtom::from("Foo"), |
3664 | | local_name_lower: DummyAtom::from("foo"), |
3665 | | }, |
3666 | | ], |
3667 | | specificity(0, 1, 0), |
3668 | | Default::default(), |
3669 | | )])) |
3670 | | ); |
3671 | | // Default namespace does apply to type selectors |
3672 | | assert_eq!( |
3673 | | parse_ns("e", &parser), |
3674 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3675 | | vec![ |
3676 | | Component::DefaultNamespace(MATHML.into()), |
3677 | | Component::LocalName(LocalName { |
3678 | | name: DummyAtom::from("e"), |
3679 | | lower_name: DummyAtom::from("e"), |
3680 | | }), |
3681 | | ], |
3682 | | specificity(0, 0, 1), |
3683 | | Default::default(), |
3684 | | )])) |
3685 | | ); |
3686 | | assert_eq!( |
3687 | | parse_ns("*", &parser), |
3688 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3689 | | vec![ |
3690 | | Component::DefaultNamespace(MATHML.into()), |
3691 | | Component::ExplicitUniversalType, |
3692 | | ], |
3693 | | specificity(0, 0, 0), |
3694 | | Default::default(), |
3695 | | )])) |
3696 | | ); |
3697 | | assert_eq!( |
3698 | | parse_ns("*|*", &parser), |
3699 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3700 | | vec![Component::ExplicitAnyNamespace, Component::ExplicitUniversalType,], |
3701 | | specificity(0, 0, 0), |
3702 | | Default::default(), |
3703 | | )])) |
3704 | | ); |
3705 | | // Default namespace applies to universal and type selectors inside :not and :matches, |
3706 | | // but not otherwise. |
3707 | | assert_eq!( |
3708 | | parse_ns(":not(.cl)", &parser), |
3709 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3710 | | vec![ |
3711 | | Component::DefaultNamespace(MATHML.into()), |
3712 | | Component::Negation( |
3713 | | vec![Selector::from_vec( |
3714 | | vec![Component::Class(DummyAtom::from("cl"))], |
3715 | | specificity(0, 1, 0), |
3716 | | Default::default(), |
3717 | | )] |
3718 | | .into_boxed_slice() |
3719 | | ), |
3720 | | ], |
3721 | | specificity(0, 1, 0), |
3722 | | Default::default(), |
3723 | | )])) |
3724 | | ); |
3725 | | assert_eq!( |
3726 | | parse_ns(":not(*)", &parser), |
3727 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3728 | | vec![ |
3729 | | Component::DefaultNamespace(MATHML.into()), |
3730 | | Component::Negation( |
3731 | | vec![Selector::from_vec( |
3732 | | vec![ |
3733 | | Component::DefaultNamespace(MATHML.into()), |
3734 | | Component::ExplicitUniversalType, |
3735 | | ], |
3736 | | specificity(0, 0, 0), |
3737 | | Default::default(), |
3738 | | )] |
3739 | | .into_boxed_slice(), |
3740 | | ), |
3741 | | ], |
3742 | | specificity(0, 0, 0), |
3743 | | Default::default(), |
3744 | | )])) |
3745 | | ); |
3746 | | assert_eq!( |
3747 | | parse_ns(":not(e)", &parser), |
3748 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3749 | | vec![ |
3750 | | Component::DefaultNamespace(MATHML.into()), |
3751 | | Component::Negation( |
3752 | | vec![Selector::from_vec( |
3753 | | vec![ |
3754 | | Component::DefaultNamespace(MATHML.into()), |
3755 | | Component::LocalName(LocalName { |
3756 | | name: DummyAtom::from("e"), |
3757 | | lower_name: DummyAtom::from("e"), |
3758 | | }), |
3759 | | ], |
3760 | | specificity(0, 0, 1), |
3761 | | Default::default(), |
3762 | | ),] |
3763 | | .into_boxed_slice() |
3764 | | ), |
3765 | | ], |
3766 | | specificity(0, 0, 1), |
3767 | | Default::default(), |
3768 | | )])) |
3769 | | ); |
3770 | | assert_eq!( |
3771 | | parse("[attr|=\"foo\"]"), |
3772 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3773 | | vec![Component::AttributeInNoNamespace { |
3774 | | local_name: DummyAtom::from("attr"), |
3775 | | operator: AttrSelectorOperator::DashMatch, |
3776 | | value: DummyAttrValue::from("foo"), |
3777 | | never_matches: false, |
3778 | | case_sensitivity: ParsedCaseSensitivity::CaseSensitive, |
3779 | | }], |
3780 | | specificity(0, 1, 0), |
3781 | | Default::default(), |
3782 | | )])) |
3783 | | ); |
3784 | | // https://github.com/mozilla/servo/issues/1723 |
3785 | | assert_eq!( |
3786 | | parse("::before"), |
3787 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3788 | | vec![ |
3789 | | Component::Combinator(Combinator::PseudoElement), |
3790 | | Component::PseudoElement(PseudoElement::Before), |
3791 | | ], |
3792 | | specificity(0, 0, 1), |
3793 | | SelectorFlags::HAS_PSEUDO, |
3794 | | )])) |
3795 | | ); |
3796 | | assert_eq!( |
3797 | | parse("::before:hover"), |
3798 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3799 | | vec![ |
3800 | | Component::Combinator(Combinator::PseudoElement), |
3801 | | Component::PseudoElement(PseudoElement::Before), |
3802 | | Component::NonTSPseudoClass(PseudoClass::Hover), |
3803 | | ], |
3804 | | specificity(0, 1, 1), |
3805 | | SelectorFlags::HAS_PSEUDO, |
3806 | | )])) |
3807 | | ); |
3808 | | assert_eq!( |
3809 | | parse("::before:hover:hover"), |
3810 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3811 | | vec![ |
3812 | | Component::Combinator(Combinator::PseudoElement), |
3813 | | Component::PseudoElement(PseudoElement::Before), |
3814 | | Component::NonTSPseudoClass(PseudoClass::Hover), |
3815 | | Component::NonTSPseudoClass(PseudoClass::Hover), |
3816 | | ], |
3817 | | specificity(0, 2, 1), |
3818 | | SelectorFlags::HAS_PSEUDO, |
3819 | | )])) |
3820 | | ); |
3821 | | assert!(parse("::before:hover:lang(foo)").is_err()); |
3822 | | assert!(parse("::before:hover .foo").is_err()); |
3823 | | assert!(parse("::before .foo").is_err()); |
3824 | | assert!(parse("::before ~ bar").is_err()); |
3825 | | assert!(parse("::before:active").is_ok()); |
3826 | | |
3827 | | // https://github.com/servo/servo/issues/15335 |
3828 | | assert!(parse(":: before").is_err()); |
3829 | | assert_eq!( |
3830 | | parse("div ::after"), |
3831 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3832 | | vec![ |
3833 | | Component::LocalName(LocalName { |
3834 | | name: DummyAtom::from("div"), |
3835 | | lower_name: DummyAtom::from("div"), |
3836 | | }), |
3837 | | Component::Combinator(Combinator::Descendant), |
3838 | | Component::Combinator(Combinator::PseudoElement), |
3839 | | Component::PseudoElement(PseudoElement::After), |
3840 | | ], |
3841 | | specificity(0, 0, 2), |
3842 | | SelectorFlags::HAS_PSEUDO, |
3843 | | )])) |
3844 | | ); |
3845 | | assert_eq!( |
3846 | | parse("#d1 > .ok"), |
3847 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3848 | | vec![ |
3849 | | Component::ID(DummyAtom::from("d1")), |
3850 | | Component::Combinator(Combinator::Child), |
3851 | | Component::Class(DummyAtom::from("ok")), |
3852 | | ], |
3853 | | (1 << 20) + (1 << 10) + (0 << 0), |
3854 | | Default::default(), |
3855 | | )])) |
3856 | | ); |
3857 | | parser.default_ns = None; |
3858 | | assert!(parse(":not(#provel.old)").is_ok()); |
3859 | | assert!(parse(":not(#provel > old)").is_ok()); |
3860 | | assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok()); |
3861 | | // https://github.com/servo/servo/issues/16017 |
3862 | | assert_eq!( |
3863 | | parse_ns(":not(*)", &parser), |
3864 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3865 | | vec![Component::Negation( |
3866 | | vec![Selector::from_vec( |
3867 | | vec![Component::ExplicitUniversalType], |
3868 | | specificity(0, 0, 0), |
3869 | | Default::default(), |
3870 | | )] |
3871 | | .into_boxed_slice() |
3872 | | )], |
3873 | | specificity(0, 0, 0), |
3874 | | Default::default(), |
3875 | | )])) |
3876 | | ); |
3877 | | assert_eq!( |
3878 | | parse_ns(":not(|*)", &parser), |
3879 | | Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3880 | | vec![Component::Negation( |
3881 | | vec![Selector::from_vec( |
3882 | | vec![Component::ExplicitNoNamespace, Component::ExplicitUniversalType,], |
3883 | | specificity(0, 0, 0), |
3884 | | Default::default(), |
3885 | | )] |
3886 | | .into_boxed_slice(), |
3887 | | )], |
3888 | | specificity(0, 0, 0), |
3889 | | Default::default(), |
3890 | | )])) |
3891 | | ); |
3892 | | // *| should be elided if there is no default namespace. |
3893 | | // https://github.com/servo/servo/pull/17537 |
3894 | | // assert_eq!( |
3895 | | // parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")), |
3896 | | // Ok(SelectorList::from_vec(vec![Selector::from_vec( |
3897 | | // vec![Component::Negation( |
3898 | | // vec![Selector::from_vec( |
3899 | | // vec![Component::ExplicitUniversalType], |
3900 | | // specificity(0, 0, 0), |
3901 | | // Default::default() |
3902 | | // )] |
3903 | | // .into_boxed_slice() |
3904 | | // )], |
3905 | | // specificity(0, 0, 0), |
3906 | | // Default::default(), |
3907 | | // )])) |
3908 | | // ); |
3909 | | |
3910 | | assert!(parse("::slotted()").is_err()); |
3911 | | assert!(parse("::slotted(div)").is_ok()); |
3912 | | assert!(parse("::slotted(div).foo").is_err()); |
3913 | | assert!(parse("::slotted(div + bar)").is_err()); |
3914 | | assert!(parse("::slotted(div) + foo").is_err()); |
3915 | | |
3916 | | assert!(parse("::part()").is_err()); |
3917 | | assert!(parse("::part(42)").is_err()); |
3918 | | assert!(parse("::part(foo bar)").is_ok()); |
3919 | | assert!(parse("::part(foo):hover").is_ok()); |
3920 | | assert!(parse("::part(foo) + bar").is_err()); |
3921 | | |
3922 | | assert!(parse("div ::slotted(div)").is_ok()); |
3923 | | assert!(parse("div + slot::slotted(div)").is_ok()); |
3924 | | assert!(parse("div + slot::slotted(div.foo)").is_ok()); |
3925 | | assert!(parse("slot::slotted(div,foo)::first-line").is_err()); |
3926 | | assert!(parse("::slotted(div)::before").is_ok()); |
3927 | | assert!(parse("slot::slotted(div,foo)").is_err()); |
3928 | | |
3929 | | assert!(parse("foo:where()").is_err()); |
3930 | | assert!(parse("foo:where(div, foo, .bar baz)").is_ok()); |
3931 | | assert!(parse("foo:where(::before)").is_err()); |
3932 | | |
3933 | | assert!(parse("foo::details-content").is_ok()); |
3934 | | assert!(parse("foo::target-text").is_ok()); |
3935 | | assert!(parse("foo::search-text").is_ok()); |
3936 | | assert!(parse(":current::search-text").is_ok()); |
3937 | | |
3938 | | assert!(parse("::highlight").is_err()); |
3939 | | assert!(parse("::highlight()").is_err()); |
3940 | | assert!(parse("::highlight(custom-highlight-name)").is_ok()); |
3941 | | |
3942 | | assert!(parse("select::picker").is_err()); |
3943 | | assert!(parse("::picker()").is_err()); |
3944 | | assert!(parse("::picker(select)").is_ok()); |
3945 | | assert!(parse("select::picker-icon").is_ok()); |
3946 | | assert!(parse("option::checkmark").is_ok()); |
3947 | | |
3948 | | assert!(parse("::grammar-error").is_ok()); |
3949 | | assert!(parse("::spelling-error").is_ok()); |
3950 | | assert!(parse("::part(mypart)::grammar-error").is_ok()); |
3951 | | assert!(parse("::part(mypart)::spelling-error").is_ok()); |
3952 | | } |
3953 | | |
3954 | | #[test] |
3955 | | fn test_pseudo_iter() { |
3956 | | let selector = &parse("q::before").unwrap().0[0]; |
3957 | | assert!(!selector.is_universal()); |
3958 | | let mut iter = selector.iter(); |
3959 | | assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); |
3960 | | assert_eq!(iter.next(), None); |
3961 | | let combinator = iter.next_sequence(); |
3962 | | assert_eq!(combinator, Some(Combinator::PseudoElement)); |
3963 | | assert!(matches!(iter.next(), Some(&Component::LocalName(..)))); |
3964 | | assert_eq!(iter.next(), None); |
3965 | | assert_eq!(iter.next_sequence(), None); |
3966 | | } |
3967 | | |
3968 | | #[test] |
3969 | | fn test_universal() { |
3970 | | let selector = &parse_ns( |
3971 | | "*|*::before", |
3972 | | &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")), |
3973 | | ) |
3974 | | .unwrap() |
3975 | | .0[0]; |
3976 | | assert!(selector.is_universal()); |
3977 | | } |
3978 | | |
3979 | | #[test] |
3980 | | fn test_empty_pseudo_iter() { |
3981 | | let selector = &parse("::before").unwrap().0[0]; |
3982 | | assert!(selector.is_universal()); |
3983 | | let mut iter = selector.iter(); |
3984 | | assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); |
3985 | | assert_eq!(iter.next(), None); |
3986 | | assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement)); |
3987 | | assert_eq!(iter.next(), None); |
3988 | | assert_eq!(iter.next_sequence(), None); |
3989 | | } |
3990 | | |
3991 | | struct TestVisitor { |
3992 | | seen: Vec<String>, |
3993 | | } |
3994 | | |
3995 | | impl<'i> SelectorVisitor<'i> for TestVisitor { |
3996 | | type Impl = DummySelectorImpl; |
3997 | | |
3998 | | fn visit_simple_selector(&mut self, s: &Component<DummySelectorImpl>) -> bool { |
3999 | | let mut dest = String::new(); |
4000 | | s.to_css(&mut dest).unwrap(); |
4001 | | self.seen.push(dest); |
4002 | | true |
4003 | | } |
4004 | | } |
4005 | | |
4006 | | #[test] |
4007 | | fn visitor() { |
4008 | | let mut test_visitor = TestVisitor { seen: vec![] }; |
4009 | | parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor); |
4010 | | assert!(test_visitor.seen.contains(&":hover".into())); |
4011 | | |
4012 | | let mut test_visitor = TestVisitor { seen: vec![] }; |
4013 | | parse("::before:hover").unwrap().0[0].visit(&mut test_visitor); |
4014 | | assert!(test_visitor.seen.contains(&":hover".into())); |
4015 | | } |
4016 | | } |