use crate::style::unexpected_token;
use std::{borrow::Cow, collections::HashMap, fmt, str::FromStr};
use cssparser::{Parser, ParserInput, Token, match_ignore_ascii_case};
use paste::paste;
use serde::de::IgnoredAny;
use smallvec::{SmallVec, smallvec};
use crate::style::selector::{PropertyRule, StyleDeclarationParser};
use crate::{
error::StyleDeclarationBlockParseError,
style::{CssInput, CssValueSeed, SizingContext, properties::*},
};
use cssparser::RuleBodyParser;
#[path = "stylesheets_helpers.rs"]
mod stylesheets_helpers;
#[path = "stylesheets_mask.rs"]
mod stylesheets_mask;
#[path = "stylesheets_query.rs"]
mod stylesheets_query;
#[path = "stylesheets_vars.rs"]
mod stylesheets_vars;
use self::stylesheets_helpers::*;
pub(crate) use self::stylesheets_mask::PropertyMask;
use self::stylesheets_vars::apply_deferred_declaration;
macro_rules! define_inherited_default {
($parent:expr, $inherit:tt) => {
$parent.to_owned()
};
($parent:expr) => {
Default::default()
};
}
type ParsedDeclarations = SmallVec<[StyleDeclaration; 8]>;
#[derive(Debug, Clone, PartialEq, Eq)]
struct DeferredDeclaration {
property: PropertyId,
specified_value: String,
}
#[derive(Clone, Copy)]
struct InterpolationContext<'a> {
progress: f32,
sizing: &'a SizingContext,
current_color: Color,
}
fn interpolate_option_with_missing<T: Animatable + Clone>(
target: &mut Option<T>,
from: &Option<T>,
to: &Option<T>,
missing_from: T,
missing_to: T,
context: InterpolationContext<'_>,
) {
*target = match (from, to) {
(Some(from), Some(to)) => {
let mut value = from.clone();
value.interpolate(
from,
to,
context.progress,
context.sizing,
context.current_color,
);
Some(value)
}
(Some(from), None) => {
let mut value = from.clone();
value.interpolate(
from,
&missing_to,
context.progress,
context.sizing,
context.current_color,
);
Some(value)
}
(None, Some(to)) => {
let mut value = missing_from.clone();
value.interpolate(
&missing_from,
to,
context.progress,
context.sizing,
context.current_color,
);
Some(value)
}
(None, None) => None,
};
}
macro_rules! push_expanded_declarations {
($target:expr; $($declaration:expr),+ $(,)?) => {{
$(
$target.push($declaration);
)+
}};
}
macro_rules! push_axis_declarations {
($target:expr, $value:expr, $first:ident, $second:ident) => {{
let value = $value;
push_expanded_declarations!(
$target;
StyleDeclaration::$first(value.x),
StyleDeclaration::$second(value.y),
);
}};
}
macro_rules! push_four_side_declarations {
($target:expr, $values:expr, $top:ident, $right:ident, $bottom:ident, $left:ident) => {{
let values = $values;
push_expanded_declarations!(
$target;
StyleDeclaration::$top(values[0]),
StyleDeclaration::$right(values[1]),
StyleDeclaration::$bottom(values[2]),
StyleDeclaration::$left(values[3]),
);
}};
}
macro_rules! define_style {
(
longhands {
$(
$longhand:ident: $longhand_ty:ty
$(where inherit = $longhand_inherit:expr)?,
)*
}
transient_longhands {
$(
$transient:ident: $transient_ty:ty
=> ($transient_ltr:ident, $transient_rtl:ident),
)*
}
shorthands {
$(
$shorthand:ident: $shorthand_ty:ty
$(where inherit = $shorthand_inherit:expr)?
=> [$($target:ident),+ $(,)?]
|$value:ident, $target_var:ident|
$expand:block,
)*
}
) => {
paste! {
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LonghandId {
$([<$longhand:camel>],)*
$([<$transient:camel>],)*
}
impl LonghandId {
const COUNT: usize = [$(Self::[<$longhand:camel>]),* $(, Self::[<$transient:camel>])*].len();
const ALL: [Self; Self::COUNT] = [
$(Self::[<$longhand:camel>],)*
$(Self::[<$transient:camel>],)*
];
const fn index(self) -> usize {
self as usize
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ShorthandId {
$([<$shorthand:camel>],)*
}
impl LonghandId {
fn parse_declarations<'i>(
self,
input: &mut cssparser::Parser<'i, '_>,
) -> ParseResult<'i, ParsedDeclarations> {
let state = input.state();
let keyword = input.try_parse(CssWideKeyword::from_css).ok();
if let Some(keyword) = keyword {
return Ok(smallvec![StyleDeclaration::CssWideKeyword(self, keyword)]);
}
input.reset(&state);
match self {
$(
Self::[<$longhand:camel>] => Ok(smallvec?,
)]),
)*
$(
Self::[<$transient:camel>] => Ok(smallvec?,
)]),
)*
}
}
fn parse_css_input_declarations<'de>(
self,
css_input: CssInput<'de>,
) -> Result<ParsedDeclarations, CssInputParseError<'de>> {
if let Some(keyword) = parse_css_wide_keyword(&css_input) {
return Ok(smallvec![StyleDeclaration::CssWideKeyword(self, keyword)]);
}
match self {
$(
Self::[<$longhand:camel>] => Ok(smallvec?,
)]),
)*
$(
Self::[<$transient:camel>] => Ok(smallvec?,
)]),
)*
}
}
}
impl ShorthandId {
fn parse_declarations<'i>(
self,
input: &mut cssparser::Parser<'i, '_>,
) -> ParseResult<'i, ParsedDeclarations> {
match self {
$(
Self::[<$shorthand:camel>] => Ok(expand_shorthand(
<$shorthand_ty as FromCss>::from_css(input)?,
|$value, $target_var| {
$expand
},
)),
)*
}
}
fn parse_css_input_declarations<'de>(
self,
css_input: CssInput<'de>,
) -> Result<ParsedDeclarations, CssInputParseError<'de>> {
match self {
$(
Self::[<$shorthand:camel>] => {
if let Some(keyword) = parse_css_wide_keyword(&css_input) {
let mut declarations = ParsedDeclarations::new();
$(
declarations.push(StyleDeclaration::CssWideKeyword(LonghandId::$target, keyword));
)+
return Ok(declarations);
}
Ok(expand_shorthand(
parse_css_input_value::<$shorthand_ty>(css_input)?,
|$value, $target_var| {
$expand
},
))
}
)*
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PropertyId {
Ignored,
Custom,
Longhand(LonghandId),
Shorthand(ShorthandId),
}
impl PropertyId {
fn from_normalized_name(name: &str) -> Self {
match name {
$(stringify!($longhand) => Self::Longhand(LonghandId::[<$longhand:camel>]),)*
$(stringify!($transient) => Self::Longhand(LonghandId::[<$transient:camel>]),)*
$(stringify!($shorthand) => Self::Shorthand(ShorthandId::[<$shorthand:camel>]),)*
_ => Self::Ignored,
}
}
fn from_kebab_case(name: &str) -> Self {
PropertyId::from_name(name, normalize_kebab_property_name)
}
#[allow(dead_code)]
pub fn from_camel_case(name: &str) -> Self {
PropertyId::from_name(name, normalize_camel_property_name)
}
fn parse_declarations<'i>(
self,
name: &str,
input: &mut cssparser::Parser<'i, '_>,
) -> ParseResult<'i, ParsedDeclarations> {
match self {
Self::Ignored => {
while input.next_including_whitespace_and_comments().is_ok() {}
Ok(ParsedDeclarations::new())
}
Self::Custom => {
let start = input.position();
while input.next_including_whitespace_and_comments().is_ok() {}
Ok(smallvec![StyleDeclaration::CustomProperty(
name.to_owned(),
input.slice_from(start).trim().to_owned(),
)])
}
Self::Shorthand(property) => property.parse_declarations(input),
Self::Longhand(property) => property.parse_declarations(input),
}
}
fn parse_css_input_declarations<'de>(
self,
css_input: CssInput<'de>,
) -> Result<ParsedDeclarations, CssInputParseError<'de>> {
debug_assert!(
!matches!(self, Self::Custom),
"custom properties should be handled before parse_css_input_declarations",
);
let css_string = match &css_input {
CssInput::Str(value) => Some(value.as_ref()),
CssInput::Number(_) => None,
CssInput::Unexpected(_) => None,
};
if css_string.is_some_and(contains_var_function) {
return Ok(smallvec![StyleDeclaration::Deferred(DeferredDeclaration {
property: self,
specified_value: css_input.into_string(),
})]);
}
match self {
Self::Ignored => Ok(ParsedDeclarations::new()),
Self::Custom => Ok(ParsedDeclarations::new()),
Self::Shorthand(property) => property.parse_css_input_declarations(css_input),
Self::Longhand(property) => property.parse_css_input_declarations(css_input),
}
}
fn target_longhands(self) -> PropertyMask {
match self {
Self::Ignored | Self::Custom => PropertyMask::default(),
Self::Longhand(property) => [property].into_iter().collect(),
Self::Shorthand(property) => match property {
$(ShorthandId::[<$shorthand:camel>] => {
[$(LonghandId::$target),+].into_iter().collect()
})*
},
}
}
}
fn parse_style_declaration<'i>(
name: &str,
input: &mut cssparser::Parser<'i, '_>,
) -> ParseResult<'i, StyleDeclarationBlock> {
let property = PropertyId::from_kebab_case(name);
let start = input.position();
if !matches!(property, PropertyId::Ignored | PropertyId::Custom) {
let state = input.state();
while input.next_including_whitespace_and_comments().is_ok() {}
let specified_value = input.slice_from(start).trim();
if contains_var_function(specified_value) {
return Ok(StyleDeclarationBlock::from_parsed_declarations(
smallvec![StyleDeclaration::Deferred(DeferredDeclaration {
property,
specified_value: specified_value.to_owned(),
})],
false,
));
}
input.reset(&state);
}
property.parse_declarations(name, input).map(|declarations| {
StyleDeclarationBlock::from_parsed_declarations(declarations, false)
})
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Style {
pub declarations: StyleDeclarationBlock,
}
impl<'de> serde::Deserialize<'de> for Style {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct StyleVisitor;
impl<'de> serde::de::Visitor<'de> for StyleVisitor {
type Value = Style;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a style object")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut style = Style::default();
while let Some(key) = map.next_key::<Cow<'de, str>>()? {
let property = PropertyId::from_camel_case(&key);
if matches!(property, PropertyId::Ignored) {
map.next_value::<IgnoredAny>()?;
continue;
}
let css_input = map.next_value_seed(CssValueSeed)?;
if matches!(property, PropertyId::Custom) {
if !matches!(css_input, CssInput::Unexpected(_)) {
style.declarations.push(
StyleDeclaration::CustomProperty(key.into_owned(), css_input.into_string()),
false,
);
}
} else {
style
.declarations
.append_parsed_declarations(
property
.parse_css_input_declarations(css_input)
.map_err(|error| error.into_serde_error(&key, property))?,
false,
);
}
}
Ok(style)
}
}
deserializer.deserialize_map(StyleVisitor)
}
}
impl<'de> serde::Deserialize<'de> for StyleDeclarationBlock {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Style::deserialize(deserializer).map(Into::into)
}
}
impl Style {
fn with_declarations(
mut self,
declarations: impl IntoIterator<Item = StyleDeclaration>,
important: bool,
) -> Self {
for declaration in declarations {
self.declarations.push(declaration, important);
}
self
}
pub fn with(self, declaration: StyleDeclaration) -> Self {
self.with_declarations([declaration], false)
}
$(
pub fn [<with_ $shorthand>](self, value: $shorthand_ty) -> Self {
self.with_declarations(
expand_shorthand(value, |$value, $target_var| {
$expand
}),
false,
)
}
)*
pub fn with_important(self, declaration: StyleDeclaration) -> Self {
self.with_declarations([declaration], true)
}
pub fn append_block(&mut self, declarations: StyleDeclarationBlock) {
self.declarations.append(declarations);
}
pub fn push(&mut self, declaration: StyleDeclaration, important: bool) {
self.declarations.push(declaration, important);
}
pub fn resource_urls(&self) -> impl Iterator<Item = &str> {
self.declarations.resource_urls()
}
pub fn inherit(self, parent: &ComputedStyle) -> ComputedStyle {
let mut style = ComputedStyle::from_parent(parent);
let mut declarations = ParsedDeclarations::new();
for declaration in self.declarations.declarations {
match declaration {
StyleDeclaration::CustomProperty(name, value) => {
style.custom_properties.insert(name, value);
}
declaration => declarations.push(declaration),
}
}
for declaration in &declarations {
match declaration {
StyleDeclaration::Direction(d) => style.direction = *d,
StyleDeclaration::CssWideKeyword(LonghandId::Direction, keyword) => {
style.direction = match keyword {
CssWideKeyword::Initial => Direction::default(),
CssWideKeyword::Inherit | CssWideKeyword::Unset => parent.direction,
};
}
StyleDeclaration::Deferred(deferred)
if matches!(deferred.property, PropertyId::Longhand(LonghandId::Direction)) =>
{
apply_deferred_declaration(&mut style, Some(parent), deferred);
}
_ => {}
}
}
for declaration in declarations {
declaration.apply_with_parent(&mut style, parent);
}
style
}
pub fn merge_from(&mut self, other: Self) {
self.append_block(other.declarations);
}
}
impl From<StyleDeclarationBlock> for Style {
fn from(declarations: StyleDeclarationBlock) -> Self {
Self { declarations }
}
}
impl From<Style> for StyleDeclarationBlock {
fn from(style: Style) -> Self {
style.declarations
}
}
#[derive(Clone, Debug, Default)]
pub struct ComputedStyle {
pub custom_properties: HashMap<String, String>,
pub registered_custom_properties: HashMap<String, PropertyRule>,
$(pub $longhand: $longhand_ty,)*
}
#[allow(private_interfaces)]
#[derive(Debug, Clone, PartialEq)]
pub enum StyleDeclaration {
$(
[<$longhand:camel>]($longhand_ty),
)*
$(
[<$transient:camel>]($transient_ty),
)*
CustomProperty(String, String),
Deferred(DeferredDeclaration),
CssWideKeyword(LonghandId, CssWideKeyword),
}
impl ComputedStyle {
pub fn from_parent(parent: &Self) -> Self {
Self {
custom_properties: if parent.custom_properties.is_empty() {
HashMap::new()
} else {
parent.custom_properties.clone()
},
registered_custom_properties: if parent.registered_custom_properties.is_empty() {
HashMap::new()
} else {
parent.registered_custom_properties.clone()
},
$($longhand: define_inherited_default!(parent.$longhand $(, $longhand_inherit)?),)*
}
}
pub fn make_computed_values(&mut self, sizing: &SizingContext) {
$(self.$longhand.make_computed(sizing);)*
}
pub(crate) fn apply_interpolated_properties(
&mut self,
from: &Self,
to: &Self,
animated_properties: &PropertyMask,
progress: f32,
sizing: &SizingContext,
current_color: Color,
) {
let interpolation_context = InterpolationContext {
progress,
sizing,
current_color,
};
for property in animated_properties.iter() {
match property {
$(
LonghandId::[<$longhand:camel>] => {
self.$longhand.interpolate(
&from.$longhand,
&to.$longhand,
progress,
sizing,
current_color,
);
}
)*
$(LonghandId::[<$transient:camel>] => {})*
}
}
if animated_properties.contains(&LonghandId::FlexGrow) {
interpolate_option_with_missing(
&mut self.flex_grow,
&from.flex_grow,
&to.flex_grow,
FlexGrow(0.0),
FlexGrow(0.0),
interpolation_context,
);
}
if animated_properties.contains(&LonghandId::FlexShrink) {
interpolate_option_with_missing(
&mut self.flex_shrink,
&from.flex_shrink,
&to.flex_shrink,
FlexGrow(1.0),
FlexGrow(1.0),
interpolation_context,
);
}
if animated_properties.contains(&LonghandId::WebkitTextStrokeWidth) {
interpolate_option_with_missing(
&mut self.webkit_text_stroke_width,
&from.webkit_text_stroke_width,
&to.webkit_text_stroke_width,
Length::zero(),
Length::zero(),
interpolation_context,
);
}
if animated_properties.contains(&LonghandId::WebkitTextStrokeColor) {
interpolate_option_with_missing(
&mut self.webkit_text_stroke_color,
&from.webkit_text_stroke_color,
&to.webkit_text_stroke_color,
ColorInput::CurrentColor,
ColorInput::CurrentColor,
interpolation_context,
);
}
if animated_properties.contains(&LonghandId::WebkitTextFillColor) {
interpolate_option_with_missing(
&mut self.webkit_text_fill_color,
&from.webkit_text_fill_color,
&to.webkit_text_fill_color,
from.color,
to.color,
interpolation_context,
);
}
}
}
impl StyleDeclaration {
$(
pub fn $longhand(value: $longhand_ty) -> Self {
Self::[<$longhand:camel>](value)
}
)*
$(
pub fn $transient(value: $transient_ty) -> Self {
Self::[<$transient:camel>](value)
}
)*
pub fn longhand_id(&self) -> LonghandId {
match self {
$(Self::[<$longhand:camel>](..) => LonghandId::[<$longhand:camel>],)*
$(Self::[<$transient:camel>](..) => LonghandId::[<$transient:camel>],)*
Self::CustomProperty(..) | Self::Deferred(..) => {
unreachable!("custom and deferred declarations do not map to a single longhand")
}
Self::CssWideKeyword(id, _) => *id,
}
}
pub(crate) fn affected_longhands(&self) -> PropertyMask {
match self {
Self::CssWideKeyword(id, _) => [*id].into_iter().collect(),
Self::CustomProperty(..) => PropertyMask::default(),
Self::Deferred(deferred) => deferred.property.target_longhands(),
_ => [self.longhand_id()].into_iter().collect(),
}
}
pub fn apply_with_parent(
self,
style: &mut ComputedStyle,
parent: &ComputedStyle,
) {
let is_rtl = style.direction == Direction::Rtl;
match self {
Self::CssWideKeyword(property, keyword) => {
match property {
$(
LonghandId::[<$longhand:camel>] => {
style.$longhand = match keyword {
CssWideKeyword::Initial => Default::default(),
CssWideKeyword::Inherit => parent.$longhand.to_owned(),
CssWideKeyword::Unset => define_inherited_default!(parent.$longhand $(, $longhand_inherit)?),
};
}
)*
$(
LonghandId::[<$transient:camel>] => {
let target = if is_rtl { &mut style.$transient_rtl } else { &mut style.$transient_ltr };
*target = match keyword {
CssWideKeyword::Initial | CssWideKeyword::Unset => Default::default(),
CssWideKeyword::Inherit => {
if parent.direction == Direction::Rtl {
parent.$transient_rtl.to_owned()
} else {
parent.$transient_ltr.to_owned()
}
}
};
}
)*
}
}
Self::CustomProperty(name, value) => {
style.custom_properties.insert(name, value);
}
Self::Deferred(deferred) => {
apply_deferred_declaration(style, Some(parent), &deferred);
}
$(Self::[<$longhand:camel>](value) => style.$longhand = value,)*
$(
Self::[<$transient:camel>](value) => {
if is_rtl { style.$transient_rtl = value } else { style.$transient_ltr = value }
}
)*
}
}
pub fn apply_to_computed(&self, style: &mut ComputedStyle) {
let is_rtl = style.direction == Direction::Rtl;
match self {
Self::CssWideKeyword(property, keyword) => match keyword {
CssWideKeyword::Initial => match property {
$(
LonghandId::[<$longhand:camel>] => {
style.$longhand = Default::default();
}
)*
$(
LonghandId::[<$transient:camel>] => {
if is_rtl { style.$transient_rtl = Default::default() }
else { style.$transient_ltr = Default::default() }
}
)*
},
CssWideKeyword::Inherit | CssWideKeyword::Unset => {}
},
Self::CustomProperty(name, value) => {
style
.custom_properties
.insert(name.to_owned(), value.to_owned());
}
Self::Deferred(deferred) => apply_deferred_declaration(style, None, deferred),
$(Self::[<$longhand:camel>](value) => style.$longhand.clone_from(value),)*
$(
Self::[<$transient:camel>](value) => {
if is_rtl { style.$transient_rtl.clone_from(value) }
else { style.$transient_ltr.clone_from(value) }
}
)*
}
}
pub fn merge_into_ref(&self, style: &mut Style) {
style.declarations.push(self.to_owned(), false);
}
}
impl crate::style::properties::ToCss for StyleDeclaration {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match self {
$(
Self::[<$longhand:camel>](value) => {
let name = stringify!($longhand).replace("_", "-");
if name.starts_with("webkit-") {
dest.write_str("-")?;
}
dest.write_str(&name)?;
dest.write_str(": ")?;
value.to_css(dest)?;
dest.write_str(";")
}
)*
$(
Self::[<$transient:camel>](value) => {
let name = stringify!($transient).replace("_", "-");
dest.write_str(&name)?;
dest.write_str(": ")?;
value.to_css(dest)?;
dest.write_str(";")
}
)*
Self::CustomProperty(name, value) => {
write!(dest, "{}: {};", name, value)
}
Self::Deferred(deferred) => {
let name = match deferred.property {
PropertyId::Longhand(id) => format!("{:?}", id),
PropertyId::Shorthand(id) => format!("{:?}", id),
_ => return Ok(()),
};
write!(dest, "{}: {};", to_kebab_case(&name), deferred.specified_value)
}
Self::CssWideKeyword(id, keyword) => {
let name = format!("{:?}", id);
let keyword_str = match keyword {
CssWideKeyword::Initial => "initial",
CssWideKeyword::Inherit => "inherit",
CssWideKeyword::Unset => "unset",
};
write!(dest, "{}: {};", to_kebab_case(&name), keyword_str)
}
}
}
}
}
};
}
define_style! {
longhands {
box_sizing: BoxSizing,
opacity: PercentageNumber,
animation_name: AnimationNames,
animation_duration: AnimationDurations,
animation_delay: AnimationDurations,
animation_timing_function: AnimationTimingFunctions,
animation_iteration_count: AnimationIterationCounts,
animation_direction: AnimationDirections,
animation_fill_mode: AnimationFillModes,
animation_play_state: AnimationPlayStates,
display: Display,
width: Length,
height: Length,
max_width: Length,
max_height: Length,
min_width: Length,
min_height: Length,
aspect_ratio: AspectRatio,
padding_top: LengthDefaultsToZero,
padding_right: LengthDefaultsToZero,
padding_bottom: LengthDefaultsToZero,
padding_left: LengthDefaultsToZero,
margin_top: LengthDefaultsToZero,
margin_right: LengthDefaultsToZero,
margin_bottom: LengthDefaultsToZero,
margin_left: LengthDefaultsToZero,
top: Length,
right: Length,
bottom: Length,
left: Length,
flex_direction: FlexDirection,
justify_self: AlignItems,
justify_content: JustifyContent,
align_content: JustifyContent,
justify_items: AlignItems,
align_items: AlignItems,
align_self: AlignItems,
flex_wrap: FlexWrap,
flex_basis: Option<Length>,
order: Order,
z_index: ZIndex,
position: Position,
rotate: Option<Angle>,
scale: SpacePair<PercentageNumber>,
translate: SpacePair<Length>,
transform: Option<Transforms>,
transform_origin: TransformOrigin,
mask_image: Option<BackgroundImages>,
mask_size: BackgroundSizes,
mask_position: BackgroundPositions,
mask_repeat: BackgroundRepeats,
column_gap: LengthDefaultsToZero,
row_gap: LengthDefaultsToZero,
flex_grow: Option<FlexGrow>,
flex_shrink: Option<FlexGrow>,
border_top_left_radius: SpacePair<LengthDefaultsToZero>,
border_top_right_radius: SpacePair<LengthDefaultsToZero>,
border_bottom_right_radius: SpacePair<LengthDefaultsToZero>,
border_bottom_left_radius: SpacePair<LengthDefaultsToZero>,
border_top_width: Length,
border_right_width: Length,
border_bottom_width: Length,
border_left_width: Length,
border_top_style: BorderStyle,
border_right_style: BorderStyle,
border_bottom_style: BorderStyle,
border_left_style: BorderStyle,
border_top_color: ColorInput,
border_right_color: ColorInput,
border_bottom_color: ColorInput,
border_left_color: ColorInput,
outline_width: Length,
outline_style: BorderStyle,
outline_color: ColorInput,
outline_offset: Length,
object_fit: ObjectFit,
overflow_x: Overflow,
overflow_y: Overflow,
object_position: ObjectPosition,
background_image: Option<BackgroundImages>,
background_position: BackgroundPositions,
background_size: BackgroundSizes,
background_repeat: BackgroundRepeats,
background_blend_mode: BlendModes,
background_color: ColorDefaultsToTransparent,
background_clip: BackgroundClip,
box_shadow: Option<BoxShadows>,
grid_auto_columns: Option<GridTrackSizes>,
grid_auto_rows: Option<GridTrackSizes>,
grid_auto_flow: GridAutoFlow,
grid_row_start: GridPlacement,
grid_row_end: GridPlacement,
grid_column_start: GridPlacement,
grid_column_end: GridPlacement,
grid_template_columns: Option<GridTemplateComponents>,
grid_template_rows: Option<GridTemplateComponents>,
grid_template_areas: Option<GridTemplateAreas>,
text_overflow: TextOverflow,
text_fit: TextFit,
text_transform: TextTransform where inherit = true,
font_style: FontStyle where inherit = true,
font_stretch: FontStretch where inherit = true,
color: ColorInput where inherit = true,
filter: Filters,
backdrop_filter: Filters,
font_size: FontSize where inherit = true,
font_family: FontFamily where inherit = true,
line_height: LineHeight where inherit = true,
font_weight: FontWeight where inherit = true,
font_variation_settings: FontVariationSettings where inherit = true,
font_feature_settings: FontFeatureSettings where inherit = true,
font_synthesis_weight: FontSynthesic where inherit = true,
font_synthesis_style: FontSynthesic where inherit = true,
line_clamp: Option<LineClamp> where inherit = true,
text_align: TextAlign where inherit = true,
webkit_text_stroke_width: Option<LengthDefaultsToZero> where inherit = true,
webkit_text_stroke_color: Option<ColorInput> where inherit = true,
webkit_text_fill_color: Option<ColorInput> where inherit = true,
stroke_linejoin: LineJoin where inherit = true,
text_shadow: Option<TextShadows> where inherit = true,
text_decoration_line: Option<TextDecorationLines>,
text_decoration_style: TextDecorationStyle,
text_decoration_color: ColorInput,
text_decoration_thickness: TextDecorationThickness,
text_decoration_skip_ink: TextDecorationSkipInk where inherit = true,
text_indent: TextIndent where inherit = true,
letter_spacing: Length where inherit = true,
word_spacing: Length where inherit = true,
image_rendering: ImageScalingAlgorithm where inherit = true,
overflow_wrap: OverflowWrap where inherit = true,
word_break: WordBreak where inherit = true,
clip_path: Option<BasicShape>,
clip_rule: FillRule where inherit = true,
white_space_collapse: WhiteSpaceCollapse where inherit = true,
text_wrap_mode: TextWrapMode where inherit = true,
text_wrap_style: TextWrapStyle where inherit = true,
direction: Direction where inherit = true,
float: Float,
clear: Clear,
isolation: Isolation,
mix_blend_mode: BlendMode,
visibility: Visibility where inherit = true,
vertical_align: VerticalAlign,
content: ContentValue,
}
transient_longhands {
margin_inline_start: LengthDefaultsToZero => (margin_left, margin_right),
margin_inline_end: LengthDefaultsToZero => (margin_right, margin_left),
padding_inline_start: LengthDefaultsToZero => (padding_left, padding_right),
padding_inline_end: LengthDefaultsToZero => (padding_right, padding_left),
}
shorthands {
animation: Animations => [AnimationName, AnimationDuration, AnimationDelay, AnimationTimingFunction, AnimationIterationCount, AnimationDirection, AnimationFillMode, AnimationPlayState] |value, target| {
target.push(StyleDeclaration::animation_duration(value.iter().map(|animation| animation.duration).collect()));
target.push(StyleDeclaration::animation_delay(value.iter().map(|animation| animation.delay).collect()));
target.push(StyleDeclaration::animation_timing_function(
value
.iter()
.map(|animation| animation.timing_function)
.collect(),
));
target.push(StyleDeclaration::animation_iteration_count(
value
.iter()
.map(|animation| animation.iteration_count)
.collect(),
));
target.push(StyleDeclaration::animation_direction(
value.iter().map(|animation| animation.direction).collect(),
));
target.push(StyleDeclaration::animation_fill_mode(
value.iter().map(|animation| animation.fill_mode).collect(),
));
target.push(StyleDeclaration::animation_play_state(
value.iter().map(|animation| animation.play_state).collect(),
));
target.push(StyleDeclaration::animation_name(value.into_iter().map(|animation| animation.name).collect()));
},
padding: Sides<LengthDefaultsToZero> => [PaddingTop, PaddingRight, PaddingBottom, PaddingLeft] |value, target| {
push_four_side_declarations!(
target,
value.0,
padding_top,
padding_right,
padding_bottom,
padding_left
);
},
padding_inline: SpacePair<LengthDefaultsToZero> => [PaddingInlineStart, PaddingInlineEnd] |value, target| {
push_axis_declarations!(target, value, padding_inline_start, padding_inline_end);
},
padding_block: SpacePair<LengthDefaultsToZero> => [PaddingTop, PaddingBottom] |value, target| {
push_axis_declarations!(target, value, padding_top, padding_bottom);
},
margin: Sides<LengthDefaultsToZero> => [MarginTop, MarginRight, MarginBottom, MarginLeft] |value, target| {
push_four_side_declarations!(
target,
value.0,
margin_top,
margin_right,
margin_bottom,
margin_left
);
},
margin_inline: SpacePair<LengthDefaultsToZero> => [MarginInlineStart, MarginInlineEnd] |value, target| {
push_axis_declarations!(target, value, margin_inline_start, margin_inline_end);
},
margin_block: SpacePair<LengthDefaultsToZero> => [MarginTop, MarginBottom] |value, target| {
push_axis_declarations!(target, value, margin_top, margin_bottom);
},
inset: Sides<Length> => [Top, Right, Bottom, Left] |value, target| {
push_four_side_declarations!(target, value.0, top, right, bottom, left);
},
inset_inline: SpacePair<Length> => [Left, Right] |value, target| {
push_axis_declarations!(target, value, left, right);
},
inset_block: SpacePair<Length> => [Top, Bottom] |value, target| {
push_axis_declarations!(target, value, top, bottom);
},
mask: Backgrounds => [MaskImage, MaskPosition, MaskSize, MaskRepeat] |value, target| {
target.push(StyleDeclaration::mask_position(
value.iter().map(|background| background.position).collect(),
));
target.push(StyleDeclaration::mask_size(
value.iter().map(|background| background.size).collect(),
));
target.push(StyleDeclaration::mask_repeat(
value.iter().map(|background| background.repeat).collect(),
));
target.push(StyleDeclaration::mask_image(Some(
value
.into_iter()
.map(|background| background.image)
.collect(),
)));
},
gap: SpacePair<LengthDefaultsToZero> => [RowGap, ColumnGap] |value, target| {
push_axis_declarations!(target, value, row_gap, column_gap);
},
flex_flow: FlexFlow => [FlexDirection, FlexWrap] |value, target| {
target.push(StyleDeclaration::flex_direction(value.direction));
target.push(StyleDeclaration::flex_wrap(value.wrap));
},
flex: Option<Flex> => [FlexGrow, FlexShrink, FlexBasis] |value, target| {
target.push(StyleDeclaration::flex_grow(
value.map(|value| FlexGrow(value.grow)),
));
target.push(StyleDeclaration::flex_shrink(
value.map(|value| FlexGrow(value.shrink)),
));
target.push(StyleDeclaration::flex_basis(value.map(|value| value.basis)));
},
place_items: PlaceItems => [AlignItems, JustifyItems] |value, target| {
target.push(StyleDeclaration::align_items(value.align));
target.push(StyleDeclaration::justify_items(value.justify));
},
place_content: PlaceContent => [AlignContent, JustifyContent] |value, target| {
target.push(StyleDeclaration::align_content(value.align));
target.push(StyleDeclaration::justify_content(value.justify));
},
place_self: PlaceSelf => [AlignSelf, JustifySelf] |value, target| {
target.push(StyleDeclaration::align_self(value.align));
target.push(StyleDeclaration::justify_self(value.justify));
},
grid_column: GridLine => [GridColumnStart, GridColumnEnd] |value, target| {
target.push(StyleDeclaration::grid_column_start(value.start));
target.push(StyleDeclaration::grid_column_end(value.end));
},
grid_row: GridLine => [GridRowStart, GridRowEnd] |value, target| {
target.push(StyleDeclaration::grid_row_start(value.start));
target.push(StyleDeclaration::grid_row_end(value.end));
},
grid_area: GridArea => [GridRowStart, GridColumnStart, GridRowEnd, GridColumnEnd] |value, target| {
target.push(StyleDeclaration::grid_row_start(value.row_start));
target.push(StyleDeclaration::grid_column_start(value.column_start));
target.push(StyleDeclaration::grid_row_end(value.row_end));
target.push(StyleDeclaration::grid_column_end(value.column_end));
},
border_radius: BorderRadius => [BorderTopLeftRadius, BorderTopRightRadius, BorderBottomRightRadius, BorderBottomLeftRadius] |value, target| {
push_four_side_declarations!(
target,
value.0.0,
border_top_left_radius,
border_top_right_radius,
border_bottom_right_radius,
border_bottom_left_radius
);
},
border_width: Sides<Length> => [BorderTopWidth, BorderRightWidth, BorderBottomWidth, BorderLeftWidth] |value, target| {
push_four_side_declarations!(
target,
value.0,
border_top_width,
border_right_width,
border_bottom_width,
border_left_width
);
},
border_inline_width: SpacePair<Length> => [BorderLeftWidth, BorderRightWidth] |value, target| {
push_axis_declarations!(
target,
value,
border_left_width,
border_right_width
);
},
border_block_width: SpacePair<Length> => [BorderTopWidth, BorderBottomWidth] |value, target| {
push_axis_declarations!(
target,
value,
border_top_width,
border_bottom_width
);
},
border: Border => [BorderTopWidth, BorderRightWidth, BorderBottomWidth, BorderLeftWidth, BorderTopStyle, BorderRightStyle, BorderBottomStyle, BorderLeftStyle, BorderTopColor, BorderRightColor, BorderBottomColor, BorderLeftColor] |value, target| {
target.push(StyleDeclaration::border_top_width(value.width));
target.push(StyleDeclaration::border_right_width(value.width));
target.push(StyleDeclaration::border_bottom_width(value.width));
target.push(StyleDeclaration::border_left_width(value.width));
target.push(StyleDeclaration::border_top_style(value.style));
target.push(StyleDeclaration::border_right_style(value.style));
target.push(StyleDeclaration::border_bottom_style(value.style));
target.push(StyleDeclaration::border_left_style(value.style));
target.push(StyleDeclaration::border_top_color(value.color));
target.push(StyleDeclaration::border_right_color(value.color));
target.push(StyleDeclaration::border_bottom_color(value.color));
target.push(StyleDeclaration::border_left_color(value.color));
},
border_top: Border => [BorderTopWidth, BorderTopStyle, BorderTopColor] |value, target| {
target.push(StyleDeclaration::border_top_width(value.width));
target.push(StyleDeclaration::border_top_style(value.style));
target.push(StyleDeclaration::border_top_color(value.color));
},
border_right: Border => [BorderRightWidth, BorderRightStyle, BorderRightColor] |value, target| {
target.push(StyleDeclaration::border_right_width(value.width));
target.push(StyleDeclaration::border_right_style(value.style));
target.push(StyleDeclaration::border_right_color(value.color));
},
border_bottom: Border => [BorderBottomWidth, BorderBottomStyle, BorderBottomColor] |value, target| {
target.push(StyleDeclaration::border_bottom_width(value.width));
target.push(StyleDeclaration::border_bottom_style(value.style));
target.push(StyleDeclaration::border_bottom_color(value.color));
},
border_left: Border => [BorderLeftWidth, BorderLeftStyle, BorderLeftColor] |value, target| {
target.push(StyleDeclaration::border_left_width(value.width));
target.push(StyleDeclaration::border_left_style(value.style));
target.push(StyleDeclaration::border_left_color(value.color));
},
border_style: Sides<BorderStyle> => [BorderTopStyle, BorderRightStyle, BorderBottomStyle, BorderLeftStyle] |value, target| {
push_four_side_declarations!(
target,
value.0,
border_top_style,
border_right_style,
border_bottom_style,
border_left_style
);
},
border_color: Sides<ColorInput> => [BorderTopColor, BorderRightColor, BorderBottomColor, BorderLeftColor] |value, target| {
push_four_side_declarations!(
target,
value.0,
border_top_color,
border_right_color,
border_bottom_color,
border_left_color
);
},
outline: Border => [OutlineWidth, OutlineStyle, OutlineColor] |value, target| {
target.push(StyleDeclaration::outline_width(value.width));
target.push(StyleDeclaration::outline_style(value.style));
target.push(StyleDeclaration::outline_color(value.color));
},
overflow: SpacePair<Overflow> => [OverflowX, OverflowY] |value, target| {
push_axis_declarations!(target, value, overflow_x, overflow_y);
},
background: Backgrounds => [BackgroundImage, BackgroundPosition, BackgroundSize, BackgroundRepeat, BackgroundBlendMode, BackgroundColor, BackgroundClip] |value, target| {
target.push(StyleDeclaration::background_position(
value.iter().map(|background| background.position).collect(),
));
target.push(StyleDeclaration::background_size(
value.iter().map(|background| background.size).collect(),
));
target.push(StyleDeclaration::background_repeat(
value.iter().map(|background| background.repeat).collect(),
));
target.push(StyleDeclaration::background_blend_mode(
value
.iter()
.map(|background| background.blend_mode)
.collect(),
));
target.push(StyleDeclaration::background_color(
value
.iter()
.filter_map(|background| background.color)
.next_back()
.unwrap_or_default(),
));
target.push(StyleDeclaration::background_clip(
value
.last()
.map(|background| background.clip)
.unwrap_or_default(),
));
target.push(StyleDeclaration::background_image(Some(
value
.into_iter()
.map(|background| background.image)
.collect(),
)));
},
font_synthesis: FontSynthesis where inherit = true => [FontSynthesisWeight, FontSynthesisStyle] |value, target| {
target.push(StyleDeclaration::font_synthesis_weight(value.weight));
target.push(StyleDeclaration::font_synthesis_style(value.style));
},
webkit_text_stroke: Option<TextStroke> where inherit = true => [WebkitTextStrokeWidth, WebkitTextStrokeColor] |value, target| {
target.push(StyleDeclaration::webkit_text_stroke_width(
value.map(|value| value.width),
));
target.push(StyleDeclaration::webkit_text_stroke_color(
value.and_then(|value| value.color),
));
},
text_decoration: TextDecoration => [TextDecorationLine, TextDecorationStyle, TextDecorationColor, TextDecorationThickness] |value, target| {
target.push(StyleDeclaration::text_decoration_line(Some(value.line)));
target.push(StyleDeclaration::text_decoration_style(value.style));
target.push(StyleDeclaration::text_decoration_color(value.color));
target.push(StyleDeclaration::text_decoration_thickness(value.thickness));
},
white_space: WhiteSpace where inherit = true => [TextWrapMode, WhiteSpaceCollapse] |value, target| {
target.push(StyleDeclaration::text_wrap_mode(value.text_wrap_mode));
target.push(StyleDeclaration::white_space_collapse(
value.white_space_collapse,
));
},
text_wrap: TextWrap where inherit = true => [TextWrapMode, TextWrapStyle] |value, target| {
target.push(StyleDeclaration::text_wrap_mode(value.mode));
target.push(StyleDeclaration::text_wrap_style(value.style));
},
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CssWideKeyword {
Initial,
Inherit,
Unset,
}
impl<'i> FromCss<'i> for CssWideKeyword {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
let location = input.current_source_location();
let ident = input.expect_ident_cloned()?;
match_ignore_ascii_case! { ident.as_ref(),
"initial" => Ok(Self::Initial),
"inherit" => Ok(Self::Inherit),
"unset" => Ok(Self::Unset),
_ => Err(unexpected_token!(location, &Token::Ident(ident))),
}
}
const VALID_TOKENS: &'static [CssToken] = &[
CssToken::Keyword("initial"),
CssToken::Keyword("inherit"),
CssToken::Keyword("unset"),
];
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DeclarationImportance {
pub(crate) longhands: PropertyMask,
pub custom_properties: SmallVec<[Box<str>; 1]>,
}
impl DeclarationImportance {
pub fn is_empty(&self) -> bool {
self.custom_properties.is_empty() && self.longhands.iter().next().is_none()
}
pub fn insert_declaration(&mut self, declaration: &StyleDeclaration) {
self
.longhands
.extend(declaration.affected_longhands().iter());
if let StyleDeclaration::CustomProperty(name, _) = declaration {
self.insert_custom_property(name);
}
}
pub fn append(&mut self, other: &mut Self) {
self.longhands.append(&mut other.longhands);
for name in other.custom_properties.drain(..) {
if self
.custom_properties
.iter()
.all(|existing| existing != &name)
{
self.custom_properties.push(name);
}
}
}
fn insert_custom_property(&mut self, name: &str) {
if self
.custom_properties
.iter()
.all(|existing| existing.as_ref() != name)
{
self.custom_properties.push(name.into());
}
}
}
impl<T> From<T> for DeclarationImportance
where
T: IntoIterator<Item = LonghandId>,
{
fn from(value: T) -> Self {
Self {
longhands: value.into_iter().collect(),
custom_properties: SmallVec::new(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct StyleDeclarationBlock {
pub declarations: SmallVec<[StyleDeclaration; 8]>,
pub importance: DeclarationImportance,
}
impl StyleDeclarationBlock {
fn from_parsed_declarations(declarations: ParsedDeclarations, important: bool) -> Self {
let mut block = Self::default();
block.append_parsed_declarations(declarations, important);
block
}
pub fn push(&mut self, declaration: StyleDeclaration, important: bool) {
if important {
self.importance.insert_declaration(&declaration);
}
self.declarations.push(declaration);
}
fn append_parsed_declarations(&mut self, declarations: ParsedDeclarations, important: bool) {
for declaration in declarations {
self.push(declaration, important);
}
}
pub fn append(&mut self, mut other: Self) {
self.importance.append(&mut other.importance);
self.declarations.extend(other.declarations);
}
pub fn iter(&self) -> std::slice::Iter<'_, StyleDeclaration> {
self.declarations.iter()
}
pub fn resource_urls(&self) -> impl Iterator<Item = &str> {
fn background_image_url(image: &BackgroundImage) -> Option<&str> {
if let BackgroundImage::Url(url) = image {
Some(url.as_ref())
} else {
None
}
}
self
.iter()
.flat_map(|declaration| -> Box<dyn Iterator<Item = &str> + '_> {
match declaration {
StyleDeclaration::BackgroundImage(Some(images))
| StyleDeclaration::MaskImage(Some(images)) => {
Box::new(images.iter().filter_map(background_image_url))
}
StyleDeclaration::Content(ContentValue::Items(items)) => {
Box::new(items.iter().filter_map(|item| match item {
ContentItem::Image(image) => background_image_url(image.as_ref()),
_ => None,
}))
}
_ => Box::new(std::iter::empty()),
}
})
}
pub fn parse<'i>(name: &str, input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
parse_style_declaration(name, input)
}
}
impl FromStr for StyleDeclarationBlock {
type Err = StyleDeclarationBlockParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let mut parser_input = ParserInput::new(input);
let mut parser = Parser::new(&mut parser_input);
let mut declaration_parser = StyleDeclarationParser;
let mut block = Self::default();
for result in RuleBodyParser::new(&mut parser, &mut declaration_parser) {
match result {
Ok(declarations) => block.append(declarations),
Err((error, context)) => {
return Err(StyleDeclarationBlockParseError::InvalidDeclarationBlock {
input: input.to_owned(),
context: context.to_owned(),
reason: format!("{error:?}"),
});
}
}
}
Ok(block)
}
}
pub(crate) fn to_kebab_case(s: &str) -> String {
let mut result = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i > 0 {
result.push('-');
}
result.push(c.to_ascii_lowercase());
} else {
result.push(c);
}
}
if result.starts_with("webkit-") {
result.insert(0, '-');
}
result
}
#[cfg(test)]
#[path = "stylesheets_tests.rs"]
mod tests;