Skip to main content

style/properties/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Supported CSS properties and the cascade.
6
7pub mod cascade;
8pub mod declaration_block;
9pub mod shorthands;
10
11pub use self::cascade::*;
12pub use self::declaration_block::*;
13pub use self::generated::*;
14
15/// The CSS properties supported by the style system.
16/// Generated from the properties.mako.rs template by build.rs
17#[macro_use]
18#[allow(unsafe_code)]
19#[deny(missing_docs)]
20pub mod generated {
21    include!(concat!(env!("OUT_DIR"), "/properties.rs"));
22}
23
24use crate::applicable_declarations::RevertKind;
25use crate::custom_properties::{self, ComputedSubstitutionFunctions, SubstitutionResult};
26use crate::derives::*;
27use crate::dom::AttributeTracker;
28#[cfg(feature = "gecko")]
29use crate::gecko_bindings::structs::{CSSPropertyId, NonCustomCSSPropertyId, RefPtr};
30use crate::logical_geometry::WritingMode;
31use crate::parser::ParserContext;
32use crate::stylesheets::CssRuleType;
33use crate::stylesheets::Origin;
34use crate::stylist::Stylist;
35use crate::values::{computed, serialize_atom_name};
36use arrayvec::{ArrayVec, Drain as ArrayVecDrain};
37use cssparser::{match_ignore_ascii_case, Parser, ParserInput};
38use rustc_hash::FxHashMap;
39use servo_arc::Arc;
40use std::{
41    borrow::Cow,
42    fmt::{self, Write},
43    mem,
44};
45use style_traits::{
46    CssString, CssWriter, KeywordsCollectFn, ParseError, ParsingMode, SpecifiedValueInfo, ToCss,
47    ToTyped, TypedValue,
48};
49use thin_vec::ThinVec;
50
51bitflags! {
52    /// A set of flags for properties.
53    #[derive(Clone, Copy)]
54    pub struct PropertyFlags: u16 {
55        /// This longhand property applies to ::first-letter.
56        const APPLIES_TO_FIRST_LETTER = 1 << 1;
57        /// This longhand property applies to ::first-line.
58        const APPLIES_TO_FIRST_LINE = 1 << 2;
59        /// This longhand property applies to ::placeholder.
60        const APPLIES_TO_PLACEHOLDER = 1 << 3;
61        ///  This longhand property applies to ::cue.
62        const APPLIES_TO_CUE = 1 << 4;
63        /// This longhand property applies to ::marker.
64        const APPLIES_TO_MARKER = 1 << 5;
65        /// This property is a legacy shorthand.
66        ///
67        /// https://drafts.csswg.org/css-cascade/#legacy-shorthand
68        const IS_LEGACY_SHORTHAND = 1 << 6;
69
70        /* The following flags are currently not used in Rust code, they
71         * only need to be listed in corresponding properties so that
72         * they can be checked in the C++ side via ServoCSSPropList.h. */
73
74        /// This property can be animated on the compositor.
75        const CAN_ANIMATE_ON_COMPOSITOR = 0;
76        /// See data.py's documentation about the affects_flags.
77        const AFFECTS_LAYOUT = 0;
78        #[allow(missing_docs)]
79        const AFFECTS_OVERFLOW = 0;
80        #[allow(missing_docs)]
81        const AFFECTS_PAINT = 0;
82    }
83}
84
85/// An enum to represent a CSS Wide keyword.
86#[derive(
87    Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
88)]
89pub enum CSSWideKeyword {
90    /// The `initial` keyword.
91    Initial,
92    /// The `inherit` keyword.
93    Inherit,
94    /// The `unset` keyword.
95    Unset,
96    /// The `revert` keyword.
97    Revert,
98    /// The `revert-layer` keyword.
99    RevertLayer,
100    /// The `revert-rule` keyword.
101    RevertRule,
102}
103
104impl CSSWideKeyword {
105    /// Returns the string representation of the keyword.
106    pub fn to_str(&self) -> &'static str {
107        match *self {
108            Self::Initial => "initial",
109            Self::Inherit => "inherit",
110            Self::Unset => "unset",
111            Self::Revert => "revert",
112            Self::RevertLayer => "revert-layer",
113            Self::RevertRule => "revert-rule",
114        }
115    }
116
117    /// Parses a CSS wide keyword from a CSS identifier.
118    pub fn from_ident(ident: &str) -> Result<Self, ()> {
119        Ok(match_ignore_ascii_case! { ident,
120            "initial" => Self::Initial,
121            "inherit" => Self::Inherit,
122            "unset" => Self::Unset,
123            "revert" => Self::Revert,
124            "revert-layer" => Self::RevertLayer,
125            "revert-rule" if static_prefs::pref!("layout.css.revert-rule.enabled") => Self::RevertRule,
126            _ => return Err(()),
127        })
128    }
129
130    /// Parses a CSS wide keyword completely.
131    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
132        let keyword = {
133            let ident = input.expect_ident().map_err(|_| ())?;
134            Self::from_ident(ident)?
135        };
136        input.expect_exhausted().map_err(|_| ())?;
137        Ok(keyword)
138    }
139
140    /// Returns the revert kind for this wide keyword.
141    pub fn revert_kind(self) -> Option<RevertKind> {
142        Some(match self {
143            Self::Initial | Self::Inherit | Self::Unset => return None,
144            Self::Revert => RevertKind::Origin,
145            Self::RevertLayer => RevertKind::Layer,
146            Self::RevertRule => RevertKind::Rule,
147        })
148    }
149}
150
151/// A declaration using a CSS-wide keyword.
152#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)]
153pub struct WideKeywordDeclaration {
154    #[css(skip)]
155    id: LonghandId,
156    /// The CSS-wide keyword.
157    pub keyword: CSSWideKeyword,
158}
159
160// XXX Switch back to ToTyped derive once it can automatically handle structs
161// Tracking in bug 1991631
162impl ToTyped for WideKeywordDeclaration {
163    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
164        self.keyword.to_typed(dest)
165    }
166}
167
168/// An unparsed declaration that contains `var()` functions.
169#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf, ToTyped)]
170pub struct VariableDeclaration {
171    /// The id of the property this declaration represents.
172    #[css(skip)]
173    id: LonghandId,
174    /// The unparsed value of the variable.
175    #[ignore_malloc_size_of = "Arc"]
176    pub value: Arc<UnparsedValue>,
177}
178
179/// A custom property declaration value is either an unparsed value or a CSS
180/// wide-keyword.
181#[derive(Clone, PartialEq, ToCss, ToShmem)]
182pub enum CustomDeclarationValue {
183    /// An unparsed value.
184    Unparsed(Arc<custom_properties::SpecifiedValue>),
185    /// An already-parsed value.
186    Parsed(Arc<crate::properties_and_values::value::SpecifiedValue>),
187    /// A wide keyword.
188    CSSWideKeyword(CSSWideKeyword),
189}
190
191/// A custom property declaration with the property name and the declared value.
192#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf, ToTyped)]
193pub struct CustomDeclaration {
194    /// The name of the custom property.
195    #[css(skip)]
196    pub name: custom_properties::Name,
197    /// The value of the custom property.
198    #[ignore_malloc_size_of = "Arc"]
199    pub value: CustomDeclarationValue,
200}
201
202impl fmt::Debug for PropertyDeclaration {
203    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204        self.id().to_css(&mut CssWriter::new(f))?;
205        f.write_str(": ")?;
206
207        // Because PropertyDeclaration::to_css requires CssStringWriter, we can't write
208        // it directly to f, and need to allocate an intermediate string. This is
209        // fine for debug-only code.
210        let mut s = CssString::new();
211        self.to_css(&mut s)?;
212        write!(f, "{}", s)
213    }
214}
215
216/// A longhand or shorthand property.
217#[derive(
218    Clone, Copy, Debug, PartialEq, Eq, Hash, ToComputedValue, ToResolvedValue, ToShmem, MallocSizeOf,
219)]
220#[repr(C)]
221pub struct NonCustomPropertyId(u16);
222
223impl ToCss for NonCustomPropertyId {
224    #[inline]
225    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
226    where
227        W: Write,
228    {
229        dest.write_str(self.name())
230    }
231}
232
233impl NonCustomPropertyId {
234    /// Returns the underlying index, used for use counter.
235    pub fn bit(self) -> usize {
236        self.0 as usize
237    }
238
239    /// Convert a `NonCustomPropertyId` into a `NonCustomCSSPropertyId`.
240    #[cfg(feature = "gecko")]
241    #[inline]
242    pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
243        // unsafe: guaranteed by static_assert_noncustomcsspropertyid.
244        unsafe { mem::transmute(self.0) }
245    }
246
247    /// Convert an `NonCustomCSSPropertyId` into a `NonCustomPropertyId`.
248    #[cfg(feature = "gecko")]
249    #[inline]
250    pub fn from_noncustomcsspropertyid(prop: NonCustomCSSPropertyId) -> Option<Self> {
251        let prop = prop as u16;
252        if prop >= property_counts::NON_CUSTOM as u16 {
253            return None;
254        }
255        // guaranteed by static_assert_noncustomcsspropertyid above.
256        Some(NonCustomPropertyId(prop))
257    }
258
259    /// Resolves the alias of a given property if needed.
260    pub fn unaliased(self) -> Self {
261        let Some(alias_id) = self.as_alias() else {
262            return self;
263        };
264        alias_id.aliased_property()
265    }
266
267    /// Turns this `NonCustomPropertyId` into a `PropertyId`.
268    #[inline]
269    pub fn to_property_id(self) -> PropertyId {
270        PropertyId::NonCustom(self)
271    }
272
273    /// Returns a longhand id, if this property is one.
274    #[inline]
275    pub fn as_longhand(self) -> Option<LonghandId> {
276        if self.0 < property_counts::LONGHANDS as u16 {
277            return Some(unsafe { mem::transmute(self.0 as u16) });
278        }
279        None
280    }
281
282    /// Returns a shorthand id, if this property is one.
283    #[inline]
284    pub fn as_shorthand(self) -> Option<ShorthandId> {
285        if self.0 >= property_counts::LONGHANDS as u16
286            && self.0 < property_counts::LONGHANDS_AND_SHORTHANDS as u16
287        {
288            return Some(unsafe { mem::transmute(self.0 - (property_counts::LONGHANDS as u16)) });
289        }
290        None
291    }
292
293    /// Returns an alias id, if this property is one.
294    #[inline]
295    pub fn as_alias(self) -> Option<AliasId> {
296        debug_assert!((self.0 as usize) < property_counts::NON_CUSTOM);
297        if self.0 >= property_counts::LONGHANDS_AND_SHORTHANDS as u16 {
298            return Some(unsafe {
299                mem::transmute(self.0 - (property_counts::LONGHANDS_AND_SHORTHANDS as u16))
300            });
301        }
302        None
303    }
304
305    /// Returns either a longhand or a shorthand, resolving aliases.
306    #[inline]
307    pub fn longhand_or_shorthand(self) -> Result<LonghandId, ShorthandId> {
308        let id = self.unaliased();
309        match id.as_longhand() {
310            Some(lh) => Ok(lh),
311            None => Err(id.as_shorthand().unwrap()),
312        }
313    }
314
315    /// Converts a longhand id into a non-custom property id.
316    #[inline]
317    pub const fn from_longhand(id: LonghandId) -> Self {
318        Self(id as u16)
319    }
320
321    /// Converts a shorthand id into a non-custom property id.
322    #[inline]
323    pub const fn from_shorthand(id: ShorthandId) -> Self {
324        Self((id as u16) + (property_counts::LONGHANDS as u16))
325    }
326
327    /// Converts an alias id into a non-custom property id.
328    #[inline]
329    pub const fn from_alias(id: AliasId) -> Self {
330        Self((id as u16) + (property_counts::LONGHANDS_AND_SHORTHANDS as u16))
331    }
332}
333
334impl From<LonghandId> for NonCustomPropertyId {
335    #[inline]
336    fn from(id: LonghandId) -> Self {
337        Self::from_longhand(id)
338    }
339}
340
341impl From<ShorthandId> for NonCustomPropertyId {
342    #[inline]
343    fn from(id: ShorthandId) -> Self {
344        Self::from_shorthand(id)
345    }
346}
347
348impl From<AliasId> for NonCustomPropertyId {
349    #[inline]
350    fn from(id: AliasId) -> Self {
351        Self::from_alias(id)
352    }
353}
354
355/// Representation of a CSS property, that is, either a longhand, a shorthand, or a custom
356/// property.
357#[derive(Clone, Eq, PartialEq, Debug)]
358pub enum PropertyId {
359    /// An alias for a shorthand property.
360    NonCustom(NonCustomPropertyId),
361    /// A custom property.
362    Custom(custom_properties::Name),
363}
364
365impl ToCss for PropertyId {
366    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
367    where
368        W: Write,
369    {
370        match *self {
371            PropertyId::NonCustom(id) => dest.write_str(id.name()),
372            PropertyId::Custom(ref name) => {
373                dest.write_str("--")?;
374                serialize_atom_name(name, dest)
375            },
376        }
377    }
378}
379
380impl PropertyId {
381    /// Return the longhand id that this property id represents.
382    #[inline]
383    pub fn longhand_id(&self) -> Option<LonghandId> {
384        self.non_custom_non_alias_id()?.as_longhand()
385    }
386
387    /// Returns true if this property is one of the animatable properties.
388    pub fn is_animatable(&self) -> bool {
389        match self {
390            Self::NonCustom(id) => id.is_animatable(),
391            Self::Custom(_) => true,
392        }
393    }
394
395    /// Returns a given property from the given name, _regardless of whether it is enabled or
396    /// not_, or Err(()) for unknown properties.
397    ///
398    /// Do not use for non-testing purposes.
399    pub fn parse_unchecked_for_testing(name: &str) -> Result<Self, ()> {
400        Self::parse_unchecked(name, None)
401    }
402
403    /// Parses a property name, and returns an error if it's unknown or isn't enabled for all
404    /// content.
405    #[inline]
406    pub fn parse_enabled_for_all_content(name: &str) -> Result<Self, ()> {
407        let id = Self::parse_unchecked(name, None)?;
408
409        if !id.enabled_for_all_content() {
410            return Err(());
411        }
412
413        Ok(id)
414    }
415
416    /// Parses a property name, and returns an error if it's unknown or isn't allowed in this
417    /// context.
418    #[inline]
419    pub fn parse(name: &str, context: &ParserContext) -> Result<Self, ()> {
420        let id = Self::parse_unchecked(name, context.use_counters)?;
421        if !id.allowed_in(context) {
422            return Err(());
423        }
424        Ok(id)
425    }
426
427    /// Parses a property name, and returns an error if it's unknown or isn't allowed in this
428    /// context, ignoring the rule_type checks.
429    ///
430    /// This is useful for parsing stuff from CSS values, for example.
431    #[inline]
432    pub fn parse_ignoring_rule_type(name: &str, context: &ParserContext) -> Result<Self, ()> {
433        let id = Self::parse_unchecked(name, None)?;
434        if !id.allowed_in_ignoring_rule_type(context) {
435            return Err(());
436        }
437        Ok(id)
438    }
439
440    /// Returns a property id from Gecko's NonCustomCSSPropertyId.
441    #[cfg(feature = "gecko")]
442    #[inline]
443    pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> {
444        Some(NonCustomPropertyId::from_noncustomcsspropertyid(id)?.to_property_id())
445    }
446
447    /// Returns a property id from Gecko's CSSPropertyId.
448    #[cfg(feature = "gecko")]
449    #[inline]
450    pub fn from_gecko_css_property_id(property: &CSSPropertyId) -> Option<Self> {
451        Some(
452            if property.mId == NonCustomCSSPropertyId::eCSSPropertyExtra_variable {
453                debug_assert!(!property.mCustomName.mRawPtr.is_null());
454                Self::Custom(unsafe { crate::Atom::from_raw(property.mCustomName.mRawPtr) })
455            } else {
456                Self::NonCustom(NonCustomPropertyId::from_noncustomcsspropertyid(
457                    property.mId,
458                )?)
459            },
460        )
461    }
462
463    /// Returns true if the property is a shorthand or shorthand alias.
464    #[inline]
465    pub fn is_shorthand(&self) -> bool {
466        self.as_shorthand().is_ok()
467    }
468
469    /// Given this property id, get it either as a shorthand or as a
470    /// `PropertyDeclarationId`.
471    pub fn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId<'_>> {
472        match *self {
473            Self::NonCustom(id) => match id.longhand_or_shorthand() {
474                Ok(lh) => Err(PropertyDeclarationId::Longhand(lh)),
475                Err(sh) => Ok(sh),
476            },
477            Self::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)),
478        }
479    }
480
481    /// Returns the `NonCustomPropertyId` corresponding to this property id.
482    pub fn non_custom_id(&self) -> Option<NonCustomPropertyId> {
483        match *self {
484            Self::Custom(_) => None,
485            Self::NonCustom(id) => Some(id),
486        }
487    }
488
489    /// Returns non-alias NonCustomPropertyId corresponding to this
490    /// property id.
491    fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> {
492        self.non_custom_id().map(NonCustomPropertyId::unaliased)
493    }
494
495    /// Whether the property is enabled for all content regardless of the
496    /// stylesheet it was declared on (that is, in practice only checks prefs).
497    #[inline]
498    pub fn enabled_for_all_content(&self) -> bool {
499        let id = match self.non_custom_id() {
500            // Custom properties are allowed everywhere
501            None => return true,
502            Some(id) => id,
503        };
504
505        id.enabled_for_all_content()
506    }
507
508    /// Converts this PropertyId in NonCustomCSSPropertyId, resolving aliases to the
509    /// resolved property, and returning eCSSPropertyExtra_variable for custom
510    /// properties.
511    #[cfg(feature = "gecko")]
512    #[inline]
513    pub fn to_noncustomcsspropertyid_resolving_aliases(&self) -> NonCustomCSSPropertyId {
514        match self.non_custom_non_alias_id() {
515            Some(id) => id.to_noncustomcsspropertyid(),
516            None => NonCustomCSSPropertyId::eCSSPropertyExtra_variable,
517        }
518    }
519
520    fn allowed_in(&self, context: &ParserContext) -> bool {
521        let id = match self.non_custom_id() {
522            // Custom properties are allowed everywhere, except `position-try`.
523            None => {
524                return !context
525                    .nesting_context
526                    .rule_types
527                    .contains(CssRuleType::PositionTry)
528            },
529            Some(id) => id,
530        };
531        id.allowed_in(context)
532    }
533
534    #[inline]
535    fn allowed_in_ignoring_rule_type(&self, context: &ParserContext) -> bool {
536        let id = match self.non_custom_id() {
537            // Custom properties are allowed everywhere
538            None => return true,
539            Some(id) => id,
540        };
541        id.allowed_in_ignoring_rule_type(context)
542    }
543
544    /// Whether the property supports the given CSS type.
545    /// `ty` should a bitflags of constants in style_traits::CssType.
546    pub fn supports_type(&self, ty: u8) -> bool {
547        let id = self.non_custom_non_alias_id();
548        id.map_or(0, |id| id.supported_types()) & ty != 0
549    }
550
551    /// Collect supported starting word of values of this property.
552    ///
553    /// See style_traits::SpecifiedValueInfo::collect_completion_keywords for more
554    /// details.
555    pub fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) {
556        if let Some(id) = self.non_custom_non_alias_id() {
557            id.collect_property_completion_keywords(f);
558        }
559        CSSWideKeyword::collect_completion_keywords(f);
560    }
561}
562
563impl ToCss for LonghandId {
564    #[inline]
565    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
566    where
567        W: Write,
568    {
569        dest.write_str(self.name())
570    }
571}
572
573impl fmt::Debug for LonghandId {
574    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
575        formatter.write_str(self.name())
576    }
577}
578
579impl LonghandId {
580    /// Get the name of this longhand property.
581    #[inline]
582    pub fn name(&self) -> &'static str {
583        NonCustomPropertyId::from(*self).name()
584    }
585
586    /// Returns whether the longhand property is inherited by default.
587    #[inline]
588    pub fn inherited(self) -> bool {
589        !LonghandIdSet::reset().contains(self)
590    }
591
592    /// Returns whether the longhand property is zoom-dependent.
593    #[inline]
594    pub fn zoom_dependent(self) -> bool {
595        LonghandIdSet::zoom_dependent().contains(self)
596    }
597
598    /// Returns true if the property is one that is ignored when document
599    /// colors are disabled.
600    #[inline]
601    pub fn ignored_when_document_colors_disabled(self) -> bool {
602        LonghandIdSet::ignored_when_colors_disabled().contains(self)
603    }
604
605    /// Returns whether this longhand is `non_custom` or is a longhand of it.
606    pub fn is_or_is_longhand_of(self, non_custom: NonCustomPropertyId) -> bool {
607        match non_custom.longhand_or_shorthand() {
608            Ok(lh) => self == lh,
609            Err(sh) => self.is_longhand_of(sh),
610        }
611    }
612
613    /// Returns whether this longhand is a longhand of `shorthand`.
614    pub fn is_longhand_of(self, shorthand: ShorthandId) -> bool {
615        self.shorthands().any(|s| s == shorthand)
616    }
617
618    /// Returns whether this property is animatable.
619    #[inline]
620    pub fn is_animatable(self) -> bool {
621        NonCustomPropertyId::from(self).is_animatable()
622    }
623
624    /// Returns whether this property is animatable in a discrete way.
625    #[inline]
626    pub fn is_discrete_animatable(self) -> bool {
627        LonghandIdSet::discrete_animatable().contains(self)
628    }
629
630    /// Converts from a LonghandId to an adequate NonCustomCSSPropertyId.
631    #[cfg(feature = "gecko")]
632    #[inline]
633    pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
634        NonCustomPropertyId::from(self).to_noncustomcsspropertyid()
635    }
636
637    #[cfg(feature = "gecko")]
638    /// Returns a longhand id from Gecko's NonCustomCSSPropertyId.
639    pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> {
640        NonCustomPropertyId::from_noncustomcsspropertyid(id)?
641            .unaliased()
642            .as_longhand()
643    }
644
645    /// Return whether this property is logical.
646    #[inline]
647    pub fn is_logical(self) -> bool {
648        LonghandIdSet::logical().contains(self)
649    }
650}
651
652impl ToCss for ShorthandId {
653    #[inline]
654    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
655    where
656        W: Write,
657    {
658        dest.write_str(self.name())
659    }
660}
661
662impl ShorthandId {
663    /// Get the name for this shorthand property.
664    #[inline]
665    pub fn name(&self) -> &'static str {
666        NonCustomPropertyId::from(*self).name()
667    }
668
669    /// Converts from a ShorthandId to an adequate NonCustomCSSPropertyId.
670    #[cfg(feature = "gecko")]
671    #[inline]
672    pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
673        NonCustomPropertyId::from(self).to_noncustomcsspropertyid()
674    }
675
676    /// Converts from a NonCustomCSSPropertyId to a ShorthandId.
677    #[cfg(feature = "gecko")]
678    #[inline]
679    pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> {
680        NonCustomPropertyId::from_noncustomcsspropertyid(id)?
681            .unaliased()
682            .as_shorthand()
683    }
684
685    /// Finds and returns an appendable value for the given declarations.
686    ///
687    /// Returns the optional appendable value.
688    pub fn get_shorthand_appendable_value<'a, 'b: 'a>(
689        self,
690        declarations: &'a [&'b PropertyDeclaration],
691    ) -> Option<AppendableValue<'a, 'b>> {
692        let first_declaration = declarations.get(0)?;
693        let rest = || declarations.iter().skip(1);
694
695        // https://drafts.csswg.org/css-variables/#variables-in-shorthands
696        if let Some(css) = first_declaration.with_variables_from_shorthand(self) {
697            if rest().all(|d| d.with_variables_from_shorthand(self) == Some(css)) {
698                return Some(AppendableValue::Css(css));
699            }
700            return None;
701        }
702
703        // Check whether they are all the same CSS-wide keyword.
704        if let Some(keyword) = first_declaration.get_css_wide_keyword() {
705            if rest().all(|d| d.get_css_wide_keyword() == Some(keyword)) {
706                return Some(AppendableValue::Css(keyword.to_str()));
707            }
708            return None;
709        }
710
711        if self == ShorthandId::All {
712            // 'all' only supports variables and CSS wide keywords.
713            return None;
714        }
715
716        // Check whether all declarations can be serialized as part of shorthand.
717        if declarations
718            .iter()
719            .all(|d| d.may_serialize_as_part_of_shorthand())
720        {
721            return Some(AppendableValue::DeclarationsForShorthand(
722                self,
723                declarations,
724            ));
725        }
726
727        None
728    }
729
730    /// Returns whether this property is a legacy shorthand.
731    #[inline]
732    pub fn is_legacy_shorthand(self) -> bool {
733        self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND)
734    }
735}
736
737fn parse_non_custom_property_declaration_value_into<'i>(
738    declarations: &mut SourcePropertyDeclaration,
739    context: &ParserContext,
740    input: &mut Parser<'i, '_>,
741    start: &cssparser::ParserState,
742    parse_entirely_into: impl FnOnce(
743        &mut SourcePropertyDeclaration,
744        &mut Parser<'i, '_>,
745    ) -> Result<(), ParseError<'i>>,
746    parsed_wide_keyword: impl FnOnce(&mut SourcePropertyDeclaration, CSSWideKeyword),
747    parsed_custom: impl FnOnce(&mut SourcePropertyDeclaration, custom_properties::VariableValue),
748) -> Result<(), ParseError<'i>> {
749    let mut starts_with_curly_block = false;
750    if let Ok(token) = input.next() {
751        match token {
752            cssparser::Token::Ident(ref ident) => match CSSWideKeyword::from_ident(ident) {
753                Ok(wk) => {
754                    if input.expect_exhausted().is_ok() {
755                        return Ok(parsed_wide_keyword(declarations, wk));
756                    }
757                },
758                Err(()) => {},
759            },
760            cssparser::Token::CurlyBracketBlock => {
761                starts_with_curly_block = true;
762            },
763            _ => {},
764        }
765    };
766
767    input.reset(&start);
768    input.look_for_arbitrary_substitution_functions(
769        if static_prefs::pref!("layout.css.attr.enabled") {
770            &["var", "env", "attr"]
771        } else {
772            &["var", "env"]
773        },
774    );
775
776    let err = match parse_entirely_into(declarations, input) {
777        Ok(()) => {
778            input.seen_arbitrary_substitution_functions();
779            return Ok(());
780        },
781        Err(e) => e,
782    };
783
784    // Look for var(), env() and top-level curly blocks after the error.
785    let start_pos = start.position();
786    let mut at_start = start_pos == input.position();
787    let mut invalid = false;
788    while let Ok(token) = input.next() {
789        if matches!(token, cssparser::Token::CurlyBracketBlock) {
790            if !starts_with_curly_block || !at_start {
791                invalid = true;
792                break;
793            }
794        } else if starts_with_curly_block {
795            invalid = true;
796            break;
797        }
798        at_start = false;
799    }
800    if !input.seen_arbitrary_substitution_functions() || invalid {
801        return Err(err);
802    }
803    input.reset(start);
804    let value = custom_properties::VariableValue::parse(
805        input,
806        Some(&context.namespaces.prefixes),
807        &context.url_data,
808    )?;
809    parsed_custom(declarations, value);
810    Ok(())
811}
812
813impl PropertyDeclaration {
814    fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option<&str> {
815        match *self {
816            PropertyDeclaration::WithVariables(ref declaration) => {
817                let s = declaration.value.from_shorthand?;
818                if s != shorthand {
819                    return None;
820                }
821                Some(&*declaration.value.variable_value.css)
822            },
823            _ => None,
824        }
825    }
826
827    /// Returns a CSS-wide keyword declaration for a given property.
828    #[inline]
829    pub fn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self {
830        Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword })
831    }
832
833    /// Returns a CSS-wide keyword if the declaration's value is one.
834    #[inline]
835    pub fn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> {
836        match *self {
837            PropertyDeclaration::CSSWideKeyword(ref declaration) => Some(declaration.keyword),
838            _ => None,
839        }
840    }
841
842    /// Returns whether the declaration may be serialized as part of a shorthand.
843    ///
844    /// This method returns false if this declaration contains variable or has a
845    /// CSS-wide keyword value, since these values cannot be serialized as part
846    /// of a shorthand.
847    ///
848    /// Caller should check `with_variables_from_shorthand()` and whether all
849    /// needed declarations has the same CSS-wide keyword first.
850    ///
851    /// Note that, serialization of a shorthand may still fail because of other
852    /// property-specific requirement even when this method returns true for all
853    /// the longhand declarations.
854    pub fn may_serialize_as_part_of_shorthand(&self) -> bool {
855        match *self {
856            PropertyDeclaration::CSSWideKeyword(..) | PropertyDeclaration::WithVariables(..) => {
857                false
858            },
859            PropertyDeclaration::Custom(..) => {
860                unreachable!("Serializing a custom property as part of shorthand?")
861            },
862            _ => true,
863        }
864    }
865
866    /// Returns true if this property declaration is for one of the animatable properties.
867    pub fn is_animatable(&self) -> bool {
868        self.id().is_animatable()
869    }
870
871    /// Returns true if this property is a custom property, false
872    /// otherwise.
873    pub fn is_custom(&self) -> bool {
874        matches!(*self, PropertyDeclaration::Custom(..))
875    }
876
877    /// The `context` parameter controls this:
878    ///
879    /// <https://drafts.csswg.org/css-animations/#keyframes>
880    /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
881    /// > except those defined in this specification,
882    /// > but does accept the `animation-play-state` property and interprets it specially.
883    ///
884    /// This will not actually parse Importance values, and will always set things
885    /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
886    /// we only set them here so that we don't have to reallocate
887    pub fn parse_into<'i, 't>(
888        declarations: &mut SourcePropertyDeclaration,
889        id: PropertyId,
890        context: &ParserContext,
891        input: &mut Parser<'i, 't>,
892    ) -> Result<(), ParseError<'i>> {
893        assert!(declarations.is_empty());
894        debug_assert!(id.allowed_in(context), "{:?}", id);
895        input.skip_whitespace();
896
897        let start = input.state();
898        let non_custom_id = match id {
899            PropertyId::Custom(property_name) => {
900                let value = match input.try_parse(CSSWideKeyword::parse) {
901                    Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword),
902                    Err(()) => CustomDeclarationValue::Unparsed(Arc::new(
903                        custom_properties::VariableValue::parse(
904                            input,
905                            Some(&context.namespaces.prefixes),
906                            &context.url_data,
907                        )?,
908                    )),
909                };
910                declarations.push(PropertyDeclaration::Custom(CustomDeclaration {
911                    name: property_name,
912                    value,
913                }));
914                return Ok(());
915            },
916            PropertyId::NonCustom(id) => id,
917        };
918        match non_custom_id.longhand_or_shorthand() {
919            Ok(longhand_id) => {
920                parse_non_custom_property_declaration_value_into(
921                    declarations,
922                    context,
923                    input,
924                    &start,
925                    |declarations, input| {
926                        let decl = input
927                            .parse_entirely(|input| longhand_id.parse_value(context, input))?;
928                        declarations.push(decl);
929                        Ok(())
930                    },
931                    |declarations, wk| {
932                        declarations.push(PropertyDeclaration::css_wide_keyword(longhand_id, wk));
933                    },
934                    |declarations, variable_value| {
935                        declarations.push(PropertyDeclaration::WithVariables(VariableDeclaration {
936                            id: longhand_id,
937                            value: Arc::new(UnparsedValue {
938                                variable_value,
939                                from_shorthand: None,
940                            }),
941                        }))
942                    },
943                )?;
944            },
945            Err(shorthand_id) => {
946                parse_non_custom_property_declaration_value_into(
947                    declarations,
948                    context,
949                    input,
950                    &start,
951                    // Not using parse_entirely here: each ShorthandId::parse_into function needs
952                    // to do so *before* pushing to `declarations`.
953                    |declarations, input| shorthand_id.parse_into(declarations, context, input),
954                    |declarations, wk| {
955                        if shorthand_id == ShorthandId::All {
956                            declarations.all_shorthand = AllShorthand::CSSWideKeyword(wk)
957                        } else {
958                            for longhand in shorthand_id.longhands() {
959                                declarations
960                                    .push(PropertyDeclaration::css_wide_keyword(longhand, wk));
961                            }
962                        }
963                    },
964                    |declarations, variable_value| {
965                        let unparsed = Arc::new(UnparsedValue {
966                            variable_value,
967                            from_shorthand: Some(shorthand_id),
968                        });
969                        if shorthand_id == ShorthandId::All {
970                            declarations.all_shorthand = AllShorthand::WithVariables(unparsed)
971                        } else {
972                            for id in shorthand_id.longhands() {
973                                declarations.push(PropertyDeclaration::WithVariables(
974                                    VariableDeclaration {
975                                        id,
976                                        value: unparsed.clone(),
977                                    },
978                                ))
979                            }
980                        }
981                    },
982                )?;
983            },
984        }
985        if let Some(use_counters) = context.use_counters {
986            use_counters.non_custom_properties.record(non_custom_id);
987        }
988        Ok(())
989    }
990}
991
992/// A PropertyDeclarationId without references, for use as a hash map key.
993#[derive(Clone, Debug, PartialEq, Eq, Hash)]
994pub enum OwnedPropertyDeclarationId {
995    /// A longhand.
996    Longhand(LonghandId),
997    /// A custom property declaration.
998    Custom(custom_properties::Name),
999}
1000
1001impl OwnedPropertyDeclarationId {
1002    /// Return whether this property is logical.
1003    #[inline]
1004    pub fn is_logical(&self) -> bool {
1005        self.as_borrowed().is_logical()
1006    }
1007
1008    /// Returns the corresponding PropertyDeclarationId.
1009    #[inline]
1010    pub fn as_borrowed(&self) -> PropertyDeclarationId<'_> {
1011        match self {
1012            Self::Longhand(id) => PropertyDeclarationId::Longhand(*id),
1013            Self::Custom(name) => PropertyDeclarationId::Custom(name),
1014        }
1015    }
1016
1017    /// Convert an `CSSPropertyId` into an `OwnedPropertyDeclarationId`.
1018    #[cfg(feature = "gecko")]
1019    #[inline]
1020    pub fn from_gecko_css_property_id(property: &CSSPropertyId) -> Option<Self> {
1021        Some(match PropertyId::from_gecko_css_property_id(property)? {
1022            PropertyId::Custom(name) => Self::Custom(name),
1023            PropertyId::NonCustom(id) => Self::Longhand(id.as_longhand()?),
1024        })
1025    }
1026}
1027
1028/// An identifier for a given property declaration, which can be either a
1029/// longhand or a custom property.
1030#[derive(Clone, Copy, Debug, PartialEq, MallocSizeOf)]
1031pub enum PropertyDeclarationId<'a> {
1032    /// A longhand.
1033    Longhand(LonghandId),
1034    /// A custom property declaration.
1035    Custom(&'a custom_properties::Name),
1036}
1037
1038impl<'a> ToCss for PropertyDeclarationId<'a> {
1039    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1040    where
1041        W: Write,
1042    {
1043        match *self {
1044            PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()),
1045            PropertyDeclarationId::Custom(name) => {
1046                dest.write_str("--")?;
1047                serialize_atom_name(name, dest)
1048            },
1049        }
1050    }
1051}
1052
1053impl<'a> PropertyDeclarationId<'a> {
1054    /// Returns PropertyFlags for given property.
1055    #[inline(always)]
1056    pub fn flags(&self) -> PropertyFlags {
1057        match self {
1058            Self::Longhand(id) => id.flags(),
1059            Self::Custom(_) => PropertyFlags::empty(),
1060        }
1061    }
1062
1063    /// Convert to an OwnedPropertyDeclarationId.
1064    pub fn to_owned(&self) -> OwnedPropertyDeclarationId {
1065        match self {
1066            PropertyDeclarationId::Longhand(id) => OwnedPropertyDeclarationId::Longhand(*id),
1067            PropertyDeclarationId::Custom(name) => {
1068                OwnedPropertyDeclarationId::Custom((*name).clone())
1069            },
1070        }
1071    }
1072
1073    /// Whether a given declaration id is either the same as `other`, or a
1074    /// longhand of it.
1075    pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool {
1076        match *self {
1077            PropertyDeclarationId::Longhand(id) => match *other {
1078                PropertyId::NonCustom(non_custom_id) => id.is_or_is_longhand_of(non_custom_id),
1079                PropertyId::Custom(_) => false,
1080            },
1081            PropertyDeclarationId::Custom(name) => {
1082                matches!(*other, PropertyId::Custom(ref other_name) if name == other_name)
1083            },
1084        }
1085    }
1086
1087    /// Whether a given declaration id is a longhand belonging to this
1088    /// shorthand.
1089    pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool {
1090        match *self {
1091            PropertyDeclarationId::Longhand(ref id) => id.is_longhand_of(shorthand),
1092            _ => false,
1093        }
1094    }
1095
1096    /// Returns the name of the property without CSS escaping.
1097    pub fn name(&self) -> Cow<'static, str> {
1098        match *self {
1099            PropertyDeclarationId::Longhand(id) => id.name().into(),
1100            PropertyDeclarationId::Custom(name) => {
1101                let mut s = String::new();
1102                write!(&mut s, "--{}", name).unwrap();
1103                s.into()
1104            },
1105        }
1106    }
1107
1108    /// Returns longhand id if it is, None otherwise.
1109    #[inline]
1110    pub fn as_longhand(&self) -> Option<LonghandId> {
1111        match *self {
1112            PropertyDeclarationId::Longhand(id) => Some(id),
1113            _ => None,
1114        }
1115    }
1116
1117    /// Return whether this property is logical.
1118    #[inline]
1119    pub fn is_logical(&self) -> bool {
1120        match self {
1121            PropertyDeclarationId::Longhand(id) => id.is_logical(),
1122            PropertyDeclarationId::Custom(_) => false,
1123        }
1124    }
1125
1126    /// If this is a logical property, return the corresponding physical one in
1127    /// the given writing mode.
1128    ///
1129    /// Otherwise, return unchanged.
1130    #[inline]
1131    pub fn to_physical(&self, wm: WritingMode) -> Self {
1132        match self {
1133            Self::Longhand(id) => Self::Longhand(id.to_physical(wm)),
1134            Self::Custom(_) => self.clone(),
1135        }
1136    }
1137
1138    /// Returns whether this property is animatable.
1139    #[inline]
1140    pub fn is_animatable(&self) -> bool {
1141        match self {
1142            Self::Longhand(id) => id.is_animatable(),
1143            Self::Custom(_) => true,
1144        }
1145    }
1146
1147    /// Returns whether this property is animatable in a discrete way.
1148    #[inline]
1149    pub fn is_discrete_animatable(&self) -> bool {
1150        match self {
1151            Self::Longhand(longhand) => longhand.is_discrete_animatable(),
1152            // TODO(bug 1885995): Refine this.
1153            Self::Custom(_) => true,
1154        }
1155    }
1156
1157    /// Converts from a to an adequate NonCustomCSSPropertyId, returning
1158    /// eCSSPropertyExtra_variable for custom properties.
1159    #[cfg(feature = "gecko")]
1160    #[inline]
1161    pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
1162        match self {
1163            PropertyDeclarationId::Longhand(id) => id.to_noncustomcsspropertyid(),
1164            PropertyDeclarationId::Custom(_) => NonCustomCSSPropertyId::eCSSPropertyExtra_variable,
1165        }
1166    }
1167
1168    /// Convert a `PropertyDeclarationId` into an `CSSPropertyId`
1169    ///
1170    /// FIXME(emilio, bug 1870107): We should consider using cbindgen to generate the property id
1171    /// representation or so.
1172    #[cfg(feature = "gecko")]
1173    #[inline]
1174    pub fn to_gecko_css_property_id(&self) -> CSSPropertyId {
1175        match self {
1176            Self::Longhand(id) => CSSPropertyId {
1177                mId: id.to_noncustomcsspropertyid(),
1178                mCustomName: RefPtr::null(),
1179            },
1180            Self::Custom(name) => {
1181                let mut property_id = CSSPropertyId {
1182                    mId: NonCustomCSSPropertyId::eCSSPropertyExtra_variable,
1183                    mCustomName: RefPtr::null(),
1184                };
1185                property_id.mCustomName.mRawPtr = (*name).clone().into_addrefed();
1186                property_id
1187            },
1188        }
1189    }
1190}
1191
1192/// A set of all properties.
1193#[derive(Clone, PartialEq, Default)]
1194pub struct NonCustomPropertyIdSet {
1195    storage: [u32; ((property_counts::NON_CUSTOM as usize) - 1 + 32) / 32],
1196}
1197
1198impl NonCustomPropertyIdSet {
1199    /// Creates an empty `NonCustomPropertyIdSet`.
1200    pub fn new() -> Self {
1201        Self {
1202            storage: Default::default(),
1203        }
1204    }
1205
1206    /// Insert a non-custom-property in the set.
1207    #[inline]
1208    pub fn insert(&mut self, id: NonCustomPropertyId) {
1209        let bit = id.0 as usize;
1210        self.storage[bit / 32] |= 1 << (bit % 32);
1211    }
1212
1213    /// Return whether the given property is in the set
1214    #[inline]
1215    pub fn contains(&self, id: NonCustomPropertyId) -> bool {
1216        let bit = id.0 as usize;
1217        (self.storage[bit / 32] & (1 << (bit % 32))) != 0
1218    }
1219}
1220
1221/// A set of longhand properties
1222#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
1223pub struct LonghandIdSet {
1224    storage: [u32; ((property_counts::LONGHANDS as usize) - 1 + 32) / 32],
1225}
1226
1227to_shmem::impl_trivial_to_shmem!(LonghandIdSet);
1228
1229impl LonghandIdSet {
1230    /// Return an empty LonghandIdSet.
1231    #[inline]
1232    pub fn new() -> Self {
1233        Self {
1234            storage: Default::default(),
1235        }
1236    }
1237
1238    /// Iterate over the current longhand id set.
1239    pub fn iter(&self) -> LonghandIdSetIterator<'_> {
1240        LonghandIdSetIterator {
1241            chunks: &self.storage,
1242            cur_chunk: 0,
1243            cur_bit: 0,
1244        }
1245    }
1246
1247    /// Returns whether this set contains at least every longhand that `other`
1248    /// also contains.
1249    pub fn contains_all(&self, other: &Self) -> bool {
1250        for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
1251            if (*self_cell & *other_cell) != *other_cell {
1252                return false;
1253            }
1254        }
1255        true
1256    }
1257
1258    /// Returns whether this set contains any longhand that `other` also contains.
1259    pub fn contains_any(&self, other: &Self) -> bool {
1260        for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
1261            if (*self_cell & *other_cell) != 0 {
1262                return true;
1263            }
1264        }
1265        false
1266    }
1267
1268    /// Remove all the given properties from the set.
1269    #[inline]
1270    pub fn remove_all(&mut self, other: &Self) {
1271        for (self_cell, other_cell) in self.storage.iter_mut().zip(other.storage.iter()) {
1272            *self_cell &= !*other_cell;
1273        }
1274    }
1275
1276    /// Return whether the given property is in the set
1277    #[inline]
1278    pub fn contains(&self, id: LonghandId) -> bool {
1279        let bit = id as usize;
1280        (self.storage[bit / 32] & (1 << (bit % 32))) != 0
1281    }
1282
1283    /// Return whether this set contains any reset longhand.
1284    #[inline]
1285    pub fn contains_any_reset(&self) -> bool {
1286        self.contains_any(Self::reset())
1287    }
1288
1289    /// Add the given property to the set
1290    #[inline]
1291    pub fn insert(&mut self, id: LonghandId) {
1292        let bit = id as usize;
1293        self.storage[bit / 32] |= 1 << (bit % 32);
1294    }
1295
1296    /// Remove the given property from the set
1297    #[inline]
1298    pub fn remove(&mut self, id: LonghandId) {
1299        let bit = id as usize;
1300        self.storage[bit / 32] &= !(1 << (bit % 32));
1301    }
1302
1303    /// Clear all bits
1304    #[inline]
1305    pub fn clear(&mut self) {
1306        for cell in &mut self.storage {
1307            *cell = 0
1308        }
1309    }
1310
1311    /// Returns whether the set is empty.
1312    #[inline]
1313    pub fn is_empty(&self) -> bool {
1314        self.storage.iter().all(|c| *c == 0)
1315    }
1316}
1317
1318/// An iterator over a set of longhand ids.
1319pub struct LonghandIdSetIterator<'a> {
1320    chunks: &'a [u32],
1321    cur_chunk: u32,
1322    cur_bit: u32, // [0..31], note that zero means the end-most bit
1323}
1324
1325impl<'a> Iterator for LonghandIdSetIterator<'a> {
1326    type Item = LonghandId;
1327
1328    fn next(&mut self) -> Option<Self::Item> {
1329        loop {
1330            debug_assert!(self.cur_bit < 32);
1331            let cur_chunk = self.cur_chunk;
1332            let cur_bit = self.cur_bit;
1333            let chunk = *self.chunks.get(cur_chunk as usize)?;
1334            let next_bit = (chunk >> cur_bit).trailing_zeros();
1335            if next_bit == 32 {
1336                // Totally empty chunk, skip it.
1337                self.cur_bit = 0;
1338                self.cur_chunk += 1;
1339                continue;
1340            }
1341            debug_assert!(cur_bit + next_bit < 32);
1342            let longhand_id = cur_chunk * 32 + cur_bit + next_bit;
1343            debug_assert!(longhand_id as usize <= property_counts::LONGHANDS);
1344            let id: LonghandId = unsafe { mem::transmute(longhand_id as u16) };
1345            self.cur_bit += next_bit + 1;
1346            if self.cur_bit == 32 {
1347                self.cur_bit = 0;
1348                self.cur_chunk += 1;
1349            }
1350            return Some(id);
1351        }
1352    }
1353}
1354
1355/// An ArrayVec of subproperties, contains space for the longest shorthand except all.
1356pub type SubpropertiesVec<T> = ArrayVec<T, { property_counts::MAX_SHORTHAND_EXPANDED }>;
1357
1358/// A stack-allocated vector of `PropertyDeclaration`
1359/// large enough to parse one CSS `key: value` declaration.
1360/// (Shorthands expand to multiple `PropertyDeclaration`s.)
1361#[derive(Default)]
1362pub struct SourcePropertyDeclaration {
1363    /// The storage for the actual declarations (except for all).
1364    pub declarations: SubpropertiesVec<PropertyDeclaration>,
1365    /// Stored separately to keep SubpropertiesVec smaller.
1366    pub all_shorthand: AllShorthand,
1367}
1368
1369// This is huge, but we allocate it on the stack and then never move it,
1370// we only pass `&mut SourcePropertyDeclaration` references around.
1371#[cfg(feature = "gecko")]
1372size_of_test!(SourcePropertyDeclaration, 632);
1373#[cfg(feature = "servo")]
1374size_of_test!(SourcePropertyDeclaration, 568);
1375
1376impl SourcePropertyDeclaration {
1377    /// Create one with a single PropertyDeclaration.
1378    #[inline]
1379    pub fn with_one(decl: PropertyDeclaration) -> Self {
1380        let mut result = Self::default();
1381        result.declarations.push(decl);
1382        result
1383    }
1384
1385    /// Similar to Vec::drain: leaves this empty when the return value is dropped.
1386    pub fn drain(&mut self) -> SourcePropertyDeclarationDrain<'_> {
1387        SourcePropertyDeclarationDrain {
1388            declarations: self.declarations.drain(..),
1389            all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet),
1390        }
1391    }
1392
1393    /// Reset to initial state
1394    pub fn clear(&mut self) {
1395        self.declarations.clear();
1396        self.all_shorthand = AllShorthand::NotSet;
1397    }
1398
1399    /// Whether we're empty.
1400    pub fn is_empty(&self) -> bool {
1401        self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet)
1402    }
1403
1404    /// Push a single declaration.
1405    pub fn push(&mut self, declaration: PropertyDeclaration) {
1406        let _result = self.declarations.try_push(declaration);
1407        debug_assert!(_result.is_ok());
1408    }
1409}
1410
1411/// Return type of SourcePropertyDeclaration::drain
1412pub struct SourcePropertyDeclarationDrain<'a> {
1413    /// A drain over the non-all declarations.
1414    pub declarations:
1415        ArrayVecDrain<'a, PropertyDeclaration, { property_counts::MAX_SHORTHAND_EXPANDED }>,
1416    /// The all shorthand that was set.
1417    pub all_shorthand: AllShorthand,
1418}
1419
1420/// An unparsed property value that contains `var()` functions.
1421#[derive(Debug, Eq, PartialEq, ToShmem)]
1422pub struct UnparsedValue {
1423    /// The variable value, references and so on.
1424    pub(super) variable_value: custom_properties::VariableValue,
1425    /// The shorthand this came from.
1426    from_shorthand: Option<ShorthandId>,
1427}
1428
1429impl ToCss for UnparsedValue {
1430    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1431    where
1432        W: Write,
1433    {
1434        // https://drafts.csswg.org/css-variables/#variables-in-shorthands
1435        if self.from_shorthand.is_none() {
1436            self.variable_value.to_css(dest)?;
1437        }
1438        Ok(())
1439    }
1440}
1441
1442/// A simple cache for properties that come from a shorthand and have variable
1443/// references.
1444///
1445/// This cache works because of the fact that you can't have competing values
1446/// for a given longhand coming from the same shorthand (but note that this is
1447/// why the shorthand needs to be part of the cache key).
1448pub type ShorthandsWithPropertyReferencesCache =
1449    FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>;
1450
1451impl UnparsedValue {
1452    fn substitute_variables<'cache>(
1453        &self,
1454        longhand_id: LonghandId,
1455        substitution_functions: &ComputedSubstitutionFunctions,
1456        stylist: &Stylist,
1457        computed_context: &computed::Context,
1458        shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
1459        attribute_tracker: &mut AttributeTracker,
1460    ) -> Cow<'cache, PropertyDeclaration> {
1461        let invalid_at_computed_value_time = || {
1462            let keyword = if longhand_id.inherited() {
1463                CSSWideKeyword::Inherit
1464            } else {
1465                CSSWideKeyword::Initial
1466            };
1467            Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword))
1468        };
1469
1470        if computed_context
1471            .builder
1472            .invalid_non_custom_properties
1473            .contains(longhand_id)
1474        {
1475            return invalid_at_computed_value_time();
1476        }
1477
1478        if let Some(shorthand_id) = self.from_shorthand {
1479            let key = (shorthand_id, longhand_id);
1480            if shorthand_cache.contains_key(&key) {
1481                // FIXME: This double lookup should be avoidable, but rustc
1482                // doesn't like that, see:
1483                //
1484                // https://github.com/rust-lang/rust/issues/82146
1485                return Cow::Borrowed(&shorthand_cache[&key]);
1486            }
1487        }
1488
1489        let SubstitutionResult {
1490            css,
1491            attribute_tainted,
1492        } = match custom_properties::substitute(
1493            &self.variable_value,
1494            substitution_functions,
1495            stylist,
1496            computed_context,
1497            attribute_tracker,
1498        ) {
1499            Ok(css) => css,
1500            Err(..) => return invalid_at_computed_value_time(),
1501        };
1502
1503        // As of this writing, only the base URL is used for property
1504        // values.
1505        //
1506        // NOTE(emilio): we intentionally pase `None` as the rule type here.
1507        // If something starts depending on it, it's probably a bug, since
1508        // it'd change how values are parsed depending on whether we're in a
1509        // @keyframes rule or not, for example... So think twice about
1510        // whether you want to do this!
1511        //
1512        // FIXME(emilio): ParsingMode is slightly fishy...
1513        let mut parsing_mode = ParsingMode::DEFAULT;
1514        if attribute_tainted {
1515            parsing_mode.insert(ParsingMode::DISALLOW_URLS);
1516        }
1517        let context = ParserContext::new(
1518            Origin::Author,
1519            &self.variable_value.url_data,
1520            None,
1521            parsing_mode,
1522            computed_context.quirks_mode,
1523            /* namespaces = */ Default::default(),
1524            None,
1525            None,
1526        );
1527
1528        let mut input = ParserInput::new(&css);
1529        let mut input = Parser::new(&mut input);
1530        input.skip_whitespace();
1531
1532        if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
1533            return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword));
1534        }
1535
1536        let shorthand = match self.from_shorthand {
1537            None => {
1538                return match input.parse_entirely(|input| longhand_id.parse_value(&context, input))
1539                {
1540                    Ok(decl) => Cow::Owned(decl),
1541                    Err(..) => invalid_at_computed_value_time(),
1542                }
1543            },
1544            Some(shorthand) => shorthand,
1545        };
1546
1547        let mut decls = SourcePropertyDeclaration::default();
1548        // parse_into takes care of doing `parse_entirely` for us.
1549        if shorthand
1550            .parse_into(&mut decls, &context, &mut input)
1551            .is_err()
1552        {
1553            return invalid_at_computed_value_time();
1554        }
1555
1556        for declaration in decls.declarations.drain(..) {
1557            let longhand = declaration.id().as_longhand().unwrap();
1558            if longhand.is_logical() {
1559                let writing_mode = computed_context.builder.writing_mode;
1560                shorthand_cache.insert(
1561                    (shorthand, longhand.to_physical(writing_mode)),
1562                    declaration.clone(),
1563                );
1564            }
1565            shorthand_cache.insert((shorthand, longhand), declaration);
1566        }
1567
1568        let key = (shorthand, longhand_id);
1569        match shorthand_cache.get(&key) {
1570            Some(decl) => Cow::Borrowed(decl),
1571            // NOTE: Under normal circumstances we should always have a value, but when prefs
1572            // change we might hit this case. Consider something like `animation-timeline`, which
1573            // is a conditionally-enabled longhand of `animation`:
1574            //
1575            // If we have a sheet with `animation: var(--foo)`, and the `animation-timeline` pref
1576            // enabled, then that expands to an `animation-timeline` declaration at parse time.
1577            //
1578            // If the user disables the pref and, some time later, we get here wanting to compute
1579            // `animation-timeline`, parse_into won't generate any declaration for it anymore, so
1580            // we haven't inserted in the cache. Computing to invalid / initial seems like the most
1581            // sensible thing to do here.
1582            None => invalid_at_computed_value_time(),
1583        }
1584    }
1585}
1586/// A parsed all-shorthand value.
1587pub enum AllShorthand {
1588    /// Not present.
1589    NotSet,
1590    /// A CSS-wide keyword.
1591    CSSWideKeyword(CSSWideKeyword),
1592    /// An all shorthand with var() references that we can't resolve right now.
1593    WithVariables(Arc<UnparsedValue>),
1594}
1595
1596impl Default for AllShorthand {
1597    fn default() -> Self {
1598        Self::NotSet
1599    }
1600}
1601
1602impl AllShorthand {
1603    /// Iterates property declarations from the given all shorthand value.
1604    #[inline]
1605    pub fn declarations(&self) -> AllShorthandDeclarationIterator<'_> {
1606        AllShorthandDeclarationIterator {
1607            all_shorthand: self,
1608            longhands: ShorthandId::All.longhands(),
1609        }
1610    }
1611}
1612
1613/// An iterator over the all shorthand's shorthand declarations.
1614pub struct AllShorthandDeclarationIterator<'a> {
1615    all_shorthand: &'a AllShorthand,
1616    longhands: NonCustomPropertyIterator<LonghandId>,
1617}
1618
1619impl<'a> Iterator for AllShorthandDeclarationIterator<'a> {
1620    type Item = PropertyDeclaration;
1621
1622    #[inline]
1623    fn next(&mut self) -> Option<Self::Item> {
1624        match *self.all_shorthand {
1625            AllShorthand::NotSet => None,
1626            AllShorthand::CSSWideKeyword(ref keyword) => Some(
1627                PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword),
1628            ),
1629            AllShorthand::WithVariables(ref unparsed) => {
1630                Some(PropertyDeclaration::WithVariables(VariableDeclaration {
1631                    id: self.longhands.next()?,
1632                    value: unparsed.clone(),
1633                }))
1634            },
1635        }
1636    }
1637}
1638
1639/// An iterator over all the property ids that are enabled for a given
1640/// shorthand, if that shorthand is enabled for all content too.
1641pub struct NonCustomPropertyIterator<Item: 'static> {
1642    filter: bool,
1643    iter: std::slice::Iter<'static, Item>,
1644}
1645
1646impl<Item> Iterator for NonCustomPropertyIterator<Item>
1647where
1648    Item: 'static + Copy + Into<NonCustomPropertyId>,
1649{
1650    type Item = Item;
1651
1652    fn next(&mut self) -> Option<Self::Item> {
1653        loop {
1654            let id = *self.iter.next()?;
1655            if !self.filter || id.into().enabled_for_all_content() {
1656                return Some(id);
1657            }
1658        }
1659    }
1660}
1661
1662/// An iterator over all the properties that transition on a given style.
1663pub struct TransitionPropertyIterator<'a> {
1664    style: &'a ComputedValues,
1665    index_range: core::ops::Range<usize>,
1666    longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>,
1667}
1668
1669impl<'a> TransitionPropertyIterator<'a> {
1670    /// Create a `TransitionPropertyIterator` for the given style.
1671    pub fn from_style(style: &'a ComputedValues) -> Self {
1672        Self {
1673            style,
1674            index_range: 0..style.get_ui().transition_property_count(),
1675            longhand_iterator: None,
1676        }
1677    }
1678}
1679
1680/// A single iteration of the TransitionPropertyIterator.
1681pub struct TransitionPropertyIteration {
1682    /// The id of the longhand for this property.
1683    pub property: OwnedPropertyDeclarationId,
1684    /// The index of this property in the list of transition properties for this iterator's
1685    /// style.
1686    pub index: usize,
1687}
1688
1689impl<'a> Iterator for TransitionPropertyIterator<'a> {
1690    type Item = TransitionPropertyIteration;
1691
1692    fn next(&mut self) -> Option<Self::Item> {
1693        use crate::values::computed::TransitionProperty;
1694        loop {
1695            if let Some(ref mut longhand_iterator) = self.longhand_iterator {
1696                if let Some(longhand_id) = longhand_iterator.next() {
1697                    return Some(TransitionPropertyIteration {
1698                        property: OwnedPropertyDeclarationId::Longhand(longhand_id),
1699                        index: self.index_range.start - 1,
1700                    });
1701                }
1702                self.longhand_iterator = None;
1703            }
1704
1705            let index = self.index_range.next()?;
1706            match self.style.get_ui().transition_property_at(index) {
1707                TransitionProperty::NonCustom(id) => {
1708                    match id.longhand_or_shorthand() {
1709                        Ok(longhand_id) => {
1710                            return Some(TransitionPropertyIteration {
1711                                property: OwnedPropertyDeclarationId::Longhand(longhand_id),
1712                                index,
1713                            });
1714                        },
1715                        Err(shorthand_id) => {
1716                            // In the other cases, we set up our state so that we are ready to
1717                            // compute the next value of the iterator and then loop (equivalent
1718                            // to calling self.next()).
1719                            self.longhand_iterator = Some(shorthand_id.longhands());
1720                        },
1721                    }
1722                },
1723                TransitionProperty::Custom(name) => {
1724                    return Some(TransitionPropertyIteration {
1725                        property: OwnedPropertyDeclarationId::Custom(name),
1726                        index,
1727                    })
1728                },
1729                TransitionProperty::Unsupported(..) => {},
1730            }
1731        }
1732    }
1733}