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