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