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