Source code

Revision control

Copy as Markdown

Other Tools

/* 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.
///
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<Self, ()> {
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<Self, ()> {
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<UnparsedValue>,
}
/// 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<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)]
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<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.
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<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.
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<LonghandId> {
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<ShorthandId> {
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<AliasId> {
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<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]
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<LonghandId> for NonCustomPropertyId {
#[inline]
fn from(id: LonghandId) -> Self {
Self::from_longhand(id)
}
}
impl From<ShorthandId> for NonCustomPropertyId {
#[inline]
fn from(id: ShorthandId) -> Self {
Self::from_shorthand(id)
}
}
impl From<AliasId> 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<W>(&self, dest: &mut CssWriter<W>) -> 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<LonghandId> {
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(),
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, ()> {
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<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]
pub fn 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]
pub fn 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]
pub fn 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]
pub fn 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]
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<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.
pub fn 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]
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<W>(&self, dest: &mut CssWriter<W>) -> 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<Self> {
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<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]
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<Self> {
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<AppendableValue<'a, 'b>> {
let first_declaration = declarations.get(0)?;
let rest = || declarations.iter().skip(1);
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<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.
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 <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
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<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)]
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<W>(&self, dest: &mut CssWriter<W>) -> 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<LonghandId> {
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(),
Self::Custom(_) => true,
}
}
/// 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.
Self::Custom(_) => true,
}
}
/// 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<Self::Item> {
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<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)]
pub struct 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]
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<ShorthandId>,
}
impl ToCss for UnparsedValue {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
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:
//
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<UnparsedValue>),
}
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<LonghandId>,
}
impl<'a> Iterator for AllShorthandDeclarationIterator<'a> {
type Item = PropertyDeclaration;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
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<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(&mut self) -> Option<Self::Item> {
loop {
let id = *self.iter.next()?;
if !self.filter || id.into().enabled_for_all_content() {
return Some(id);
}
}
}
}