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