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