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