/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Keyframes: https://drafts.csswg.org/css-animations/#keyframes use crate::error_reporting::ContextualParseError; use crate::parser::ParserContext; use crate::properties::{ longhands::{ animation_composition::single_value::SpecifiedValue as SpecifiedComposition, transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction, }, parse_property_declaration_list, LonghandId, PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId, PropertyDeclarationIdSet, }; use crate::shared_lock::{DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard}; use crate::shared_lock::{Locked, ToCssWithGuard}; use crate::str::CssStringWriter; use crate::stylesheets::rule_parser::VendorPrefix; use crate::stylesheets::{CssRuleType, StylesheetContents}; use crate::values::{serialize_percentage, KeyframesName}; use cssparser::{ parse_one_rule, AtRuleParser, DeclarationParser, Parser, ParserInput, ParserState, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, Token, }; use servo_arc::Arc; use std::borrow::Cow; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss}; /// A [`@keyframes`][keyframes] rule. /// /// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes #[derive(Debug, ToShmem)] pub struct KeyframesRule { /// The name of the current animation. pub name: KeyframesName, /// The keyframes specified for this CSS rule. pub keyframes: Vec>>, /// Vendor prefix type the @keyframes has. pub vendor_prefix: Option, /// The line and column of the rule's source code. pub source_location: SourceLocation, } impl ToCssWithGuard for KeyframesRule { // Serialization of KeyframesRule is not specced. fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { dest.write_str("@keyframes ")?; self.name.to_css(&mut CssWriter::new(dest))?; dest.write_str(" {")?; let iter = self.keyframes.iter(); for lock in iter { dest.write_str("\n")?; let keyframe = lock.read_with(&guard); keyframe.to_css(guard, dest)?; } dest.write_str("\n}") } } impl KeyframesRule { /// Returns the index of the last keyframe that matches the given selector. /// If the selector is not valid, or no keyframe is found, returns None. /// /// Related spec: /// pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option { let mut input = ParserInput::new(selector); if let Ok(selector) = Parser::new(&mut input).parse_entirely(KeyframeSelector::parse) { for (i, keyframe) in self.keyframes.iter().enumerate().rev() { if keyframe.read_with(guard).selector == selector { return Some(i); } } } None } } impl DeepCloneWithLock for KeyframesRule { fn deep_clone_with_lock( &self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard, ) -> Self { KeyframesRule { name: self.name.clone(), keyframes: self .keyframes .iter() .map(|x| { Arc::new( lock.wrap(x.read_with(guard).deep_clone_with_lock(lock, guard)), ) }) .collect(), vendor_prefix: self.vendor_prefix.clone(), source_location: self.source_location.clone(), } } } /// A number from 0 to 1, indicating the percentage of the animation when this /// keyframe should run. #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)] pub struct KeyframePercentage(pub f32); impl ::std::cmp::Ord for KeyframePercentage { #[inline] fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { // We know we have a number from 0 to 1, so unwrap() here is safe. self.0.partial_cmp(&other.0).unwrap() } } impl ::std::cmp::Eq for KeyframePercentage {} impl ToCss for KeyframePercentage { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { serialize_percentage(self.0, dest) } } impl KeyframePercentage { /// Trivially constructs a new `KeyframePercentage`. #[inline] pub fn new(value: f32) -> KeyframePercentage { debug_assert!(value >= 0. && value <= 1.); KeyframePercentage(value) } fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { let token = input.next()?.clone(); match token { Token::Ident(ref identifier) if identifier.as_ref().eq_ignore_ascii_case("from") => { Ok(KeyframePercentage::new(0.)) }, Token::Ident(ref identifier) if identifier.as_ref().eq_ignore_ascii_case("to") => { Ok(KeyframePercentage::new(1.)) }, Token::Percentage { unit_value: percentage, .. } if percentage >= 0. && percentage <= 1. => Ok(KeyframePercentage::new(percentage)), _ => Err(input.new_unexpected_token_error(token)), } } } /// A keyframes selector is a list of percentages or from/to symbols, which are /// converted at parse time to percentages. #[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] #[css(comma)] pub struct KeyframeSelector(#[css(iterable)] Vec); impl KeyframeSelector { /// Return the list of percentages this selector contains. #[inline] pub fn percentages(&self) -> &[KeyframePercentage] { &self.0 } /// A dummy public function so we can write a unit test for this. pub fn new_for_unit_testing(percentages: Vec) -> KeyframeSelector { KeyframeSelector(percentages) } /// Parse a keyframe selector from CSS input. pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { input .parse_comma_separated(KeyframePercentage::parse) .map(KeyframeSelector) } } /// A keyframe. #[derive(Debug, ToShmem)] pub struct Keyframe { /// The selector this keyframe was specified from. pub selector: KeyframeSelector, /// The declaration block that was declared inside this keyframe. /// /// Note that `!important` rules in keyframes don't apply, but we keep this /// `Arc` just for convenience. pub block: Arc>, /// The line and column of the rule's source code. pub source_location: SourceLocation, } impl ToCssWithGuard for Keyframe { fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { self.selector.to_css(&mut CssWriter::new(dest))?; dest.write_str(" { ")?; self.block.read_with(guard).to_css(dest)?; dest.write_str(" }")?; Ok(()) } } impl Keyframe { /// Parse a CSS keyframe. pub fn parse<'i>( css: &'i str, parent_stylesheet_contents: &StylesheetContents, lock: &SharedRwLock, ) -> Result>, ParseError<'i>> { let url_data = parent_stylesheet_contents.url_data.read(); let namespaces = parent_stylesheet_contents.namespaces.read(); let mut context = ParserContext::new( parent_stylesheet_contents.origin, &url_data, Some(CssRuleType::Keyframe), ParsingMode::DEFAULT, parent_stylesheet_contents.quirks_mode, Cow::Borrowed(&*namespaces), None, None, ); let mut input = ParserInput::new(css); let mut input = Parser::new(&mut input); let mut rule_parser = KeyframeListParser { context: &mut context, shared_lock: &lock, }; parse_one_rule(&mut input, &mut rule_parser) } } impl DeepCloneWithLock for Keyframe { /// Deep clones this Keyframe. fn deep_clone_with_lock( &self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard, ) -> Keyframe { Keyframe { selector: self.selector.clone(), block: Arc::new(lock.wrap(self.block.read_with(guard).clone())), source_location: self.source_location.clone(), } } } /// A keyframes step value. This can be a synthetised keyframes animation, that /// is, one autogenerated from the current computed values, or a list of /// declarations to apply. /// /// TODO: Find a better name for this? #[derive(Clone, Debug, MallocSizeOf)] pub enum KeyframesStepValue { /// A step formed by a declaration block specified by the CSS. Declarations { /// The declaration block per se. #[cfg_attr( feature = "gecko", ignore_malloc_size_of = "XXX: Primary ref, measure if DMD says it's worthwhile" )] #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] block: Arc>, }, /// A synthetic step computed from the current computed values at the time /// of the animation. ComputedValues, } /// A single step from a keyframe animation. #[derive(Clone, Debug, MallocSizeOf)] pub struct KeyframesStep { /// The percentage of the animation duration when this step starts. pub start_percentage: KeyframePercentage, /// Declarations that will determine the final style during the step, or /// `ComputedValues` if this is an autogenerated step. pub value: KeyframesStepValue, /// Whether an animation-timing-function declaration exists in the list of /// declarations. /// /// This is used to know when to override the keyframe animation style. pub declared_timing_function: bool, /// Whether an animation-composition declaration exists in the list of /// declarations. /// /// This is used to know when to override the keyframe animation style. pub declared_composition: bool, } impl KeyframesStep { #[inline] fn new( start_percentage: KeyframePercentage, value: KeyframesStepValue, guard: &SharedRwLockReadGuard, ) -> Self { let mut declared_timing_function = false; let mut declared_composition = false; if let KeyframesStepValue::Declarations { ref block } = value { for prop_decl in block.read_with(guard).declarations().iter() { match *prop_decl { PropertyDeclaration::AnimationTimingFunction(..) => { declared_timing_function = true; }, PropertyDeclaration::AnimationComposition(..) => { declared_composition = true; }, _ => continue, } // Don't need to continue the loop if both are found. if declared_timing_function && declared_composition { break; } } } KeyframesStep { start_percentage, value, declared_timing_function, declared_composition, } } /// Return specified PropertyDeclaration. #[inline] fn get_declared_property<'a>( &'a self, guard: &'a SharedRwLockReadGuard, property: LonghandId, ) -> Option<&'a PropertyDeclaration> { match self.value { KeyframesStepValue::Declarations { ref block } => { let guard = block.read_with(guard); let (declaration, _) = guard .get(PropertyDeclarationId::Longhand(property)) .unwrap(); match *declaration { PropertyDeclaration::CSSWideKeyword(..) => None, // FIXME: Bug 1710735: Support css variable in @keyframes rule. PropertyDeclaration::WithVariables(..) => None, _ => Some(declaration), } }, KeyframesStepValue::ComputedValues => { panic!("Shouldn't happen to set this property in missing keyframes") }, } } /// Return specified TransitionTimingFunction if this KeyframesSteps has /// 'animation-timing-function'. pub fn get_animation_timing_function( &self, guard: &SharedRwLockReadGuard, ) -> Option { if !self.declared_timing_function { return None; } self.get_declared_property(guard, LonghandId::AnimationTimingFunction) .map(|decl| { match *decl { PropertyDeclaration::AnimationTimingFunction(ref value) => { // Use the first value value.0[0].clone() }, _ => unreachable!("Unexpected PropertyDeclaration"), } }) } /// Return CompositeOperation if this KeyframesSteps has 'animation-composition'. pub fn get_animation_composition( &self, guard: &SharedRwLockReadGuard, ) -> Option { if !self.declared_composition { return None; } self.get_declared_property(guard, LonghandId::AnimationComposition) .map(|decl| { match *decl { PropertyDeclaration::AnimationComposition(ref value) => { // Use the first value value.0[0].clone() }, _ => unreachable!("Unexpected PropertyDeclaration"), } }) } } /// This structure represents a list of animation steps computed from the list /// of keyframes, in order. /// /// It only takes into account animable properties. #[derive(Clone, Debug, MallocSizeOf)] pub struct KeyframesAnimation { /// The difference steps of the animation. pub steps: Vec, /// The properties that change in this animation. pub properties_changed: PropertyDeclarationIdSet, /// Vendor prefix type the @keyframes has. pub vendor_prefix: Option, } /// Get all the animated properties in a keyframes animation. fn get_animated_properties( keyframes: &[Arc>], guard: &SharedRwLockReadGuard, ) -> PropertyDeclarationIdSet { let mut ret = PropertyDeclarationIdSet::default(); // NB: declarations are already deduplicated, so we don't have to check for // it here. for keyframe in keyframes { let keyframe = keyframe.read_with(&guard); let block = keyframe.block.read_with(guard); // CSS Animations spec clearly defines that properties with !important // in keyframe rules are invalid and ignored, but it's still ambiguous // whether we should drop the !important properties or retain the // properties when they are set via CSSOM. So we assume there might // be properties with !important in keyframe rules here. // See the spec issue https://github.com/w3c/csswg-drafts/issues/1824 for declaration in block.normal_declaration_iter() { let declaration_id = declaration.id(); if declaration_id == PropertyDeclarationId::Longhand(LonghandId::Display) { continue; } if !declaration_id.is_animatable() { continue; } ret.insert(declaration_id); } } ret } impl KeyframesAnimation { /// Create a keyframes animation from a given list of keyframes. /// /// This will return a keyframe animation with empty steps and /// properties_changed if the list of keyframes is empty, or there are no /// animated properties obtained from the keyframes. /// /// Otherwise, this will compute and sort the steps used for the animation, /// and return the animation object. pub fn from_keyframes( keyframes: &[Arc>], vendor_prefix: Option, guard: &SharedRwLockReadGuard, ) -> Self { let mut result = KeyframesAnimation { steps: vec![], properties_changed: PropertyDeclarationIdSet::default(), vendor_prefix, }; if keyframes.is_empty() { return result; } result.properties_changed = get_animated_properties(keyframes, guard); if result.properties_changed.is_empty() { return result; } for keyframe in keyframes { let keyframe = keyframe.read_with(&guard); for percentage in keyframe.selector.0.iter() { result.steps.push(KeyframesStep::new( *percentage, KeyframesStepValue::Declarations { block: keyframe.block.clone(), }, guard, )); } } // Sort by the start percentage, so we can easily find a frame. result.steps.sort_by_key(|step| step.start_percentage); // Prepend autogenerated keyframes if appropriate. if result.steps[0].start_percentage.0 != 0. { result.steps.insert( 0, KeyframesStep::new( KeyframePercentage::new(0.), KeyframesStepValue::ComputedValues, guard, ), ); } if result.steps.last().unwrap().start_percentage.0 != 1. { result.steps.push(KeyframesStep::new( KeyframePercentage::new(1.), KeyframesStepValue::ComputedValues, guard, )); } result } } /// Parses a keyframes list, like: /// 0%, 50% { /// width: 50%; /// } /// /// 40%, 60%, 100% { /// width: 100%; /// } struct KeyframeListParser<'a, 'b> { context: &'a mut ParserContext<'b>, shared_lock: &'a SharedRwLock, } /// Parses a keyframe list from CSS input. pub fn parse_keyframe_list<'a>( context: &mut ParserContext<'a>, input: &mut Parser, shared_lock: &SharedRwLock, ) -> Vec>> { let mut parser = KeyframeListParser { context, shared_lock, }; RuleBodyParser::new(input, &mut parser) .filter_map(Result::ok) .collect() } impl<'a, 'b, 'i> AtRuleParser<'i> for KeyframeListParser<'a, 'b> { type Prelude = (); type AtRule = Arc>; type Error = StyleParseErrorKind<'i>; } impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeListParser<'a, 'b> { type Declaration = Arc>; type Error = StyleParseErrorKind<'i>; } impl<'a, 'b, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a, 'b> { type Prelude = KeyframeSelector; type QualifiedRule = Arc>; type Error = StyleParseErrorKind<'i>; fn parse_prelude<'t>( &mut self, input: &mut Parser<'i, 't>, ) -> Result> { let start_position = input.position(); KeyframeSelector::parse(input).map_err(|e| { let location = e.location; let error = ContextualParseError::InvalidKeyframeRule( input.slice_from(start_position), e.clone(), ); self.context.log_css_error(location, error); e }) } fn parse_block<'t>( &mut self, selector: Self::Prelude, start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result> { let block = self.context.nest_for_rule(CssRuleType::Keyframe, |p| { parse_property_declaration_list(&p, input, &[]) }); Ok(Arc::new(self.shared_lock.wrap(Keyframe { selector, block: Arc::new(self.shared_lock.wrap(block)), source_location: start.source_location(), }))) } } impl<'a, 'b, 'i> RuleBodyItemParser<'i, Arc>, StyleParseErrorKind<'i>> for KeyframeListParser<'a, 'b> { fn parse_qualified(&self) -> bool { true } fn parse_declarations(&self) -> bool { false } }