Skip to main content

style/properties/
mod.rs

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