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)]
359#[css(comma)]
360pub struct AnchorName(
361    #[css(iterable, if_empty = "none")]
362    #[ignore_malloc_size_of = "Arc"]
363    pub crate::ArcSlice<DashedIdent>,
364);
365
366impl AnchorName {
367    /// Return the `none` value.
368    pub fn none() -> Self {
369        Self(Default::default())
370    }
371
372    /// Returns whether this is the `none` value.
373    pub fn is_none(&self) -> bool {
374        self.0.is_empty()
375    }
376}
377
378impl Parse for AnchorName {
379    fn parse<'i, 't>(
380        context: &ParserContext,
381        input: &mut Parser<'i, 't>,
382    ) -> Result<Self, ParseError<'i>> {
383        let location = input.current_source_location();
384        let first = input.expect_ident()?;
385        if first.eq_ignore_ascii_case("none") {
386            return Ok(Self::none());
387        }
388        // The common case is probably just to have a single anchor name, so
389        // space for four on the stack should be plenty.
390        let mut idents: SmallVec<[DashedIdent; 4]> =
391            smallvec![DashedIdent::from_ident(location, first,)?];
392        while input.try_parse(|input| input.expect_comma()).is_ok() {
393            idents.push(DashedIdent::parse(context, input)?);
394        }
395        Ok(AnchorName(ArcSlice::from_iter(idents.drain(..))))
396    }
397}
398
399/// https://drafts.csswg.org/css-anchor-position-1/#propdef-scope
400#[derive(
401    Clone,
402    Debug,
403    MallocSizeOf,
404    PartialEq,
405    SpecifiedValueInfo,
406    ToComputedValue,
407    ToCss,
408    ToResolvedValue,
409    ToShmem,
410)]
411#[repr(u8)]
412pub enum AnchorScope {
413    /// `none`
414    None,
415    /// `all`
416    All,
417    /// `<dashed-ident>#`
418    #[css(comma)]
419    Idents(
420        #[css(iterable)]
421        #[ignore_malloc_size_of = "Arc"]
422        crate::ArcSlice<DashedIdent>,
423    ),
424}
425
426impl AnchorScope {
427    /// Return the `none` value.
428    pub fn none() -> Self {
429        Self::None
430    }
431
432    /// Returns whether this is the `none` value.
433    pub fn is_none(&self) -> bool {
434        *self == Self::None
435    }
436}
437
438impl Parse for AnchorScope {
439    fn parse<'i, 't>(
440        context: &ParserContext,
441        input: &mut Parser<'i, 't>,
442    ) -> Result<Self, ParseError<'i>> {
443        let location = input.current_source_location();
444        let first = input.expect_ident()?;
445        if first.eq_ignore_ascii_case("none") {
446            return Ok(Self::None);
447        }
448        if first.eq_ignore_ascii_case("all") {
449            return Ok(Self::All);
450        }
451        // Authors using more than a handful of anchored elements is likely
452        // uncommon, so we only pre-allocate for 8 on the stack here.
453        let mut idents: SmallVec<[DashedIdent; 8]> =
454            smallvec![DashedIdent::from_ident(location, first,)?];
455        while input.try_parse(|input| input.expect_comma()).is_ok() {
456            idents.push(DashedIdent::parse(context, input)?);
457        }
458        Ok(AnchorScope::Idents(ArcSlice::from_iter(idents.drain(..))))
459    }
460}
461
462/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-anchor
463#[derive(
464    Clone,
465    Debug,
466    MallocSizeOf,
467    Parse,
468    PartialEq,
469    SpecifiedValueInfo,
470    ToComputedValue,
471    ToCss,
472    ToResolvedValue,
473    ToShmem,
474)]
475#[repr(u8)]
476pub enum PositionAnchor {
477    /// `auto`
478    Auto,
479    /// `<dashed-ident>`
480    Ident(DashedIdent),
481}
482
483impl PositionAnchor {
484    /// Return the `auto` value.
485    pub fn auto() -> Self {
486        Self::Auto
487    }
488
489    /// Returns whether this is the `auto` value.
490    pub fn is_auto(&self) -> bool {
491        *self == Self::Auto
492    }
493}
494
495#[derive(
496    Clone,
497    Copy,
498    Debug,
499    Default,
500    Eq,
501    MallocSizeOf,
502    Parse,
503    PartialEq,
504    Serialize,
505    SpecifiedValueInfo,
506    ToComputedValue,
507    ToCss,
508    ToResolvedValue,
509    ToShmem,
510)]
511#[repr(u8)]
512/// How to swap values for the automatically-generated position tactic.
513pub enum PositionTryFallbacksTryTacticKeyword {
514    /// Magic value for no change.
515    #[css(skip)]
516    #[default]
517    None,
518    /// Swap the values in the block axis.
519    FlipBlock,
520    /// Swap the values in the inline axis.
521    FlipInline,
522    /// Swap the values in the start properties.
523    FlipStart,
524}
525
526impl PositionTryFallbacksTryTacticKeyword {
527    fn is_none(&self) -> bool {
528        *self == Self::None
529    }
530}
531
532#[derive(
533    Clone,
534    Copy,
535    Debug,
536    Default,
537    Eq,
538    MallocSizeOf,
539    PartialEq,
540    Serialize,
541    SpecifiedValueInfo,
542    ToComputedValue,
543    ToCss,
544    ToResolvedValue,
545    ToShmem,
546)]
547#[repr(C)]
548/// Changes for the automatically-generated position option.
549/// Note that this is order-dependent - e.g. `flip-start flip-inline` != `flip-inline flip-start`.
550///
551/// https://drafts.csswg.org/css-anchor-position-1/#typedef-position-try-fallbacks-try-tactic
552pub struct PositionTryFallbacksTryTactic(
553    pub PositionTryFallbacksTryTacticKeyword,
554    pub PositionTryFallbacksTryTacticKeyword,
555    pub PositionTryFallbacksTryTacticKeyword,
556);
557
558impl Parse for PositionTryFallbacksTryTactic {
559    fn parse<'i, 't>(
560        _context: &ParserContext,
561        input: &mut Parser<'i, 't>,
562    ) -> Result<Self, ParseError<'i>> {
563        let first = input.try_parse(PositionTryFallbacksTryTacticKeyword::parse)?;
564        let second = input
565            .try_parse(PositionTryFallbacksTryTacticKeyword::parse)
566            .unwrap_or_default();
567        let third = input
568            .try_parse(PositionTryFallbacksTryTacticKeyword::parse)
569            .unwrap_or_default();
570        if first == second || first == third || (!second.is_none() && second == third) {
571            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
572        }
573        Ok(Self(first, second, third))
574    }
575}
576
577impl PositionTryFallbacksTryTactic {
578    fn is_empty(&self) -> bool {
579        self.0.is_none()
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)]
674#[css(comma)]
675#[repr(C)]
676/// https://drafts.csswg.org/css-anchor-position-1/#position-try-fallbacks
677pub struct PositionTryFallbacks(
678    #[css(iterable, if_empty = "none")]
679    #[ignore_malloc_size_of = "Arc"]
680    pub crate::ArcSlice<PositionTryFallbacksItem>,
681);
682
683impl PositionTryFallbacks {
684    #[inline]
685    /// Return the `none` value.
686    pub fn none() -> Self {
687        Self(Default::default())
688    }
689
690    /// Returns whether this is the `none` value.
691    pub fn is_none(&self) -> bool {
692        self.0.is_empty()
693    }
694}
695
696impl Parse for PositionTryFallbacks {
697    fn parse<'i, 't>(
698        context: &ParserContext,
699        input: &mut Parser<'i, 't>,
700    ) -> Result<Self, ParseError<'i>> {
701        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
702            return Ok(Self::none());
703        }
704        // The common case is unlikely to include many alternate positioning
705        // styles, so space for four on the stack should typically be enough.
706        let mut items: SmallVec<[PositionTryFallbacksItem; 4]> =
707            smallvec![PositionTryFallbacksItem::parse(context, input)?];
708        while input.try_parse(|input| input.expect_comma()).is_ok() {
709            items.push(PositionTryFallbacksItem::parse(context, input)?);
710        }
711        Ok(Self(ArcSlice::from_iter(items.drain(..))))
712    }
713}
714
715/// https://drafts.csswg.org/css-anchor-position-1/#position-try-order-property
716#[derive(
717    Clone,
718    Copy,
719    Debug,
720    Default,
721    Eq,
722    MallocSizeOf,
723    Parse,
724    PartialEq,
725    SpecifiedValueInfo,
726    ToComputedValue,
727    ToCss,
728    ToResolvedValue,
729    ToShmem,
730)]
731#[repr(u8)]
732pub enum PositionTryOrder {
733    #[default]
734    /// `normal`
735    Normal,
736    /// `most-width`
737    MostWidth,
738    /// `most-height`
739    MostHeight,
740    /// `most-block-size`
741    MostBlockSize,
742    /// `most-inline-size`
743    MostInlineSize,
744}
745
746impl PositionTryOrder {
747    #[inline]
748    /// Return the `auto` value.
749    pub fn normal() -> Self {
750        Self::Normal
751    }
752
753    /// Returns whether this is the `auto` value.
754    pub fn is_normal(&self) -> bool {
755        *self == Self::Normal
756    }
757}
758
759#[derive(
760    Clone,
761    Copy,
762    Debug,
763    Eq,
764    MallocSizeOf,
765    Parse,
766    PartialEq,
767    Serialize,
768    SpecifiedValueInfo,
769    ToComputedValue,
770    ToCss,
771    ToResolvedValue,
772    ToShmem,
773)]
774#[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))]
775#[repr(C)]
776/// Specified keyword values for the position-visibility property.
777pub struct PositionVisibility(u8);
778bitflags! {
779    impl PositionVisibility: u8 {
780        /// Element is displayed without regard for its anchors or its overflowing status.
781        const ALWAYS = 0;
782        /// anchors-valid
783        const ANCHORS_VALID = 1 << 0;
784        /// anchors-visible
785        const ANCHORS_VISIBLE = 1 << 1;
786        /// no-overflow
787        const NO_OVERFLOW = 1 << 2;
788    }
789}
790
791impl Default for PositionVisibility {
792    fn default() -> Self {
793        Self::ALWAYS
794    }
795}
796
797impl PositionVisibility {
798    #[inline]
799    /// Returns the initial value of position-visibility
800    pub fn always() -> Self {
801        Self::ALWAYS
802    }
803}
804
805#[derive(
806    Clone,
807    Copy,
808    Debug,
809    Default,
810    Eq,
811    MallocSizeOf,
812    Parse,
813    PartialEq,
814    SpecifiedValueInfo,
815    ToComputedValue,
816    ToCss,
817    ToResolvedValue,
818    ToShmem,
819)]
820#[allow(missing_docs)]
821#[repr(u8)]
822/// Possible values for the `position-area` preperty's keywords.
823/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area
824pub enum PositionAreaKeyword {
825    #[default]
826    None,
827
828    // Common (shared) keywords:
829    Center,
830    SpanAll,
831
832    // Horizontal keywords:
833    Left,
834    Right,
835    SpanLeft,
836    SpanRight,
837    XStart,
838    XEnd,
839    SpanXStart,
840    SpanXEnd,
841    XSelfStart,
842    XSelfEnd,
843    SpanXSelfStart,
844    SpanXSelfEnd,
845    // Vertical keywords:
846    Top,
847    Bottom,
848    SpanTop,
849    SpanBottom,
850    YStart,
851    YEnd,
852    SpanYStart,
853    SpanYEnd,
854    YSelfStart,
855    YSelfEnd,
856    SpanYSelfStart,
857    SpanYSelfEnd,
858
859    // Block keywords:
860    BlockStart,
861    BlockEnd,
862    SpanBlockStart,
863    SpanBlockEnd,
864    // Inline keywords:
865    InlineStart,
866    InlineEnd,
867    SpanInlineStart,
868    SpanInlineEnd,
869
870    // "Self" block keywords:
871    SelfBlockStart,
872    SelfBlockEnd,
873    SpanSelfBlockStart,
874    SpanSelfBlockEnd,
875    // "Self" inline keywords:
876    SelfInlineStart,
877    SelfInlineEnd,
878    SpanSelfInlineStart,
879    SpanSelfInlineEnd,
880
881    // Inferred axis keywords:
882    Start,
883    End,
884    SpanStart,
885    SpanEnd,
886
887    // "Self" inferred axis keywords:
888    SelfStart,
889    SelfEnd,
890    SpanSelfStart,
891    SpanSelfEnd,
892}
893
894#[allow(missing_docs)]
895impl PositionAreaKeyword {
896    #[inline]
897    pub fn none() -> Self {
898        Self::None
899    }
900
901    pub fn is_none(&self) -> bool {
902        *self == Self::None
903    }
904
905    /// Is a value that's common to all compatible keyword groupings.
906    pub fn is_common(&self) -> bool {
907        *self == Self::Center || *self == Self::SpanAll
908    }
909
910    pub fn is_horizontal(&self) -> bool {
911        matches!(
912            self,
913            Self::Left
914                | Self::Right
915                | Self::SpanLeft
916                | Self::SpanRight
917                | Self::XStart
918                | Self::XEnd
919                | Self::SpanXStart
920                | Self::SpanXEnd
921                | Self::XSelfStart
922                | Self::XSelfEnd
923                | Self::SpanXSelfStart
924                | Self::SpanXSelfEnd
925        )
926    }
927    pub fn is_vertical(&self) -> bool {
928        matches!(
929            self,
930            Self::Top
931                | Self::Bottom
932                | Self::SpanTop
933                | Self::SpanBottom
934                | Self::YStart
935                | Self::YEnd
936                | Self::SpanYStart
937                | Self::SpanYEnd
938                | Self::YSelfStart
939                | Self::YSelfEnd
940                | Self::SpanYSelfStart
941                | Self::SpanYSelfEnd
942        )
943    }
944
945    pub fn is_block(&self) -> bool {
946        matches!(
947            self,
948            Self::BlockStart | Self::BlockEnd | Self::SpanBlockStart | Self::SpanBlockEnd
949        )
950    }
951    pub fn is_inline(&self) -> bool {
952        matches!(
953            self,
954            Self::InlineStart | Self::InlineEnd | Self::SpanInlineStart | Self::SpanInlineEnd
955        )
956    }
957
958    pub fn is_self_block(&self) -> bool {
959        matches!(
960            self,
961            Self::SelfBlockStart
962                | Self::SelfBlockEnd
963                | Self::SpanSelfBlockStart
964                | Self::SpanSelfBlockEnd
965        )
966    }
967    pub fn is_self_inline(&self) -> bool {
968        matches!(
969            self,
970            Self::SelfInlineStart
971                | Self::SelfInlineEnd
972                | Self::SpanSelfInlineStart
973                | Self::SpanSelfInlineEnd
974        )
975    }
976
977    pub fn is_inferred_logical(&self) -> bool {
978        matches!(
979            self,
980            Self::Start | Self::End | Self::SpanStart | Self::SpanEnd
981        )
982    }
983
984    pub fn is_self_inferred_logical(&self) -> bool {
985        matches!(
986            self,
987            Self::SelfStart | Self::SelfEnd | Self::SpanSelfStart | Self::SpanSelfEnd
988        )
989    }
990}
991
992#[inline]
993fn is_compatible_pairing(first: PositionAreaKeyword, second: PositionAreaKeyword) -> bool {
994    if first.is_none() || second.is_none() {
995        // `none` is not allowed as one of the keywords when two keywords are
996        // provided.
997        return false;
998    }
999    if first.is_common() || second.is_common() {
1000        return true;
1001    }
1002    if first.is_horizontal() {
1003        return second.is_vertical();
1004    }
1005    if first.is_vertical() {
1006        return second.is_horizontal();
1007    }
1008    if first.is_block() {
1009        return second.is_inline();
1010    }
1011    if first.is_inline() {
1012        return second.is_block();
1013    }
1014    if first.is_self_block() {
1015        return second.is_self_inline();
1016    }
1017    if first.is_self_inline() {
1018        return second.is_self_block();
1019    }
1020    if first.is_inferred_logical() {
1021        return second.is_inferred_logical();
1022    }
1023    if first.is_self_inferred_logical() {
1024        return second.is_self_inferred_logical();
1025    }
1026
1027    debug_assert!(false, "Not reached");
1028
1029    // Return false to increase the chances of this being reported to us if we
1030    // ever were to get here.
1031    false
1032}
1033
1034#[derive(
1035    Clone,
1036    Copy,
1037    Debug,
1038    Eq,
1039    MallocSizeOf,
1040    PartialEq,
1041    SpecifiedValueInfo,
1042    ToComputedValue,
1043    ToCss,
1044    ToResolvedValue,
1045    ToShmem,
1046)]
1047#[repr(C)]
1048/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area
1049pub struct PositionArea {
1050    /// First keyword, if any.
1051    pub first: PositionAreaKeyword,
1052    /// Second keyword, if any.
1053    #[css(skip_if = "PositionAreaKeyword::is_none")]
1054    pub second: PositionAreaKeyword,
1055}
1056
1057#[allow(missing_docs)]
1058impl PositionArea {
1059    #[inline]
1060    pub fn none() -> Self {
1061        Self {
1062            first: PositionAreaKeyword::None,
1063            second: PositionAreaKeyword::None,
1064        }
1065    }
1066
1067    #[inline]
1068    pub fn is_none(&self) -> bool {
1069        self.first.is_none()
1070    }
1071
1072    pub fn parse_except_none<'i, 't>(
1073        context: &ParserContext,
1074        input: &mut Parser<'i, 't>,
1075    ) -> Result<Self, ParseError<'i>> {
1076        Self::parse_internal(context, input, /*allow_none*/ false)
1077    }
1078
1079    fn parse_internal<'i, 't>(
1080        _context: &ParserContext,
1081        input: &mut Parser<'i, 't>,
1082        allow_none: bool,
1083    ) -> Result<Self, ParseError<'i>> {
1084        let mut location = input.current_source_location();
1085        let mut first = PositionAreaKeyword::parse(input)?;
1086        if first.is_none() {
1087            if allow_none {
1088                return Ok(Self::none());
1089            }
1090            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1091        }
1092
1093        location = input.current_source_location();
1094        let second = input.try_parse(PositionAreaKeyword::parse);
1095        if let Ok(PositionAreaKeyword::None) = second {
1096            // `none` is only allowed as a single value
1097            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1098        }
1099        let mut second = second.unwrap_or(PositionAreaKeyword::None);
1100        if second.is_none() {
1101            // Either there was no second keyword and try_parse returned a
1102            // BasicParseErrorKind::EndOfInput, or else the second "keyword"
1103            // was invalid. We assume the former case here, and if it's the
1104            // latter case then our caller detects the error (try_parse will,
1105            // have rewound, leaving an unparsed token).
1106            return Ok(Self { first, second });
1107        }
1108
1109        if !is_compatible_pairing(first, second) {
1110            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1111        }
1112
1113        // Normalize by applying the shortest serialization principle:
1114        // https://drafts.csswg.org/cssom/#serializing-css-values
1115        if first.is_inferred_logical()
1116            || second.is_inferred_logical()
1117            || first.is_self_inferred_logical()
1118            || second.is_self_inferred_logical()
1119            || (first.is_common() && second.is_common())
1120        {
1121            // In these cases we must not change the order of the keywords
1122            // since their meaning is inferred from their order. However, if
1123            // both keywords are the same, only one should be set.
1124            if first == second {
1125                second = PositionAreaKeyword::None;
1126            }
1127        } else if second == PositionAreaKeyword::SpanAll {
1128            // Span-all is the default behavior, so specifying `span-all` is
1129            // superfluous.
1130            second = PositionAreaKeyword::None;
1131        } else if first == PositionAreaKeyword::SpanAll {
1132            // Same here, but the non-superfluous keyword must come first.
1133            first = second;
1134            second = PositionAreaKeyword::None;
1135        } else if first.is_vertical()
1136            || second.is_horizontal()
1137            || first.is_inline()
1138            || second.is_block()
1139            || first.is_self_inline()
1140            || second.is_self_block()
1141        {
1142            // Canonical order is horizontal before vertical, block before inline.
1143            std::mem::swap(&mut first, &mut second);
1144        }
1145
1146        Ok(Self { first, second })
1147    }
1148}
1149
1150impl Parse for PositionArea {
1151    fn parse<'i, 't>(
1152        context: &ParserContext,
1153        input: &mut Parser<'i, 't>,
1154    ) -> Result<Self, ParseError<'i>> {
1155        Self::parse_internal(context, input, /*allow_none*/ true)
1156    }
1157}
1158
1159/// Represents a side, either horizontal or vertical, of a CSS position.
1160pub trait Side {
1161    /// Returns the start side.
1162    fn start() -> Self;
1163
1164    /// Returns whether this side is the start side.
1165    fn is_start(&self) -> bool;
1166}
1167
1168impl Side for HorizontalPositionKeyword {
1169    #[inline]
1170    fn start() -> Self {
1171        HorizontalPositionKeyword::Left
1172    }
1173
1174    #[inline]
1175    fn is_start(&self) -> bool {
1176        *self == Self::start()
1177    }
1178}
1179
1180impl Side for VerticalPositionKeyword {
1181    #[inline]
1182    fn start() -> Self {
1183        VerticalPositionKeyword::Top
1184    }
1185
1186    #[inline]
1187    fn is_start(&self) -> bool {
1188        *self == Self::start()
1189    }
1190}
1191
1192/// Controls how the auto-placement algorithm works specifying exactly how auto-placed items
1193/// get flowed into the grid: [ row | column ] || dense
1194/// https://drafts.csswg.org/css-grid-2/#grid-auto-flow-property
1195#[derive(
1196    Clone,
1197    Copy,
1198    Debug,
1199    Eq,
1200    MallocSizeOf,
1201    Parse,
1202    PartialEq,
1203    SpecifiedValueInfo,
1204    ToComputedValue,
1205    ToResolvedValue,
1206    ToShmem,
1207)]
1208#[css(bitflags(
1209    mixed = "row,column,dense",
1210    validate_mixed = "Self::validate_and_simplify"
1211))]
1212#[repr(C)]
1213pub struct GridAutoFlow(u8);
1214bitflags! {
1215    impl GridAutoFlow: u8 {
1216        /// 'row' - mutually exclusive with 'column'
1217        const ROW = 1 << 0;
1218        /// 'column' - mutually exclusive with 'row'
1219        const COLUMN = 1 << 1;
1220        /// 'dense'
1221        const DENSE = 1 << 2;
1222    }
1223}
1224
1225impl GridAutoFlow {
1226    /// [ row | column ] || dense
1227    fn validate_and_simplify(&mut self) -> bool {
1228        if self.contains(Self::ROW | Self::COLUMN) {
1229            // row and column are mutually exclusive.
1230            return false;
1231        }
1232        if *self == Self::DENSE {
1233            // If there's no column, default to row.
1234            self.insert(Self::ROW);
1235        }
1236        true
1237    }
1238}
1239
1240impl ToCss for GridAutoFlow {
1241    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1242    where
1243        W: Write,
1244    {
1245        let dense = self.intersects(Self::DENSE);
1246        if self.intersects(Self::ROW) {
1247            return if dense {
1248                dest.write_str("dense")
1249            } else {
1250                dest.write_str("row")
1251            };
1252        }
1253        debug_assert!(self.intersects(Self::COLUMN));
1254        if dense {
1255            dest.write_str("column dense")
1256        } else {
1257            dest.write_str("column")
1258        }
1259    }
1260}
1261
1262#[repr(u8)]
1263#[derive(
1264    Clone,
1265    Copy,
1266    Debug,
1267    Eq,
1268    MallocSizeOf,
1269    PartialEq,
1270    SpecifiedValueInfo,
1271    ToComputedValue,
1272    ToCss,
1273    ToResolvedValue,
1274    ToShmem,
1275)]
1276/// Masonry auto-placement algorithm packing.
1277pub enum MasonryPlacement {
1278    /// Place the item in the track(s) with the smallest extent so far.
1279    Pack,
1280    /// Place the item after the last item, from start to end.
1281    Next,
1282}
1283
1284#[repr(u8)]
1285#[derive(
1286    Clone,
1287    Copy,
1288    Debug,
1289    Eq,
1290    MallocSizeOf,
1291    PartialEq,
1292    SpecifiedValueInfo,
1293    ToComputedValue,
1294    ToCss,
1295    ToResolvedValue,
1296    ToShmem,
1297)]
1298/// Masonry auto-placement algorithm item sorting option.
1299pub enum MasonryItemOrder {
1300    /// Place all items with a definite placement before auto-placed items.
1301    DefiniteFirst,
1302    /// Place items in `order-modified document order`.
1303    Ordered,
1304}
1305
1306#[derive(
1307    Clone,
1308    Copy,
1309    Debug,
1310    Eq,
1311    MallocSizeOf,
1312    PartialEq,
1313    SpecifiedValueInfo,
1314    ToComputedValue,
1315    ToCss,
1316    ToResolvedValue,
1317    ToShmem,
1318)]
1319#[repr(C)]
1320/// Controls how the Masonry layout algorithm works
1321/// specifying exactly how auto-placed items get flowed in the masonry axis.
1322pub struct MasonryAutoFlow {
1323    /// Specify how to pick a auto-placement track.
1324    #[css(contextual_skip_if = "is_pack_with_non_default_order")]
1325    pub placement: MasonryPlacement,
1326    /// Specify how to pick an item to place.
1327    #[css(skip_if = "is_item_order_definite_first")]
1328    pub order: MasonryItemOrder,
1329}
1330
1331#[inline]
1332fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
1333    *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
1334}
1335
1336#[inline]
1337fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
1338    *order == MasonryItemOrder::DefiniteFirst
1339}
1340
1341impl MasonryAutoFlow {
1342    #[inline]
1343    /// Get initial `masonry-auto-flow` value.
1344    pub fn initial() -> MasonryAutoFlow {
1345        MasonryAutoFlow {
1346            placement: MasonryPlacement::Pack,
1347            order: MasonryItemOrder::DefiniteFirst,
1348        }
1349    }
1350}
1351
1352impl Parse for MasonryAutoFlow {
1353    /// [ definite-first | ordered ] || [ pack | next ]
1354    fn parse<'i, 't>(
1355        _context: &ParserContext,
1356        input: &mut Parser<'i, 't>,
1357    ) -> Result<MasonryAutoFlow, ParseError<'i>> {
1358        let mut value = MasonryAutoFlow::initial();
1359        let mut got_placement = false;
1360        let mut got_order = false;
1361        while !input.is_exhausted() {
1362            let location = input.current_source_location();
1363            let ident = input.expect_ident()?;
1364            let success = match_ignore_ascii_case! { &ident,
1365                "pack" if !got_placement => {
1366                    got_placement = true;
1367                    true
1368                },
1369                "next" if !got_placement => {
1370                    value.placement = MasonryPlacement::Next;
1371                    got_placement = true;
1372                    true
1373                },
1374                "definite-first" if !got_order => {
1375                    got_order = true;
1376                    true
1377                },
1378                "ordered" if !got_order => {
1379                    value.order = MasonryItemOrder::Ordered;
1380                    got_order = true;
1381                    true
1382                },
1383                _ => false
1384            };
1385            if !success {
1386                return Err(location
1387                    .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
1388            }
1389        }
1390
1391        if got_placement || got_order {
1392            Ok(value)
1393        } else {
1394            Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1395        }
1396    }
1397}
1398
1399#[derive(
1400    Clone,
1401    Debug,
1402    MallocSizeOf,
1403    PartialEq,
1404    SpecifiedValueInfo,
1405    ToComputedValue,
1406    ToCss,
1407    ToResolvedValue,
1408    ToShmem,
1409)]
1410#[repr(C)]
1411/// https://drafts.csswg.org/css-grid/#named-grid-area
1412pub struct TemplateAreas {
1413    /// `named area` containing for each template area
1414    #[css(skip)]
1415    pub areas: crate::OwnedSlice<NamedArea>,
1416    /// The simplified CSS strings for serialization purpose.
1417    /// https://drafts.csswg.org/css-grid/#serialize-template
1418    // Note: We also use the length of `strings` when computing the explicit grid end line number
1419    // (i.e. row number).
1420    #[css(iterable)]
1421    pub strings: crate::OwnedSlice<crate::OwnedStr>,
1422    /// The number of columns of the grid.
1423    #[css(skip)]
1424    pub width: u32,
1425}
1426
1427/// Parser for grid template areas.
1428#[derive(Default)]
1429pub struct TemplateAreasParser {
1430    areas: Vec<NamedArea>,
1431    area_indices: PrecomputedHashMap<Atom, usize>,
1432    strings: Vec<crate::OwnedStr>,
1433    width: u32,
1434    row: u32,
1435}
1436
1437impl TemplateAreasParser {
1438    /// Parse a single string.
1439    pub fn try_parse_string<'i>(
1440        &mut self,
1441        input: &mut Parser<'i, '_>,
1442    ) -> Result<(), ParseError<'i>> {
1443        input.try_parse(|input| {
1444            self.parse_string(input.expect_string()?)
1445                .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1446        })
1447    }
1448
1449    /// Parse a single string.
1450    fn parse_string(&mut self, string: &str) -> Result<(), ()> {
1451        self.row += 1;
1452        let mut simplified_string = String::new();
1453        let mut current_area_index: Option<usize> = None;
1454        let mut column = 0u32;
1455        for token in TemplateAreasTokenizer(string) {
1456            column += 1;
1457            if column > 1 {
1458                simplified_string.push(' ');
1459            }
1460            let name = if let Some(token) = token? {
1461                simplified_string.push_str(token);
1462                Atom::from(token)
1463            } else {
1464                if let Some(index) = current_area_index.take() {
1465                    if self.areas[index].columns.end != column {
1466                        return Err(());
1467                    }
1468                }
1469                simplified_string.push('.');
1470                continue;
1471            };
1472            if let Some(index) = current_area_index {
1473                if self.areas[index].name == name {
1474                    if self.areas[index].rows.start == self.row {
1475                        self.areas[index].columns.end += 1;
1476                    }
1477                    continue;
1478                }
1479                if self.areas[index].columns.end != column {
1480                    return Err(());
1481                }
1482            }
1483            match self.area_indices.entry(name) {
1484                Entry::Occupied(ref e) => {
1485                    let index = *e.get();
1486                    if self.areas[index].columns.start != column
1487                        || self.areas[index].rows.end != self.row
1488                    {
1489                        return Err(());
1490                    }
1491                    self.areas[index].rows.end += 1;
1492                    current_area_index = Some(index);
1493                },
1494                Entry::Vacant(v) => {
1495                    let index = self.areas.len();
1496                    let name = v.key().clone();
1497                    v.insert(index);
1498                    self.areas.push(NamedArea {
1499                        name,
1500                        columns: UnsignedRange {
1501                            start: column,
1502                            end: column + 1,
1503                        },
1504                        rows: UnsignedRange {
1505                            start: self.row,
1506                            end: self.row + 1,
1507                        },
1508                    });
1509                    current_area_index = Some(index);
1510                },
1511            }
1512        }
1513        if column == 0 {
1514            // Each string must produce a valid token.
1515            // https://github.com/w3c/csswg-drafts/issues/5110
1516            return Err(());
1517        }
1518        if let Some(index) = current_area_index {
1519            if self.areas[index].columns.end != column + 1 {
1520                debug_assert_ne!(self.areas[index].rows.start, self.row);
1521                return Err(());
1522            }
1523        }
1524        if self.row == 1 {
1525            self.width = column;
1526        } else if self.width != column {
1527            return Err(());
1528        }
1529
1530        self.strings.push(simplified_string.into());
1531        Ok(())
1532    }
1533
1534    /// Return the parsed template areas.
1535    pub fn finish(self) -> Result<TemplateAreas, ()> {
1536        if self.strings.is_empty() {
1537            return Err(());
1538        }
1539        Ok(TemplateAreas {
1540            areas: self.areas.into(),
1541            strings: self.strings.into(),
1542            width: self.width,
1543        })
1544    }
1545}
1546
1547impl TemplateAreas {
1548    fn parse_internal(input: &mut Parser) -> Result<Self, ()> {
1549        let mut parser = TemplateAreasParser::default();
1550        while parser.try_parse_string(input).is_ok() {}
1551        parser.finish()
1552    }
1553}
1554
1555impl Parse for TemplateAreas {
1556    fn parse<'i, 't>(
1557        _: &ParserContext,
1558        input: &mut Parser<'i, 't>,
1559    ) -> Result<Self, ParseError<'i>> {
1560        Self::parse_internal(input)
1561            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1562    }
1563}
1564
1565/// Arc type for `Arc<TemplateAreas>`
1566#[derive(
1567    Clone,
1568    Debug,
1569    MallocSizeOf,
1570    PartialEq,
1571    SpecifiedValueInfo,
1572    ToComputedValue,
1573    ToCss,
1574    ToResolvedValue,
1575    ToShmem,
1576)]
1577#[repr(transparent)]
1578pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
1579
1580impl Parse for TemplateAreasArc {
1581    fn parse<'i, 't>(
1582        context: &ParserContext,
1583        input: &mut Parser<'i, 't>,
1584    ) -> Result<Self, ParseError<'i>> {
1585        let parsed = TemplateAreas::parse(context, input)?;
1586        Ok(TemplateAreasArc(Arc::new(parsed)))
1587    }
1588}
1589
1590/// A range of rows or columns. Using this instead of std::ops::Range for FFI
1591/// purposes.
1592#[repr(C)]
1593#[derive(
1594    Clone,
1595    Debug,
1596    MallocSizeOf,
1597    PartialEq,
1598    SpecifiedValueInfo,
1599    ToComputedValue,
1600    ToResolvedValue,
1601    ToShmem,
1602)]
1603pub struct UnsignedRange {
1604    /// The start of the range.
1605    pub start: u32,
1606    /// The end of the range.
1607    pub end: u32,
1608}
1609
1610#[derive(
1611    Clone,
1612    Debug,
1613    MallocSizeOf,
1614    PartialEq,
1615    SpecifiedValueInfo,
1616    ToComputedValue,
1617    ToResolvedValue,
1618    ToShmem,
1619)]
1620#[repr(C)]
1621/// Not associated with any particular grid item, but can be referenced from the
1622/// grid-placement properties.
1623pub struct NamedArea {
1624    /// Name of the `named area`
1625    pub name: Atom,
1626    /// Rows of the `named area`
1627    pub rows: UnsignedRange,
1628    /// Columns of the `named area`
1629    pub columns: UnsignedRange,
1630}
1631
1632/// Tokenize the string into a list of the tokens,
1633/// using longest-match semantics
1634struct TemplateAreasTokenizer<'a>(&'a str);
1635
1636impl<'a> Iterator for TemplateAreasTokenizer<'a> {
1637    type Item = Result<Option<&'a str>, ()>;
1638
1639    fn next(&mut self) -> Option<Self::Item> {
1640        let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
1641        if rest.is_empty() {
1642            return None;
1643        }
1644        if rest.starts_with('.') {
1645            self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
1646            return Some(Ok(None));
1647        }
1648        if !rest.starts_with(is_name_code_point) {
1649            return Some(Err(()));
1650        }
1651        let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
1652        let token = &rest[..token_len];
1653        self.0 = &rest[token_len..];
1654        Some(Ok(Some(token)))
1655    }
1656}
1657
1658fn is_name_code_point(c: char) -> bool {
1659    c >= 'A' && c <= 'Z'
1660        || c >= 'a' && c <= 'z'
1661        || c >= '\u{80}'
1662        || c == '_'
1663        || c >= '0' && c <= '9'
1664        || c == '-'
1665}
1666
1667/// This property specifies named grid areas.
1668///
1669/// The syntax of this property also provides a visualization of the structure
1670/// of the grid, making the overall layout of the grid container easier to
1671/// understand.
1672#[repr(C, u8)]
1673#[derive(
1674    Clone,
1675    Debug,
1676    MallocSizeOf,
1677    Parse,
1678    PartialEq,
1679    SpecifiedValueInfo,
1680    ToComputedValue,
1681    ToCss,
1682    ToResolvedValue,
1683    ToShmem,
1684)]
1685pub enum GridTemplateAreas {
1686    /// The `none` value.
1687    None,
1688    /// The actual value.
1689    Areas(TemplateAreasArc),
1690}
1691
1692impl GridTemplateAreas {
1693    #[inline]
1694    /// Get default value as `none`
1695    pub fn none() -> GridTemplateAreas {
1696        GridTemplateAreas::None
1697    }
1698}
1699
1700/// A specified value for the `z-index` property.
1701pub type ZIndex = GenericZIndex<Integer>;
1702
1703/// A specified value for the `aspect-ratio` property.
1704pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
1705
1706impl Parse for AspectRatio {
1707    fn parse<'i, 't>(
1708        context: &ParserContext,
1709        input: &mut Parser<'i, 't>,
1710    ) -> Result<Self, ParseError<'i>> {
1711        use crate::values::generics::position::PreferredRatio;
1712        use crate::values::specified::Ratio;
1713
1714        let location = input.current_source_location();
1715        let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
1716        let ratio = input.try_parse(|i| Ratio::parse(context, i));
1717        if auto.is_err() {
1718            auto = input.try_parse(|i| i.expect_ident_matching("auto"));
1719        }
1720
1721        if auto.is_err() && ratio.is_err() {
1722            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1723        }
1724
1725        Ok(AspectRatio {
1726            auto: auto.is_ok(),
1727            ratio: match ratio {
1728                Ok(ratio) => PreferredRatio::Ratio(ratio),
1729                Err(..) => PreferredRatio::None,
1730            },
1731        })
1732    }
1733}
1734
1735impl AspectRatio {
1736    /// Returns Self by a valid ratio.
1737    pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
1738        use crate::values::generics::position::PreferredRatio;
1739        use crate::values::generics::ratio::Ratio;
1740        AspectRatio {
1741            auto: true,
1742            ratio: PreferredRatio::Ratio(Ratio(
1743                NonNegativeNumber::new(w),
1744                NonNegativeNumber::new(h),
1745            )),
1746        }
1747    }
1748}
1749
1750/// A specified value for inset types.
1751pub type Inset = GenericInset<specified::Percentage, LengthPercentage>;
1752
1753impl Inset {
1754    /// Parses an inset type, allowing the unitless length quirk.
1755    /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk>
1756    #[inline]
1757    pub fn parse_quirky<'i, 't>(
1758        context: &ParserContext,
1759        input: &mut Parser<'i, 't>,
1760        allow_quirks: AllowQuirks,
1761    ) -> Result<Self, ParseError<'i>> {
1762        if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
1763        {
1764            return Ok(Self::LengthPercentage(l));
1765        }
1766        match input.try_parse(|i| i.expect_ident_matching("auto")) {
1767            Ok(_) => return Ok(Self::Auto),
1768            Err(e) if !static_prefs::pref!("layout.css.anchor-positioning.enabled") => {
1769                return Err(e.into())
1770            },
1771            Err(_) => (),
1772        };
1773        Self::parse_anchor_functions_quirky(context, input, allow_quirks)
1774    }
1775
1776    fn parse_as_anchor_function_fallback<'i, 't>(
1777        context: &ParserContext,
1778        input: &mut Parser<'i, 't>,
1779    ) -> Result<Self, ParseError<'i>> {
1780        if let Ok(l) =
1781            input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No))
1782        {
1783            return Ok(Self::LengthPercentage(l));
1784        }
1785        Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No)
1786    }
1787
1788    fn parse_anchor_functions_quirky<'i, 't>(
1789        context: &ParserContext,
1790        input: &mut Parser<'i, 't>,
1791        allow_quirks: AllowQuirks,
1792    ) -> Result<Self, ParseError<'i>> {
1793        debug_assert!(
1794            static_prefs::pref!("layout.css.anchor-positioning.enabled"),
1795            "How are we parsing with pref off?"
1796        );
1797        if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) {
1798            return Ok(Self::AnchorFunction(Box::new(inner)));
1799        }
1800        if let Ok(inner) =
1801            input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i))
1802        {
1803            return Ok(Self::AnchorSizeFunction(Box::new(inner)));
1804        }
1805        Ok(Self::AnchorContainingCalcFunction(input.try_parse(
1806            |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks),
1807        )?))
1808    }
1809}
1810
1811impl Parse for Inset {
1812    fn parse<'i, 't>(
1813        context: &ParserContext,
1814        input: &mut Parser<'i, 't>,
1815    ) -> Result<Self, ParseError<'i>> {
1816        Self::parse_quirky(context, input, AllowQuirks::No)
1817    }
1818}
1819
1820/// A specified value for `anchor()` function.
1821pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>;
1822
1823impl Parse for AnchorFunction {
1824    fn parse<'i, 't>(
1825        context: &ParserContext,
1826        input: &mut Parser<'i, 't>,
1827    ) -> Result<Self, ParseError<'i>> {
1828        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
1829            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1830        }
1831        input.expect_function_matching("anchor")?;
1832        input.parse_nested_block(|i| {
1833            let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
1834            let side = GenericAnchorSide::parse(context, i)?;
1835            let target_element = if target_element.is_none() {
1836                i.try_parse(|i| DashedIdent::parse(context, i)).ok()
1837            } else {
1838                target_element
1839            };
1840            let fallback = i
1841                .try_parse(|i| {
1842                    i.expect_comma()?;
1843                    Inset::parse_as_anchor_function_fallback(context, i)
1844                })
1845                .ok();
1846            Ok(Self {
1847                target_element: target_element.unwrap_or_else(DashedIdent::empty),
1848                side,
1849                fallback: fallback.into(),
1850            })
1851        })
1852    }
1853}