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