Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/servo/components/style/gecko/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 22 kB image not shown  

Quelle  pseudo_element.rs

  Sprache: Rust
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * 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.

use crate::gecko_bindings::structs::PseudoStyleType;
use crate::properties::longhands::display::computed_value::T as Display;
use crate::properties::{ComputedValues, PropertyFlags};
use crate::selector_parser::{PseudoElementCascadeType, SelectorImpl};
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
use crate::string_cache::Atom;
use crate::values::serialize_atom_identifier;
use crate::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)]
    pub struct 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;
    }
}

include!(concat!(
    env!("OUT_DIR"),
    "/gecko/pseudo_element_definition.rs"
));

/// The target we are using for parsing pseudo-elements.
pub enum 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)]
pub struct PtNameAndClassSelector(thin_vec::ThinVec<Atom>);

impl PtNameAndClassSelector {
    /// Constructs a new one from a name.
    pub fn from_name(name: Atom) -> Self {
        Self(thin_vec::thin_vec![name])
    }

    /// Returns the name component.
    pub fn name(&self) -> &Atom {
        debug_assert!(!self.0.is_empty());
        self.0.first().expect("Shouldn't be empty")
    }

    /// Returns the classes component.
    pub fn classes(&self) -> &[Atom] {
        debug_assert!(!self.0.is_empty());
        &self.0[1..]
    }

    /// Returns the vector we store.
    pub fn 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).
    pub fn parse<'i, 't>(
        input: &mut Parser<'i, 't>,
        target: Target,
    ) -> Result<Self, ParseError<'i>> {
        use crate::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.
            if let 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();
        }
        let mut classes = thin_vec::ThinVec::new();
        while let 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).
        let mut 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 in self.classes() {
            dest.write_char('.')?;
            serialize_atom_identifier(class, dest)?;
        }

        Ok(())
    }
}

impl PseudoElementTrait for PseudoElement {
    type Impl = SelectorImpl;

    // ::slotted() should support all tree-abiding pseudo-elements, see
    // https://drafts.csswg.org/css-scoping/#slotted-pseudo
    // https://drafts.csswg.org/css-pseudo-4/#treelike
    #[inline]
    fn valid_after_slotted(&self) -> bool {
        self.flags()
            .intersects(PseudoStyleTypeFlags::IS_TREE_ABIDING)
    }

    // ::before/::after should support ::marker, but no others.
    // https://drafts.csswg.org/css-pseudo-4/#marker-pseudo
    #[inline]
    fn valid_after_before_or_after(&self) -> bool {
        matches!(*selfSelf::Marker)
    }

