/* 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/. */ //! Supported CSS properties and the cascade. pub mod cascade; pub mod declaration_block; pub use self::cascade::*; pub use self::declaration_block::*; pub use self::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)] pub mod generated { include!(concat!(env!("OUT_DIR"), "/properties.rs")); } use crate::custom_properties::{self, ComputedCustomProperties}; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{nsCSSPropertyID, AnimatedPropertyID, RefPtr}; use crate::logical_geometry::WritingMode; use crate::parser::ParserContext; use crate::str::CssString; use crate::stylesheets::CssRuleType; use crate::stylesheets::Origin; use crate::stylist::Stylist; use crate::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)] pub struct 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 * only need to be listed in corresponding properties so that * 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)] pub enum 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. pub fn to_str(&self) -> &'static str { match *self { CSSWideKeyword::Initial => "initial", CSSWideKeyword::Inherit => "inherit", CSSWideKeyword::Unset => "unset", CSSWideKeyword::Revert => "revert", CSSWideKeyword::RevertLayer => "revert-layer", } } } impl CSSWideKeyword { /// Parses a CSS wide keyword from a CSS identifier. pub fn from_ident(ident: &str) -> Result { Ok(match_ignore_ascii_case! { ident, "initial" => CSSWideKeyword::Initial, "inherit" => CSSWideKeyword::Inherit, "unset" => CSSWideKeyword::Unset, "revert" => CSSWideKeyword::Revert, "revert-layer" => CSSWideKeyword::RevertLayer, _ => return Err(()), }) } /// Parses a CSS wide keyword completely. pub fn parse(input: &mut Parser) -> Result { let keyword = { let ident = input.expect_ident().map_err(|_| ())?; Self::from_ident(ident)? }; input.expect_exhausted().map_err(|_| ())?; Ok(keyword) } } /// A declaration using a CSS-wide keyword. #[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] pub struct 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)] pub struct 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, } /// A custom property declaration value is either an unparsed value or a CSS /// wide-keyword. #[derive(Clone, PartialEq, ToCss, ToShmem)] pub enum CustomDeclarationValue { /// An unparsed value. Unparsed(Arc), /// An already-parsed value. Parsed(Arc), /// A wide keyword. CSSWideKeyword(CSSWideKeyword), } /// A custom property declaration with the property name and the declared value. #[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] pub struct 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, } impl fmt::Debug for PropertyDeclaration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.id().to_css(&mut CssWriter::new(f))?; f.write_str(": ")?; // 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. let mut s = CssString::new(); self.to_css(&mut s)?; write!(f, "{}", s) } } /// A longhand or shorthand property. #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, ToComputedValue, ToResolvedValue, ToShmem, MallocSizeOf, )] #[repr(C)] pub struct NonCustomPropertyId(u16); impl ToCss for NonCustomPropertyId { #[inline] fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_str(self.name()) } } impl NonCustomPropertyId { /// Returns the underlying index, used for use counter. pub fn bit(self) -> usize { self.0 as usize } /// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`. #[cfg(feature = "gecko")] #[inline] pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { // unsafe: guaranteed by static_assert_nscsspropertyid. unsafe { mem::transmute(self.0 as i32) } } /// Convert an `nsCSSPropertyID` into a `NonCustomPropertyId`. #[cfg(feature = "gecko")] #[inline] pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Option { 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. pub fn unaliased(self) -> Self { let Some(alias_id) = self.as_alias() else { return self; }; alias_id.aliased_property() } /// Turns this `NonCustomPropertyId` into a `PropertyId`. #[inline] pub fn to_property_id(self) -> PropertyId { PropertyId::NonCustom(self) } /// Returns a longhand id, if this property is one. #[inline] pub fn as_longhand(self) -> Option { if self.0 < property_counts::LONGHANDS as u16 { return Some(unsafe { mem::transmute(self.0 as u16) }); } None } /// Returns a shorthand id, if this property is one. #[inline] pub fn as_shorthand(self) -> Option { if self.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] pub fn as_alias(self) -> Option { debug_assert!((self.0 as usize) < property_counts::NON_CUSTOM); if self.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] pub fn longhand_or_shorthand(self) -> Result { 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] pub const fn from_longhand(id: LonghandId) -> Self { Self(id as u16) } /// Converts a shorthand id into a non-custom property id. #[inline] pub const fn 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] pub const fn from_alias(id: AliasId) -> Self { Self((id as u16) + (property_counts::LONGHANDS_AND_SHORTHANDS as u16)) } } impl From for NonCustomPropertyId { #[inline] fn from(id: LonghandId) -> Self { Self::from_longhand(id) } } impl From for NonCustomPropertyId { #[inline] fn from(id: ShorthandId) -> Self { Self::from_shorthand(id) } } impl From for NonCustomPropertyId { #[inline] fn from(id: AliasId) -> Self { Self::from_alias(id) } } /// Representation of a CSS property, that is, either a longhand, a shorthand, or a custom /// property. #[derive(Clone, Eq, PartialEq, Debug)] pub enum PropertyId { /// An alias for a shorthand property. NonCustom(NonCustomPropertyId), /// A custom property. Custom(custom_properties::Name), } impl ToCss for PropertyId { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { PropertyId::NonCustom(id) => dest.write_str(id.name()), PropertyId::Custom(ref name) => { dest.write_str("--")?; serialize_atom_name(name, dest) }, } } } impl PropertyId { /// Return the longhand id that this property id represents. #[inline] pub fn longhand_id(&self) -> Option { self.non_custom_non_alias_id()?.as_longhand() } /// Returns true if this property is one of the animatable properties. pub fn is_animatable(&self) -> bool { match self { Self::NonCustom(id) => id.is_animatable(), #[cfg(feature = "gecko")] Self::Custom(_) => true, #[cfg(feature = "servo")] Self::Custom(_) => false, } } /// Returns true if this property is one of the transitionable properties. pub fn is_transitionable(&self) -> bool { match self { Self::NonCustom(id) => id.is_transitionable(), 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. pub fn parse_unchecked_for_testing(name: &str) -> Result { 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] pub fn parse_enabled_for_all_content(name: &str) -> Result { 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] pub fn parse(name: &str, context: &ParserContext) -> Result { 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] pub fn parse_ignoring_rule_type(name: &str, context: &ParserContext) -> Result { 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] pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Option { Some(NonCustomPropertyId::from_nscsspropertyid(id)?.to_property_id()) } /// Returns a property id from Gecko's AnimatedPropertyID. #[cfg(feature = "gecko")] #[inline] pub fn from_gecko_animated_property_id(property: &AnimatedPropertyID) -> Option { 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] pub fn is_shorthand(&self) -> bool { self.as_shorthand().is_ok() } /// Given this property id, get it either as a shorthand or as a /// `PropertyDeclarationId`. pub fn as_shorthand(&self) -> Result { 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. pub fn non_custom_id(&self) -> Option { 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 { 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] pub fn enabled_for_all_content(&self) -> bool { let id = match self.non_custom_id() { // Custom properties are allowed everywhere None => return true, 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] pub fn to_nscsspropertyid_resolving_aliases(&self) -> nsCSSPropertyID { match self.non_custom_non_alias_id() { Some(id) => id.to_nscsspropertyid(), None => nsCSSPropertyID::eCSSPropertyExtra_variable, } } fn allowed_in(&self, context: &ParserContext) -> bool { let id = match self.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 = match self.non_custom_id() { // Custom properties are allowed everywhere None => return true, 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. pub fn 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. pub fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { if let 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(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_str(self.name()) } } impl fmt::Debug for LonghandId { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(self.name()) } } impl LonghandId { /// Get the name of this longhand property. #[inline] pub fn name(&self) -> &'static str { NonCustomPropertyId::from(*self).name() } /// Returns whether the longhand property is inherited by default. #[inline] pub fn inherited(self) -> bool { !LonghandIdSet::reset().contains(self) } /// Returns true if the property is one that is ignored when document /// colors are disabled. #[inline] pub fn 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. pub fn 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`. pub fn is_longhand_of(self, shorthand: ShorthandId) -> bool { self.shorthands().any(|s| s == shorthand) } /// Returns whether this property is animatable. #[inline] pub fn is_animatable(self) -> bool { NonCustomPropertyId::from(self).is_animatable() } /// Returns whether this property is animatable in a discrete way. #[inline] pub fn is_discrete_animatable(self) -> bool { LonghandIdSet::discrete_animatable().contains(self) } /// Converts from a LonghandId to an adequate nsCSSPropertyID. #[cfg(feature = "gecko")] #[inline] pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { NonCustomPropertyId::from(self).to_nscsspropertyid() } #[cfg(feature = "gecko")] /// Returns a longhand id from Gecko's nsCSSPropertyID. pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Option { NonCustomPropertyId::from_nscsspropertyid(id)? .unaliased() .as_longhand() } /// Return whether this property is logical. #[inline] pub fn is_logical(self) -> bool { LonghandIdSet::logical().contains(self) } } impl ToCss for ShorthandId { #[inline] fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_str(self.name()) } } impl ShorthandId { /// Get the name for this shorthand property. #[inline] pub fn name(&self) -> &'static str { NonCustomPropertyId::from(*self).name() } /// Converts from a ShorthandId to an adequate nsCSSPropertyID. #[cfg(feature = "gecko")] #[inline] pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { NonCustomPropertyId::from(self).to_nscsspropertyid() } /// Converts from a nsCSSPropertyID to a ShorthandId. #[cfg(feature = "gecko")] #[inline] pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Option { NonCustomPropertyId::from_nscsspropertyid(id)? .unaliased() .as_shorthand() } /// Finds and returns an appendable value for the given declarations. /// /// Returns the optional appendable value. pub fn get_shorthand_appendable_value<'a, 'b: 'a>( self, declarations: &'a [&'b PropertyDeclaration], ) -> Option> { let first_declaration = declarations.get(0)?; let rest = || declarations.iter().skip(1); // https://drafts.csswg.org/css-variables/#variables-in-shorthands if let Some(css) = first_declaration.with_variables_from_shorthand(self) { if rest().all(|d| d.with_variables_from_shorthand(self) == Some(css)) { return Some(AppendableValue::Css(css)); } return None; } // Check whether they are all the same CSS-wide keyword. if let 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; } if self == 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] pub fn is_legacy_shorthand(self) -> bool { self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND) } } fn parse_non_custom_property_declaration_value_into<'i>( declarations: &mut SourcePropertyDeclaration, context: &ParserContext, input: &mut Parser<'i, '_>, start: &cssparser::ParserState, parse_entirely_into: impl FnOnce( &mut SourcePropertyDeclaration, &mut Parser<'i, '_>, ) -> Result<(), ParseError<'i>>, parsed_wide_keyword: impl FnOnce(&mut SourcePropertyDeclaration, CSSWideKeyword), parsed_custom: impl FnOnce(&mut SourcePropertyDeclaration, custom_properties::VariableValue), ) -> Result<(), ParseError<'i>> { let mut starts_with_curly_block = false; if let Ok(token) = input.next() { match token { cssparser::Token::Ident(ref ident) => match CSSWideKeyword::from_ident(ident) { Ok(wk) => { if input.expect_exhausted().is_ok() { return Ok(parsed_wide_keyword(declarations, wk)); } }, Err(()) => {}, }, cssparser::Token::CurlyBracketBlock => { starts_with_curly_block = true; }, _ => {}, } }; 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(); let mut at_start = start_pos == input.position(); let mut invalid = false; while let Ok(token) = input.next() { if matches!(token, cssparser::Token::CurlyBracketBlock) { if !starts_with_curly_block || !at_start { invalid = true; break; } } else if 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] pub fn 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] pub fn get_css_wide_keyword(&self) -> Option { 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. pub fn 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. pub fn is_animatable(&self) -> bool { self.id().is_animatable() } /// Returns true if this property is a custom property, false /// otherwise. pub fn is_custom(&self) -> bool { matches!(*self, PropertyDeclaration::Custom(..)) } /// The `context` parameter controls this: /// /// /// > The inside of 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 pub fn 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(), }, )) } } }, )?; }, } if let 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)] pub enum OwnedPropertyDeclarationId { /// A longhand. Longhand(LonghandId), /// A custom property declaration. Custom(custom_properties::Name), } impl OwnedPropertyDeclarationId { /// Return whether this property is logical. #[inline] pub fn is_logical(&self) -> bool { self.as_borrowed().is_logical() } /// Returns the corresponding PropertyDeclarationId. #[inline] pub fn as_borrowed(&self) -> PropertyDeclarationId { match self { Self::Longhand(id) => PropertyDeclarationId::Longhand(*id), Self::Custom(name) => PropertyDeclarationId::Custom(name), } } /// Convert an `AnimatedPropertyID` into an `OwnedPropertyDeclarationId`. #[cfg(feature = "gecko")] #[inline] pub fn from_gecko_animated_property_id(property: &AnimatedPropertyID) -> Option { 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)] pub enum PropertyDeclarationId<'a> { /// A longhand. Longhand(LonghandId), /// A custom property declaration. Custom(&'a custom_properties::Name), } impl<'a> ToCss for PropertyDeclarationId<'a> { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()), PropertyDeclarationId::Custom(name) => { dest.write_str("--")?; serialize_atom_name(name, dest) }, } } } impl<'a> PropertyDeclarationId<'a> { /// Returns PropertyFlags for given property. #[inline(always)] pub fn flags(&self) -> PropertyFlags { match self { Self::Longhand(id) => id.flags(), Self::Custom(_) => PropertyFlags::empty(), } } /// Convert to an OwnedPropertyDeclarationId. pub fn to_owned(&self) -> OwnedPropertyDeclarationId { match self { PropertyDeclarationId::Longhand(id) => OwnedPropertyDeclarationId::Longhand(*id), PropertyDeclarationId::Custom(name) => { OwnedPropertyDeclarationId::Custom((*name).clone()) }, } } /// Whether a given declaration id is either the same as `other`, or a /// longhand of it. pub fn 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. pub fn 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. pub fn name(&self) -> Cow<'static, str> { match *self { PropertyDeclarationId::Longhand(id) => id.name().into(), PropertyDeclarationId::Custom(name) => { let mut s = String::new(); write!(&mut s, "--{}", name).unwrap(); s.into() }, } } /// Returns longhand id if it is, None otherwise. #[inline] pub fn as_longhand(&self) -> Option { match *self { PropertyDeclarationId::Longhand(id) => Some(id), _ => None, } } /// Return whether this property is logical. #[inline] pub fn is_logical(&self) -> bool { match self { PropertyDeclarationId::Longhand(id) => id.is_logical(), PropertyDeclarationId::Custom(_) => false, } } /// If this is a logical property, return the corresponding physical one in /// the given writing mode. /// /// Otherwise, return unchanged. #[inline] pub fn to_physical(&self, wm: WritingMode) -> Self { match self { Self::Longhand(id) => Self::Longhand(id.to_physical(wm)), Self::Custom(_) => self.clone(), } } /// Returns whether this property is animatable. #[inline] pub fn is_animatable(&self) -> bool { match self { Self::Longhand(id) => id.is_animatable(), #[cfg(feature = "gecko")] PropertyDeclarationId::Custom(_) => true, #[cfg(feature = "servo")] PropertyDeclarationId::Custom(_) => false, } } /// Returns whether this property is animatable in a discrete way. #[inline] pub fn is_discrete_animatable(&self) -> bool { match self { Self::Longhand(longhand) => longhand.is_discrete_animatable(), // TODO(bug 1885995): Refine this. #[cfg(feature = "gecko")] Self::Custom(_) => true, #[cfg(feature = "servo")] Self::Custom(_) => false, } } /// Returns whether this property is transitionable. #[inline] pub fn is_transitionable(self) -> bool { match self { PropertyDeclarationId::Longhand(id) => id.is_transitionable(), PropertyDeclarationId::Custom(..) => false, } } /// Converts from a to an adequate nsCSSPropertyID, returning /// eCSSPropertyExtra_variable for custom properties. #[cfg(feature = "gecko")] #[inline] pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { match self { 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] pub fn to_gecko_animated_property_id(&self) -> AnimatedPropertyID { match self { Self::Longhand(id) => AnimatedPropertyID { mID: id.to_nscsspropertyid(), mCustomName: RefPtr::null(), }, Self::Custom(name) => { let mut 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)] pub struct NonCustomPropertyIdSet { storage: [u32; ((property_counts::NON_CUSTOM as usize) - 1 + 32) / 32], } impl NonCustomPropertyIdSet { /// Creates an empty `NonCustomPropertyIdSet`. pub fn new() -> Self { Self { storage: Default::default(), } } /// Insert a non-custom-property in the set. #[inline] pub fn insert(&mut self, id: NonCustomPropertyId) { let bit = id.0 as usize; self.storage[bit / 32] |= 1 << (bit % 32); } /// Return whether the given property is in the set #[inline] pub fn contains(&self, id: NonCustomPropertyId) -> bool { let bit = id.0 as usize; (self.storage[bit / 32] & (1 << (bit % 32))) != 0 } } /// A set of longhand properties #[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] pub struct LonghandIdSet { storage: [u32; ((property_counts::LONGHANDS as usize) - 1 + 32) / 32], } to_shmem::impl_trivial_to_shmem!(LonghandIdSet); impl LonghandIdSet { /// Return an empty LonghandIdSet. #[inline] pub fn new() -> Self { Self { storage: Default::default(), } } /// Iterate over the current longhand id set. pub fn iter(&self) -> LonghandIdSetIterator { LonghandIdSetIterator { longhands: self, cur: 0, } } /// Returns whether this set contains at least every longhand that `other` /// also contains. pub fn contains_all(&self, other: &Self) -> bool { for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) { if (*self_cell & *other_cell) != *other_cell { return false; } } true } /// Returns whether this set contains any longhand that `other` also contains. pub fn contains_any(&self, other: &Self) -> bool { for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) { if (*self_cell & *other_cell) != 0 { return true; } } false } /// Remove all the given properties from the set. #[inline] pub fn remove_all(&mut self, other: &Self) { for (self_cell, other_cell) in self.storage.iter_mut().zip(other.storage.iter()) { *self_cell &= !*other_cell; } } /// Return whether the given property is in the set #[inline] pub fn 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] pub fn contains_any_reset(&self) -> bool { self.contains_any(Self::reset()) } /// Add the given property to the set #[inline] pub fn insert(&mut self, id: LonghandId) { let bit = id as usize; self.storage[bit / 32] |= 1 << (bit % 32); } /// Remove the given property from the set #[inline] pub fn remove(&mut self, id: LonghandId) { let bit = id as usize; self.storage[bit / 32] &= !(1 << (bit % 32)); } /// Clear all bits #[inline] pub fn clear(&mut self) { for cell in &mut self.storage { *cell = 0 } } /// Returns whether the set is empty. #[inline] pub fn is_empty(&self) -> bool { self.storage.iter().all(|c| *c == 0) } } /// An iterator over a set of longhand ids. pub struct LonghandIdSetIterator<'a> { longhands: &'a LonghandIdSet, cur: usize, } impl<'a> Iterator for LonghandIdSetIterator<'a> { type Item = LonghandId; fn next(&mut self) -> Option { loop { if self.cur >= property_counts::LONGHANDS { return None; } let id: LonghandId = unsafe { mem::transmute(self.cur as u16) }; self.cur += 1; if self.longhands.contains(id) { return Some(id); } } } } /// An ArrayVec of subproperties, contains space for the longest shorthand except all. pub type SubpropertiesVec = ArrayVec; /// A stack-allocated vector of `PropertyDeclaration` /// large enough to parse one CSS `key: value` declaration. /// (Shorthands expand to multiple `PropertyDeclaration`s.) #[derive(Default)] pub struct SourcePropertyDeclaration { /// The storage for the actual declarations (except for all). pub declarations: SubpropertiesVec, /// 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, 568); impl SourcePropertyDeclaration { /// Create one with a single PropertyDeclaration. #[inline] pub fn with_one(decl: PropertyDeclaration) -> Self { let mut result = Self::default(); result.declarations.push(decl); result } /// Similar to Vec::drain: leaves this empty when the return value is dropped. pub fn drain(&mut self) -> SourcePropertyDeclarationDrain { SourcePropertyDeclarationDrain { declarations: self.declarations.drain(..), all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet), } } /// Reset to initial state pub fn clear(&mut self) { self.declarations.clear(); self.all_shorthand = AllShorthand::NotSet; } /// Whether we're empty. pub fn is_empty(&self) -> bool { self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet) } /// Push a single declaration. pub fn push(&mut self, declaration: PropertyDeclaration) { let _result = self.declarations.try_push(declaration); debug_assert!(_result.is_ok()); } } /// Return type of SourcePropertyDeclaration::drain pub struct 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)] pub struct UnparsedValue { /// The variable value, references and so on. pub(super) variable_value: custom_properties::VariableValue, /// The shorthand this came from. from_shorthand: Option, } impl ToCss for UnparsedValue { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { // https://drafts.csswg.org/css-variables/#variables-in-shorthands if self.from_shorthand.is_none() { self.variable_value.to_css(dest)?; } Ok(()) } } /// 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). pub type ShorthandsWithPropertyReferencesCache = FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>; impl UnparsedValue { fn substitute_variables<'cache>( &self, longhand_id: LonghandId, custom_properties: &ComputedCustomProperties, stylist: &Stylist, computed_context: &computed::Context, shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache, ) -> Cow<'cache, PropertyDeclaration> { let invalid_at_computed_value_time = || { let keyword = if longhand_id.inherited() { CSSWideKeyword::Inherit } else { CSSWideKeyword::Initial }; Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)) }; if computed_context .builder .invalid_non_custom_properties .contains(longhand_id) { return invalid_at_computed_value_time(); } if let 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, ); let mut input = ParserInput::new(&css); let mut input = Parser::new(&mut input); input.skip_whitespace(); if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) { return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)); } let shorthand = match self.from_shorthand { None => { return match input.parse_entirely(|input| longhand_id.parse_value(&context, input)) { Ok(decl) => Cow::Owned(decl), Err(..) => invalid_at_computed_value_time(), } }, Some(shorthand) => shorthand, }; let mut 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. pub enum 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), } impl Default for AllShorthand { fn default() -> Self { Self::NotSet } } impl AllShorthand { /// Iterates property declarations from the given all shorthand value. #[inline] pub fn declarations(&self) -> AllShorthandDeclarationIterator { AllShorthandDeclarationIterator { all_shorthand: self, longhands: ShorthandId::All.longhands(), } } } /// An iterator over the all shorthand's shorthand declarations. pub struct AllShorthandDeclarationIterator<'a> { all_shorthand: &'a AllShorthand, longhands: NonCustomPropertyIterator, } impl<'a> Iterator for AllShorthandDeclarationIterator<'a> { type Item = PropertyDeclaration; #[inline] fn next(&mut self) -> Option { match *self.all_shorthand { AllShorthand::NotSet => None, AllShorthand::CSSWideKeyword(ref keyword) => Some( PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword), ), AllShorthand::WithVariables(ref unparsed) => { Some(PropertyDeclaration::WithVariables(VariableDeclaration { id: self.longhands.next()?, value: unparsed.clone(), })) }, } } } /// An iterator over all the property ids that are enabled for a given /// shorthand, if that shorthand is enabled for all content too. pub struct NonCustomPropertyIterator { filter: bool, iter: std::slice::Iter<'static, Item>, } impl Iterator for NonCustomPropertyIterator where Item: 'static + Copy + Into, { type Item = Item; fn next(&mut self) -> Option { loop { let id = *self.iter.next()?; if !self.filter || id.into().enabled_for_all_content() { return Some(id); } } } }