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