/* 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/. */
//! Supported CSS properties and the cascade.
pubmod cascade; pubmod declaration_block;
pubuseself::cascade::*; pubuseself::declaration_block::*; pubuseself::generated::*; /// The CSS properties supported by the style system. /// Generated from the properties.mako.rs template by build.rs #[macro_use] #[allow(unsafe_code)] #[deny(missing_docs)] pubmod generated {
include!(concat!(env!("OUT_DIR"), "/properties.rs"));
}
usecrate::custom_properties::{self, ComputedCustomProperties}; #[cfg(feature = "gecko")] usecrate::gecko_bindings::structs::{nsCSSPropertyID, AnimatedPropertyID, RefPtr}; usecrate::logical_geometry::WritingMode; usecrate::parser::ParserContext; usecrate::str::CssString; usecrate::stylesheets::CssRuleType; usecrate::stylesheets::Origin; usecrate::stylist::Stylist; usecrate::values::{computed, serialize_atom_name}; use arrayvec::{ArrayVec, Drain as ArrayVecDrain}; use cssparser::{Parser, ParserInput}; use fxhash::FxHashMap; use servo_arc::Arc; use std::{
borrow::Cow,
fmt::{self, Write},
mem,
}; use style_traits::{
CssWriter, KeywordsCollectFn, ParseError, ParsingMode, SpecifiedValueInfo, ToCss,
};
bitflags! { /// A set of flags for properties. #[derive(Clone, Copy)] pubstruct PropertyFlags: u16 { /// This longhand property applies to ::first-letter. const APPLIES_TO_FIRST_LETTER = 1 << 1; /// This longhand property applies to ::first-line. const APPLIES_TO_FIRST_LINE = 1 << 2; /// This longhand property applies to ::placeholder. const APPLIES_TO_PLACEHOLDER = 1 << 3; /// This longhand property applies to ::cue. const APPLIES_TO_CUE = 1 << 4; /// This longhand property applies to ::marker. const APPLIES_TO_MARKER = 1 << 5; /// This property is a legacy shorthand. /// /// https://drafts.csswg.org/css-cascade/#legacy-shorthand const IS_LEGACY_SHORTHAND = 1 << 6;
/* The following flags are currently not used in Rust code, they *onlyneedtobelistedincorrespondingpropertiessothat
* they can be checked in the C++ side via ServoCSSPropList.h. */
/// This property can be animated on the compositor. const CAN_ANIMATE_ON_COMPOSITOR = 0; /// See data.py's documentation about the affects_flags. const AFFECTS_LAYOUT = 0; #[allow(missing_docs)] const AFFECTS_OVERFLOW = 0; #[allow(missing_docs)] const AFFECTS_PAINT = 0;
}
}
/// An enum to represent a CSS Wide keyword. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] pubenum CSSWideKeyword { /// The `initial` keyword.
Initial, /// The `inherit` keyword.
Inherit, /// The `unset` keyword.
Unset, /// The `revert` keyword.
Revert, /// The `revert-layer` keyword.
RevertLayer,
}
impl CSSWideKeyword { /// Returns the string representation of the keyword. pubfn to_str(&self) -> &'static str { match *self {
CSSWideKeyword::Initial => "initial",
CSSWideKeyword::Inherit => "inherit",
CSSWideKeyword::Unset => "unset",
CSSWideKeyword::Revert => "revert",
CSSWideKeyword::RevertLayer => "revert-layer",
}
}
}
/// A declaration using a CSS-wide keyword. #[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] pubstruct WideKeywordDeclaration { #[css(skip)]
id: LonghandId, /// The CSS-wide keyword. pub keyword: CSSWideKeyword,
}
/// An unparsed declaration that contains `var()` functions. #[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] pubstruct VariableDeclaration { /// The id of the property this declaration represents. #[css(skip)]
id: LonghandId, /// The unparsed value of the variable. #[ignore_malloc_size_of = "Arc"] pub value: Arc<UnparsedValue>,
}
/// A custom property declaration value is either an unparsed value or a CSS /// wide-keyword. #[derive(Clone, PartialEq, ToCss, ToShmem)] pubenum CustomDeclarationValue { /// An unparsed value.
Unparsed(Arc<custom_properties::SpecifiedValue>), /// An already-parsed value.
Parsed(Arc<crate::properties_and_values::value::SpecifiedValue>), /// A wide keyword.
CSSWideKeyword(CSSWideKeyword),
}
/// A custom property declaration with the property name and the declared value. #[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] pubstruct CustomDeclaration { /// The name of the custom property. #[css(skip)] pub name: custom_properties::Name, /// The value of the custom property. #[ignore_malloc_size_of = "Arc"] pub value: CustomDeclarationValue,
}
// Because PropertyDeclaration::to_css requires CssStringWriter, we can't write // it directly to f, and need to allocate an intermediate string. This is // fine for debug-only code. letmut s = CssString::new(); self.to_css(&mut s)?;
write!(f, "{}", s)
}
}
impl ToCss for NonCustomPropertyId { #[inline] fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where
W: Write,
{
dest.write_str(self.name())
}
}
impl NonCustomPropertyId { /// Returns the underlying index, used for use counter. pubfn bit(self) -> usize { self.0as usize
}
/// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`. #[cfg(feature = "gecko")] #[inline] pubfn to_nscsspropertyid(self) -> nsCSSPropertyID { // unsafe: guaranteed by static_assert_nscsspropertyid. unsafe { mem::transmute(self.0as i32) }
}
/// Convert an `nsCSSPropertyID` into a `NonCustomPropertyId`. #[cfg(feature = "gecko")] #[inline] pubfn from_nscsspropertyid(prop: nsCSSPropertyID) -> Option<Self> { let prop = prop as i32; if prop < 0 || prop >= property_counts::NON_CUSTOM as i32 { return None;
} // guaranteed by static_assert_nscsspropertyid above.
Some(NonCustomPropertyId(prop as u16))
}
/// Resolves the alias of a given property if needed. pubfn unaliased(self) -> Self { let Some(alias_id) = self.as_alias() else { returnself;
};
alias_id.aliased_property()
}
/// Turns this `NonCustomPropertyId` into a `PropertyId`. #[inline] pubfn to_property_id(self) -> PropertyId {
PropertyId::NonCustom(self)
}
/// Returns a longhand id, if this property is one. #[inline] pubfn as_longhand(self) -> Option<LonghandId> { ifself.0 < property_counts::LONGHANDS as u16 { return Some(unsafe { mem::transmute(self.0as u16) });
}
None
}
/// Returns a shorthand id, if this property is one. #[inline] pubfn as_shorthand(self) -> Option<ShorthandId> { ifself.0 >= property_counts::LONGHANDS as u16 && self.0 < property_counts::LONGHANDS_AND_SHORTHANDS as u16
{ return Some(unsafe { mem::transmute(self.0 - (property_counts::LONGHANDS as u16)) });
}
None
}
/// Returns an alias id, if this property is one. #[inline] pubfn as_alias(self) -> Option<AliasId> {
debug_assert!((self.0as usize) < property_counts::NON_CUSTOM); ifself.0 >= property_counts::LONGHANDS_AND_SHORTHANDS as u16 { return Some(unsafe {
mem::transmute(self.0 - (property_counts::LONGHANDS_AND_SHORTHANDS as u16))
});
}
None
}
/// Returns either a longhand or a shorthand, resolving aliases. #[inline] pubfn longhand_or_shorthand(self) -> Result<LonghandId, ShorthandId> { let id = self.unaliased(); match id.as_longhand() {
Some(lh) => Ok(lh),
None => Err(id.as_shorthand().unwrap()),
}
}
/// Converts a longhand id into a non-custom property id. #[inline] pubconstfn from_longhand(id: LonghandId) -> Self { Self(id as u16)
}
/// Converts a shorthand id into a non-custom property id. #[inline] pubconstfn from_shorthand(id: ShorthandId) -> Self { Self((id as u16) + (property_counts::LONGHANDS as u16))
}
/// Converts an alias id into a non-custom property id. #[inline] pubconstfn from_alias(id: AliasId) -> Self { Self((id as u16) + (property_counts::LONGHANDS_AND_SHORTHANDS as u16))
}
}
/// Representation of a CSS property, that is, either a longhand, a shorthand, or a custom /// property. #[derive(Clone, Eq, PartialEq, Debug)] pubenum PropertyId { /// An alias for a shorthand property.
NonCustom(NonCustomPropertyId), /// A custom property.
Custom(custom_properties::Name),
}
impl PropertyId { /// Return the longhand id that this property id represents. #[inline] pubfn longhand_id(&self) -> Option<LonghandId> { self.non_custom_non_alias_id()?.as_longhand()
}
/// Returns true if this property is one of the animatable properties. pubfn is_animatable(&self) -> bool { matchself { Self::NonCustom(id) => id.is_animatable(), Self::Custom(..) => true,
}
}
/// Returns a given property from the given name, _regardless of whether it is enabled or /// not_, or Err(()) for unknown properties. /// /// Do not use for non-testing purposes. pubfn parse_unchecked_for_testing(name: &str) -> Result<Self, ()> { Self::parse_unchecked(name, None)
}
/// Parses a property name, and returns an error if it's unknown or isn't enabled for all /// content. #[inline] pubfn parse_enabled_for_all_content(name: &str) -> Result<Self, ()> { let id = Self::parse_unchecked(name, None)?;
if !id.enabled_for_all_content() { return Err(());
}
Ok(id)
}
/// Parses a property name, and returns an error if it's unknown or isn't allowed in this /// context. #[inline] pubfn parse(name: &str, context: &ParserContext) -> Result<Self, ()> { let id = Self::parse_unchecked(name, context.use_counters)?; if !id.allowed_in(context) { return Err(());
}
Ok(id)
}
/// Parses a property name, and returns an error if it's unknown or isn't allowed in this /// context, ignoring the rule_type checks. /// /// This is useful for parsing stuff from CSS values, for example. #[inline] pubfn parse_ignoring_rule_type(name: &str, context: &ParserContext) -> Result<Self, ()> { let id = Self::parse_unchecked(name, None)?; if !id.allowed_in_ignoring_rule_type(context) { return Err(());
}
Ok(id)
}
/// Returns a property id from Gecko's nsCSSPropertyID. #[cfg(feature = "gecko")] #[inline] pubfn from_nscsspropertyid(id: nsCSSPropertyID) -> Option<Self> {
Some(NonCustomPropertyId::from_nscsspropertyid(id)?.to_property_id())
}
/// Returns a property id from Gecko's AnimatedPropertyID. #[cfg(feature = "gecko")] #[inline] pubfn from_gecko_animated_property_id(property: &AnimatedPropertyID) -> Option<Self> {
Some( if property.mID == nsCSSPropertyID::eCSSPropertyExtra_variable {
debug_assert!(!property.mCustomName.mRawPtr.is_null()); Self::Custom(unsafe { crate::Atom::from_raw(property.mCustomName.mRawPtr) })
} else { Self::NonCustom(NonCustomPropertyId::from_nscsspropertyid(property.mID)?)
},
)
}
/// Returns true if the property is a shorthand or shorthand alias. #[inline] pubfn is_shorthand(&self) -> bool { self.as_shorthand().is_ok()
}
/// Given this property id, get it either as a shorthand or as a /// `PropertyDeclarationId`. pubfn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId> { match *self { Self::NonCustom(id) => match id.longhand_or_shorthand() {
Ok(lh) => Err(PropertyDeclarationId::Longhand(lh)),
Err(sh) => Ok(sh),
}, Self::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)),
}
}
/// Returns the `NonCustomPropertyId` corresponding to this property id. pubfn non_custom_id(&self) -> Option<NonCustomPropertyId> { match *self { Self::Custom(_) => None, Self::NonCustom(id) => Some(id),
}
}
/// Returns non-alias NonCustomPropertyId corresponding to this /// property id. fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> { self.non_custom_id().map(NonCustomPropertyId::unaliased)
}
/// Whether the property is enabled for all content regardless of the /// stylesheet it was declared on (that is, in practice only checks prefs). #[inline] pubfn enabled_for_all_content(&self) -> bool { let id = matchself.non_custom_id() { // Custom properties are allowed everywhere
None => returntrue,
Some(id) => id,
};
id.enabled_for_all_content()
}
/// Converts this PropertyId in nsCSSPropertyID, resolving aliases to the /// resolved property, and returning eCSSPropertyExtra_variable for custom /// properties. #[cfg(feature = "gecko")] #[inline] pubfn to_nscsspropertyid_resolving_aliases(&self) -> nsCSSPropertyID { matchself.non_custom_non_alias_id() {
Some(id) => id.to_nscsspropertyid(),
None => nsCSSPropertyID::eCSSPropertyExtra_variable,
}
}
fn allowed_in(&self, context: &ParserContext) -> bool { let id = matchself.non_custom_id() { // Custom properties are allowed everywhere, except `position-try`.
None => return !context.nesting_context.rule_types.contains(CssRuleType::PositionTry),
Some(id) => id,
};
id.allowed_in(context)
}
#[inline] fn allowed_in_ignoring_rule_type(&self, context: &ParserContext) -> bool { let id = matchself.non_custom_id() { // Custom properties are allowed everywhere
None => returntrue,
Some(id) => id,
};
id.allowed_in_ignoring_rule_type(context)
}
/// Whether the property supports the given CSS type. /// `ty` should a bitflags of constants in style_traits::CssType. pubfn supports_type(&self, ty: u8) -> bool { let id = self.non_custom_non_alias_id();
id.map_or(0, |id| id.supported_types()) & ty != 0
}
/// Collect supported starting word of values of this property. /// /// See style_traits::SpecifiedValueInfo::collect_completion_keywords for more /// details. pubfn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { iflet Some(id) = self.non_custom_non_alias_id() {
id.collect_property_completion_keywords(f);
}
CSSWideKeyword::collect_completion_keywords(f);
}
}
impl ToCss for LonghandId { #[inline] fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where
W: Write,
{
dest.write_str(self.name())
}
}
impl LonghandId { /// Get the name of this longhand property. #[inline] pubfn name(&self) -> &'static str {
NonCustomPropertyId::from(*self).name()
}
/// Returns whether the longhand property is inherited by default. #[inline] pubfn inherited(self) -> bool {
!LonghandIdSet::reset().contains(self)
}
/// Returns true if the property is one that is ignored when document /// colors are disabled. #[inline] pubfn ignored_when_document_colors_disabled(self) -> bool {
LonghandIdSet::ignored_when_colors_disabled().contains(self)
}
/// Returns whether this longhand is `non_custom` or is a longhand of it. pubfn is_or_is_longhand_of(self, non_custom: NonCustomPropertyId) -> bool { match non_custom.longhand_or_shorthand() {
Ok(lh) => self == lh,
Err(sh) => self.is_longhand_of(sh),
}
}
/// Returns whether this longhand is a longhand of `shorthand`. pubfn is_longhand_of(self, shorthand: ShorthandId) -> bool { self.shorthands().any(|s| s == shorthand)
}
/// Returns whether this property is animatable. #[inline] pubfn is_animatable(self) -> bool {
NonCustomPropertyId::from(self).is_animatable()
}
/// Returns whether this property is animatable in a discrete way. #[inline] pubfn is_discrete_animatable(self) -> bool {
LonghandIdSet::discrete_animatable().contains(self)
}
/// Converts from a LonghandId to an adequate nsCSSPropertyID. #[cfg(feature = "gecko")] #[inline] pubfn to_nscsspropertyid(self) -> nsCSSPropertyID {
NonCustomPropertyId::from(self).to_nscsspropertyid()
}
#[cfg(feature = "gecko")] /// Returns a longhand id from Gecko's nsCSSPropertyID. pubfn from_nscsspropertyid(id: nsCSSPropertyID) -> Option<Self> {
NonCustomPropertyId::from_nscsspropertyid(id)?
.unaliased()
.as_longhand()
}
/// Return whether this property is logical. #[inline] pubfn is_logical(self) -> bool {
LonghandIdSet::logical().contains(self)
}
}
impl ToCss for ShorthandId { #[inline] fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where
W: Write,
{
dest.write_str(self.name())
}
}
impl ShorthandId { /// Get the name for this shorthand property. #[inline] pubfn name(&self) -> &'static str {
NonCustomPropertyId::from(*self).name()
}
/// Converts from a ShorthandId to an adequate nsCSSPropertyID. #[cfg(feature = "gecko")] #[inline] pubfn to_nscsspropertyid(self) -> nsCSSPropertyID {
NonCustomPropertyId::from(self).to_nscsspropertyid()
}
/// Converts from a nsCSSPropertyID to a ShorthandId. #[cfg(feature = "gecko")] #[inline] pubfn from_nscsspropertyid(id: nsCSSPropertyID) -> Option<Self> {
NonCustomPropertyId::from_nscsspropertyid(id)?
.unaliased()
.as_shorthand()
}
/// Finds and returns an appendable value for the given declarations. /// /// Returns the optional appendable value. pubfn get_shorthand_appendable_value<'a, 'b: 'a>( self,
declarations: &'a [&'b PropertyDeclaration],
) -> Option<AppendableValue<'a, 'b>> { let first_declaration = declarations.get(0)?; let rest = || declarations.iter().skip(1);
// Check whether they are all the same CSS-wide keyword. iflet Some(keyword) = first_declaration.get_css_wide_keyword() { if rest().all(|d| d.get_css_wide_keyword() == Some(keyword)) { return Some(AppendableValue::Css(keyword.to_str()));
} return None;
}
ifself == ShorthandId::All { // 'all' only supports variables and CSS wide keywords. return None;
}
// Check whether all declarations can be serialized as part of shorthand. if declarations
.iter()
.all(|d| d.may_serialize_as_part_of_shorthand())
{ return Some(AppendableValue::DeclarationsForShorthand( self,
declarations,
));
}
None
}
/// Returns whether this property is a legacy shorthand. #[inline] pubfn is_legacy_shorthand(self) -> bool { self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND)
}
}
input.reset(&start);
input.look_for_var_or_env_functions(); let err = match parse_entirely_into(declarations, input) {
Ok(()) => {
input.seen_var_or_env_functions(); return Ok(());
},
Err(e) => e,
};
// Look for var(), env() and top-level curly blocks after the error. let start_pos = start.position(); letmut at_start = start_pos == input.position(); letmut invalid = false; whilelet Ok(token) = input.next() { if matches!(token, cssparser::Token::CurlyBracketBlock) { if !starts_with_curly_block || !at_start {
invalid = true; break;
}
} elseif starts_with_curly_block {
invalid = true; break;
}
at_start = false;
} if !input.seen_var_or_env_functions() || invalid { return Err(err);
}
input.reset(start); let value = custom_properties::VariableValue::parse(input, &context.url_data)?;
parsed_custom(declarations, value);
Ok(())
}
impl PropertyDeclaration { fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option<&str> { match *self {
PropertyDeclaration::WithVariables(ref declaration) => { let s = declaration.value.from_shorthand?; if s != shorthand { return None;
}
Some(&*declaration.value.variable_value.css)
},
_ => None,
}
}
/// Returns a CSS-wide keyword declaration for a given property. #[inline] pubfn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self { Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword })
}
/// Returns a CSS-wide keyword if the declaration's value is one. #[inline] pubfn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> { match *self {
PropertyDeclaration::CSSWideKeyword(ref declaration) => Some(declaration.keyword),
_ => None,
}
}
/// Returns whether the declaration may be serialized as part of a shorthand. /// /// This method returns false if this declaration contains variable or has a /// CSS-wide keyword value, since these values cannot be serialized as part /// of a shorthand. /// /// Caller should check `with_variables_from_shorthand()` and whether all /// needed declarations has the same CSS-wide keyword first. /// /// Note that, serialization of a shorthand may still fail because of other /// property-specific requirement even when this method returns true for all /// the longhand declarations. pubfn may_serialize_as_part_of_shorthand(&self) -> bool { match *self {
PropertyDeclaration::CSSWideKeyword(..) | PropertyDeclaration::WithVariables(..) => { false
},
PropertyDeclaration::Custom(..) => {
unreachable!("Serializing a custom property as part of shorthand?")
},
_ => true,
}
}
/// Returns true if this property declaration is for one of the animatable properties. pubfn is_animatable(&self) -> bool { self.id().is_animatable()
}
/// Returns true if this property is a custom property, false /// otherwise. pubfn is_custom(&self) -> bool {
matches!(*self, PropertyDeclaration::Custom(..))
}
/// The `context` parameter controls this: /// /// <https://drafts.csswg.org/css-animations/#keyframes> /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property /// > except those defined in this specification, /// > but does accept the `animation-play-state` property and interprets it specially. /// /// This will not actually parse Importance values, and will always set things /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser, /// we only set them here so that we don't have to reallocate pubfn parse_into<'i, 't>(
declarations: &mut SourcePropertyDeclaration,
id: PropertyId,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
assert!(declarations.is_empty());
debug_assert!(id.allowed_in(context), "{:?}", id);
input.skip_whitespace();
let start = input.state(); let non_custom_id = match id {
PropertyId::Custom(property_name) => { let value = match input.try_parse(CSSWideKeyword::parse) {
Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword),
Err(()) => CustomDeclarationValue::Unparsed(Arc::new(
custom_properties::VariableValue::parse(input, &context.url_data)?,
)),
};
declarations.push(PropertyDeclaration::Custom(CustomDeclaration {
name: property_name,
value,
})); return Ok(());
},
PropertyId::NonCustom(id) => id,
}; match non_custom_id.longhand_or_shorthand() {
Ok(longhand_id) => {
parse_non_custom_property_declaration_value_into(
declarations,
context,
input,
&start,
|declarations, input| { let decl = input
.parse_entirely(|input| longhand_id.parse_value(context, input))?;
declarations.push(decl);
Ok(())
},
|declarations, wk| {
declarations.push(PropertyDeclaration::css_wide_keyword(longhand_id, wk));
},
|declarations, variable_value| {
declarations.push(PropertyDeclaration::WithVariables(VariableDeclaration {
id: longhand_id,
value: Arc::new(UnparsedValue {
variable_value,
from_shorthand: None,
}),
}))
},
)?;
},
Err(shorthand_id) => {
parse_non_custom_property_declaration_value_into(
declarations,
context,
input,
&start, // Not using parse_entirely here: each ShorthandId::parse_into function needs // to do so *before* pushing to `declarations`.
|declarations, input| shorthand_id.parse_into(declarations, context, input),
|declarations, wk| { if shorthand_id == ShorthandId::All {
declarations.all_shorthand = AllShorthand::CSSWideKeyword(wk)
} else { for longhand in shorthand_id.longhands() {
declarations
.push(PropertyDeclaration::css_wide_keyword(longhand, wk));
}
}
},
|declarations, variable_value| { let unparsed = Arc::new(UnparsedValue {
variable_value,
from_shorthand: Some(shorthand_id),
}); if shorthand_id == ShorthandId::All {
declarations.all_shorthand = AllShorthand::WithVariables(unparsed)
} else { for id in shorthand_id.longhands() {
declarations.push(PropertyDeclaration::WithVariables(
VariableDeclaration {
id,
value: unparsed.clone(),
},
))
}
}
},
)?;
},
} iflet Some(use_counters) = context.use_counters {
use_counters.non_custom_properties.record(non_custom_id);
}
Ok(())
}
}
/// A PropertyDeclarationId without references, for use as a hash map key. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pubenum OwnedPropertyDeclarationId { /// A longhand.
Longhand(LonghandId), /// A custom property declaration.
Custom(custom_properties::Name),
}
impl OwnedPropertyDeclarationId { /// Return whether this property is logical. #[inline] pubfn is_logical(&self) -> bool { self.as_borrowed().is_logical()
}
/// Convert an `AnimatedPropertyID` into an `OwnedPropertyDeclarationId`. #[cfg(feature = "gecko")] #[inline] pubfn from_gecko_animated_property_id(property: &AnimatedPropertyID) -> Option<Self> {
Some( match PropertyId::from_gecko_animated_property_id(property)? {
PropertyId::Custom(name) => Self::Custom(name),
PropertyId::NonCustom(id) => Self::Longhand(id.as_longhand()?),
},
)
}
}
/// An identifier for a given property declaration, which can be either a /// longhand or a custom property. #[derive(Clone, Copy, Debug, PartialEq, MallocSizeOf)] pubenum PropertyDeclarationId<'a> { /// A longhand.
Longhand(LonghandId), /// A custom property declaration.
Custom(&'a custom_properties::Name),
}
/// Whether a given declaration id is either the same as `other`, or a /// longhand of it. pubfn is_or_is_longhand_of(&self, other: &PropertyId) -> bool { match *self {
PropertyDeclarationId::Longhand(id) => match *other {
PropertyId::NonCustom(non_custom_id) => id.is_or_is_longhand_of(non_custom_id),
PropertyId::Custom(_) => false,
},
PropertyDeclarationId::Custom(name) => {
matches!(*other, PropertyId::Custom(ref other_name) if name == other_name)
},
}
}
/// Whether a given declaration id is a longhand belonging to this /// shorthand. pubfn is_longhand_of(&self, shorthand: ShorthandId) -> bool { match *self {
PropertyDeclarationId::Longhand(ref id) => id.is_longhand_of(shorthand),
_ => false,
}
}
/// Returns the name of the property without CSS escaping. pubfn name(&self) -> Cow<'static, str> { match *self {
PropertyDeclarationId::Longhand(id) => id.name().into(),
PropertyDeclarationId::Custom(name) => { letmut s = String::new();
write!(&mut s, "--{}", name).unwrap();
s.into()
},
}
}
/// Returns longhand id if it is, None otherwise. #[inline] pubfn as_longhand(&self) -> Option<LonghandId> { match *self {
PropertyDeclarationId::Longhand(id) => Some(id),
_ => None,
}
}
/// If this is a logical property, return the corresponding physical one in /// the given writing mode. /// /// Otherwise, return unchanged. #[inline] pubfn to_physical(&self, wm: WritingMode) -> Self { matchself { Self::Longhand(id) => Self::Longhand(id.to_physical(wm)), Self::Custom(_) => self.clone(),
}
}
/// Returns whether this property is animatable in a discrete way. #[inline] pubfn is_discrete_animatable(&self) -> bool { matchself { Self::Longhand(longhand) => longhand.is_discrete_animatable(), // TODO(bug 1885995): Refine this. Self::Custom(_) => true,
}
}
/// Converts from a to an adequate nsCSSPropertyID, returning /// eCSSPropertyExtra_variable for custom properties. #[cfg(feature = "gecko")] #[inline] pubfn to_nscsspropertyid(self) -> nsCSSPropertyID { matchself {
PropertyDeclarationId::Longhand(id) => id.to_nscsspropertyid(),
PropertyDeclarationId::Custom(_) => nsCSSPropertyID::eCSSPropertyExtra_variable,
}
}
/// Convert a `PropertyDeclarationId` into an `AnimatedPropertyID` /// /// FIXME(emilio, bug 1870107): We should consider using cbindgen to generate the property id /// representation or so. #[cfg(feature = "gecko")] #[inline] pubfn to_gecko_animated_property_id(&self) -> AnimatedPropertyID { matchself { Self::Longhand(id) => AnimatedPropertyID {
mID: id.to_nscsspropertyid(),
mCustomName: RefPtr::null(),
}, Self::Custom(name) => { letmut property_id = AnimatedPropertyID {
mID: nsCSSPropertyID::eCSSPropertyExtra_variable,
mCustomName: RefPtr::null(),
};
property_id.mCustomName.mRawPtr = (*name).clone().into_addrefed();
property_id
},
}
}
}
/// A set of all properties. #[derive(Clone, PartialEq, Default)] pubstruct NonCustomPropertyIdSet {
storage: [u32; ((property_counts::NON_CUSTOM as usize) - 1 + 32) / 32],
}
/// Insert a non-custom-property in the set. #[inline] pubfn insert(&mutself, id: NonCustomPropertyId) { let bit = id.0as usize; self.storage[bit / 32] |= 1 << (bit % 32);
}
/// Return whether the given property is in the set #[inline] pubfn contains(&self, id: NonCustomPropertyId) -> bool { let bit = id.0as usize;
(self.storage[bit / 32] & (1 << (bit % 32))) != 0
}
}
/// A set of longhand properties #[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] pubstruct LonghandIdSet {
storage: [u32; ((property_counts::LONGHANDS as usize) - 1 + 32) / 32],
}
/// Iterate over the current longhand id set. pubfn iter(&self) -> LonghandIdSetIterator {
LonghandIdSetIterator {
longhands: self,
cur: 0,
}
}
/// Returns whether this set contains at least every longhand that `other` /// also contains. pubfn contains_all(&self, other: &Self) -> bool { for (self_cell, other_cell) inself.storage.iter().zip(other.storage.iter()) { if (*self_cell & *other_cell) != *other_cell { returnfalse;
}
} true
}
/// Returns whether this set contains any longhand that `other` also contains. pubfn contains_any(&self, other: &Self) -> bool { for (self_cell, other_cell) inself.storage.iter().zip(other.storage.iter()) { if (*self_cell & *other_cell) != 0 { returntrue;
}
} false
}
/// Remove all the given properties from the set. #[inline] pubfn remove_all(&mutself, other: &Self) { for (self_cell, other_cell) inself.storage.iter_mut().zip(other.storage.iter()) {
*self_cell &= !*other_cell;
}
}
/// Return whether the given property is in the set #[inline] pubfn contains(&self, id: LonghandId) -> bool { let bit = id as usize;
(self.storage[bit / 32] & (1 << (bit % 32))) != 0
}
/// Return whether this set contains any reset longhand. #[inline] pubfn contains_any_reset(&self) -> bool { self.contains_any(Self::reset())
}
/// Add the given property to the set #[inline] pubfn insert(&mutself, id: LonghandId) { let bit = id as usize; self.storage[bit / 32] |= 1 << (bit % 32);
}
/// Remove the given property from the set #[inline] pubfn remove(&mutself, id: LonghandId) { let bit = id as usize; self.storage[bit / 32] &= !(1 << (bit % 32));
}
/// Clear all bits #[inline] pubfn clear(&mutself) { for cell in &mutself.storage {
*cell = 0
}
}
/// Returns whether the set is empty. #[inline] pubfn is_empty(&self) -> bool { self.storage.iter().all(|c| *c == 0)
}
}
/// An iterator over a set of longhand ids. pubstruct LonghandIdSetIterator<'a> {
longhands: &'a LonghandIdSet,
cur: usize,
}
impl<'a> Iterator for LonghandIdSetIterator<'a> { type Item = LonghandId;
/// An ArrayVec of subproperties, contains space for the longest shorthand except all. pubtype SubpropertiesVec<T> = ArrayVec<T, { property_counts::MAX_SHORTHAND_EXPANDED }>;
/// A stack-allocated vector of `PropertyDeclaration` /// large enough to parse one CSS `key: value` declaration. /// (Shorthands expand to multiple `PropertyDeclaration`s.) #[derive(Default)] pubstruct SourcePropertyDeclaration { /// The storage for the actual declarations (except for all). pub declarations: SubpropertiesVec<PropertyDeclaration>, /// Stored separately to keep SubpropertiesVec smaller. pub all_shorthand: AllShorthand,
}
// This is huge, but we allocate it on the stack and then never move it, // we only pass `&mut SourcePropertyDeclaration` references around.
size_of_test!(SourcePropertyDeclaration, 632);
impl SourcePropertyDeclaration { /// Create one with a single PropertyDeclaration. #[inline] pubfn with_one(decl: PropertyDeclaration) -> Self { letmut result = Self::default();
result.declarations.push(decl);
result
}
/// Similar to Vec::drain: leaves this empty when the return value is dropped. pubfn drain(&mutself) -> SourcePropertyDeclarationDrain {
SourcePropertyDeclarationDrain {
declarations: self.declarations.drain(..),
all_shorthand: mem::replace(&mutself.all_shorthand, AllShorthand::NotSet),
}
}
/// Reset to initial state pubfn clear(&mutself) { self.declarations.clear(); self.all_shorthand = AllShorthand::NotSet;
}
/// Push a single declaration. pubfn push(&mutself, declaration: PropertyDeclaration) { let _result = self.declarations.try_push(declaration);
debug_assert!(_result.is_ok());
}
}
/// Return type of SourcePropertyDeclaration::drain pubstruct SourcePropertyDeclarationDrain<'a> { /// A drain over the non-all declarations. pub declarations:
ArrayVecDrain<'a, PropertyDeclaration, { property_counts::MAX_SHORTHAND_EXPANDED }>, /// The all shorthand that was set. pub all_shorthand: AllShorthand,
}
/// An unparsed property value that contains `var()` functions. #[derive(Debug, Eq, PartialEq, ToShmem)] pubstruct UnparsedValue { /// The variable value, references and so on. pub(super) variable_value: custom_properties::VariableValue, /// The shorthand this came from.
from_shorthand: Option<ShorthandId>,
}
/// A simple cache for properties that come from a shorthand and have variable /// references. /// /// This cache works because of the fact that you can't have competing values /// for a given longhand coming from the same shorthand (but note that this is /// why the shorthand needs to be part of the cache key). pubtype ShorthandsWithPropertyReferencesCache =
FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>;
if computed_context
.builder
.invalid_non_custom_properties
.contains(longhand_id)
{ return invalid_at_computed_value_time();
}
iflet Some(shorthand_id) = self.from_shorthand { let key = (shorthand_id, longhand_id); if shorthand_cache.contains_key(&key) { // FIXME: This double lookup should be avoidable, but rustc // doesn't like that, see: // // https://github.com/rust-lang/rust/issues/82146 return Cow::Borrowed(&shorthand_cache[&key]);
}
}
let css = match custom_properties::substitute(
&self.variable_value,
custom_properties,
stylist,
computed_context,
) {
Ok(css) => css,
Err(..) => return invalid_at_computed_value_time(),
};
// As of this writing, only the base URL is used for property // values. // // NOTE(emilio): we intentionally pase `None` as the rule type here. // If something starts depending on it, it's probably a bug, since // it'd change how values are parsed depending on whether we're in a // @keyframes rule or not, for example... So think twice about // whether you want to do this! // // FIXME(emilio): ParsingMode is slightly fishy... let context = ParserContext::new(
Origin::Author,
&self.variable_value.url_data,
None,
ParsingMode::DEFAULT,
computed_context.quirks_mode, /* namespaces = */ Default::default(),
None,
None,
);
letmut decls = SourcePropertyDeclaration::default(); // parse_into takes care of doing `parse_entirely` for us. if shorthand
.parse_into(&mut decls, &context, &mut input)
.is_err()
{ return invalid_at_computed_value_time();
}
for declaration in decls.declarations.drain(..) { let longhand = declaration.id().as_longhand().unwrap(); if longhand.is_logical() { let writing_mode = computed_context.builder.writing_mode;
shorthand_cache.insert(
(shorthand, longhand.to_physical(writing_mode)),
declaration.clone(),
);
}
shorthand_cache.insert((shorthand, longhand), declaration);
}
let key = (shorthand, longhand_id); match shorthand_cache.get(&key) {
Some(decl) => Cow::Borrowed(decl), // NOTE: Under normal circumstances we should always have a value, but when prefs // change we might hit this case. Consider something like `animation-timeline`, which // is a conditionally-enabled longhand of `animation`: // // If we have a sheet with `animation: var(--foo)`, and the `animation-timeline` pref // enabled, then that expands to an `animation-timeline` declaration at parse time. // // If the user disables the pref and, some time later, we get here wanting to compute // `animation-timeline`, parse_into won't generate any declaration for it anymore, so // we haven't inserted in the cache. Computing to invalid / initial seems like the most // sensible thing to do here.
None => invalid_at_computed_value_time(),
}
}
} /// A parsed all-shorthand value. pubenum AllShorthand { /// Not present.
NotSet, /// A CSS-wide keyword.
CSSWideKeyword(CSSWideKeyword), /// An all shorthand with var() references that we can't resolve right now.
WithVariables(Arc<UnparsedValue>),
}
impl AllShorthand { /// Iterates property declarations from the given all shorthand value. #[inline] pubfn declarations(&self) -> AllShorthandDeclarationIterator {
AllShorthandDeclarationIterator {
all_shorthand: self,
longhands: ShorthandId::All.longhands(),
}
}
}
/// An iterator over the all shorthand's shorthand declarations. pubstruct AllShorthandDeclarationIterator<'a> {
all_shorthand: &'a AllShorthand,
longhands: NonCustomPropertyIterator<LonghandId>,
}
impl<'a> Iterator for AllShorthandDeclarationIterator<'a> { type Item = PropertyDeclaration;
/// An iterator over all the property ids that are enabled for a given /// shorthand, if that shorthand is enabled for all content too. pubstruct NonCustomPropertyIterator<Item: 'static> {
filter: bool,
iter: std::slice::Iter<'static, Item>,
}
impl<Item> Iterator for NonCustomPropertyIterator<Item> where
Item: 'static + Copy + Into<NonCustomPropertyId>,
{ type Item = Item;
fn next(&mutself) -> Option<Self::Item> { loop { let id = *self.iter.next()?; if !self.filter || id.into().enabled_for_all_content() { return Some(id);
}
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet am 2026-06-18)
¤
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.