#![warn(unused_must_use)]
use crate::DeclarationList;
use crate::Parser;
use crate::PrintErr;
use crate::Printer;
use crate::PropertyHandlerContext;
use crate::Result as CssResult;
use crate::SmallList;
use bun_alloc::ArenaVecExt as _;
use crate::css_properties::Property;
use crate::css_properties::PropertyId;
use crate::css_properties::masking;
use crate::css_values::easing::EasingFunction;
use crate::css_values::time::Time;
use crate::VendorPrefix;
use crate::compat;
use crate::prefixes::Feature;
#[derive(Clone, PartialEq)]
pub struct Transition {
pub property: PropertyId,
pub duration: Time,
pub delay: Time,
pub timing_function: EasingFunction,
}
impl Transition {
pub(crate) fn eql(&self, rhs: &Self) -> bool {
self == rhs
}
pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self {
self.clone()
}
pub(crate) fn parse(parser: &mut Parser) -> CssResult<Self> {
let mut property: Option<PropertyId> = None;
let mut duration: Option<Time> = None;
let mut delay: Option<Time> = None;
let mut timing_function: Option<EasingFunction> = None;
loop {
if duration.is_none() {
if let Ok(value) = parser.try_parse(Time::parse) {
duration = Some(value);
continue;
}
}
if timing_function.is_none() {
if let Ok(value) = parser.try_parse(EasingFunction::parse) {
timing_function = Some(value);
continue;
}
}
if delay.is_none() {
if let Ok(value) = parser.try_parse(Time::parse) {
delay = Some(value);
continue;
}
}
if property.is_none() {
if let Ok(value) = parser.try_parse(PropertyId::parse) {
property = Some(value);
continue;
}
}
break;
}
Ok(Self {
property: property.unwrap_or(PropertyId::All),
duration: duration.unwrap_or(Time::Seconds(0.0)),
delay: delay.unwrap_or(Time::Seconds(0.0)),
timing_function: timing_function.unwrap_or(EasingFunction::Ease),
})
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
self.property.to_css(dest)?;
if !self.duration.is_zero() || !self.delay.is_zero() {
dest.write_char(b' ')?;
self.duration.to_css(dest)?;
}
if !self.timing_function.is_ease() {
dest.write_char(b' ')?;
self.timing_function.to_css(dest)?;
}
if !self.delay.is_zero() {
dest.write_char(b' ')?;
self.delay.to_css(dest)?;
}
Ok(())
}
}
#[derive(Default)]
pub struct TransitionHandler {
pub properties: Option<(SmallList<PropertyId, 1>, VendorPrefix)>,
pub durations: Option<(SmallList<Time, 1>, VendorPrefix)>,
pub delays: Option<(SmallList<Time, 1>, VendorPrefix)>,
pub timing_functions: Option<(SmallList<EasingFunction, 1>, VendorPrefix)>,
pub has_any: bool,
}
macro_rules! handler_maybe_flush {
($this:expr, $dest:expr, $context:expr, $field:ident, $val:expr, $vp:expr) => {{
if let Some((v, prefixes)) = &$this.$field {
if !SmallList::eql($val, v) && !prefixes.contains($vp) {
$this.flush($dest, $context);
}
}
}};
}
macro_rules! handler_property {
($this:expr, $dest:expr, $context:expr, $arena:expr, $feature:expr, $field:ident, $val:expr, $vp:expr) => {{
handler_maybe_flush!($this, $dest, $context, $field, $val, $vp);
if let Some((v, prefixes)) = &mut $this.$field {
*v = $val.deep_clone($arena);
prefixes.insert($vp);
*prefixes = $context.targets.prefixes(*prefixes, $feature);
} else {
let prefixes = $context.targets.prefixes($vp, $feature);
let cloned_val = $val.deep_clone($arena);
$this.$field = Some((cloned_val, prefixes));
$this.has_any = true;
}
}};
}
mod transition_handler_body {
use super::*;
use crate::generics::CssEql as _;
use crate::generics::DeepClone;
impl TransitionHandler {
pub(crate) fn handle_property(
&mut self,
prop: &Property,
dest: &mut DeclarationList,
context: &mut PropertyHandlerContext,
) -> bool {
let arena = dest.bump();
match prop {
Property::TransitionProperty(x) => {
handler_property!(
self,
dest,
context,
arena,
Feature::TransitionProperty,
properties,
&x.0,
x.1
)
}
Property::TransitionDuration(x) => {
handler_property!(
self,
dest,
context,
arena,
Feature::TransitionDuration,
durations,
&x.0,
x.1
)
}
Property::TransitionDelay(x) => {
handler_property!(
self,
dest,
context,
arena,
Feature::TransitionDelay,
delays,
&x.0,
x.1
)
}
Property::TransitionTimingFunction(x) => {
handler_property!(
self,
dest,
context,
arena,
Feature::TransitionTimingFunction,
timing_functions,
&x.0,
x.1
)
}
Property::Transition(x) => {
let val: &SmallList<Transition, 1> = &x.0;
let vp: VendorPrefix = x.1;
let mut properties = SmallList::<PropertyId, 1>::init_capacity(val.len());
let mut durations = SmallList::<Time, 1>::init_capacity(val.len());
let mut delays = SmallList::<Time, 1>::init_capacity(val.len());
let mut timing_functions =
SmallList::<EasingFunction, 1>::init_capacity(val.len());
for item in val.slice() {
properties.append(item.property.deep_clone(arena));
}
handler_maybe_flush!(self, dest, context, properties, &properties, vp);
for item in val.slice() {
durations.append(item.duration.deep_clone(arena));
}
handler_maybe_flush!(self, dest, context, durations, &durations, vp);
for item in val.slice() {
delays.append(item.delay.deep_clone(arena));
}
handler_maybe_flush!(self, dest, context, delays, &delays, vp);
for item in val.slice() {
timing_functions.append(item.timing_function.deep_clone(arena));
}
handler_maybe_flush!(
self,
dest,
context,
timing_functions,
&timing_functions,
vp
);
handler_property!(
self,
dest,
context,
arena,
Feature::TransitionProperty,
properties,
&properties,
vp
);
handler_property!(
self,
dest,
context,
arena,
Feature::TransitionDuration,
durations,
&durations,
vp
);
handler_property!(
self,
dest,
context,
arena,
Feature::TransitionDelay,
delays,
&delays,
vp
);
handler_property!(
self,
dest,
context,
arena,
Feature::TransitionTimingFunction,
timing_functions,
&timing_functions,
vp
);
}
Property::Unparsed(x) => {
if is_transition_property(&x.property_id) {
self.flush(dest, context);
dest.push(Property::Unparsed(x.get_prefixed(
arena,
&context.targets,
Feature::Transition,
)));
} else {
return false;
}
}
_ => return false,
}
true
}
pub(crate) fn finalize(
&mut self,
dest: &mut DeclarationList,
context: &mut PropertyHandlerContext,
) {
self.flush(dest, context);
}
fn flush(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext) {
if !self.has_any {
return;
}
self.has_any = false;
let arena = dest.bump();
let mut _properties: Option<(SmallList<PropertyId, 1>, VendorPrefix)> =
self.properties.take();
let mut _durations: Option<(SmallList<Time, 1>, VendorPrefix)> = self.durations.take();
let mut _delays: Option<(SmallList<Time, 1>, VendorPrefix)> = self.delays.take();
let mut _timing_functions: Option<(SmallList<EasingFunction, 1>, VendorPrefix)> =
self.timing_functions.take();
let mut rtl_properties: Option<SmallList<PropertyId, 1>> =
if let Some(p) = &mut _properties {
expand_properties(&mut p.0, arena, context)
} else {
None
};
if let (
Some((properties, property_prefixes)),
Some((durations, duration_prefixes)),
Some((delays, delay_prefixes)),
Some((timing_functions, timing_prefixes)),
) = (
&mut _properties,
&mut _durations,
&mut _delays,
&mut _timing_functions,
) {
let intersection =
*property_prefixes & *duration_prefixes & *delay_prefixes & *timing_prefixes;
if !intersection.is_empty() {
let transitions =
get_transitions(arena, properties, durations, delays, timing_functions);
if let Some(rtl_properties2) = &mut rtl_properties {
let rtl_transitions = get_transitions(
arena,
rtl_properties2,
durations,
delays,
timing_functions,
);
context.add_logical_rule(
Property::Transition((transitions, intersection)),
Property::Transition((rtl_transitions, intersection)),
);
} else {
dest.push(Property::Transition((
transitions.deep_clone(arena),
intersection,
)));
}
property_prefixes.remove(intersection);
duration_prefixes.remove(intersection);
timing_prefixes.remove(intersection);
delay_prefixes.remove(intersection);
}
}
if let Some((properties, prefix)) = _properties.take() {
if !prefix.is_empty() {
if let Some(rtl_properties2) = rtl_properties.take() {
context.add_logical_rule(
Property::TransitionProperty((properties, prefix)),
Property::TransitionProperty((rtl_properties2, prefix)),
);
} else {
dest.push(Property::TransitionProperty((properties, prefix)));
}
}
}
if let Some((durations, prefix)) = _durations.take() {
if !prefix.is_empty() {
dest.push(Property::TransitionDuration((durations, prefix)));
}
}
if let Some((delays, prefix)) = _delays.take() {
if !prefix.is_empty() {
dest.push(Property::TransitionDelay((delays, prefix)));
}
}
if let Some((timing_functions, prefix)) = _timing_functions.take() {
if !prefix.is_empty() {
dest.push(Property::TransitionTimingFunction((
timing_functions,
prefix,
)));
}
}
self.reset();
}
pub(crate) fn reset(&mut self) {
self.properties = None;
self.durations = None;
self.delays = None;
self.timing_functions = None;
self.has_any = false;
}
}
#[inline]
fn get_transitions(
arena: &bun_alloc::Arena,
properties: &mut SmallList<PropertyId, 1>,
durations: &mut SmallList<Time, 1>,
delays: &mut SmallList<Time, 1>,
timing_functions: &mut SmallList<EasingFunction, 1>,
) -> SmallList<Transition, 1> {
#[inline]
fn cycle_bump(idx: &mut u32, len: u32) {
*idx = (*idx + 1) % len;
}
let mut transitions = SmallList::<Transition, 1>::init_capacity(1);
let mut durations_idx: u32 = 0;
let mut delays_idx: u32 = 0;
let mut timing_idx: u32 = 0;
for property_id in properties.slice() {
let duration = if durations.len() > durations_idx {
durations.at(durations_idx).deep_clone(arena)
} else {
Time::Seconds(0.0)
};
let delay = if delays.len() > delays_idx {
delays.at(delays_idx).deep_clone(arena)
} else {
Time::Seconds(0.0)
};
let timing_function = if timing_functions.len() > timing_idx {
timing_functions.at(timing_idx).deep_clone(arena)
} else {
EasingFunction::Ease
};
cycle_bump(&mut durations_idx, durations.len());
cycle_bump(&mut delays_idx, delays.len());
cycle_bump(&mut timing_idx, timing_functions.len());
let transition = Transition {
property: property_id.deep_clone(arena),
duration,
delay,
timing_function,
};
let mut cloned = false;
let prefix_to_iter = property_id.prefix().or_none();
for &prefix_flag in VendorPrefix::FIELDS {
if prefix_to_iter.contains(prefix_flag) {
let mut t = if cloned {
transition.deep_clone(arena)
} else {
transition.deep_clone(arena)
};
cloned = true;
t.property = property_id.with_prefix(prefix_flag);
transitions.append(t);
}
}
let _ = cloned;
}
transitions
}
fn expand_properties(
properties: &mut SmallList<PropertyId, 1>,
arena: &bun_alloc::Arena,
context: &mut PropertyHandlerContext,
) -> Option<SmallList<PropertyId, 1>> {
#[inline]
fn replace(
arena: &bun_alloc::Arena,
propertiez: &mut SmallList<PropertyId, 1>,
props: &[PropertyId],
i: u32,
) {
propertiez.slice_mut()[i as usize] = props[0].deep_clone(arena);
if props.len() > 1 {
propertiez.insert_slice(i + 1, &props[1..]);
}
}
let mut rtl_properties: Option<SmallList<PropertyId, 1>> = None;
let mut i: u32 = 0;
while i < properties.len() {
let result = get_logical_properties(properties.at(i));
match result {
LogicalPropertyId::Block(feature, block)
if context.should_compile_logical(feature) =>
{
replace(arena, properties, block, i);
if let Some(rtl) = &mut rtl_properties {
replace(arena, rtl, block, i);
}
i += 1;
}
LogicalPropertyId::Inline(feature, ltr, rtl)
if context.should_compile_logical(feature) =>
{
if rtl_properties.is_none() {
rtl_properties = Some(properties.deep_clone(arena));
}
replace(arena, properties, ltr, i);
if let Some(rtl_props) = &mut rtl_properties {
replace(arena, rtl_props, rtl, i);
}
i += u32::try_from(ltr.len()).expect("int cast");
}
_ => {
let index = i;
properties.slice_mut()[index as usize]
.set_prefixes_for_targets(&context.targets);
if let Some(property_id) =
masking::get_webkit_mask_property(properties.at(index))
{
if context
.targets
.prefixes(VendorPrefix::NONE, Feature::MaskBorder)
.contains(VendorPrefix::WEBKIT)
{
properties.insert(index, property_id);
i += 1;
}
}
if let Some(rtl_props) = &mut rtl_properties {
rtl_props.slice_mut()[index as usize]
.set_prefixes_for_targets(&context.targets);
if let Some(property_id) =
masking::get_webkit_mask_property(rtl_props.at(index))
{
if context
.targets
.prefixes(VendorPrefix::NONE, Feature::MaskBorder)
.contains(VendorPrefix::WEBKIT)
{
rtl_props.insert(index, property_id);
}
}
}
i += 1;
}
}
}
rtl_properties
}
enum LogicalPropertyId {
None,
Block(compat::Feature, &'static [PropertyId]),
Inline(
compat::Feature,
&'static [PropertyId],
&'static [PropertyId],
),
}
fn get_logical_properties(property_id: &PropertyId) -> LogicalPropertyId {
use LogicalPropertyId::{Block, Inline};
use compat::Feature as F;
match property_id {
PropertyId::BlockSize => Block(F::LogicalSize, &[PropertyId::Height]),
PropertyId::InlineSize => {
Inline(F::LogicalSize, &[PropertyId::Width], &[PropertyId::Height])
}
PropertyId::MinBlockSize => Block(F::LogicalSize, &[PropertyId::MinHeight]),
PropertyId::MaxBlockSize => Block(F::LogicalSize, &[PropertyId::MaxHeight]),
PropertyId::MinInlineSize => Inline(
F::LogicalSize,
&[PropertyId::MinWidth],
&[PropertyId::MinHeight],
),
PropertyId::MaxInlineSize => Inline(
F::LogicalSize,
&[PropertyId::MaxWidth],
&[PropertyId::MaxHeight],
),
PropertyId::InsetBlockStart => Block(F::LogicalInset, &[PropertyId::Top]),
PropertyId::InsetBlockEnd => Block(F::LogicalInset, &[PropertyId::Bottom]),
PropertyId::InsetInlineStart => {
Inline(F::LogicalInset, &[PropertyId::Left], &[PropertyId::Right])
}
PropertyId::InsetInlineEnd => {
Inline(F::LogicalInset, &[PropertyId::Right], &[PropertyId::Left])
}
PropertyId::InsetBlock => {
Block(F::LogicalInset, &[PropertyId::Top, PropertyId::Bottom])
}
PropertyId::InsetInline => {
Block(F::LogicalInset, &[PropertyId::Left, PropertyId::Right])
}
PropertyId::Inset => Block(
F::LogicalInset,
&[
PropertyId::Top,
PropertyId::Bottom,
PropertyId::Left,
PropertyId::Right,
],
),
PropertyId::MarginBlockStart => Block(F::LogicalMargin, &[PropertyId::MarginTop]),
PropertyId::MarginBlockEnd => Block(F::LogicalMargin, &[PropertyId::MarginBottom]),
PropertyId::MarginInlineStart => Inline(
F::LogicalMargin,
&[PropertyId::MarginLeft],
&[PropertyId::MarginRight],
),
PropertyId::MarginInlineEnd => Inline(
F::LogicalMargin,
&[PropertyId::MarginRight],
&[PropertyId::MarginLeft],
),
PropertyId::MarginBlock => Block(
F::LogicalMargin,
&[PropertyId::MarginTop, PropertyId::MarginBottom],
),
PropertyId::MarginInline => Block(
F::LogicalMargin,
&[PropertyId::MarginLeft, PropertyId::MarginRight],
),
PropertyId::PaddingBlockStart => Block(F::LogicalPadding, &[PropertyId::PaddingTop]),
PropertyId::PaddingBlockEnd => Block(F::LogicalPadding, &[PropertyId::PaddingBottom]),
PropertyId::PaddingInlineStart => Inline(
F::LogicalPadding,
&[PropertyId::PaddingLeft],
&[PropertyId::PaddingRight],
),
PropertyId::PaddingInlineEnd => Inline(
F::LogicalPadding,
&[PropertyId::PaddingRight],
&[PropertyId::PaddingLeft],
),
PropertyId::PaddingBlock => Block(
F::LogicalPadding,
&[PropertyId::PaddingTop, PropertyId::PaddingBottom],
),
PropertyId::PaddingInline => Block(
F::LogicalPadding,
&[PropertyId::PaddingLeft, PropertyId::PaddingRight],
),
PropertyId::BorderBlockStart => Block(F::LogicalBorders, &[PropertyId::BorderTop]),
PropertyId::BorderBlockStartWidth => {
Block(F::LogicalBorders, &[PropertyId::BorderTopWidth])
}
PropertyId::BorderBlockStartColor => {
Block(F::LogicalBorders, &[PropertyId::BorderTopColor])
}
PropertyId::BorderBlockStartStyle => {
Block(F::LogicalBorders, &[PropertyId::BorderTopStyle])
}
PropertyId::BorderBlockEnd => Block(F::LogicalBorders, &[PropertyId::BorderBottom]),
PropertyId::BorderBlockEndWidth => {
Block(F::LogicalBorders, &[PropertyId::BorderBottomWidth])
}
PropertyId::BorderBlockEndColor => {
Block(F::LogicalBorders, &[PropertyId::BorderBottomColor])
}
PropertyId::BorderBlockEndStyle => {
Block(F::LogicalBorders, &[PropertyId::BorderBottomStyle])
}
PropertyId::BorderInlineStart => Inline(
F::LogicalBorders,
&[PropertyId::BorderLeft],
&[PropertyId::BorderRight],
),
PropertyId::BorderInlineStartWidth => Inline(
F::LogicalBorders,
&[PropertyId::BorderLeftWidth],
&[PropertyId::BorderRightWidth],
),
PropertyId::BorderInlineStartColor => Inline(
F::LogicalBorders,
&[PropertyId::BorderLeftColor],
&[PropertyId::BorderRightColor],
),
PropertyId::BorderInlineStartStyle => Inline(
F::LogicalBorders,
&[PropertyId::BorderLeftStyle],
&[PropertyId::BorderRightStyle],
),
PropertyId::BorderInlineEnd => Inline(
F::LogicalBorders,
&[PropertyId::BorderRight],
&[PropertyId::BorderLeft],
),
PropertyId::BorderInlineEndWidth => Inline(
F::LogicalBorders,
&[PropertyId::BorderRightWidth],
&[PropertyId::BorderLeftWidth],
),
PropertyId::BorderInlineEndColor => Inline(
F::LogicalBorders,
&[PropertyId::BorderRightColor],
&[PropertyId::BorderLeftColor],
),
PropertyId::BorderInlineEndStyle => Inline(
F::LogicalBorders,
&[PropertyId::BorderRightStyle],
&[PropertyId::BorderLeftStyle],
),
PropertyId::BorderBlock => Block(
F::LogicalBorders,
&[PropertyId::BorderTop, PropertyId::BorderBottom],
),
PropertyId::BorderBlockColor => Block(
F::LogicalBorders,
&[PropertyId::BorderTopColor, PropertyId::BorderBottomColor],
),
PropertyId::BorderBlockWidth => Block(
F::LogicalBorders,
&[PropertyId::BorderTopWidth, PropertyId::BorderBottomWidth],
),
PropertyId::BorderBlockStyle => Block(
F::LogicalBorders,
&[PropertyId::BorderTopStyle, PropertyId::BorderBottomStyle],
),
PropertyId::BorderInline => Block(
F::LogicalBorders,
&[PropertyId::BorderLeft, PropertyId::BorderRight],
),
PropertyId::BorderInlineColor => Block(
F::LogicalBorders,
&[PropertyId::BorderLeftColor, PropertyId::BorderRightColor],
),
PropertyId::BorderInlineWidth => Block(
F::LogicalBorders,
&[PropertyId::BorderLeftWidth, PropertyId::BorderRightWidth],
),
PropertyId::BorderInlineStyle => Block(
F::LogicalBorders,
&[PropertyId::BorderLeftStyle, PropertyId::BorderRightStyle],
),
PropertyId::BorderStartStartRadius => Inline(
F::LogicalBorders,
&[PropertyId::BorderTopLeftRadius(VendorPrefix::NONE)],
&[PropertyId::BorderTopRightRadius(VendorPrefix::NONE)],
),
PropertyId::BorderStartEndRadius => Inline(
F::LogicalBorders,
&[PropertyId::BorderTopRightRadius(VendorPrefix::NONE)],
&[PropertyId::BorderTopLeftRadius(VendorPrefix::NONE)],
),
PropertyId::BorderEndStartRadius => Inline(
F::LogicalBorders,
&[PropertyId::BorderBottomLeftRadius(VendorPrefix::NONE)],
&[PropertyId::BorderBottomRightRadius(VendorPrefix::NONE)],
),
PropertyId::BorderEndEndRadius => Inline(
F::LogicalBorders,
&[PropertyId::BorderBottomRightRadius(VendorPrefix::NONE)],
&[PropertyId::BorderBottomLeftRadius(VendorPrefix::NONE)],
),
_ => LogicalPropertyId::None,
}
}
fn is_transition_property(property_id: &PropertyId) -> bool {
matches!(
property_id,
PropertyId::TransitionProperty(..)
| PropertyId::TransitionDuration(..)
| PropertyId::TransitionDelay(..)
| PropertyId::TransitionTimingFunction(..)
| PropertyId::Transition(..)
)
}
}