Skip to main content

style/stylesheets/
keyframes_rule.rs

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//! Keyframes: https://drafts.csswg.org/css-animations/#keyframes
6
7use crate::derives::*;
8use crate::error_reporting::ContextualParseError;
9use crate::parser::{Parse, ParserContext};
10use crate::properties::{
11    longhands::{
12        animation_composition::single_value::SpecifiedValue as SpecifiedComposition,
13        transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction,
14    },
15    parse_property_declaration_list, LonghandId, PropertyDeclaration, PropertyDeclarationBlock,
16    PropertyDeclarationId, PropertyDeclarationIdSet,
17};
18use crate::shared_lock::{DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard};
19use crate::shared_lock::{Locked, ToCssWithGuard};
20use crate::stylesheets::rule_parser::VendorPrefix;
21use crate::stylesheets::{CssRuleType, StylesheetContents};
22use crate::values::specified::animation::TimelineRangeName;
23use crate::values::specified::{Number, Percentage};
24use crate::values::{serialize_percentage, KeyframesName};
25use cssparser::{
26    parse_one_rule, AtRuleParser, DeclarationParser, Parser, ParserState, QualifiedRuleParser,
27    RuleBodyItemParser, RuleBodyParser, SourceLocation, Token,
28};
29use servo_arc::Arc;
30use std::borrow::Cow;
31use std::fmt::{self, Write};
32use style_traits::{
33    CssStringWriter, CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss,
34};
35
36/// A [`@keyframes`][keyframes] rule.
37///
38/// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
39#[derive(Debug, ToShmem)]
40pub struct KeyframesRule {
41    /// The name of the current animation.
42    pub name: KeyframesName,
43    /// The keyframes specified for this CSS rule.
44    pub keyframes: Vec<Arc<Locked<Keyframe>>>,
45    /// Vendor prefix type the @keyframes has.
46    pub vendor_prefix: Option<VendorPrefix>,
47    /// The line and column of the rule's source code.
48    pub source_location: SourceLocation,
49}
50
51impl ToCssWithGuard for KeyframesRule {
52    // Serialization of KeyframesRule is not specced.
53    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
54        dest.write_str("@keyframes ")?;
55        self.name.to_css(&mut CssWriter::new(dest))?;
56        dest.write_str(" {")?;
57        let iter = self.keyframes.iter();
58        for lock in iter {
59            dest.write_str("\n")?;
60            let keyframe = lock.read_with(guard);
61            keyframe.to_css(guard, dest)?;
62        }
63        dest.write_str("\n}")
64    }
65}
66
67impl KeyframesRule {
68    /// Returns the index of the last keyframe that matches the given selector.
69    /// If the selector is not valid, or no keyframe is found, returns None.
70    ///
71    /// Related spec:
72    /// <https://drafts.csswg.org/css-animations-1/#interface-csskeyframesrule-findrule>
73    pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option<usize> {
74        if let Ok(selector) = Parser::new(selector).parse_entirely(KeyframeSelectors::parse) {
75            for (i, keyframe) in self.keyframes.iter().enumerate().rev() {
76                if keyframe.read_with(guard).selector == selector {
77                    return Some(i);
78                }
79            }
80        }
81        None
82    }
83}
84
85impl DeepCloneWithLock for KeyframesRule {
86    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
87        KeyframesRule {
88            name: self.name.clone(),
89            keyframes: self
90                .keyframes
91                .iter()
92                .map(|x| Arc::new(lock.wrap(x.read_with(guard).deep_clone_with_lock(lock, guard))))
93                .collect(),
94            vendor_prefix: self.vendor_prefix.clone(),
95            source_location: self.source_location,
96        }
97    }
98}
99
100/// A number from 0 to 1, indicating the percentage of the animation when this
101/// keyframe should run.
102#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)]
103pub struct KeyframePercentage(pub f32);
104
105impl ::std::cmp::Ord for KeyframePercentage {
106    #[inline]
107    fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
108        // We know we have a number from 0 to 1, so unwrap() here is safe.
109        self.0.partial_cmp(&other.0).unwrap()
110    }
111}
112
113impl ::std::cmp::Eq for KeyframePercentage {}
114
115impl ToCss for KeyframePercentage {
116    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
117    where
118        W: Write,
119    {
120        serialize_percentage(self.0, dest)
121    }
122}
123
124impl KeyframePercentage {
125    /// Trivially constructs a new `KeyframePercentage`.
126    #[inline]
127    pub fn new(value: f32) -> KeyframePercentage {
128        KeyframePercentage(value)
129    }
130
131    fn parse(input: &mut Parser) -> Result<KeyframePercentage, ParseError> {
132        let token = input.next()?.clone();
133        match token {
134            Token::Ident(ref identifier) if identifier.as_ref().eq_ignore_ascii_case("from") => {
135                Ok(KeyframePercentage::new(0.))
136            },
137            Token::Ident(ref identifier) if identifier.as_ref().eq_ignore_ascii_case("to") => {
138                Ok(KeyframePercentage::new(1.))
139            },
140            Token::Percentage {
141                unit_value: percentage,
142                ..
143            } if (0. ..=1.).contains(&percentage) => Ok(KeyframePercentage::new(percentage)),
144            _ => Err(ParseError::unexpected_token()),
145        }
146    }
147}
148
149/// A single `<keyframe-selector>`:
150/// `<keyframe-selector> = from | to | <percentage [0,100]> | <timeline-range-name> <percentage>`
151/// It could be a percentage, from/to, or a timeline range name together with a percentage.
152/// https://drafts.csswg.org/scroll-animations-1/#named-range-keyframes
153#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
154pub struct KeyframeSelector {
155    /// The named timeline range name component of the selector. If it is omitted, we use
156    /// `TimelineRangeName::None`. Note that `TimelineRangeName::Normal` is not used for the
157    /// selector.
158    pub range_name: TimelineRangeName,
159    /// The percentage component of the selector. It is a percentage or a from/to symbol, which is
160    /// converted at parse time to percentage.
161    pub percentage: KeyframePercentage,
162}
163
164impl KeyframeSelector {
165    /// Returns Self as a percentage.
166    fn from_percentage(percentage: KeyframePercentage) -> Self {
167        debug_assert!(percentage.0 >= 0. && percentage.0 <= 1.);
168        KeyframeSelector {
169            range_name: TimelineRangeName::None,
170            percentage,
171        }
172    }
173
174    /// Parse a keyframe selector from CSS input.
175    pub fn parse_internal(input: &mut Parser) -> Result<Self, ParseError> {
176        // `from | to | <percentage [0,100]>`
177        if let Ok(percentage) = input.try_parse(KeyframePercentage::parse) {
178            return Ok(Self::from_percentage(percentage));
179        }
180
181        // We parse the the extension of keyframe selector for scroll-driven animation.
182        if !crate::pref!("layout.css.scroll-driven-animations.enabled") {
183            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
184        }
185
186        // `<timeline-range-name> <percentage>`
187        // Note that <percentage> could be out of [0,100].
188        Ok(Self {
189            range_name: TimelineRangeName::parse(input)?,
190            percentage: KeyframePercentage::new(input.expect_percentage()?),
191        })
192    }
193}
194
195impl Parse for KeyframeSelector {
196    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
197        KeyframeSelector::parse_internal(input)
198    }
199}
200
201/// A list of `<keyframe-selector>`s.
202#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
203#[css(comma)]
204pub struct KeyframeSelectors(#[css(iterable)] Vec<KeyframeSelector>);
205
206impl KeyframeSelectors {
207    /// A dummy public function so we can write a unit test for this.
208    pub fn new_for_unit_testing(percentages: Vec<KeyframePercentage>) -> KeyframeSelectors {
209        KeyframeSelectors(
210            percentages
211                .into_iter()
212                .map(KeyframeSelector::from_percentage)
213                .collect(),
214        )
215    }
216
217    /// Parse the keyframe selectors from CSS input.
218    pub fn parse(input: &mut Parser) -> Result<Self, ParseError> {
219        input
220            .parse_comma_separated(KeyframeSelector::parse_internal)
221            .map(KeyframeSelectors)
222    }
223}
224
225/// A keyframe.
226#[derive(Debug, ToShmem)]
227pub struct Keyframe {
228    /// The selector this keyframe was specified from.
229    pub selector: KeyframeSelectors,
230
231    /// The declaration block that was declared inside this keyframe.
232    ///
233    /// Note that `!important` rules in keyframes don't apply, but we keep this
234    /// `Arc` just for convenience.
235    pub block: Arc<Locked<PropertyDeclarationBlock>>,
236
237    /// The line and column of the rule's source code.
238    pub source_location: SourceLocation,
239}
240
241impl ToCssWithGuard for Keyframe {
242    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
243        self.selector.to_css(&mut CssWriter::new(dest))?;
244        dest.write_str(" { ")?;
245        self.block.read_with(guard).to_css(dest)?;
246        dest.write_str(" }")?;
247        Ok(())
248    }
249}
250
251impl Keyframe {
252    /// Parse a CSS keyframe.
253    pub fn parse(
254        css: &str,
255        parent_stylesheet_contents: &StylesheetContents,
256        lock: &SharedRwLock,
257    ) -> Result<Arc<Locked<Self>>, ParseError> {
258        let url_data = &parent_stylesheet_contents.url_data;
259        let namespaces = &parent_stylesheet_contents.namespaces;
260        let mut context = ParserContext::new(
261            parent_stylesheet_contents.origin,
262            url_data,
263            Some(CssRuleType::Keyframe),
264            ParsingMode::DEFAULT,
265            parent_stylesheet_contents.quirks_mode,
266            Cow::Borrowed(namespaces),
267            None,
268            None,
269            /* attr_taint */ Default::default(),
270        );
271        let mut input = Parser::new(css);
272
273        let mut rule_parser = KeyframeListParser {
274            context: &mut context,
275            shared_lock: lock,
276        };
277        parse_one_rule(&mut input, &mut rule_parser)
278    }
279}
280
281impl DeepCloneWithLock for Keyframe {
282    /// Deep clones this Keyframe.
283    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Keyframe {
284        Keyframe {
285            selector: self.selector.clone(),
286            block: Arc::new(lock.wrap(self.block.read_with(guard).clone())),
287            source_location: self.source_location,
288        }
289    }
290}
291
292/// A keyframes step value. This can be a synthetised keyframes animation, that
293/// is, one autogenerated from the current computed values, or a list of
294/// declarations to apply.
295///
296/// TODO: Find a better name for this?
297#[derive(Clone, Debug, MallocSizeOf)]
298pub enum KeyframesStepValue {
299    /// A step formed by a declaration block specified by the CSS.
300    Declarations {
301        /// The declaration block per se.
302        #[cfg_attr(
303            feature = "gecko",
304            ignore_malloc_size_of = "XXX: Primary ref, measure if DMD says it's worthwhile"
305        )]
306        #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
307        block: Arc<Locked<PropertyDeclarationBlock>>,
308    },
309    /// A synthetic step computed from the current computed values at the time
310    /// of the animation.
311    ComputedValues,
312}
313
314/// A single step from a keyframe animation.
315#[derive(Clone, Debug, MallocSizeOf)]
316pub struct KeyframesStep {
317    /// The offset of the animation duration when this step starts.
318    pub start_offset: KeyframeSelector,
319    /// Declarations that will determine the final style during the step, or
320    /// `ComputedValues` if this is an autogenerated step.
321    pub value: KeyframesStepValue,
322    /// Whether an animation-timing-function declaration exists in the list of
323    /// declarations.
324    ///
325    /// This is used to know when to override the keyframe animation style.
326    pub declared_timing_function: bool,
327    /// Whether an animation-composition declaration exists in the list of
328    /// declarations.
329    ///
330    /// This is used to know when to override the keyframe animation style.
331    pub declared_composition: bool,
332}
333
334impl KeyframesStep {
335    #[inline]
336    fn new(
337        start_offset: KeyframeSelector,
338        value: KeyframesStepValue,
339        guard: &SharedRwLockReadGuard,
340    ) -> Self {
341        let mut declared_timing_function = false;
342        let mut declared_composition = false;
343        if let KeyframesStepValue::Declarations { ref block } = value {
344            for prop_decl in block.read_with(guard).declarations().iter() {
345                match *prop_decl {
346                    PropertyDeclaration::AnimationTimingFunction(..) => {
347                        declared_timing_function = true;
348                    },
349                    PropertyDeclaration::AnimationComposition(..) => {
350                        declared_composition = true;
351                    },
352                    _ => continue,
353                }
354                // Don't need to continue the loop if both are found.
355                if declared_timing_function && declared_composition {
356                    break;
357                }
358            }
359        }
360
361        KeyframesStep {
362            start_offset,
363            value,
364            declared_timing_function,
365            declared_composition,
366        }
367    }
368
369    /// Return specified PropertyDeclaration.
370    #[inline]
371    fn get_declared_property<'a>(
372        &'a self,
373        guard: &'a SharedRwLockReadGuard,
374        property: LonghandId,
375    ) -> Option<&'a PropertyDeclaration> {
376        match self.value {
377            KeyframesStepValue::Declarations { ref block } => {
378                let guard = block.read_with(guard);
379                let (declaration, _) = guard
380                    .get(PropertyDeclarationId::Longhand(property))
381                    .unwrap();
382                match *declaration {
383                    PropertyDeclaration::CSSWideKeyword(..) => None,
384                    // FIXME: Bug 1710735: Support css variable in @keyframes rule.
385                    PropertyDeclaration::WithVariables(..) => None,
386                    _ => Some(declaration),
387                }
388            },
389            KeyframesStepValue::ComputedValues => {
390                panic!("Shouldn't happen to set this property in missing keyframes")
391            },
392        }
393    }
394
395    /// Return specified TransitionTimingFunction if this KeyframesSteps has
396    /// 'animation-timing-function'.
397    pub fn get_animation_timing_function(
398        &self,
399        guard: &SharedRwLockReadGuard,
400    ) -> Option<SpecifiedTimingFunction> {
401        if !self.declared_timing_function {
402            return None;
403        }
404
405        self.get_declared_property(guard, LonghandId::AnimationTimingFunction)
406            .map(|decl| {
407                match *decl {
408                    PropertyDeclaration::AnimationTimingFunction(ref value) => {
409                        // Use the first value
410                        value.0[0].clone()
411                    },
412                    _ => unreachable!("Unexpected PropertyDeclaration"),
413                }
414            })
415    }
416
417    /// Return CompositeOperation if this KeyframesSteps has 'animation-composition'.
418    pub fn get_animation_composition(
419        &self,
420        guard: &SharedRwLockReadGuard,
421    ) -> Option<SpecifiedComposition> {
422        if !self.declared_composition {
423            return None;
424        }
425
426        self.get_declared_property(guard, LonghandId::AnimationComposition)
427            .map(|decl| {
428                match *decl {
429                    PropertyDeclaration::AnimationComposition(ref value) => {
430                        // Use the first value
431                        value.0[0]
432                    },
433                    _ => unreachable!("Unexpected PropertyDeclaration"),
434                }
435            })
436    }
437}
438
439/// This structure represents a list of animation steps computed from the list
440/// of keyframes, in order.
441///
442/// It only takes into account animable properties.
443#[derive(Clone, Debug, MallocSizeOf)]
444pub struct KeyframesAnimation {
445    /// The different steps of the animation.
446    pub steps: Vec<KeyframesStep>,
447    /// The different steps of the animation. Those steps are only for keyframe selectors with
448    /// timeline range names. We intentionally use a different vector because it is unsorted and we
449    /// would like to maintain its specified order when grouping them. Also, per spec, the computed
450    /// order requires us to pull percentage-only keyframes to the front and sort them, so using a
451    /// separate vector makes it easier to group them to maintain the computed order.
452    /// https://drafts.csswg.org/css-animations-2/#keyframe-processing
453    /// https://github.com/w3c/csswg-drafts/issues/8507
454    pub steps_with_range_name: Vec<KeyframesStep>,
455    /// The properties that change in this animation.
456    #[cfg(feature = "servo")]
457    pub properties_changed: PropertyDeclarationIdSet,
458    /// Vendor prefix type the @keyframes has.
459    pub vendor_prefix: Option<VendorPrefix>,
460}
461
462/// True if there are any animated properties in a keyframes animation.
463/// If a PropertyDeclarationIdSet is provided, it will be populated with the animated properties.
464fn has_animated_properties(
465    keyframes: &[Arc<Locked<Keyframe>>],
466    guard: &SharedRwLockReadGuard,
467    mut properties_changed: Option<&mut PropertyDeclarationIdSet>,
468) -> bool {
469    // NB: declarations are already deduplicated, so we don't have to check for
470    // it here.
471    for keyframe in keyframes {
472        let keyframe = keyframe.read_with(guard);
473        let block = keyframe.block.read_with(guard);
474        // CSS Animations spec clearly defines that properties with !important
475        // in keyframe rules are invalid and ignored, but it's still ambiguous
476        // whether we should drop the !important properties or retain the
477        // properties when they are set via CSSOM. So we assume there might
478        // be properties with !important in keyframe rules here.
479        // See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
480        for declaration in block.normal_declaration_iter() {
481            let declaration_id = declaration.id();
482
483            if declaration_id == PropertyDeclarationId::Longhand(LonghandId::Display)
484                && !crate::pref!("layout.css.display-animations.enabled")
485            {
486                continue;
487            }
488
489            if !declaration_id.is_animatable() {
490                continue;
491            }
492
493            if let Some(ref mut properties_changed) = properties_changed {
494                properties_changed.insert(declaration_id);
495            } else {
496                return true;
497            }
498        }
499    }
500
501    if let Some(properties_changed) = properties_changed {
502        !properties_changed.is_empty()
503    } else {
504        false
505    }
506}
507
508impl KeyframesAnimation {
509    /// Create a keyframes animation from a given list of keyframes.
510    ///
511    /// This will return a keyframe animation with empty steps if the list of
512    /// keyframes is empty, or there are no animated properties obtained from
513    /// the keyframes.
514    ///
515    /// Otherwise, this will compute and sort the steps used for the animation,
516    /// and return the animation object.
517    pub fn from_keyframes(
518        keyframes: &[Arc<Locked<Keyframe>>],
519        vendor_prefix: Option<VendorPrefix>,
520        guard: &SharedRwLockReadGuard,
521    ) -> Self {
522        let mut result = KeyframesAnimation {
523            steps: vec![],
524            steps_with_range_name: vec![],
525            #[cfg(feature = "servo")]
526            properties_changed: PropertyDeclarationIdSet::default(),
527            vendor_prefix,
528        };
529
530        #[cfg(feature = "servo")]
531        let properties_changed = Some(&mut result.properties_changed);
532        #[cfg(feature = "gecko")]
533        let properties_changed = None;
534
535        if keyframes.is_empty() || !has_animated_properties(keyframes, guard, properties_changed) {
536            return result;
537        }
538
539        // The steps with percentage only.
540        let mut steps = vec![];
541
542        for keyframe in keyframes {
543            let keyframe = keyframe.read_with(guard);
544            for selector in keyframe.selector.0.iter() {
545                let step = KeyframesStep::new(
546                    *selector,
547                    KeyframesStepValue::Declarations {
548                        block: keyframe.block.clone(),
549                    },
550                    guard,
551                );
552
553                if !selector.range_name.is_none() {
554                    result.steps_with_range_name.push(step);
555                } else {
556                    steps.push(step);
557                }
558            }
559        }
560
561        // Sort by the percentage, so we can easily find a frame. Note that we only sort the
562        // keyframes with percentage since we have to maintain the order of keyframes with
563        // TimelineRange as specified.
564        steps.sort_by_key(|step| step.start_offset.percentage);
565
566        // Prepend autogenerated keyframes if appropriate.
567        //
568        // FIXME: Bug 2037642. For animation-timeline: none or auto, if all the keyframes use
569        // `<timeline-range-name>`, we shouldn't generate 0% and 100% keyframes. The better way is
570        // to fill the implicit keyframes lazily, in getKeyframes() or when using them, after they
571        // have `computedOffset` set.
572        //
573        // https://github.com/w3c/csswg-drafts/issues/13872
574        // https://drafts.csswg.org/css-animations-2/#keyframe-processing
575        #[cfg(feature = "servo")]
576        if steps.is_empty() || steps[0].start_offset.percentage.0 != 0. {
577            steps.insert(
578                0,
579                KeyframesStep::new(
580                    KeyframeSelector::from_percentage(KeyframePercentage::new(0.)),
581                    KeyframesStepValue::ComputedValues,
582                    guard,
583                ),
584            );
585        }
586        #[cfg(feature = "servo")]
587        if steps.last().unwrap().start_offset.percentage.0 != 1. {
588            steps.push(KeyframesStep::new(
589                KeyframeSelector::from_percentage(KeyframePercentage::new(1.)),
590                KeyframesStepValue::ComputedValues,
591                guard,
592            ));
593        }
594
595        result.steps = steps;
596        result
597    }
598}
599
600/// Parses a keyframes list, like:
601/// 0%, 50% {
602///     width: 50%;
603/// }
604///
605/// 40%, 60%, 100% {
606///     width: 100%;
607/// }
608struct KeyframeListParser<'a, 'b> {
609    context: &'a mut ParserContext<'b>,
610    shared_lock: &'a SharedRwLock,
611}
612
613/// Parses a keyframe list from CSS input.
614pub fn parse_keyframe_list<'a>(
615    context: &mut ParserContext<'a>,
616    input: &mut Parser,
617    shared_lock: &SharedRwLock,
618) -> Vec<Arc<Locked<Keyframe>>> {
619    let mut parser = KeyframeListParser {
620        context,
621        shared_lock,
622    };
623    RuleBodyParser::new(input, &mut parser)
624        .filter_map(Result::ok)
625        .collect()
626}
627
628impl<'a, 'b, 'i> AtRuleParser<'i> for KeyframeListParser<'a, 'b> {
629    type Prelude = ();
630    type AtRule = Arc<Locked<Keyframe>>;
631    type Error = StyleParseErrorKind;
632}
633
634impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeListParser<'a, 'b> {
635    type Declaration = Arc<Locked<Keyframe>>;
636    type Error = StyleParseErrorKind;
637}
638
639impl<'a, 'b, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a, 'b> {
640    type Prelude = KeyframeSelectors;
641    type QualifiedRule = Arc<Locked<Keyframe>>;
642    type Error = StyleParseErrorKind;
643
644    fn parse_prelude(&mut self, input: &mut Parser<'i>) -> Result<Self::Prelude, ParseError> {
645        let start_position = input.position();
646        let start_location = input.current_source_location();
647        KeyframeSelectors::parse(input).inspect_err(|e| {
648            let error = ContextualParseError::InvalidKeyframeRule(
649                input.slice_from(start_position),
650                e.clone(),
651            );
652            self.context.log_css_error(start_location, error);
653        })
654    }
655
656    fn parse_block(
657        &mut self,
658        selector: Self::Prelude,
659        start: &ParserState,
660        input: &mut Parser<'i>,
661    ) -> Result<Self::QualifiedRule, ParseError> {
662        let block = self.context.nest_for_rule(CssRuleType::Keyframe, |p| {
663            parse_property_declaration_list(p, input, &[])
664        });
665        Ok(Arc::new(self.shared_lock.wrap(Keyframe {
666            selector,
667            block: Arc::new(self.shared_lock.wrap(block)),
668            source_location: start.source_location(),
669        })))
670    }
671}
672
673impl<'a, 'b, 'i> RuleBodyItemParser<'i, Arc<Locked<Keyframe>>, StyleParseErrorKind>
674    for KeyframeListParser<'a, 'b>
675{
676    fn parse_qualified(&self) -> bool {
677        true
678    }
679    fn parse_declarations(&self) -> bool {
680        false
681    }
682}
683
684/// The Keyframe offset for Web Animations. Since we support double value from JS, so we need to
685/// include a number for it as well.
686// Note: we don't do the range check at parse time for Web animations.
687// Per spec (step 7 in [1]), we check the range of the offset in a separate step and throw a
688// TypeError if needed. That's why we would like to handle Percentage separately and we don't check
689// the range of Number and Percentage.
690//
691// [1] https://drafts.csswg.org/web-animations-1/#process-a-keyframes-argument
692#[derive(Debug, Parse)]
693pub enum KeyframeOffset {
694    /// The double value, e.g. 0.5.
695    Number(Number),
696    /// The percentage (including calc() percentage), e.g. 10%, calc(50%).
697    // FIXME: Bug 2007780. Support length and percentage.
698    Percentage(Percentage),
699    /// The pair of TimelineRangeName and percentage.
700    KeyframeSelector(KeyframeSelector),
701}