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