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::applicable_declarations::RevertKind;
25use crate::custom_properties::{self, ComputedSubstitutionFunctions, SubstitutionResult};
26use crate::derives::*;
27use crate::dom::AttributeTracker;
28#[cfg(feature = "gecko")]
29use crate::gecko_bindings::structs::{CSSPropertyId, NonCustomCSSPropertyId, RefPtr};
30use crate::logical_geometry::WritingMode;
31use crate::parser::ParserContext;
32use crate::stylesheets::CssRuleType;
33use crate::stylesheets::Origin;
34use crate::stylist::Stylist;
35use crate::values::{computed, serialize_atom_name};
36use arrayvec::{ArrayVec, Drain as ArrayVecDrain};
37use cssparser::{match_ignore_ascii_case, Parser, ParserInput};
38use rustc_hash::FxHashMap;
39use servo_arc::Arc;
40use std::{
41    borrow::Cow,
42    fmt::{self, Write},
43    mem,
44};
45use style_traits::{
46    CssString, CssWriter, KeywordsCollectFn, ParseError, ParsingMode, SpecifiedValueInfo, ToCss,
47    ToTyped, TypedValue,
48};
49use thin_vec::ThinVec;
50
51bitflags! {
52    /// A set of flags for properties.
53    #[derive(Clone, Copy)]
54    pub struct PropertyFlags: u16 {
55        /// This longhand property applies to ::first-letter.
56        const APPLIES_TO_FIRST_LETTER = 1 << 1;
57        /// This longhand property applies to ::first-line.
58        const APPLIES_TO_FIRST_LINE = 1 << 2;
59        /// This longhand property applies to ::placeholder.
60        const APPLIES_TO_PLACEHOLDER = 1 << 3;
61        ///  This longhand property applies to ::cue.
62        const APPLIES_TO_CUE = 1 << 4;
63        /// This longhand property applies to ::marker.
64        const APPLIES_TO_MARKER = 1 << 5;
65        /// This property is a legacy shorthand.
66        ///
67        /// https://drafts.csswg.org/css-cascade/#legacy-shorthand
68        const IS_LEGACY_SHORTHAND = 1 << 6;
69
70        /* The following flags are currently not used in Rust code, they
71         * only need to be listed in corresponding properties so that
72         * they can be checked in the C++ side via ServoCSSPropList.h. */
73
74        /// This property can be animated on the compositor.
75        const CAN_ANIMATE_ON_COMPOSITOR = 0;
76        /// See data.py's documentation about the affects_flags.
77        const AFFECTS_LAYOUT = 0;
78        #[allow(missing_docs)]
79        const AFFECTS_OVERFLOW = 0;
80        #[allow(missing_docs)]
81        const AFFECTS_PAINT = 0;
82    }
83}
84
85/// An enum to represent a CSS Wide keyword.
86#[derive(
87    Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
88)]
89pub enum CSSWideKeyword {
90    /// The `initial` keyword.
91    Initial,
92    /// The `inherit` keyword.
93    Inherit,
94    /// The `unset` keyword.
95    Unset,
96    /// The `revert` keyword.
97    Revert,
98    /// The `revert-layer` keyword.
99    RevertLayer,
100    /// The `revert-rule` keyword.
101    RevertRule,
102}
103
104impl CSSWideKeyword {
105    /// Returns the string representation of the keyword.
106    pub fn to_str(&self) -> &'static str {
107        match *self {
108            Self::Initial => "initial",
109            Self::Inherit => "inherit",
110            Self::Unset => "unset",
111            Self::Revert => "revert",
112            Self::RevertLayer => "revert-layer",
113            Self::RevertRule => "revert-rule",
114        }
115    }
116
117    /// Parses a CSS wide keyword from a CSS identifier.
118    pub fn from_ident(ident: &str) -> Result<Self, ()> {
119        Ok(match_ignore_ascii_case! { ident,
120            "initial" => Self::Initial,
121            "inherit" => Self::Inherit,
122            "unset" => Self::Unset,
123            "revert" => Self::Revert,
124            "revert-layer" => Self::RevertLayer,
125            "revert-rule" if static_prefs::pref!("layout.css.revert-rule.enabled") => Self::RevertRule,
126            _ => return Err(()),
127        })
128    }
129
130    /// Parses a CSS wide keyword completely.
131    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
132        let keyword = {
133            let ident = input.expect_ident().map_err(|_| ())?;
134            Self::from_ident(ident)?
135        };
136        input.expect_exhausted().map_err(|_| ())?;
137        Ok(keyword)
138    }
139
140    /// Returns the revert kind for this wide keyword.
141    pub fn revert_kind(self) -> Option<RevertKind> {
142        Some(match self {
143            Self::Initial | Self::Inherit | Self::Unset => return None,
144            Self::Revert => RevertKind::Origin,
145            Self::RevertLayer => RevertKind::Layer,
146            Self::RevertRule => RevertKind::Rule,
147        })
148    }
149}
150
151/// A declaration using a CSS-wide keyword.
152#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)]
153pub struct WideKeywordDeclaration {
154    #[css(skip)]
155    id: LonghandId,
156    /// The CSS-wide keyword.
157    pub keyword: CSSWideKeyword,
158}
159
160// XXX Switch back to ToTyped derive once it can automatically handle structs
161// Tracking in bug 1991631
162impl ToTyped for WideKeywordDeclaration {
163    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
164        self.keyword.to_typed(dest)
165    }
166}
167
168/// An unparsed declaration that contains `var()` functions.
169#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf, ToTyped)]
170pub struct VariableDeclaration {
171    /// The id of the property this declaration represents.
172    #[css(skip)]
173    id: LonghandId,
174    /// The unparsed value of the variable.
175    #[ignore_malloc_size_of = "Arc"]
176    pub value: Arc<UnparsedValue>,
177}
178
179/// A custom property declaration value is either an unparsed value or a CSS
180/// wide-keyword.
181#[derive(Clone, PartialEq, ToCss, ToShmem)]
182pub enum CustomDeclarationValue {
183    /// An unparsed value.
184    Unparsed(Arc<custom_properties::SpecifiedValue>),
185    /// An already-parsed value.
186    Parsed(Arc<crate::properties_and_values::value::SpecifiedValue>),
187    /// A wide keyword.
188    CSSWideKeyword(CSSWideKeyword),
189}
190
191/// A custom property declaration with the property name and the declared value.
192#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf, ToTyped)]
193#[typed(todo_derive_fields)]
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(_) => true,
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
738/// Return the names of arbitrary substitution functions that are enabled.
739pub fn enabled_arbitrary_substitution_functions() -> &'static [&'static str] {
740    if static_prefs::pref!("layout.css.attr.enabled") {
741        &["var", "env", "attr"]
742    } else {
743        &["var", "env"]
744    }
745}
746
747fn parse_non_custom_property_declaration_value_into<'i>(
748    declarations: &mut SourcePropertyDeclaration,
749    context: &ParserContext,
750    input: &mut Parser<'i, '_>,
751    start: &cssparser::ParserState,
752    parse_entirely_into: impl FnOnce(
753        &mut SourcePropertyDeclaration,
754        &mut Parser<'i, '_>,
755    ) -> Result<(), ParseError<'i>>,
756    parsed_wide_keyword: impl FnOnce(&mut SourcePropertyDeclaration, CSSWideKeyword),
757    parsed_custom: impl FnOnce(&mut SourcePropertyDeclaration, custom_properties::VariableValue),
758) -> Result<(), ParseError<'i>> {
759    let mut starts_with_curly_block = false;
760    if let Ok(token) = input.next() {
761        match token {
762            cssparser::Token::Ident(ref ident) => match CSSWideKeyword::from_ident(ident) {
763                Ok(wk) => {
764                    if input.expect_exhausted().is_ok() {
765                        return Ok(parsed_wide_keyword(declarations, wk));
766                    }
767                },
768                Err(()) => {},
769            },
770            cssparser::Token::CurlyBracketBlock => {
771                starts_with_curly_block = true;
772            },
773            _ => {},
774        }
775    };
776
777    input.reset(&start);
778    input.look_for_arbitrary_substitution_functions(enabled_arbitrary_substitution_functions());
779
780    let err = match parse_entirely_into(declarations, input) {
781        Ok(()) => {
782            input.seen_arbitrary_substitution_functions();
783            return Ok(());
784        },
785        Err(e) => e,
786    };
787
788    // Look for var(), env() and top-level curly blocks after the error.
789    let start_pos = start.position();
790    let mut at_start = start_pos == input.position();
791    let mut invalid = false;
792    while let Ok(token) = input.next() {
793        if matches!(token, cssparser::Token::CurlyBracketBlock) {
794            if !starts_with_curly_block || !at_start {
795                invalid = true;
796                break;
797            }
798        } else if starts_with_curly_block {
799            invalid = true;
800            break;
801        }
802        at_start = false;
803    }
804    if !input.seen_arbitrary_substitution_functions() || invalid {
805        return Err(err);
806    }
807    input.reset(start);
808    let value = custom_properties::VariableValue::parse(
809        input,
810        Some(&context.namespaces.prefixes),
811        &context.url_data,
812    )?;
813    parsed_custom(declarations, value);
814    Ok(())
815}
816
817impl PropertyDeclaration {
818    fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option<&str> {
819        match *self {
820            PropertyDeclaration::WithVariables(ref declaration) => {
821                let s = declaration.value.from_shorthand?;
822                if s != shorthand {
823                    return None;
824                }
825                Some(&*declaration.value.variable_value.css)
826            },
827            _ => None,
828        }
829    }
830
831    /// Returns a CSS-wide keyword declaration for a given property.
832    #[inline]
833    pub fn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self {
834        Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword })
835    }
836
837    /// Returns a CSS-wide keyword if the declaration's value is one.
838    #[inline]
839    pub fn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> {
840        match *self {
841            PropertyDeclaration::CSSWideKeyword(ref declaration) => Some(declaration.keyword),
842            _ => None,
843        }
844    }
845
846    /// Returns whether the declaration may be serialized as part of a shorthand.
847    ///
848    /// This method returns false if this declaration contains variable or has a
849    /// CSS-wide keyword value, since these values cannot be serialized as part
850    /// of a shorthand.
851    ///
852    /// Caller should check `with_variables_from_shorthand()` and whether all
853    /// needed declarations has the same CSS-wide keyword first.
854    ///
855    /// Note that, serialization of a shorthand may still fail because of other
856    /// property-specific requirement even when this method returns true for all
857    /// the longhand declarations.
858    pub fn may_serialize_as_part_of_shorthand(&self) -> bool {
859        match *self {
860            PropertyDeclaration::CSSWideKeyword(..) | PropertyDeclaration::WithVariables(..) => {
861                false
862            },
863            PropertyDeclaration::Custom(..) => {
864                unreachable!("Serializing a custom property as part of shorthand?")
865            },
866            _ => true,
867        }
868    }
869
870    /// Returns true if this property declaration is for one of the animatable properties.
871    pub fn is_animatable(&self) -> bool {
872        self.id().is_animatable()
873    }
874
875    /// Returns true if this property is a custom property, false
876    /// otherwise.
877    pub fn is_custom(&self) -> bool {
878        matches!(*self, PropertyDeclaration::Custom(..))
879    }
880
881    /// The `context` parameter controls this:
882    ///
883    /// <https://drafts.csswg.org/css-animations/#keyframes>
884    /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
885    /// > except those defined in this specification,
886    /// > but does accept the `animation-play-state` property and interprets it specially.
887    ///
888    /// This will not actually parse Importance values, and will always set things
889    /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
890    /// we only set them here so that we don't have to reallocate
891    pub fn parse_into<'i, 't>(
892        declarations: &mut SourcePropertyDeclaration,
893        id: PropertyId,
894        context: &ParserContext,
895        input: &mut Parser<'i, 't>,
896    ) -> Result<(), ParseError<'i>> {
897        assert!(declarations.is_empty());
898        debug_assert!(id.allowed_in(context), "{:?}", id);
899        input.skip_whitespace();
900
901        let start = input.state();
902        let non_custom_id = match id {
903            PropertyId::Custom(property_name) => {
904                let value = match input.try_parse(CSSWideKeyword::parse) {
905                    Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword),
906                    Err(()) => CustomDeclarationValue::Unparsed(Arc::new(
907                        custom_properties::VariableValue::parse(
908                            input,
909                            Some(&context.namespaces.prefixes),
910                            &context.url_data,
911                        )?,
912                    )),
913                };
914                declarations.push(PropertyDeclaration::Custom(CustomDeclaration {
915                    name: property_name,
916                    value,
917                }));
918                return Ok(());
919            },
920            PropertyId::NonCustom(id) => id,
921        };
922        match non_custom_id.longhand_or_shorthand() {
923            Ok(longhand_id) => {
924                parse_non_custom_property_declaration_value_into(
925                    declarations,
926                    context,
927                    input,
928                    &start,
929                    |declarations, input| {
930                        let decl = input
931                            .parse_entirely(|input| longhand_id.parse_value(context, input))?;
932                        declarations.push(decl);
933                        Ok(())
934                    },
935                    |declarations, wk| {
936                        declarations.push(PropertyDeclaration::css_wide_keyword(longhand_id, wk));
937                    },
938                    |declarations, variable_value| {
939                        declarations.push(PropertyDeclaration::WithVariables(VariableDeclaration {
940                            id: longhand_id,
941                            value: Arc::new(UnparsedValue {
942                                variable_value,
943                                from_shorthand: None,
944                            }),
945                        }))
946                    },
947                )?;
948            },
949            Err(shorthand_id) => {
950                parse_non_custom_property_declaration_value_into(
951                    declarations,
952                    context,
953                    input,
954                    &start,
955                    // Not using parse_entirely here: each ShorthandId::parse_into function needs
956                    // to do so *before* pushing to `declarations`.
957                    |declarations, input| shorthand_id.parse_into(declarations, context, input),
958                    |declarations, wk| {
959                        if shorthand_id == ShorthandId::All {
960                            declarations.all_shorthand = AllShorthand::CSSWideKeyword(wk)
961                        } else {
962                            for longhand in shorthand_id.longhands() {
963                                declarations
964                                    .push(PropertyDeclaration::css_wide_keyword(longhand, wk));
965                            }
966                        }
967                    },
968                    |declarations, variable_value| {
969                        let unparsed = Arc::new(UnparsedValue {
970                            variable_value,
971                            from_shorthand: Some(shorthand_id),
972                        });
973                        if shorthand_id == ShorthandId::All {
974                            declarations.all_shorthand = AllShorthand::WithVariables(unparsed)
975                        } else {
976                            for id in shorthand_id.longhands() {
977                                declarations.push(PropertyDeclaration::WithVariables(
978                                    VariableDeclaration {
979                                        id,
980                                        value: unparsed.clone(),
981                                    },
982                                ))
983                            }
984                        }
985                    },
986                )?;
987            },
988        }
989        if let Some(use_counters) = context.use_counters {
990            use_counters.non_custom_properties.record(non_custom_id);
991        }
992        Ok(())
993    }
994}
995
996/// A PropertyDeclarationId without references, for use as a hash map key.
997#[derive(Clone, Debug, PartialEq, Eq, Hash)]
998pub enum OwnedPropertyDeclarationId {
999    /// A longhand.
1000    Longhand(LonghandId),
1001    /// A custom property declaration.
1002    Custom(custom_properties::Name),
1003}
1004
1005impl OwnedPropertyDeclarationId {
1006    /// Return whether this property is logical.
1007    #[inline]
1008    pub fn is_logical(&self) -> bool {
1009        self.as_borrowed().is_logical()
1010    }
1011
1012    /// Returns the corresponding PropertyDeclarationId.
1013    #[inline]
1014    pub fn as_borrowed(&self) -> PropertyDeclarationId<'_> {
1015        match self {
1016            Self::Longhand(id) => PropertyDeclarationId::Longhand(*id),
1017            Self::Custom(name) => PropertyDeclarationId::Custom(name),
1018        }
1019    }
1020
1021    /// Convert an `CSSPropertyId` into an `OwnedPropertyDeclarationId`.
1022    #[cfg(feature = "gecko")]
1023    #[inline]
1024    pub fn from_gecko_css_property_id(property: &CSSPropertyId) -> Option<Self> {
1025        Some(match PropertyId::from_gecko_css_property_id(property)? {
1026            PropertyId::Custom(name) => Self::Custom(name),
1027            PropertyId::NonCustom(id) => Self::Longhand(id.as_longhand()?),
1028        })
1029    }
1030}
1031
1032/// An identifier for a given property declaration, which can be either a
1033/// longhand or a custom property.
1034#[derive(Clone, Copy, Debug, PartialEq, MallocSizeOf)]
1035pub enum PropertyDeclarationId<'a> {
1036    /// A longhand.
1037    Longhand(LonghandId),
1038    /// A custom property declaration.
1039    Custom(&'a custom_properties::Name),
1040}
1041
1042impl<'a> ToCss for PropertyDeclarationId<'a> {
1043    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1044    where
1045        W: Write,
1046    {
1047        match *self {
1048            PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()),
1049            PropertyDeclarationId::Custom(name) => {
1050                dest.write_str("--")?;
1051                serialize_atom_name(name, dest)
1052            },
1053        }
1054    }
1055}
1056
1057impl<'a> PropertyDeclarationId<'a> {
1058    /// Returns PropertyFlags for given property.
1059    #[inline(always)]
1060    pub fn flags(&self) -> PropertyFlags {
1061        match self {
1062            Self::Longhand(id) => id.flags(),
1063            Self::Custom(_) => PropertyFlags::empty(),
1064        }
1065    }
1066
1067    /// Convert to an OwnedPropertyDeclarationId.
1068    pub fn to_owned(&self) -> OwnedPropertyDeclarationId {
1069        match self {
1070            PropertyDeclarationId::Longhand(id) => OwnedPropertyDeclarationId::Longhand(*id),
1071            PropertyDeclarationId::Custom(name) => {
1072                OwnedPropertyDeclarationId::Custom((*name).clone())
1073            },
1074        }
1075    }
1076
1077    /// Whether a given declaration id is either the same as `other`, or a
1078    /// longhand of it.
1079    pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool {
1080        match *self {
1081            PropertyDeclarationId::Longhand(id) => match *other {
1082                PropertyId::NonCustom(non_custom_id) => id.is_or_is_longhand_of(non_custom_id),
1083                PropertyId::Custom(_) => false,
1084            },
1085            PropertyDeclarationId::Custom(name) => {
1086                matches!(*other, PropertyId::Custom(ref other_name) if name == other_name)
1087            },
1088        }
1089    }
1090
1091    /// Whether a given declaration id is a longhand belonging to this
1092    /// shorthand.
1093    pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool {
1094        match *self {
1095            PropertyDeclarationId::Longhand(ref id) => id.is_longhand_of(shorthand),
1096            _ => false,
1097        }
1098    }
1099
1100    /// Returns the name of the property without CSS escaping.
1101    pub fn name(&self) -> Cow<'static, str> {
1102        match *self {
1103            PropertyDeclarationId::Longhand(id) => id.name().into(),
1104            PropertyDeclarationId::Custom(name) => {
1105                let mut s = String::new();
1106                write!(&mut s, "--{}", name).unwrap();
1107                s.into()
1108            },
1109        }
1110    }
1111
1112    /// Returns longhand id if it is, None otherwise.
1113    #[inline]
1114    pub fn as_longhand(&self) -> Option<LonghandId> {
1115        match *self {
1116            PropertyDeclarationId::Longhand(id) => Some(id),
1117            _ => None,
1118        }
1119    }
1120
1121    /// Return whether this property is logical.
1122    #[inline]
1123    pub fn is_logical(&self) -> bool {
1124        match self {
1125            PropertyDeclarationId::Longhand(id) => id.is_logical(),
1126            PropertyDeclarationId::Custom(_) => false,
1127        }
1128    }
1129
1130    /// If this is a logical property, return the corresponding physical one in
1131    /// the given writing mode.
1132    ///
1133    /// Otherwise, return unchanged.
1134    #[inline]
1135    pub fn to_physical(&self, wm: WritingMode) -> Self {
1136        match self {
1137            Self::Longhand(id) => Self::Longhand(id.to_physical(wm)),
1138            Self::Custom(_) => self.clone(),
1139        }
1140    }
1141
1142    /// Returns whether this property is animatable.
1143    #[inline]
1144    pub fn is_animatable(&self) -> bool {
1145        match self {
1146            Self::Longhand(id) => id.is_animatable(),
1147            Self::Custom(_) => true,
1148        }
1149    }
1150
1151    /// Returns whether this property is animatable in a discrete way.
1152    #[inline]
1153    pub fn is_discrete_animatable(&self) -> bool {
1154        match self {
1155            Self::Longhand(longhand) => longhand.is_discrete_animatable(),
1156            // TODO(bug 1885995): Refine this.
1157            Self::Custom(_) => true,
1158        }
1159    }
1160
1161    /// Converts from a to an adequate NonCustomCSSPropertyId, returning
1162    /// eCSSPropertyExtra_variable for custom properties.
1163    #[cfg(feature = "gecko")]
1164    #[inline]
1165    pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
1166        match self {
1167            PropertyDeclarationId::Longhand(id) => id.to_noncustomcsspropertyid(),
1168            PropertyDeclarationId::Custom(_) => NonCustomCSSPropertyId::eCSSPropertyExtra_variable,
1169        }
1170    }
1171
1172    /// Convert a `PropertyDeclarationId` into an `CSSPropertyId`
1173    ///
1174    /// FIXME(emilio, bug 1870107): We should consider using cbindgen to generate the property id
1175    /// representation or so.
1176    #[cfg(feature = "gecko")]
1177    #[inline]
1178    pub fn to_gecko_css_property_id(&self) -> CSSPropertyId {
1179        match self {
1180            Self::Longhand(id) => CSSPropertyId {
1181                mId: id.to_noncustomcsspropertyid(),
1182                mCustomName: RefPtr::null(),
1183            },
1184            Self::Custom(name) => {
1185                let mut property_id = CSSPropertyId {
1186                    mId: NonCustomCSSPropertyId::eCSSPropertyExtra_variable,
1187                    mCustomName: RefPtr::null(),
1188                };
1189                property_id.mCustomName.mRawPtr = (*name).clone().into_addrefed();
1190                property_id
1191            },
1192        }
1193    }
1194}
1195
1196/// A set of all properties.
1197#[derive(Clone, PartialEq, Default)]
1198pub struct NonCustomPropertyIdSet {
1199    storage: [u32; ((property_counts::NON_CUSTOM as usize) - 1 + 32) / 32],
1200}
1201
1202impl NonCustomPropertyIdSet {
1203    /// Creates an empty `NonCustomPropertyIdSet`.
1204    pub fn new() -> Self {
1205        Self {
1206            storage: Default::default(),
1207        }
1208    }
1209
1210    /// Insert a non-custom-property in the set.
1211    #[inline]
1212    pub fn insert(&mut self, id: NonCustomPropertyId) {
1213        let bit = id.0 as usize;
1214        self.storage[bit / 32] |= 1 << (bit % 32);
1215    }
1216
1217    /// Return whether the given property is in the set
1218    #[inline]
1219    pub fn contains(&self, id: NonCustomPropertyId) -> bool {
1220        let bit = id.0 as usize;
1221        (self.storage[bit / 32] & (1 << (bit % 32))) != 0
1222    }
1223}
1224
1225/// A set of longhand properties
1226#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
1227pub struct LonghandIdSet {
1228    storage: [u32; ((property_counts::LONGHANDS as usize) - 1 + 32) / 32],
1229}
1230
1231to_shmem::impl_trivial_to_shmem!(LonghandIdSet);
1232
1233impl LonghandIdSet {
1234    /// Return an empty LonghandIdSet.
1235    #[inline]
1236    pub fn new() -> Self {
1237        Self {
1238            storage: Default::default(),
1239        }
1240    }
1241
1242    /// Iterate over the current longhand id set.
1243    pub fn iter(&self) -> LonghandIdSetIterator<'_> {
1244        LonghandIdSetIterator {
1245            chunks: &self.storage,
1246            cur_chunk: 0,
1247            cur_bit: 0,
1248        }
1249    }
1250
1251    /// Returns whether this set contains at least every longhand that `other`
1252    /// also contains.
1253    pub fn contains_all(&self, other: &Self) -> bool {
1254        for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
1255            if (*self_cell & *other_cell) != *other_cell {
1256                return false;
1257            }
1258        }
1259        true
1260    }
1261
1262    /// Returns whether this set contains any longhand that `other` also contains.
1263    pub fn contains_any(&self, other: &Self) -> bool {
1264        for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
1265            if (*self_cell & *other_cell) != 0 {
1266                return true;
1267            }
1268        }
1269        false
1270    }
1271
1272    /// Remove all the given properties from the set.
1273    #[inline]
1274    pub fn remove_all(&mut self, other: &Self) {
1275        for (self_cell, other_cell) in self.storage.iter_mut().zip(other.storage.iter()) {
1276            *self_cell &= !*other_cell;
1277        }
1278    }
1279
1280    /// Return whether the given property is in the set
1281    #[inline]
1282    pub fn contains(&self, id: LonghandId) -> bool {
1283        let bit = id as usize;
1284        (self.storage[bit / 32] & (1 << (bit % 32))) != 0
1285    }
1286
1287    /// Return whether this set contains any reset longhand.
1288    #[inline]
1289    pub fn contains_any_reset(&self) -> bool {
1290        self.contains_any(Self::reset())
1291    }
1292
1293    /// Add the given property to the set
1294    #[inline]
1295    pub fn insert(&mut self, id: LonghandId) {
1296        let bit = id as usize;
1297        self.storage[bit / 32] |= 1 << (bit % 32);
1298    }
1299
1300    /// Remove the given property from the set
1301    #[inline]
1302    pub fn remove(&mut self, id: LonghandId) {
1303        let bit = id as usize;
1304        self.storage[bit / 32] &= !(1 << (bit % 32));
1305    }
1306
1307    /// Clear all bits
1308    #[inline]
1309    pub fn clear(&mut self) {
1310        for cell in &mut self.storage {
1311            *cell = 0
1312        }
1313    }
1314
1315    /// Returns whether the set is empty.
1316    #[inline]
1317    pub fn is_empty(&self) -> bool {
1318        self.storage.iter().all(|c| *c == 0)
1319    }
1320}
1321
1322/// An iterator over a set of longhand ids.
1323pub struct LonghandIdSetIterator<'a> {
1324    chunks: &'a [u32],
1325    cur_chunk: u32,
1326    cur_bit: u32, // [0..31], note that zero means the end-most bit
1327}
1328
1329impl<'a> Iterator for LonghandIdSetIterator<'a> {
1330    type Item = LonghandId;
1331
1332    fn next(&mut self) -> Option<Self::Item> {
1333        loop {
1334            debug_assert!(self.cur_bit < 32);
1335            let cur_chunk = self.cur_chunk;
1336            let cur_bit = self.cur_bit;
1337            let chunk = *self.chunks.get(cur_chunk as usize)?;
1338            let next_bit = (chunk >> cur_bit).trailing_zeros();
1339            if next_bit == 32 {
1340                // Totally empty chunk, skip it.
1341                self.cur_bit = 0;
1342                self.cur_chunk += 1;
1343                continue;
1344            }
1345            debug_assert!(cur_bit + next_bit < 32);
1346            let longhand_id = cur_chunk * 32 + cur_bit + next_bit;
1347            debug_assert!(longhand_id as usize <= property_counts::LONGHANDS);
1348            let id: LonghandId = unsafe { mem::transmute(longhand_id as u16) };
1349            self.cur_bit += next_bit + 1;
1350            if self.cur_bit == 32 {
1351                self.cur_bit = 0;
1352                self.cur_chunk += 1;
1353            }
1354            return Some(id);
1355        }
1356    }
1357}
1358
1359/// An ArrayVec of subproperties, contains space for the longest shorthand except all.
1360pub type SubpropertiesVec<T> = ArrayVec<T, { property_counts::MAX_SHORTHAND_EXPANDED }>;
1361
1362/// A stack-allocated vector of `PropertyDeclaration`
1363/// large enough to parse one CSS `key: value` declaration.
1364/// (Shorthands expand to multiple `PropertyDeclaration`s.)
1365#[derive(Default)]
1366pub struct SourcePropertyDeclaration {
1367    /// The storage for the actual declarations (except for all).
1368    pub declarations: SubpropertiesVec<PropertyDeclaration>,
1369    /// Stored separately to keep SubpropertiesVec smaller.
1370    pub all_shorthand: AllShorthand,
1371}
1372
1373// This is huge, but we allocate it on the stack and then never move it,
1374// we only pass `&mut SourcePropertyDeclaration` references around.
1375#[cfg(feature = "gecko")]
1376size_of_test!(SourcePropertyDeclaration, 632);
1377#[cfg(feature = "servo")]
1378size_of_test!(SourcePropertyDeclaration, 568);
1379
1380impl SourcePropertyDeclaration {
1381    /// Create one with a single PropertyDeclaration.
1382    #[inline]
1383    pub fn with_one(decl: PropertyDeclaration) -> Self {
1384        let mut result = Self::default();
1385        result.declarations.push(decl);
1386        result
1387    }
1388
1389    /// Similar to Vec::drain: leaves this empty when the return value is dropped.
1390    pub fn drain(&mut self) -> SourcePropertyDeclarationDrain<'_> {
1391        SourcePropertyDeclarationDrain {
1392            declarations: self.declarations.drain(..),
1393            all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet),
1394        }
1395    }
1396
1397    /// Reset to initial state
1398    pub fn clear(&mut self) {
1399        self.declarations.clear();
1400        self.all_shorthand = AllShorthand::NotSet;
1401    }
1402
1403    /// Whether we're empty.
1404    pub fn is_empty(&self) -> bool {
1405        self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet)
1406    }
1407
1408    /// Push a single declaration.
1409    pub fn push(&mut self, declaration: PropertyDeclaration) {
1410        let _result = self.declarations.try_push(declaration);
1411        debug_assert!(_result.is_ok());
1412    }
1413}
1414
1415/// Return type of SourcePropertyDeclaration::drain
1416pub struct SourcePropertyDeclarationDrain<'a> {
1417    /// A drain over the non-all declarations.
1418    pub declarations:
1419        ArrayVecDrain<'a, PropertyDeclaration, { property_counts::MAX_SHORTHAND_EXPANDED }>,
1420    /// The all shorthand that was set.
1421    pub all_shorthand: AllShorthand,
1422}
1423
1424/// An unparsed property value that contains `var()` functions.
1425#[derive(Debug, Eq, PartialEq, ToShmem)]
1426pub struct UnparsedValue {
1427    /// The variable value, references and so on.
1428    pub(super) variable_value: custom_properties::VariableValue,
1429    /// The shorthand this came from.
1430    from_shorthand: Option<ShorthandId>,
1431}
1432
1433impl ToCss for UnparsedValue {
1434    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1435    where
1436        W: Write,
1437    {
1438        // https://drafts.csswg.org/css-variables/#variables-in-shorthands
1439        if self.from_shorthand.is_none() {
1440            self.variable_value.to_css(dest)?;
1441        }
1442        Ok(())
1443    }
1444}
1445
1446impl ToTyped for UnparsedValue {
1447    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
1448        if self.from_shorthand.is_none() {
1449            self.variable_value.to_typed(dest)?;
1450            return Ok(());
1451        }
1452        Err(())
1453    }
1454}
1455
1456/// A simple cache for properties that come from a shorthand and have variable
1457/// references.
1458///
1459/// This cache works because of the fact that you can't have competing values
1460/// for a given longhand coming from the same shorthand (but note that this is
1461/// why the shorthand needs to be part of the cache key).
1462pub type ShorthandsWithPropertyReferencesCache =
1463    FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>;
1464
1465impl UnparsedValue {
1466    fn substitute_variables<'cache>(
1467        &self,
1468        longhand_id: LonghandId,
1469        substitution_functions: &ComputedSubstitutionFunctions,
1470        stylist: &Stylist,
1471        computed_context: &computed::Context,
1472        shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
1473        attribute_tracker: &mut AttributeTracker,
1474    ) -> Cow<'cache, PropertyDeclaration> {
1475        let invalid_at_computed_value_time = || {
1476            let keyword = if longhand_id.inherited() {
1477                CSSWideKeyword::Inherit
1478            } else {
1479                CSSWideKeyword::Initial
1480            };
1481            Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword))
1482        };
1483
1484        if computed_context
1485            .builder
1486            .invalid_non_custom_properties
1487            .contains(longhand_id)
1488        {
1489            return invalid_at_computed_value_time();
1490        }
1491
1492        if let Some(shorthand_id) = self.from_shorthand {
1493            let key = (shorthand_id, longhand_id);
1494            if shorthand_cache.contains_key(&key) {
1495                // FIXME: This double lookup should be avoidable, but rustc
1496                // doesn't like that, see:
1497                //
1498                // https://github.com/rust-lang/rust/issues/82146
1499                return Cow::Borrowed(&shorthand_cache[&key]);
1500            }
1501        }
1502
1503        let SubstitutionResult { css, attr_taint } = match custom_properties::substitute(
1504            &self.variable_value,
1505            substitution_functions,
1506            stylist,
1507            computed_context,
1508            attribute_tracker,
1509        ) {
1510            Ok(css) => css,
1511            Err(..) => return invalid_at_computed_value_time(),
1512        };
1513
1514        // As of this writing, only the base URL is used for property
1515        // values.
1516        //
1517        // NOTE(emilio): we intentionally pase `None` as the rule type here.
1518        // If something starts depending on it, it's probably a bug, since
1519        // it'd change how values are parsed depending on whether we're in a
1520        // @keyframes rule or not, for example... So think twice about
1521        // whether you want to do this!
1522        //
1523        // FIXME(emilio): ParsingMode is slightly fishy...
1524        let context = ParserContext::new(
1525            Origin::Author,
1526            &self.variable_value.url_data,
1527            None,
1528            ParsingMode::DEFAULT,
1529            computed_context.quirks_mode,
1530            /* namespaces = */ Default::default(),
1531            None,
1532            None,
1533            attr_taint,
1534        );
1535
1536        let mut input = ParserInput::new(&css);
1537        let mut input = Parser::new(&mut input);
1538        input.skip_whitespace();
1539
1540        if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
1541            return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword));
1542        }
1543
1544        let shorthand = match self.from_shorthand {
1545            None => {
1546                return match input.parse_entirely(|input| longhand_id.parse_value(&context, input))
1547                {
1548                    Ok(decl) => Cow::Owned(decl),
1549                    Err(..) => invalid_at_computed_value_time(),
1550                }
1551            },
1552            Some(shorthand) => shorthand,
1553        };
1554
1555        let mut decls = SourcePropertyDeclaration::default();
1556        // parse_into takes care of doing `parse_entirely` for us.
1557        if shorthand
1558            .parse_into(&mut decls, &context, &mut input)
1559            .is_err()
1560        {
1561            return invalid_at_computed_value_time();
1562        }
1563
1564        for declaration in decls.declarations.drain(..) {
1565            let longhand = declaration.id().as_longhand().unwrap();
1566            if longhand.is_logical() {
1567                let writing_mode = computed_context.builder.writing_mode;
1568                shorthand_cache.insert(
1569                    (shorthand, longhand.to_physical(writing_mode)),
1570                    declaration.clone(),
1571                );
1572            }
1573            shorthand_cache.insert((shorthand, longhand), declaration);
1574        }
1575
1576        let key = (shorthand, longhand_id);
1577        match shorthand_cache.get(&key) {
1578            Some(decl) => Cow::Borrowed(decl),
1579            // NOTE: Under normal circumstances we should always have a value, but when prefs
1580            // change we might hit this case. Consider something like `animation-timeline`, which
1581            // is a conditionally-enabled longhand of `animation`:
1582            //
1583            // If we have a sheet with `animation: var(--foo)`, and the `animation-timeline` pref
1584            // enabled, then that expands to an `animation-timeline` declaration at parse time.
1585            //
1586            // If the user disables the pref and, some time later, we get here wanting to compute
1587            // `animation-timeline`, parse_into won't generate any declaration for it anymore, so
1588            // we haven't inserted in the cache. Computing to invalid / initial seems like the most
1589            // sensible thing to do here.
1590            None => invalid_at_computed_value_time(),
1591        }
1592    }
1593}
1594/// A parsed all-shorthand value.
1595pub enum AllShorthand {
1596    /// Not present.
1597    NotSet,
1598    /// A CSS-wide keyword.
1599    CSSWideKeyword(CSSWideKeyword),
1600    /// An all shorthand with var() references that we can't resolve right now.
1601    WithVariables(Arc<UnparsedValue>),
1602}
1603
1604impl Default for AllShorthand {
1605    fn default() -> Self {
1606        Self::NotSet
1607    }
1608}
1609
1610impl AllShorthand {
1611    /// Iterates property declarations from the given all shorthand value.
1612    #[inline]
1613    pub fn declarations(&self) -> AllShorthandDeclarationIterator<'_> {
1614        AllShorthandDeclarationIterator {
1615            all_shorthand: self,
1616            longhands: ShorthandId::All.longhands(),
1617        }
1618    }
1619}
1620
1621/// An iterator over the all shorthand's shorthand declarations.
1622pub struct AllShorthandDeclarationIterator<'a> {
1623    all_shorthand: &'a AllShorthand,
1624    longhands: NonCustomPropertyIterator<LonghandId>,
1625}
1626
1627impl<'a> Iterator for AllShorthandDeclarationIterator<'a> {
1628    type Item = PropertyDeclaration;
1629
1630    #[inline]
1631    fn next(&mut self) -> Option<Self::Item> {
1632        match *self.all_shorthand {
1633            AllShorthand::NotSet => None,
1634            AllShorthand::CSSWideKeyword(ref keyword) => Some(
1635                PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword),
1636            ),
1637            AllShorthand::WithVariables(ref unparsed) => {
1638                Some(PropertyDeclaration::WithVariables(VariableDeclaration {
1639                    id: self.longhands.next()?,
1640                    value: unparsed.clone(),
1641                }))
1642            },
1643        }
1644    }
1645}
1646
1647/// An iterator over all the property ids that are enabled for a given
1648/// shorthand, if that shorthand is enabled for all content too.
1649pub struct NonCustomPropertyIterator<Item: 'static> {
1650    filter: bool,
1651    iter: std::slice::Iter<'static, Item>,
1652}
1653
1654impl<Item> Iterator for NonCustomPropertyIterator<Item>
1655where
1656    Item: 'static + Copy + Into<NonCustomPropertyId>,
1657{
1658    type Item = Item;
1659
1660    fn next(&mut self) -> Option<Self::Item> {
1661        loop {
1662            let id = *self.iter.next()?;
1663            if !self.filter || id.into().enabled_for_all_content() {
1664                return Some(id);
1665            }
1666        }
1667    }
1668}
1669
1670/// An iterator over all the properties that transition on a given style.
1671pub struct TransitionPropertyIterator<'a> {
1672    style: &'a ComputedValues,
1673    index_range: core::ops::Range<usize>,
1674    longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>,
1675}
1676
1677impl<'a> TransitionPropertyIterator<'a> {
1678    /// Create a `TransitionPropertyIterator` for the given style.
1679    pub fn from_style(style: &'a ComputedValues) -> Self {
1680        Self {
1681            style,
1682            index_range: 0..style.get_ui().transition_property_count(),
1683            longhand_iterator: None,
1684        }
1685    }
1686}
1687
1688/// A single iteration of the TransitionPropertyIterator.
1689pub struct TransitionPropertyIteration {
1690    /// The id of the longhand for this property.
1691    pub property: OwnedPropertyDeclarationId,
1692    /// The index of this property in the list of transition properties for this iterator's
1693    /// style.
1694    pub index: usize,
1695}
1696
1697impl<'a> Iterator for TransitionPropertyIterator<'a> {
1698    type Item = TransitionPropertyIteration;
1699
1700    fn next(&mut self) -> Option<Self::Item> {
1701        use crate::values::computed::TransitionProperty;
1702        loop {
1703            if let Some(ref mut longhand_iterator) = self.longhand_iterator {
1704                if let Some(longhand_id) = longhand_iterator.next() {
1705                    return Some(TransitionPropertyIteration {
1706                        property: OwnedPropertyDeclarationId::Longhand(longhand_id),
1707                        index: self.index_range.start - 1,
1708                    });
1709                }
1710                self.longhand_iterator = None;
1711            }
1712
1713            let index = self.index_range.next()?;
1714            match self.style.get_ui().transition_property_at(index) {
1715                TransitionProperty::NonCustom(id) => {
1716                    match id.longhand_or_shorthand() {
1717                        Ok(longhand_id) => {
1718                            return Some(TransitionPropertyIteration {
1719                                property: OwnedPropertyDeclarationId::Longhand(longhand_id),
1720                                index,
1721                            });
1722                        },
1723                        Err(shorthand_id) => {
1724                            // In the other cases, we set up our state so that we are ready to
1725                            // compute the next value of the iterator and then loop (equivalent
1726                            // to calling self.next()).
1727                            self.longhand_iterator = Some(shorthand_id.longhands());
1728                        },
1729                    }
1730                },
1731                TransitionProperty::Custom(name) => {
1732                    return Some(TransitionPropertyIteration {
1733                        property: OwnedPropertyDeclarationId::Custom(name),
1734                        index,
1735                    })
1736                },
1737                TransitionProperty::Unsupported(..) => {},
1738            }
1739        }
1740    }
1741}