/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Specified types for box properties. use crate::parser::{Parse, ParserContext}; use crate::properties::{LonghandId, PropertyDeclarationId, PropertyId}; use crate::values::generics::box_::{ GenericContainIntrinsicSize, GenericLineClamp, GenericPerspective, GenericVerticalAlign, VerticalAlignKeyword, }; use crate::values::specified::length::{LengthPercentage, NonNegativeLength}; use crate::values::specified::{AllowQuirks, Integer}; use crate::values::CustomIdent; use cssparser::Parser; use num_traits::FromPrimitive; use std::fmt::{self, Write}; use style_traits::{CssWriter, KeywordsCollectFn, ParseError}; use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; #[cfg(not(feature = "servo-layout-2020"))] fn flexbox_enabled() -> bool { true } #[cfg(feature = "servo-layout-2020")] fn flexbox_enabled() -> bool { servo_config::prefs::pref_map() .get("layout.flexbox.enabled") .as_bool() .unwrap_or(false) } /// Defines an element’s display type, which consists of /// the two basic qualities of how an element generates boxes /// #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)] #[repr(u8)] pub enum DisplayOutside { None = 0, Inline, Block, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableCaption, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] InternalTable, #[cfg(feature = "gecko")] InternalRuby, } #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)] #[repr(u8)] pub enum DisplayInside { None = 0, #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] Contents, Flow, FlowRoot, Flex, #[cfg(feature = "gecko")] Grid, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] Table, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableRowGroup, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableColumn, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableColumnGroup, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableHeaderGroup, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableFooterGroup, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableRow, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableCell, #[cfg(feature = "gecko")] Ruby, #[cfg(feature = "gecko")] RubyBase, #[cfg(feature = "gecko")] RubyBaseContainer, #[cfg(feature = "gecko")] RubyText, #[cfg(feature = "gecko")] RubyTextContainer, #[cfg(feature = "gecko")] WebkitBox, } #[allow(missing_docs)] #[derive( Clone, Copy, Debug, Eq, FromPrimitive, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(transparent)] pub struct Display(u16); /// Gecko-only impl block for Display (shared stuff later in this file): #[allow(missing_docs)] #[allow(non_upper_case_globals)] impl Display { // Our u16 bits are used as follows: LOOOOOOOIIIIIIII const LIST_ITEM_BIT: u16 = 0x8000; //^ const DISPLAY_OUTSIDE_BITS: u16 = 7; // ^^^^^^^ const DISPLAY_INSIDE_BITS: u16 = 8; // ^^^^^^^^ /// https://drafts.csswg.org/css-display/#the-display-properties pub const None: Self = Self::new(DisplayOutside::None, DisplayInside::None); #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] pub const Contents: Self = Self::new(DisplayOutside::None, DisplayInside::Contents); pub const Inline: Self = Self::new(DisplayOutside::Inline, DisplayInside::Flow); pub const InlineBlock: Self = Self::new(DisplayOutside::Inline, DisplayInside::FlowRoot); pub const Block: Self = Self::new(DisplayOutside::Block, DisplayInside::Flow); #[cfg(feature = "gecko")] pub const FlowRoot: Self = Self::new(DisplayOutside::Block, DisplayInside::FlowRoot); pub const Flex: Self = Self::new(DisplayOutside::Block, DisplayInside::Flex); pub const InlineFlex: Self = Self::new(DisplayOutside::Inline, DisplayInside::Flex); #[cfg(feature = "gecko")] pub const Grid: Self = Self::new(DisplayOutside::Block, DisplayInside::Grid); #[cfg(feature = "gecko")] pub const InlineGrid: Self = Self::new(DisplayOutside::Inline, DisplayInside::Grid); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const Table: Self = Self::new(DisplayOutside::Block, DisplayInside::Table); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const InlineTable: Self = Self::new(DisplayOutside::Inline, DisplayInside::Table); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableCaption: Self = Self::new(DisplayOutside::TableCaption, DisplayInside::Flow); #[cfg(feature = "gecko")] pub const Ruby: Self = Self::new(DisplayOutside::Inline, DisplayInside::Ruby); #[cfg(feature = "gecko")] pub const WebkitBox: Self = Self::new(DisplayOutside::Block, DisplayInside::WebkitBox); #[cfg(feature = "gecko")] pub const WebkitInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::WebkitBox); // Internal table boxes. #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableRowGroup: Self = Self::new(DisplayOutside::InternalTable, DisplayInside::TableRowGroup); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableHeaderGroup: Self = Self::new( DisplayOutside::InternalTable, DisplayInside::TableHeaderGroup, ); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableFooterGroup: Self = Self::new( DisplayOutside::InternalTable, DisplayInside::TableFooterGroup, ); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableColumn: Self = Self::new(DisplayOutside::InternalTable, DisplayInside::TableColumn); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableColumnGroup: Self = Self::new( DisplayOutside::InternalTable, DisplayInside::TableColumnGroup, ); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableRow: Self = Self::new(DisplayOutside::InternalTable, DisplayInside::TableRow); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableCell: Self = Self::new(DisplayOutside::InternalTable, DisplayInside::TableCell); /// Internal ruby boxes. #[cfg(feature = "gecko")] pub const RubyBase: Self = Self::new(DisplayOutside::InternalRuby, DisplayInside::RubyBase); #[cfg(feature = "gecko")] pub const RubyBaseContainer: Self = Self::new( DisplayOutside::InternalRuby, DisplayInside::RubyBaseContainer, ); #[cfg(feature = "gecko")] pub const RubyText: Self = Self::new(DisplayOutside::InternalRuby, DisplayInside::RubyText); #[cfg(feature = "gecko")] pub const RubyTextContainer: Self = Self::new( DisplayOutside::InternalRuby, DisplayInside::RubyTextContainer, ); /// Make a raw display value from and values. #[inline] const fn new(outside: DisplayOutside, inside: DisplayInside) -> Self { let o: u16 = ((outside as u8) as u16) << Self::DISPLAY_INSIDE_BITS; let i: u16 = (inside as u8) as u16; Self(o | i) } /// Make a display enum value from and values. #[inline] fn from3(outside: DisplayOutside, inside: DisplayInside, list_item: bool) -> Self { let v = Self::new(outside, inside); if !list_item { return v; } Self(v.0 | Self::LIST_ITEM_BIT) } /// Accessor for the value. #[inline] pub fn inside(&self) -> DisplayInside { DisplayInside::from_u16(self.0 & ((1 << Self::DISPLAY_INSIDE_BITS) - 1)).unwrap() } /// Accessor for the value. #[inline] pub fn outside(&self) -> DisplayOutside { DisplayOutside::from_u16( (self.0 >> Self::DISPLAY_INSIDE_BITS) & ((1 << Self::DISPLAY_OUTSIDE_BITS) - 1), ) .unwrap() } /// Returns the raw underlying u16 value. #[inline] pub const fn to_u16(&self) -> u16 { self.0 } /// Whether this is `display: inline` (or `inline list-item`). #[inline] pub fn is_inline_flow(&self) -> bool { self.outside() == DisplayOutside::Inline && self.inside() == DisplayInside::Flow } /// Returns whether this `display` value is some kind of list-item. #[inline] pub const fn is_list_item(&self) -> bool { (self.0 & Self::LIST_ITEM_BIT) != 0 } /// Returns whether this `display` value is a ruby level container. pub fn is_ruby_level_container(&self) -> bool { match *self { #[cfg(feature = "gecko")] Display::RubyBaseContainer | Display::RubyTextContainer => true, _ => false, } } /// Returns whether this `display` value is one of the types for ruby. pub fn is_ruby_type(&self) -> bool { match self.inside() { #[cfg(feature = "gecko")] DisplayInside::Ruby | DisplayInside::RubyBase | DisplayInside::RubyText | DisplayInside::RubyBaseContainer | DisplayInside::RubyTextContainer => true, _ => false, } } } /// Shared Display impl for both Gecko and Servo. #[allow(non_upper_case_globals)] impl Display { /// The initial display value. #[inline] pub fn inline() -> Self { Display::Inline } /// #[cfg(feature = "servo")] #[inline] pub fn is_atomic_inline_level(&self) -> bool { match *self { Display::InlineBlock | Display::InlineFlex => true, #[cfg(any(feature = "servo-layout-2013"))] Display::InlineTable => true, _ => false, } } /// Returns whether this `display` value is the display of a flex or /// grid container. /// /// This is used to implement various style fixups. pub fn is_item_container(&self) -> bool { match self.inside() { DisplayInside::Flex => true, #[cfg(feature = "gecko")] DisplayInside::Grid => true, _ => false, } } /// Returns whether an element with this display type is a line /// participant, which means it may lay its children on the same /// line as itself. pub fn is_line_participant(&self) -> bool { if self.is_inline_flow() { return true; } match *self { #[cfg(feature = "gecko")] Display::Contents | Display::Ruby | Display::RubyBaseContainer => true, _ => false, } } /// Convert this display into an equivalent block display. /// /// Also used for :root style adjustments. pub fn equivalent_block_display(&self, _is_root_element: bool) -> Self { #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] { // Special handling for `contents` and `list-item`s on the root element. if _is_root_element && (self.is_contents() || self.is_list_item()) { return Display::Block; } } match self.outside() { DisplayOutside::Inline => { let inside = match self.inside() { // `inline-block` blockifies to `block` rather than // `flow-root`, for legacy reasons. DisplayInside::FlowRoot => DisplayInside::Flow, inside => inside, }; Display::from3(DisplayOutside::Block, inside, self.is_list_item()) }, DisplayOutside::Block | DisplayOutside::None => *self, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] _ => Display::Block, } } /// Convert this display into an equivalent inline-outside display. /// https://drafts.csswg.org/css-display/#inlinify #[cfg(feature = "gecko")] pub fn inlinify(&self) -> Self { match self.outside() { DisplayOutside::Block => { let inside = match self.inside() { // `display: block` inlinifies to `display: inline-block`, // rather than `inline`, for legacy reasons. DisplayInside::Flow => DisplayInside::FlowRoot, inside => inside, }; Display::from3(DisplayOutside::Inline, inside, self.is_list_item()) }, _ => *self, } } /// Returns true if the value is `Contents` #[inline] pub fn is_contents(&self) -> bool { match *self { #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] Display::Contents => true, _ => false, } } /// Returns true if the value is `None` #[inline] pub fn is_none(&self) -> bool { *self == Display::None } } impl ToCss for Display { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { let outside = self.outside(); let inside = self.inside(); match *self { Display::Block | Display::Inline => outside.to_css(dest), Display::InlineBlock => dest.write_str("inline-block"), #[cfg(feature = "gecko")] Display::WebkitInlineBox => dest.write_str("-webkit-inline-box"), #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] Display::TableCaption => dest.write_str("table-caption"), _ => match (outside, inside) { #[cfg(feature = "gecko")] (DisplayOutside::Inline, DisplayInside::Grid) => dest.write_str("inline-grid"), (DisplayOutside::Inline, DisplayInside::Flex) => dest.write_str("inline-flex"), #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] (DisplayOutside::Inline, DisplayInside::Table) => dest.write_str("inline-table"), #[cfg(feature = "gecko")] (DisplayOutside::Block, DisplayInside::Ruby) => dest.write_str("block ruby"), (_, inside) => { if self.is_list_item() { if outside != DisplayOutside::Block { outside.to_css(dest)?; dest.write_char(' ')?; } if inside != DisplayInside::Flow { inside.to_css(dest)?; dest.write_char(' ')?; } dest.write_str("list-item") } else { inside.to_css(dest) } }, }, } } } /// = flow | flow-root | table | flex | grid | ruby /// https://drafts.csswg.org/css-display/#typedef-display-inside fn parse_display_inside<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result> { Ok(try_match_ident_ignore_ascii_case! { input, "flow" => DisplayInside::Flow, "flex" if flexbox_enabled() => DisplayInside::Flex, #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] "flow-root" => DisplayInside::FlowRoot, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table" => DisplayInside::Table, #[cfg(feature = "gecko")] "grid" => DisplayInside::Grid, #[cfg(feature = "gecko")] "ruby" => DisplayInside::Ruby, }) } /// = block | inline | run-in /// https://drafts.csswg.org/css-display/#typedef-display-outside fn parse_display_outside<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result> { Ok(try_match_ident_ignore_ascii_case! { input, "block" => DisplayOutside::Block, "inline" => DisplayOutside::Inline, // FIXME(bug 2056): not supported in layout yet: //"run-in" => DisplayOutside::RunIn, }) } /// (flow | flow-root)? fn parse_display_inside_for_list_item<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result> { Ok(try_match_ident_ignore_ascii_case! { input, "flow" => DisplayInside::Flow, #[cfg(feature = "gecko")] "flow-root" => DisplayInside::FlowRoot, }) } /// Test a Result for same values as above. fn is_valid_inside_for_list_item<'i>(inside: &Result>) -> bool { match inside { Ok(DisplayInside::Flow) => true, #[cfg(feature = "gecko")] Ok(DisplayInside::FlowRoot) => true, _ => false, } } /// Parse `list-item`. fn parse_list_item<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> { Ok(input.expect_ident_matching("list-item")?) } impl Parse for Display { #[allow(unused)] // `context` isn't used for servo-2020 for now fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { // Parse all combinations of ? and `list-item`? first. let mut got_list_item = input.try_parse(parse_list_item).is_ok(); let mut inside = if got_list_item { input.try_parse(parse_display_inside_for_list_item) } else { input.try_parse(parse_display_inside) }; // = ? && [ flow | flow-root ]? && list-item // https://drafts.csswg.org/css-display/#typedef-display-listitem if !got_list_item && is_valid_inside_for_list_item(&inside) { got_list_item = input.try_parse(parse_list_item).is_ok(); } let outside = input.try_parse(parse_display_outside); if outside.is_ok() { if !got_list_item && (inside.is_err() || is_valid_inside_for_list_item(&inside)) { got_list_item = input.try_parse(parse_list_item).is_ok(); } if inside.is_err() { inside = if got_list_item { input.try_parse(parse_display_inside_for_list_item) } else { input.try_parse(parse_display_inside) }; if !got_list_item && is_valid_inside_for_list_item(&inside) { got_list_item = input.try_parse(parse_list_item).is_ok(); } } } if got_list_item || inside.is_ok() || outside.is_ok() { let inside = inside.unwrap_or(DisplayInside::Flow); let outside = outside.unwrap_or(match inside { // "If is omitted, the element’s outside display type // defaults to block — except for ruby, which defaults to inline." // https://drafts.csswg.org/css-display/#inside-model #[cfg(feature = "gecko")] DisplayInside::Ruby => DisplayOutside::Inline, _ => DisplayOutside::Block, }); return Ok(Display::from3(outside, inside, got_list_item)); } // Now parse the single-keyword `display` values. Ok(try_match_ident_ignore_ascii_case! { input, "none" => Display::None, #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] "contents" => Display::Contents, "inline-block" => Display::InlineBlock, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "inline-table" => Display::InlineTable, "-webkit-flex" if flexbox_enabled() => Display::Flex, "inline-flex" | "-webkit-inline-flex" if flexbox_enabled() => Display::InlineFlex, #[cfg(feature = "gecko")] "inline-grid" => Display::InlineGrid, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-caption" => Display::TableCaption, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-row-group" => Display::TableRowGroup, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-header-group" => Display::TableHeaderGroup, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-footer-group" => Display::TableFooterGroup, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-column" => Display::TableColumn, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-column-group" => Display::TableColumnGroup, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-row" => Display::TableRow, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-cell" => Display::TableCell, #[cfg(feature = "gecko")] "ruby-base" => Display::RubyBase, #[cfg(feature = "gecko")] "ruby-base-container" => Display::RubyBaseContainer, #[cfg(feature = "gecko")] "ruby-text" => Display::RubyText, #[cfg(feature = "gecko")] "ruby-text-container" => Display::RubyTextContainer, #[cfg(feature = "gecko")] "-webkit-box" => Display::WebkitBox, #[cfg(feature = "gecko")] "-webkit-inline-box" => Display::WebkitInlineBox, }) } } impl SpecifiedValueInfo for Display { fn collect_completion_keywords(f: KeywordsCollectFn) { f(&[ "block", "contents", "flex", "flow-root", "flow-root list-item", "grid", "inline", "inline-block", "inline-flex", "inline-grid", "inline-table", "inline list-item", "inline flow-root list-item", "list-item", "none", "block ruby", "ruby", "ruby-base", "ruby-base-container", "ruby-text", "ruby-text-container", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", "-webkit-box", "-webkit-inline-box", ]); } } /// A specified value for the `contain-intrinsic-size` property. pub type ContainIntrinsicSize = GenericContainIntrinsicSize; /// A specified value for the `line-clamp` property. pub type LineClamp = GenericLineClamp; /// A specified value for the `vertical-align` property. pub type VerticalAlign = GenericVerticalAlign; impl Parse for VerticalAlign { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes)) { return Ok(GenericVerticalAlign::Length(lp)); } Ok(GenericVerticalAlign::Keyword(VerticalAlignKeyword::parse( input, )?)) } } /// A specified value for the `baseline-source` property. /// https://drafts.csswg.org/css-inline-3/#baseline-source #[derive( Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToComputedValue, ToResolvedValue, )] #[repr(u8)] pub enum BaselineSource { /// `Last` for `inline-block`, `First` otherwise. Auto, /// Use first baseline for alignment. First, /// Use last baseline for alignment. Last, } /// https://drafts.csswg.org/css-scroll-snap-1/#snap-axis #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum ScrollSnapAxis { X, Y, Block, Inline, Both, } /// https://drafts.csswg.org/css-scroll-snap-1/#snap-strictness #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum ScrollSnapStrictness { #[css(skip)] None, // Used to represent scroll-snap-type: none. It's not parsed. Mandatory, Proximity, } /// https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-type #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct ScrollSnapType { axis: ScrollSnapAxis, strictness: ScrollSnapStrictness, } impl ScrollSnapType { /// Returns `none`. #[inline] pub fn none() -> Self { Self { axis: ScrollSnapAxis::Both, strictness: ScrollSnapStrictness::None, } } } impl Parse for ScrollSnapType { /// none | [ x | y | block | inline | both ] [ mandatory | proximity ]? fn parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if input .try_parse(|input| input.expect_ident_matching("none")) .is_ok() { return Ok(ScrollSnapType::none()); } let axis = ScrollSnapAxis::parse(input)?; let strictness = input .try_parse(ScrollSnapStrictness::parse) .unwrap_or(ScrollSnapStrictness::Proximity); Ok(Self { axis, strictness }) } } impl ToCss for ScrollSnapType { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { if self.strictness == ScrollSnapStrictness::None { return dest.write_str("none"); } self.axis.to_css(dest)?; if self.strictness != ScrollSnapStrictness::Proximity { dest.write_char(' ')?; self.strictness.to_css(dest)?; } Ok(()) } } /// Specified value of scroll-snap-align keyword value. #[allow(missing_docs)] #[derive( Clone, Copy, Debug, Eq, FromPrimitive, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum ScrollSnapAlignKeyword { None, Start, End, Center, } /// https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-align #[allow(missing_docs)] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct ScrollSnapAlign { block: ScrollSnapAlignKeyword, inline: ScrollSnapAlignKeyword, } impl ScrollSnapAlign { /// Returns `none`. #[inline] pub fn none() -> Self { ScrollSnapAlign { block: ScrollSnapAlignKeyword::None, inline: ScrollSnapAlignKeyword::None, } } } impl Parse for ScrollSnapAlign { /// [ none | start | end | center ]{1,2} fn parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let block = ScrollSnapAlignKeyword::parse(input)?; let inline = input .try_parse(ScrollSnapAlignKeyword::parse) .unwrap_or(block); Ok(ScrollSnapAlign { block, inline }) } } impl ToCss for ScrollSnapAlign { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { self.block.to_css(dest)?; if self.block != self.inline { dest.write_char(' ')?; self.inline.to_css(dest)?; } Ok(()) } } #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum ScrollSnapStop { Normal, Always, } #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum OverscrollBehavior { Auto, Contain, None, } #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum OverflowAnchor { Auto, None, } #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum OverflowClipBox { PaddingBox, ContentBox, } #[derive( Clone, Debug, Default, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[css(comma)] #[repr(C)] /// Provides a rendering hint to the user agent, stating what kinds of changes /// the author expects to perform on the element. /// /// `auto` is represented by an empty `features` list. /// /// pub struct WillChange { /// The features that are supposed to change. /// /// TODO(emilio): Consider using ArcSlice since we just clone them from the /// specified value? That'd save an allocation, which could be worth it. #[css(iterable, if_empty = "auto")] features: crate::OwnedSlice, /// A bitfield with the kind of change that the value will create, based /// on the above field. #[css(skip)] bits: WillChangeBits, } impl WillChange { #[inline] /// Get default value of `will-change` as `auto` pub fn auto() -> Self { Self::default() } } bitflags! { /// The change bits that we care about. #[derive(Clone, Copy, Default, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] pub struct WillChangeBits: u16 { /// Whether a property which can create a stacking context **on any /// box** will change. const STACKING_CONTEXT_UNCONDITIONAL = 1 << 0; /// Whether `transform` or related properties will change. const TRANSFORM = 1 << 1; /// Whether `scroll-position` will change. const SCROLL = 1 << 2; /// Whether `contain` will change. const CONTAIN = 1 << 3; /// Whether `opacity` will change. const OPACITY = 1 << 4; /// Whether `perspective` will change. const PERSPECTIVE = 1 << 5; /// Whether `z-index` will change. const Z_INDEX = 1 << 6; /// Whether any property which creates a containing block for non-svg /// text frames will change. const FIXPOS_CB_NON_SVG = 1 << 7; /// Whether the position property will change. const POSITION = 1 << 8; } } fn change_bits_for_longhand(longhand: LonghandId) -> WillChangeBits { match longhand { LonghandId::Opacity => WillChangeBits::OPACITY, LonghandId::Contain => WillChangeBits::CONTAIN, LonghandId::Perspective => WillChangeBits::PERSPECTIVE, LonghandId::Position => { WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::POSITION }, LonghandId::ZIndex => WillChangeBits::Z_INDEX, LonghandId::Transform | LonghandId::TransformStyle | LonghandId::Translate | LonghandId::Rotate | LonghandId::Scale | LonghandId::OffsetPath => WillChangeBits::TRANSFORM, LonghandId::BackdropFilter | LonghandId::Filter => { WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::FIXPOS_CB_NON_SVG }, LonghandId::MixBlendMode | LonghandId::Isolation | LonghandId::MaskImage | LonghandId::ClipPath => WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL, _ => WillChangeBits::empty(), } } fn change_bits_for_maybe_property(ident: &str, context: &ParserContext) -> WillChangeBits { let id = match PropertyId::parse_ignoring_rule_type(ident, context) { Ok(id) => id, Err(..) => return WillChangeBits::empty(), }; match id.as_shorthand() { Ok(shorthand) => shorthand .longhands() .fold(WillChangeBits::empty(), |flags, p| { flags | change_bits_for_longhand(p) }), Err(PropertyDeclarationId::Longhand(longhand)) => change_bits_for_longhand(longhand), Err(PropertyDeclarationId::Custom(..)) => WillChangeBits::empty(), } } impl Parse for WillChange { /// auto | # fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if input .try_parse(|input| input.expect_ident_matching("auto")) .is_ok() { return Ok(Self::default()); } let mut bits = WillChangeBits::empty(); let custom_idents = input.parse_comma_separated(|i| { let location = i.current_source_location(); let parser_ident = i.expect_ident()?; let ident = CustomIdent::from_ident( location, parser_ident, &["will-change", "none", "all", "auto"], )?; if context.in_ua_sheet() && ident.0 == atom!("-moz-fixed-pos-containing-block") { bits |= WillChangeBits::FIXPOS_CB_NON_SVG; } else if ident.0 == atom!("scroll-position") { bits |= WillChangeBits::SCROLL; } else { bits |= change_bits_for_maybe_property(&parser_ident, context); } Ok(ident) })?; Ok(Self { features: custom_idents.into(), bits, }) } } bitflags! { /// Values for the `touch-action` property. #[derive(Clone, Copy, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] #[css(bitflags(single = "none,auto,manipulation", mixed = "pan-x,pan-y,pinch-zoom"))] #[repr(C)] pub struct TouchAction: u8 { /// `none` variant const NONE = 1 << 0; /// `auto` variant const AUTO = 1 << 1; /// `pan-x` variant const PAN_X = 1 << 2; /// `pan-y` variant const PAN_Y = 1 << 3; /// `manipulation` variant const MANIPULATION = 1 << 4; /// `pinch-zoom` variant const PINCH_ZOOM = 1 << 5; } } impl TouchAction { #[inline] /// Get default `touch-action` as `auto` pub fn auto() -> TouchAction { TouchAction::AUTO } } bitflags! { #[derive(Clone, Copy, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] #[css(bitflags(single = "none,strict,content", mixed="size,layout,style,paint,inline-size", overlapping_bits))] #[repr(C)] /// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property pub struct Contain: u8 { /// `none` variant, just for convenience. const NONE = 0; /// `inline-size` variant, turns on single-axis inline size containment const INLINE_SIZE = 1 << 0; /// `block-size` variant, turns on single-axis block size containment, internal only const BLOCK_SIZE = 1 << 1; /// `layout` variant, turns on layout containment const LAYOUT = 1 << 2; /// `style` variant, turns on style containment const STYLE = 1 << 3; /// `paint` variant, turns on paint containment const PAINT = 1 << 4; /// 'size' variant, turns on size containment const SIZE = 1 << 5 | Contain::INLINE_SIZE.bits | Contain::BLOCK_SIZE.bits; /// `content` variant, turns on layout and paint containment const CONTENT = 1 << 6 | Contain::LAYOUT.bits | Contain::STYLE.bits | Contain::PAINT.bits; /// `strict` variant, turns on all types of containment const STRICT = 1 << 7 | Contain::LAYOUT.bits | Contain::STYLE.bits | Contain::PAINT.bits | Contain::SIZE.bits; } } impl Parse for ContainIntrinsicSize { /// none | | auto fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(l) = input.try_parse(|i| NonNegativeLength::parse(context, i)) { return Ok(Self::Length(l)); } if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { let l = NonNegativeLength::parse(context, input)?; return Ok(Self::AutoLength(l)); } input.expect_ident_matching("none")?; Ok(Self::None) } } impl Parse for LineClamp { /// none | fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(i) = input.try_parse(|i| crate::values::specified::PositiveInteger::parse(context, i)) { return Ok(Self(i.0)); } input.expect_ident_matching("none")?; Ok(Self::none()) } } /// https://drafts.csswg.org/css-contain-2/#content-visibility #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum ContentVisibility { /// `auto` variant, the element turns on layout containment, style containment, and paint /// containment. In addition, if the element is not relevant to the user (such as by being /// offscreen) it also skips its content Auto, /// `hidden` variant, the element skips its content Hidden, /// 'visible' variant, no effect Visible, } #[derive( Clone, Copy, Debug, PartialEq, Eq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, Parse, ToResolvedValue, ToShmem, )] #[repr(u8)] #[allow(missing_docs)] /// https://drafts.csswg.org/css-contain-3/#container-type pub enum ContainerType { /// The `normal` variant. Normal, /// The `inline-size` variant. InlineSize, /// The `size` variant. Size, } impl ContainerType { /// Is this container-type: normal? pub fn is_normal(self) -> bool { self == Self::Normal } /// Is this type containing size in any way? pub fn is_size_container_type(self) -> bool { !self.is_normal() } } /// https://drafts.csswg.org/css-contain-3/#container-name #[repr(transparent)] #[derive( Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] pub struct ContainerName(#[css(iterable, if_empty = "none")] pub crate::OwnedSlice); impl ContainerName { /// Return the `none` value. pub fn none() -> Self { Self(Default::default()) } /// Returns whether this is the `none` value. pub fn is_none(&self) -> bool { self.0.is_empty() } fn parse_internal<'i>( input: &mut Parser<'i, '_>, for_query: bool, ) -> Result> { let mut idents = vec![]; let location = input.current_source_location(); let first = input.expect_ident()?; if !for_query && first.eq_ignore_ascii_case("none") { return Ok(Self::none()); } const DISALLOWED_CONTAINER_NAMES: &'static [&'static str] = &["none", "not", "or", "and"]; idents.push(CustomIdent::from_ident( location, first, DISALLOWED_CONTAINER_NAMES, )?); if !for_query { while let Ok(name) = input.try_parse(|input| { let ident = input.expect_ident()?; CustomIdent::from_ident(location, &ident, DISALLOWED_CONTAINER_NAMES) }) { idents.push(name); } } Ok(ContainerName(idents.into())) } /// https://github.com/w3c/csswg-drafts/issues/7203 /// Only a single name allowed in @container rule. /// Disallow none for container-name in @container rule. pub fn parse_for_query<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { Self::parse_internal(input, /* for_query = */ true) } } impl Parse for ContainerName { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { Self::parse_internal(input, /* for_query = */ false) } } /// A specified value for the `perspective` property. pub type Perspective = GenericPerspective; #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, )] /// https://drafts.csswg.org/css-box/#propdef-float pub enum Float { Left, Right, None, // https://drafts.csswg.org/css-logical-props/#float-clear InlineStart, InlineEnd, } #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, )] /// https://drafts.csswg.org/css2/#propdef-clear pub enum Clear { None, Left, Right, Both, // https://drafts.csswg.org/css-logical-props/#float-clear InlineStart, InlineEnd, } /// https://drafts.csswg.org/css-ui/#propdef-resize #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, )] pub enum Resize { None, Both, Horizontal, Vertical, // https://drafts.csswg.org/css-logical-1/#resize Inline, Block, } /// The value for the `appearance` property. /// /// https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance #[allow(missing_docs)] #[derive( Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum Appearance { /// No appearance at all. None, /// Default appearance for the element. /// /// This value doesn't make sense for -moz-default-appearance, but we don't bother to guard /// against parsing it. Auto, /// A searchfield. Searchfield, /// A multi-line text field, e.g. HTML