tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

position.rs (67564B)


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