Skip to main content

style/values/specified/
position.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//! CSS handling for the specified value of
6//! [`position`][position]s
7//!
8//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
9
10use crate::Atom;
11use crate::derives::*;
12use crate::logical_geometry::{LogicalAxis, LogicalSide, PhysicalSide, WritingMode};
13use crate::parser::{Parse, ParserContext};
14use crate::selector_map::PrecomputedHashMap;
15use crate::str::HTML_SPACE_CHARACTERS;
16use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
17use crate::values::computed::{Context, Percentage, ToComputedValue};
18use crate::values::generics::length::GenericAnchorSizeFunction;
19use crate::values::generics::position::PositionComponent as GenericPositionComponent;
20use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
21use crate::values::generics::position::ZIndex as GenericZIndex;
22use crate::values::generics::position::{AspectRatio as GenericAspectRatio, GenericAnchorSide};
23use crate::values::generics::position::{GenericAnchorFunction, GenericInset, TreeScoped};
24use crate::values::generics::position::{IsTreeScoped, Position as GenericPosition};
25use crate::values::specified;
26use crate::values::specified::align::AlignFlags;
27use crate::values::specified::percentage::NoCalcPercentage;
28use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
29use crate::values::{AtomIdent, DashedIdent};
30use cssparser::Parser;
31use hashbrown::hash_map::Entry;
32use num_traits::FromPrimitive;
33use servo_arc::Arc;
34use smallvec::{SmallVec, smallvec};
35use std::fmt::{self, Write};
36use style_traits::arc_slice::ArcSlice;
37use style_traits::values::specified::AllowedNumericType;
38use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
39use thin_vec::ThinVec;
40
41/// The specified value of a CSS `<position>`
42pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
43
44/// The specified value of an `auto | <position>`.
45pub type PositionOrAuto = GenericPositionOrAuto<Position>;
46
47/// The specified value of a horizontal position.
48pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
49
50/// The specified value of a vertical position.
51pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;
52
53/// The specified value of a component of a CSS `<position>`.
54#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
55#[typed(todo_derive_fields)]
56pub enum PositionComponent<S> {
57    /// `center`
58    Center,
59    /// `<length-percentage>`
60    Length(LengthPercentage),
61    /// `<side> <length-percentage>?`
62    Side(S, Option<LengthPercentage>),
63}
64
65/// A keyword for the X direction.
66#[derive(
67    Clone,
68    Copy,
69    Debug,
70    Eq,
71    Hash,
72    MallocSizeOf,
73    Parse,
74    PartialEq,
75    SpecifiedValueInfo,
76    ToComputedValue,
77    ToCss,
78    ToResolvedValue,
79    ToShmem,
80)]
81#[allow(missing_docs)]
82#[repr(u8)]
83pub enum HorizontalPositionKeyword {
84    Left,
85    Right,
86}
87
88/// A keyword for the Y direction.
89#[derive(
90    Clone,
91    Copy,
92    Debug,
93    Eq,
94    Hash,
95    MallocSizeOf,
96    Parse,
97    PartialEq,
98    SpecifiedValueInfo,
99    ToComputedValue,
100    ToCss,
101    ToResolvedValue,
102    ToShmem,
103)]
104#[allow(missing_docs)]
105#[repr(u8)]
106pub enum VerticalPositionKeyword {
107    Top,
108    Bottom,
109}
110
111impl Parse for Position {
112    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
113        let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?;
114        if position.is_three_value_syntax() {
115            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
116        }
117        Ok(position)
118    }
119}
120
121impl Position {
122    /// Parses a `<bg-position>`, with quirks.
123    pub fn parse_three_value_quirky(
124        context: &ParserContext,
125        input: &mut Parser,
126        allow_quirks: AllowQuirks,
127    ) -> Result<Self, ParseError> {
128        match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
129            Ok(x_pos @ PositionComponent::Center) => {
130                if let Ok(y_pos) =
131                    input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
132                {
133                    return Ok(Self::new(x_pos, y_pos));
134                }
135                let x_pos = input
136                    .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
137                    .unwrap_or(x_pos);
138                let y_pos = PositionComponent::Center;
139                return Ok(Self::new(x_pos, y_pos));
140            },
141            Ok(PositionComponent::Side(x_keyword, lp)) => {
142                if input
143                    .try_parse(|i| i.expect_ident_matching("center"))
144                    .is_ok()
145                {
146                    let x_pos = PositionComponent::Side(x_keyword, lp);
147                    let y_pos = PositionComponent::Center;
148                    return Ok(Self::new(x_pos, y_pos));
149                }
150                if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
151                    let y_lp = input
152                        .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
153                        .ok();
154                    let x_pos = PositionComponent::Side(x_keyword, lp);
155                    let y_pos = PositionComponent::Side(y_keyword, y_lp);
156                    return Ok(Self::new(x_pos, y_pos));
157                }
158                let x_pos = PositionComponent::Side(x_keyword, None);
159                let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length);
160                return Ok(Self::new(x_pos, y_pos));
161            },
162            Ok(x_pos @ PositionComponent::Length(_)) => {
163                if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
164                    let y_pos = PositionComponent::Side(y_keyword, None);
165                    return Ok(Self::new(x_pos, y_pos));
166                }
167                if let Ok(y_lp) =
168                    input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
169                {
170                    let y_pos = PositionComponent::Length(y_lp);
171                    return Ok(Self::new(x_pos, y_pos));
172                }
173                let y_pos = PositionComponent::Center;
174                let _ = input.try_parse(|i| i.expect_ident_matching("center"));
175                return Ok(Self::new(x_pos, y_pos));
176            },
177            Err(_) => {},
178        }
179        let y_keyword = VerticalPositionKeyword::parse(input)?;
180        let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| {
181            let y_lp = i
182                .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
183                .ok();
184            if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
185                let x_lp = i
186                    .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
187                    .ok();
188                let x_pos = PositionComponent::Side(x_keyword, x_lp);
189                return Ok((y_lp, x_pos));
190            };
191            i.expect_ident_matching("center")?;
192            let x_pos = PositionComponent::Center;
193            Ok((y_lp, x_pos))
194        });
195        if let Ok((y_lp, x_pos)) = lp_and_x_pos {
196            let y_pos = PositionComponent::Side(y_keyword, y_lp);
197            return Ok(Self::new(x_pos, y_pos));
198        }
199        let x_pos = PositionComponent::Center;
200        let y_pos = PositionComponent::Side(y_keyword, None);
201        Ok(Self::new(x_pos, y_pos))
202    }
203
204    /// `center center`
205    #[inline]
206    pub fn center() -> Self {
207        Self::new(PositionComponent::Center, PositionComponent::Center)
208    }
209
210    /// Returns true if this uses a 3 value syntax.
211    #[inline]
212    fn is_three_value_syntax(&self) -> bool {
213        self.horizontal.component_count() != self.vertical.component_count()
214    }
215}
216
217impl ToCss for Position {
218    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
219    where
220        W: Write,
221    {
222        match (&self.horizontal, &self.vertical) {
223            (x_pos @ &PositionComponent::Side(_, Some(_)), PositionComponent::Length(y_lp)) => {
224                x_pos.to_css(dest)?;
225                dest.write_str(" top ")?;
226                y_lp.to_css(dest)
227            },
228            (PositionComponent::Length(x_lp), y_pos @ &PositionComponent::Side(_, Some(_))) => {
229                dest.write_str("left ")?;
230                x_lp.to_css(dest)?;
231                dest.write_char(' ')?;
232                y_pos.to_css(dest)
233            },
234            (x_pos, y_pos) => {
235                x_pos.to_css(dest)?;
236                dest.write_char(' ')?;
237                y_pos.to_css(dest)
238            },
239        }
240    }
241}
242
243impl<S: Parse> Parse for PositionComponent<S> {
244    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
245        Self::parse_quirky(context, input, AllowQuirks::No)
246    }
247}
248
249impl<S: Parse> PositionComponent<S> {
250    /// Parses a component of a CSS position, with quirks.
251    pub fn parse_quirky(
252        context: &ParserContext,
253        input: &mut Parser,
254        allow_quirks: AllowQuirks,
255    ) -> Result<Self, ParseError> {
256        if input
257            .try_parse(|i| i.expect_ident_matching("center"))
258            .is_ok()
259        {
260            return Ok(PositionComponent::Center);
261        }
262        if let Ok(lp) =
263            input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
264        {
265            return Ok(PositionComponent::Length(lp));
266        }
267        let keyword = S::parse(context, input)?;
268        let lp = input
269            .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
270            .ok();
271        Ok(PositionComponent::Side(keyword, lp))
272    }
273}
274
275impl<S> GenericPositionComponent for PositionComponent<S> {
276    fn is_center(&self) -> bool {
277        match *self {
278            PositionComponent::Center => true,
279            PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.get() == 0.5,
280            // 50% from any side is still the center.
281            PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => {
282                per.get() == 0.5
283            },
284            _ => false,
285        }
286    }
287}
288
289impl<S> PositionComponent<S> {
290    /// `0%`
291    pub fn zero() -> Self {
292        PositionComponent::Length(LengthPercentage::Percentage(NoCalcPercentage::zero()))
293    }
294
295    /// Returns the count of this component.
296    fn component_count(&self) -> usize {
297        match *self {
298            PositionComponent::Length(..) | PositionComponent::Center => 1,
299            PositionComponent::Side(_, ref lp) => {
300                if lp.is_some() {
301                    2
302                } else {
303                    1
304                }
305            },
306        }
307    }
308}
309
310impl<S: Side> ToComputedValue for PositionComponent<S> {
311    type ComputedValue = ComputedLengthPercentage;
312
313    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
314        match *self {
315            PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)),
316            PositionComponent::Side(ref keyword, None) => {
317                let p = Percentage(if keyword.is_start() { 0. } else { 1. });
318                ComputedLengthPercentage::new_percent(p)
319            },
320            PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
321                let length = length.to_computed_value(context);
322                // We represent `<end-side> <length>` as `calc(100% - <length>)`.
323                ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All)
324            },
325            PositionComponent::Side(_, Some(ref length))
326            | PositionComponent::Length(ref length) => length.to_computed_value(context),
327        }
328    }
329
330    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
331        PositionComponent::Length(ToComputedValue::from_computed_value(computed))
332    }
333}
334
335impl<S: Side> PositionComponent<S> {
336    /// The initial specified value of a position component, i.e. the start side.
337    pub fn initial_specified_value() -> Self {
338        PositionComponent::Side(S::start(), None)
339    }
340}
341
342/// https://drafts.csswg.org/css-anchor-position-1/#propdef-anchor-name
343#[derive(
344    Animate,
345    Clone,
346    Debug,
347    MallocSizeOf,
348    PartialEq,
349    SpecifiedValueInfo,
350    ToComputedValue,
351    ToCss,
352    ToResolvedValue,
353    ToShmem,
354    ToTyped,
355)]
356#[css(comma)]
357#[repr(transparent)]
358#[typed(todo_derive_fields)]
359pub struct AnchorNameIdent(
360    #[css(iterable, if_empty = "none")]
361    #[ignore_malloc_size_of = "Arc"]
362    #[animation(constant)]
363    pub crate::ArcSlice<DashedIdent>,
364);
365
366impl AnchorNameIdent {
367    /// Return the `none` value.
368    pub fn none() -> Self {
369        Self(Default::default())
370    }
371}
372
373impl Parse for AnchorNameIdent {
374    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
375        let first = input.expect_ident()?;
376        if first.eq_ignore_ascii_case("none") {
377            return Ok(Self::none());
378        }
379        // The common case is probably just to have a single anchor name, so
380        // space for four on the stack should be plenty.
381        let mut idents: SmallVec<[DashedIdent; 4]> = smallvec![DashedIdent::from_ident(first,)?];
382        while input.try_parse(|input| input.expect_comma()).is_ok() {
383            idents.push(DashedIdent::parse(context, input)?);
384        }
385        Ok(AnchorNameIdent(ArcSlice::from_iter(idents.drain(..))))
386    }
387}
388
389impl IsTreeScoped for AnchorNameIdent {
390    fn is_tree_scoped(&self) -> bool {
391        !self.0.is_empty()
392    }
393}
394
395/// https://drafts.csswg.org/css-anchor-position-1/#propdef-anchor-name
396pub type AnchorName = TreeScoped<AnchorNameIdent>;
397
398impl AnchorName {
399    /// Return the `none` value.
400    pub fn none() -> Self {
401        Self::with_default_level(AnchorNameIdent::none())
402    }
403}
404
405/// List of scoped names, or none.
406#[derive(
407    Clone,
408    Debug,
409    MallocSizeOf,
410    PartialEq,
411    SpecifiedValueInfo,
412    ToComputedValue,
413    ToCss,
414    ToResolvedValue,
415    ToShmem,
416    ToTyped,
417)]
418#[repr(transparent)]
419#[css(comma)]
420#[typed(todo_derive_fields)]
421#[value_info(other_values = "all")]
422pub struct ScopedNameList(
423    /// `none | all | <dashed-ident>#`
424    #[css(iterable, if_empty = "none")]
425    #[ignore_malloc_size_of = "Arc"]
426    crate::ArcSlice<AtomIdent>,
427);
428
429impl ScopedNameList {
430    /// Return the `none` value.
431    pub fn none() -> Self {
432        Self(crate::ArcSlice::default())
433    }
434
435    /// Whether we're the `none` value.
436    pub fn is_none(&self) -> bool {
437        self.0.is_empty()
438    }
439
440    /// Return the `all` value.
441    pub fn all() -> Self {
442        static ALL: std::sync::LazyLock<ScopedNameList> = std::sync::LazyLock::new(|| {
443            ScopedNameList(crate::ArcSlice::from_iter_leaked(std::iter::once(
444                AtomIdent::new(atom!("all")),
445            )))
446        });
447        ALL.clone()
448    }
449}
450
451impl Parse for ScopedNameList {
452    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
453        let first = input.expect_ident()?;
454        if first.eq_ignore_ascii_case("none") {
455            return Ok(Self::none());
456        }
457        if first.eq_ignore_ascii_case("all") {
458            return Ok(Self::all());
459        }
460        // Authors using more than a handful of anchored elements is likely
461        // uncommon, so we only pre-allocate for 8 on the stack here.
462        let mut idents = SmallVec::<[AtomIdent; 8]>::new();
463        idents.push(AtomIdent::new(DashedIdent::from_ident(first)?.0));
464        while input.try_parse(|input| input.expect_comma()).is_ok() {
465            idents.push(AtomIdent::new(DashedIdent::parse(context, input)?.0));
466        }
467        Ok(Self(ArcSlice::from_iter(idents.drain(..))))
468    }
469}
470
471impl IsTreeScoped for ScopedNameList {
472    fn is_tree_scoped(&self) -> bool {
473        !self.is_none()
474    }
475}
476
477/// A scoped name type, such as:
478/// * https://drafts.csswg.org/css-anchor-position-1/#propdef-scope
479pub type ScopedName = TreeScoped<ScopedNameList>;
480
481impl ScopedName {
482    /// Return the `none` value.
483    pub fn none() -> Self {
484        Self::with_default_level(ScopedNameList::none())
485    }
486
487    /// Returns true if no scoped name is specified.
488    pub fn is_none(&self) -> bool {
489        self.value.is_none()
490    }
491}
492
493/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-anchor
494#[derive(
495    Clone,
496    Debug,
497    MallocSizeOf,
498    Parse,
499    PartialEq,
500    SpecifiedValueInfo,
501    ToComputedValue,
502    ToCss,
503    ToResolvedValue,
504    ToShmem,
505    ToTyped,
506)]
507#[repr(u8)]
508#[typed(todo_derive_fields)]
509pub enum PositionAnchorKeyword {
510    /// `normal`
511    Normal,
512    /// `none`
513    None,
514    /// `auto`
515    Auto,
516    /// `<dashed-ident>`
517    Ident(DashedIdent),
518}
519
520impl IsTreeScoped for PositionAnchorKeyword {
521    fn is_tree_scoped(&self) -> bool {
522        match *self {
523            Self::Normal | Self::None | Self::Auto => false,
524            Self::Ident(_) => true,
525        }
526    }
527}
528
529/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-anchor
530pub type PositionAnchor = TreeScoped<PositionAnchorKeyword>;
531
532impl PositionAnchor {
533    /// Return the `normal` value.
534    pub fn normal() -> Self {
535        Self::with_default_level(PositionAnchorKeyword::Normal)
536    }
537}
538
539#[derive(
540    Clone,
541    Copy,
542    Debug,
543    Eq,
544    MallocSizeOf,
545    Parse,
546    PartialEq,
547    Serialize,
548    SpecifiedValueInfo,
549    ToComputedValue,
550    ToCss,
551    ToResolvedValue,
552    ToShmem,
553)]
554#[repr(u8)]
555/// How to swap values for the automatically-generated position tactic.
556pub enum PositionTryFallbacksTryTacticKeyword {
557    /// Swap the values in the block axis.
558    FlipBlock,
559    /// Swap the values in the inline axis.
560    FlipInline,
561    /// Swap the values in the start properties.
562    FlipStart,
563    /// Swap the values in the X axis.
564    FlipX,
565    /// Swap the values in the Y axis.
566    FlipY,
567}
568
569#[derive(
570    Clone,
571    Debug,
572    Default,
573    Eq,
574    MallocSizeOf,
575    PartialEq,
576    SpecifiedValueInfo,
577    ToComputedValue,
578    ToCss,
579    ToResolvedValue,
580    ToShmem,
581)]
582#[repr(transparent)]
583/// Changes for the automatically-generated position option.
584/// Note that this is order-dependent - e.g. `flip-start flip-inline` != `flip-inline flip-start`.
585///
586/// https://drafts.csswg.org/css-anchor-position-1/#typedef-position-try-fallbacks-try-tactic
587pub struct PositionTryFallbacksTryTactic(
588    #[css(iterable)] pub ThinVec<PositionTryFallbacksTryTacticKeyword>,
589);
590
591impl Parse for PositionTryFallbacksTryTactic {
592    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
593        let mut result = ThinVec::with_capacity(5);
594        // Collect up to 5 keywords, disallowing duplicates.
595        for _ in 0..5 {
596            if let Ok(kw) = input.try_parse(PositionTryFallbacksTryTacticKeyword::parse) {
597                if result.contains(&kw) {
598                    return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
599                }
600                result.push(kw);
601            } else {
602                break;
603            }
604        }
605        if result.is_empty() {
606            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
607        }
608        Ok(Self(result))
609    }
610}
611
612impl PositionTryFallbacksTryTactic {
613    /// Returns whether there's any tactic.
614    #[inline]
615    pub fn is_empty(&self) -> bool {
616        self.0.is_empty()
617    }
618
619    /// Iterates over the fallbacks in order.
620    #[inline]
621    pub fn iter(&self) -> impl Iterator<Item = &PositionTryFallbacksTryTacticKeyword> {
622        self.0.iter()
623    }
624}
625
626#[derive(
627    Clone,
628    Debug,
629    MallocSizeOf,
630    PartialEq,
631    SpecifiedValueInfo,
632    ToComputedValue,
633    ToCss,
634    ToResolvedValue,
635    ToShmem,
636)]
637#[repr(C)]
638/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-try-fallbacks
639/// <dashed-ident> || <try-tactic>
640pub struct DashedIdentAndOrTryTactic {
641    /// `<dashed-ident>`
642    pub ident: DashedIdent,
643    /// `<try-tactic>`
644    pub try_tactic: PositionTryFallbacksTryTactic,
645}
646
647impl Parse for DashedIdentAndOrTryTactic {
648    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
649        let mut result = Self {
650            ident: DashedIdent::empty(),
651            try_tactic: PositionTryFallbacksTryTactic::default(),
652        };
653
654        loop {
655            if result.ident.is_empty()
656                && let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i))
657            {
658                result.ident = ident;
659                continue;
660            }
661            if result.try_tactic.is_empty()
662                && let Ok(try_tactic) =
663                    input.try_parse(|i| PositionTryFallbacksTryTactic::parse(context, i))
664            {
665                result.try_tactic = try_tactic;
666                continue;
667            }
668            break;
669        }
670
671        if result.ident.is_empty() && result.try_tactic.is_empty() {
672            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
673        }
674        Ok(result)
675    }
676}
677
678#[derive(
679    Clone,
680    Debug,
681    MallocSizeOf,
682    Parse,
683    PartialEq,
684    SpecifiedValueInfo,
685    ToComputedValue,
686    ToCss,
687    ToResolvedValue,
688    ToShmem,
689)]
690#[repr(u8)]
691/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-try-fallbacks
692/// [ [<dashed-ident> || <try-tactic>] | <'position-area'> ]
693pub enum PositionTryFallbacksItem {
694    /// `<dashed-ident> || <try-tactic>`
695    IdentAndOrTactic(DashedIdentAndOrTryTactic),
696    #[parse(parse_fn = "PositionArea::parse_except_none")]
697    /// `<position-area>`
698    PositionArea(PositionArea),
699}
700
701#[derive(
702    Clone,
703    Debug,
704    Default,
705    MallocSizeOf,
706    PartialEq,
707    SpecifiedValueInfo,
708    ToComputedValue,
709    ToCss,
710    ToResolvedValue,
711    ToShmem,
712    ToTyped,
713)]
714#[css(comma)]
715#[repr(C)]
716#[typed(todo_derive_fields)]
717/// https://drafts.csswg.org/css-anchor-position-1/#position-try-fallbacks
718pub struct PositionTryFallbacksList(
719    #[css(iterable, if_empty = "none")]
720    #[ignore_malloc_size_of = "Arc"]
721    pub crate::ArcSlice<PositionTryFallbacksItem>,
722);
723
724impl IsTreeScoped for PositionTryFallbacksList {
725    fn is_tree_scoped(&self) -> bool {
726        !self.is_none()
727    }
728}
729
730impl PositionTryFallbacksList {
731    #[inline]
732    /// Return the `none` value.
733    pub fn none() -> Self {
734        Self(Default::default())
735    }
736
737    /// Returns whether this is the `none` value.
738    pub fn is_none(&self) -> bool {
739        self.0.is_empty()
740    }
741}
742
743impl Parse for PositionTryFallbacksList {
744    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
745        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
746            return Ok(Self::none());
747        }
748        // The common case is unlikely to include many alternate positioning
749        // styles, so space for four on the stack should typically be enough.
750        let mut items: SmallVec<[PositionTryFallbacksItem; 4]> =
751            smallvec![PositionTryFallbacksItem::parse(context, input)?];
752        while input.try_parse(|input| input.expect_comma()).is_ok() {
753            items.push(PositionTryFallbacksItem::parse(context, input)?);
754        }
755        Ok(Self(ArcSlice::from_iter(items.drain(..))))
756    }
757}
758
759/// https://drafts.csswg.org/css-anchor-position-1/#position-try-fallbacks
760pub type PositionTryFallbacks = TreeScoped<PositionTryFallbacksList>;
761
762impl PositionTryFallbacks {
763    /// Returns the default value, `none`.
764    pub fn none() -> Self {
765        Self::with_default_level(PositionTryFallbacksList::none())
766    }
767}
768
769/// https://drafts.csswg.org/css-anchor-position-1/#position-try-order-property
770#[derive(
771    Clone,
772    Copy,
773    Debug,
774    Default,
775    Eq,
776    MallocSizeOf,
777    Parse,
778    PartialEq,
779    SpecifiedValueInfo,
780    ToComputedValue,
781    ToCss,
782    ToResolvedValue,
783    ToShmem,
784    ToTyped,
785)]
786#[repr(u8)]
787pub enum PositionTryOrder {
788    #[default]
789    /// `normal`
790    Normal,
791    /// `most-width`
792    MostWidth,
793    /// `most-height`
794    MostHeight,
795    /// `most-block-size`
796    MostBlockSize,
797    /// `most-inline-size`
798    MostInlineSize,
799}
800
801impl PositionTryOrder {
802    #[inline]
803    /// Return the `normal` value.
804    pub fn normal() -> Self {
805        Self::Normal
806    }
807
808    /// Returns whether this is the `normal` value.
809    pub fn is_normal(&self) -> bool {
810        *self == Self::Normal
811    }
812}
813
814#[derive(
815    Clone,
816    Copy,
817    Debug,
818    Eq,
819    MallocSizeOf,
820    Parse,
821    PartialEq,
822    Serialize,
823    SpecifiedValueInfo,
824    ToComputedValue,
825    ToCss,
826    ToResolvedValue,
827    ToShmem,
828    ToTyped,
829)]
830#[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))]
831#[repr(C)]
832/// Specified keyword values for the position-visibility property.
833pub struct PositionVisibility(u8);
834bitflags! {
835    impl PositionVisibility: u8 {
836        /// Element is displayed without regard for its anchors or its overflowing status.
837        const ALWAYS = 0;
838        /// anchors-valid
839        const ANCHORS_VALID = 1 << 0;
840        /// anchors-visible
841        const ANCHORS_VISIBLE = 1 << 1;
842        /// no-overflow
843        const NO_OVERFLOW = 1 << 2;
844    }
845}
846
847impl Default for PositionVisibility {
848    fn default() -> Self {
849        Self::ALWAYS
850    }
851}
852
853impl PositionVisibility {
854    #[inline]
855    /// Returns the initial value of position-visibility
856    pub fn always() -> Self {
857        Self::ALWAYS
858    }
859}
860
861/// A value indicating which high level group in the formal grammar a
862/// PositionAreaKeyword or PositionArea belongs to.
863#[repr(u8)]
864#[derive(Clone, Copy, Debug, Eq, PartialEq)]
865pub enum PositionAreaType {
866    /// X || Y
867    Physical,
868    /// block || inline
869    Logical,
870    /// self-block || self-inline
871    SelfLogical,
872    /// start|end|span-* {1,2}
873    Inferred,
874    /// self-start|self-end|span-self-* {1,2}
875    SelfInferred,
876    /// center, span-all
877    Common,
878    /// none
879    None,
880}
881
882/// A three-bit value that represents the axis in which position-area operates on.
883/// Represented as 4 bits: axis type (physical or logical), direction type (physical or logical),
884/// axis value.
885///
886/// There are two special values on top (Inferred and None) that represent ambiguous or axis-less
887/// keywords, respectively.
888#[repr(u8)]
889#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
890#[allow(missing_docs)]
891pub enum PositionAreaAxis {
892    Horizontal = 0b000,
893    Vertical = 0b001,
894
895    X = 0b010,
896    Y = 0b011,
897
898    Block = 0b110,
899    Inline = 0b111,
900
901    Inferred = 0b100,
902    None = 0b101,
903}
904
905impl PositionAreaAxis {
906    /// Whether this axis is physical or not.
907    pub fn is_physical(self) -> bool {
908        (self as u8 & 0b100) == 0
909    }
910
911    /// Whether the direction is logical or not.
912    fn is_flow_relative_direction(self) -> bool {
913        self == Self::Inferred || (self as u8 & 0b10) != 0
914    }
915
916    /// Whether this axis goes first in the canonical syntax.
917    fn is_canonically_first(self) -> bool {
918        self != Self::Inferred && (self as u8) & 1 == 0
919    }
920
921    #[allow(unused)]
922    fn flip(self) -> Self {
923        if matches!(self, Self::Inferred | Self::None) {
924            return self;
925        }
926        Self::from_u8(self as u8 ^ 1u8).unwrap()
927    }
928
929    fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> {
930        Some(match self {
931            PositionAreaAxis::Horizontal | PositionAreaAxis::X => {
932                if wm.is_vertical() {
933                    LogicalAxis::Block
934                } else {
935                    LogicalAxis::Inline
936                }
937            },
938            PositionAreaAxis::Vertical | PositionAreaAxis::Y => {
939                if wm.is_vertical() {
940                    LogicalAxis::Inline
941                } else {
942                    LogicalAxis::Block
943                }
944            },
945            PositionAreaAxis::Block => LogicalAxis::Block,
946            PositionAreaAxis::Inline => LogicalAxis::Inline,
947            PositionAreaAxis::Inferred => inferred,
948            PositionAreaAxis::None => return None,
949        })
950    }
951}
952
953/// Specifies which tracks(s) on the axis that the position-area span occupies.
954/// Represented as 3 bits: start, center, end track.
955#[repr(u8)]
956#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
957pub enum PositionAreaTrack {
958    /// First track
959    Start = 0b001,
960    /// First and center.
961    SpanStart = 0b011,
962    /// Last track.
963    End = 0b100,
964    /// Last and center.
965    SpanEnd = 0b110,
966    /// Center track.
967    Center = 0b010,
968    /// All tracks
969    SpanAll = 0b111,
970}
971
972impl PositionAreaTrack {
973    fn flip(self) -> Self {
974        match self {
975            Self::Start => Self::End,
976            Self::SpanStart => Self::SpanEnd,
977            Self::End => Self::Start,
978            Self::SpanEnd => Self::SpanStart,
979            Self::Center | Self::SpanAll => self,
980        }
981    }
982
983    fn start(self) -> bool {
984        self as u8 & 1 != 0
985    }
986}
987
988/// The shift to the left needed to set the axis.
989pub const AXIS_SHIFT: usize = 3;
990/// The mask used to extract the axis.
991pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT;
992/// The mask used to extract the track.
993pub const TRACK_MASK: u8 = 0b111u8;
994/// The self-wm bit.
995pub const SELF_WM: u8 = 1u8 << 6;
996
997#[derive(
998    Clone,
999    Copy,
1000    Debug,
1001    Default,
1002    Eq,
1003    MallocSizeOf,
1004    Parse,
1005    PartialEq,
1006    SpecifiedValueInfo,
1007    ToComputedValue,
1008    ToCss,
1009    ToResolvedValue,
1010    ToShmem,
1011    FromPrimitive,
1012)]
1013#[allow(missing_docs)]
1014#[repr(u8)]
1015/// Possible values for the `position-area` property's keywords.
1016/// Represented by [0z xxx yyy], where z means "self wm resolution", xxxx is the axis (as in
1017/// PositionAreaAxis) and yyy is the PositionAreaTrack
1018/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area
1019pub enum PositionAreaKeyword {
1020    #[default]
1021    None = (PositionAreaAxis::None as u8) << AXIS_SHIFT,
1022
1023    // Common (shared) keywords:
1024    Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8,
1025    SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8,
1026
1027    // Inferred-axis edges:
1028    Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1029    End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1030    SpanStart =
1031        ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1032    SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1033
1034    // Purely physical edges:
1035    Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1036    Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1037    Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1038    Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1039
1040    // Flow-relative physical-axis edges:
1041    XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1042    XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1043    YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1044    YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1045
1046    // Logical edges:
1047    BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1048    BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1049    InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1050    InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1051
1052    // Composite values with Span:
1053    SpanLeft =
1054        ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1055    SpanRight =
1056        ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1057    SpanTop =
1058        ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1059    SpanBottom =
1060        ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1061
1062    // Flow-relative physical-axis edges:
1063    SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1064    SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1065    SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1066    SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1067
1068    // Logical edges:
1069    SpanBlockStart =
1070        ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1071    SpanBlockEnd =
1072        ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1073    SpanInlineStart =
1074        ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1075    SpanInlineEnd =
1076        ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1077
1078    // Values using the Self element's writing-mode:
1079    SelfStart = SELF_WM | (Self::Start as u8),
1080    SelfEnd = SELF_WM | (Self::End as u8),
1081    SpanSelfStart = SELF_WM | (Self::SpanStart as u8),
1082    SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8),
1083
1084    SelfXStart = SELF_WM | (Self::XStart as u8),
1085    SelfXEnd = SELF_WM | (Self::XEnd as u8),
1086    SelfYStart = SELF_WM | (Self::YStart as u8),
1087    SelfYEnd = SELF_WM | (Self::YEnd as u8),
1088    SelfBlockStart = SELF_WM | (Self::BlockStart as u8),
1089    SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8),
1090    SelfInlineStart = SELF_WM | (Self::InlineStart as u8),
1091    SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8),
1092
1093    SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8),
1094    SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8),
1095    SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8),
1096    SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8),
1097    SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8),
1098    SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8),
1099    SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8),
1100    SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8),
1101}
1102
1103impl PositionAreaKeyword {
1104    /// Returns the 'none' value.
1105    #[inline]
1106    pub fn none() -> Self {
1107        Self::None
1108    }
1109
1110    /// Returns true if this is the none keyword.
1111    pub fn is_none(&self) -> bool {
1112        *self == Self::None
1113    }
1114
1115    /// Whether we're one of the self-wm keywords.
1116    pub fn self_wm(self) -> bool {
1117        (self as u8 & SELF_WM) != 0
1118    }
1119
1120    /// Get this keyword's axis.
1121    pub fn axis(self) -> PositionAreaAxis {
1122        PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap()
1123    }
1124
1125    /// Returns this keyword but with the axis swapped by the argument.
1126    pub fn with_axis(self, axis: PositionAreaAxis) -> Self {
1127        Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap()
1128    }
1129
1130    /// If this keyword uses an inferred axis, replaces it.
1131    pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self {
1132        if self.axis() == PositionAreaAxis::Inferred {
1133            self.with_axis(axis)
1134        } else {
1135            self
1136        }
1137    }
1138
1139    /// Get this keyword's track, or None if we're the `None` keyword.
1140    pub fn track(self) -> Option<PositionAreaTrack> {
1141        let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK);
1142        debug_assert_eq!(
1143            result.is_none(),
1144            self.is_none(),
1145            "Only the none keyword has no track"
1146        );
1147        result
1148    }
1149
1150    fn group_type(self) -> PositionAreaType {
1151        let axis = self.axis();
1152        if axis == PositionAreaAxis::None {
1153            if self.is_none() {
1154                return PositionAreaType::None;
1155            }
1156            return PositionAreaType::Common;
1157        }
1158        if axis == PositionAreaAxis::Inferred {
1159            return if self.self_wm() {
1160                PositionAreaType::SelfInferred
1161            } else {
1162                PositionAreaType::Inferred
1163            };
1164        }
1165        if axis.is_physical() {
1166            return PositionAreaType::Physical;
1167        }
1168        if self.self_wm() {
1169            PositionAreaType::SelfLogical
1170        } else {
1171            PositionAreaType::Logical
1172        }
1173    }
1174
1175    fn to_physical(
1176        self,
1177        cb_wm: WritingMode,
1178        self_wm: WritingMode,
1179        inferred_axis: LogicalAxis,
1180    ) -> Self {
1181        let wm = if self.self_wm() { self_wm } else { cb_wm };
1182        let axis = self.axis();
1183        if !axis.is_flow_relative_direction() {
1184            return self;
1185        }
1186        let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else {
1187            return self;
1188        };
1189        let Some(track) = self.track() else {
1190            debug_assert!(false, "How did we end up with no track here? {self:?}");
1191            return self;
1192        };
1193        let start = track.start();
1194        let logical_side = match logical_axis {
1195            LogicalAxis::Block => {
1196                if start {
1197                    LogicalSide::BlockStart
1198                } else {
1199                    LogicalSide::BlockEnd
1200                }
1201            },
1202            LogicalAxis::Inline => {
1203                if start {
1204                    LogicalSide::InlineStart
1205                } else {
1206                    LogicalSide::InlineEnd
1207                }
1208            },
1209        };
1210        let physical_side = logical_side.to_physical(wm);
1211        let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left);
1212        let new_track = if physical_start != start {
1213            track.flip()
1214        } else {
1215            track
1216        };
1217        let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) {
1218            PositionAreaAxis::Vertical
1219        } else {
1220            PositionAreaAxis::Horizontal
1221        };
1222        Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap()
1223    }
1224
1225    fn flip_track(self) -> Self {
1226        let Some(old_track) = self.track() else {
1227            return self;
1228        };
1229        let new_track = old_track.flip();
1230        Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap()
1231    }
1232
1233    /// Returns a value for the self-alignment properties in order to resolve
1234    /// `normal`, in terms of the containing block's writing mode.
1235    ///
1236    /// Note that the caller must have converted the position-area to physical
1237    /// values.
1238    ///
1239    /// <https://drafts.csswg.org/css-anchor-position/#position-area-alignment>
1240    pub fn to_self_alignment(self, axis: LogicalAxis, cb_wm: &WritingMode) -> Option<AlignFlags> {
1241        let track = self.track()?;
1242        Some(match track {
1243            // "If the only the center track in an axis is selected, the default alignment in that axis is center."
1244            PositionAreaTrack::Center => AlignFlags::CENTER,
1245            // "If all three tracks are selected, the default alignment in that axis is anchor-center."
1246            PositionAreaTrack::SpanAll => AlignFlags::ANCHOR_CENTER,
1247            // "Otherwise, the default alignment in that axis is toward the non-specified side track: if it’s
1248            // specifying the “start” track of its axis, the default alignment in that axis is end; etc."
1249            _ => {
1250                debug_assert_eq!(self.group_type(), PositionAreaType::Physical);
1251                if axis == LogicalAxis::Inline {
1252                    // For the inline axis, map 'start' to 'end' unless the axis is inline-reversed,
1253                    // meaning that its logical flow is counter to physical coordinates and therefore
1254                    // physical 'start' already corresponds to logical 'end'.
1255                    if track.start() == cb_wm.intersects(WritingMode::INLINE_REVERSED) {
1256                        AlignFlags::START
1257                    } else {
1258                        AlignFlags::END
1259                    }
1260                } else {
1261                    // For the block axis, only vertical-rl has reversed flow and therefore
1262                    // does not map 'start' to 'end' here.
1263                    if track.start() == cb_wm.is_vertical_rl() {
1264                        AlignFlags::START
1265                    } else {
1266                        AlignFlags::END
1267                    }
1268                }
1269            },
1270        })
1271    }
1272}
1273
1274#[derive(
1275    Clone,
1276    Copy,
1277    Debug,
1278    Eq,
1279    MallocSizeOf,
1280    PartialEq,
1281    SpecifiedValueInfo,
1282    ToCss,
1283    ToResolvedValue,
1284    ToShmem,
1285    ToTyped,
1286)]
1287#[repr(C)]
1288#[typed(todo_derive_fields)]
1289/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area
1290pub struct PositionArea {
1291    /// First keyword, if any.
1292    pub first: PositionAreaKeyword,
1293    /// Second keyword, if any.
1294    #[css(skip_if = "PositionAreaKeyword::is_none")]
1295    pub second: PositionAreaKeyword,
1296}
1297
1298impl PositionArea {
1299    /// Returns the none value.
1300    #[inline]
1301    pub fn none() -> Self {
1302        Self {
1303            first: PositionAreaKeyword::None,
1304            second: PositionAreaKeyword::None,
1305        }
1306    }
1307
1308    /// Returns whether we're the none value.
1309    #[inline]
1310    pub fn is_none(&self) -> bool {
1311        self.first.is_none()
1312    }
1313
1314    /// Parses a <position-area> without allowing `none`.
1315    pub fn parse_except_none(
1316        context: &ParserContext,
1317        input: &mut Parser,
1318    ) -> Result<Self, ParseError> {
1319        Self::parse_internal(context, input, /*allow_none*/ false)
1320    }
1321
1322    /// Get the high-level grammar group of this.
1323    pub fn get_type(&self) -> PositionAreaType {
1324        let first = self.first.group_type();
1325        let second = self.second.group_type();
1326        if matches!(second, PositionAreaType::None | PositionAreaType::Common) {
1327            return first;
1328        }
1329        if first == PositionAreaType::Common {
1330            return second;
1331        }
1332        if first != second {
1333            return PositionAreaType::None;
1334        }
1335        let first_axis = self.first.axis();
1336        if first_axis != PositionAreaAxis::Inferred
1337            && first_axis.is_canonically_first() == self.second.axis().is_canonically_first()
1338        {
1339            return PositionAreaType::None;
1340        }
1341        first
1342    }
1343
1344    fn parse_internal(
1345        _: &ParserContext,
1346        input: &mut Parser,
1347        allow_none: bool,
1348    ) -> Result<Self, ParseError> {
1349        let mut first = PositionAreaKeyword::parse(input)?;
1350        if first.is_none() {
1351            if allow_none {
1352                return Ok(Self::none());
1353            }
1354            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1355        }
1356
1357        let second = input.try_parse(PositionAreaKeyword::parse);
1358        if let Ok(PositionAreaKeyword::None) = second {
1359            // `none` is only allowed as a single value
1360            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1361        }
1362        let mut second = second.unwrap_or(PositionAreaKeyword::None);
1363        if second.is_none() {
1364            // Either there was no second keyword and try_parse returned a
1365            // BasicParseErrorKind::EndOfInput, or else the second "keyword"
1366            // was invalid. We assume the former case here, and if it's the
1367            // latter case then our caller detects the error (try_parse will,
1368            // have rewound, leaving an unparsed token).
1369            return Ok(Self { first, second });
1370        }
1371
1372        let pair_type = Self { first, second }.get_type();
1373        if pair_type == PositionAreaType::None {
1374            // Mismatched types or what not.
1375            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1376        }
1377        // For types that have a canonical order, remove 'span-all' (the default behavior;
1378        // unnecessary for keyword pairs with a known order).
1379        if matches!(
1380            pair_type,
1381            PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical
1382        ) {
1383            if second == PositionAreaKeyword::SpanAll {
1384                // Span-all is the default behavior, so specifying `span-all` is
1385                // superfluous.
1386                second = PositionAreaKeyword::None;
1387            } else if first == PositionAreaKeyword::SpanAll {
1388                first = second;
1389                second = PositionAreaKeyword::None;
1390            }
1391        }
1392        if first == second {
1393            second = PositionAreaKeyword::None;
1394        }
1395        let mut result = Self { first, second };
1396        result.canonicalize_order();
1397        Ok(result)
1398    }
1399
1400    fn canonicalize_order(&mut self) {
1401        let first_axis = self.first.axis();
1402        if first_axis.is_canonically_first() || self.second.is_none() {
1403            return;
1404        }
1405        let second_axis = self.second.axis();
1406        if first_axis == second_axis {
1407            // Inferred or axis-less keywords.
1408            return;
1409        }
1410        if second_axis.is_canonically_first()
1411            || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred)
1412        {
1413            std::mem::swap(&mut self.first, &mut self.second);
1414        }
1415    }
1416
1417    fn make_missing_second_explicit(&mut self) {
1418        if !self.second.is_none() {
1419            return;
1420        }
1421        let axis = self.first.axis();
1422        if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) {
1423            self.second = self.first;
1424            return;
1425        }
1426        self.second = PositionAreaKeyword::SpanAll;
1427        if !axis.is_canonically_first() {
1428            std::mem::swap(&mut self.first, &mut self.second);
1429        }
1430    }
1431
1432    /// Turns this <position-area> value into a physical <position-area>.
1433    pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self {
1434        self.make_missing_second_explicit();
1435        // If both axes are None, to_physical and canonicalize_order are not useful.
1436        // The first value refers to the block axis, the second to the inline axis;
1437        // but as a physical type, they will be interpreted as the x- and y-axis
1438        // respectively, so if the writing mode is horizontal we need to swap the
1439        // values (block -> y, inline -> x).
1440        if self.first.axis() == PositionAreaAxis::None
1441            && self.second.axis() == PositionAreaAxis::None
1442            && !cb_wm.is_vertical()
1443        {
1444            std::mem::swap(&mut self.first, &mut self.second);
1445        } else {
1446            self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block);
1447            self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline);
1448            self.canonicalize_order();
1449        }
1450        self
1451    }
1452
1453    fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) {
1454        if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) {
1455            self.first = self.first.flip_track();
1456        } else {
1457            self.second = self.second.flip_track();
1458        }
1459    }
1460
1461    fn flip_start(&mut self) {
1462        self.first = self.first.with_axis(self.first.axis().flip());
1463        self.second = self.second.with_axis(self.second.axis().flip());
1464    }
1465
1466    /// Applies a try tactic to this `<position-area>` value.
1467    pub fn with_tactic(
1468        mut self,
1469        wm: WritingMode,
1470        tactic: PositionTryFallbacksTryTacticKeyword,
1471    ) -> Self {
1472        self.make_missing_second_explicit();
1473        let axis_to_flip = match tactic {
1474            PositionTryFallbacksTryTacticKeyword::FlipStart => {
1475                self.flip_start();
1476                return self;
1477            },
1478            PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block,
1479            PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline,
1480            PositionTryFallbacksTryTacticKeyword::FlipX => {
1481                if wm.is_horizontal() {
1482                    LogicalAxis::Inline
1483                } else {
1484                    LogicalAxis::Block
1485                }
1486            },
1487            PositionTryFallbacksTryTacticKeyword::FlipY => {
1488                if wm.is_vertical() {
1489                    LogicalAxis::Inline
1490                } else {
1491                    LogicalAxis::Block
1492                }
1493            },
1494        };
1495        self.flip_logical_axis(wm, axis_to_flip);
1496        self
1497    }
1498}
1499
1500impl Parse for PositionArea {
1501    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1502        Self::parse_internal(context, input, /* allow_none = */ true)
1503    }
1504}
1505
1506/// Represents a side, either horizontal or vertical, of a CSS position.
1507pub trait Side {
1508    /// Returns the start side.
1509    fn start() -> Self;
1510
1511    /// Returns whether this side is the start side.
1512    fn is_start(&self) -> bool;
1513}
1514
1515impl Side for HorizontalPositionKeyword {
1516    #[inline]
1517    fn start() -> Self {
1518        HorizontalPositionKeyword::Left
1519    }
1520
1521    #[inline]
1522    fn is_start(&self) -> bool {
1523        *self == Self::start()
1524    }
1525}
1526
1527impl Side for VerticalPositionKeyword {
1528    #[inline]
1529    fn start() -> Self {
1530        VerticalPositionKeyword::Top
1531    }
1532
1533    #[inline]
1534    fn is_start(&self) -> bool {
1535        *self == Self::start()
1536    }
1537}
1538
1539/// Controls how the auto-placement algorithm works specifying exactly how auto-placed items
1540/// get flowed into the grid: [ row | column ] || dense
1541/// https://drafts.csswg.org/css-grid-2/#grid-auto-flow-property
1542#[derive(
1543    Clone,
1544    Copy,
1545    Debug,
1546    Eq,
1547    MallocSizeOf,
1548    Parse,
1549    PartialEq,
1550    SpecifiedValueInfo,
1551    ToComputedValue,
1552    ToResolvedValue,
1553    ToShmem,
1554    ToTyped,
1555)]
1556#[css(bitflags(
1557    mixed = "row,column,dense",
1558    validate_mixed = "Self::validate_and_simplify"
1559))]
1560#[repr(C)]
1561pub struct GridAutoFlow(u8);
1562bitflags! {
1563    impl GridAutoFlow: u8 {
1564        /// 'row' - mutually exclusive with 'column'
1565        const ROW = 1 << 0;
1566        /// 'column' - mutually exclusive with 'row'
1567        const COLUMN = 1 << 1;
1568        /// 'dense'
1569        const DENSE = 1 << 2;
1570    }
1571}
1572
1573impl GridAutoFlow {
1574    /// [ row | column ] || dense
1575    fn validate_and_simplify(&mut self) -> bool {
1576        if self.contains(Self::ROW | Self::COLUMN) {
1577            // row and column are mutually exclusive.
1578            return false;
1579        }
1580        if *self == Self::DENSE {
1581            // If there's no column, default to row.
1582            self.insert(Self::ROW);
1583        }
1584        true
1585    }
1586}
1587
1588impl ToCss for GridAutoFlow {
1589    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1590    where
1591        W: Write,
1592    {
1593        let dense = self.intersects(Self::DENSE);
1594        if self.intersects(Self::ROW) {
1595            return if dense {
1596                dest.write_str("dense")
1597            } else {
1598                dest.write_str("row")
1599            };
1600        }
1601        debug_assert!(self.intersects(Self::COLUMN));
1602        if dense {
1603            dest.write_str("column dense")
1604        } else {
1605            dest.write_str("column")
1606        }
1607    }
1608}
1609
1610/// Whether the `balance` value of `flex-wrap` is enabled.
1611#[inline]
1612fn flex_wrap_balance_enabled() -> bool {
1613    crate::pref!("layout.flexbox.balance", gecko = false)
1614}
1615
1616/// The specified and computed value of the `flex-wrap` property:
1617/// `nowrap | [ wrap | wrap-reverse ] || balance`
1618///
1619/// <https://drafts.csswg.org/css-flexbox-2/#flex-wrap-property>
1620#[derive(
1621    Clone,
1622    Copy,
1623    Debug,
1624    Eq,
1625    MallocSizeOf,
1626    Parse,
1627    PartialEq,
1628    SpecifiedValueInfo,
1629    ToComputedValue,
1630    ToCss,
1631    ToResolvedValue,
1632    ToShmem,
1633    ToTyped,
1634)]
1635#[css(bitflags(
1636    single = "nowrap",
1637    mixed = "wrap,wrap-reverse,balance",
1638    validate_mixed = "Self::validate_and_simplify"
1639))]
1640#[repr(C)]
1641pub struct FlexWrap(u8);
1642bitflags! {
1643    impl FlexWrap: u8 {
1644        /// `nowrap`
1645        const NOWRAP = 0;
1646        /// `wrap` - mutually exclusive with `wrap-reverse`
1647        const WRAP = 1 << 0;
1648        /// `wrap-reverse` - mutually exclusive with `wrap`
1649        const WRAP_REVERSE = 1 << 1;
1650        /// `balance`
1651        const BALANCE = 1 << 2;
1652    }
1653}
1654
1655impl FlexWrap {
1656    /// `nowrap | [ wrap | wrap-reverse ] || balance`
1657    fn validate_and_simplify(&mut self) -> bool {
1658        if self.contains(Self::WRAP | Self::WRAP_REVERSE) {
1659            return false;
1660        }
1661        if self.contains(Self::BALANCE) {
1662            if !flex_wrap_balance_enabled() {
1663                return false;
1664            }
1665            // `wrap balance` computes to `balance`.
1666            self.remove(Self::WRAP);
1667        }
1668        true
1669    }
1670}
1671
1672#[derive(
1673    Clone,
1674    Debug,
1675    MallocSizeOf,
1676    PartialEq,
1677    SpecifiedValueInfo,
1678    ToComputedValue,
1679    ToCss,
1680    ToResolvedValue,
1681    ToShmem,
1682)]
1683#[repr(C)]
1684/// https://drafts.csswg.org/css-grid/#named-grid-area
1685pub struct TemplateAreas {
1686    /// `named area` containing for each template area
1687    #[css(skip)]
1688    pub areas: crate::OwnedSlice<NamedArea>,
1689    /// The simplified CSS strings for serialization purpose.
1690    /// https://drafts.csswg.org/css-grid/#serialize-template
1691    // Note: We also use the length of `strings` when computing the explicit grid end line number
1692    // (i.e. row number).
1693    #[css(iterable)]
1694    pub strings: crate::OwnedSlice<crate::OwnedStr>,
1695    /// The number of columns of the grid.
1696    #[css(skip)]
1697    pub width: u32,
1698}
1699
1700/// Parser for grid template areas.
1701#[derive(Default)]
1702pub struct TemplateAreasParser {
1703    areas: Vec<NamedArea>,
1704    area_indices: PrecomputedHashMap<Atom, usize>,
1705    strings: Vec<crate::OwnedStr>,
1706    width: u32,
1707    row: u32,
1708}
1709
1710impl TemplateAreasParser {
1711    /// Parse a single string.
1712    pub fn try_parse_string(&mut self, input: &mut Parser) -> Result<(), ParseError> {
1713        input.try_parse(|input| {
1714            self.parse_string(input.expect_string()?)
1715                .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1716        })
1717    }
1718
1719    /// Parse a single string.
1720    fn parse_string(&mut self, string: &str) -> Result<(), ()> {
1721        self.row += 1;
1722        let mut simplified_string = String::new();
1723        let mut current_area_index: Option<usize> = None;
1724        let mut column = 0u32;
1725        for token in TemplateAreasTokenizer(string) {
1726            column += 1;
1727            if column > 1 {
1728                simplified_string.push(' ');
1729            }
1730            let name = if let Some(token) = token? {
1731                simplified_string.push_str(token);
1732                Atom::from(token)
1733            } else {
1734                if let Some(index) = current_area_index.take()
1735                    && self.areas[index].columns.end != column
1736                {
1737                    return Err(());
1738                }
1739                simplified_string.push('.');
1740                continue;
1741            };
1742            if let Some(index) = current_area_index {
1743                if self.areas[index].name == name {
1744                    if self.areas[index].rows.start == self.row {
1745                        self.areas[index].columns.end += 1;
1746                    }
1747                    continue;
1748                }
1749                if self.areas[index].columns.end != column {
1750                    return Err(());
1751                }
1752            }
1753            match self.area_indices.entry(name) {
1754                Entry::Occupied(ref e) => {
1755                    let index = *e.get();
1756                    if self.areas[index].columns.start != column
1757                        || self.areas[index].rows.end != self.row
1758                    {
1759                        return Err(());
1760                    }
1761                    self.areas[index].rows.end += 1;
1762                    current_area_index = Some(index);
1763                },
1764                Entry::Vacant(v) => {
1765                    let index = self.areas.len();
1766                    let name = v.key().clone();
1767                    v.insert(index);
1768                    self.areas.push(NamedArea {
1769                        name,
1770                        columns: UnsignedRange {
1771                            start: column,
1772                            end: column + 1,
1773                        },
1774                        rows: UnsignedRange {
1775                            start: self.row,
1776                            end: self.row + 1,
1777                        },
1778                    });
1779                    current_area_index = Some(index);
1780                },
1781            }
1782        }
1783        if column == 0 {
1784            // Each string must produce a valid token.
1785            // https://github.com/w3c/csswg-drafts/issues/5110
1786            return Err(());
1787        }
1788        if let Some(index) = current_area_index
1789            && self.areas[index].columns.end != column + 1
1790        {
1791            debug_assert_ne!(self.areas[index].rows.start, self.row);
1792            return Err(());
1793        }
1794        if self.row == 1 {
1795            self.width = column;
1796        } else if self.width != column {
1797            return Err(());
1798        }
1799
1800        self.strings.push(simplified_string.into());
1801        Ok(())
1802    }
1803
1804    /// Return the parsed template areas.
1805    pub fn finish(self) -> Result<TemplateAreas, ()> {
1806        if self.strings.is_empty() {
1807            return Err(());
1808        }
1809        Ok(TemplateAreas {
1810            areas: self.areas.into(),
1811            strings: self.strings.into(),
1812            width: self.width,
1813        })
1814    }
1815}
1816
1817impl TemplateAreas {
1818    fn parse_internal(input: &mut Parser) -> Result<Self, ()> {
1819        let mut parser = TemplateAreasParser::default();
1820        while parser.try_parse_string(input).is_ok() {}
1821        parser.finish()
1822    }
1823}
1824
1825impl Parse for TemplateAreas {
1826    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1827        Self::parse_internal(input)
1828            .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1829    }
1830}
1831
1832/// Arc type for `Arc<TemplateAreas>`
1833#[derive(
1834    Clone,
1835    Debug,
1836    MallocSizeOf,
1837    PartialEq,
1838    SpecifiedValueInfo,
1839    ToComputedValue,
1840    ToCss,
1841    ToResolvedValue,
1842    ToShmem,
1843)]
1844#[repr(transparent)]
1845pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
1846
1847impl Parse for TemplateAreasArc {
1848    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1849        let parsed = TemplateAreas::parse(context, input)?;
1850        Ok(TemplateAreasArc(Arc::new(parsed)))
1851    }
1852}
1853
1854/// A range of rows or columns. Using this instead of std::ops::Range for FFI
1855/// purposes.
1856#[repr(C)]
1857#[derive(
1858    Clone,
1859    Debug,
1860    MallocSizeOf,
1861    PartialEq,
1862    SpecifiedValueInfo,
1863    ToComputedValue,
1864    ToResolvedValue,
1865    ToShmem,
1866)]
1867pub struct UnsignedRange {
1868    /// The start of the range.
1869    pub start: u32,
1870    /// The end of the range.
1871    pub end: u32,
1872}
1873
1874#[derive(
1875    Clone,
1876    Debug,
1877    MallocSizeOf,
1878    PartialEq,
1879    SpecifiedValueInfo,
1880    ToComputedValue,
1881    ToResolvedValue,
1882    ToShmem,
1883)]
1884#[repr(C)]
1885/// Not associated with any particular grid item, but can be referenced from the
1886/// grid-placement properties.
1887pub struct NamedArea {
1888    /// Name of the `named area`
1889    pub name: Atom,
1890    /// Rows of the `named area`
1891    pub rows: UnsignedRange,
1892    /// Columns of the `named area`
1893    pub columns: UnsignedRange,
1894}
1895
1896/// Tokenize the string into a list of the tokens,
1897/// using longest-match semantics
1898struct TemplateAreasTokenizer<'a>(&'a str);
1899
1900impl<'a> Iterator for TemplateAreasTokenizer<'a> {
1901    type Item = Result<Option<&'a str>, ()>;
1902
1903    fn next(&mut self) -> Option<Self::Item> {
1904        let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
1905        if rest.is_empty() {
1906            return None;
1907        }
1908        if rest.starts_with('.') {
1909            self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
1910            return Some(Ok(None));
1911        }
1912        if !rest.starts_with(is_name_code_point) {
1913            return Some(Err(()));
1914        }
1915        let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
1916        let token = &rest[..token_len];
1917        self.0 = &rest[token_len..];
1918        Some(Ok(Some(token)))
1919    }
1920}
1921
1922fn is_name_code_point(c: char) -> bool {
1923    c.is_ascii_uppercase()
1924        || c.is_ascii_lowercase()
1925        || c >= '\u{80}'
1926        || c == '_'
1927        || c.is_ascii_digit()
1928        || c == '-'
1929}
1930
1931/// This property specifies named grid areas.
1932///
1933/// The syntax of this property also provides a visualization of the structure
1934/// of the grid, making the overall layout of the grid container easier to
1935/// understand.
1936#[repr(C, u8)]
1937#[derive(
1938    Clone,
1939    Debug,
1940    MallocSizeOf,
1941    Parse,
1942    PartialEq,
1943    SpecifiedValueInfo,
1944    ToComputedValue,
1945    ToCss,
1946    ToResolvedValue,
1947    ToShmem,
1948    ToTyped,
1949)]
1950#[typed(todo_derive_fields)]
1951pub enum GridTemplateAreas {
1952    /// The `none` value.
1953    None,
1954    /// The actual value.
1955    Areas(TemplateAreasArc),
1956}
1957
1958impl GridTemplateAreas {
1959    #[inline]
1960    /// Get default value as `none`
1961    pub fn none() -> GridTemplateAreas {
1962        GridTemplateAreas::None
1963    }
1964}
1965
1966/// A specified value for the `z-index` property.
1967pub type ZIndex = GenericZIndex<Integer>;
1968
1969/// A specified value for the `aspect-ratio` property.
1970pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
1971
1972impl Parse for AspectRatio {
1973    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1974        use crate::values::generics::position::PreferredRatio;
1975        use crate::values::specified::Ratio;
1976
1977        let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
1978        let ratio = input.try_parse(|i| Ratio::parse(context, i));
1979        if auto.is_err() {
1980            auto = input.try_parse(|i| i.expect_ident_matching("auto"));
1981        }
1982
1983        if auto.is_err() && ratio.is_err() {
1984            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1985        }
1986
1987        Ok(AspectRatio {
1988            auto: auto.is_ok(),
1989            ratio: match ratio {
1990                Ok(ratio) => PreferredRatio::Ratio(ratio),
1991                Err(..) => PreferredRatio::None,
1992            },
1993        })
1994    }
1995}
1996
1997impl AspectRatio {
1998    /// Returns Self by a valid ratio.
1999    pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
2000        use crate::values::generics::position::PreferredRatio;
2001        use crate::values::generics::ratio::Ratio;
2002        AspectRatio {
2003            auto: true,
2004            ratio: PreferredRatio::Ratio(Ratio(
2005                NonNegativeNumber::new(w),
2006                NonNegativeNumber::new(h),
2007            )),
2008        }
2009    }
2010}
2011
2012/// A specified value for inset types.
2013pub type Inset = GenericInset<specified::Percentage, LengthPercentage>;
2014
2015impl Inset {
2016    /// Parses an inset type, allowing the unitless length quirk.
2017    /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk>
2018    #[inline]
2019    pub fn parse_quirky(
2020        context: &ParserContext,
2021        input: &mut Parser,
2022        allow_quirks: AllowQuirks,
2023    ) -> Result<Self, ParseError> {
2024        if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
2025        {
2026            return Ok(Self::LengthPercentage(l));
2027        }
2028        match input.try_parse(|i| i.expect_ident_matching("auto")) {
2029            Ok(_) => return Ok(Self::Auto),
2030            Err(e) if !crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) => {
2031                return Err(e.into());
2032            },
2033            Err(_) => (),
2034        };
2035        Self::parse_anchor_functions_quirky(context, input, allow_quirks)
2036    }
2037
2038    fn parse_as_anchor_function_fallback(
2039        context: &ParserContext,
2040        input: &mut Parser,
2041    ) -> Result<Self, ParseError> {
2042        if let Ok(l) =
2043            input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No))
2044        {
2045            return Ok(Self::LengthPercentage(l));
2046        }
2047        Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No)
2048    }
2049
2050    fn parse_anchor_functions_quirky(
2051        context: &ParserContext,
2052        input: &mut Parser,
2053        allow_quirks: AllowQuirks,
2054    ) -> Result<Self, ParseError> {
2055        debug_assert!(
2056            crate::pref!("layout.css.anchor-positioning.enabled", gecko = true),
2057            "How are we parsing with pref off?"
2058        );
2059        if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) {
2060            return Ok(Self::AnchorFunction(Box::new(inner)));
2061        }
2062        if let Ok(inner) =
2063            input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i))
2064        {
2065            return Ok(Self::AnchorSizeFunction(Box::new(inner)));
2066        }
2067        Ok(Self::AnchorContainingCalcFunction(input.try_parse(
2068            |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks),
2069        )?))
2070    }
2071}
2072
2073impl Parse for Inset {
2074    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
2075        Self::parse_quirky(context, input, AllowQuirks::No)
2076    }
2077}
2078
2079/// A specified value for `anchor()` function.
2080pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>;
2081
2082impl Parse for AnchorFunction {
2083    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
2084        if !crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) {
2085            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2086        }
2087        input.expect_function_matching("anchor")?;
2088        input.parse_nested_block(|i| {
2089            let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
2090            let side = GenericAnchorSide::parse(context, i)?;
2091            let target_element = if target_element.is_none() {
2092                i.try_parse(|i| DashedIdent::parse(context, i)).ok()
2093            } else {
2094                target_element
2095            };
2096            let fallback = i
2097                .try_parse(|i| {
2098                    i.expect_comma()?;
2099                    Inset::parse_as_anchor_function_fallback(context, i)
2100                })
2101                .ok();
2102            Ok(Self {
2103                target_element: TreeScoped::with_default_level(
2104                    target_element.unwrap_or_else(DashedIdent::empty),
2105                ),
2106                side,
2107                fallback: fallback.into(),
2108            })
2109        })
2110    }
2111}
2112
2113/// https://drafts.csswg.org/css-flexbox/#flex-direction-property
2114#[allow(missing_docs)]
2115#[derive(
2116    Clone,
2117    Copy,
2118    Debug,
2119    Deserialize,
2120    Eq,
2121    FromPrimitive,
2122    Hash,
2123    MallocSizeOf,
2124    Parse,
2125    PartialEq,
2126    Serialize,
2127    SpecifiedValueInfo,
2128    ToComputedValue,
2129    ToCss,
2130    ToResolvedValue,
2131    ToShmem,
2132    ToTyped,
2133)]
2134#[repr(u8)]
2135pub enum FlexDirection {
2136    Row,
2137    RowReverse,
2138    Column,
2139    ColumnReverse,
2140}
2141
2142/// https://drafts.csswg.org/css-ui/#propdef-box-sizing
2143#[allow(missing_docs)]
2144#[derive(
2145    Clone,
2146    Copy,
2147    Debug,
2148    Deserialize,
2149    Eq,
2150    FromPrimitive,
2151    Hash,
2152    MallocSizeOf,
2153    Parse,
2154    PartialEq,
2155    Serialize,
2156    SpecifiedValueInfo,
2157    ToComputedValue,
2158    ToCss,
2159    ToResolvedValue,
2160    ToShmem,
2161    ToTyped,
2162)]
2163#[repr(u8)]
2164pub enum BoxSizing {
2165    ContentBox,
2166    BorderBox,
2167}
2168
2169/// https://drafts.csswg.org/css-images/#propdef-object-fit
2170#[allow(missing_docs)]
2171#[derive(
2172    Clone,
2173    Copy,
2174    Debug,
2175    Deserialize,
2176    Eq,
2177    FromPrimitive,
2178    Hash,
2179    MallocSizeOf,
2180    Parse,
2181    PartialEq,
2182    Serialize,
2183    SpecifiedValueInfo,
2184    ToComputedValue,
2185    ToCss,
2186    ToResolvedValue,
2187    ToShmem,
2188    ToTyped,
2189)]
2190#[repr(u8)]
2191pub enum ObjectFit {
2192    Fill,
2193    Contain,
2194    Cover,
2195    None,
2196    ScaleDown,
2197}