Skip to main content

style/values/specified/
position.rs

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