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