diff options
Diffstat (limited to 'servo/components/style/values/specified/box.rs')
-rw-r--r-- | servo/components/style/values/specified/box.rs | 1905 |
1 files changed, 1905 insertions, 0 deletions
diff --git a/servo/components/style/values/specified/box.rs b/servo/components/style/values/specified/box.rs new file mode 100644 index 0000000000..7c6daf8eeb --- /dev/null +++ b/servo/components/style/values/specified/box.rs @@ -0,0 +1,1905 @@ +/* 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 +/// <https://drafts.csswg.org/css-display/#propdef-display> +#[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 <display-outside> and <display-inside> 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 <display-outside> and <display-inside> 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 <display-inside> value. + #[inline] + pub fn inside(&self) -> DisplayInside { + DisplayInside::from_u16(self.0 & ((1 << Self::DISPLAY_INSIDE_BITS) - 1)).unwrap() + } + + /// Accessor for the <display-outside> 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 + } + + /// <https://drafts.csswg.org/css2/visuren.html#x13> + #[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<W>(&self, dest: &mut CssWriter<W>) -> 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) + } + }, + }, + } + } +} + +/// <display-inside> = 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<DisplayInside, ParseError<'i>> { + 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, + }) +} + +/// <display-outside> = 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<DisplayOutside, ParseError<'i>> { + 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<DisplayInside, ParseError<'i>> { + Ok(try_match_ident_ignore_ascii_case! { input, + "flow" => DisplayInside::Flow, + #[cfg(feature = "gecko")] + "flow-root" => DisplayInside::FlowRoot, + }) +} +/// Test a <display-inside> Result for same values as above. +fn is_valid_inside_for_list_item<'i>(inside: &Result<DisplayInside, ParseError<'i>>) -> 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<Display, ParseError<'i>> { + // Parse all combinations of <display-inside/outside>? 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) + }; + // <display-listitem> = <display-outside>? && [ 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 <display-outside> 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<NonNegativeLength>; + +/// A specified value for the `line-clamp` property. +pub type LineClamp = GenericLineClamp<Integer>; + +/// A specified value for the `vertical-align` property. +pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>; + +impl Parse for VerticalAlign { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + 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<Self, ParseError<'i>> { + 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<W>(&self, dest: &mut CssWriter<W>) -> 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<ScrollSnapAlign, ParseError<'i>> { + 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<W>(&self, dest: &mut CssWriter<W>) -> 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. +/// +/// <https://drafts.csswg.org/css-will-change/#will-change> +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<CustomIdent>, + /// 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 | <animateable-feature># + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + 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 | <length> | auto <length> + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + 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 | <positive-integer> + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + 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<CustomIdent>); + +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<Self, ParseError<'i>> { + 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, ParseError<'i>> { + Self::parse_internal(input, /* for_query = */ true) + } +} + +impl Parse for ContainerName { + fn parse<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Self::parse_internal(input, /* for_query = */ false) + } +} + +/// A specified value for the `perspective` property. +pub type Perspective = GenericPerspective<NonNegativeLength>; + +#[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 <textarea>. + Textarea, + /// A checkbox element. + Checkbox, + /// A radio element within a radio group. + Radio, + /// A dropdown list. + Menulist, + /// List boxes. + Listbox, + /// A horizontal meter bar. + Meter, + /// A horizontal progress bar. + ProgressBar, + /// A typical dialog button. + Button, + /// A single-line text field, e.g. HTML <input type=text>. + Textfield, + /// The dropdown button(s) that open up a dropdown list. + MenulistButton, + /// Various arrows that go in buttons + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ButtonArrowDown, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ButtonArrowNext, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ButtonArrowPrevious, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ButtonArrowUp, + /// A dual toolbar button (e.g., a Back button with a dropdown) + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Dualbutton, + /// A groupbox. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Groupbox, + /// Menu Bar background + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menubar, + /// <menu> and <menuitem> appearances + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menuitem, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Checkmenuitem, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Radiomenuitem, + /// For text on non-iconic menuitems only + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menuitemtext, + /// The text part of a dropdown list, to left of button. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MenulistText, + /// Menu Popup background. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menupopup, + /// menu checkbox/radio appearances + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menucheckbox, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menuradio, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menuseparator, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menuarrow, + /// An image in the menu gutter, like in bookmarks or history. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Menuimage, + /// The meter bar's meter indicator. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Meterchunk, + /// The "arrowed" part of the dropdown button that open up a dropdown list. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMenulistArrowButton, + /// For HTML's <input type=number> + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + NumberInput, + /// The progress bar's progress indicator + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Progresschunk, + /// A generic container that always repaints on state changes. This is a + /// hack to make XUL checkboxes and radio buttons work. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + CheckboxContainer, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + RadioContainer, + /// The label part of a checkbox or radio button, used for painting a focus + /// outline. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + CheckboxLabel, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + RadioLabel, + /// nsRangeFrame and its subparts + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Range, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + RangeThumb, + /// The scrollbar slider + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbarHorizontal, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbarVertical, + /// A scrollbar button (up/down/left/right). + /// Keep these in order (some code casts these values to `int` in order to + /// compare them against each other). + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbarbuttonUp, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbarbuttonDown, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbarbuttonLeft, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbarbuttonRight, + /// The scrollbar thumb. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbarthumbHorizontal, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbarthumbVertical, + /// The scrollbar track. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbartrackHorizontal, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ScrollbartrackVertical, + /// The scroll corner + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Scrollcorner, + /// A separator. Can be horizontal or vertical. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Separator, + /// A spin control (up/down control for time/date pickers). + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Spinner, + /// The up button of a spin control. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + SpinnerUpbutton, + /// The down button of a spin control. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + SpinnerDownbutton, + /// The textfield of a spin control + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + SpinnerTextfield, + /// A splitter. Can be horizontal or vertical. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Splitter, + /// A status bar in a main application window. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Statusbar, + /// A single tab in a tab widget. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Tab, + /// A single pane (inside the tabpanels container). + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Tabpanel, + /// The tab panels container. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Tabpanels, + /// The tabs scroll arrows (left/right). + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + TabScrollArrowBack, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + TabScrollArrowForward, + /// A toolbar in an application window. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Toolbar, + /// A single toolbar button (with no associated dropdown). + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Toolbarbutton, + /// The dropdown portion of a toolbar button + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + ToolbarbuttonDropdown, + /// The gripper for a toolbar. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Toolbargripper, + /// The toolbox that contains the toolbars. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Toolbox, + /// A tooltip. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Tooltip, + /// A listbox or tree widget header + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Treeheader, + /// An individual header cell + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Treeheadercell, + /// The sort arrow for a header. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Treeheadersortarrow, + /// A tree item. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Treeitem, + /// A tree widget branch line + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Treeline, + /// A tree widget twisty. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Treetwisty, + /// Open tree widget twisty. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Treetwistyopen, + /// A tree widget. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Treeview, + /// Window and dialog backgrounds. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Window, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + Dialog, + + /// Vista Rebars. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWinCommunicationsToolbox, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWinMediaToolbox, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWinBrowsertabbarToolbox, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWinBorderlessGlass, + /// -moz-apperance style used in setting proper glass margins. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWinExcludeGlass, + + /// Mac help button. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMacHelpButton, + + /// Windows themed window frame elements. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWindowButtonBox, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWindowButtonBoxMaximized, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWindowButtonClose, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWindowButtonMaximize, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWindowButtonMinimize, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWindowButtonRestore, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWindowTitlebar, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozWindowTitlebarMaximized, + + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMacActiveSourceListSelection, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMacDisclosureButtonClosed, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMacDisclosureButtonOpen, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMacSourceList, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMacSourceListSelection, + + /// A themed focus outline (for outline:auto). + /// + /// This isn't exposed to CSS at all, just here for convenience. + #[css(skip)] + FocusOutline, + + /// A dummy variant that should be last to let the GTK widget do hackery. + #[css(skip)] + Count, +} + +/// A kind of break between two boxes. +/// +/// https://drafts.csswg.org/css-break/#break-between +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum BreakBetween { + Always, + Auto, + Page, + Avoid, + Left, + Right, +} + +impl BreakBetween { + /// Parse a legacy break-between value for `page-break-{before,after}`. + /// + /// See https://drafts.csswg.org/css-break/#page-break-properties. + #[inline] + pub(crate) fn parse_legacy<'i>( + _: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Self, ParseError<'i>> { + let break_value = BreakBetween::parse(input)?; + match break_value { + BreakBetween::Always => Ok(BreakBetween::Page), + BreakBetween::Auto | BreakBetween::Avoid | BreakBetween::Left | BreakBetween::Right => { + Ok(break_value) + }, + BreakBetween::Page => { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + }, + } + } + + /// Serialize a legacy break-between value for `page-break-*`. + /// + /// See https://drafts.csswg.org/css-break/#page-break-properties. + pub(crate) fn to_css_legacy<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + BreakBetween::Auto | BreakBetween::Avoid | BreakBetween::Left | BreakBetween::Right => { + self.to_css(dest) + }, + BreakBetween::Page => dest.write_str("always"), + BreakBetween::Always => Ok(()), + } + } +} + +/// A kind of break within a box. +/// +/// https://drafts.csswg.org/css-break/#break-within +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum BreakWithin { + Auto, + Avoid, + AvoidPage, + AvoidColumn, +} + +impl BreakWithin { + /// Parse a legacy break-between value for `page-break-inside`. + /// + /// See https://drafts.csswg.org/css-break/#page-break-properties. + #[inline] + pub(crate) fn parse_legacy<'i>( + _: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Self, ParseError<'i>> { + let break_value = BreakWithin::parse(input)?; + match break_value { + BreakWithin::Auto | BreakWithin::Avoid => Ok(break_value), + BreakWithin::AvoidPage | BreakWithin::AvoidColumn => { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + }, + } + } + + /// Serialize a legacy break-between value for `page-break-inside`. + /// + /// See https://drafts.csswg.org/css-break/#page-break-properties. + pub(crate) fn to_css_legacy<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + BreakWithin::Auto | BreakWithin::Avoid => self.to_css(dest), + BreakWithin::AvoidPage | BreakWithin::AvoidColumn => Ok(()), + } + } +} + +/// The value for the `overflow-x` / `overflow-y` properties. +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum Overflow { + Visible, + Hidden, + Scroll, + Auto, + #[cfg(feature = "gecko")] + Clip, +} + +// This can be derived once we remove or keep `-moz-hidden-unscrollable` +// indefinitely. +impl Parse for Overflow { + fn parse<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Ok(try_match_ident_ignore_ascii_case! { input, + "visible" => Self::Visible, + "hidden" => Self::Hidden, + "scroll" => Self::Scroll, + "auto" => Self::Auto, + #[cfg(feature = "gecko")] + "clip" => Self::Clip, + #[cfg(feature = "gecko")] + "-moz-hidden-unscrollable" if static_prefs::pref!("layout.css.overflow-moz-hidden-unscrollable.enabled") => { + Overflow::Clip + }, + "overlay" if static_prefs::pref!("layout.css.overflow-overlay.enabled") => { + Overflow::Auto + }, + }) + } +} + +impl Overflow { + /// Return true if the value will create a scrollable box. + #[inline] + pub fn is_scrollable(&self) -> bool { + matches!(*self, Self::Hidden | Self::Scroll | Self::Auto) + } + /// Convert the value to a scrollable value if it's not already scrollable. + /// This maps `visible` to `auto` and `clip` to `hidden`. + #[inline] + pub fn to_scrollable(&self) -> Self { + match *self { + Self::Hidden | Self::Scroll | Self::Auto => *self, + Self::Visible => Self::Auto, + #[cfg(feature = "gecko")] + Self::Clip => Self::Hidden, + } + } +} + +bitflags! { + #[derive(Clone, Copy, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] + #[repr(C)] + #[css(bitflags(single = "auto", mixed = "stable,both-edges", validate_mixed="Self::has_stable"))] + /// Values for scrollbar-gutter: + /// <https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property> + pub struct ScrollbarGutter: u8 { + /// `auto` variant. Just for convenience if there is no flag set. + const AUTO = 0; + /// `stable` variant. + const STABLE = 1 << 0; + /// `both-edges` variant. + const BOTH_EDGES = 1 << 1; + } +} + +impl ScrollbarGutter { + #[inline] + fn has_stable(&self) -> bool { + self.intersects(Self::STABLE) + } +} |