/* 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, NonNegativeNumberOrPercentage}; use crate::values::CustomIdent; use cssparser::Parser; use num_traits::FromPrimitive; use std::fmt::{self, Debug, Write}; use style_traits::{CssWriter, KeywordsCollectFn, ParseError}; use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; #[cfg(not(feature = "servo"))] fn flexbox_enabled() -> bool { true } #[cfg(not(feature = "servo"))] fn grid_enabled() -> bool { true } #[cfg(feature = "servo")] fn flexbox_enabled() -> bool { style_config::get_bool("layout.flexbox.enabled") } #[cfg(feature = "servo")] fn grid_enabled() -> bool { style_config::get_bool("layout.grid.enabled") } /// 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, TableCaption, 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, Contents, Flow, FlowRoot, Flex, Grid, Table, TableRowGroup, TableColumn, TableColumnGroup, TableHeaderGroup, TableFooterGroup, TableRow, 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, } impl DisplayInside { fn is_valid_for_list_item(self) -> bool { match self { DisplayInside::Flow => true, #[cfg(feature = "gecko")] DisplayInside::FlowRoot => true, _ => false, } } /// https://drafts.csswg.org/css-display/#inside-model: /// If is omitted, the element’s outside display type defaults to block /// — except for ruby, which defaults to inline. fn default_display_outside(self) -> DisplayOutside { match self { #[cfg(feature = "gecko")] DisplayInside::Ruby => DisplayOutside::Inline, _ => DisplayOutside::Block, } } } #[allow(missing_docs)] #[derive( Clone, Copy, Debug, Eq, FromPrimitive, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] 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 pub const LIST_ITEM_MASK: u16 = 0b1000000000000000; pub const OUTSIDE_MASK: u16 = 0b0111111100000000; pub const INSIDE_MASK: u16 = 0b0000000011111111; pub const OUTSIDE_SHIFT: u16 = 8; /// https://drafts.csswg.org/css-display/#the-display-properties /// ::new() inlined so cbindgen can use it pub const None: Self = Self(((DisplayOutside::None as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::None as u16); pub const Contents: Self = Self( ((DisplayOutside::None as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Contents as u16, ); pub const Inline: Self = Self(((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Flow as u16); pub const InlineBlock: Self = Self( ((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::FlowRoot as u16, ); pub const Block: Self = Self(((DisplayOutside::Block as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Flow as u16); #[cfg(feature = "gecko")] pub const FlowRoot: Self = Self( ((DisplayOutside::Block as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::FlowRoot as u16, ); pub const Flex: Self = Self(((DisplayOutside::Block as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Flex as u16); pub const InlineFlex: Self = Self(((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Flex as u16); pub const Grid: Self = Self(((DisplayOutside::Block as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Grid as u16); pub const InlineGrid: Self = Self(((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Grid as u16); pub const Table: Self = Self(((DisplayOutside::Block as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Table as u16); pub const InlineTable: Self = Self( ((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Table as u16, ); pub const TableCaption: Self = Self( ((DisplayOutside::TableCaption as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Flow as u16, ); #[cfg(feature = "gecko")] pub const Ruby: Self = Self(((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Ruby as u16); #[cfg(feature = "gecko")] pub const WebkitBox: Self = Self( ((DisplayOutside::Block as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::WebkitBox as u16, ); #[cfg(feature = "gecko")] pub const WebkitInlineBox: Self = Self( ((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::WebkitBox as u16, ); // Internal table boxes. pub const TableRowGroup: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableRowGroup as u16, ); pub const TableHeaderGroup: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableHeaderGroup as u16, ); pub const TableFooterGroup: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableFooterGroup as u16, ); pub const TableColumn: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableColumn as u16, ); pub const TableColumnGroup: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableColumnGroup as u16, ); pub const TableRow: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableRow as u16, ); pub const TableCell: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableCell as u16, ); /// Internal ruby boxes. #[cfg(feature = "gecko")] pub const RubyBase: Self = Self( ((DisplayOutside::InternalRuby as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::RubyBase as u16, ); #[cfg(feature = "gecko")] pub const RubyBaseContainer: Self = Self( ((DisplayOutside::InternalRuby as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::RubyBaseContainer as u16, ); #[cfg(feature = "gecko")] pub const RubyText: Self = Self( ((DisplayOutside::InternalRuby as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::RubyText as u16, ); #[cfg(feature = "gecko")] pub const RubyTextContainer: Self = Self( ((DisplayOutside::InternalRuby as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::RubyTextContainer as u16, ); /// Make a raw display value from and values. #[inline] const fn new(outside: DisplayOutside, inside: DisplayInside) -> Self { Self((outside as u16) << Self::OUTSIDE_SHIFT | inside as u16) } /// 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_MASK) } /// Accessor for the value. #[inline] pub fn inside(&self) -> DisplayInside { DisplayInside::from_u16(self.0 & Self::INSIDE_MASK).unwrap() } /// Accessor for the value. #[inline] pub fn outside(&self) -> DisplayOutside { DisplayOutside::from_u16((self.0 & Self::OUTSIDE_MASK) >> Self::OUTSIDE_SHIFT).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_MASK) != 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. 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, 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, 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 { { // 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, _ => 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 { Display::Contents => true, _ => false, } } /// Returns true if the value is `None` #[inline] pub fn is_none(&self) -> bool { *self == Display::None } } enum DisplayKeyword { Full(Display), Inside(DisplayInside), Outside(DisplayOutside), ListItem, } impl DisplayKeyword { fn parse<'i>(input: &mut Parser<'i, '_>) -> Result> { use self::DisplayKeyword::*; Ok(try_match_ident_ignore_ascii_case! { input, "none" => Full(Display::None), "contents" => Full(Display::Contents), "inline-block" => Full(Display::InlineBlock), "inline-table" => Full(Display::InlineTable), "-webkit-flex" if flexbox_enabled() => Full(Display::Flex), "inline-flex" | "-webkit-inline-flex" if flexbox_enabled() => Full(Display::InlineFlex), "inline-grid" if grid_enabled() => Full(Display::InlineGrid), "table-caption" => Full(Display::TableCaption), "table-row-group" => Full(Display::TableRowGroup), "table-header-group" => Full(Display::TableHeaderGroup), "table-footer-group" => Full(Display::TableFooterGroup), "table-column" => Full(Display::TableColumn), "table-column-group" => Full(Display::TableColumnGroup), "table-row" => Full(Display::TableRow), "table-cell" => Full(Display::TableCell), #[cfg(feature = "gecko")] "ruby-base" => Full(Display::RubyBase), #[cfg(feature = "gecko")] "ruby-base-container" => Full(Display::RubyBaseContainer), #[cfg(feature = "gecko")] "ruby-text" => Full(Display::RubyText), #[cfg(feature = "gecko")] "ruby-text-container" => Full(Display::RubyTextContainer), #[cfg(feature = "gecko")] "-webkit-box" => Full(Display::WebkitBox), #[cfg(feature = "gecko")] "-webkit-inline-box" => Full(Display::WebkitInlineBox), /// = block | inline | run-in /// https://drafts.csswg.org/css-display/#typedef-display-outside "block" => Outside(DisplayOutside::Block), "inline" => Outside(DisplayOutside::Inline), "list-item" => ListItem, /// = flow | flow-root | table | flex | grid | ruby /// https://drafts.csswg.org/css-display/#typedef-display-inside "flow" => Inside(DisplayInside::Flow), "flex" if flexbox_enabled() => Inside(DisplayInside::Flex), "flow-root" => Inside(DisplayInside::FlowRoot), "table" => Inside(DisplayInside::Table), "grid" if grid_enabled() => Inside(DisplayInside::Grid), #[cfg(feature = "gecko")] "ruby" => Inside(DisplayInside::Ruby), }) } } 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"), Display::TableCaption => dest.write_str("table-caption"), _ => match (outside, inside) { (DisplayOutside::Inline, DisplayInside::Grid) => dest.write_str("inline-grid"), (DisplayOutside::Inline, DisplayInside::Flex) => dest.write_str("inline-flex"), (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) } }, }, } } } impl Parse for Display { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let mut got_list_item = false; let mut inside = None; let mut outside = None; match DisplayKeyword::parse(input)? { DisplayKeyword::Full(d) => return Ok(d), DisplayKeyword::Outside(o) => { outside = Some(o); }, DisplayKeyword::Inside(i) => { inside = Some(i); }, DisplayKeyword::ListItem => { got_list_item = true; }, }; while let Ok(kw) = input.try_parse(DisplayKeyword::parse) { match kw { DisplayKeyword::ListItem if !got_list_item => { got_list_item = true; }, DisplayKeyword::Outside(o) if outside.is_none() => { outside = Some(o); }, DisplayKeyword::Inside(i) if inside.is_none() => { inside = Some(i); }, _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } } let inside = inside.unwrap_or(DisplayInside::Flow); let outside = outside.unwrap_or_else(|| inside.default_display_outside()); if got_list_item && !inside.is_valid_for_list_item() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } return Ok(Display::from3(outside, inside, got_list_item)); } } 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() } } /// The change bits that we care about. #[derive( Clone, Copy, Debug, Default, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct WillChangeBits(u16); bitflags! { impl 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; } } #[cfg(feature="servo")] fn change_bits_for_longhand(_longhand: LonghandId) -> WillChangeBits { WillChangeBits::empty() } #[cfg(feature = "gecko")] 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, }) } } /// Values for the `touch-action` property. #[derive( Clone, Copy, Debug, 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); bitflags! { impl 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 } } #[derive( Clone, Copy, Debug, 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); bitflags! { impl 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() { if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { return Ok(Self::AutoNone); } 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, ToAnimatedValue, 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| CustomIdent::parse(input, 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