diff --git a/components/layout/animation.rs b/components/layout/animation.rs index c28a4c33d8e..8f0ac53bd45 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -14,16 +14,17 @@ use script_traits::{AnimationState, LayoutMsg as ConstellationMsg}; use std::collections::HashMap; use std::sync::mpsc::Receiver; use style::animation::{Animation, update_style_for_animation}; +use style::selector_impl::{SelectorImplExt, ServoSelectorImpl}; use time; /// Processes any new animations that were discovered after style recalculation. /// Also expire any old animations that have completed, inserting them into /// `expired_animations`. -pub fn update_animation_state(constellation_chan: &IpcSender, - running_animations: &mut HashMap>, - expired_animations: &mut HashMap>, - new_animations_receiver: &Receiver, - pipeline_id: PipelineId) { +pub fn update_animation_state(constellation_chan: &IpcSender, + running_animations: &mut HashMap>>, + expired_animations: &mut HashMap>>, + new_animations_receiver: &Receiver>, + pipeline_id: PipelineId) { let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { let mut should_push = true; @@ -118,9 +119,13 @@ pub fn update_animation_state(constellation_chan: &IpcSender, /// Recalculates style for a set of animations. This does *not* run with the DOM /// lock held. +// NB: This is specific for ServoSelectorImpl, since the layout context and the +// flows are ServoSelectorImpl specific too. If that goes away at some point, +// this should be made generic. pub fn recalc_style_for_animations(context: &SharedLayoutContext, flow: &mut Flow, - animations: &HashMap>) { + animations: &HashMap>>) { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { if let Some(ref animations) = animations.get(&fragment.node) { diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 0f5e558305a..e47670f99c1 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -98,7 +98,6 @@ use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::{Arc, Mutex, MutexGuard, RwLock}; -use style::animation::Animation; use style::computed_values::{filter, mix_blend_mode}; use style::context::ReflowGoal; use style::dom::{TDocument, TElement, TNode}; @@ -109,7 +108,7 @@ use style::parallel::WorkQueueData; use style::properties::ComputedValues; use style::refcell::RefCell; use style::selector_matching::USER_OR_USER_AGENT_STYLESHEETS; -use style::servo::{SharedStyleContext, Stylesheet, Stylist}; +use style::servo::{Animation, SharedStyleContext, Stylesheet, Stylist}; use style::stylesheets::CSSRuleIteratorExt; use url::Url; use util::geometry::MAX_RECT; diff --git a/components/style/animation.rs b/components/style/animation.rs index 28dfb2b7a92..4c8bfdc9bf4 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -4,12 +4,11 @@ //! CSS transitions and animations. -use app_units::Au; use bezier::Bezier; use context::SharedStyleContext; use dom::{OpaqueNode, TRestyleDamage}; use euclid::point::Point2D; -use keyframes::KeyframesStep; +use keyframes::{KeyframesStep, KeyframesStepValue}; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::animation_direction::computed_value::AnimationDirection; use properties::longhands::animation_iteration_count::computed_value::AnimationIterationCount; @@ -54,7 +53,7 @@ pub enum KeyframesRunningState { /// playing or paused). // TODO: unify the use of f32/f64 in this file. #[derive(Debug, Clone)] -pub struct KeyframesAnimationState { +pub struct KeyframesAnimationState { /// The time this animation started at. pub started_at: f64, /// The duration of this animation. @@ -71,9 +70,12 @@ pub struct KeyframesAnimationState { pub current_direction: AnimationDirection, /// Werther this keyframe animation is outdated due to a restyle. pub expired: bool, + /// The original cascade style, needed to compute the generated keyframes of + /// the animation. + pub cascade_style: Arc, } -impl KeyframesAnimationState { +impl KeyframesAnimationState { /// Performs a tick in the animation state, i.e., increments the counter of /// the current iteration count, updates times and then toggles the /// direction if appropriate. @@ -121,7 +123,8 @@ impl KeyframesAnimationState { /// /// There are some bits of state we can't just replace, over all taking in /// account times, so here's that logic. - pub fn update_from_other(&mut self, other: &Self) { + pub fn update_from_other(&mut self, other: &Self) + where Self: Clone { use self::KeyframesRunningState::*; debug!("KeyframesAnimationState::update_from_other({:?}, {:?})", self, other); @@ -167,7 +170,7 @@ impl KeyframesAnimationState { /// State relating to an animation. #[derive(Clone, Debug)] -pub enum Animation { +pub enum Animation { /// A transition is just a single frame triggered at a time, with a reflow. /// /// the f64 field is the start time as returned by `time::precise_time_s()`. @@ -176,10 +179,10 @@ pub enum Animation { Transition(OpaqueNode, f64, AnimationFrame, bool), /// A keyframes animation is identified by a name, and can have a /// node-dependent state (i.e. iteration count, etc.). - Keyframes(OpaqueNode, Atom, KeyframesAnimationState), + Keyframes(OpaqueNode, Atom, KeyframesAnimationState), } -impl Animation { +impl Animation { #[inline] pub fn mark_as_expired(&mut self) { debug_assert!(!self.is_expired()); @@ -344,18 +347,20 @@ impl GetMod for Vec { // // TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a // cloneable part and a non-cloneable part.. -pub fn start_transitions_if_applicable(new_animations_sender: &Mutex>, - node: OpaqueNode, - old_style: &C, - new_style: &mut C) - -> bool { +pub fn start_transitions_if_applicable(new_animations_sender: &Mutex>>, + node: OpaqueNode, + old_style: &Impl::ComputedValues, + new_style: &mut Arc) + -> bool { let mut had_animations = false; for i in 0..new_style.get_box().transition_count() { // Create any property animations, if applicable. - let property_animations = PropertyAnimation::from_transition(i, old_style, new_style); + let property_animations = PropertyAnimation::from_transition(i, old_style, Arc::make_mut(new_style)); for property_animation in property_animations { // Set the property to the initial value. - property_animation.update(new_style, 0.0); + // NB: get_mut is guaranteed to succeed since we called make_mut() + // above. + property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0); // Kick off the animation. let now = time::precise_time_s(); @@ -378,24 +383,33 @@ pub fn start_transitions_if_applicable(new_animations_sender: fn compute_style_for_animation_step(context: &SharedStyleContext, step: &KeyframesStep, - old_style: &Impl::ComputedValues) -> Impl::ComputedValues { - let declaration_block = DeclarationBlock { - declarations: step.declarations.clone(), - source_order: 0, - specificity: ::std::u32::MAX, - }; - let (computed, _) = properties::cascade(context.viewport_size, - &[declaration_block], - false, - Some(old_style), - None, - context.error_reporter.clone()); - computed + previous_style: &Impl::ComputedValues, + style_from_cascade: &Impl::ComputedValues) + -> Impl::ComputedValues { + match step.value { + // TODO: avoiding this spurious clone might involve having to create + // an Arc in the below (more common case). + KeyframesStepValue::ComputedValues => style_from_cascade.clone(), + KeyframesStepValue::Declarations(ref declarations) => { + let declaration_block = DeclarationBlock { + declarations: declarations.clone(), + source_order: 0, + specificity: ::std::u32::MAX, + }; + let (computed, _) = properties::cascade(context.viewport_size, + &[declaration_block], + false, + Some(previous_style), + None, + context.error_reporter.clone()); + computed + } + } } pub fn maybe_start_animations(context: &SharedStyleContext, node: OpaqueNode, - new_style: &Impl::ComputedValues) -> bool + new_style: &Arc) -> bool { let mut had_animations = false; @@ -407,8 +421,17 @@ pub fn maybe_start_animations(context: &SharedStyleContex continue } - if context.stylist.animations().get(&name).is_some() { + if let Some(ref anim) = context.stylist.animations().get(&name) { debug!("maybe_start_animations: animation {} found", name); + + // If this animation doesn't have any keyframe, we can just continue + // without submitting it to the compositor, since both the first and + // the second keyframes would be synthetised from the computed + // values. + if anim.steps.is_empty() { + continue; + } + let delay = box_style.animation_delay.0.get_mod(i).seconds(); let now = time::precise_time_s(); let animation_start = now + delay as f64; @@ -444,6 +467,7 @@ pub fn maybe_start_animations(context: &SharedStyleContex direction: animation_direction, current_direction: initial_direction, expired: false, + cascade_style: new_style.clone(), })).unwrap(); had_animations = true; } @@ -474,7 +498,7 @@ pub fn update_style_for_animation_frame(mut new_style: &mut A /// Updates a single animation and associated style based on the current time. /// If `damage` is provided, inserts the appropriate restyle damage. pub fn update_style_for_animation(context: &SharedStyleContext, - animation: &Animation, + animation: &Animation, style: &mut Arc, damage: Option<&mut Damage>) where Impl: SelectorImplExt, @@ -516,6 +540,8 @@ where Impl: SelectorImplExt, Some(animation) => animation, }; + debug_assert!(!animation.steps.is_empty()); + let maybe_index = style.as_servo() .get_box().animation_name.0.iter() .position(|animation_name| name == animation_name); @@ -579,8 +605,6 @@ where Impl: SelectorImplExt, let target_keyframe = match target_keyframe_position { Some(target) => &animation.steps[target], None => { - // TODO: The 0. case falls here, maybe we should just resort - // to the first keyframe instead. warn!("update_style_for_animation: No current keyframe found for animation \"{}\" at progress {}", name, total_progress); return; @@ -605,7 +629,8 @@ where Impl: SelectorImplExt, // TODO: How could we optimise it? Is it such a big deal? let from_style = compute_style_for_animation_step(context, last_keyframe, - &**style); + &**style, + &state.cascade_style); // NB: The spec says that the timing function can be overwritten // from the keyframe style. @@ -616,7 +641,8 @@ where Impl: SelectorImplExt, let target_style = compute_style_for_animation_step(context, target_keyframe, - &from_style); + &from_style, + &state.cascade_style); let mut new_style = (*style).clone(); diff --git a/components/style/context.rs b/components/style/context.rs index 839d7b81a2e..041b52d2a17 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -34,16 +34,16 @@ pub struct SharedStyleContext { /// A channel on which new animations that have been triggered by style recalculation can be /// sent. - pub new_animations_sender: Mutex>, + pub new_animations_sender: Mutex>>, /// Why is this reflow occurring pub goal: ReflowGoal, /// The animations that are currently running. - pub running_animations: Arc>>>, + pub running_animations: Arc>>>>, /// The list of animations that have expired since the last style recalculation. - pub expired_animations: Arc>>>, + pub expired_animations: Arc>>>>, ///The CSS error reporter for all CSS loaded in this layout thread pub error_reporter: Box, diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 07e504a5988..cbfa35b78c4 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -2,33 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use cssparser::{Parser, Delimiter}; -use parser::ParserContext; +use cssparser::{AtRuleParser, Delimiter, Parser, QualifiedRuleParser, RuleListParser}; +use parser::{ParserContext, log_css_error}; use properties::animated_properties::TransitionProperty; use properties::{PropertyDeclaration, parse_property_declaration_list}; use std::sync::Arc; -/// Parses a keyframes list, like: -/// 0%, 50% { -/// width: 50%; -/// } -/// -/// 40%, 60%, 100% { -/// width: 100%; -/// } -pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Result, ()> { - let mut keyframes = vec![]; - while !input.is_exhausted() { - keyframes.push(try!(Keyframe::parse(context, input))); - } - - if keyframes.len() < 2 { - return Err(()) - } - - Ok(keyframes) -} - /// A number from 1 to 100, indicating the percentage of the animation where /// this keyframe should run. #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, HeapSizeOf)] @@ -57,7 +36,11 @@ impl KeyframePercentage { } else if input.try(|input| input.expect_ident_matching("to")).is_ok() { KeyframePercentage::new(1.) } else { - KeyframePercentage::new(try!(input.expect_percentage())) + let percentage = try!(input.expect_percentage()); + if percentage > 1. || percentage < 0. { + return Err(()); + } + KeyframePercentage::new(percentage) }; Ok(percentage) @@ -100,8 +83,7 @@ impl Keyframe { Ok(parse_property_declaration_list(context, input)) }).unwrap(); - // NB: Other browsers seem to ignore important declarations in keyframe - // animations too. + // NB: Important declarations are explicitely ignored in the spec. Ok(Keyframe { selector: selector, declarations: declarations.normal, @@ -109,22 +91,33 @@ impl Keyframe { } } +/// 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(Debug, Clone, PartialEq, HeapSizeOf)] +pub enum KeyframesStepValue { + Declarations(Arc>), + ComputedValues, +} + /// A single step from a keyframe animation. #[derive(Debug, Clone, PartialEq, HeapSizeOf)] 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. - pub declarations: Arc>, + /// Declarations that will determine the final style during the step, or + /// `ComputedValues` if this is an autogenerated step. + pub value: KeyframesStepValue, } impl KeyframesStep { #[inline] fn new(percentage: KeyframePercentage, - declarations: Arc>) -> Self { + value: KeyframesStepValue) -> Self { KeyframesStep { start_percentage: percentage, - declarations: declarations, + value: value, } } } @@ -161,24 +154,34 @@ fn get_animated_properties(keyframe: &Keyframe) -> Vec { impl KeyframesAnimation { pub fn from_keyframes(keyframes: &[Keyframe]) -> Option { - debug_assert!(keyframes.len() > 1); - let mut steps = vec![]; - let animated_properties = get_animated_properties(&keyframes[0]); - if animated_properties.is_empty() { + if keyframes.is_empty() || animated_properties.is_empty() { return None; } + let mut steps = vec![]; + for keyframe in keyframes { for percentage in keyframe.selector.0.iter() { steps.push(KeyframesStep::new(*percentage, - keyframe.declarations.clone())); + KeyframesStepValue::Declarations(keyframe.declarations.clone()))); } } // Sort by the start percentage, so we can easily find a frame. steps.sort_by_key(|step| step.start_percentage); + // Prepend autogenerated keyframes if appropriate. + if steps[0].start_percentage.0 != 0. { + steps.insert(0, KeyframesStep::new(KeyframePercentage::new(0.), + KeyframesStepValue::ComputedValues)); + } + + if steps.last().unwrap().start_percentage.0 != 1. { + steps.push(KeyframesStep::new(KeyframePercentage::new(0.), + KeyframesStepValue::ComputedValues)); + } + Some(KeyframesAnimation { steps: steps, properties_changed: animated_properties, @@ -186,3 +189,54 @@ impl KeyframesAnimation { } } +/// Parses a keyframes list, like: +/// 0%, 50% { +/// width: 50%; +/// } +/// +/// 40%, 60%, 100% { +/// width: 100%; +/// } +struct KeyframeListParser<'a> { + context: &'a ParserContext<'a>, +} + +pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Vec { + RuleListParser::new_for_nested_rule(input, KeyframeListParser { context: context }) + .filter_map(Result::ok) + .collect() +} + +enum Void {} +impl<'a> AtRuleParser for KeyframeListParser<'a> { + type Prelude = Void; + type AtRule = Keyframe; +} + +impl<'a> QualifiedRuleParser for KeyframeListParser<'a> { + type Prelude = KeyframeSelector; + type QualifiedRule = Keyframe; + + fn parse_prelude(&self, input: &mut Parser) -> Result { + let start = input.position(); + match input.parse_comma_separated(|input| KeyframePercentage::parse(input)) { + Ok(percentages) => Ok(KeyframeSelector(percentages)), + Err(()) => { + let message = format!("Invalid keyframe rule: '{}'", input.slice_from(start)); + log_css_error(input, start, &message, self.context); + Err(()) + } + } + } + + fn parse_block(&self, prelude: Self::Prelude, input: &mut Parser) + -> Result { + Ok(Keyframe { + selector: prelude, + // FIXME: needs parsing different from parse_property_declaration_list: + // https://drafts.csswg.org/css-animations/#keyframes + // Paragraph "The inside of ..." + declarations: parse_property_declaration_list(self.context, input).normal, + }) + } +} diff --git a/components/style/matching.rs b/components/style/matching.rs index aed3958a36e..1f85effdb33 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -386,7 +386,7 @@ trait PrivateMatchMethods: TNode cacheable = !self.update_animations_for_cascade(context, &mut style) && cacheable; } - let mut this_style; + let this_style; match parent_style { Some(ref parent_style) => { let cache_entry = applicable_declarations_cache.find(applicable_declarations); @@ -416,6 +416,8 @@ trait PrivateMatchMethods: TNode } }; + let mut this_style = Arc::new(this_style); + if animate_properties { let this_opaque = self.opaque(); // Trigger any present animations if necessary. @@ -428,7 +430,7 @@ trait PrivateMatchMethods: TNode // to its old value if it did trigger a transition. if let Some(ref style) = style { animations_started |= - animation::start_transitions_if_applicable::( + animation::start_transitions_if_applicable::<::Impl>( &context.new_animations_sender, this_opaque, &**style, @@ -439,7 +441,6 @@ trait PrivateMatchMethods: TNode } // Calculate style difference. - let this_style = Arc::new(this_style); let damage = Self::ConcreteRestyleDamage::compute(style.map(|s| &*s), &*this_style); // Cache the resolved style if it was cacheable. diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index e1a21572a85..c42f973205c 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -14,7 +14,7 @@ use std::ascii::AsciiExt; use std::boxed::Box as StdBox; use std::collections::HashSet; use std::fmt; -use std::fmt::Write; +use std::fmt::{Debug, Write}; use std::sync::Arc; use app_units::Au; @@ -1069,9 +1069,10 @@ impl PropertyDeclaration { pub mod style_struct_traits { use super::longhands; + use std::fmt::Debug; % for style_struct in data.active_style_structs(): - pub trait ${style_struct.trait_name}: Clone { + pub trait ${style_struct.trait_name}: Debug + Clone { % for longhand in style_struct.longhands: #[allow(non_snake_case)] fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T); @@ -1100,7 +1101,7 @@ pub mod style_structs { #[derive(Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] % else: - #[derive(PartialEq, Clone)] + #[derive(PartialEq, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] % endif pub struct ${style_struct.servo_struct_name} { @@ -1249,7 +1250,7 @@ pub mod style_structs { % endfor } -pub trait ComputedValues : Clone + Send + Sync + 'static { +pub trait ComputedValues : Debug + Clone + Send + Sync + 'static { % for style_struct in data.active_style_structs(): type Concrete${style_struct.trait_name}: style_struct_traits::${style_struct.trait_name}; % endfor @@ -1292,7 +1293,7 @@ pub trait ComputedValues : Clone + Send + Sync + 'static { fn is_multicol(&self) -> bool; } -#[derive(Clone)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct ServoComputedValues { % for style_struct in data.active_style_structs(): diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index a394ef987cc..7f80cb2ea38 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -9,6 +9,7 @@ use properties::{self, ServoComputedValues}; use selector_matching::{USER_OR_USER_AGENT_STYLESHEETS, QUIRKS_MODE_STYLESHEET}; use selectors::Element; use selectors::parser::{ParserContext, SelectorImpl}; +use std::fmt::Debug; use stylesheets::Stylesheet; /// This function determines if a pseudo-element is eagerly cascaded or not. @@ -62,7 +63,9 @@ pub trait ElementExt: Element { fn is_link(&self) -> bool; } -pub trait SelectorImplExt : SelectorImpl + Sized { +// NB: The `Clone` trait is here for convenience due to: +// https://github.com/rust-lang/rust/issues/26925 +pub trait SelectorImplExt : SelectorImpl + Clone + Debug + Sized { type ComputedValues: properties::ComputedValues; fn pseudo_element_cascade_type(pseudo: &Self::PseudoElement) -> PseudoElementCascadeType; diff --git a/components/style/servo.rs b/components/style/servo.rs index 8278713d26f..b592104832a 100644 --- a/components/style/servo.rs +++ b/components/style/servo.rs @@ -1,9 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - //! Concrete types for servo Style implementation +use animation; use context; use data; use properties::ServoComputedValues; @@ -15,3 +15,4 @@ pub type Stylesheet = stylesheets::Stylesheet; pub type PrivateStyleData = data::PrivateStyleData; pub type Stylist = selector_matching::Stylist; pub type SharedStyleContext = context::SharedStyleContext; +pub type Animation = animation::Animation; diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index 726db0251cf..98864868233 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -5,7 +5,7 @@ //! Style sheets and their CSS rules. use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, decode_stylesheet_bytes}; -use cssparser::{AtRuleType, RuleListParser}; +use cssparser::{AtRuleType, RuleListParser, Token}; use encoding::EncodingRef; use error_reporting::ParseErrorReporter; use font_face::{FontFaceRule, parse_font_face_block}; @@ -506,7 +506,12 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> } }, "keyframes" => { - let name = try!(input.expect_ident()); + let name = match input.next() { + Ok(Token::Ident(ref value)) if value != "none" => Atom::from(&**value), + Ok(Token::QuotedString(value)) => Atom::from(&*value), + _ => return Err(()) + }; + Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(Atom::from(name)))) }, _ => Err(()) @@ -530,7 +535,7 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> AtRulePrelude::Keyframes(name) => { Ok(CSSRule::Keyframes(KeyframesRule { name: name, - keyframes: try!(parse_keyframe_list(&self.context, input)), + keyframes: parse_keyframe_list(&self.context, input), })) } }