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