diff options
Diffstat (limited to 'servo/components/style/properties/shorthands/ui.mako.rs')
-rw-r--r-- | servo/components/style/properties/shorthands/ui.mako.rs | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/servo/components/style/properties/shorthands/ui.mako.rs b/servo/components/style/properties/shorthands/ui.mako.rs new file mode 100644 index 0000000000..02bed12806 --- /dev/null +++ b/servo/components/style/properties/shorthands/ui.mako.rs @@ -0,0 +1,427 @@ +/* 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/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +macro_rules! try_parse_one { + ($context: expr, $input: expr, $var: ident, $prop_module: ident) => { + if $var.is_none() { + if let Ok(value) = $input.try_parse(|i| { + $prop_module::single_value::parse($context, i) + }) { + $var = Some(value); + continue; + } + } + }; +} + +<%helpers:shorthand name="transition" + engines="gecko servo-2013 servo-2020" + extra_prefixes="moz:layout.css.prefixes.transitions webkit" + sub_properties="transition-property transition-duration + transition-timing-function + transition-delay" + spec="https://drafts.csswg.org/css-transitions/#propdef-transition"> + use crate::parser::Parse; + % for prop in "delay duration property timing_function".split(): + use crate::properties::longhands::transition_${prop}; + % endfor + use crate::values::specified::TransitionProperty; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + struct SingleTransition { + % for prop in "duration timing_function delay".split(): + transition_${prop}: transition_${prop}::SingleSpecifiedValue, + % endfor + // Unlike other properties, transition-property uses an Option<> to + // represent 'none' as `None`. + transition_property: Option<TransitionProperty>, + } + + fn parse_one_transition<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SingleTransition,ParseError<'i>> { + % for prop in "property duration timing_function delay".split(): + let mut ${prop} = None; + % endfor + + let mut parsed = 0; + loop { + parsed += 1; + + try_parse_one!(context, input, duration, transition_duration); + try_parse_one!(context, input, timing_function, transition_timing_function); + try_parse_one!(context, input, delay, transition_delay); + // Must check 'transition-property' after 'transition-timing-function' since + // 'transition-property' accepts any keyword. + if property.is_none() { + if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) { + property = Some(Some(value)); + continue; + } + + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + // 'none' is not a valid value for <single-transition-property>, + // so it's not acceptable in the function above. + property = Some(None); + continue; + } + } + + parsed -= 1; + break + } + + if parsed != 0 { + Ok(SingleTransition { + % for prop in "duration timing_function delay".split(): + transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value + ::get_initial_specified_value), + % endfor + transition_property: property.unwrap_or( + Some(transition_property::single_value::get_initial_specified_value())), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + % for prop in "property duration timing_function delay".split(): + let mut ${prop}s = Vec::new(); + % endfor + + let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?; + let multiple_items = results.len() >= 2; + for result in results { + if let Some(value) = result.transition_property { + propertys.push(value); + } else if multiple_items { + // If there is more than one item, and any of transitions has 'none', + // then it's invalid. Othersize, leave propertys to be empty (which + // means "transition-property: none"); + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + % for prop in "duration timing_function delay".split(): + ${prop}s.push(result.transition_${prop}); + % endfor + } + + Ok(expanded! { + % for prop in "property duration timing_function delay".split(): + transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let property_len = self.transition_property.0.len(); + + // There are two cases that we can do shorthand serialization: + // * when all value lists have the same length, or + // * when transition-property is none, and other value lists have exactly one item. + if property_len == 0 { + % for name in "duration delay timing_function".split(): + if self.transition_${name}.0.len() != 1 { + return Ok(()); + } + % endfor + } else { + % for name in "duration delay timing_function".split(): + if self.transition_${name}.0.len() != property_len { + return Ok(()); + } + % endfor + } + + // Representative length. + let len = self.transition_duration.0.len(); + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + if property_len == 0 { + dest.write_str("none")?; + } else { + self.transition_property.0[i].to_css(dest)?; + } + % for name in "duration timing_function delay".split(): + dest.write_char(' ')?; + self.transition_${name}.0[i].to_css(dest)?; + % endfor + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand name="animation" + engines="gecko servo-2013 servo-2020" + extra_prefixes="moz:layout.css.prefixes.animations webkit" + sub_properties="animation-name animation-duration + animation-timing-function animation-delay + animation-iteration-count animation-direction + animation-fill-mode animation-play-state animation-timeline" + rule_types_allowed="Style" + spec="https://drafts.csswg.org/css-animations/#propdef-animation"> + <% + props = "name timeline duration timing_function delay iteration_count \ + direction fill_mode play_state".split() + %> + % for prop in props: + use crate::properties::longhands::animation_${prop}; + % endfor + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + struct SingleAnimation { + % for prop in props: + animation_${prop}: animation_${prop}::SingleSpecifiedValue, + % endfor + } + + fn parse_one_animation<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SingleAnimation, ParseError<'i>> { + % for prop in props: + let mut ${prop} = None; + % endfor + + let mut parsed = 0; + // NB: Name must be the last one here so that keywords valid for other + // longhands are not interpreted as names. + // + // Also, duration must be before delay, see + // https://drafts.csswg.org/css-animations/#typedef-single-animation + loop { + parsed += 1; + try_parse_one!(context, input, duration, animation_duration); + try_parse_one!(context, input, timing_function, animation_timing_function); + try_parse_one!(context, input, delay, animation_delay); + try_parse_one!(context, input, iteration_count, animation_iteration_count); + try_parse_one!(context, input, direction, animation_direction); + try_parse_one!(context, input, fill_mode, animation_fill_mode); + try_parse_one!(context, input, play_state, animation_play_state); + try_parse_one!(context, input, name, animation_name); + if static_prefs::pref!("layout.css.scroll-driven-animations.enabled") { + try_parse_one!(context, input, timeline, animation_timeline); + } + + parsed -= 1; + break + } + + // If nothing is parsed, this is an invalid entry. + if parsed == 0 { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } else { + Ok(SingleAnimation { + % for prop in props: + animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value + ::get_initial_specified_value), + % endfor + }) + } + } + + % for prop in props: + let mut ${prop}s = vec![]; + % endfor + + let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?; + for result in results.into_iter() { + % for prop in props: + ${prop}s.push(result.animation_${prop}); + % endfor + } + + Ok(expanded! { + % for prop in props: + animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let len = self.animation_name.0.len(); + // There should be at least one declared value + if len == 0 { + return Ok(()); + } + + // If any value list length is differs then we don't do a shorthand serialization + // either. + % for name in props[2:]: + if len != self.animation_${name}.0.len() { + return Ok(()) + } + % endfor + + // If the preference of animation-timeline is disabled, `self.animation_timeline` is + // None. + if self.animation_timeline.map_or(false, |v| len != v.0.len()) { + return Ok(()); + } + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + + % for name in props[2:]: + self.animation_${name}.0[i].to_css(dest)?; + dest.write_char(' ')?; + % endfor + + self.animation_name.0[i].to_css(dest)?; + + // Based on the spec, the default values of other properties must be output in at + // least the cases necessary to distinguish an animation-name. The serialization + // order of animation-timeline is always later than animation-name, so it's fine + // to not serialize it if it is the default value. It's still possible to + // distinguish them (because we always serialize animation-name). + // https://drafts.csswg.org/css-animations-1/#animation + // https://drafts.csswg.org/css-animations-2/#typedef-single-animation + // + // Note: it's also fine to always serialize this. However, it seems Blink + // doesn't serialize default animation-timeline now, so we follow the same rule. + if let Some(ref timeline) = self.animation_timeline { + if !timeline.0[i].is_auto() { + dest.write_char(' ')?; + timeline.0[i].to_css(dest)?; + } + } + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + engines="gecko" + name="scroll-timeline" + sub_properties="scroll-timeline-name scroll-timeline-axis" + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-shorthand" +> + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::properties::longhands::{scroll_timeline_axis, scroll_timeline_name}; + + let mut names = Vec::with_capacity(1); + let mut axes = Vec::with_capacity(1); + input.parse_comma_separated(|input| { + let name = scroll_timeline_name::single_value::parse(context, input)?; + let axis = input.try_parse(|i| scroll_timeline_axis::single_value::parse(context, i)); + + names.push(name); + axes.push(axis.unwrap_or_default()); + + Ok(()) + })?; + + Ok(expanded! { + scroll_timeline_name: scroll_timeline_name::SpecifiedValue(names.into()), + scroll_timeline_axis: scroll_timeline_axis::SpecifiedValue(axes.into()), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + // If any value list length is differs then we don't do a shorthand serialization + // either. + let len = self.scroll_timeline_name.0.len(); + if len != self.scroll_timeline_axis.0.len() { + return Ok(()); + } + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + + self.scroll_timeline_name.0[i].to_css(dest)?; + + if self.scroll_timeline_axis.0[i] != Default::default() { + dest.write_char(' ')?; + self.scroll_timeline_axis.0[i].to_css(dest)?; + } + + } + Ok(()) + } + } +</%helpers:shorthand> + +// Note: view-timeline shorthand doesn't take view-timeline-inset into account. +<%helpers:shorthand + engines="gecko" + name="view-timeline" + sub_properties="view-timeline-name view-timeline-axis" + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-shorthand" +> + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::properties::longhands::{view_timeline_axis, view_timeline_name}; + + let mut names = Vec::with_capacity(1); + let mut axes = Vec::with_capacity(1); + input.parse_comma_separated(|input| { + let name = view_timeline_name::single_value::parse(context, input)?; + let axis = input.try_parse(|i| view_timeline_axis::single_value::parse(context, i)); + + names.push(name); + axes.push(axis.unwrap_or_default()); + + Ok(()) + })?; + + Ok(expanded! { + view_timeline_name: view_timeline_name::SpecifiedValue(names.into()), + view_timeline_axis: view_timeline_axis::SpecifiedValue(axes.into()), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + // If any value list length is differs then we don't do a shorthand serialization + // either. + let len = self.view_timeline_name.0.len(); + if len != self.view_timeline_axis.0.len() { + return Ok(()); + } + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + + self.view_timeline_name.0[i].to_css(dest)?; + + if self.view_timeline_axis.0[i] != Default::default() { + dest.write_char(' ')?; + self.view_timeline_axis.0[i].to_css(dest)?; + } + + } + Ok(()) + } + } +</%helpers:shorthand> |