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