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