selector_parser.rs (23811B)
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 //! Gecko-specific bits for selector-parsing. 6 7 use crate::computed_value_flags::ComputedValueFlags; 8 use crate::derives::*; 9 use crate::invalidation::element::document_state::InvalidationMatchingData; 10 use crate::properties::ComputedValues; 11 use crate::selector_parser::{Direction, HorizontalDirection, SelectorParser}; 12 use crate::str::starts_with_ignore_ascii_case; 13 use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; 14 use crate::values::{AtomIdent, AtomString, CSSInteger, CustomIdent}; 15 use cssparser::{match_ignore_ascii_case, CowRcStr, SourceLocation, ToCss, Token}; 16 use cssparser::{BasicParseError, BasicParseErrorKind, Parser}; 17 use dom::{DocumentState, ElementState, HEADING_LEVEL_OFFSET}; 18 use selectors::parser::SelectorParseErrorKind; 19 use std::fmt; 20 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss as ToCss_}; 21 use thin_vec::ThinVec; 22 23 pub use crate::gecko::pseudo_element::{ 24 PseudoElement, Target, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT, PSEUDO_COUNT, 25 }; 26 pub use crate::gecko::snapshot::SnapshotMap; 27 28 bitflags! { 29 // See NonTSPseudoClass::is_enabled_in() 30 #[derive(Copy, Clone)] 31 struct NonTSPseudoClassFlag: u8 { 32 const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS = 1 << 0; 33 const PSEUDO_CLASS_ENABLED_IN_CHROME = 1 << 1; 34 const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME = 35 NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS.bits() | 36 NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME.bits(); 37 } 38 } 39 40 /// The type used to store the language argument to the `:lang` pseudo-class. 41 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)] 42 #[css(comma)] 43 pub struct Lang(#[css(iterable)] pub ThinVec<AtomIdent>); 44 45 /// The type used to store the state argument to the `:state` pseudo-class. 46 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)] 47 pub struct CustomState(pub AtomIdent); 48 49 /// The properties that comprise a :heading() pseudoclass (e.g. a list of An+Bs). 50 /// https://drafts.csswg.org/selectors-5/#headings 51 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] 52 pub struct HeadingSelectorData(pub ThinVec<CSSInteger>); 53 54 impl HeadingSelectorData { 55 /// Matches the heading level from the given state against the list of 56 /// heading level selectors. If AnPlusBs intersect with the level packed in 57 /// ElementState then this will return true. 58 pub fn matches_state(&self, state: ElementState) -> bool { 59 let bits = state.intersection(ElementState::HEADING_LEVEL_BITS).bits(); 60 // If none of the HEADING_LEVEL_BITS are set on ElementState, 61 // then this is not a heading level, so return false. 62 if bits == 0 { 63 return false; 64 } 65 // :heading selector will provide an empty levels list. It matches against any 66 // heading level, so we can return true here. 67 if self.0.is_empty() { 68 return true; 69 } 70 let level = (bits >> HEADING_LEVEL_OFFSET) as i32; 71 debug_assert!(level > 0 && level < 16); 72 self.0.iter().any(|item| *item == level) 73 } 74 } 75 76 macro_rules! pseudo_class_name { 77 ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { 78 /// Our representation of a non tree-structural pseudo-class. 79 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] 80 pub enum NonTSPseudoClass { 81 $( 82 #[doc = $css] 83 $name, 84 )* 85 /// The `:lang` pseudo-class. 86 Lang(Lang), 87 /// The `:dir` pseudo-class. 88 Dir(Direction), 89 /// The :state` pseudo-class. 90 CustomState(CustomState), 91 /// The `:heading` & `:heading()` pseudo-classes. 92 Heading(HeadingSelectorData), 93 /// The :active-view-transition-type() pseudo-class: 94 /// https://drafts.csswg.org/css-view-transitions-2/#the-active-view-transition-type-pseudo 95 ActiveViewTransitionType(ThinVec<CustomIdent>), 96 /// The non-standard `:-moz-locale-dir` pseudo-class. 97 MozLocaleDir(Direction), 98 } 99 } 100 } 101 apply_non_ts_list!(pseudo_class_name); 102 103 impl ToCss for NonTSPseudoClass { 104 fn to_css<W>(&self, dest: &mut W) -> fmt::Result 105 where 106 W: fmt::Write, 107 { 108 macro_rules! pseudo_class_serialize { 109 ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { 110 match *self { 111 $(NonTSPseudoClass::$name => concat!(":", $css),)* 112 NonTSPseudoClass::Lang(ref lang) => { 113 dest.write_str(":lang(")?; 114 lang.to_css(&mut CssWriter::new(dest))?; 115 return dest.write_char(')'); 116 }, 117 NonTSPseudoClass::CustomState(ref state) => { 118 dest.write_str(":state(")?; 119 state.to_css(&mut CssWriter::new(dest))?; 120 return dest.write_char(')'); 121 }, 122 NonTSPseudoClass::MozLocaleDir(ref dir) => { 123 dest.write_str(":-moz-locale-dir(")?; 124 dir.to_css(&mut CssWriter::new(dest))?; 125 return dest.write_char(')') 126 }, 127 NonTSPseudoClass::Dir(ref dir) => { 128 dest.write_str(":dir(")?; 129 dir.to_css(&mut CssWriter::new(dest))?; 130 return dest.write_char(')') 131 }, 132 NonTSPseudoClass::ActiveViewTransitionType(ref types) => { 133 dest.write_str(":active-view-transition-type(")?; 134 let mut first = true; 135 for ty in types.iter() { 136 if !first { 137 dest.write_str(", ")?; 138 } 139 first = false; 140 ty.to_css(&mut CssWriter::new(dest))?; 141 } 142 return dest.write_char(')') 143 }, 144 NonTSPseudoClass::Heading(ref levels) => { 145 dest.write_str(":heading")?; 146 if levels.0.is_empty() { 147 return Ok(()); 148 } 149 dest.write_str("(")?; 150 let mut first = true; 151 for item in levels.0.iter() { 152 if !first { 153 dest.write_str(", ")?; 154 } 155 first = false; 156 ToCss::to_css(item, dest)?; 157 } 158 return dest.write_str(")"); 159 }, 160 } 161 } 162 } 163 let ser = apply_non_ts_list!(pseudo_class_serialize); 164 dest.write_str(ser) 165 } 166 } 167 168 impl NonTSPseudoClass { 169 /// Parses the name and returns a non-ts-pseudo-class if succeeds. 170 /// None otherwise. It doesn't check whether the pseudo-class is enabled 171 /// in a particular state. 172 pub fn parse_non_functional(name: &str) -> Option<Self> { 173 macro_rules! pseudo_class_parse { 174 ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { 175 match_ignore_ascii_case! { &name, 176 $($css => Some(NonTSPseudoClass::$name),)* 177 "heading" => Some(NonTSPseudoClass::Heading(HeadingSelectorData([].into()))), 178 "-moz-full-screen" => Some(NonTSPseudoClass::Fullscreen), 179 "-moz-read-only" => Some(NonTSPseudoClass::ReadOnly), 180 "-moz-read-write" => Some(NonTSPseudoClass::ReadWrite), 181 "-moz-focusring" => Some(NonTSPseudoClass::FocusVisible), 182 "-moz-ui-valid" => Some(NonTSPseudoClass::UserValid), 183 "-moz-ui-invalid" => Some(NonTSPseudoClass::UserInvalid), 184 "-webkit-autofill" => Some(NonTSPseudoClass::Autofill), 185 _ => None, 186 } 187 } 188 } 189 apply_non_ts_list!(pseudo_class_parse) 190 } 191 192 /// Returns true if this pseudo-class has any of the given flags set. 193 fn has_any_flag(&self, flags: NonTSPseudoClassFlag) -> bool { 194 macro_rules! check_flag { 195 (_) => { 196 false 197 }; 198 ($flags:ident) => { 199 NonTSPseudoClassFlag::$flags.intersects(flags) 200 }; 201 } 202 macro_rules! pseudo_class_check_is_enabled_in { 203 ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { 204 match *self { 205 $(NonTSPseudoClass::$name => check_flag!($flags),)* 206 NonTSPseudoClass::MozLocaleDir(_) => check_flag!(PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), 207 NonTSPseudoClass::CustomState(_) | 208 NonTSPseudoClass::Heading(_) | 209 NonTSPseudoClass::Lang(_) | 210 NonTSPseudoClass::ActiveViewTransitionType(_) | 211 NonTSPseudoClass::Dir(_) => false, 212 } 213 } 214 } 215 apply_non_ts_list!(pseudo_class_check_is_enabled_in) 216 } 217 218 /// Returns whether the pseudo-class is enabled in content sheets. 219 #[inline] 220 fn is_enabled_in_content(&self) -> bool { 221 if matches!( 222 *self, 223 Self::ActiveViewTransition | Self::ActiveViewTransitionType(..) 224 ) { 225 return static_prefs::pref!("dom.viewTransitions.enabled"); 226 } 227 if matches!(*self, Self::Heading(..)) { 228 return static_prefs::pref!("layout.css.heading-selector.enabled"); 229 } 230 !self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME) 231 } 232 233 /// Get the state flag associated with a pseudo-class, if any. 234 pub fn state_flag(&self) -> ElementState { 235 macro_rules! flag { 236 (_) => { 237 ElementState::empty() 238 }; 239 ($state:ident) => { 240 ElementState::$state 241 }; 242 } 243 macro_rules! pseudo_class_state { 244 ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { 245 match *self { 246 $(NonTSPseudoClass::$name => flag!($state),)* 247 NonTSPseudoClass::Dir(ref dir) => dir.element_state(), 248 NonTSPseudoClass::Heading(..) => ElementState::HEADING_LEVEL_BITS, 249 NonTSPseudoClass::ActiveViewTransitionType(..) => ElementState::ACTIVE_VIEW_TRANSITION, 250 NonTSPseudoClass::MozLocaleDir(..) | 251 NonTSPseudoClass::CustomState(..) | 252 NonTSPseudoClass::Lang(..) => ElementState::empty(), 253 } 254 } 255 } 256 apply_non_ts_list!(pseudo_class_state) 257 } 258 259 /// Get the document state flag associated with a pseudo-class, if any. 260 pub fn document_state_flag(&self) -> DocumentState { 261 match *self { 262 NonTSPseudoClass::MozLocaleDir(ref dir) => match dir.as_horizontal_direction() { 263 Some(HorizontalDirection::Ltr) => DocumentState::LTR_LOCALE, 264 Some(HorizontalDirection::Rtl) => DocumentState::RTL_LOCALE, 265 None => DocumentState::empty(), 266 }, 267 NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, 268 _ => DocumentState::empty(), 269 } 270 } 271 272 /// Returns true if the given pseudoclass should trigger style sharing cache 273 /// revalidation. 274 pub fn needs_cache_revalidation(&self) -> bool { 275 self.state_flag().is_empty() 276 && !matches!( 277 *self, 278 // :dir() depends on state only, but may have an empty state_flag for invalid 279 // arguments. 280 NonTSPseudoClass::Dir(_) | 281 // We prevent style sharing for NAC. 282 NonTSPseudoClass::MozNativeAnonymous | 283 // :-moz-placeholder is parsed but never matches. 284 NonTSPseudoClass::MozPlaceholder | 285 // :-moz-is-html, :-moz-locale-dir and :-moz-window-inactive 286 // depend only on the state of the document, which is invariant across all 287 // elements involved in a given style cache. 288 NonTSPseudoClass::MozIsHTML | 289 NonTSPseudoClass::MozLocaleDir(_) | 290 NonTSPseudoClass::MozWindowInactive 291 ) 292 } 293 } 294 295 impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass { 296 type Impl = SelectorImpl; 297 298 #[inline] 299 fn is_active_or_hover(&self) -> bool { 300 matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover) 301 } 302 303 /// We intentionally skip the link-related ones. 304 #[inline] 305 fn is_user_action_state(&self) -> bool { 306 matches!( 307 *self, 308 NonTSPseudoClass::Hover | NonTSPseudoClass::Active | NonTSPseudoClass::Focus 309 ) 310 } 311 } 312 313 /// The dummy struct we use to implement our selector parsing. 314 #[derive(Clone, Debug, Eq, PartialEq)] 315 pub struct SelectorImpl; 316 317 /// A set of extra data to carry along with the matching context, either for 318 /// selector-matching or invalidation. 319 #[derive(Default)] 320 pub struct ExtraMatchingData<'a> { 321 /// The invalidation data to invalidate doc-state pseudo-classes correctly. 322 pub invalidation_data: InvalidationMatchingData, 323 324 /// The invalidation bits from matching container queries. These are here 325 /// just for convenience mostly. 326 pub cascade_input_flags: ComputedValueFlags, 327 328 /// The style of the originating element in order to evaluate @container 329 /// size queries affecting pseudo-elements. 330 pub originating_element_style: Option<&'a ComputedValues>, 331 } 332 333 impl ::selectors::SelectorImpl for SelectorImpl { 334 type ExtraMatchingData<'a> = ExtraMatchingData<'a>; 335 type AttrValue = AtomString; 336 type Identifier = AtomIdent; 337 type LocalName = AtomIdent; 338 type NamespacePrefix = AtomIdent; 339 type NamespaceUrl = Namespace; 340 type BorrowedNamespaceUrl = WeakNamespace; 341 type BorrowedLocalName = WeakAtom; 342 343 type PseudoElement = PseudoElement; 344 type NonTSPseudoClass = NonTSPseudoClass; 345 346 fn should_collect_attr_hash(name: &AtomIdent) -> bool { 347 !crate::bloom::is_attr_name_excluded_from_filter(name) 348 } 349 } 350 351 impl<'a> SelectorParser<'a> { 352 fn is_pseudo_class_enabled(&self, pseudo_class: &NonTSPseudoClass) -> bool { 353 if pseudo_class.is_enabled_in_content() { 354 return true; 355 } 356 357 if self.in_user_agent_stylesheet() 358 && pseudo_class.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS) 359 { 360 return true; 361 } 362 363 if self.chrome_rules_enabled() 364 && pseudo_class.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME) 365 { 366 return true; 367 } 368 369 if matches!(*pseudo_class, NonTSPseudoClass::MozBroken) { 370 return static_prefs::pref!("layout.css.moz-broken.content.enabled"); 371 } 372 373 return false; 374 } 375 376 fn is_pseudo_element_enabled(&self, pseudo_element: &PseudoElement) -> bool { 377 if pseudo_element.enabled_in_content() { 378 return true; 379 } 380 381 if self.in_user_agent_stylesheet() && pseudo_element.enabled_in_ua_sheets() { 382 return true; 383 } 384 385 if self.chrome_rules_enabled() && pseudo_element.enabled_in_chrome() { 386 return true; 387 } 388 389 return false; 390 } 391 } 392 393 /// Parse the functional pseudo-element with the function name. 394 pub fn parse_functional_pseudo_element_with_name<'i, 't>( 395 name: CowRcStr<'i>, 396 parser: &mut Parser<'i, 't>, 397 target: Target, 398 ) -> Result<PseudoElement, ParseError<'i>> { 399 use crate::gecko::pseudo_element::PtNameAndClassSelector; 400 401 if matches!(target, Target::Selector) && starts_with_ignore_ascii_case(&name, "-moz-tree-") { 402 // Tree pseudo-elements can have zero or more arguments, separated 403 // by either comma or space. 404 let mut args = ThinVec::new(); 405 loop { 406 let location = parser.current_source_location(); 407 match parser.next() { 408 Ok(&Token::Ident(ref ident)) => args.push(Atom::from(ident.as_ref())), 409 Ok(&Token::Comma) => {}, 410 Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), 411 Err(BasicParseError { 412 kind: BasicParseErrorKind::EndOfInput, 413 .. 414 }) => break, 415 _ => unreachable!("Parser::next() shouldn't return any other error"), 416 } 417 } 418 return PseudoElement::tree_pseudo_element(&name, args).ok_or(parser.new_custom_error( 419 SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name), 420 )); 421 } 422 423 Ok(match_ignore_ascii_case! { &name, 424 "highlight" => { 425 PseudoElement::Highlight(AtomIdent::from(parser.expect_ident()?.as_ref())) 426 }, 427 "view-transition-group" => { 428 PseudoElement::ViewTransitionGroup(PtNameAndClassSelector::parse(parser, target)?) 429 }, 430 "view-transition-image-pair" => { 431 PseudoElement::ViewTransitionImagePair(PtNameAndClassSelector::parse(parser, target)?) 432 }, 433 "view-transition-old" => { 434 PseudoElement::ViewTransitionOld(PtNameAndClassSelector::parse(parser, target)?) 435 }, 436 "view-transition-new" => { 437 PseudoElement::ViewTransitionNew(PtNameAndClassSelector::parse(parser, target)?) 438 }, 439 _ => return Err(parser.new_custom_error( 440 SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name) 441 )) 442 }) 443 } 444 445 impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { 446 type Impl = SelectorImpl; 447 type Error = StyleParseErrorKind<'i>; 448 449 #[inline] 450 fn parse_parent_selector(&self) -> bool { 451 true 452 } 453 454 #[inline] 455 fn parse_slotted(&self) -> bool { 456 true 457 } 458 459 #[inline] 460 fn parse_host(&self) -> bool { 461 true 462 } 463 464 #[inline] 465 fn parse_nth_child_of(&self) -> bool { 466 true 467 } 468 469 #[inline] 470 fn parse_is_and_where(&self) -> bool { 471 true 472 } 473 474 #[inline] 475 fn parse_has(&self) -> bool { 476 true 477 } 478 479 #[inline] 480 fn parse_part(&self) -> bool { 481 true 482 } 483 484 #[inline] 485 fn is_is_alias(&self, function: &str) -> bool { 486 function.eq_ignore_ascii_case("-moz-any") 487 } 488 489 #[inline] 490 fn allow_forgiving_selectors(&self) -> bool { 491 !self.for_supports_rule 492 } 493 494 fn parse_non_ts_pseudo_class( 495 &self, 496 location: SourceLocation, 497 name: CowRcStr<'i>, 498 ) -> Result<NonTSPseudoClass, ParseError<'i>> { 499 if let Some(pseudo_class) = NonTSPseudoClass::parse_non_functional(&name) { 500 if self.is_pseudo_class_enabled(&pseudo_class) { 501 return Ok(pseudo_class); 502 } 503 } 504 Err( 505 location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( 506 name, 507 )), 508 ) 509 } 510 511 fn parse_non_ts_functional_pseudo_class<'t>( 512 &self, 513 name: CowRcStr<'i>, 514 parser: &mut Parser<'i, 't>, 515 _after_part: bool, 516 ) -> Result<NonTSPseudoClass, ParseError<'i>> { 517 let pseudo_class = match_ignore_ascii_case! { &name, 518 "lang" => { 519 let result = parser.parse_comma_separated(|input| { 520 Ok(AtomIdent::from(input.expect_ident_or_string()?.as_ref())) 521 })?; 522 if result.is_empty() { 523 return Err(parser.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 524 } 525 NonTSPseudoClass::Lang(Lang(result.into())) 526 }, 527 "state" => { 528 let result = AtomIdent::from(parser.expect_ident()?.as_ref()); 529 NonTSPseudoClass::CustomState(CustomState(result)) 530 }, 531 "heading" => { 532 let result = parser.parse_comma_separated(|input| Ok(input.expect_integer()?))?; 533 if result.is_empty() { 534 return Err(parser.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 535 } 536 NonTSPseudoClass::Heading(HeadingSelectorData(result.into())) 537 }, 538 "active-view-transition-type" => { 539 let result = parser.parse_comma_separated(|input| CustomIdent::parse(input, &[]))?; 540 if result.is_empty() { 541 return Err(parser.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 542 } 543 NonTSPseudoClass::ActiveViewTransitionType(result.into()) 544 }, 545 "-moz-locale-dir" => { 546 NonTSPseudoClass::MozLocaleDir(Direction::parse(parser)?) 547 }, 548 "dir" => { 549 NonTSPseudoClass::Dir(Direction::parse(parser)?) 550 }, 551 _ => return Err(parser.new_custom_error( 552 SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone()) 553 )) 554 }; 555 if self.is_pseudo_class_enabled(&pseudo_class) { 556 Ok(pseudo_class) 557 } else { 558 Err( 559 parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( 560 name, 561 )), 562 ) 563 } 564 } 565 566 fn parse_pseudo_element( 567 &self, 568 location: SourceLocation, 569 name: CowRcStr<'i>, 570 ) -> Result<PseudoElement, ParseError<'i>> { 571 let allow_unkown_webkit = !self.for_supports_rule; 572 if let Some(pseudo) = PseudoElement::from_slice(&name, allow_unkown_webkit) { 573 if self.is_pseudo_element_enabled(&pseudo) { 574 return Ok(pseudo); 575 } 576 } 577 578 Err( 579 location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( 580 name, 581 )), 582 ) 583 } 584 585 fn parse_functional_pseudo_element<'t>( 586 &self, 587 name: CowRcStr<'i>, 588 parser: &mut Parser<'i, 't>, 589 ) -> Result<PseudoElement, ParseError<'i>> { 590 let pseudo = 591 parse_functional_pseudo_element_with_name(name.clone(), parser, Target::Selector)?; 592 if self.is_pseudo_element_enabled(&pseudo) { 593 return Ok(pseudo); 594 } 595 596 Err( 597 parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( 598 name, 599 )), 600 ) 601 } 602 603 fn default_namespace(&self) -> Option<Namespace> { 604 self.namespaces.default.clone() 605 } 606 607 fn namespace_for_prefix(&self, prefix: &AtomIdent) -> Option<Namespace> { 608 self.namespaces.prefixes.get(prefix).cloned() 609 } 610 } 611 612 impl SelectorImpl { 613 /// A helper to traverse each eagerly cascaded pseudo-element, executing 614 /// `fun` on it. 615 #[inline] 616 pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F) 617 where 618 F: FnMut(PseudoElement), 619 { 620 for pseudo in &EAGER_PSEUDOS { 621 fun(pseudo.clone()) 622 } 623 } 624 } 625 626 // Selector and component sizes are important for matching performance. 627 size_of_test!(selectors::parser::Selector<SelectorImpl>, 8); 628 size_of_test!(selectors::parser::Component<SelectorImpl>, 24); 629 size_of_test!(PseudoElement, 16); 630 size_of_test!(NonTSPseudoClass, 16);