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, ToTyped,
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 ToTyped,
210)]
211#[value_info(other_values = "none")]
212#[repr(C)]
213pub struct AnimationName(pub KeyframesName);
214
215impl AnimationName {
216 pub fn as_atom(&self) -> Option<&Atom> {
218 if self.is_none() {
219 return None;
220 }
221 Some(self.0.as_atom())
222 }
223
224 pub fn none() -> Self {
226 AnimationName(KeyframesName::none())
227 }
228
229 pub fn is_none(&self) -> bool {
231 self.0.is_none()
232 }
233}
234
235impl Parse for AnimationName {
236 fn parse<'i, 't>(
237 context: &ParserContext,
238 input: &mut Parser<'i, 't>,
239 ) -> Result<Self, ParseError<'i>> {
240 if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
241 return Ok(AnimationName(name));
242 }
243
244 input.expect_ident_matching("none")?;
245 Ok(AnimationName(KeyframesName::none()))
246 }
247}
248
249#[derive(
251 Copy,
252 Clone,
253 Debug,
254 MallocSizeOf,
255 Parse,
256 PartialEq,
257 SpecifiedValueInfo,
258 ToComputedValue,
259 ToCss,
260 ToResolvedValue,
261 ToShmem,
262 ToTyped,
263)]
264#[repr(u8)]
265#[allow(missing_docs)]
266pub enum AnimationDirection {
267 Normal,
268 Reverse,
269 Alternate,
270 AlternateReverse,
271}
272
273impl AnimationDirection {
274 #[inline]
276 pub fn match_keywords(name: &AnimationName) -> bool {
277 if let Some(name) = name.as_atom() {
278 #[cfg(feature = "gecko")]
279 return name.with_str(|n| Self::from_ident(n).is_ok());
280 #[cfg(feature = "servo")]
281 return Self::from_ident(name).is_ok();
282 }
283 false
284 }
285}
286
287#[derive(
289 Copy,
290 Clone,
291 Debug,
292 MallocSizeOf,
293 Parse,
294 PartialEq,
295 SpecifiedValueInfo,
296 ToComputedValue,
297 ToCss,
298 ToResolvedValue,
299 ToShmem,
300 ToTyped,
301)]
302#[repr(u8)]
303#[allow(missing_docs)]
304pub enum AnimationPlayState {
305 Running,
306 Paused,
307}
308
309impl AnimationPlayState {
310 #[inline]
312 pub fn match_keywords(name: &AnimationName) -> bool {
313 if let Some(name) = name.as_atom() {
314 #[cfg(feature = "gecko")]
315 return name.with_str(|n| Self::from_ident(n).is_ok());
316 #[cfg(feature = "servo")]
317 return Self::from_ident(name).is_ok();
318 }
319 false
320 }
321}
322
323#[derive(
325 Copy,
326 Clone,
327 Debug,
328 MallocSizeOf,
329 Parse,
330 PartialEq,
331 SpecifiedValueInfo,
332 ToComputedValue,
333 ToCss,
334 ToResolvedValue,
335 ToShmem,
336 ToTyped,
337)]
338#[repr(u8)]
339#[allow(missing_docs)]
340pub enum AnimationFillMode {
341 None,
342 Forwards,
343 Backwards,
344 Both,
345}
346
347impl AnimationFillMode {
348 #[inline]
351 pub fn match_keywords(name: &AnimationName) -> bool {
352 if let Some(name) = name.as_atom() {
353 #[cfg(feature = "gecko")]
354 return name.with_str(|n| Self::from_ident(n).is_ok());
355 #[cfg(feature = "servo")]
356 return Self::from_ident(name).is_ok();
357 }
358 false
359 }
360}
361
362#[derive(
364 Copy,
365 Clone,
366 Debug,
367 MallocSizeOf,
368 Parse,
369 PartialEq,
370 SpecifiedValueInfo,
371 ToComputedValue,
372 ToCss,
373 ToResolvedValue,
374 ToShmem,
375 ToTyped,
376)]
377#[repr(u8)]
378#[allow(missing_docs)]
379pub enum AnimationComposition {
380 Replace,
381 Add,
382 Accumulate,
383}
384
385#[derive(
389 Copy,
390 Clone,
391 Debug,
392 Eq,
393 Hash,
394 MallocSizeOf,
395 Parse,
396 PartialEq,
397 SpecifiedValueInfo,
398 ToComputedValue,
399 ToCss,
400 ToResolvedValue,
401 ToShmem,
402)]
403#[repr(u8)]
404pub enum Scroller {
405 Nearest,
407 Root,
409 #[css(keyword = "self")]
411 SelfElement,
412}
413
414impl Scroller {
415 #[inline]
417 fn is_default(&self) -> bool {
418 matches!(*self, Self::Nearest)
419 }
420}
421
422impl Default for Scroller {
423 fn default() -> Self {
424 Self::Nearest
425 }
426}
427
428#[derive(
434 Copy,
435 Clone,
436 Debug,
437 Eq,
438 Hash,
439 MallocSizeOf,
440 Parse,
441 PartialEq,
442 SpecifiedValueInfo,
443 ToComputedValue,
444 ToCss,
445 ToResolvedValue,
446 ToShmem,
447)]
448#[repr(u8)]
449pub enum ScrollAxis {
450 Block = 0,
452 Inline = 1,
454 X = 2,
456 Y = 3,
458}
459
460impl ScrollAxis {
461 #[inline]
463 pub fn is_default(&self) -> bool {
464 matches!(*self, Self::Block)
465 }
466}
467
468impl Default for ScrollAxis {
469 fn default() -> Self {
470 Self::Block
471 }
472}
473
474#[derive(
477 Copy,
478 Clone,
479 Debug,
480 MallocSizeOf,
481 PartialEq,
482 SpecifiedValueInfo,
483 ToComputedValue,
484 ToCss,
485 ToResolvedValue,
486 ToShmem,
487)]
488#[css(function = "scroll")]
489#[repr(C)]
490pub struct ScrollFunction {
491 #[css(skip_if = "Scroller::is_default")]
493 pub scroller: Scroller,
494 #[css(skip_if = "ScrollAxis::is_default")]
496 pub axis: ScrollAxis,
497}
498
499impl ScrollFunction {
500 fn parse_arguments<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
502 let mut scroller = None;
505 let mut axis = None;
506 loop {
507 if scroller.is_none() {
508 scroller = input.try_parse(Scroller::parse).ok();
509 }
510
511 if axis.is_none() {
512 axis = input.try_parse(ScrollAxis::parse).ok();
513 if axis.is_some() {
514 continue;
515 }
516 }
517 break;
518 }
519
520 Ok(Self {
521 scroller: scroller.unwrap_or_default(),
522 axis: axis.unwrap_or_default(),
523 })
524 }
525}
526
527impl generics::ViewFunction<LengthPercentage> {
528 fn parse_arguments<'i, 't>(
530 context: &ParserContext,
531 input: &mut Parser<'i, 't>,
532 ) -> Result<Self, ParseError<'i>> {
533 let mut axis = None;
536 let mut inset = None;
537 loop {
538 if axis.is_none() {
539 axis = input.try_parse(ScrollAxis::parse).ok();
540 }
541
542 if inset.is_none() {
543 inset = input
544 .try_parse(|i| ViewTimelineInset::parse(context, i))
545 .ok();
546 if inset.is_some() {
547 continue;
548 }
549 }
550 break;
551 }
552
553 Ok(Self {
554 inset: inset.unwrap_or_default(),
555 axis: axis.unwrap_or_default(),
556 })
557 }
558}
559
560#[derive(
565 Clone,
566 Debug,
567 Eq,
568 Hash,
569 MallocSizeOf,
570 PartialEq,
571 SpecifiedValueInfo,
572 ToComputedValue,
573 ToResolvedValue,
574 ToShmem,
575)]
576#[repr(C)]
577pub struct TimelineName(DashedIdent);
578
579impl TimelineName {
580 pub fn none() -> Self {
582 Self(DashedIdent::empty())
583 }
584
585 pub fn is_none(&self) -> bool {
587 self.0.is_empty()
588 }
589}
590
591impl Parse for TimelineName {
592 fn parse<'i, 't>(
593 context: &ParserContext,
594 input: &mut Parser<'i, 't>,
595 ) -> Result<Self, ParseError<'i>> {
596 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
597 return Ok(Self::none());
598 }
599
600 DashedIdent::parse(context, input).map(TimelineName)
601 }
602}
603
604impl ToCss for TimelineName {
605 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
606 where
607 W: Write,
608 {
609 if self.is_none() {
610 return dest.write_str("none");
611 }
612
613 self.0.to_css(dest)
614 }
615}
616
617pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
619
620impl Parse for AnimationTimeline {
621 fn parse<'i, 't>(
622 context: &ParserContext,
623 input: &mut Parser<'i, 't>,
624 ) -> Result<Self, ParseError<'i>> {
625 use crate::values::generics::animation::ViewFunction;
626
627 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
631 return Ok(Self::Auto);
632 }
633
634 if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
636 return Ok(AnimationTimeline::Timeline(name));
637 }
638
639 let location = input.current_source_location();
641 let function = input.expect_function()?.clone();
642 input.parse_nested_block(move |i| {
643 match_ignore_ascii_case! { &function,
644 "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
645 "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
646 _ => {
647 Err(location.new_custom_error(
648 StyleParseErrorKind::UnexpectedFunction(function.clone())
649 ))
650 },
651 }
652 })
653 }
654}
655
656pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
658
659impl Parse for ViewTimelineInset {
660 fn parse<'i, 't>(
661 context: &ParserContext,
662 input: &mut Parser<'i, 't>,
663 ) -> Result<Self, ParseError<'i>> {
664 use crate::values::specified::LengthPercentageOrAuto;
665
666 let start = LengthPercentageOrAuto::parse(context, input)?;
667 let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
668 Ok(end) => end,
669 Err(_) => start.clone(),
670 };
671
672 Ok(Self { start, end })
673 }
674}
675
676#[derive(
682 Clone,
683 Debug,
684 Eq,
685 Hash,
686 PartialEq,
687 MallocSizeOf,
688 SpecifiedValueInfo,
689 ToComputedValue,
690 ToResolvedValue,
691 ToShmem,
692 ToTyped,
693)]
694#[repr(C, u8)]
695pub enum ViewTransitionName {
696 None,
698 MatchElement,
701 Ident(Atom),
703}
704
705impl ViewTransitionName {
706 pub fn none() -> Self {
708 Self::None
709 }
710}
711
712impl Parse for ViewTransitionName {
713 fn parse<'i, 't>(
714 _: &ParserContext,
715 input: &mut Parser<'i, 't>,
716 ) -> Result<Self, ParseError<'i>> {
717 let location = input.current_source_location();
718 let ident = input.expect_ident()?;
719 if ident.eq_ignore_ascii_case("none") {
720 return Ok(Self::none());
721 }
722
723 if ident.eq_ignore_ascii_case("match-element") {
724 return Ok(Self::MatchElement);
725 }
726
727 CustomIdent::from_ident(location, ident, &["auto"]).map(|i| Self::Ident(i.0))
730 }
731}
732
733impl ToCss for ViewTransitionName {
734 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
735 where
736 W: Write,
737 {
738 use crate::values::serialize_atom_identifier;
739 match *self {
740 Self::None => dest.write_str("none"),
741 Self::MatchElement => dest.write_str("match-element"),
742 Self::Ident(ref ident) => serialize_atom_identifier(ident, dest),
743 }
744 }
745}
746
747#[derive(
753 Clone,
754 Debug,
755 Eq,
756 Hash,
757 PartialEq,
758 MallocSizeOf,
759 SpecifiedValueInfo,
760 ToComputedValue,
761 ToCss,
762 ToResolvedValue,
763 ToShmem,
764 ToTyped,
765)]
766#[repr(C)]
767#[value_info(other_values = "none")]
768pub struct ViewTransitionClass(
769 #[css(iterable, if_empty = "none")]
770 #[ignore_malloc_size_of = "Arc"]
771 crate::ArcSlice<CustomIdent>,
772);
773
774impl ViewTransitionClass {
775 pub fn none() -> Self {
777 Self(Default::default())
778 }
779
780 pub fn is_none(&self) -> bool {
782 self.0.is_empty()
783 }
784}
785
786impl Parse for ViewTransitionClass {
787 fn parse<'i, 't>(
788 _: &ParserContext,
789 input: &mut Parser<'i, 't>,
790 ) -> Result<Self, ParseError<'i>> {
791 use style_traits::{Separator, Space};
792
793 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
794 return Ok(Self::none());
795 }
796
797 Ok(Self(crate::ArcSlice::from_iter(
798 Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
799 )))
800 }
801}