1use crate::parser::{Parse, ParserContext};
8use crate::properties::{NonCustomPropertyId, PropertyId, ShorthandId};
9use crate::values::generics::animation as generics;
10use crate::values::specified::{LengthPercentage, NonNegativeNumber, Time};
11use crate::values::{CustomIdent, DashedIdent, KeyframesName};
12use crate::Atom;
13use cssparser::Parser;
14use std::fmt::{self, Write};
15use style_traits::{
16 CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
17};
18
19#[derive(
22 Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
23)]
24#[repr(u8)]
25pub enum TransitionProperty {
26 NonCustom(NonCustomPropertyId),
28 Custom(Atom),
30 Unsupported(CustomIdent),
33}
34
35impl ToCss for TransitionProperty {
36 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
37 where
38 W: Write,
39 {
40 match *self {
41 TransitionProperty::NonCustom(ref id) => id.to_css(dest),
42 TransitionProperty::Custom(ref name) => {
43 dest.write_str("--")?;
44 crate::values::serialize_atom_name(name, dest)
45 },
46 TransitionProperty::Unsupported(ref i) => i.to_css(dest),
47 }
48 }
49}
50
51impl Parse for TransitionProperty {
52 fn parse<'i, 't>(
53 context: &ParserContext,
54 input: &mut Parser<'i, 't>,
55 ) -> Result<Self, ParseError<'i>> {
56 let location = input.current_source_location();
57 let ident = input.expect_ident()?;
58
59 let id = match PropertyId::parse_ignoring_rule_type(&ident, context) {
60 Ok(id) => id,
61 Err(..) => {
62 return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident(
64 location,
65 ident,
66 &["none"],
67 )?));
68 },
69 };
70
71 Ok(match id {
72 PropertyId::NonCustom(id) => TransitionProperty::NonCustom(id.unaliased()),
73 PropertyId::Custom(name) => TransitionProperty::Custom(name),
74 })
75 }
76}
77
78impl SpecifiedValueInfo for TransitionProperty {
79 fn collect_completion_keywords(f: KeywordsCollectFn) {
80 f(&["all"]);
84 }
85}
86
87impl TransitionProperty {
88 #[inline]
90 pub fn none() -> Self {
91 TransitionProperty::Unsupported(CustomIdent(atom!("none")))
92 }
93
94 #[inline]
96 pub fn is_none(&self) -> bool {
97 matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none"))
98 }
99
100 #[inline]
102 pub fn all() -> Self {
103 TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All))
104 }
105
106 #[inline]
108 pub fn is_all(&self) -> bool {
109 self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(
110 ShorthandId::All,
111 ))
112 }
113}
114
115#[derive(
119 Clone,
120 Copy,
121 Debug,
122 MallocSizeOf,
123 Parse,
124 PartialEq,
125 SpecifiedValueInfo,
126 ToComputedValue,
127 ToCss,
128 ToResolvedValue,
129 ToShmem,
130)]
131#[repr(u8)]
132pub enum TransitionBehavior {
133 Normal,
135 AllowDiscrete,
137}
138
139impl TransitionBehavior {
140 #[inline]
142 pub fn normal() -> Self {
143 Self::Normal
144 }
145
146 #[inline]
148 pub fn is_normal(&self) -> bool {
149 matches!(*self, Self::Normal)
150 }
151}
152
153pub type AnimationDuration = generics::GenericAnimationDuration<Time>;
155
156impl Parse for AnimationDuration {
157 fn parse<'i, 't>(
158 context: &ParserContext,
159 input: &mut Parser<'i, 't>,
160 ) -> Result<Self, ParseError<'i>> {
161 if static_prefs::pref!("layout.css.scroll-driven-animations.enabled")
162 && input.try_parse(|i| i.expect_ident_matching("auto")).is_ok()
163 {
164 return Ok(Self::auto());
165 }
166
167 Time::parse_non_negative(context, input).map(AnimationDuration::Time)
168 }
169}
170
171#[derive(
173 Copy, Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem,
174)]
175pub enum AnimationIterationCount {
176 Number(NonNegativeNumber),
178 Infinite,
180}
181
182impl AnimationIterationCount {
183 #[inline]
185 pub fn one() -> Self {
186 Self::Number(NonNegativeNumber::new(1.0))
187 }
188
189 #[inline]
191 pub fn is_one(&self) -> bool {
192 *self == Self::one()
193 }
194}
195
196#[derive(
198 Clone,
199 Debug,
200 Eq,
201 Hash,
202 MallocSizeOf,
203 PartialEq,
204 SpecifiedValueInfo,
205 ToComputedValue,
206 ToCss,
207 ToResolvedValue,
208 ToShmem,
209)]
210#[value_info(other_values = "none")]
211#[repr(C)]
212pub struct AnimationName(pub KeyframesName);
213
214impl AnimationName {
215 pub fn as_atom(&self) -> Option<&Atom> {
217 if self.is_none() {
218 return None;
219 }
220 Some(self.0.as_atom())
221 }
222
223 pub fn none() -> Self {
225 AnimationName(KeyframesName::none())
226 }
227
228 pub fn is_none(&self) -> bool {
230 self.0.is_none()
231 }
232}
233
234impl Parse for AnimationName {
235 fn parse<'i, 't>(
236 context: &ParserContext,
237 input: &mut Parser<'i, 't>,
238 ) -> Result<Self, ParseError<'i>> {
239 if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
240 return Ok(AnimationName(name));
241 }
242
243 input.expect_ident_matching("none")?;
244 Ok(AnimationName(KeyframesName::none()))
245 }
246}
247
248#[derive(
250 Copy,
251 Clone,
252 Debug,
253 MallocSizeOf,
254 Parse,
255 PartialEq,
256 SpecifiedValueInfo,
257 ToComputedValue,
258 ToCss,
259 ToResolvedValue,
260 ToShmem,
261)]
262#[repr(u8)]
263#[allow(missing_docs)]
264pub enum AnimationDirection {
265 Normal,
266 Reverse,
267 Alternate,
268 AlternateReverse,
269}
270
271impl AnimationDirection {
272 #[inline]
274 pub fn match_keywords(name: &AnimationName) -> bool {
275 if let Some(name) = name.as_atom() {
276 #[cfg(feature = "gecko")]
277 return name.with_str(|n| Self::from_ident(n).is_ok());
278 #[cfg(feature = "servo")]
279 return Self::from_ident(name).is_ok();
280 }
281 false
282 }
283}
284
285#[derive(
287 Copy,
288 Clone,
289 Debug,
290 MallocSizeOf,
291 Parse,
292 PartialEq,
293 SpecifiedValueInfo,
294 ToComputedValue,
295 ToCss,
296 ToResolvedValue,
297 ToShmem,
298)]
299#[repr(u8)]
300#[allow(missing_docs)]
301pub enum AnimationPlayState {
302 Running,
303 Paused,
304}
305
306impl AnimationPlayState {
307 #[inline]
309 pub fn match_keywords(name: &AnimationName) -> bool {
310 if let Some(name) = name.as_atom() {
311 #[cfg(feature = "gecko")]
312 return name.with_str(|n| Self::from_ident(n).is_ok());
313 #[cfg(feature = "servo")]
314 return Self::from_ident(name).is_ok();
315 }
316 false
317 }
318}
319
320#[derive(
322 Copy,
323 Clone,
324 Debug,
325 MallocSizeOf,
326 Parse,
327 PartialEq,
328 SpecifiedValueInfo,
329 ToComputedValue,
330 ToCss,
331 ToResolvedValue,
332 ToShmem,
333)]
334#[repr(u8)]
335#[allow(missing_docs)]
336pub enum AnimationFillMode {
337 None,
338 Forwards,
339 Backwards,
340 Both,
341}
342
343impl AnimationFillMode {
344 #[inline]
347 pub fn match_keywords(name: &AnimationName) -> bool {
348 if let Some(name) = name.as_atom() {
349 #[cfg(feature = "gecko")]
350 return name.with_str(|n| Self::from_ident(n).is_ok());
351 #[cfg(feature = "servo")]
352 return Self::from_ident(name).is_ok();
353 }
354 false
355 }
356}
357
358#[derive(
360 Copy,
361 Clone,
362 Debug,
363 MallocSizeOf,
364 Parse,
365 PartialEq,
366 SpecifiedValueInfo,
367 ToComputedValue,
368 ToCss,
369 ToResolvedValue,
370 ToShmem,
371)]
372#[repr(u8)]
373#[allow(missing_docs)]
374pub enum AnimationComposition {
375 Replace,
376 Add,
377 Accumulate,
378}
379
380#[derive(
384 Copy,
385 Clone,
386 Debug,
387 Eq,
388 Hash,
389 MallocSizeOf,
390 Parse,
391 PartialEq,
392 SpecifiedValueInfo,
393 ToComputedValue,
394 ToCss,
395 ToResolvedValue,
396 ToShmem,
397)]
398#[repr(u8)]
399pub enum Scroller {
400 Nearest,
402 Root,
404 #[css(keyword = "self")]
406 SelfElement,
407}
408
409impl Scroller {
410 #[inline]
412 fn is_default(&self) -> bool {
413 matches!(*self, Self::Nearest)
414 }
415}
416
417impl Default for Scroller {
418 fn default() -> Self {
419 Self::Nearest
420 }
421}
422
423#[derive(
429 Copy,
430 Clone,
431 Debug,
432 Eq,
433 Hash,
434 MallocSizeOf,
435 Parse,
436 PartialEq,
437 SpecifiedValueInfo,
438 ToComputedValue,
439 ToCss,
440 ToResolvedValue,
441 ToShmem,
442)]
443#[repr(u8)]
444pub enum ScrollAxis {
445 Block = 0,
447 Inline = 1,
449 X = 2,
451 Y = 3,
453}
454
455impl ScrollAxis {
456 #[inline]
458 pub fn is_default(&self) -> bool {
459 matches!(*self, Self::Block)
460 }
461}
462
463impl Default for ScrollAxis {
464 fn default() -> Self {
465 Self::Block
466 }
467}
468
469#[derive(
472 Copy,
473 Clone,
474 Debug,
475 MallocSizeOf,
476 PartialEq,
477 SpecifiedValueInfo,
478 ToComputedValue,
479 ToCss,
480 ToResolvedValue,
481 ToShmem,
482)]
483#[css(function = "scroll")]
484#[repr(C)]
485pub struct ScrollFunction {
486 #[css(skip_if = "Scroller::is_default")]
488 pub scroller: Scroller,
489 #[css(skip_if = "ScrollAxis::is_default")]
491 pub axis: ScrollAxis,
492}
493
494impl ScrollFunction {
495 fn parse_arguments<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
497 let mut scroller = None;
500 let mut axis = None;
501 loop {
502 if scroller.is_none() {
503 scroller = input.try_parse(Scroller::parse).ok();
504 }
505
506 if axis.is_none() {
507 axis = input.try_parse(ScrollAxis::parse).ok();
508 if axis.is_some() {
509 continue;
510 }
511 }
512 break;
513 }
514
515 Ok(Self {
516 scroller: scroller.unwrap_or_default(),
517 axis: axis.unwrap_or_default(),
518 })
519 }
520}
521
522impl generics::ViewFunction<LengthPercentage> {
523 fn parse_arguments<'i, 't>(
525 context: &ParserContext,
526 input: &mut Parser<'i, 't>,
527 ) -> Result<Self, ParseError<'i>> {
528 let mut axis = None;
531 let mut inset = None;
532 loop {
533 if axis.is_none() {
534 axis = input.try_parse(ScrollAxis::parse).ok();
535 }
536
537 if inset.is_none() {
538 inset = input
539 .try_parse(|i| ViewTimelineInset::parse(context, i))
540 .ok();
541 if inset.is_some() {
542 continue;
543 }
544 }
545 break;
546 }
547
548 Ok(Self {
549 inset: inset.unwrap_or_default(),
550 axis: axis.unwrap_or_default(),
551 })
552 }
553}
554
555#[derive(
560 Clone,
561 Debug,
562 Eq,
563 Hash,
564 MallocSizeOf,
565 PartialEq,
566 SpecifiedValueInfo,
567 ToComputedValue,
568 ToResolvedValue,
569 ToShmem,
570)]
571#[repr(C)]
572pub struct TimelineName(DashedIdent);
573
574impl TimelineName {
575 pub fn none() -> Self {
577 Self(DashedIdent::empty())
578 }
579
580 pub fn is_none(&self) -> bool {
582 self.0.is_empty()
583 }
584}
585
586impl Parse for TimelineName {
587 fn parse<'i, 't>(
588 context: &ParserContext,
589 input: &mut Parser<'i, 't>,
590 ) -> Result<Self, ParseError<'i>> {
591 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
592 return Ok(Self::none());
593 }
594
595 DashedIdent::parse(context, input).map(TimelineName)
596 }
597}
598
599impl ToCss for TimelineName {
600 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
601 where
602 W: Write,
603 {
604 if self.is_none() {
605 return dest.write_str("none");
606 }
607
608 self.0.to_css(dest)
609 }
610}
611
612pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
614
615impl Parse for AnimationTimeline {
616 fn parse<'i, 't>(
617 context: &ParserContext,
618 input: &mut Parser<'i, 't>,
619 ) -> Result<Self, ParseError<'i>> {
620 use crate::values::generics::animation::ViewFunction;
621
622 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
626 return Ok(Self::Auto);
627 }
628
629 if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
631 return Ok(AnimationTimeline::Timeline(name));
632 }
633
634 let location = input.current_source_location();
636 let function = input.expect_function()?.clone();
637 input.parse_nested_block(move |i| {
638 match_ignore_ascii_case! { &function,
639 "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
640 "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
641 _ => {
642 Err(location.new_custom_error(
643 StyleParseErrorKind::UnexpectedFunction(function.clone())
644 ))
645 },
646 }
647 })
648 }
649}
650
651pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
653
654impl Parse for ViewTimelineInset {
655 fn parse<'i, 't>(
656 context: &ParserContext,
657 input: &mut Parser<'i, 't>,
658 ) -> Result<Self, ParseError<'i>> {
659 use crate::values::specified::LengthPercentageOrAuto;
660
661 let start = LengthPercentageOrAuto::parse(context, input)?;
662 let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
663 Ok(end) => end,
664 Err(_) => start.clone(),
665 };
666
667 Ok(Self { start, end })
668 }
669}
670
671#[derive(
677 Clone,
678 Debug,
679 Eq,
680 Hash,
681 PartialEq,
682 MallocSizeOf,
683 SpecifiedValueInfo,
684 ToComputedValue,
685 ToResolvedValue,
686 ToShmem,
687)]
688#[repr(C, u8)]
689pub enum ViewTransitionName {
690 None,
692 MatchElement,
695 Ident(Atom),
697}
698
699impl ViewTransitionName {
700 pub fn none() -> Self {
702 Self::None
703 }
704}
705
706impl Parse for ViewTransitionName {
707 fn parse<'i, 't>(
708 _: &ParserContext,
709 input: &mut Parser<'i, 't>,
710 ) -> Result<Self, ParseError<'i>> {
711 let location = input.current_source_location();
712 let ident = input.expect_ident()?;
713 if ident.eq_ignore_ascii_case("none") {
714 return Ok(Self::none());
715 }
716
717 if ident.eq_ignore_ascii_case("match-element") {
718 return Ok(Self::MatchElement);
719 }
720
721 CustomIdent::from_ident(location, ident, &["auto"]).map(|i| Self::Ident(i.0))
724 }
725}
726
727impl ToCss for ViewTransitionName {
728 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
729 where
730 W: Write,
731 {
732 use crate::values::serialize_atom_identifier;
733 match *self {
734 Self::None => dest.write_str("none"),
735 Self::MatchElement => dest.write_str("match-element"),
736 Self::Ident(ref ident) => serialize_atom_identifier(ident, dest),
737 }
738 }
739}
740
741#[derive(
747 Clone,
748 Debug,
749 Eq,
750 Hash,
751 PartialEq,
752 MallocSizeOf,
753 SpecifiedValueInfo,
754 ToComputedValue,
755 ToCss,
756 ToResolvedValue,
757 ToShmem,
758)]
759#[repr(C)]
760#[value_info(other_values = "none")]
761pub struct ViewTransitionClass(
762 #[css(iterable, if_empty = "none")]
763 #[ignore_malloc_size_of = "Arc"]
764 crate::ArcSlice<CustomIdent>,
765);
766
767impl ViewTransitionClass {
768 pub fn none() -> Self {
770 Self(Default::default())
771 }
772
773 pub fn is_none(&self) -> bool {
775 self.0.is_empty()
776 }
777}
778
779impl Parse for ViewTransitionClass {
780 fn parse<'i, 't>(
781 _: &ParserContext,
782 input: &mut Parser<'i, 't>,
783 ) -> Result<Self, ParseError<'i>> {
784 use style_traits::{Separator, Space};
785
786 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
787 return Ok(Self::none());
788 }
789
790 Ok(Self(crate::ArcSlice::from_iter(
791 Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
792 )))
793 }
794}