ui.mako.rs (21601B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 <%namespace name="helpers" file="/helpers.mako.rs" /> 6 7 macro_rules! try_parse_one { 8 ($context: expr, $input: expr, $var: ident, $prop_module: ident) => { 9 if $var.is_none() { 10 if let Ok(value) = $input.try_parse(|i| { 11 $prop_module::single_value::parse($context, i) 12 }) { 13 $var = Some(value); 14 continue; 15 } 16 } 17 }; 18 } 19 20 <%helpers:shorthand name="transition" 21 engines="gecko servo" 22 extra_prefixes="moz:layout.css.prefixes.transitions webkit" 23 sub_properties="transition-property transition-duration 24 transition-timing-function 25 transition-delay transition-behavior" 26 spec="https://drafts.csswg.org/css-transitions/#propdef-transition"> 27 use crate::parser::Parse; 28 % for prop in "delay duration property timing_function behavior".split(): 29 use crate::properties::longhands::transition_${prop}; 30 % endfor 31 use crate::values::specified::TransitionProperty; 32 33 pub fn parse_value<'i, 't>( 34 context: &ParserContext, 35 input: &mut Parser<'i, 't>, 36 ) -> Result<Longhands, ParseError<'i>> { 37 struct SingleTransition { 38 % for prop in "property duration timing_function delay behavior".split(): 39 transition_${prop}: transition_${prop}::SingleSpecifiedValue, 40 % endfor 41 } 42 43 fn parse_one_transition<'i, 't>( 44 context: &ParserContext, 45 input: &mut Parser<'i, 't>, 46 first: bool, 47 ) -> Result<SingleTransition,ParseError<'i>> { 48 % for prop in "property duration timing_function delay behavior".split(): 49 let mut ${prop} = None; 50 % endfor 51 52 let mut parsed = 0; 53 loop { 54 parsed += 1; 55 56 try_parse_one!(context, input, duration, transition_duration); 57 try_parse_one!(context, input, timing_function, transition_timing_function); 58 try_parse_one!(context, input, delay, transition_delay); 59 try_parse_one!(context, input, behavior, transition_behavior); 60 // Must check 'transition-property' after 'transition-timing-function' since 61 // 'transition-property' accepts any keyword. 62 if property.is_none() { 63 if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) { 64 property = Some(value); 65 continue; 66 } 67 68 // 'none' is not a valid value for <single-transition-property>, 69 // so it's only acceptable as the first item. 70 if first && input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { 71 property = Some(TransitionProperty::none()); 72 continue; 73 } 74 } 75 76 parsed -= 1; 77 break 78 } 79 80 if parsed != 0 { 81 Ok(SingleTransition { 82 % for prop in "property duration timing_function delay behavior".split(): 83 transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value 84 ::get_initial_specified_value), 85 % endfor 86 }) 87 } else { 88 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 89 } 90 } 91 92 % for prop in "property duration timing_function delay behavior".split(): 93 let mut ${prop}s = Vec::new(); 94 % endfor 95 96 let mut first = true; 97 let mut has_transition_property_none = false; 98 let results = input.parse_comma_separated(|i| { 99 if has_transition_property_none { 100 // If you specify transition-property: none, multiple items are invalid. 101 return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 102 } 103 let transition = parse_one_transition(context, i, first)?; 104 first = false; 105 has_transition_property_none = transition.transition_property.is_none(); 106 Ok(transition) 107 })?; 108 for result in results { 109 % for prop in "property duration timing_function delay behavior".split(): 110 ${prop}s.push(result.transition_${prop}); 111 % endfor 112 } 113 114 Ok(expanded! { 115 % for prop in "property duration timing_function delay behavior".split(): 116 transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()), 117 % endfor 118 }) 119 } 120 121 impl<'a> ToCss for LonghandsToSerialize<'a> { 122 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 123 use crate::Zero; 124 use style_traits::values::SequenceWriter; 125 126 let property_len = self.transition_property.0.len(); 127 128 // There are two cases that we can do shorthand serialization: 129 // * when all value lists have the same length, or 130 // * when transition-property is none, and other value lists have exactly one item. 131 if property_len == 0 { 132 % for name in "duration delay timing_function".split(): 133 if self.transition_${name}.0.len() != 1 { 134 return Ok(()); 135 } 136 % endfor 137 138 if self.transition_behavior.0.len() != 1 { 139 return Ok(()); 140 } 141 } else { 142 % for name in "duration delay timing_function".split(): 143 if self.transition_${name}.0.len() != property_len { 144 return Ok(()); 145 } 146 % endfor 147 148 if self.transition_behavior.0.len() != property_len { 149 return Ok(()); 150 } 151 } 152 153 // Representative length. 154 let len = self.transition_duration.0.len(); 155 156 for i in 0..len { 157 if i != 0 { 158 dest.write_str(", ")?; 159 } 160 161 let has_duration = !self.transition_duration.0[i].is_zero(); 162 let has_timing = !self.transition_timing_function.0[i].is_ease(); 163 let has_delay = !self.transition_delay.0[i].is_zero(); 164 let has_behavior = !self.transition_behavior.0[i].is_normal(); 165 let has_any = has_duration || has_timing || has_delay || has_behavior; 166 167 let mut writer = SequenceWriter::new(dest, " "); 168 169 if property_len == 0 { 170 writer.raw_item("none")?; 171 } else if !self.transition_property.0[i].is_all() || !has_any { 172 writer.item(&self.transition_property.0[i])?; 173 } 174 175 // In order to avoid ambiguity, we have to serialize duration if we have delay. 176 if has_duration || has_delay { 177 writer.item(&self.transition_duration.0[i])?; 178 } 179 180 if has_timing { 181 writer.item(&self.transition_timing_function.0[i])?; 182 } 183 184 if has_delay { 185 writer.item(&self.transition_delay.0[i])?; 186 } 187 188 if has_behavior { 189 writer.item(&self.transition_behavior.0[i])?; 190 } 191 } 192 Ok(()) 193 } 194 } 195 </%helpers:shorthand> 196 197 <%helpers:shorthand name="animation" 198 engines="gecko servo" 199 extra_prefixes="moz:layout.css.prefixes.animations webkit" 200 sub_properties="animation-name animation-duration 201 animation-timing-function animation-delay 202 animation-iteration-count animation-direction 203 animation-fill-mode animation-play-state animation-timeline" 204 rule_types_allowed="Style" 205 spec="https://drafts.csswg.org/css-animations/#propdef-animation"> 206 <% 207 props = "name duration timing_function delay iteration_count \ 208 direction fill_mode play_state".split() 209 %> 210 % for prop in props: 211 use crate::properties::longhands::animation_${prop}; 212 % endfor 213 use crate::properties::longhands::animation_timeline; 214 215 pub fn parse_value<'i, 't>( 216 context: &ParserContext, 217 input: &mut Parser<'i, 't>, 218 ) -> Result<Longhands, ParseError<'i>> { 219 struct SingleAnimation { 220 % for prop in props: 221 animation_${prop}: animation_${prop}::SingleSpecifiedValue, 222 % endfor 223 } 224 225 fn parse_one_animation<'i, 't>( 226 context: &ParserContext, 227 input: &mut Parser<'i, 't>, 228 ) -> Result<SingleAnimation, ParseError<'i>> { 229 % for prop in props: 230 let mut ${prop} = None; 231 % endfor 232 233 let mut parsed = 0; 234 // NB: Name must be the last one here so that keywords valid for other 235 // longhands are not interpreted as names. 236 // 237 // Also, duration must be before delay, see 238 // https://drafts.csswg.org/css-animations/#typedef-single-animation 239 loop { 240 parsed += 1; 241 try_parse_one!(context, input, duration, animation_duration); 242 try_parse_one!(context, input, timing_function, animation_timing_function); 243 try_parse_one!(context, input, delay, animation_delay); 244 try_parse_one!(context, input, iteration_count, animation_iteration_count); 245 try_parse_one!(context, input, direction, animation_direction); 246 try_parse_one!(context, input, fill_mode, animation_fill_mode); 247 try_parse_one!(context, input, play_state, animation_play_state); 248 try_parse_one!(context, input, name, animation_name); 249 250 // Note: per spec issue discussion, all animation longhands not defined in 251 // Animations 1 are defined as reset-only sub-properties for now. 252 // https://github.com/w3c/csswg-drafts/issues/6946#issuecomment-1233190360 253 // 254 // FIXME: Bug 1824261. We should revisit this when the spec gets updated with the 255 // new syntax. 256 // https://github.com/w3c/csswg-drafts/issues/6946 257 258 parsed -= 1; 259 break 260 } 261 262 // If nothing is parsed, this is an invalid entry. 263 if parsed == 0 { 264 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 265 } else { 266 Ok(SingleAnimation { 267 % for prop in props: 268 animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value 269 ::get_initial_specified_value), 270 % endfor 271 }) 272 } 273 } 274 275 % for prop in props: 276 let mut ${prop}s = vec![]; 277 % endfor 278 279 let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?; 280 for result in results.into_iter() { 281 % for prop in props: 282 ${prop}s.push(result.animation_${prop}); 283 % endfor 284 } 285 286 Ok(expanded! { 287 % for prop in props: 288 animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()), 289 % endfor 290 // FIXME: Bug 1824261. animation-timeline is reset-only for now. 291 animation_timeline: animation_timeline::SpecifiedValue( 292 vec![animation_timeline::single_value::get_initial_specified_value()].into() 293 ), 294 }) 295 } 296 297 impl<'a> ToCss for LonghandsToSerialize<'a> { 298 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 299 use crate::values::specified::easing::TimingFunction; 300 use crate::values::specified::{ 301 AnimationDirection, AnimationFillMode, AnimationPlayState, 302 }; 303 use crate::Zero; 304 use style_traits::values::SequenceWriter; 305 306 let len = self.animation_name.0.len(); 307 // There should be at least one declared value 308 if len == 0 { 309 return Ok(()); 310 } 311 312 // If any value list length is differs then we don't do a shorthand serialization 313 // either. 314 % for name in props[1:]: 315 if len != self.animation_${name}.0.len() { 316 return Ok(()) 317 } 318 % endfor 319 320 // FIXME: Bug 1824261. We don't serialize this shorthand if the animation-timeline is 321 // speficied, per the wpt update: https://github.com/web-platform-tests/wpt/pull/38848. 322 if self.animation_timeline.map_or(false, |v| v.0.len() != 1 || !v.0[0].is_auto()) { 323 return Ok(()); 324 } 325 326 for i in 0..len { 327 if i != 0 { 328 dest.write_str(", ")?; 329 } 330 331 // We follow the order of this syntax: 332 // <single-animation> = 333 // <animation-duration> || 334 // <easing-function> || 335 // <animation-delay> || 336 // <single-animation-iteration-count> || 337 // <single-animation-direction> || 338 // <single-animation-fill-mode> || 339 // <single-animation-play-state> || 340 // [ none | <keyframes-name> ] || 341 // <single-animation-timeline> 342 // 343 // https://drafts.csswg.org/css-animations-2/#animation-shorthand 344 // 345 // Note: animation-timeline is not serialized for now because it is always the 346 // initial value in this loop. Therefore, animation-duration is always resolved as 347 // 0s if it is auto because animation-timeline is the initial value, i.e. 348 // time-driven animations. In conclusion, we don't serialize animation-duration if 349 // it is auto (for specified value) or if it is 0s (for resolved value). 350 // https://drafts.csswg.org/css-animations-2/#animation-duration 351 let has_duration = !self.animation_duration.0[i].is_auto() 352 && !self.animation_duration.0[i].is_zero(); 353 let has_timing_function = !self.animation_timing_function.0[i].is_ease(); 354 let has_delay = !self.animation_delay.0[i].is_zero(); 355 let has_iteration_count = !self.animation_iteration_count.0[i].is_one(); 356 let has_direction = 357 !matches!(self.animation_direction.0[i], AnimationDirection::Normal); 358 let has_fill_mode = 359 !matches!(self.animation_fill_mode.0[i], AnimationFillMode::None); 360 let has_play_state = 361 !matches!(self.animation_play_state.0[i], AnimationPlayState::Running); 362 let animation_name = &self.animation_name.0[i]; 363 let has_name = !animation_name.is_none(); 364 365 let mut writer = SequenceWriter::new(dest, " "); 366 367 // To avoid ambiguity, we have to serialize duration if duration is initial 368 // but delay is not. (In other words, it's ambiguous if we serialize delay only.) 369 if has_duration || has_delay { 370 writer.item(&self.animation_duration.0[i])?; 371 } 372 373 if has_timing_function || TimingFunction::match_keywords(animation_name) { 374 writer.item(&self.animation_timing_function.0[i])?; 375 } 376 377 // For animation-delay and animation-iteration-count. 378 % for name in props[3:5]: 379 if has_${name} { 380 writer.item(&self.animation_${name}.0[i])?; 381 } 382 % endfor 383 384 if has_direction || AnimationDirection::match_keywords(animation_name) { 385 writer.item(&self.animation_direction.0[i])?; 386 } 387 388 if has_fill_mode || AnimationFillMode::match_keywords(animation_name) { 389 writer.item(&self.animation_fill_mode.0[i])?; 390 } 391 392 if has_play_state || AnimationPlayState::match_keywords(animation_name) { 393 writer.item(&self.animation_play_state.0[i])?; 394 } 395 396 // If all values are initial, we must serialize animation-name. 397 let has_any = { 398 has_duration 399 % for name in props[2:]: 400 || has_${name} 401 % endfor 402 }; 403 if has_name || !has_any { 404 writer.item(animation_name)?; 405 } 406 } 407 Ok(()) 408 } 409 } 410 </%helpers:shorthand> 411 412 <%helpers:shorthand 413 engines="gecko" 414 name="scroll-timeline" 415 sub_properties="scroll-timeline-name scroll-timeline-axis" 416 gecko_pref="layout.css.scroll-driven-animations.enabled", 417 spec="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-shorthand" 418 > 419 pub fn parse_value<'i>( 420 context: &ParserContext, 421 input: &mut Parser<'i, '_>, 422 ) -> Result<Longhands, ParseError<'i>> { 423 use crate::properties::longhands::{scroll_timeline_axis, scroll_timeline_name}; 424 425 let mut names = Vec::with_capacity(1); 426 let mut axes = Vec::with_capacity(1); 427 input.parse_comma_separated(|input| { 428 let name = scroll_timeline_name::single_value::parse(context, input)?; 429 let axis = input.try_parse(|i| scroll_timeline_axis::single_value::parse(context, i)); 430 431 names.push(name); 432 axes.push(axis.unwrap_or_default()); 433 434 Ok(()) 435 })?; 436 437 Ok(expanded! { 438 scroll_timeline_name: scroll_timeline_name::SpecifiedValue(names.into()), 439 scroll_timeline_axis: scroll_timeline_axis::SpecifiedValue(axes.into()), 440 }) 441 } 442 443 impl<'a> ToCss for LonghandsToSerialize<'a> { 444 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 445 // If any value list length is differs then we don't do a shorthand serialization 446 // either. 447 let len = self.scroll_timeline_name.0.len(); 448 if len != self.scroll_timeline_axis.0.len() { 449 return Ok(()); 450 } 451 452 for i in 0..len { 453 if i != 0 { 454 dest.write_str(", ")?; 455 } 456 457 self.scroll_timeline_name.0[i].to_css(dest)?; 458 459 if self.scroll_timeline_axis.0[i] != Default::default() { 460 dest.write_char(' ')?; 461 self.scroll_timeline_axis.0[i].to_css(dest)?; 462 } 463 464 } 465 Ok(()) 466 } 467 } 468 </%helpers:shorthand> 469 470 // Note: view-timeline shorthand doesn't take view-timeline-inset into account. 471 <%helpers:shorthand 472 engines="gecko" 473 name="view-timeline" 474 sub_properties="view-timeline-name view-timeline-axis" 475 gecko_pref="layout.css.scroll-driven-animations.enabled", 476 spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-shorthand" 477 > 478 pub fn parse_value<'i>( 479 context: &ParserContext, 480 input: &mut Parser<'i, '_>, 481 ) -> Result<Longhands, ParseError<'i>> { 482 use crate::properties::longhands::{view_timeline_axis, view_timeline_name}; 483 484 let mut names = Vec::with_capacity(1); 485 let mut axes = Vec::with_capacity(1); 486 input.parse_comma_separated(|input| { 487 let name = view_timeline_name::single_value::parse(context, input)?; 488 let axis = input.try_parse(|i| view_timeline_axis::single_value::parse(context, i)); 489 490 names.push(name); 491 axes.push(axis.unwrap_or_default()); 492 493 Ok(()) 494 })?; 495 496 Ok(expanded! { 497 view_timeline_name: view_timeline_name::SpecifiedValue(names.into()), 498 view_timeline_axis: view_timeline_axis::SpecifiedValue(axes.into()), 499 }) 500 } 501 502 impl<'a> ToCss for LonghandsToSerialize<'a> { 503 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 504 // If any value list length is differs then we don't do a shorthand serialization 505 // either. 506 let len = self.view_timeline_name.0.len(); 507 if len != self.view_timeline_axis.0.len() { 508 return Ok(()); 509 } 510 511 for i in 0..len { 512 if i != 0 { 513 dest.write_str(", ")?; 514 } 515 516 self.view_timeline_name.0[i].to_css(dest)?; 517 518 if self.view_timeline_axis.0[i] != Default::default() { 519 dest.write_char(' ')?; 520 self.view_timeline_axis.0[i].to_css(dest)?; 521 } 522 523 } 524 Ok(()) 525 } 526 } 527 </%helpers:shorthand>