/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Gecko's definition of a pseudo-element. //! //! Note that a few autogenerated bits of this live in //! `pseudo_element_definition.mako.rs`. If you touch that file, you probably //! need to update the checked-in files for Servo.
usecrate::gecko_bindings::structs::PseudoStyleType; usecrate::properties::longhands::display::computed_value::T as Display; usecrate::properties::{ComputedValues, PropertyFlags}; usecrate::selector_parser::{PseudoElementCascadeType, SelectorImpl}; usecrate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; usecrate::string_cache::Atom; usecrate::values::serialize_atom_identifier; usecrate::values::AtomIdent; use cssparser::{Parser, ToCss}; use selectors::parser::PseudoElement as PseudoElementTrait; use static_prefs::pref; use std::fmt; use style_traits::ParseError;
bitflags! { /// Various pseudo-element flags, see pseudo_elements.toml and anonymous_boxes.toml for the /// meaning. #[derive(Clone, Copy, Default)] pubstruct PseudoStyleTypeFlags : u16 { /// No flags const NONE = 0; /// Whether we're enabled in UA sheets. const ENABLED_IN_UA = 1 << 0; /// Whether we're enabled in chrome sheets. const ENABLED_IN_CHROME = 1 << 1; /// Whether we're enabled by a pref. const ENABLED_BY_PREF = 1 << 2; /// Whether we're an anonymous box. const IS_PSEUDO_ELEMENT = 1 << 3; /// Whether we're a CSS2 pseudo-element. const IS_CSS2 = 1 << 4; /// Whether we're an eagerly-cascaded pseudo-element. const IS_EAGER = 1 << 5; /// Whether we can be created by JS const IS_JS_CREATED_NAC = 1 << 6; /// Whether we can be a flex or grid item. const IS_FLEX_OR_GRID_ITEM = 1 << 7; /// Whether we're backed by a real element. const IS_ELEMENT_BACKED = 1 << 8; /// Whether we're a tree-abiding pseudo as per /// https://drafts.csswg.org/css-pseudo-4/#treelike const IS_TREE_ABIDING = 1 << 9; /// Whether we support user-action state pseudo-classes after the pseudo-element. const SUPPORTS_USER_ACTION_STATE = 1 << 10; /// Whether we are an inheriting anon-box. const IS_INHERITING_ANON_BOX = 1 << 11; /// Whether we are a non-inheriting anon box. const IS_NON_INHERITING_ANON_BOX = 1 << 12; /// Combo of the above to cover all anon boxes. const IS_ANON_BOX = Self::IS_INHERITING_ANON_BOX.bits() | Self::IS_NON_INHERITING_ANON_BOX.bits(); /// Whether we're a wrapping anon box. const IS_WRAPPER_ANON_BOX = 1 << 13; /// Whether we parse as an element-backed pseudo-element. const PARSES_AS_ELEMENT_BACKED = 1 << 14;
}
}
/// The target we are using for parsing pseudo-elements. pubenum Target { /// When parsing a selector, we want to use the full syntax.
Selector, /// When parsing the pseudo-element string (from CSSOM), we only accept CusomIdent for named /// view transition pseudo-elements.
Cssom,
}
/// The type to hold the value of `<pt-name-and-class-selector>`. /// /// `<pt-name-and-class-selector> = <pt-name-selector> <pt-class-selector>? | <pt-class-selector>` /// `<pt-name-selector> = '*' | <custom-ident>` /// `<pt-class-selector> = ['.' <custom-ident>]+` /// /// This type should have at least one element. /// If there is no <pt-name-selector>, the first element would be the universal symbol, i.e. '*'. /// In other words, when we match it, ".abc" is the same as "*.abc". /// Note that we also serialize ".abc" as "*.abc". /// /// We use a single ThinVec<> to represent this structure to avoid allocating too much memory for a /// single selectors::parser::Component (max: 24 bytes) and PseudoElement (max: 16 bytes). /// /// https://drafts.csswg.org/css-view-transitions-2/#typedef-pt-name-and-class-selector #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] pubstruct PtNameAndClassSelector(thin_vec::ThinVec<Atom>);
impl PtNameAndClassSelector { /// Constructs a new one from a name. pubfn from_name(name: Atom) -> Self { Self(thin_vec::thin_vec![name])
}
/// Returns the name component. pubfn name(&self) -> &Atom {
debug_assert!(!self.0.is_empty()); self.0.first().expect("Shouldn't be empty")
}
/// Returns the vector we store. pubfn name_and_classes(&self) -> &thin_vec::ThinVec<Atom> {
&self.0
}
/// Parse the pseudo-element tree name and/or class. /// |for_selector| is true if we are parsing the CSS selectors and so need to check the /// universal symbol, i.e. '*', and classes. // Note: We share the same type for both pseudo-element and pseudo-element selector. The // universal symbol (i.e. '*') and `<pt-class-selector>` are used only in the selector (for // matching). pubfn parse<'i, 't>(
input: &mut Parser<'i, 't>,
target: Target,
) -> Result<Self, ParseError<'i>> { usecrate::values::CustomIdent; use cssparser::Token; use style_traits::StyleParseErrorKind;
// <pt-name-selector> = '*' | <custom-ident> let parse_pt_name = |input: &mut Parser<'i, '_>| { // For pseudo-element string, we don't accept '*'. if matches!(target, Target::Selector)
&& input.try_parse(|i| i.expect_delim('*')).is_ok()
{
Ok(atom!("*"))
} else {
CustomIdent::parse(input, &[]).map(|c| c.0)
}
}; let name = input.try_parse(parse_pt_name);
// Skip <pt-class-selector> for pseudo-element string. if matches!(target, Target::Cssom) { return name.map(Self::from_name);
}
// <pt-class-selector> = ['.' <custom-ident>]+ let parse_pt_class = |input: &mut Parser<'i, '_>| { // The white space is forbidden: // 1. Between <pt-name-selector> and <pt-class-selector> // 2. Between any of the components of <pt-class-selector>. let location = input.current_source_location(); match input.next_including_whitespace()? {
Token::Delim('.') => (),
t => return Err(location.new_unexpected_token_error(t.clone())),
} // Whitespace is not allowed between '.' and the class name. iflet Ok(token) = input.try_parse(|i| i.expect_whitespace()) { return Err(input.new_unexpected_token_error(Token::WhiteSpace(token)));
}
CustomIdent::parse(input, &[]).map(|c| c.0)
}; // If there is no `<pt-name-selector>`, it's fine to have whitespaces before the first '.'. if name.is_err() {
input.skip_whitespace();
} letmut classes = thin_vec::ThinVec::new(); whilelet Ok(class) = input.try_parse(parse_pt_class) {
classes.push(class);
}
// If we don't have `<pt-name-selector>`, we must have `<pt-class-selector>`, per the // syntax: `<pt-name-selector> <pt-class-selector>? | <pt-class-selector>`. if name.is_err() && classes.is_empty() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
// Use the universal symbol as the first element to present the part of // `<pt-name-selector>` because they are equivalent (and the serialization is the same). letmut result = thin_vec::thin_vec![name.unwrap_or(atom!("*"))];
result.append(&mut classes);
Ok(Self(result))
}
}
impl ToCss for PtNameAndClassSelector { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where
W: fmt::Write,
{ let name = self.name(); if name == &atom!("*") { // serialize_atom_identifier() may serialize "*" as "\*", so we handle it separately.
dest.write_char('*')?;
} else {
serialize_atom_identifier(name, dest)?;
}
for class inself.classes() {
dest.write_char('.')?;
serialize_atom_identifier(class, dest)?;
}
Ok(())
}
}
impl PseudoElementTrait for PseudoElement { typeImpl = SelectorImpl;
#[inline] fn accepts_state_pseudo_classes(&self) -> bool { // Note: if the pseudo element is a descendants of a pseudo element, `only-child` should be // allowed after it. self.supports_user_action_state() || self.is_in_pseudo_element_tree()
}
#[inline] fn is_in_pseudo_element_tree(&self) -> bool { // All the named view transition pseudo-elements are the descendants of a pseudo-element // root. self.is_named_view_transition()
}
/// Whether the current pseudo element is ::before or ::after. #[inline] fn is_before_or_after(&self) -> bool {
matches!(*self, PseudoElement::Before | PseudoElement::After)
}
}
impl PseudoElement { /// Whether this pseudo-element is "element-backed", which means that it inherits from its regular /// flat tree parent, which might not be the originating element. #[inline] pubfn is_element_backed(&self) -> bool { self.flags()
.intersects(PseudoStyleTypeFlags::IS_ELEMENT_BACKED)
}
/// Returns the kind of cascade type that a given pseudo is going to use. /// /// In Gecko we only compute ::before and ::after eagerly. We save the rules /// for anonymous boxes separately, so we resolve them as precomputed /// pseudos. /// /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`. pubfn cascade_type(&self) -> PseudoElementCascadeType { ifself.is_eager() {
debug_assert!(!self.is_anon_box()); return PseudoElementCascadeType::Eager;
}
/// Whether this pseudo-element is an unknown Webkit-prefixed pseudo-element. #[inline] pubfn is_unknown_webkit_pseudo_element(&self) -> bool {
matches!(*self, PseudoElement::UnknownWebkit(..))
}
/// Whether this pseudo-element is an anonymous box. #[inline] pubfn is_anon_box(&self) -> bool { self.flags().intersects(PseudoStyleTypeFlags::IS_ANON_BOX)
}
/// Whether this pseudo-element is eagerly-cascaded. #[inline] pubfn is_eager(&self) -> bool { self.flags().intersects(PseudoStyleTypeFlags::IS_EAGER)
}
/// Gets the canonical index of this eagerly-cascaded pseudo-element. #[inline] pubfn eager_index(&self) -> usize {
EAGER_PSEUDOS
.iter()
.position(|p| p == self)
.expect("Not an eager pseudo")
}
/// Creates a pseudo-element from an eager index. #[inline] pubfn from_eager_index(i: usize) -> Self {
EAGER_PSEUDOS[i].clone()
}
/// Whether animations for the current pseudo element are stored in the /// parent element. #[inline] pubfn animations_stored_in_parent(&self) -> bool {
matches!(
*self, Self::Before | Self::After | Self::Marker | Self::Backdrop
)
}
/// Whether this pseudo-element is the ::before pseudo. #[inline] pubfn is_before(&self) -> bool {
*self == PseudoElement::Before
}
/// Whether this pseudo-element is the ::after pseudo. #[inline] pubfn is_after(&self) -> bool {
*self == PseudoElement::After
}
/// Whether this pseudo-element is the ::marker pseudo. #[inline] pubfn is_marker(&self) -> bool {
*self == PseudoElement::Marker
}
/// Whether this pseudo-element is the ::selection pseudo. #[inline] pubfn is_selection(&self) -> bool {
*self == PseudoElement::Selection
}
/// Whether this pseudo-element is ::first-letter. #[inline] pubfn is_first_letter(&self) -> bool {
*self == PseudoElement::FirstLetter
}
/// Whether this pseudo-element is ::first-line. #[inline] pubfn is_first_line(&self) -> bool {
*self == PseudoElement::FirstLine
}
/// Whether this pseudo-element is lazily-cascaded. #[inline] pubfn is_lazy(&self) -> bool {
!self.is_eager() && !self.is_precomputed()
}
/// The identifier of the highlight this pseudo-element represents. pubfn highlight_name(&self) -> Option<&AtomIdent> { match *self { Self::Highlight(ref name) => Some(name),
_ => None,
}
}
/// Whether this pseudo-element is the ::highlight pseudo. pubfn is_highlight(&self) -> bool {
matches!(*self, Self::Highlight(_))
}
/// Whether this pseudo-element is the ::target-text pseudo. #[inline] pubfn is_target_text(&self) -> bool {
*self == PseudoElement::TargetText
}
/// Whether this is a highlight pseudo-element that is styled lazily during /// painting rather than during the restyle traversal. These pseudos need /// explicit repaint triggering when their styles change. #[inline] pubfn is_lazy_painted_highlight_pseudo(&self) -> bool { self.is_selection() || self.is_highlight() || self.is_target_text()
}
/// Whether this pseudo-element is a named view transition pseudo-element. pubfn is_named_view_transition(&self) -> bool {
matches!(
*self, Self::ViewTransitionGroup(..)
| Self::ViewTransitionImagePair(..)
| Self::ViewTransitionOld(..)
| Self::ViewTransitionNew(..)
)
}
/// The count we contribute to the specificity from this pseudo-element. pubfn specificity_count(&self) -> u32 { match *self { Self::ViewTransitionGroup(ref name_and_class)
| Self::ViewTransitionImagePair(ref name_and_class)
| Self::ViewTransitionOld(ref name_and_class)
| Self::ViewTransitionNew(ref name_and_class) => { // The specificity of a named view transition pseudo-element selector with either: // 1. a <pt-name-selector> with a <custom-ident>; or // 2. a <pt-class-selector> with at least one <custom-ident>, // is equivalent to a type selector. // // The specificity of a named view transition pseudo-element selector with a `*` // argument and with an empty <pt-class-selector> is zero. // https://drafts.csswg.org/css-view-transitions-2/#pseudo-element-class-additions
(name_and_class.name() != &atom!("*") || !name_and_class.classes().is_empty()) as u32
},
_ => 1,
}
}
/// Whether this pseudo-element supports user action selectors. pubfn supports_user_action_state(&self) -> bool { self.flags()
.intersects(PseudoStyleTypeFlags::SUPPORTS_USER_ACTION_STATE)
}
/// Returns true if the given pseudo-element should be treated as disabled for /// the document represented by `url_data`, based on its `disabled_domains_pref` /// toml setting. fn is_pseudo_disabled_for_url(&self, url_data: &crate::stylesheets::UrlExtraData) -> bool { let Some(list) = self.disabled_domains() else { returnfalse;
}; if list.is_empty() { returnfalse;
} unsafe { crate::gecko_bindings::bindings::Gecko_IsURIInList(url_data.ptr(), &*list) }
}
/// Whether this pseudo-element is enabled for all content. pubfn enabled_in_content(&self, url_data: &crate::stylesheets::UrlExtraData) -> bool { Self::type_enabled_in_content(self.pseudo_type())
&& !self.is_pseudo_disabled_for_url(url_data)
}
/// Whether this pseudo is enabled explicitly in UA sheets. pubfn enabled_in_ua_sheets(&self) -> bool { self.flags().intersects(PseudoStyleTypeFlags::ENABLED_IN_UA)
}
/// Whether this pseudo is enabled explicitly in chrome sheets. pubfn enabled_in_chrome(&self) -> bool { self.flags()
.intersects(PseudoStyleTypeFlags::ENABLED_IN_CHROME)
}
/// Whether this pseudo-element is precomputed. #[inline] pubfn is_precomputed(&self) -> bool { self.is_anon_box()
}
/// Property flag that properties must have to apply to this pseudo-element. #[inline] pubfn property_restriction(&self) -> Option<PropertyFlags> {
Some(match *self {
PseudoElement::FirstLetter => PropertyFlags::APPLIES_TO_FIRST_LETTER,
PseudoElement::FirstLine => PropertyFlags::APPLIES_TO_FIRST_LINE,
PseudoElement::Placeholder => PropertyFlags::APPLIES_TO_PLACEHOLDER,
PseudoElement::Cue => PropertyFlags::APPLIES_TO_CUE,
PseudoElement::Marker => PropertyFlags::APPLIES_TO_MARKER,
_ => return None,
})
}
/// Whether this pseudo-element should actually exist if it has /// the given styles. pubfn should_exist(&self, style: &ComputedValues) -> bool {
debug_assert!(self.is_eager());
if style.get_box().clone_display() == Display::None { returnfalse;
}
/// Parse the pseudo-element string without the check of enabled state. This may includes /// all possible PseudoElement, including tree pseudo-elements and anonymous box. // TODO: Bug 1845712. Merge this with the pseudo element part in parse_one_simple_selector(). pubfn parse_ignore_enabled_state<'i, 't>(
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> { usecrate::gecko::selector_parser; use cssparser::Token; use selectors::parser::{is_css2_pseudo_element, SelectorParseErrorKind}; use style_traits::StyleParseErrorKind;
// The pseudo-element string should start with ':'.
input.expect_colon()?;
let location = input.current_source_location(); let next = input.next_including_whitespace()?; if !matches!(next, Token::Colon) { // Parse a CSS2 pseudo-element. let name = match next {
Token::Ident(name) if is_css2_pseudo_element(&name) => name,
_ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}; return PseudoElement::from_slice(&name).ok_or(location.new_custom_error(
SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone()),
));
}
// Now we have double colons, so check the following tokens. match input.next_including_whitespace()?.clone() {
Token::Ident(name) => { // We don't need to parse unknown ::-webkit-* pseudo-elements in this function.
PseudoElement::from_slice(&name).ok_or(input.new_custom_error(
SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
))
},
Token::Function(name) => { // Note: ::slotted() and ::part() are not accepted in getComputedStyle(). // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle
input.parse_nested_block(|input| {
selector_parser::parse_functional_pseudo_element_with_name(
&name,
input,
Target::Cssom,
)
})
},
t => return Err(input.new_unexpected_token_error(t)),
}
}
/// Returns true if this pseudo-element matches its selector. pubfn matches_named_view_transition_pseudo_element(
&self,
selector: &Self,
element: &super::wrapper::GeckoElement,
) -> bool { usecrate::gecko_bindings::bindings;
match (self, selector) {
(
&Self::ViewTransitionGroup(ref name),
&Self::ViewTransitionGroup(ref s_name_class),
)
| (
&Self::ViewTransitionImagePair(ref name),
&Self::ViewTransitionImagePair(ref s_name_class),
)
| (&Self::ViewTransitionOld(ref name), &>Self::ViewTransitionOld(ref s_name_class))
| (&Self::ViewTransitionNew(ref name), &>Self::ViewTransitionNew(ref s_name_class)) => { // Named view transition pseudos accept the universal selector as the name, so we // check it first. // https://drafts.csswg.org/css-view-transitions-1/#named-view-transition-pseudo let s_name = s_name_class.name(); if s_name != name.name() && s_name != &atom!("*") { returnfalse;
}
// We have to check class list only when the name is matched and there are one or // more <pt-class-selector>s.
s_name_class.classes().is_empty()
|| unsafe {
bindings::Gecko_MatchViewTransitionClass(
element.0,
s_name_class.name_and_classes(),
)
}
},
_ => false,
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.