diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /servo/components/style/stylesheets/keyframes_rule.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style/stylesheets/keyframes_rule.rs')
-rw-r--r-- | servo/components/style/stylesheets/keyframes_rule.rs | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/servo/components/style/stylesheets/keyframes_rule.rs b/servo/components/style/stylesheets/keyframes_rule.rs new file mode 100644 index 0000000000..6e5016080e --- /dev/null +++ b/servo/components/style/stylesheets/keyframes_rule.rs @@ -0,0 +1,691 @@ +/* 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; +use crate::properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction; +use crate::properties::LonghandIdSet; +use crate::properties::{Importance, PropertyDeclaration}; +use crate::properties::{LonghandId, PropertyDeclarationBlock, PropertyId}; +use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration}; +use crate::shared_lock::{DeepCloneParams, 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, CowRcStr, 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<Arc<Locked<Keyframe>>>, + /// Vendor prefix type the @keyframes has. + pub vendor_prefix: Option<VendorPrefix>, + /// 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: + /// <https://drafts.csswg.org/css-animations-1/#interface-csskeyframesrule-findrule> + pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option<usize> { + 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, + params: &DeepCloneParams, + ) -> 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, params)), + ) + }) + .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<W>(&self, dest: &mut CssWriter<W>) -> 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<KeyframePercentage, ParseError<'i>> { + 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<KeyframePercentage>); + +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<KeyframePercentage>) -> KeyframeSelector { + KeyframeSelector(percentages) + } + + /// Parse a keyframe selector from CSS input. + pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { + 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<Locked<PropertyDeclarationBlock>>, + + /// 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<Arc<Locked<Self>>, 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 declarations = SourcePropertyDeclaration::default(); + let mut rule_parser = KeyframeListParser { + context: &mut context, + shared_lock: &lock, + declarations: &mut declarations, + }; + 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, + _params: &DeepCloneParams, + ) -> 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<Locked<PropertyDeclarationBlock>>, + }, + /// 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<SpecifiedTimingFunction> { + 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<SpecifiedComposition> { + 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<KeyframesStep>, + /// The properties that change in this animation. + pub properties_changed: LonghandIdSet, + /// Vendor prefix type the @keyframes has. + pub vendor_prefix: Option<VendorPrefix>, +} + +/// Get all the animated properties in a keyframes animation. +fn get_animated_properties( + keyframes: &[Arc<Locked<Keyframe>>], + guard: &SharedRwLockReadGuard, +) -> LonghandIdSet { + let mut ret = LonghandIdSet::new(); + // 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 longhand_id = match declaration.id() { + PropertyDeclarationId::Longhand(id) => id, + _ => continue, + }; + + if longhand_id == LonghandId::Display { + continue; + } + + if !longhand_id.is_animatable() { + continue; + } + + ret.insert(longhand_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<Locked<Keyframe>>], + vendor_prefix: Option<VendorPrefix>, + guard: &SharedRwLockReadGuard, + ) -> Self { + let mut result = KeyframesAnimation { + steps: vec![], + properties_changed: LonghandIdSet::new(), + 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, + declarations: &'a mut SourcePropertyDeclaration, +} + +/// Parses a keyframe list from CSS input. +pub fn parse_keyframe_list<'a>( + context: &mut ParserContext<'a>, + input: &mut Parser, + shared_lock: &SharedRwLock, +) -> Vec<Arc<Locked<Keyframe>>> { + let mut declarations = SourcePropertyDeclaration::default(); + let mut parser = KeyframeListParser { + context, + shared_lock, + declarations: &mut declarations, + }; + 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<Locked<Keyframe>>; + type Error = StyleParseErrorKind<'i>; +} + +impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeListParser<'a, 'b> { + type Declaration = Arc<Locked<Keyframe>>; + type Error = StyleParseErrorKind<'i>; +} + +impl<'a, 'b, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a, 'b> { + type Prelude = KeyframeSelector; + type QualifiedRule = Arc<Locked<Keyframe>>; + type Error = StyleParseErrorKind<'i>; + + fn parse_prelude<'t>( + &mut self, + input: &mut Parser<'i, 't>, + ) -> Result<Self::Prelude, ParseError<'i>> { + 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<Self::QualifiedRule, ParseError<'i>> { + let mut block = PropertyDeclarationBlock::new(); + let declarations = &mut self.declarations; + self.context + .nest_for_rule(CssRuleType::Keyframe, |context| { + let mut parser = KeyframeDeclarationParser { + context: &context, + declarations, + }; + let mut iter = RuleBodyParser::new(input, &mut parser); + while let Some(declaration) = iter.next() { + match declaration { + Ok(()) => { + block.extend(iter.parser.declarations.drain(), Importance::Normal); + }, + Err((error, slice)) => { + iter.parser.declarations.clear(); + let location = error.location; + let error = + ContextualParseError::UnsupportedKeyframePropertyDeclaration( + slice, error, + ); + context.log_css_error(location, error); + }, + } + // `parse_important` is not called here, `!important` is not allowed in keyframe blocks. + } + }); + 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<Locked<Keyframe>>, StyleParseErrorKind<'i>> + for KeyframeListParser<'a, 'b> +{ + fn parse_qualified(&self) -> bool { + true + } + fn parse_declarations(&self) -> bool { + false + } +} + +struct KeyframeDeclarationParser<'a, 'b: 'a> { + context: &'a ParserContext<'b>, + declarations: &'a mut SourcePropertyDeclaration, +} + +/// Default methods reject all at rules. +impl<'a, 'b, 'i> AtRuleParser<'i> for KeyframeDeclarationParser<'a, 'b> { + type Prelude = (); + type AtRule = (); + type Error = StyleParseErrorKind<'i>; +} + +impl<'a, 'b, 'i> QualifiedRuleParser<'i> for KeyframeDeclarationParser<'a, 'b> { + type Prelude = (); + type QualifiedRule = (); + type Error = StyleParseErrorKind<'i>; +} + +impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeDeclarationParser<'a, 'b> { + type Declaration = (); + type Error = StyleParseErrorKind<'i>; + + fn parse_value<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + let id = match PropertyId::parse(&name, self.context) { + Ok(id) => id, + Err(()) => { + return Err(input.new_custom_error(StyleParseErrorKind::UnknownProperty(name))); + }, + }; + + // TODO(emilio): Shouldn't this use parse_entirely? + PropertyDeclaration::parse_into(self.declarations, id, self.context, input)?; + + // In case there is still unparsed text in the declaration, we should + // roll back. + input.expect_exhausted()?; + + Ok(()) + } +} + +impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> + for KeyframeDeclarationParser<'a, 'b> +{ + fn parse_qualified(&self) -> bool { + false + } + fn parse_declarations(&self) -> bool { + true + } +} |