summaryrefslogtreecommitdiffstats
path: root/servo/components/style/properties/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--servo/components/style/properties/mod.rs1531
1 files changed, 1531 insertions, 0 deletions
diff --git a/servo/components/style/properties/mod.rs b/servo/components/style/properties/mod.rs
new file mode 100644
index 0000000000..7adb6d4ae6
--- /dev/null
+++ b/servo/components/style/properties/mod.rs
@@ -0,0 +1,1531 @@
+/* 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"));
+
+ #[cfg(feature = "gecko")]
+ #[allow(unsafe_code, missing_docs)]
+ pub mod gecko {
+ include!(concat!(env!("OUT_DIR"), "/gecko_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::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;
+ /// This shorthand property is accessible from getComputedStyle.
+ const SHORTHAND_IN_GETCS = 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 {
+ /// A value.
+ Value(Arc<custom_properties::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
+ None => return true,
+ 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);
+
+ // 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)
+ }
+}
+
+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:
+ ///
+ /// <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
+ 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::Value(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) => {
+ let declaration = input
+ .try_parse(CSSWideKeyword::parse)
+ .map(|keyword| PropertyDeclaration::css_wide_keyword(longhand_id, keyword))
+ .or_else(|()| {
+ input.look_for_var_or_env_functions();
+ input.parse_entirely(|input| longhand_id.parse_value(context, input))
+ })
+ .or_else(|err| {
+ while let Ok(_) = input.next() {} // Look for var() after the error.
+ if !input.seen_var_or_env_functions() {
+ return Err(err);
+ }
+ input.reset(&start);
+ let variable_value =
+ custom_properties::VariableValue::parse(input, &context.url_data)?;
+ Ok(PropertyDeclaration::WithVariables(VariableDeclaration {
+ id: longhand_id,
+ value: Arc::new(UnparsedValue {
+ variable_value,
+ from_shorthand: None,
+ }),
+ }))
+ })?;
+ declarations.push(declaration)
+ },
+ Err(shorthand_id) => {
+ if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
+ if shorthand_id == ShorthandId::All {
+ declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword)
+ } else {
+ for longhand in shorthand_id.longhands() {
+ declarations
+ .push(PropertyDeclaration::css_wide_keyword(longhand, keyword));
+ }
+ }
+ } else {
+ input.look_for_var_or_env_functions();
+ // Not using parse_entirely here: each
+ // ${shorthand.ident}::parse_into function needs to do so
+ // *before* pushing to `declarations`.
+ shorthand_id
+ .parse_into(declarations, context, input)
+ .or_else(|err| {
+ while let Ok(_) = input.next() {} // Look for var() after the error.
+ if !input.seen_var_or_env_functions() {
+ return Err(err);
+ }
+
+ input.reset(&start);
+ let variable_value =
+ custom_properties::VariableValue::parse(input, &context.url_data)?;
+ 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(),
+ },
+ ))
+ }
+ }
+ Ok(())
+ })?;
+ }
+ },
+ }
+ 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 1846516): 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`
+ /// Note that the rust AnimatedPropertyID doesn't implement Drop, so owned controls whether the
+ /// custom name should be addrefed or not.
+ ///
+ /// FIXME(emilio, bug 1870107): This is a bit error-prone. 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, owned: bool) -> 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 = if owned {
+ (*name).clone().into_addrefed()
+ } else {
+ name.as_ptr()
+ };
+ 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,
+ {
+ // 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),
+ None => {
+ // FIXME: We should always have the key here but it seems
+ // sometimes we don't, see bug 1696409.
+ #[cfg(feature = "gecko")]
+ {
+ if crate::gecko_bindings::structs::GECKO_IS_NIGHTLY {
+ panic!("Expected {:?} to be in the cache but it was not!", key);
+ }
+ }
+ 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);
+ }
+ }
+ }
+}