    #[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 specificity_count(&self) -> u32 {
        self.specificity_count()
    }

    #[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()
    }

    fn parses_as_element_backed(&self) -> bool {
        self.flags()
            .intersects(PseudoStyleTypeFlags::PARSES_AS_ELEMENT_BACKED)
    }

    /// 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]
    pub fn 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`.
    pub fn cascade_type(&self) -> PseudoElementCascadeType {
        if self.is_eager() {
            debug_assert!(!self.is_anon_box());
            return PseudoElementCascadeType::Eager;
        }

        if self.is_precomputed() {
            return PseudoElementCascadeType::Precomputed;
        }

        PseudoElementCascadeType::Lazy
    }

    /// Returns an index of the pseudo-element.
    #[inline]
    pub fn index(&self) -> usize {
        self.discriminant() as usize
    }

    #[inline]
    fn discriminant(&self) -> u8 {
        // SAFETY: #[repr(u8) guarantees this, see comments in
        // https://doc.rust-lang.org/std/mem/fn.discriminant.html
        unsafe { *(self as *const _ as *const u8) }
    }

    /// Whether this pseudo-element is an unknown Webkit-prefixed pseudo-element.
    #[inline]
    pub fn is_unknown_webkit_pseudo_element(&self) -> bool {
        matches!(*self, PseudoElement::UnknownWebkit(..))
    }

    /// Whether this pseudo-element is an anonymous box.
    #[inline]
    pub fn is_anon_box(&self) -> bool {
        self.flags().intersects(PseudoStyleTypeFlags::IS_ANON_BOX)
    }

    /// Whether this pseudo-element is eagerly-cascaded.
    #[inline]
    pub fn is_eager(&self) -> bool {
        self.flags().intersects(PseudoStyleTypeFlags::IS_EAGER)
    }

    /// Gets the canonical index of this eagerly-cascaded pseudo-element.
    #[inline]
    pub fn 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]
    pub fn from_eager_index(i: usize) -> Self {
        EAGER_PSEUDOS[i].clone()
    }

    /// Whether animations for the current pseudo element are stored in the
    /// parent element.
    #[inline]
    pub fn 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]
    pub fn is_before(&self) -> bool {
        *self == PseudoElement::Before
    }

    /// Whether this pseudo-element is the ::after pseudo.
    #[inline]
    pub fn is_after(&self) -> bool {
        *self == PseudoElement::After
    }

    /// Whether this pseudo-element is the ::marker pseudo.
    #[inline]
    pub fn is_marker(&self) -> bool {
        *self == PseudoElement::Marker
    }

    /// Whether this pseudo-element is the ::selection pseudo.
    #[inline]
    pub fn is_selection(&self) -> bool {
        *self == PseudoElement::Selection
    }

    /// Whether this pseudo-element is ::first-letter.
    #[inline]
    pub fn is_first_letter(&self) -> bool {
        *self == PseudoElement::FirstLetter
    }

    /// Whether this pseudo-element is ::first-line.
    #[inline]
    pub fn is_first_line(&self) -> bool {
        *self == PseudoElement::FirstLine
    }

    /// Whether this pseudo-element is lazily-cascaded.
    #[inline]
    pub fn is_lazy(&self) -> bool {
        !self.is_eager() && !self.is_precomputed()
    }

    /// The identifier of the highlight this pseudo-element represents.
    pub fn highlight_name(&self) -> Option<&AtomIdent> {
        match *self {
            Self::Highlight(ref name) => Some(name),
            _ => None,
        }
    }

    /// Whether this pseudo-element is the ::highlight pseudo.
    pub fn is_highlight(&self) -> bool {
        matches!(*selfSelf::Highlight(_))
    }

    /// Whether this pseudo-element is the ::target-text pseudo.
    #[inline]
    pub fn 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]
    pub fn 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.
    pub fn 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.
    pub fn 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.
    pub fn 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 {
            return false;
        };
        if list.is_empty() {
            return false;
        }
        unsafe { crate::gecko_bindings::bindings::Gecko_IsURIInList(url_data.ptr(), &*list) }
    }

    /// Whether this pseudo-element is enabled for all content.
    pub fn 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.
    pub fn enabled_in_ua_sheets(&self) -> bool {
        self.flags().intersects(PseudoStyleTypeFlags::ENABLED_IN_UA)
    }

    /// Whether this pseudo is enabled explicitly in chrome sheets.
    pub fn enabled_in_chrome(&self) -> bool {
        self.flags()
            .intersects(PseudoStyleTypeFlags::ENABLED_IN_CHROME)
    }

    /// Whether this pseudo-element skips flex/grid container display-based
    /// fixup.
    #[inline]
    pub fn skip_item_display_fixup(&self) -> bool {
        !self
            .flags()
            .intersects(PseudoStyleTypeFlags::IS_FLEX_OR_GRID_ITEM)
    }

    /// Whether this pseudo-element is precomputed.
    #[inline]
    pub fn is_precomputed(&self) -> bool {
        self.is_anon_box()
    }

    /// Property flag that properties must have to apply to this pseudo-element.
    #[inline]
    pub fn 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.
    pub fn should_exist(&self, style: &ComputedValues) -> bool {
        debug_assert!(self.is_eager());

        if style.get_box().clone_display() == Display::None {
            return false;
        }

        if self.is_before_or_after() && style.ineffective_content_property() {
            return false;
        }

        true
    }

    /// 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().
    pub fn parse_ignore_enabled_state<'i, 't>(
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        use crate::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.
    pub fn matches_named_view_transition_pseudo_element(
        &self,
        selector: &Self,
        element: &super::wrapper::GeckoElement,
    ) -> bool {
        use crate::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!("*") {
                    return false;
                }

                // 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
C=82 H=99 G=90

¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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.