diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2016-06-28 17:31:01 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-28 17:31:01 -0500 |
commit | d3a81373e44634c30d31da0457e1c1e86c0911ed (patch) | |
tree | fbefd4e32ec3adff49b2ffec316f8efff6c647d4 | |
parent | 7b2080c5b7c933f74c0d92249a66f8602340ebbe (diff) | |
parent | 392f243ca7dd08a34da4ca15dfc5596f69adf4d8 (diff) | |
download | servo-d3a81373e44634c30d31da0457e1c1e86c0911ed.tar.gz servo-d3a81373e44634c30d31da0457e1c1e86c0911ed.zip |
Auto merge of #11766 - emilio:keyframes-parsing, r=SimonSapin,pcwalton
Add `@keyframes` and `animation-*` support.
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
<!-- Either: -->
- [x] There are tests for these changes
<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This adds support for parsing `@keyframes` rules, and animation properties. Stylo will need it sometime soonish, plus I want to make animations work in Servo.
The remaining part is doin the math and trigger the animations correctly from servo. I don't expect it to be *that* hard, but probaby I'll need to learn a bit more about the current animation infra (e.g. why the heck is the `new_animations_sender` guarded by a `Mutex`?).
I'd expect to land this, since this is already a bunch of work, this is the part exclusively required by stylo (at least if we don't use Servo's machinery), the media query parsing is tested, and the properties land after a flag, but if you prefer to wait until I finish this up it's fine for me too.
r? @SimonSapin
cc @pcwalton @bholley
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/11766)
<!-- Reviewable:end -->
51 files changed, 2640 insertions, 1404 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index be73d3895d9..1439f24b0b2 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -1136,7 +1136,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> let msg = LayoutControlMsg::TickAnimations; match self.pipelines.get(&pipeline_id) { Some(pipeline) => pipeline.layout_chan.send(msg), - None => return warn!("Pipeline {:?} got script tick after closure.", pipeline_id), + None => return warn!("Pipeline {:?} got layout tick after closure.", pipeline_id), } } }; diff --git a/components/layout/animation.rs b/components/layout/animation.rs index cc87a2824a4..8f0ac53bd45 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -4,6 +4,7 @@ //! CSS transitions and animations. +use context::SharedLayoutContext; use flow::{self, Flow}; use gfx::display_list::OpaqueNode; use ipc_channel::ipc::IpcSender; @@ -11,21 +12,44 @@ use msg::constellation_msg::PipelineId; use script_layout_interface::restyle_damage::RestyleDamage; use script_traits::{AnimationState, LayoutMsg as ConstellationMsg}; use std::collections::HashMap; -use std::collections::hash_map::Entry; 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<ConstellationMsg>, - running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, - expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, - new_animations_receiver: &Receiver<Animation>, - pipeline_id: PipelineId) { - let mut new_running_animations = Vec::new(); +/// Also expire any old animations that have completed, inserting them into +/// `expired_animations`. +pub fn update_animation_state<Impl: SelectorImplExt>(constellation_chan: &IpcSender<ConstellationMsg>, + running_animations: &mut HashMap<OpaqueNode, Vec<Animation<Impl>>>, + expired_animations: &mut HashMap<OpaqueNode, Vec<Animation<Impl>>>, + new_animations_receiver: &Receiver<Animation<Impl>>, + pipeline_id: PipelineId) { + let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { - new_running_animations.push(animation) + let mut should_push = true; + if let Animation::Keyframes(ref node, ref name, ref state) = animation { + // If the animation was already present in the list for the + // node, just update its state, else push the new animation to + // run. + if let Some(ref mut animations) = running_animations.get_mut(node) { + // TODO: This being linear is probably not optimal. + for mut anim in animations.iter_mut() { + if let Animation::Keyframes(_, ref anim_name, ref mut anim_state) = *anim { + if *name == *anim_name { + debug!("update_animation_state: Found other animation {}", name); + anim_state.update_from_other(&state); + should_push = false; + break; + } + } + } + } + } + + if should_push { + new_running_animations.push(animation); + } } if running_animations.is_empty() && new_running_animations.is_empty() { @@ -34,62 +58,82 @@ pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, return } - // Expire old running animations. let now = time::precise_time_s(); - let mut keys_to_remove = Vec::new(); + // Expire old running animations. + // + // TODO: Do not expunge Keyframes animations, since we need that state if + // the animation gets re-triggered. Probably worth splitting in two + // different maps, or at least using a linked list? + let mut keys_to_remove = vec![]; for (key, running_animations) in running_animations.iter_mut() { let mut animations_still_running = vec![]; - for running_animation in running_animations.drain(..) { - if now < running_animation.end_time { + for mut running_animation in running_animations.drain(..) { + let still_running = !running_animation.is_expired() && match running_animation { + Animation::Transition(_, started_at, ref frame, _expired) => { + now < started_at + frame.duration + } + Animation::Keyframes(_, _, ref mut state) => { + // This animation is still running, or we need to keep + // iterating. + now < state.started_at + state.duration || state.tick() + } + }; + + if still_running { animations_still_running.push(running_animation); continue } - match expired_animations.entry(*key) { - Entry::Vacant(entry) => { - entry.insert(vec![running_animation]); - } - Entry::Occupied(mut entry) => entry.get_mut().push(running_animation), - } + + expired_animations.entry(*key) + .or_insert_with(Vec::new) + .push(running_animation); } - if animations_still_running.len() == 0 { + + if animations_still_running.is_empty() { keys_to_remove.push(*key); } else { *running_animations = animations_still_running } } + for key in keys_to_remove { running_animations.remove(&key).unwrap(); } // Add new running animations. for new_running_animation in new_running_animations { - match running_animations.entry(new_running_animation.node) { - Entry::Vacant(entry) => { - entry.insert(vec![new_running_animation]); - } - Entry::Occupied(mut entry) => entry.get_mut().push(new_running_animation), - } + running_animations.entry(*new_running_animation.node()) + .or_insert_with(Vec::new) + .push(new_running_animation); } - let animation_state; - if running_animations.is_empty() { - animation_state = AnimationState::NoAnimationsPresent; + let animation_state = if running_animations.is_empty() { + AnimationState::NoAnimationsPresent } else { - animation_state = AnimationState::AnimationsPresent; - } + AnimationState::AnimationsPresent + }; constellation_chan.send(ConstellationMsg::ChangeRunningAnimationsState(pipeline_id, animation_state)) .unwrap(); } -/// Recalculates style for a set of animations. This does *not* run with the DOM lock held. -pub fn recalc_style_for_animations(flow: &mut Flow, - animations: &HashMap<OpaqueNode, Vec<Animation>>) { +/// 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<OpaqueNode, + Vec<Animation<ServoSelectorImpl>>>) { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { if let Some(ref animations) = animations.get(&fragment.node) { - for animation in *animations { - update_style_for_animation(animation, &mut fragment.style, Some(&mut damage)); + for animation in animations.iter() { + update_style_for_animation(&context.style_context, + animation, + &mut fragment.style, + Some(&mut damage)); } } }); @@ -97,6 +141,6 @@ pub fn recalc_style_for_animations(flow: &mut Flow, let base = flow::mut_base(flow); base.restyle_damage.insert(damage); for kid in base.children.iter_mut() { - recalc_style_for_animations(kid, animations) + recalc_style_for_animations(context, kid, animations) } } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index fad57c499d3..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; @@ -1290,7 +1289,7 @@ impl LayoutThread { self.tick_animations(&mut rw_data); } - pub fn tick_animations(&mut self, rw_data: &mut LayoutThreadData) { + fn tick_animations(&mut self, rw_data: &mut LayoutThreadData) { let reflow_info = Reflow { goal: ReflowGoal::ForDisplay, page_clip_rect: MAX_RECT, @@ -1307,8 +1306,9 @@ impl LayoutThread { self.profiler_metadata(), self.time_profiler_chan.clone(), || { - animation::recalc_style_for_animations(flow_ref::deref_mut(&mut root_flow), - &*animations) + animation::recalc_style_for_animations(&layout_context, + flow_ref::deref_mut(&mut root_flow), + &animations) }); } diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index cf7c3ade7f2..8d587b9760a 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -327,4 +327,21 @@ partial interface CSSStyleDeclaration { [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-shrink; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString alignSelf; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString align-self; + + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-name; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationName; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-duration; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDuration; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-timing-function; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationTimingFunction; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-iteration-count; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationIterationCount; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-direction; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDirection; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-play-state; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationPlayState; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-fill-mode; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationFillMode; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-delay; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDelay; }; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 6697f334696..96555524c5d 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -671,7 +671,7 @@ impl ScriptThread { } // Store new resizes, and gather all other events. - let mut sequential = vec!(); + let mut sequential = vec![]; // Receive at least one message so we don't spinloop. let mut event = { diff --git a/components/style/animation.rs b/components/style/animation.rs index 4a399a973f5..b2a64a5fa5e 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -4,83 +4,266 @@ //! CSS transitions and animations. -use app_units::Au; use bezier::Bezier; -use cssparser::{Color, RGBA}; +use context::SharedStyleContext; use dom::{OpaqueNode, TRestyleDamage}; use euclid::point::Point2D; -use properties::longhands::background_position::computed_value::T as BackgroundPosition; -use properties::longhands::border_spacing::computed_value::T as BorderSpacing; -use properties::longhands::clip::computed_value::ClipRect; -use properties::longhands::font_weight::computed_value::T as FontWeight; -use properties::longhands::line_height::computed_value::T as LineHeight; -use properties::longhands::text_shadow::computed_value::T as TextShadowList; -use properties::longhands::text_shadow::computed_value::TextShadow; -use properties::longhands::transform::computed_value::ComputedMatrix; -use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; -use properties::longhands::transform::computed_value::T as TransformList; -use properties::longhands::transition_property; -use properties::longhands::transition_property::computed_value::TransitionProperty; +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; +use properties::longhands::animation_play_state::computed_value::AnimationPlayState; use properties::longhands::transition_timing_function::computed_value::StartEnd; use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; -use properties::longhands::vertical_align::computed_value::T as VerticalAlign; -use properties::longhands::visibility::computed_value::T as Visibility; -use properties::longhands::z_index::computed_value::T as ZIndex; use properties::style_struct_traits::Box; -use properties::{ComputedValues, ServoComputedValues}; -use std::cmp::Ordering; -use std::iter::repeat; +use properties::{self, ComputedValues}; +use selector_impl::SelectorImplExt; +use selectors::matching::DeclarationBlock; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; +use string_cache::Atom; use time; -use values::CSSFloat; -use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; -use values::computed::{CalcLengthOrPercentage, Length, LengthOrPercentage, Time}; +use values::computed::Time; -/// State relating to an animation. -#[derive(Clone)] -pub struct Animation { - /// An opaque reference to the DOM node participating in the animation. - pub node: OpaqueNode, - /// A description of the property animation that is occurring. - pub property_animation: PropertyAnimation, - /// The start time of the animation, as returned by `time::precise_time_s()`. - pub start_time: f64, - /// The end time of the animation, as returned by `time::precise_time_s()`. - pub end_time: f64, +/// This structure represents a keyframes animation current iteration state. +/// +/// If the iteration count is infinite, there's no other state, otherwise we +/// have to keep track the current iteration and the max iteration count. +#[derive(Debug, Clone)] +pub enum KeyframesIterationState { + Infinite, + // current, max + Finite(u32, u32), } -impl Animation { - /// Returns the duration of this animation in seconds. +/// This structure represents wether an animation is actually running. +/// +/// An animation can be running, or paused at a given time. +#[derive(Debug, Clone)] +pub enum KeyframesRunningState { + /// This animation is paused. The inner field is the percentage of progress + /// when it was paused, from 0 to 1. + Paused(f64), + /// This animation is actually running. + Running, +} + +/// This structure represents the current keyframe animation state, i.e., the +/// duration, the current and maximum iteration count, and the state (either +/// playing or paused). +// TODO: unify the use of f32/f64 in this file. +#[derive(Debug, Clone)] +pub struct KeyframesAnimationState<Impl: SelectorImplExt> { + /// The time this animation started at. + pub started_at: f64, + /// The duration of this animation. + pub duration: f64, + /// The delay of the animation. + pub delay: f64, + /// The current iteration state for the animation. + pub iteration_state: KeyframesIterationState, + /// Werther this animation is paused. + pub running_state: KeyframesRunningState, + /// The declared animation direction of this animation. + pub direction: AnimationDirection, + /// The current animation direction. This can only be `normal` or `reverse`. + 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::ComputedValues>, +} + +impl<Impl: SelectorImplExt> KeyframesAnimationState<Impl> { + /// 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. + /// + /// Returns true if the animation should keep running. + pub fn tick(&mut self) -> bool { + debug!("KeyframesAnimationState::tick"); + + self.started_at += self.duration + self.delay; + match self.running_state { + // If it's paused, don't update direction or iteration count. + KeyframesRunningState::Paused(_) => return true, + KeyframesRunningState::Running => {}, + } + + if let KeyframesIterationState::Finite(ref mut current, ref max) = self.iteration_state { + *current += 1; + // NB: This prevent us from updating the direction, which might be + // needed for the correct handling of animation-fill-mode. + if *current >= *max { + return false; + } + } + + // Update the next iteration direction if applicable. + match self.direction { + AnimationDirection::alternate | + AnimationDirection::alternate_reverse => { + self.current_direction = match self.current_direction { + AnimationDirection::normal => AnimationDirection::reverse, + AnimationDirection::reverse => AnimationDirection::normal, + _ => unreachable!(), + }; + } + _ => {}, + } + + true + } + + /// Updates the appropiate state from other animation. + /// + /// This happens when an animation is re-submitted to layout, presumably + /// because of an state change. + /// + /// 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) { + use self::KeyframesRunningState::*; + + debug!("KeyframesAnimationState::update_from_other({:?}, {:?})", self, other); + + // NB: We shall not touch the started_at field, since we don't want to + // restart the animation. + let old_started_at = self.started_at; + let old_duration = self.duration; + let old_direction = self.current_direction; + let old_running_state = self.running_state.clone(); + let old_iteration_state = self.iteration_state.clone(); + *self = other.clone(); + + let mut new_started_at = old_started_at; + + // If we're unpausing the animation, fake the start time so we seem to + // restore it. + // + // If the animation keeps paused, keep the old value. + // + // If we're pausing the animation, compute the progress value. + match (&mut self.running_state, old_running_state) { + (&mut Running, Paused(progress)) + => new_started_at = time::precise_time_s() - (self.duration * progress), + (&mut Paused(ref mut new), Paused(old)) + => *new = old, + (&mut Paused(ref mut progress), Running) + => *progress = (time::precise_time_s() - old_started_at) / old_duration, + _ => {}, + } + + // Don't update the iteration count, just the iteration limit. + // TODO: see how changing the limit affects rendering in other browsers. + // We might need to keep the iteration count even when it's infinite. + match (&mut self.iteration_state, old_iteration_state) { + (&mut KeyframesIterationState::Finite(ref mut iters, _), KeyframesIterationState::Finite(old_iters, _)) + => *iters = old_iters, + _ => {} + } + + self.current_direction = old_direction; + self.started_at = new_started_at; + } + #[inline] - pub fn duration(&self) -> f64 { - self.end_time - self.start_time + fn is_paused(&self) -> bool { + match self.running_state { + KeyframesRunningState::Paused(..) => true, + KeyframesRunningState::Running => false, + } } } - +/// State relating to an animation. #[derive(Clone, Debug)] +pub enum Animation<Impl: SelectorImplExt> { + /// 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()`. + /// + /// The `bool` field is werther this animation should no longer run. + 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<Impl>), +} + +impl<Impl: SelectorImplExt> Animation<Impl> { + #[inline] + pub fn mark_as_expired(&mut self) { + debug_assert!(!self.is_expired()); + match *self { + Animation::Transition(_, _, _, ref mut expired) => *expired = true, + Animation::Keyframes(_, _, ref mut state) => state.expired = true, + } + } + + #[inline] + pub fn is_expired(&self) -> bool { + match *self { + Animation::Transition(_, _, _, expired) => expired, + Animation::Keyframes(_, _, ref state) => state.expired, + } + } + + #[inline] + pub fn node(&self) -> &OpaqueNode { + match *self { + Animation::Transition(ref node, _, _, _) => node, + Animation::Keyframes(ref node, _, _) => node, + } + } + + #[inline] + pub fn is_paused(&self) -> bool { + match *self { + Animation::Transition(..) => false, + Animation::Keyframes(_, _, ref state) => state.is_paused(), + } + } +} + + +/// A single animation frame of a single property. +#[derive(Debug, Clone)] +pub struct AnimationFrame { + /// A description of the property animation that is occurring. + pub property_animation: PropertyAnimation, + /// The duration of the animation. This is either relative in the keyframes + /// case (a number between 0 and 1), or absolute in the transition case. + pub duration: f64, +} + +#[derive(Debug, Clone)] pub struct PropertyAnimation { property: AnimatedProperty, timing_function: TransitionTimingFunction, - duration: Time, + duration: Time, // TODO: isn't this just repeated? } impl PropertyAnimation { /// Creates a new property animation for the given transition index and old and new styles. /// Any number of animations may be returned, from zero (if the property did not animate) to /// one (for a single transition property) to arbitrarily many (for `all`). - pub fn from_transition(transition_index: usize, - old_style: &ServoComputedValues, - new_style: &mut ServoComputedValues) - -> Vec<PropertyAnimation> { - let mut result = Vec::new(); - let transition_property = - new_style.as_servo().get_box().transition_property.0[transition_index]; + pub fn from_transition<C: ComputedValues>(transition_index: usize, + old_style: &C, + new_style: &mut C) + -> Vec<PropertyAnimation> { + let mut result = vec![]; + let box_style = new_style.as_servo().get_box(); + let transition_property = box_style.transition_property.0[transition_index]; + let timing_function = *box_style.transition_timing_function.0.get_mod(transition_index); + let duration = *box_style.transition_duration.0.get_mod(transition_index); + + if transition_property != TransitionProperty::All { if let Some(property_animation) = PropertyAnimation::from_transition_property(transition_property, - transition_index, + timing_function, + duration, old_style, new_style) { result.push(property_animation) @@ -88,118 +271,44 @@ impl PropertyAnimation { return result } - for transition_property in - transition_property::computed_value::ALL_TRANSITION_PROPERTIES.iter() { + TransitionProperty::each(|transition_property| { if let Some(property_animation) = - PropertyAnimation::from_transition_property(*transition_property, - transition_index, + PropertyAnimation::from_transition_property(transition_property, + timing_function, + duration, old_style, new_style) { result.push(property_animation) } - } + }); result } - fn from_transition_property(transition_property: TransitionProperty, - transition_index: usize, - old_style: &ServoComputedValues, - new_style: &mut ServoComputedValues) - -> Option<PropertyAnimation> { - let box_style = new_style.get_box(); - macro_rules! match_transition { - ( $( [$name:ident; $structname:ident; $field:ident] ),* ) => { - match transition_property { - TransitionProperty::All => { - panic!("Don't use `TransitionProperty::All` with \ - `PropertyAnimation::from_transition_property`!") - } - $( - TransitionProperty::$name => { - AnimatedProperty::$name(old_style.$structname().$field, - new_style.$structname().$field) - } - )* - TransitionProperty::Clip => { - AnimatedProperty::Clip(old_style.get_effects().clip.0, - new_style.get_effects().clip.0) - } - TransitionProperty::LetterSpacing => { - AnimatedProperty::LetterSpacing(old_style.get_inheritedtext().letter_spacing.0, - new_style.get_inheritedtext().letter_spacing.0) - } - TransitionProperty::TextShadow => { - AnimatedProperty::TextShadow(old_style.get_inheritedtext().text_shadow.clone(), - new_style.get_inheritedtext().text_shadow.clone()) - } - TransitionProperty::Transform => { - AnimatedProperty::Transform(old_style.get_effects().transform.clone(), - new_style.get_effects().transform.clone()) - } - TransitionProperty::WordSpacing => { - AnimatedProperty::WordSpacing(old_style.get_inheritedtext().word_spacing.0, - new_style.get_inheritedtext().word_spacing.0) - } - } - } - } - let animated_property = match_transition!( - [BackgroundColor; get_background; background_color], - [BackgroundPosition; get_background; background_position], - [BorderBottomColor; get_border; border_bottom_color], - [BorderBottomWidth; get_border; border_bottom_width], - [BorderLeftColor; get_border; border_left_color], - [BorderLeftWidth; get_border; border_left_width], - [BorderRightColor; get_border; border_right_color], - [BorderRightWidth; get_border; border_right_width], - [BorderSpacing; get_inheritedtable; border_spacing], - [BorderTopColor; get_border; border_top_color], - [BorderTopWidth; get_border; border_top_width], - [Bottom; get_position; bottom], - [Color; get_color; color], - [FontSize; get_font; font_size], - [FontWeight; get_font; font_weight], - [Height; get_position; height], - [Left; get_position; left], - [LineHeight; get_inheritedtext; line_height], - [MarginBottom; get_margin; margin_bottom], - [MarginLeft; get_margin; margin_left], - [MarginRight; get_margin; margin_right], - [MarginTop; get_margin; margin_top], - [MaxHeight; get_position; max_height], - [MaxWidth; get_position; max_width], - [MinHeight; get_position; min_height], - [MinWidth; get_position; min_width], - [Opacity; get_effects; opacity], - [OutlineColor; get_outline; outline_color], - [OutlineWidth; get_outline; outline_width], - [PaddingBottom; get_padding; padding_bottom], - [PaddingLeft; get_padding; padding_left], - [PaddingRight; get_padding; padding_right], - [PaddingTop; get_padding; padding_top], - [Right; get_position; right], - [TextIndent; get_inheritedtext; text_indent], - [Top; get_position; top], - [VerticalAlign; get_box; vertical_align], - [Visibility; get_inheritedbox; visibility], - [Width; get_position; width], - [ZIndex; get_position; z_index]); + fn from_transition_property<C: ComputedValues>(transition_property: TransitionProperty, + timing_function: TransitionTimingFunction, + duration: Time, + old_style: &C, + new_style: &C) + -> Option<PropertyAnimation> { + let animated_property = AnimatedProperty::from_transition_property(&transition_property, + old_style, + new_style); let property_animation = PropertyAnimation { property: animated_property, - timing_function: - *box_style.transition_timing_function.0.get_mod(transition_index), - duration: *box_style.transition_duration.0.get_mod(transition_index), + timing_function: timing_function, + duration: duration, }; - if property_animation.does_not_animate() { - None - } else { + + if property_animation.does_animate() { Some(property_animation) + } else { + None } } - pub fn update(&self, style: &mut ServoComputedValues, time: f64) { + pub fn update<C: ComputedValues>(&self, style: &mut C, time: f64) { let progress = match self.timing_function { TransitionTimingFunction::CubicBezier(p1, p2) => { // See `WebCore::AnimationBase::solveEpsilon(double)` in WebKit. @@ -215,800 +324,363 @@ impl PropertyAnimation { } }; - macro_rules! match_property( - ( $( [$name:ident; $structname:ident; $field:ident] ),* ) => { - match self.property { - $( - AnimatedProperty::$name(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.$structname().$field = value - } - } - )* - AnimatedProperty::Clip(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_effects().clip.0 = value - } - } - AnimatedProperty::LetterSpacing(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_inheritedtext().letter_spacing.0 = value - } - } - AnimatedProperty::WordSpacing(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_inheritedtext().word_spacing.0 = value - } - } - } - }); - match_property!( - [BackgroundColor; mutate_background; background_color], - [BackgroundPosition; mutate_background; background_position], - [BorderBottomColor; mutate_border; border_bottom_color], - [BorderBottomWidth; mutate_border; border_bottom_width], - [BorderLeftColor; mutate_border; border_left_color], - [BorderLeftWidth; mutate_border; border_left_width], - [BorderRightColor; mutate_border; border_right_color], - [BorderRightWidth; mutate_border; border_right_width], - [BorderSpacing; mutate_inheritedtable; border_spacing], - [BorderTopColor; mutate_border; border_top_color], - [BorderTopWidth; mutate_border; border_top_width], - [Bottom; mutate_position; bottom], - [Color; mutate_color; color], - [FontSize; mutate_font; font_size], - [FontWeight; mutate_font; font_weight], - [Height; mutate_position; height], - [Left; mutate_position; left], - [LineHeight; mutate_inheritedtext; line_height], - [MarginBottom; mutate_margin; margin_bottom], - [MarginLeft; mutate_margin; margin_left], - [MarginRight; mutate_margin; margin_right], - [MarginTop; mutate_margin; margin_top], - [MaxHeight; mutate_position; max_height], - [MaxWidth; mutate_position; max_width], - [MinHeight; mutate_position; min_height], - [MinWidth; mutate_position; min_width], - [Opacity; mutate_effects; opacity], - [OutlineColor; mutate_outline; outline_color], - [OutlineWidth; mutate_outline; outline_width], - [PaddingBottom; mutate_padding; padding_bottom], - [PaddingLeft; mutate_padding; padding_left], - [PaddingRight; mutate_padding; padding_right], - [PaddingTop; mutate_padding; padding_top], - [Right; mutate_position; right], - [TextIndent; mutate_inheritedtext; text_indent], - [TextShadow; mutate_inheritedtext; text_shadow], - [Top; mutate_position; top], - [Transform; mutate_effects; transform], - [VerticalAlign; mutate_box; vertical_align], - [Visibility; mutate_inheritedbox; visibility], - [Width; mutate_position; width], - [ZIndex; mutate_position; z_index]); - } - - #[inline] - fn does_not_animate(&self) -> bool { - self.property.does_not_animate() || self.duration == Time(0.0) + self.property.update(style, progress); } -} - -#[derive(Clone, Debug)] -enum AnimatedProperty { - BackgroundColor(Color, Color), - BackgroundPosition(BackgroundPosition, BackgroundPosition), - BorderBottomColor(Color, Color), - BorderBottomWidth(Length, Length), - BorderLeftColor(Color, Color), - BorderLeftWidth(Length, Length), - BorderRightColor(Color, Color), - BorderRightWidth(Length, Length), - BorderSpacing(BorderSpacing, BorderSpacing), - BorderTopColor(Color, Color), - BorderTopWidth(Length, Length), - Bottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Color(RGBA, RGBA), - Clip(Option<ClipRect>, Option<ClipRect>), - FontSize(Length, Length), - FontWeight(FontWeight, FontWeight), - Height(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Left(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - LetterSpacing(Option<Au>, Option<Au>), - LineHeight(LineHeight, LineHeight), - MarginBottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginLeft(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginRight(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginTop(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MaxHeight(LengthOrPercentageOrNone, LengthOrPercentageOrNone), - MaxWidth(LengthOrPercentageOrNone, LengthOrPercentageOrNone), - MinHeight(LengthOrPercentage, LengthOrPercentage), - MinWidth(LengthOrPercentage, LengthOrPercentage), - Opacity(CSSFloat, CSSFloat), - OutlineColor(Color, Color), - OutlineWidth(Length, Length), - PaddingBottom(LengthOrPercentage, LengthOrPercentage), - PaddingLeft(LengthOrPercentage, LengthOrPercentage), - PaddingRight(LengthOrPercentage, LengthOrPercentage), - PaddingTop(LengthOrPercentage, LengthOrPercentage), - Right(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - TextIndent(LengthOrPercentage, LengthOrPercentage), - TextShadow(TextShadowList, TextShadowList), - Top(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Transform(TransformList, TransformList), - VerticalAlign(VerticalAlign, VerticalAlign), - Visibility(Visibility, Visibility), - Width(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - WordSpacing(Option<Au>, Option<Au>), - ZIndex(ZIndex, ZIndex), -} -impl AnimatedProperty { #[inline] - fn does_not_animate(&self) -> bool { - match *self { - AnimatedProperty::Top(ref a, ref b) | - AnimatedProperty::Right(ref a, ref b) | - AnimatedProperty::Bottom(ref a, ref b) | - AnimatedProperty::Left(ref a, ref b) | - AnimatedProperty::MarginTop(ref a, ref b) | - AnimatedProperty::MarginRight(ref a, ref b) | - AnimatedProperty::MarginBottom(ref a, ref b) | - AnimatedProperty::MarginLeft(ref a, ref b) | - AnimatedProperty::Width(ref a, ref b) | - AnimatedProperty::Height(ref a, ref b) => a == b, - AnimatedProperty::MaxWidth(ref a, ref b) | - AnimatedProperty::MaxHeight(ref a, ref b) => a == b, - AnimatedProperty::MinWidth(ref a, ref b) | - AnimatedProperty::MinHeight(ref a, ref b) | - AnimatedProperty::TextIndent(ref a, ref b) => a == b, - AnimatedProperty::FontSize(ref a, ref b) | - AnimatedProperty::BorderTopWidth(ref a, ref b) | - AnimatedProperty::BorderRightWidth(ref a, ref b) | - AnimatedProperty::BorderBottomWidth(ref a, ref b) | - AnimatedProperty::BorderLeftWidth(ref a, ref b) => a == b, - AnimatedProperty::BorderTopColor(ref a, ref b) | - AnimatedProperty::BorderRightColor(ref a, ref b) | - AnimatedProperty::BorderBottomColor(ref a, ref b) | - AnimatedProperty::BorderLeftColor(ref a, ref b) | - AnimatedProperty::OutlineColor(ref a, ref b) | - AnimatedProperty::BackgroundColor(ref a, ref b) => a == b, - AnimatedProperty::PaddingTop(ref a, ref b) | - AnimatedProperty::PaddingRight(ref a, ref b) | - AnimatedProperty::PaddingBottom(ref a, ref b) | - AnimatedProperty::PaddingLeft(ref a, ref b) => a == b, - AnimatedProperty::LineHeight(ref a, ref b) => a == b, - AnimatedProperty::LetterSpacing(ref a, ref b) => a == b, - AnimatedProperty::BackgroundPosition(ref a, ref b) => a == b, - AnimatedProperty::BorderSpacing(ref a, ref b) => a == b, - AnimatedProperty::Clip(ref a, ref b) => a == b, - AnimatedProperty::Color(ref a, ref b) => a == b, - AnimatedProperty::FontWeight(ref a, ref b) => a == b, - AnimatedProperty::Opacity(ref a, ref b) => a == b, - AnimatedProperty::OutlineWidth(ref a, ref b) => a == b, - AnimatedProperty::TextShadow(ref a, ref b) => a == b, - AnimatedProperty::VerticalAlign(ref a, ref b) => a == b, - AnimatedProperty::Visibility(ref a, ref b) => a == b, - AnimatedProperty::WordSpacing(ref a, ref b) => a == b, - AnimatedProperty::ZIndex(ref a, ref b) => a == b, - AnimatedProperty::Transform(ref a, ref b) => a == b, - } + fn does_animate(&self) -> bool { + self.property.does_animate() && self.duration != Time(0.0) } } -/// A trait used to implement [interpolation][interpolated-types]. +/// Accesses an element of an array, "wrapping around" using modular arithmetic. This is needed +/// to handle [repeatable lists][lists] of differing lengths. /// -/// [interpolated-types]: https://drafts.csswg.org/css-transitions/#interpolated-types -trait Interpolate: Sized { - fn interpolate(&self, other: &Self, time: f64) -> Option<Self>; -} - -impl Interpolate for Au { - #[inline] - fn interpolate(&self, other: &Au, time: f64) -> Option<Au> { - Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32)) - } -} - -impl <T> Interpolate for Option<T> where T: Interpolate { - #[inline] - fn interpolate(&self, other: &Option<T>, time: f64) -> Option<Option<T>> { - match (self, other) { - (&Some(ref this), &Some(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(Some(value)) - }) - } - (_, _) => None - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-number -impl Interpolate for f32 { - #[inline] - fn interpolate(&self, other: &f32, time: f64) -> Option<f32> { - Some(((*self as f64) + ((*other as f64) - (*self as f64)) * time) as f32) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-number -impl Interpolate for f64 { - #[inline] - fn interpolate(&self, other: &f64, time: f64) -> Option<f64> { - Some(*self + (*other - *self) * time) - } +/// [lists]: https://drafts.csswg.org/css-transitions/#animtype-repeatable-list +pub trait GetMod { + type Item; + fn get_mod(&self, i: usize) -> &Self::Item; } -/// https://drafts.csswg.org/css-transitions/#animtype-integer -impl Interpolate for i32 { +impl<T> GetMod for Vec<T> { + type Item = T; #[inline] - fn interpolate(&self, other: &i32, time: f64) -> Option<i32> { - let a = *self as f64; - let b = *other as f64; - Some((a + (b - a) * time).round() as i32) + fn get_mod(&self, i: usize) -> &T { + &(*self)[i % self.len()] } } -impl Interpolate for Angle { - #[inline] - fn interpolate(&self, other: &Angle, time: f64) -> Option<Angle> { - self.radians().interpolate(&other.radians(), time).map(Angle) - } -} +/// Inserts transitions into the queue of running animations as applicable for +/// the given style difference. This is called from the layout worker threads. +/// Returns true if any animations were kicked off and false otherwise. +// +// TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a +// cloneable part and a non-cloneable part.. +pub fn start_transitions_if_applicable<Impl: SelectorImplExt>(new_animations_sender: &Mutex<Sender<Animation<Impl>>>, + node: OpaqueNode, + old_style: &Impl::ComputedValues, + new_style: &mut Arc<Impl::ComputedValues>) + -> 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, Arc::make_mut(new_style)); + for property_animation in property_animations { + // Set the property to the initial value. + // NB: get_mut is guaranteed to succeed since we called make_mut() + // above. + property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0); -/// https://drafts.csswg.org/css-transitions/#animtype-visibility -impl Interpolate for Visibility { - #[inline] - fn interpolate(&self, other: &Visibility, time: f64) - -> Option<Visibility> { - match (*self, *other) { - (Visibility::visible, _) | (_, Visibility::visible) => { - if time >= 0.0 && time <= 1.0 { - Some(Visibility::visible) - } else if time < 0.0 { - Some(*self) - } else { - Some(*other) - } - } - (_, _) => None, + // Kick off the animation. + let now = time::precise_time_s(); + let box_style = new_style.as_servo().get_box(); + let start_time = + now + (box_style.transition_delay.0.get_mod(i).seconds() as f64); + new_animations_sender + .lock().unwrap() + .send(Animation::Transition(node, start_time, AnimationFrame { + duration: box_style.transition_duration.0.get_mod(i).seconds() as f64, + property_animation: property_animation, + }, /* is_expired = */ false)).unwrap(); + + had_animations = true; } } -} -/// https://drafts.csswg.org/css-transitions/#animtype-integer -impl Interpolate for ZIndex { - #[inline] - fn interpolate(&self, other: &ZIndex, time: f64) - -> Option<ZIndex> { - match (*self, *other) { - (ZIndex::Number(ref this), - ZIndex::Number(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(ZIndex::Number(value)) - }) - } - (_, _) => None, - } - } + had_animations } -/// https://drafts.csswg.org/css-transitions/#animtype-length -impl Interpolate for VerticalAlign { - #[inline] - fn interpolate(&self, other: &VerticalAlign, time: f64) - -> Option<VerticalAlign> { - match (*self, *other) { - (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)), - VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => { - this.interpolate(other, time).and_then(|value| { - Some(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))) - }) - } - (_, _) => None, +fn compute_style_for_animation_step<Impl: SelectorImplExt>(context: &SharedStyleContext<Impl>, + step: &KeyframesStep, + 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 } } } -/// https://drafts.csswg.org/css-transitions/#animtype-simple-list -impl Interpolate for BorderSpacing { - #[inline] - fn interpolate(&self, other: &BorderSpacing, time: f64) - -> Option<BorderSpacing> { - self.horizontal.interpolate(&other.horizontal, time).and_then(|horizontal| { - self.vertical.interpolate(&other.vertical, time).and_then(|vertical| { - Some(BorderSpacing { horizontal: horizontal, vertical: vertical }) - }) - }) - } -} +pub fn maybe_start_animations<Impl: SelectorImplExt>(context: &SharedStyleContext<Impl>, + node: OpaqueNode, + new_style: &Arc<Impl::ComputedValues>) -> bool +{ + let mut had_animations = false; -/// https://drafts.csswg.org/css-transitions/#animtype-color -impl Interpolate for RGBA { - #[inline] - fn interpolate(&self, other: &RGBA, time: f64) -> Option<RGBA> { - match (self.red.interpolate(&other.red, time), - self.green.interpolate(&other.green, time), - self.blue.interpolate(&other.blue, time), - self.alpha.interpolate(&other.alpha, time)) { - (Some(red), Some(green), Some(blue), Some(alpha)) => { - Some(RGBA { red: red, green: green, blue: blue, alpha: alpha }) - } - (_, _, _, _) => None + let box_style = new_style.as_servo().get_box(); + for (i, name) in box_style.animation_name.0.iter().enumerate() { + debug!("maybe_start_animations: name={}", name); + let total_duration = box_style.animation_duration.0.get_mod(i).seconds(); + if total_duration == 0. { + continue } - } -} -/// https://drafts.csswg.org/css-transitions/#animtype-color -impl Interpolate for Color { - #[inline] - fn interpolate(&self, other: &Color, time: f64) -> Option<Color> { - match (*self, *other) { - (Color::RGBA(ref this), Color::RGBA(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(Color::RGBA(value)) - }) - } - (_, _) => None, - } - } -} + if let Some(ref anim) = context.stylist.animations().get(&name) { + debug!("maybe_start_animations: animation {} found", name); -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for CalcLengthOrPercentage { - #[inline] - fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64) - -> Option<CalcLengthOrPercentage> { - Some(CalcLengthOrPercentage { - length: self.length().interpolate(&other.length(), time), - percentage: self.percentage().interpolate(&other.percentage(), time), - }) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentage { - #[inline] - fn interpolate(&self, other: &LengthOrPercentage, time: f64) - -> Option<LengthOrPercentage> { - match (*self, *other) { - (LengthOrPercentage::Length(ref this), - LengthOrPercentage::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentage::Length(value)) - }) - } - (LengthOrPercentage::Percentage(ref this), - LengthOrPercentage::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentage::Percentage(value)) - }) + // 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; } - (this, other) => { - let this: CalcLengthOrPercentage = From::from(this); - let other: CalcLengthOrPercentage = From::from(other); - this.interpolate(&other, time).and_then(|value| { - Some(LengthOrPercentage::Calc(value)) - }) - } - } - } -} -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentageOrAuto { - #[inline] - fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64) - -> Option<LengthOrPercentageOrAuto> { - match (*self, *other) { - (LengthOrPercentageOrAuto::Length(ref this), - LengthOrPercentageOrAuto::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrAuto::Length(value)) - }) - } - (LengthOrPercentageOrAuto::Percentage(ref this), - LengthOrPercentageOrAuto::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrAuto::Percentage(value)) - }) - } - (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => { - Some(LengthOrPercentageOrAuto::Auto) - } - (this, other) => { - let this: Option<CalcLengthOrPercentage> = From::from(this); - let other: Option<CalcLengthOrPercentage> = From::from(other); - this.interpolate(&other, time).unwrap_or(None).and_then(|value| { - Some(LengthOrPercentageOrAuto::Calc(value)) - }) - } + let delay = box_style.animation_delay.0.get_mod(i).seconds(); + let now = time::precise_time_s(); + let animation_start = now + delay as f64; + let duration = box_style.animation_duration.0.get_mod(i).seconds(); + let iteration_state = match *box_style.animation_iteration_count.0.get_mod(i) { + AnimationIterationCount::Infinite => KeyframesIterationState::Infinite, + AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0, n), + }; + + let animation_direction = *box_style.animation_direction.0.get_mod(i); + + let initial_direction = match animation_direction { + AnimationDirection::normal | + AnimationDirection::alternate => AnimationDirection::normal, + AnimationDirection::reverse | + AnimationDirection::alternate_reverse => AnimationDirection::reverse, + }; + + let running_state = match *box_style.animation_play_state.0.get_mod(i) { + AnimationPlayState::paused => KeyframesRunningState::Paused(0.), + AnimationPlayState::running => KeyframesRunningState::Running, + }; + + + context.new_animations_sender + .lock().unwrap() + .send(Animation::Keyframes(node, name.clone(), KeyframesAnimationState { + started_at: animation_start, + duration: duration as f64, + delay: delay as f64, + iteration_state: iteration_state, + running_state: running_state, + direction: animation_direction, + current_direction: initial_direction, + expired: false, + cascade_style: new_style.clone(), + })).unwrap(); + had_animations = true; } } -} -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentageOrNone { - #[inline] - fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64) - -> Option<LengthOrPercentageOrNone> { - match (*self, *other) { - (LengthOrPercentageOrNone::Length(ref this), - LengthOrPercentageOrNone::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrNone::Length(value)) - }) - } - (LengthOrPercentageOrNone::Percentage(ref this), - LengthOrPercentageOrNone::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrNone::Percentage(value)) - }) - } - (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => { - Some(LengthOrPercentageOrNone::None) - } - (_, _) => None, - } - } + had_animations } -/// https://drafts.csswg.org/css-transitions/#animtype-number -/// https://drafts.csswg.org/css-transitions/#animtype-length -impl Interpolate for LineHeight { - #[inline] - fn interpolate(&self, other: &LineHeight, time: f64) - -> Option<LineHeight> { - match (*self, *other) { - (LineHeight::Length(ref this), - LineHeight::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LineHeight::Length(value)) - }) - } - (LineHeight::Number(ref this), - LineHeight::Number(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LineHeight::Number(value)) - }) - } - (LineHeight::Normal, LineHeight::Normal) => { - Some(LineHeight::Normal) - } - (_, _) => None, - } +/// Updates a given computed style for a given animation frame. Returns a bool +/// representing if the style was indeed updated. +pub fn update_style_for_animation_frame<C: ComputedValues>(mut new_style: &mut Arc<C>, + now: f64, + start_time: f64, + frame: &AnimationFrame) -> bool { + let mut progress = (now - start_time) / frame.duration; + if progress > 1.0 { + progress = 1.0 } -} -/// https://drafts.csswg.org/css-transitions/#animtype-font-weight -impl Interpolate for FontWeight { - #[inline] - fn interpolate(&self, other: &FontWeight, time: f64) - -> Option<FontWeight> { - let a = (*self as u32) as f64; - let b = (*other as u32) as f64; - let weight = a + (b - a) * time; - Some(if weight < 150. { - FontWeight::Weight100 - } else if weight < 250. { - FontWeight::Weight200 - } else if weight < 350. { - FontWeight::Weight300 - } else if weight < 450. { - FontWeight::Weight400 - } else if weight < 550. { - FontWeight::Weight500 - } else if weight < 650. { - FontWeight::Weight600 - } else if weight < 750. { - FontWeight::Weight700 - } else if weight < 850. { - FontWeight::Weight800 - } else { - FontWeight::Weight900 - }) + if progress <= 0.0 { + return false; } -} -/// https://drafts.csswg.org/css-transitions/#animtype-rect -impl Interpolate for ClipRect { - #[inline] - fn interpolate(&self, other: &ClipRect, time: f64) - -> Option<ClipRect> { - match (self.top.interpolate(&other.top, time), - self.right.interpolate(&other.right, time), - self.bottom.interpolate(&other.bottom, time), - self.left.interpolate(&other.left, time)) { - (Some(top), Some(right), Some(bottom), Some(left)) => { - Some(ClipRect { top: top, right: right, bottom: bottom, left: left }) - }, - (_, _, _, _) => None, - } - } -} + frame.property_animation.update(Arc::make_mut(&mut new_style), progress); -/// https://drafts.csswg.org/css-transitions/#animtype-simple-list -impl Interpolate for BackgroundPosition { - #[inline] - fn interpolate(&self, other: &BackgroundPosition, time: f64) - -> Option<BackgroundPosition> { - match (self.horizontal.interpolate(&other.horizontal, time), - self.vertical.interpolate(&other.vertical, time)) { - (Some(horizontal), Some(vertical)) => { - Some(BackgroundPosition { horizontal: horizontal, vertical: vertical }) - }, - (_, _) => None, - } - } + true } +/// 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<Damage, Impl>(context: &SharedStyleContext<Impl>, + animation: &Animation<Impl>, + style: &mut Arc<Damage::ConcreteComputedValues>, + damage: Option<&mut Damage>) +where Impl: SelectorImplExt, + Damage: TRestyleDamage<ConcreteComputedValues = Impl::ComputedValues> { + debug!("update_style_for_animation: entering"); + match *animation { + Animation::Transition(_, start_time, ref frame, expired) => { + debug_assert!(!expired); + debug!("update_style_for_animation: transition found"); + let now = time::precise_time_s(); + let mut new_style = (*style).clone(); + let updated_style = update_style_for_animation_frame(&mut new_style, + now, start_time, + frame); + if updated_style { + if let Some(damage) = damage { + *damage = *damage | Damage::compute(Some(style), &new_style); + } -/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list -impl Interpolate for TextShadow { - #[inline] - fn interpolate(&self, other: &TextShadow, time: f64) - -> Option<TextShadow> { - match (self.offset_x.interpolate(&other.offset_x, time), - self.offset_y.interpolate(&other.offset_y, time), - self.blur_radius.interpolate(&other.blur_radius, time), - self.color.interpolate(&other.color, time)) { - (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => { - Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color }) - }, - (_, _, _, _) => None, + *style = new_style + } } - } -} + Animation::Keyframes(_, ref name, ref state) => { + debug_assert!(!state.expired); + debug!("update_style_for_animation: animation found: \"{}\", {:?}", name, state); + let duration = state.duration; + let started_at = state.started_at; + + let now = match state.running_state { + KeyframesRunningState::Running => time::precise_time_s(), + KeyframesRunningState::Paused(progress) => started_at + duration * progress, + }; + + let animation = match context.stylist.animations().get(name) { + None => { + warn!("update_style_for_animation: Animation {:?} not found", name); + return; + } + Some(animation) => animation, + }; -/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list -impl Interpolate for TextShadowList { - #[inline] - fn interpolate(&self, other: &TextShadowList, time: f64) - -> Option<TextShadowList> { - let zero = TextShadow { - offset_x: Au(0), - offset_y: Au(0), - blur_radius: Au(0), - color: Color::RGBA(RGBA { - red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 - }) - }; + debug_assert!(!animation.steps.is_empty()); - let interpolate_each = |(a, b): (&TextShadow, &TextShadow)| { - a.interpolate(b, time).unwrap() - }; + let maybe_index = style.as_servo() + .get_box().animation_name.0.iter() + .position(|animation_name| name == animation_name); - Some(TextShadowList(match self.0.len().cmp(&other.0.len()) { - Ordering::Less => other.0.iter().chain(repeat(&zero)).zip(other.0.iter()).map(interpolate_each).collect(), - _ => self.0.iter().zip(other.0.iter().chain(repeat(&zero))).map(interpolate_each).collect(), - })) - } -} + let index = match maybe_index { + Some(index) => index, + None => { + warn!("update_style_for_animation: Animation {:?} not found in style", name); + return; + } + }; -/// Check if it's possible to do a direct numerical interpolation -/// between these two transform lists. -/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation -fn can_interpolate_list(from_list: &[TransformOperation], - to_list: &[TransformOperation]) -> bool { - // Lists must be equal length - if from_list.len() != to_list.len() { - return false; - } + let total_duration = style.as_servo().get_box().animation_duration.0.get_mod(index).seconds() as f64; + if total_duration == 0. { + debug!("update_style_for_animation: zero duration for animation {:?}", name); + return; + } - // Each transform operation must match primitive type in other list - for (from, to) in from_list.iter().zip(to_list) { - match (from, to) { - (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | - (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | - (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) | - (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) | - (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | - (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {} - _ => { - return false; + let mut total_progress = (now - started_at) / total_duration; + if total_progress < 0. { + warn!("Negative progress found for animation {:?}", name); + return; + } + if total_progress > 1. { + total_progress = 1.; } - } - } - true -} + debug!("update_style_for_animation: anim \"{}\", steps: {:?}, state: {:?}, progress: {}", + name, animation.steps, state, total_progress); -/// Interpolate two transform lists. -/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms -fn interpolate_transform_list(from_list: &[TransformOperation], - to_list: &[TransformOperation], - time: f64) -> TransformList { - let mut result = vec!(); - - if can_interpolate_list(from_list, to_list) { - for (from, to) in from_list.iter().zip(to_list) { - match (from, to) { - (&TransformOperation::Matrix(from), - &TransformOperation::Matrix(_to)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Matrix(from)); - } - (&TransformOperation::Skew(fx, fy), - &TransformOperation::Skew(tx, ty)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - result.push(TransformOperation::Skew(ix, iy)); - } - (&TransformOperation::Translate(fx, fy, fz), - &TransformOperation::Translate(tx, ty, tz)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - let iz = fz.interpolate(&tz, time).unwrap(); - result.push(TransformOperation::Translate(ix, iy, iz)); - } - (&TransformOperation::Scale(fx, fy, fz), - &TransformOperation::Scale(tx, ty, tz)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - let iz = fz.interpolate(&tz, time).unwrap(); - result.push(TransformOperation::Scale(ix, iy, iz)); - } - (&TransformOperation::Rotate(fx, fy, fz, fa), - &TransformOperation::Rotate(_tx, _ty, _tz, _ta)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Rotate(fx, fy, fz, fa)); - } - (&TransformOperation::Perspective(fd), - &TransformOperation::Perspective(_td)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Perspective(fd)); + // Get the target and the last keyframe position. + let last_keyframe_position; + let target_keyframe_position; + match state.current_direction { + AnimationDirection::normal => { + target_keyframe_position = + animation.steps.iter().position(|step| { + total_progress as f32 <= step.start_percentage.0 + }); + + last_keyframe_position = target_keyframe_position.and_then(|pos| { + if pos != 0 { Some(pos - 1) } else { None } + }).unwrap_or(0); } - _ => { - // This should be unreachable due to the can_interpolate_list() call. - unreachable!(); + AnimationDirection::reverse => { + target_keyframe_position = + animation.steps.iter().rev().position(|step| { + total_progress as f32 <= 1. - step.start_percentage.0 + }).map(|pos| animation.steps.len() - pos - 1); + + last_keyframe_position = target_keyframe_position.and_then(|pos| { + if pos != animation.steps.len() - 1 { Some(pos + 1) } else { None } + }).unwrap_or(animation.steps.len() - 1); } + _ => unreachable!(), } - } - } else { - // TODO(gw): Implement matrix decomposition and interpolation - result.extend_from_slice(from_list); - } - TransformList(Some(result)) -} + debug!("update_style_for_animation: keyframe from {:?} to {:?}", + last_keyframe_position, target_keyframe_position); -/// Build an equivalent 'identity transform function list' based -/// on an existing transform list. -/// https://drafts.csswg.org/css-transforms/#none-transform-animation -fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOperation> { - let mut result = vec!(); - - for operation in list { - match *operation { - TransformOperation::Matrix(..) => { - let identity = ComputedMatrix::identity(); - result.push(TransformOperation::Matrix(identity)); - } - TransformOperation::Skew(..) => { - result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0))); - } - TransformOperation::Translate(..) => { - result.push(TransformOperation::Translate(LengthOrPercentage::zero(), - LengthOrPercentage::zero(), - Au(0))); - } - TransformOperation::Scale(..) => { - result.push(TransformOperation::Scale(1.0, 1.0, 1.0)); - } - TransformOperation::Rotate(..) => { - result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0))); - } - TransformOperation::Perspective(..) => { - // http://dev.w3.org/csswg/css-transforms/#identity-transform-function - let identity = ComputedMatrix::identity(); - result.push(TransformOperation::Matrix(identity)); - } - } - } + let target_keyframe = match target_keyframe_position { + Some(target) => &animation.steps[target], + None => { + warn!("update_style_for_animation: No current keyframe found for animation \"{}\" at progress {}", + name, total_progress); + return; + } + }; - result -} + let last_keyframe = &animation.steps[last_keyframe_position]; -/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms -impl Interpolate for TransformList { - #[inline] - fn interpolate(&self, other: &TransformList, time: f64) -> Option<TransformList> { - let result = match (&self.0, &other.0) { - (&Some(ref from_list), &Some(ref to_list)) => { - // https://drafts.csswg.org/css-transforms/#transform-transform-animation - interpolate_transform_list(from_list, &to_list, time) - } - (&Some(ref from_list), &None) => { - // https://drafts.csswg.org/css-transforms/#none-transform-animation - let to_list = build_identity_transform_list(from_list); - interpolate_transform_list(from_list, &to_list, time) - } - (&None, &Some(ref to_list)) => { - // https://drafts.csswg.org/css-transforms/#none-transform-animation - let from_list = build_identity_transform_list(to_list); - interpolate_transform_list(&from_list, to_list, time) - } - _ => { - // https://drafts.csswg.org/css-transforms/#none-none-animation - TransformList(None) + let relative_timespan = (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0).abs(); + let relative_duration = relative_timespan as f64 * duration; + let last_keyframe_ended_at = match state.current_direction { + AnimationDirection::normal => { + state.started_at + (total_duration * last_keyframe.start_percentage.0 as f64) + } + AnimationDirection::reverse => { + state.started_at + (total_duration * (1. - last_keyframe.start_percentage.0 as f64)) + } + _ => unreachable!(), + }; + let relative_progress = (now - last_keyframe_ended_at) / relative_duration; + + // TODO: How could we optimise it? Is it such a big deal? + let from_style = compute_style_for_animation_step(context, + last_keyframe, + &**style, + &state.cascade_style); + + // NB: The spec says that the timing function can be overwritten + // from the keyframe style. + let mut timing_function = *style.as_servo().get_box().animation_timing_function.0.get_mod(index); + if !from_style.as_servo().get_box().animation_timing_function.0.is_empty() { + timing_function = from_style.as_servo().get_box().animation_timing_function.0[0]; + } + + let target_style = compute_style_for_animation_step(context, + target_keyframe, + &from_style, + &state.cascade_style); + + let mut new_style = (*style).clone(); + + for transition_property in &animation.properties_changed { + debug!("update_style_for_animation: scanning prop {:?} for animation \"{}\"", + transition_property, name); + match PropertyAnimation::from_transition_property(*transition_property, + timing_function, + Time(relative_duration as f32), + &from_style, + &target_style) { + Some(property_animation) => { + debug!("update_style_for_animation: got property animation for prop {:?}", transition_property); + debug!("update_style_for_animation: {:?}", property_animation); + property_animation.update(Arc::make_mut(&mut new_style), relative_progress); + } + None => { + debug!("update_style_for_animation: property animation {:?} not animating", + transition_property); + } + } } - }; - - Some(result) - } -} - -/// Accesses an element of an array, "wrapping around" using modular arithmetic. This is needed -/// to handle [repeatable lists][lists] of differing lengths. -/// -/// [lists]: https://drafts.csswg.org/css-transitions/#animtype-repeatable-list -pub trait GetMod { - type Item; - fn get_mod(&self, i: usize) -> &Self::Item; -} -impl<T> GetMod for Vec<T> { - type Item = T; - fn get_mod(&self, i: usize) -> &T { - &(*self)[i % self.len()] - } -} - -/// Inserts transitions into the queue of running animations as applicable for the given style -/// difference. This is called from the layout worker threads. Returns true if any animations were -/// kicked off and false otherwise. -pub fn start_transitions_if_applicable<C: ComputedValues>(new_animations_sender: &Mutex<Sender<Animation>>, - node: OpaqueNode, - old_style: &C, - new_style: &mut C) - -> 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.as_servo(), new_style.as_servo_mut()); - for property_animation in property_animations { - // Set the property to the initial value. - property_animation.update(new_style.as_servo_mut(), 0.0); + debug!("update_style_for_animation: got style change in animation \"{}\"", name); + if let Some(damage) = damage { + *damage = *damage | Damage::compute(Some(style), &new_style); + } - // Kick off the animation. - let now = time::precise_time_s(); - let box_style = new_style.as_servo().get_box(); - let start_time = - now + (box_style.transition_delay.0.get_mod(i).seconds() as f64); - new_animations_sender.lock().unwrap().send(Animation { - node: node, - property_animation: property_animation, - start_time: start_time, - end_time: start_time + - (box_style.transition_duration.0.get_mod(i).seconds() as f64), - }).unwrap(); - - had_animations = true + *style = new_style; } } - - had_animations -} - -/// 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<C: ComputedValues, - Damage: TRestyleDamage<ConcreteComputedValues=C>>(animation: &Animation, - style: &mut Arc<C>, - damage: Option<&mut Damage>) { - let now = time::precise_time_s(); - let mut progress = (now - animation.start_time) / animation.duration(); - if progress > 1.0 { - progress = 1.0 - } - if progress <= 0.0 { - return - } - - let mut new_style = (*style).clone(); - animation.property_animation.update(Arc::make_mut(&mut new_style).as_servo_mut(), progress); - if let Some(damage) = damage { - *damage = *damage | Damage::compute(Some(style), &new_style); - } - - *style = new_style } 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<Impl: SelectorImplExt> { /// A channel on which new animations that have been triggered by style recalculation can be /// sent. - pub new_animations_sender: Mutex<Sender<Animation>>, + pub new_animations_sender: Mutex<Sender<Animation<Impl>>>, /// Why is this reflow occurring pub goal: ReflowGoal, /// The animations that are currently running. - pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>, + pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation<Impl>>>>>, /// The list of animations that have expired since the last style recalculation. - pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>, + pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation<Impl>>>>>, ///The CSS error reporter for all CSS loaded in this layout thread pub error_reporter: Box<ParseErrorReporter + Sync>, diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs new file mode 100644 index 00000000000..cbfa35b78c4 --- /dev/null +++ b/components/style/keyframes.rs @@ -0,0 +1,242 @@ +/* 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/. */ + +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; + +/// A number from 1 to 100, indicating the percentage of the animation where +/// this keyframe should run. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, HeapSizeOf)] +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 KeyframePercentage { + #[inline] + pub fn new(value: f32) -> KeyframePercentage { + debug_assert!(value >= 0. && value <= 1.); + KeyframePercentage(value) + } + + fn parse(input: &mut Parser) -> Result<KeyframePercentage, ()> { + let percentage = if input.try(|input| input.expect_ident_matching("from")).is_ok() { + KeyframePercentage::new(0.) + } else if input.try(|input| input.expect_ident_matching("to")).is_ok() { + KeyframePercentage::new(1.) + } else { + let percentage = try!(input.expect_percentage()); + if percentage > 1. || percentage < 0. { + return Err(()); + } + KeyframePercentage::new(percentage) + }; + + Ok(percentage) + } +} + +/// A keyframes selector is a list of percentages or from/to symbols, which are +/// converted at parse time to percentages. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct KeyframeSelector(Vec<KeyframePercentage>); +impl KeyframeSelector { + #[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) + } +} + +/// A keyframe. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct Keyframe { + pub selector: KeyframeSelector, + pub declarations: Arc<Vec<PropertyDeclaration>>, +} + +impl Keyframe { + pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Keyframe, ()> { + let percentages = try!(input.parse_until_before(Delimiter::CurlyBracketBlock, |input| { + input.parse_comma_separated(|input| KeyframePercentage::parse(input)) + })); + let selector = KeyframeSelector(percentages); + + try!(input.expect_curly_bracket_block()); + + let declarations = input.parse_nested_block(|input| { + Ok(parse_property_declaration_list(context, input)) + }).unwrap(); + + // NB: Important declarations are explicitely ignored in the spec. + Ok(Keyframe { + selector: selector, + declarations: declarations.normal, + }) + } +} + +/// 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<Vec<PropertyDeclaration>>), + 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, or + /// `ComputedValues` if this is an autogenerated step. + pub value: KeyframesStepValue, +} + +impl KeyframesStep { + #[inline] + fn new(percentage: KeyframePercentage, + value: KeyframesStepValue) -> Self { + KeyframesStep { + start_percentage: percentage, + value: value, + } + } +} + +/// This structure represents a list of animation steps computed from the list +/// of keyframes, in order. +/// +/// It only takes into account animable properties. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct KeyframesAnimation { + pub steps: Vec<KeyframesStep>, + /// The properties that change in this animation. + pub properties_changed: Vec<TransitionProperty>, +} + +/// Get all the animated properties in a keyframes animation. Note that it's not +/// defined what happens when a property is not on a keyframe, so we only peek +/// the props of the first one. +/// +/// In practice, browsers seem to try to do their best job at it, so we might +/// want to go through all the actual keyframes and deduplicate properties. +fn get_animated_properties(keyframe: &Keyframe) -> Vec<TransitionProperty> { + let mut ret = vec![]; + // NB: declarations are already deduplicated, so we don't have to check for + // it here. + for declaration in keyframe.declarations.iter() { + if let Some(property) = TransitionProperty::from_declaration(&declaration) { + ret.push(property); + } + } + + ret +} + +impl KeyframesAnimation { + pub fn from_keyframes(keyframes: &[Keyframe]) -> Option<Self> { + let animated_properties = get_animated_properties(&keyframes[0]); + 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, + 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, + }) + } +} + +/// 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<Keyframe> { + 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<Self::Prelude, ()> { + 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<Self::QualifiedRule, ()> { + Ok(Keyframe { + selector: prelude, + // FIXME: needs parsing different from parse_property_declaration_list: + // https://drafts.csswg.org/css-animations/#keyframes + // Paragraph "The <declaration-list> inside of <keyframe-block> ..." + declarations: parse_property_declaration_list(self.context, input).normal, + }) + } +} diff --git a/components/style/lib.rs b/components/style/lib.rs index bad7907f964..ae1e8c84d7c 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -76,6 +76,7 @@ pub mod dom; pub mod element_state; pub mod error_reporting; pub mod font_face; +pub mod keyframes; pub mod logical_geometry; pub mod matching; pub mod media_queries; diff --git a/components/style/matching.rs b/components/style/matching.rs index 31d12ce4312..1f85effdb33 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -7,7 +7,7 @@ #![allow(unsafe_code)] use animation::{self, Animation}; -use context::SharedStyleContext; +use context::{SharedStyleContext, LocalStyleContext}; use data::PrivateStyleData; use dom::{TElement, TNode, TRestyleDamage}; use properties::{ComputedValues, PropertyDeclaration, cascade}; @@ -21,8 +21,7 @@ use smallvec::SmallVec; use std::collections::HashMap; use std::hash::{BuildHasherDefault, Hash, Hasher}; use std::slice::Iter; -use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use string_cache::{Atom, Namespace}; use util::arc_ptr_eq; use util::cache::{LRUCache, SimpleHashCache}; @@ -54,7 +53,9 @@ fn create_common_style_affecting_attributes_from_element<E: TElement>(element: & pub struct ApplicableDeclarations<Impl: SelectorImplExt> { pub normal: SmallVec<[DeclarationBlock; 16]>, - pub per_pseudo: HashMap<Impl::PseudoElement, Vec<DeclarationBlock>, BuildHasherDefault<::fnv::FnvHasher>>, + pub per_pseudo: HashMap<Impl::PseudoElement, + Vec<DeclarationBlock>, + BuildHasherDefault<::fnv::FnvHasher>>, /// Whether the `normal` declarations are shareable with other nodes. pub normal_shareable: bool, @@ -365,7 +366,11 @@ pub enum StyleSharingResult<ConcreteRestyleDamage: TRestyleDamage> { } trait PrivateMatchMethods: TNode - where <Self::ConcreteElement as Element>::Impl: SelectorImplExt { + where <Self::ConcreteElement as Element>::Impl: SelectorImplExt<ComputedValues = Self::ConcreteComputedValues> { + /// Actually cascades style for a node or a pseudo-element of a node. + /// + /// Note that animations only apply to nodes or ::before or ::after + /// pseudo-elements. fn cascade_node_pseudo_element(&self, context: &SharedStyleContext<<Self::ConcreteElement as Element>::Impl>, parent_style: Option<&Arc<Self::ConcreteComputedValues>>, @@ -373,7 +378,6 @@ trait PrivateMatchMethods: TNode mut style: Option<&mut Arc<Self::ConcreteComputedValues>>, applicable_declarations_cache: &mut ApplicableDeclarationsCache<Self::ConcreteComputedValues>, - new_animations_sender: &Mutex<Sender<Animation>>, shareable: bool, animate_properties: bool) -> (Self::ConcreteRestyleDamage, Arc<Self::ConcreteComputedValues>) { @@ -382,14 +386,15 @@ 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); let cached_computed_values = match cache_entry { - None => None, Some(ref style) => Some(&**style), + None => None, }; + let (the_style, is_cacheable) = cascade(context.viewport_size, applicable_declarations, shareable, @@ -411,22 +416,31 @@ trait PrivateMatchMethods: TNode } }; - // Trigger transitions if necessary. This will reset `this_style` back to its old value if - // it did trigger a transition. + let mut this_style = Arc::new(this_style); + if animate_properties { + let this_opaque = self.opaque(); + // Trigger any present animations if necessary. + let mut animations_started = animation::maybe_start_animations::<<Self::ConcreteElement as Element>::Impl>( + &context, + this_opaque, + &this_style); + + // Trigger transitions if necessary. This will reset `this_style` back + // to its old value if it did trigger a transition. if let Some(ref style) = style { - let animations_started = - animation::start_transitions_if_applicable::<Self::ConcreteComputedValues>( - new_animations_sender, - self.opaque(), + animations_started |= + animation::start_transitions_if_applicable::<<Self::ConcreteElement as Element>::Impl>( + &context.new_animations_sender, + this_opaque, &**style, &mut this_style); - cacheable = cacheable && !animations_started } + + cacheable = cacheable && !animations_started } // 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. @@ -457,7 +471,11 @@ trait PrivateMatchMethods: TNode had_animations_to_expire = animations_to_expire.is_some(); if let Some(ref animations) = animations_to_expire { for animation in *animations { - animation.property_animation.update(Arc::make_mut(style).as_servo_mut(), 1.0); + // NB: Expiring a keyframes animation is the same as not + // applying the keyframes style to it, so we're safe. + if let Animation::Transition(_, _, ref frame, _) = *animation { + frame.property_animation.update(Arc::make_mut(style), 1.0); + } } } } @@ -474,11 +492,11 @@ trait PrivateMatchMethods: TNode .is_some(); if had_running_animations { let mut all_running_animations = context.running_animations.write().unwrap(); - for running_animation in all_running_animations.get(&this_opaque).unwrap() { - animation::update_style_for_animation::<Self::ConcreteComputedValues, - Self::ConcreteRestyleDamage>(running_animation, style, None); + for mut running_animation in all_running_animations.get_mut(&this_opaque).unwrap() { + animation::update_style_for_animation::<Self::ConcreteRestyleDamage, + <Self::ConcreteElement as Element>::Impl>(context, running_animation, style, None); + running_animation.mark_as_expired(); } - all_running_animations.remove(&this_opaque); } had_animations_to_expire || had_running_animations @@ -486,7 +504,8 @@ trait PrivateMatchMethods: TNode } impl<N: TNode> PrivateMatchMethods for N - where <N::ConcreteElement as Element>::Impl: SelectorImplExt {} + where <N::ConcreteElement as Element>::Impl: + SelectorImplExt<ComputedValues = N::ConcreteComputedValues> {} trait PrivateElementMatchMethods: TElement { fn share_style_with_candidate_if_possible(&self, @@ -641,25 +660,27 @@ pub trait MatchMethods : TNode { unsafe fn cascade_node(&self, context: &SharedStyleContext<<Self::ConcreteElement as Element>::Impl>, + local_context: &LocalStyleContext<Self::ConcreteComputedValues>, parent: Option<Self>, - applicable_declarations: &ApplicableDeclarations<<Self::ConcreteElement as Element>::Impl>, - applicable_declarations_cache: - &mut ApplicableDeclarationsCache<Self::ConcreteComputedValues>, - new_animations_sender: &Mutex<Sender<Animation>>) - where <Self::ConcreteElement as Element>::Impl: SelectorImplExt { + applicable_declarations: &ApplicableDeclarations<<Self::ConcreteElement as Element>::Impl>) + where <Self::ConcreteElement as Element>::Impl: SelectorImplExt<ComputedValues = Self::ConcreteComputedValues> + { // Get our parent's style. This must be unsafe so that we don't touch the parent's // borrow flags. // // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow // enforced safe, race-free access to the parent style. let parent_style = match parent { - None => None, Some(parent_node) => { let parent_style = (*parent_node.borrow_data_unchecked().unwrap()).style.as_ref().unwrap(); Some(parent_style) } + None => None, }; + let mut applicable_declarations_cache = + local_context.applicable_declarations_cache.borrow_mut(); + let damage; if self.is_text_node() { let mut data_ref = self.mutate_data().unwrap(); @@ -677,8 +698,7 @@ pub trait MatchMethods : TNode { parent_style, &applicable_declarations.normal, data.style.as_mut(), - applicable_declarations_cache, - new_animations_sender, + &mut applicable_declarations_cache, applicable_declarations.normal_shareable, true); @@ -690,15 +710,18 @@ pub trait MatchMethods : TNode { if !applicable_declarations_for_this_pseudo.is_empty() { + // NB: Transitions and animations should only work for + // pseudo-elements ::before and ::after + let should_animate_properties = + <Self::ConcreteElement as Element>::Impl::pseudo_is_before_or_after(&pseudo); let (new_damage, style) = self.cascade_node_pseudo_element( context, Some(data.style.as_ref().unwrap()), &*applicable_declarations_for_this_pseudo, data.per_pseudo.get_mut(&pseudo), - applicable_declarations_cache, - new_animations_sender, + &mut applicable_declarations_cache, false, - false); + should_animate_properties); data.per_pseudo.insert(pseudo, style); damage = damage | new_damage; diff --git a/components/style/parallel.rs b/components/style/parallel.rs index e967c3839fd..55f15f1d80f 100644 --- a/components/style/parallel.rs +++ b/components/style/parallel.rs @@ -58,7 +58,7 @@ fn top_down_dom<N, C>(unsafe_nodes: UnsafeNodeList, where N: TNode, C: DomTraversalContext<N> { let context = C::new(proxy.user_data(), unsafe_nodes.1); - let mut discovered_child_nodes = Vec::new(); + let mut discovered_child_nodes = vec![]; for unsafe_node in *unsafe_nodes.0 { // Get a real layout node. let node = unsafe { N::from_unsafe(&unsafe_node) }; diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 0ac35003ff9..04da7ad225b 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -13,7 +13,7 @@ def to_rust_ident(name): def to_camel_case(ident): - return re.sub("_([a-z])", lambda m: m.group(1).upper(), ident.strip("_").capitalize()) + return re.sub("(^|_|-)([a-z])", lambda m: m.group(2).upper(), ident.strip("_").strip("-")) class Keyword(object): @@ -45,7 +45,7 @@ class Keyword(object): class Longhand(object): - def __init__(self, style_struct, name, derived_from=None, keyword=None, + def __init__(self, style_struct, name, animatable=None, derived_from=None, keyword=None, predefined_type=None, custom_cascade=False, experimental=False, internal=False, need_clone=False, gecko_ffi_name=None): self.name = name @@ -61,6 +61,16 @@ class Longhand(object): self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case self.derived_from = (derived_from or "").split() + # This is done like this since just a plain bool argument seemed like + # really random. + if animatable is None: + raise TypeError("animatable should be specified for " + name + ")") + if isinstance(animatable, bool): + self.animatable = animatable + else: + assert animatable == "True" or animatable == "False" + self.animatable = animatable == "True" + class Shorthand(object): def __init__(self, name, sub_properties, experimental=False, internal=False): diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 86961c89192..b704ef9784b 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -2,7 +2,7 @@ * 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/. */ -<%! from data import Keyword, to_rust_ident %> +<%! from data import Keyword, to_rust_ident, to_camel_case %> <%def name="longhand(name, **kwargs)"> <%call expr="raw_longhand(name, **kwargs)"> @@ -181,9 +181,11 @@ % endfor } } - #[inline] pub fn get_initial_value() -> computed_value::T { + #[inline] + pub fn get_initial_value() -> computed_value::T { computed_value::T::${to_rust_ident(values.split()[0])} } + #[inline] pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> { computed_value::T::parse(input) @@ -191,6 +193,63 @@ </%call> </%def> +<%def name="keyword_list(name, values, **kwargs)"> + <% + keyword_kwargs = {a: kwargs.pop(a, None) for a in [ + 'gecko_constant_prefix', 'extra_gecko_values', 'extra_servo_values' + ]} + %> + <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> + use values::computed::ComputedValueAsSpecified; + pub use self::computed_value::T as SpecifiedValue; + pub mod computed_value { + use cssparser::ToCss; + use std::fmt; + + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub struct T(pub Vec<${to_camel_case(name)}>); + + impl ToCss for T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + debug_assert!(!self.0.is_empty(), "Always parses at least one"); + + for (index, item) in self.0.iter().enumerate() { + if index != 0 { + try!(dest.write_str(", ")); + } + + try!(item.to_css(dest)); + } + + Ok(()) + } + } + + define_css_keyword_enum! { ${to_camel_case(name)}: + % for value in data.longhands_by_name[name].keyword.values_for(product): + "${value}" => ${to_rust_ident(value)}, + % endfor + } + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T(vec![ + computed_value::${to_camel_case(name)}::${to_rust_ident(values.split()[0])} + ]) + } + + #[inline] + pub fn parse(_context: &ParserContext, input: &mut Parser) + -> Result<SpecifiedValue, ()> { + Ok(SpecifiedValue(try!( + input.parse_comma_separated(computed_value::${to_camel_case(name)}::parse)))) + } + + impl ComputedValueAsSpecified for SpecifiedValue {} + </%call> +</%def> + <%def name="shorthand(name, sub_properties, experimental=False, **kwargs)"> <% shorthand = data.declare_shorthand(name, sub_properties.split(), experimental=experimental, diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs new file mode 100644 index 00000000000..3b75b4ee07b --- /dev/null +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -0,0 +1,878 @@ +/* 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/. */ + +use app_units::Au; +use cssparser::{Color as CSSParserColor, Parser, RGBA, ToCss}; +use euclid::{Point2D, Size2D}; +use properties::PropertyDeclaration; +use properties::longhands; +use properties::longhands::background_position::computed_value::T as BackgroundPosition; +use properties::longhands::background_size::computed_value::T as BackgroundSize; +use properties::longhands::border_spacing::computed_value::T as BorderSpacing; +use properties::longhands::clip::computed_value::ClipRect; +use properties::longhands::font_weight::computed_value::T as FontWeight; +use properties::longhands::line_height::computed_value::T as LineHeight; +use properties::longhands::text_shadow::computed_value::T as TextShadowList; +use properties::longhands::text_shadow::computed_value::TextShadow; +use properties::longhands::box_shadow::computed_value::T as BoxShadowList; +use properties::longhands::box_shadow::computed_value::BoxShadow; +use properties::longhands::transform::computed_value::ComputedMatrix; +use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; +use properties::longhands::transform::computed_value::T as TransformList; +use properties::longhands::transform_origin::computed_value::T as TransformOrigin; +use properties::longhands::vertical_align::computed_value::T as VerticalAlign; +use properties::longhands::visibility::computed_value::T as Visibility; +use properties::longhands::z_index::computed_value::T as ZIndex; +use properties::style_struct_traits::*; +use std::cmp::{self, Ordering}; +use std::fmt; +use std::iter::repeat; +use super::ComputedValues; +use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; +use values::computed::{BorderRadiusSize, LengthOrNone}; +use values::computed::{CalcLengthOrPercentage, LengthOrPercentage}; + +// NB: This needs to be here because it needs all the longhands generated +// beforehand. +#[derive(Copy, Clone, Debug, PartialEq, HeapSizeOf)] +pub enum TransitionProperty { + All, + % for prop in data.longhands: + % if prop.animatable: + ${prop.camel_case}, + % endif + % endfor +} + +impl TransitionProperty { + /// Iterates over each property that is not `All`. + pub fn each<F: FnMut(TransitionProperty) -> ()>(mut cb: F) { + % for prop in data.longhands: + % if prop.animatable: + cb(TransitionProperty::${prop.camel_case}); + % endif + % endfor + } + + pub fn parse(input: &mut Parser) -> Result<Self, ()> { + match_ignore_ascii_case! { try!(input.expect_ident()), + "all" => Ok(TransitionProperty::All), + % for prop in data.longhands: + % if prop.animatable: + "${prop.name}" => Ok(TransitionProperty::${prop.camel_case}), + % endif + % endfor + _ => Err(()) + } + } + + pub fn from_declaration(declaration: &PropertyDeclaration) -> Option<Self> { + match *declaration { + % for prop in data.longhands: + % if prop.animatable: + PropertyDeclaration::${prop.camel_case}(..) + => Some(TransitionProperty::${prop.camel_case}), + % endif + % endfor + _ => None, + } + } +} + +impl ToCss for TransitionProperty { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + TransitionProperty::All => dest.write_str("all"), + % for prop in data.longhands: + % if prop.animatable: + TransitionProperty::${prop.camel_case} => dest.write_str("${prop.name}"), + % endif + % endfor + } + } +} + +#[derive(Clone, Debug, PartialEq, HeapSizeOf)] +pub enum AnimatedProperty { + % for prop in data.longhands: + % if prop.animatable: + ${prop.camel_case}(longhands::${prop.ident}::computed_value::T, + longhands::${prop.ident}::computed_value::T), + % endif + % endfor +} + +impl AnimatedProperty { + pub fn does_animate(&self) -> bool { + match *self { + % for prop in data.longhands: + % if prop.animatable: + AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to, + % endif + % endfor + } + } + + pub fn update<C: ComputedValues>(&self, style: &mut C, progress: f64) { + match *self { + % for prop in data.longhands: + % if prop.animatable: + AnimatedProperty::${prop.camel_case}(ref from, ref to) => { + if let Some(value) = from.interpolate(to, progress) { + style.mutate_${prop.style_struct.ident.strip("_")}().set_${prop.ident}(value); + } + } + % endif + % endfor + } + } + + // NB: Transition properties need clone + pub fn from_transition_property<C: ComputedValues>(transition_property: &TransitionProperty, + old_style: &C, + new_style: &C) -> AnimatedProperty { + // TODO: Generalise this for GeckoLib, adding clone_xxx to the + // appropiate longhands. + let old_style = old_style.as_servo(); + let new_style = new_style.as_servo(); + match *transition_property { + TransitionProperty::All => panic!("Can't use TransitionProperty::All here."), + % for prop in data.longhands: + % if prop.animatable: + TransitionProperty::${prop.camel_case} => { + AnimatedProperty::${prop.camel_case}( + old_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone(), + new_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone()) + } + % endif + % endfor + } + } +} + +/// A trait used to implement [interpolation][interpolated-types]. +/// +/// [interpolated-types]: https://drafts.csswg.org/css-transitions/#interpolated-types +pub trait Interpolate: Sized { + fn interpolate(&self, other: &Self, time: f64) -> Option<Self>; +} + +/// https://drafts.csswg.org/css-transitions/#animtype-number +impl Interpolate for Au { + #[inline] + fn interpolate(&self, other: &Au, time: f64) -> Option<Au> { + Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32)) + } +} + +impl <T> Interpolate for Option<T> where T: Interpolate { + #[inline] + fn interpolate(&self, other: &Option<T>, time: f64) -> Option<Option<T>> { + match (self, other) { + (&Some(ref this), &Some(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(Some(value)) + }) + } + (_, _) => None + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-number +impl Interpolate for f32 { + #[inline] + fn interpolate(&self, other: &f32, time: f64) -> Option<f32> { + Some(((*self as f64) + ((*other as f64) - (*self as f64)) * time) as f32) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-number +impl Interpolate for f64 { + #[inline] + fn interpolate(&self, other: &f64, time: f64) -> Option<f64> { + Some(*self + (*other - *self) * time) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-number +impl Interpolate for i32 { + #[inline] + fn interpolate(&self, other: &i32, time: f64) -> Option<i32> { + let a = *self as f64; + let b = *other as f64; + Some((a + (b - a) * time).round() as i32) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-number +impl Interpolate for Angle { + #[inline] + fn interpolate(&self, other: &Angle, time: f64) -> Option<Angle> { + self.radians().interpolate(&other.radians(), time).map(Angle) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-visibility +impl Interpolate for Visibility { + #[inline] + fn interpolate(&self, other: &Visibility, time: f64) + -> Option<Visibility> { + match (*self, *other) { + (Visibility::visible, _) | (_, Visibility::visible) => { + if time >= 0.0 && time <= 1.0 { + Some(Visibility::visible) + } else if time < 0.0 { + Some(*self) + } else { + Some(*other) + } + } + (_, _) => None, + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-integer +impl Interpolate for ZIndex { + #[inline] + fn interpolate(&self, other: &ZIndex, time: f64) + -> Option<ZIndex> { + match (*self, *other) { + (ZIndex::Number(ref this), + ZIndex::Number(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(ZIndex::Number(value)) + }) + } + (_, _) => None, + } + } +} + +impl<T: Interpolate + Clone> Interpolate for Size2D<T> { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + let width = match self.width.interpolate(&other.width, time) { + Some(width) => width, + None => return None, + }; + + let height = match self.height.interpolate(&other.height, time) { + Some(height) => height, + None => return None, + }; + Some(Size2D::new(width, height)) + } +} + +impl<T: Interpolate + Clone> Interpolate for Point2D<T> { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + let x = match self.x.interpolate(&other.x, time) { + Some(x) => x, + None => return None, + }; + + let y = match self.y.interpolate(&other.y, time) { + Some(y) => y, + None => return None, + }; + + Some(Point2D::new(x, y)) + } +} + +impl Interpolate for BorderRadiusSize { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + self.0.interpolate(&other.0, time).map(BorderRadiusSize) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-length +impl Interpolate for VerticalAlign { + #[inline] + fn interpolate(&self, other: &VerticalAlign, time: f64) + -> Option<VerticalAlign> { + match (*self, *other) { + (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)), + VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => { + this.interpolate(other, time).and_then(|value| { + Some(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))) + }) + } + (_, _) => None, + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-simple-list +impl Interpolate for BorderSpacing { + #[inline] + fn interpolate(&self, other: &BorderSpacing, time: f64) + -> Option<BorderSpacing> { + self.horizontal.interpolate(&other.horizontal, time).and_then(|horizontal| { + self.vertical.interpolate(&other.vertical, time).and_then(|vertical| { + Some(BorderSpacing { horizontal: horizontal, vertical: vertical }) + }) + }) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-color +impl Interpolate for RGBA { + #[inline] + fn interpolate(&self, other: &RGBA, time: f64) -> Option<RGBA> { + match (self.red.interpolate(&other.red, time), + self.green.interpolate(&other.green, time), + self.blue.interpolate(&other.blue, time), + self.alpha.interpolate(&other.alpha, time)) { + (Some(red), Some(green), Some(blue), Some(alpha)) => { + Some(RGBA { red: red, green: green, blue: blue, alpha: alpha }) + } + (_, _, _, _) => None + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-color +impl Interpolate for CSSParserColor { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + match (*self, *other) { + (CSSParserColor::RGBA(ref this), CSSParserColor::RGBA(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(CSSParserColor::RGBA(value)) + }) + } + (_, _) => None, + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc +impl Interpolate for CalcLengthOrPercentage { + #[inline] + fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64) + -> Option<CalcLengthOrPercentage> { + Some(CalcLengthOrPercentage { + length: self.length().interpolate(&other.length(), time), + percentage: self.percentage().interpolate(&other.percentage(), time), + }) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc +impl Interpolate for LengthOrPercentage { + #[inline] + fn interpolate(&self, other: &LengthOrPercentage, time: f64) + -> Option<LengthOrPercentage> { + match (*self, *other) { + (LengthOrPercentage::Length(ref this), + LengthOrPercentage::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentage::Length(value)) + }) + } + (LengthOrPercentage::Percentage(ref this), + LengthOrPercentage::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentage::Percentage(value)) + }) + } + (this, other) => { + let this: CalcLengthOrPercentage = From::from(this); + let other: CalcLengthOrPercentage = From::from(other); + this.interpolate(&other, time).and_then(|value| { + Some(LengthOrPercentage::Calc(value)) + }) + } + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc +impl Interpolate for LengthOrPercentageOrAuto { + #[inline] + fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64) + -> Option<LengthOrPercentageOrAuto> { + match (*self, *other) { + (LengthOrPercentageOrAuto::Length(ref this), + LengthOrPercentageOrAuto::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrAuto::Length(value)) + }) + } + (LengthOrPercentageOrAuto::Percentage(ref this), + LengthOrPercentageOrAuto::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrAuto::Percentage(value)) + }) + } + (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => { + Some(LengthOrPercentageOrAuto::Auto) + } + (this, other) => { + let this: Option<CalcLengthOrPercentage> = From::from(this); + let other: Option<CalcLengthOrPercentage> = From::from(other); + this.interpolate(&other, time).unwrap_or(None).and_then(|value| { + Some(LengthOrPercentageOrAuto::Calc(value)) + }) + } + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc +impl Interpolate for LengthOrPercentageOrNone { + #[inline] + fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64) + -> Option<LengthOrPercentageOrNone> { + match (*self, *other) { + (LengthOrPercentageOrNone::Length(ref this), + LengthOrPercentageOrNone::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrNone::Length(value)) + }) + } + (LengthOrPercentageOrNone::Percentage(ref this), + LengthOrPercentageOrNone::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrNone::Percentage(value)) + }) + } + (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => { + Some(LengthOrPercentageOrNone::None) + } + (_, _) => None, + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-number +/// https://drafts.csswg.org/css-transitions/#animtype-length +impl Interpolate for LineHeight { + #[inline] + fn interpolate(&self, other: &LineHeight, time: f64) + -> Option<LineHeight> { + match (*self, *other) { + (LineHeight::Length(ref this), + LineHeight::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LineHeight::Length(value)) + }) + } + (LineHeight::Number(ref this), + LineHeight::Number(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LineHeight::Number(value)) + }) + } + (LineHeight::Normal, LineHeight::Normal) => { + Some(LineHeight::Normal) + } + (_, _) => None, + } + } +} + +/// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight +impl Interpolate for FontWeight { + #[inline] + fn interpolate(&self, other: &FontWeight, time: f64) + -> Option<FontWeight> { + let a = (*self as u32) as f64; + let b = (*other as u32) as f64; + let weight = a + (b - a) * time; + Some(if weight < 150. { + FontWeight::Weight100 + } else if weight < 250. { + FontWeight::Weight200 + } else if weight < 350. { + FontWeight::Weight300 + } else if weight < 450. { + FontWeight::Weight400 + } else if weight < 550. { + FontWeight::Weight500 + } else if weight < 650. { + FontWeight::Weight600 + } else if weight < 750. { + FontWeight::Weight700 + } else if weight < 850. { + FontWeight::Weight800 + } else { + FontWeight::Weight900 + }) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-rect +impl Interpolate for ClipRect { + #[inline] + fn interpolate(&self, other: &ClipRect, time: f64) + -> Option<ClipRect> { + match (self.top.interpolate(&other.top, time), + self.right.interpolate(&other.right, time), + self.bottom.interpolate(&other.bottom, time), + self.left.interpolate(&other.left, time)) { + (Some(top), Some(right), Some(bottom), Some(left)) => { + Some(ClipRect { top: top, right: right, bottom: bottom, left: left }) + }, + (_, _, _, _) => None, + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-simple-list +impl Interpolate for BackgroundPosition { + #[inline] + fn interpolate(&self, other: &BackgroundPosition, time: f64) + -> Option<BackgroundPosition> { + match (self.horizontal.interpolate(&other.horizontal, time), + self.vertical.interpolate(&other.vertical, time)) { + (Some(horizontal), Some(vertical)) => { + Some(BackgroundPosition { horizontal: horizontal, vertical: vertical }) + }, + (_, _) => None, + } + } +} + +impl Interpolate for BackgroundSize { + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + use properties::longhands::background_size::computed_value::ExplicitSize; + match (self, other) { + (&BackgroundSize::Explicit(ref me), &BackgroundSize::Explicit(ref other)) + => match (me.width.interpolate(&other.width, time), + me.height.interpolate(&other.height, time)) { + (Some(width), Some(height)) + => Some(BackgroundSize::Explicit( + ExplicitSize { width: width, height: height })), + _ => None, + }, + _ => None + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list +impl Interpolate for TextShadow { + #[inline] + fn interpolate(&self, other: &TextShadow, time: f64) + -> Option<TextShadow> { + match (self.offset_x.interpolate(&other.offset_x, time), + self.offset_y.interpolate(&other.offset_y, time), + self.blur_radius.interpolate(&other.blur_radius, time), + self.color.interpolate(&other.color, time)) { + (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => { + Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color }) + }, + _ => None, + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list +impl Interpolate for TextShadowList { + #[inline] + fn interpolate(&self, other: &TextShadowList, time: f64) + -> Option<TextShadowList> { + let zero = TextShadow { + offset_x: Au(0), + offset_y: Au(0), + blur_radius: Au(0), + color: CSSParserColor::RGBA(RGBA { + red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 + }) + }; + + let interpolate_each = |(a, b): (&TextShadow, &TextShadow)| { + a.interpolate(b, time).unwrap() + }; + + Some(TextShadowList(match self.0.len().cmp(&other.0.len()) { + Ordering::Less => other.0.iter().chain(repeat(&zero)).zip(other.0.iter()).map(interpolate_each).collect(), + _ => self.0.iter().zip(other.0.iter().chain(repeat(&zero))).map(interpolate_each).collect(), + })) + } +} + +/// Check if it's possible to do a direct numerical interpolation +/// between these two transform lists. +/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation +fn can_interpolate_list(from_list: &[TransformOperation], + to_list: &[TransformOperation]) -> bool { + // Lists must be equal length + if from_list.len() != to_list.len() { + return false; + } + + // Each transform operation must match primitive type in other list + for (from, to) in from_list.iter().zip(to_list) { + match (from, to) { + (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | + (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | + (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) | + (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) | + (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | + (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {} + _ => { + return false; + } + } + } + + true +} + +/// Interpolate two transform lists. +/// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms +fn interpolate_transform_list(from_list: &[TransformOperation], + to_list: &[TransformOperation], + time: f64) -> TransformList { + let mut result = vec![]; + + if can_interpolate_list(from_list, to_list) { + for (from, to) in from_list.iter().zip(to_list) { + match (from, to) { + (&TransformOperation::Matrix(from), + &TransformOperation::Matrix(_to)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Matrix(from)); + } + (&TransformOperation::Skew(fx, fy), + &TransformOperation::Skew(tx, ty)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + result.push(TransformOperation::Skew(ix, iy)); + } + (&TransformOperation::Translate(fx, fy, fz), + &TransformOperation::Translate(tx, ty, tz)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + let iz = fz.interpolate(&tz, time).unwrap(); + result.push(TransformOperation::Translate(ix, iy, iz)); + } + (&TransformOperation::Scale(fx, fy, fz), + &TransformOperation::Scale(tx, ty, tz)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + let iz = fz.interpolate(&tz, time).unwrap(); + result.push(TransformOperation::Scale(ix, iy, iz)); + } + (&TransformOperation::Rotate(fx, fy, fz, fa), + &TransformOperation::Rotate(_tx, _ty, _tz, _ta)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Rotate(fx, fy, fz, fa)); + } + (&TransformOperation::Perspective(fd), + &TransformOperation::Perspective(_td)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Perspective(fd)); + } + _ => { + // This should be unreachable due to the can_interpolate_list() call. + unreachable!(); + } + } + } + } else { + // TODO(gw): Implement matrix decomposition and interpolation + result.extend_from_slice(from_list); + } + + TransformList(Some(result)) +} + +/// Build an equivalent 'identity transform function list' based +/// on an existing transform list. +/// http://dev.w3.org/csswg/css-transforms/#none-transform-animation +fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOperation> { + let mut result = vec!(); + + for operation in list { + match *operation { + TransformOperation::Matrix(..) => { + let identity = ComputedMatrix::identity(); + result.push(TransformOperation::Matrix(identity)); + } + TransformOperation::Skew(..) => { + result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0))); + } + TransformOperation::Translate(..) => { + result.push(TransformOperation::Translate(LengthOrPercentage::zero(), + LengthOrPercentage::zero(), + Au(0))); + } + TransformOperation::Scale(..) => { + result.push(TransformOperation::Scale(1.0, 1.0, 1.0)); + } + TransformOperation::Rotate(..) => { + result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0))); + } + TransformOperation::Perspective(..) => { + // http://dev.w3.org/csswg/css-transforms/#identity-transform-function + let identity = ComputedMatrix::identity(); + result.push(TransformOperation::Matrix(identity)); + } + } + } + + result +} + +impl Interpolate for BoxShadowList { + #[inline] + fn interpolate(&self, other: &Self, time: f64) + -> Option<Self> { + // The inset value must change + let mut zero = BoxShadow { + offset_x: Au(0), + offset_y: Au(0), + spread_radius: Au(0), + blur_radius: Au(0), + color: CSSParserColor::RGBA(RGBA { + red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 + }), + inset: false, + }; + + let max_len = cmp::max(self.0.len(), other.0.len()); + let mut result = Vec::with_capacity(max_len); + + for i in 0..max_len { + let shadow = match (self.0.get(i), other.0.get(i)) { + (Some(shadow), Some(other)) => { + match shadow.interpolate(other, time) { + Some(shadow) => shadow, + None => return None, + } + } + (Some(shadow), None) => { + zero.inset = shadow.inset; + shadow.interpolate(&zero, time).unwrap() + } + (None, Some(shadow)) => { + zero.inset = shadow.inset; + zero.interpolate(&shadow, time).unwrap() + } + (None, None) => unreachable!(), + }; + result.push(shadow); + } + + Some(BoxShadowList(result)) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list +impl Interpolate for BoxShadow { + #[inline] + fn interpolate(&self, other: &Self, time: f64) + -> Option<Self> { + if self.inset != other.inset { + return None; + } + + let x = match self.offset_x.interpolate(&other.offset_x, time) { + Some(x) => x, + None => return None, + }; + + let y = match self.offset_y.interpolate(&other.offset_y, time) { + Some(y) => y, + None => return None, + }; + + let color = match self.color.interpolate(&other.color, time) { + Some(c) => c, + None => return None, + }; + + let spread = match self.spread_radius.interpolate(&other.spread_radius, time) { + Some(s) => s, + None => return None, + }; + + let blur = match self.blur_radius.interpolate(&other.blur_radius, time) { + Some(r) => r, + None => return None, + }; + + Some(BoxShadow { + offset_x: x, + offset_y: y, + blur_radius: blur, + spread_radius: spread, + color: color, + inset: self.inset, + }) + } +} + +impl Interpolate for LengthOrNone { + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + match (*self, *other) { + (LengthOrNone::Length(ref len), LengthOrNone::Length(ref other)) => + len.interpolate(&other, time).map(LengthOrNone::Length), + _ => None, + } + } +} + +impl Interpolate for TransformOrigin { + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + let horizontal = match self.horizontal.interpolate(&other.horizontal, time) { + Some(h) => h, + None => return None, + }; + + let vertical = match self.vertical.interpolate(&other.vertical, time) { + Some(v) => v, + None => return None, + }; + + let depth = match self.depth.interpolate(&other.depth, time) { + Some(d) => d, + None => return None, + }; + + Some(TransformOrigin { + horizontal: horizontal, + vertical: vertical, + depth: depth, + }) + } +} + +/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms +impl Interpolate for TransformList { + #[inline] + fn interpolate(&self, other: &TransformList, time: f64) -> Option<TransformList> { + // http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms + let result = match (&self.0, &other.0) { + (&Some(ref from_list), &Some(ref to_list)) => { + // Two lists of transforms + interpolate_transform_list(from_list, &to_list, time) + } + (&Some(ref from_list), &None) => { + // http://dev.w3.org/csswg/css-transforms/#none-transform-animation + let to_list = build_identity_transform_list(from_list); + interpolate_transform_list(from_list, &to_list, time) + } + (&None, &Some(ref to_list)) => { + // http://dev.w3.org/csswg/css-transforms/#none-transform-animation + let from_list = build_identity_transform_list(to_list); + interpolate_transform_list(&from_list, to_list, time) + } + _ => { + // http://dev.w3.org/csswg/css-transforms/#none-none-animation + TransformList(None) + } + }; + + Some(result) + } +} diff --git a/components/style/properties/longhand/background.mako.rs b/components/style/properties/longhand/background.mako.rs index b31a27d7ed8..c2ed7d3da7e 100644 --- a/components/style/properties/longhand/background.mako.rs +++ b/components/style/properties/longhand/background.mako.rs @@ -5,11 +5,12 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <% data.new_style_struct("Background", inherited=False) %> -${helpers.predefined_type( - "background-color", "CSSColor", - "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")} -<%helpers:longhand name="background-image"> +${helpers.predefined_type("background-color", "CSSColor", + "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */", + animatable=True)} + +<%helpers:longhand name="background-image" animatable="False"> use cssparser::ToCss; use std::fmt; use values::specified::Image; @@ -71,7 +72,7 @@ ${helpers.predefined_type( } </%helpers:longhand> -<%helpers:longhand name="background-position"> +<%helpers:longhand name="background-position" animatable="True"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -186,15 +187,23 @@ ${helpers.predefined_type( } </%helpers:longhand> -${helpers.single_keyword("background-repeat", "repeat repeat-x repeat-y no-repeat")} +${helpers.single_keyword("background-repeat", + "repeat repeat-x repeat-y no-repeat", + animatable=False)} -${helpers.single_keyword("background-attachment", "scroll fixed" + (" local" if product == "gecko" else ""))} +${helpers.single_keyword("background-attachment", + "scroll fixed" + (" local" if product == "gecko" else ""), + animatable=False)} -${helpers.single_keyword("background-clip", "border-box padding-box content-box")} +${helpers.single_keyword("background-clip", + "border-box padding-box content-box", + animatable=False)} -${helpers.single_keyword("background-origin", "padding-box border-box content-box")} +${helpers.single_keyword("background-origin", + "padding-box border-box content-box", + animatable=False)} -<%helpers:longhand name="background-size"> +<%helpers:longhand name="background-size" animatable="True"> use cssparser::{ToCss, Token}; use std::ascii::AsciiExt; use std::fmt; diff --git a/components/style/properties/longhand/border.mako.rs b/components/style/properties/longhand/border.mako.rs index 96656861583..57bd1880233 100644 --- a/components/style/properties/longhand/border.mako.rs +++ b/components/style/properties/longhand/border.mako.rs @@ -10,15 +10,19 @@ "bool") for side in ["top", "right", "bottom", "left"]]) %> % for side in ["top", "right", "bottom", "left"]: - ${helpers.predefined_type("border-%s-color" % side, "CSSColor", "::cssparser::Color::CurrentColor")} + ${helpers.predefined_type("border-%s-color" % side, "CSSColor", + "::cssparser::Color::CurrentColor", + animatable=True)} % endfor % for side in ["top", "right", "bottom", "left"]: - ${helpers.predefined_type("border-%s-style" % side, "BorderStyle", "specified::BorderStyle::none", need_clone=True)} + ${helpers.predefined_type("border-%s-style" % side, "BorderStyle", + "specified::BorderStyle::none", + need_clone=True, animatable=False)} % endfor % for side in ["top", "right", "bottom", "left"]: - <%helpers:longhand name="border-${side}-width"> + <%helpers:longhand name="border-${side}-width" animatable="True"> use app_units::Au; use cssparser::ToCss; use std::fmt; @@ -60,13 +64,15 @@ % for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]: ${helpers.predefined_type("border-" + corner + "-radius", "BorderRadiusSize", "computed::BorderRadiusSize::zero()", - "parse")} + "parse", + animatable=True)} % endfor -${helpers.single_keyword("box-decoration-break", "slice clone", products="gecko")} +${helpers.single_keyword("box-decoration-break", "slice clone", + products="gecko", animatable=False)} -${helpers.single_keyword("-moz-float-edge", - "content-box margin-box", +${helpers.single_keyword("-moz-float-edge", "content-box margin-box", gecko_ffi_name="mFloatEdge", gecko_constant_prefix="NS_STYLE_FLOAT_EDGE", - products="gecko")} + products="gecko", + animatable=False)} diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index c1eba31c3a3..f9f2a4be97c 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -11,7 +11,10 @@ additional_methods=[Method("transition_count", "usize")]) %> // TODO(SimonSapin): don't parse `inline-table`, since we don't support it -<%helpers:longhand name="display" need_clone="True" custom_cascade="${product == 'servo'}"> +<%helpers:longhand name="display" + need_clone="True" + animatable="False" + custom_cascade="${product == 'servo'}"> <% values = """inline block inline-block table inline-table table-row-group table-header-group table-footer-group @@ -86,9 +89,14 @@ </%helpers:longhand> -${helpers.single_keyword("position", "static absolute relative fixed", need_clone=True, extra_gecko_values="sticky")} +${helpers.single_keyword("position", "static absolute relative fixed", + need_clone=True, extra_gecko_values="sticky", animatable=False)} -<%helpers:single_keyword_computed name="float" values="none left right" need_clone="True" gecko_ffi_name="mFloats"> +<%helpers:single_keyword_computed name="float" + values="none left right" + animatable="False" + need_clone="True" + gecko_ffi_name="mFloats"> impl ToComputedValue for SpecifiedValue { type ComputedValue = computed_value::T; @@ -107,9 +115,13 @@ ${helpers.single_keyword("position", "static absolute relative fixed", need_clon </%helpers:single_keyword_computed> -${helpers.single_keyword("clear", "none left right both", gecko_ffi_name="mBreakType")} +${helpers.single_keyword("clear", "none left right both", + animatable=False, gecko_ffi_name="mBreakType")} -<%helpers:longhand name="-servo-display-for-hypothetical-box" derived_from="display" products="servo"> +<%helpers:longhand name="-servo-display-for-hypothetical-box" + animatable="False" + derived_from="display" + products="servo"> pub use super::display::{SpecifiedValue, get_initial_value}; pub use super::display::{parse}; @@ -125,7 +137,8 @@ ${helpers.single_keyword("clear", "none left right both", gecko_ffi_name="mBreak </%helpers:longhand> -<%helpers:longhand name="vertical-align"> +<%helpers:longhand name="vertical-align" + animatable="True"> use cssparser::ToCss; use std::fmt; @@ -219,18 +232,21 @@ ${helpers.single_keyword("clear", "none left right both", gecko_ffi_name="mBreak // CSS 2.1, Section 11 - Visual effects // Non-standard, see https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box#Specifications -${helpers.single_keyword("-servo-overflow-clip-box", "padding-box content-box", products="servo", - internal=True)} +${helpers.single_keyword("-servo-overflow-clip-box", "padding-box content-box", + products="servo", animatable=False, internal=True)} -${helpers.single_keyword("overflow-clip-box", "padding-box content-box", products="gecko", - internal=True)} +${helpers.single_keyword("overflow-clip-box", "padding-box content-box", + products="gecko", animatable=False, internal=True)} // FIXME(pcwalton, #2742): Implement scrolling for `scroll` and `auto`. -${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone=True, - gecko_constant_prefix="NS_STYLE_OVERFLOW")} +${helpers.single_keyword("overflow-x", "visible hidden scroll auto", + need_clone=True, animatable=False, + gecko_constant_prefix="NS_STYLE_OVERFLOW")} // FIXME(pcwalton, #2742): Implement scrolling for `scroll` and `auto`. -<%helpers:longhand name="overflow-y" need_clone="True"> +<%helpers:longhand name="overflow-y" + need_clone="True" + animatable="False"> use super::overflow_x; use cssparser::ToCss; @@ -269,7 +285,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= </%helpers:longhand> // TODO(pcwalton): Multiple transitions. -<%helpers:longhand name="transition-duration"> +<%helpers:longhand name="transition-duration" animatable="False"> + use values::computed::ComputedValueAsSpecified; use values::specified::Time; pub use self::computed_value::T as SpecifiedValue; @@ -286,15 +303,6 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct T(pub Vec<SingleComputedValue>); - impl ToComputedValue for T { - type ComputedValue = T; - - #[inline] - fn to_computed_value<Cx: TContext>(&self, _: &Cx) -> T { - (*self).clone() - } - } - impl ToCss for T { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { if self.0.is_empty() { @@ -311,6 +319,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } } + impl ComputedValueAsSpecified for SpecifiedValue {} + #[inline] pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> { Time::parse(input) @@ -333,7 +343,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= // TODO(pcwalton): Lots more timing functions. // TODO(pcwalton): Multiple transitions. -<%helpers:longhand name="transition-timing-function"> +<%helpers:longhand name="transition-timing-function" animatable="False"> use self::computed_value::{StartEnd, TransitionTimingFunction}; use euclid::point::Point2D; @@ -531,170 +541,17 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } </%helpers:longhand> -// TODO(pcwalton): Lots more properties. -<%helpers:longhand name="transition-property"> - use self::computed_value::TransitionProperty; - +<%helpers:longhand name="transition-property" animatable="False"> pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue; pub use self::computed_value::T as SpecifiedValue; pub mod computed_value { use cssparser::ToCss; use std::fmt; - - pub use self::TransitionProperty as SingleComputedValue; - - #[derive(Copy, Clone, Debug, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub enum TransitionProperty { - All, - BackgroundColor, - BackgroundPosition, - BorderBottomColor, - BorderBottomWidth, - BorderLeftColor, - BorderLeftWidth, - BorderRightColor, - BorderRightWidth, - BorderSpacing, - BorderTopColor, - BorderTopWidth, - Bottom, - Color, - Clip, - FontSize, - FontWeight, - Height, - Left, - LetterSpacing, - LineHeight, - MarginBottom, - MarginLeft, - MarginRight, - MarginTop, - MaxHeight, - MaxWidth, - MinHeight, - MinWidth, - Opacity, - OutlineColor, - OutlineWidth, - PaddingBottom, - PaddingLeft, - PaddingRight, - PaddingTop, - Right, - TextIndent, - TextShadow, - Top, - Transform, - VerticalAlign, - Visibility, - Width, - WordSpacing, - ZIndex, - } - - pub static ALL_TRANSITION_PROPERTIES: [TransitionProperty; 45] = [ - TransitionProperty::BackgroundColor, - TransitionProperty::BackgroundPosition, - TransitionProperty::BorderBottomColor, - TransitionProperty::BorderBottomWidth, - TransitionProperty::BorderLeftColor, - TransitionProperty::BorderLeftWidth, - TransitionProperty::BorderRightColor, - TransitionProperty::BorderRightWidth, - TransitionProperty::BorderSpacing, - TransitionProperty::BorderTopColor, - TransitionProperty::BorderTopWidth, - TransitionProperty::Bottom, - TransitionProperty::Color, - TransitionProperty::Clip, - TransitionProperty::FontSize, - TransitionProperty::FontWeight, - TransitionProperty::Height, - TransitionProperty::Left, - TransitionProperty::LetterSpacing, - TransitionProperty::LineHeight, - TransitionProperty::MarginBottom, - TransitionProperty::MarginLeft, - TransitionProperty::MarginRight, - TransitionProperty::MarginTop, - TransitionProperty::MaxHeight, - TransitionProperty::MaxWidth, - TransitionProperty::MinHeight, - TransitionProperty::MinWidth, - TransitionProperty::Opacity, - TransitionProperty::OutlineColor, - TransitionProperty::OutlineWidth, - TransitionProperty::PaddingBottom, - TransitionProperty::PaddingLeft, - TransitionProperty::PaddingRight, - TransitionProperty::PaddingTop, - TransitionProperty::Right, - TransitionProperty::TextIndent, - TransitionProperty::TextShadow, - TransitionProperty::Top, - TransitionProperty::Transform, - TransitionProperty::VerticalAlign, - TransitionProperty::Visibility, - TransitionProperty::Width, - TransitionProperty::WordSpacing, - TransitionProperty::ZIndex, - ]; - - impl ToCss for TransitionProperty { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - TransitionProperty::All => dest.write_str("all"), - TransitionProperty::BackgroundColor => dest.write_str("background-color"), - TransitionProperty::BackgroundPosition => dest.write_str("background-position"), - TransitionProperty::BorderBottomColor => dest.write_str("border-bottom-color"), - TransitionProperty::BorderBottomWidth => dest.write_str("border-bottom-width"), - TransitionProperty::BorderLeftColor => dest.write_str("border-left-color"), - TransitionProperty::BorderLeftWidth => dest.write_str("border-left-width"), - TransitionProperty::BorderRightColor => dest.write_str("border-right-color"), - TransitionProperty::BorderRightWidth => dest.write_str("border-right-width"), - TransitionProperty::BorderSpacing => dest.write_str("border-spacing"), - TransitionProperty::BorderTopColor => dest.write_str("border-top-color"), - TransitionProperty::BorderTopWidth => dest.write_str("border-top-width"), - TransitionProperty::Bottom => dest.write_str("bottom"), - TransitionProperty::Color => dest.write_str("color"), - TransitionProperty::Clip => dest.write_str("clip"), - TransitionProperty::FontSize => dest.write_str("font-size"), - TransitionProperty::FontWeight => dest.write_str("font-weight"), - TransitionProperty::Height => dest.write_str("height"), - TransitionProperty::Left => dest.write_str("left"), - TransitionProperty::LetterSpacing => dest.write_str("letter-spacing"), - TransitionProperty::LineHeight => dest.write_str("line-height"), - TransitionProperty::MarginBottom => dest.write_str("margin-bottom"), - TransitionProperty::MarginLeft => dest.write_str("margin-left"), - TransitionProperty::MarginRight => dest.write_str("margin-right"), - TransitionProperty::MarginTop => dest.write_str("margin-top"), - TransitionProperty::MaxHeight => dest.write_str("max-height"), - TransitionProperty::MaxWidth => dest.write_str("max-width"), - TransitionProperty::MinHeight => dest.write_str("min-height"), - TransitionProperty::MinWidth => dest.write_str("min-width"), - TransitionProperty::Opacity => dest.write_str("opacity"), - TransitionProperty::OutlineColor => dest.write_str("outline-color"), - TransitionProperty::OutlineWidth => dest.write_str("outline-width"), - TransitionProperty::PaddingBottom => dest.write_str("padding-bottom"), - TransitionProperty::PaddingLeft => dest.write_str("padding-left"), - TransitionProperty::PaddingRight => dest.write_str("padding-right"), - TransitionProperty::PaddingTop => dest.write_str("padding-top"), - TransitionProperty::Right => dest.write_str("right"), - TransitionProperty::TextIndent => dest.write_str("text-indent"), - TransitionProperty::TextShadow => dest.write_str("text-shadow"), - TransitionProperty::Top => dest.write_str("top"), - TransitionProperty::Transform => dest.write_str("transform"), - TransitionProperty::VerticalAlign => dest.write_str("vertical-align"), - TransitionProperty::Visibility => dest.write_str("visibility"), - TransitionProperty::Width => dest.write_str("width"), - TransitionProperty::WordSpacing => dest.write_str("word-spacing"), - TransitionProperty::ZIndex => dest.write_str("z-index"), - } - } - } + // NB: Can't generate the type here because it needs all the longhands + // generated beforehand. + pub use properties::animated_properties::TransitionProperty; + pub use properties::animated_properties::TransitionProperty as SingleComputedValue; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -721,61 +578,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= computed_value::T(Vec::new()) } - pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> { - match_ignore_ascii_case! { - try!(input.expect_ident()), - "all" => Ok(TransitionProperty::All), - "background-color" => Ok(TransitionProperty::BackgroundColor), - "background-position" => Ok(TransitionProperty::BackgroundPosition), - "border-bottom-color" => Ok(TransitionProperty::BorderBottomColor), - "border-bottom-width" => Ok(TransitionProperty::BorderBottomWidth), - "border-left-color" => Ok(TransitionProperty::BorderLeftColor), - "border-left-width" => Ok(TransitionProperty::BorderLeftWidth), - "border-right-color" => Ok(TransitionProperty::BorderRightColor), - "border-right-width" => Ok(TransitionProperty::BorderRightWidth), - "border-spacing" => Ok(TransitionProperty::BorderSpacing), - "border-top-color" => Ok(TransitionProperty::BorderTopColor), - "border-top-width" => Ok(TransitionProperty::BorderTopWidth), - "bottom" => Ok(TransitionProperty::Bottom), - "color" => Ok(TransitionProperty::Color), - "clip" => Ok(TransitionProperty::Clip), - "font-size" => Ok(TransitionProperty::FontSize), - "font-weight" => Ok(TransitionProperty::FontWeight), - "height" => Ok(TransitionProperty::Height), - "left" => Ok(TransitionProperty::Left), - "letter-spacing" => Ok(TransitionProperty::LetterSpacing), - "line-height" => Ok(TransitionProperty::LineHeight), - "margin-bottom" => Ok(TransitionProperty::MarginBottom), - "margin-left" => Ok(TransitionProperty::MarginLeft), - "margin-right" => Ok(TransitionProperty::MarginRight), - "margin-top" => Ok(TransitionProperty::MarginTop), - "max-height" => Ok(TransitionProperty::MaxHeight), - "max-width" => Ok(TransitionProperty::MaxWidth), - "min-height" => Ok(TransitionProperty::MinHeight), - "min-width" => Ok(TransitionProperty::MinWidth), - "opacity" => Ok(TransitionProperty::Opacity), - "outline-color" => Ok(TransitionProperty::OutlineColor), - "outline-width" => Ok(TransitionProperty::OutlineWidth), - "padding-bottom" => Ok(TransitionProperty::PaddingBottom), - "padding-left" => Ok(TransitionProperty::PaddingLeft), - "padding-right" => Ok(TransitionProperty::PaddingRight), - "padding-top" => Ok(TransitionProperty::PaddingTop), - "right" => Ok(TransitionProperty::Right), - "text-indent" => Ok(TransitionProperty::TextIndent), - "text-shadow" => Ok(TransitionProperty::TextShadow), - "top" => Ok(TransitionProperty::Top), - "transform" => Ok(TransitionProperty::Transform), - "vertical-align" => Ok(TransitionProperty::VerticalAlign), - "visibility" => Ok(TransitionProperty::Visibility), - "width" => Ok(TransitionProperty::Width), - "word-spacing" => Ok(TransitionProperty::WordSpacing), - "z-index" => Ok(TransitionProperty::ZIndex), - _ => Err(()) - } - } - pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> { - Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one)))) + Ok(SpecifiedValue(try!(input.parse_comma_separated(SingleSpecifiedValue::parse)))) } impl ToComputedValue for SpecifiedValue { @@ -788,54 +592,208 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } </%helpers:longhand> -<%helpers:longhand name="transition-delay"> +<%helpers:longhand name="transition-delay" animatable="False"> pub use properties::longhands::transition_duration::{SingleSpecifiedValue, SpecifiedValue}; pub use properties::longhands::transition_duration::{computed_value}; pub use properties::longhands::transition_duration::{get_initial_single_value}; pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one}; </%helpers:longhand> +<%helpers:longhand name="animation-name" animatable="False"> + use values::computed::ComputedValueAsSpecified; + + pub mod computed_value { + use cssparser::ToCss; + use std::fmt; + use string_cache::Atom; + + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub struct T(pub Vec<Atom>); + + impl ToCss for T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.0.is_empty() { + return dest.write_str("none") + } + + for (i, name) in self.0.iter().enumerate() { + if i != 0 { + try!(dest.write_str(", ")); + } + // NB: to_string() needed due to geckolib backend. + try!(dest.write_str(&*name.to_string())); + } + Ok(()) + } + } + } + + pub use self::computed_value::T as SpecifiedValue; + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T(vec![]) + } + + pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> { + use std::borrow::Cow; + Ok(SpecifiedValue(try!(input.parse_comma_separated(|input| { + input.expect_ident().map(Atom::from) + })))) + } + + impl ComputedValueAsSpecified for SpecifiedValue {} +</%helpers:longhand> + +<%helpers:longhand name="animation-duration" animatable="False"> + pub use super::transition_duration::computed_value; + pub use super::transition_duration::{parse, get_initial_value}; + pub use super::transition_duration::SpecifiedValue; +</%helpers:longhand> + +<%helpers:longhand name="animation-timing-function" animatable="False"> + pub use super::transition_timing_function::computed_value; + pub use super::transition_timing_function::{parse, get_initial_value}; + pub use super::transition_timing_function::SpecifiedValue; +</%helpers:longhand> + +<%helpers:longhand name="animation-iteration-count" animatable="False"> + use values::computed::ComputedValueAsSpecified; + + pub mod computed_value { + use cssparser::ToCss; + use std::fmt; + + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub enum AnimationIterationCount { + Number(u32), + Infinite, + } + + impl ToCss for AnimationIterationCount { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + AnimationIterationCount::Number(n) => write!(dest, "{}", n), + AnimationIterationCount::Infinite => dest.write_str("infinite"), + } + } + } + + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub struct T(pub Vec<AnimationIterationCount>); + + impl ToCss for T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.0.is_empty() { + return dest.write_str("none") + } + for (i, value) in self.0.iter().enumerate() { + if i != 0 { + try!(dest.write_str(", ")) + } + try!(value.to_css(dest)) + } + Ok(()) + } + } + } + + pub use self::computed_value::AnimationIterationCount; + pub use self::computed_value::T as SpecifiedValue; + + pub fn parse_one(input: &mut Parser) -> Result<AnimationIterationCount, ()> { + if input.try(|input| input.expect_ident_matching("infinite")).is_ok() { + Ok(AnimationIterationCount::Infinite) + } else { + let number = try!(input.expect_integer()); + if number < 0 { + return Err(()); + } + Ok(AnimationIterationCount::Number(number as u32)) + } + } + + + #[inline] + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> { + Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one)))) + } + + pub fn get_initial_value() -> computed_value::T { + computed_value::T(vec![AnimationIterationCount::Number(1)]) + } + + impl ComputedValueAsSpecified for SpecifiedValue {} +</%helpers:longhand> + +${helpers.keyword_list("animation-direction", + "normal reverse alternate alternate-reverse", + animatable=False)} + +${helpers.keyword_list("animation-play-state", + "running paused", + need_clone=True, + animatable=False)} + +${helpers.keyword_list("animation-fill-mode", + "none forwards backwards both", + animatable=False)} + +<%helpers:longhand name="animation-delay" animatable="False"> + pub use super::transition_duration::computed_value; + pub use super::transition_duration::{parse, get_initial_value}; + pub use super::transition_duration::SpecifiedValue; +</%helpers:longhand> + // CSSOM View Module // https://www.w3.org/TR/cssom-view-1/ ${helpers.single_keyword("scroll-behavior", "auto smooth", - products="gecko")} + products="gecko", + animatable=False)} // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-x ${helpers.single_keyword("scroll-snap-type-x", "none mandatory proximity", products="gecko", - gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE")} + gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE", + animatable=False)} // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-y ${helpers.single_keyword("scroll-snap-type-y", "none mandatory proximity", products="gecko", - gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE")} + gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE", + animatable=False)} // Compositing and Blending Level 1 // http://www.w3.org/TR/compositing-1/ ${helpers.single_keyword("isolation", "auto isolate", - products="gecko")} + products="gecko", + animatable=False)} ${helpers.single_keyword("page-break-after", "auto always avoid left right", - products="gecko")} + products="gecko", + animatable=False)} ${helpers.single_keyword("page-break-before", "auto always avoid left right", - products="gecko")} + products="gecko", + animatable=False)} ${helpers.single_keyword("page-break-inside", "auto avoid", products="gecko", gecko_ffi_name="mBreakInside", - gecko_constant_prefix="NS_STYLE_PAGE_BREAK")} + gecko_constant_prefix="NS_STYLE_PAGE_BREAK", + animatable=False)} // CSS Basic User Interface Module Level 3 // http://dev.w3.org/csswg/css-ui/ ${helpers.single_keyword("resize", "none both horizontal vertical", - products="gecko")} + products="gecko", + animatable=False)} // Non-standard ${helpers.single_keyword("-moz-appearance", @@ -862,10 +820,11 @@ ${helpers.single_keyword("-moz-appearance", """, gecko_ffi_name="mAppearance", gecko_constant_prefix="NS_THEME", - products="gecko")} + products="gecko", + animatable=False)} // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-binding -<%helpers:longhand name="-moz-binding" products="gecko"> +<%helpers:longhand name="-moz-binding" products="gecko" animatable="False"> use cssparser::{CssStringWriter, ToCss}; use gecko_bindings::ptr::{GeckoArcPrincipal, GeckoArcURI}; use std::fmt::{self, Write}; diff --git a/components/style/properties/longhand/color.mako.rs b/components/style/properties/longhand/color.mako.rs index fa19eab0726..31368dbe9ee 100644 --- a/components/style/properties/longhand/color.mako.rs +++ b/components/style/properties/longhand/color.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("Color", inherited=True) %> -<%helpers:raw_longhand name="color" need_clone="True"> +<%helpers:raw_longhand name="color" need_clone="True" animatable="True"> use cssparser::Color as CSSParserColor; use cssparser::RGBA; use values::specified::{CSSColor, CSSRGBA}; diff --git a/components/style/properties/longhand/column.mako.rs b/components/style/properties/longhand/column.mako.rs index 6e8975639af..91e1fc8fc3a 100644 --- a/components/style/properties/longhand/column.mako.rs +++ b/components/style/properties/longhand/column.mako.rs @@ -6,7 +6,8 @@ <% data.new_style_struct("Column", inherited=False) %> -<%helpers:longhand name="column-width" experimental="True"> +// FIXME: This prop should be animatable. +<%helpers:longhand name="column-width" experimental="True" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -70,7 +71,8 @@ } </%helpers:longhand> -<%helpers:longhand name="column-count" experimental="True"> +// FIXME: This prop should be animatable. +<%helpers:longhand name="column-count" experimental="True" animatable="False"> use cssparser::ToCss; use std::fmt; @@ -137,7 +139,8 @@ } </%helpers:longhand> -<%helpers:longhand name="column-gap" experimental="True"> +// FIXME: This prop should be animatable. +<%helpers:longhand name="column-gap" experimental="True" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; diff --git a/components/style/properties/longhand/counters.mako.rs b/components/style/properties/longhand/counters.mako.rs index 6dd97da6032..93fdb4bdc2c 100644 --- a/components/style/properties/longhand/counters.mako.rs +++ b/components/style/properties/longhand/counters.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %> -<%helpers:longhand name="content"> +<%helpers:longhand name="content" animatable="False"> use cssparser::Token; use std::ascii::AsciiExt; use values::computed::ComputedValueAsSpecified; @@ -171,7 +171,7 @@ } </%helpers:longhand> -<%helpers:longhand name="counter-increment"> +<%helpers:longhand name="counter-increment" animatable="False"> use std::fmt; use super::content; use values::computed::ComputedValueAsSpecified; @@ -241,7 +241,7 @@ } </%helpers:longhand> -<%helpers:longhand name="counter-reset"> +<%helpers:longhand name="counter-reset" animatable="False"> pub use super::counter_increment::{SpecifiedValue, computed_value, get_initial_value}; use super::counter_increment::{parse_common}; diff --git a/components/style/properties/longhand/effects.mako.rs b/components/style/properties/longhand/effects.mako.rs index 8781a4b553b..4fa881c93fd 100644 --- a/components/style/properties/longhand/effects.mako.rs +++ b/components/style/properties/longhand/effects.mako.rs @@ -9,9 +9,10 @@ ${helpers.predefined_type("opacity", "Opacity", - "1.0")} + "1.0", + animatable=True)} -<%helpers:longhand name="box-shadow"> +<%helpers:longhand name="box-shadow" animatable="True"> use cssparser::{self, ToCss}; use std::fmt; use values::LocalToCss; @@ -223,7 +224,8 @@ ${helpers.predefined_type("opacity", } </%helpers:longhand> -<%helpers:longhand name="clip"> +// FIXME: This prop should be animatable +<%helpers:longhand name="clip" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -394,7 +396,8 @@ ${helpers.predefined_type("opacity", } </%helpers:longhand> -<%helpers:longhand name="filter"> +// FIXME: This prop should be animatable +<%helpers:longhand name="filter" animatable="False"> //pub use self::computed_value::T as SpecifiedValue; use cssparser::ToCss; use std::fmt; @@ -630,7 +633,7 @@ ${helpers.predefined_type("opacity", } </%helpers:longhand> -<%helpers:longhand name="transform"> +<%helpers:longhand name="transform" animatable="True"> use app_units::Au; use values::CSSFloat; @@ -1174,13 +1177,20 @@ pub fn parse_origin(_: &ParserContext, input: &mut Parser) -> Result<OriginParse } } -${helpers.single_keyword("backface-visibility", "visible hidden")} +${helpers.single_keyword("backface-visibility", + "visible hidden", + animatable=False)} -${helpers.single_keyword("transform-box", "border-box fill-box view-box", products="gecko")} +${helpers.single_keyword("transform-box", + "border-box fill-box view-box", + products="gecko", + animatable=False)} -${helpers.single_keyword("transform-style", "auto flat preserve-3d")} +${helpers.single_keyword("transform-style", + "auto flat preserve-3d", + animatable=False)} -<%helpers:longhand name="transform-origin"> +<%helpers:longhand name="transform-origin" animatable="True"> use app_units::Au; use values::LocalToCss; use values::specified::{Length, LengthOrPercentage, Percentage}; @@ -1261,10 +1271,12 @@ ${helpers.single_keyword("transform-style", "auto flat preserve-3d")} </%helpers:longhand> ${helpers.predefined_type("perspective", - "LengthOrNone", - "computed::LengthOrNone::None")} + "LengthOrNone", + "computed::LengthOrNone::None", + animatable=True)} -<%helpers:longhand name="perspective-origin"> +// FIXME: This prop should be animatable +<%helpers:longhand name="perspective-origin" animatable="False"> use values::specified::{LengthOrPercentage, Percentage}; use cssparser::ToCss; @@ -1337,6 +1349,7 @@ ${helpers.predefined_type("perspective", </%helpers:longhand> ${helpers.single_keyword("mix-blend-mode", - """normal multiply screen overlay darken lighten color-dodge - color-burn hard-light soft-light difference exclusion hue - saturation color luminosity""", gecko_constant_prefix="NS_STYLE_BLEND")} + """normal multiply screen overlay darken lighten color-dodge + color-burn hard-light soft-light difference exclusion hue + saturation color luminosity""", gecko_constant_prefix="NS_STYLE_BLEND", + animatable=False)} diff --git a/components/style/properties/longhand/font.mako.rs b/components/style/properties/longhand/font.mako.rs index cf63c578adb..601097a604c 100644 --- a/components/style/properties/longhand/font.mako.rs +++ b/components/style/properties/longhand/font.mako.rs @@ -8,7 +8,7 @@ <% data.new_style_struct("Font", inherited=True, additional_methods=[Method("compute_font_hash", is_mut=True)]) %> -<%helpers:longhand name="font-family"> +<%helpers:longhand name="font-family" animatable="False"> use self::computed_value::FontFamily; use values::computed::ComputedValueAsSpecified; pub use self::computed_value::T as SpecifiedValue; @@ -117,10 +117,15 @@ </%helpers:longhand> -${helpers.single_keyword("font-style", "normal italic oblique", gecko_constant_prefix="NS_FONT_STYLE")} -${helpers.single_keyword("font-variant", "normal small-caps")} +${helpers.single_keyword("font-style", + "normal italic oblique", + gecko_constant_prefix="NS_FONT_STYLE", + animatable=False)} +${helpers.single_keyword("font-variant", + "normal small-caps", + animatable=False)} -<%helpers:longhand name="font-weight" need_clone="True"> +<%helpers:longhand name="font-weight" need_clone="True" animatable="True"> use cssparser::ToCss; use std::fmt; @@ -241,7 +246,7 @@ ${helpers.single_keyword("font-variant", "normal small-caps")} } </%helpers:longhand> -<%helpers:longhand name="font-size" need_clone="True"> +<%helpers:longhand name="font-size" need_clone="True" animatable="True"> use app_units::Au; use cssparser::ToCss; use std::fmt; @@ -307,8 +312,14 @@ ${helpers.single_keyword("font-variant", "normal small-caps")} } </%helpers:longhand> +// FIXME: This prop should be animatable ${helpers.single_keyword("font-stretch", - "normal ultra-condensed extra-condensed condensed semi-condensed semi-expanded \ - expanded extra-expanded ultra-expanded")} + "normal ultra-condensed extra-condensed condensed \ + semi-condensed semi-expanded expanded extra-expanded \ + ultra-expanded", + animatable=False)} -${helpers.single_keyword("font-kerning", "auto none normal", products="gecko")} +${helpers.single_keyword("font-kerning", + "auto none normal", + products="gecko", + animatable=False)} diff --git a/components/style/properties/longhand/inherited_box.mako.rs b/components/style/properties/longhand/inherited_box.mako.rs index 482780f3a6e..65fac030ec2 100644 --- a/components/style/properties/longhand/inherited_box.mako.rs +++ b/components/style/properties/longhand/inherited_box.mako.rs @@ -6,20 +6,22 @@ <% data.new_style_struct("InheritedBox", inherited=True, gecko_name="Visibility") %> -${helpers.single_keyword("direction", "ltr rtl", need_clone=True)} +${helpers.single_keyword("direction", "ltr rtl", need_clone=True, animatable=False)} // TODO: collapse. Well, do tables first. ${helpers.single_keyword("visibility", "visible hidden", extra_gecko_values="collapse", - gecko_ffi_name="mVisible")} + gecko_ffi_name="mVisible", + animatable=True)} // CSS Writing Modes Level 3 // http://dev.w3.org/csswg/css-writing-modes/ ${helpers.single_keyword("writing-mode", "horizontal-tb vertical-rl vertical-lr", experimental=True, - need_clone=True)} + need_clone=True, + animatable=False)} // FIXME(SimonSapin): Add 'mixed' and 'upright' (needs vertical text support) // FIXME(SimonSapin): initial (first) value should be 'mixed', when that's implemented @@ -29,13 +31,16 @@ ${helpers.single_keyword("text-orientation", experimental=True, need_clone=True, extra_gecko_values="mixed upright", - extra_servo_values="sideways-right sideways-left")} + extra_servo_values="sideways-right sideways-left", + animatable=False)} // CSS Color Module Level 4 // https://drafts.csswg.org/css-color/ -${helpers.single_keyword("color-adjust", "economy exact", products="gecko")} +${helpers.single_keyword("color-adjust", + "economy exact", products="gecko", + animatable=False)} -<%helpers:longhand name="image-rendering"> +<%helpers:longhand name="image-rendering" animatable="False"> pub mod computed_value { use cssparser::ToCss; use std::fmt; @@ -92,7 +97,10 @@ ${helpers.single_keyword("color-adjust", "economy exact", products="gecko")} // Used in the bottom-up flow construction traversal to avoid constructing flows for // descendants of nodes with `display: none`. -<%helpers:longhand name="-servo-under-display-none" derived_from="display" products="servo"> +<%helpers:longhand name="-servo-under-display-none" + derived_from="display" + products="servo" + animatable="False"> use cssparser::ToCss; use std::fmt; use values::computed::ComputedValueAsSpecified; diff --git a/components/style/properties/longhand/inherited_svg.mako.rs b/components/style/properties/longhand/inherited_svg.mako.rs index ca0c959a3ca..59e21e60ce5 100644 --- a/components/style/properties/longhand/inherited_svg.mako.rs +++ b/components/style/properties/longhand/inherited_svg.mako.rs @@ -10,36 +10,52 @@ inherited=True, gecko_name="SVG") %> +// TODO(emilio): Should some of these types be animatable? + // Section 10 - Text -${helpers.single_keyword("text-anchor", "start middle end", products="gecko")} +${helpers.single_keyword("text-anchor", + "start middle end", + products="gecko", + animatable=False)} // Section 11 - Painting: Filling, Stroking and Marker Symbols -${helpers.single_keyword("color-interpolation", "auto sRGB linearRGB", products="gecko")} - -${helpers.single_keyword("color-interpolation-filters", +${helpers.single_keyword("color-interpolation", "auto sRGB linearRGB", products="gecko", - gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION")} + animatable=False)} -${helpers.predefined_type("fill-opacity", "Opacity", "1.0", products="gecko")} +${helpers.single_keyword("color-interpolation-filters", "auto sRGB linearRGB", + products="gecko", + gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION", + animatable=False)} + +${helpers.predefined_type("fill-opacity", "Opacity", "1.0", + products="gecko", animatable=False)} -${helpers.single_keyword("fill-rule", "nonzero evenodd", products="gecko")} +${helpers.single_keyword("fill-rule", "nonzero evenodd", + products="gecko", animatable=False)} ${helpers.single_keyword("shape-rendering", "auto optimizeSpeed crispEdges geometricPrecision", - products="gecko")} + products="gecko", + animatable=False)} -${helpers.single_keyword("stroke-linecap", "butt round square", products="gecko")} +${helpers.single_keyword("stroke-linecap", "butt round square", + products="gecko", animatable=False)} -${helpers.single_keyword("stroke-linejoin", "miter round bevel", products="gecko")} +${helpers.single_keyword("stroke-linejoin", "miter round bevel", + products="gecko", animatable=False)} -${helpers.predefined_type("stroke-miterlimit", "Number", "4.0", "parse_at_least_one", - products="gecko")} +${helpers.predefined_type("stroke-miterlimit", "Number", "4.0", + "parse_at_least_one", products="gecko", + animatable=False)} -${helpers.predefined_type("stroke-opacity", "Opacity", "1.0", products="gecko")} +${helpers.predefined_type("stroke-opacity", "Opacity", "1.0", + products="gecko", animatable=False)} // Section 14 - Clipping, Masking and Compositing ${helpers.single_keyword("clip-rule", "nonzero evenodd", products="gecko", - gecko_constant_prefix="NS_STYLE_FILL_RULE")} + gecko_constant_prefix="NS_STYLE_FILL_RULE", + animatable=False)} diff --git a/components/style/properties/longhand/inherited_table.mako.rs b/components/style/properties/longhand/inherited_table.mako.rs index 7a11caae0b4..dd9b468ee1f 100644 --- a/components/style/properties/longhand/inherited_table.mako.rs +++ b/components/style/properties/longhand/inherited_table.mako.rs @@ -6,11 +6,17 @@ <% data.new_style_struct("InheritedTable", inherited=True, gecko_name="TableBorder") %> -${helpers.single_keyword("border-collapse", "separate collapse", gecko_constant_prefix="NS_STYLE_BORDER")} -${helpers.single_keyword("empty-cells", "show hide", gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS")} -${helpers.single_keyword("caption-side", "top bottom", extra_gecko_values="right left top-outside bottom-outside")} +${helpers.single_keyword("border-collapse", "separate collapse", + gecko_constant_prefix="NS_STYLE_BORDER", + animatable=False)} +${helpers.single_keyword("empty-cells", "show hide", + gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS", + animatable=False)} +${helpers.single_keyword("caption-side", "top bottom", + extra_gecko_values="right left top-outside bottom-outside", + animatable=False)} -<%helpers:longhand name="border-spacing"> +<%helpers:longhand name="border-spacing" animatable="False"> use app_units::Au; use values::LocalToCss; diff --git a/components/style/properties/longhand/inherited_text.mako.rs b/components/style/properties/longhand/inherited_text.mako.rs index b088e9bf63d..d902b62739a 100644 --- a/components/style/properties/longhand/inherited_text.mako.rs +++ b/components/style/properties/longhand/inherited_text.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %> -<%helpers:longhand name="line-height"> +<%helpers:longhand name="line-height" animatable="True"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -120,7 +120,7 @@ } </%helpers:longhand> -<%helpers:longhand name="text-align"> +<%helpers:longhand name="text-align" animatable="False"> pub use self::computed_value::T as SpecifiedValue; use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -179,7 +179,8 @@ } </%helpers:longhand> -<%helpers:longhand name="letter-spacing"> +// FIXME: This prop should be animatable. +<%helpers:longhand name="letter-spacing" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -243,7 +244,7 @@ } </%helpers:longhand> -<%helpers:longhand name="word-spacing"> +<%helpers:longhand name="word-spacing" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -309,27 +310,33 @@ ${helpers.predefined_type("text-indent", "LengthOrPercentage", - "computed::LengthOrPercentage::Length(Au(0))")} + "computed::LengthOrPercentage::Length(Au(0))", + animatable=True)} // Also known as "word-wrap" (which is more popular because of IE), but this is the preferred // name per CSS-TEXT 6.2. ${helpers.single_keyword("overflow-wrap", "normal break-word", - gecko_constant_prefix="NS_STYLE_OVERFLOWWRAP")} + gecko_constant_prefix="NS_STYLE_OVERFLOWWRAP", + animatable=False)} // TODO(pcwalton): Support `word-break: keep-all` once we have better CJK support. ${helpers.single_keyword("word-break", "normal break-all", extra_gecko_values="keep-all", - gecko_constant_prefix="NS_STYLE_WORDBREAK")} + gecko_constant_prefix="NS_STYLE_WORDBREAK", + animatable=False)} // TODO(pcwalton): Support `text-justify: distribute`. ${helpers.single_keyword("text-justify", "auto none inter-word", - products="servo")} + products="servo", + animatable=False)} <%helpers:longhand name="-servo-text-decorations-in-effect" - derived_from="display text-decoration" need_clone="True" products="servo"> + derived_from="display text-decoration" + need_clone="True" products="servo" + animatable="False"> use cssparser::{RGBA, ToCss}; use std::fmt; @@ -410,8 +417,10 @@ ${helpers.single_keyword("text-justify", } </%helpers:longhand> -<%helpers:single_keyword_computed name="white-space" values="normal pre nowrap pre-wrap pre-line", - gecko_constant_prefix="NS_STYLE_WHITESPACE"> +<%helpers:single_keyword_computed name="white-space" + values="normal pre nowrap pre-wrap pre-line" + gecko_constant_prefix="NS_STYLE_WHITESPACE" + animatable="False"> use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -448,7 +457,7 @@ ${helpers.single_keyword("text-justify", } </%helpers:single_keyword_computed> -<%helpers:longhand name="text-shadow"> +<%helpers:longhand name="text-shadow" animatable="True"> use cssparser::{self, ToCss}; use std::fmt; use values::LocalToCss; @@ -635,16 +644,22 @@ ${helpers.single_keyword("text-justify", // TODO(pcwalton): `full-width` ${helpers.single_keyword("text-transform", "none capitalize uppercase lowercase", - extra_gecko_values="full-width")} + extra_gecko_values="full-width", + animatable=False)} -${helpers.single_keyword("text-rendering", "auto optimizespeed optimizelegibility geometricprecision")} +${helpers.single_keyword("text-rendering", + "auto optimizespeed optimizelegibility geometricprecision", + animatable=False)} // CSS Text Module Level 3 // https://www.w3.org/TR/css-text-3/ -${helpers.single_keyword("hyphens", "none manual auto", products="gecko")} +${helpers.single_keyword("hyphens", "none manual auto", + products="gecko", animatable=False)} // CSS Ruby Layout Module Level 1 // https://www.w3.org/TR/css-ruby-1/ -${helpers.single_keyword("ruby-align", "start center space-between space-around", products="gecko")} +${helpers.single_keyword("ruby-align", "start center space-between space-around", + products="gecko", animatable=False)} -${helpers.single_keyword("ruby-position", "over under", products="gecko")} +${helpers.single_keyword("ruby-position", "over under", + products="gecko", animatable=False)} diff --git a/components/style/properties/longhand/list.mako.rs b/components/style/properties/longhand/list.mako.rs index 298f387cf13..c5d5c6be5b0 100644 --- a/components/style/properties/longhand/list.mako.rs +++ b/components/style/properties/longhand/list.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("List", inherited=True) %> -${helpers.single_keyword("list-style-position", "outside inside")} +${helpers.single_keyword("list-style-position", "outside inside", animatable=False)} // TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1: // @@ -23,9 +23,10 @@ ${helpers.single_keyword("list-style-type", """ myanmar oriya persian telugu thai tibetan cjk-earthly-branch cjk-heavenly-stem lower-greek hiragana hiragana-iroha katakana katakana-iroha""", - gecko_constant_prefix="NS_STYLE_LIST_STYLE")} + gecko_constant_prefix="NS_STYLE_LIST_STYLE", + animatable=False)} -<%helpers:longhand name="list-style-image"> +<%helpers:longhand name="list-style-image" animatable="False"> use cssparser::{ToCss, Token}; use std::fmt; use url::Url; @@ -92,7 +93,7 @@ ${helpers.single_keyword("list-style-type", """ } </%helpers:longhand> -<%helpers:longhand name="quotes"> +<%helpers:longhand name="quotes" animatable="False"> use std::borrow::Cow; use std::fmt; use values::computed::ComputedValueAsSpecified; diff --git a/components/style/properties/longhand/margin.mako.rs b/components/style/properties/longhand/margin.mako.rs index a9e4e477bbd..32f5243d745 100644 --- a/components/style/properties/longhand/margin.mako.rs +++ b/components/style/properties/longhand/margin.mako.rs @@ -8,5 +8,6 @@ % for side in ["top", "right", "bottom", "left"]: ${helpers.predefined_type("margin-" + side, "LengthOrPercentageOrAuto", - "computed::LengthOrPercentageOrAuto::Length(Au(0))")} + "computed::LengthOrPercentageOrAuto::Length(Au(0))", + animatable=True)} % endfor diff --git a/components/style/properties/longhand/outline.mako.rs b/components/style/properties/longhand/outline.mako.rs index cfe24868d29..0869df7a657 100644 --- a/components/style/properties/longhand/outline.mako.rs +++ b/components/style/properties/longhand/outline.mako.rs @@ -10,9 +10,10 @@ additional_methods=[Method("outline_has_nonzero_width", "bool")]) %> // TODO(pcwalton): `invert` -${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::CurrentColor")} +${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::CurrentColor", + animatable=True)} -<%helpers:longhand name="outline-style" need_clone="True"> +<%helpers:longhand name="outline-style" need_clone="True" animatable="False"> pub use values::specified::BorderStyle as SpecifiedValue; pub fn get_initial_value() -> SpecifiedValue { SpecifiedValue::none } pub mod computed_value { @@ -26,7 +27,7 @@ ${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::Curr } </%helpers:longhand> -<%helpers:longhand name="outline-width"> +<%helpers:longhand name="outline-width" animatable="True"> use app_units::Au; use cssparser::ToCss; use std::fmt; @@ -60,10 +61,12 @@ ${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::Curr </%helpers:longhand> // The -moz-outline-radius-* properties are non-standard and not on a standards track. +// TODO: Should they animate? % for corner in ["topleft", "topright", "bottomright", "bottomleft"]: ${helpers.predefined_type("-moz-outline-radius-" + corner, "BorderRadiusSize", "computed::BorderRadiusSize::zero()", - "parse", products="gecko")} + "parse", products="gecko", + animatable=False)} % endfor -${helpers.predefined_type("outline-offset", "Length", "Au(0)")} +${helpers.predefined_type("outline-offset", "Length", "Au(0)", animatable=True)} diff --git a/components/style/properties/longhand/padding.mako.rs b/components/style/properties/longhand/padding.mako.rs index f5448e393de..f022ba58878 100644 --- a/components/style/properties/longhand/padding.mako.rs +++ b/components/style/properties/longhand/padding.mako.rs @@ -9,5 +9,6 @@ % for side in ["top", "right", "bottom", "left"]: ${helpers.predefined_type("padding-" + side, "LengthOrPercentage", "computed::LengthOrPercentage::Length(Au(0))", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} % endfor diff --git a/components/style/properties/longhand/pointing.mako.rs b/components/style/properties/longhand/pointing.mako.rs index c2ad1502a9e..2394e2f3bee 100644 --- a/components/style/properties/longhand/pointing.mako.rs +++ b/components/style/properties/longhand/pointing.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("Pointing", inherited=True, gecko_name="UserInterface") %> -<%helpers:longhand name="cursor"> +<%helpers:longhand name="cursor" animatable="False"> pub use self::computed_value::T as SpecifiedValue; use values::computed::ComputedValueAsSpecified; @@ -54,16 +54,20 @@ // NB: `pointer-events: auto` (and use of `pointer-events` in anything that isn't SVG, in fact) // is nonstandard, slated for CSS4-UI. // TODO(pcwalton): SVG-only values. -${helpers.single_keyword("pointer-events", "auto none")} +${helpers.single_keyword("pointer-events", "auto none", animatable=False)} -${helpers.single_keyword("-moz-user-input", "none enabled disabled", products="gecko", - gecko_ffi_name="mUserInput", gecko_constant_prefix="NS_STYLE_USER_INPUT")} +${helpers.single_keyword("-moz-user-input", "none enabled disabled", + products="gecko", gecko_ffi_name="mUserInput", + gecko_constant_prefix="NS_STYLE_USER_INPUT", + animatable=False)} -${helpers.single_keyword("-moz-user-modify", "read-only read-write write-only", products="gecko", - gecko_ffi_name="mUserModify", gecko_constant_prefix="NS_STYLE_USER_MODIFY")} +${helpers.single_keyword("-moz-user-modify", "read-only read-write write-only", + products="gecko", gecko_ffi_name="mUserModify", + gecko_constant_prefix="NS_STYLE_USER_MODIFY", + animatable=False)} ${helpers.single_keyword("-moz-user-focus", "ignore normal select-after select-before select-menu select-same select-all none", - products="gecko", - gecko_ffi_name="mUserFocus", - gecko_constant_prefix="NS_STYLE_USER_FOCUS")} + products="gecko", gecko_ffi_name="mUserFocus", + gecko_constant_prefix="NS_STYLE_USER_FOCUS", + animatable=False)} diff --git a/components/style/properties/longhand/position.mako.rs b/components/style/properties/longhand/position.mako.rs index 5ce0b83f87d..a143dab44c5 100644 --- a/components/style/properties/longhand/position.mako.rs +++ b/components/style/properties/longhand/position.mako.rs @@ -8,10 +8,11 @@ % for side in ["top", "right", "bottom", "left"]: ${helpers.predefined_type(side, "LengthOrPercentageOrAuto", - "computed::LengthOrPercentageOrAuto::Auto")} + "computed::LengthOrPercentageOrAuto::Auto", + animatable=True)} % endfor -<%helpers:longhand name="z-index"> +<%helpers:longhand name="z-index" animatable="True"> use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -62,39 +63,49 @@ // http://www.w3.org/TR/css3-flexbox/ // Flex container properties -${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse", experimental=True)} +${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse", + experimental=True, animatable=False)} -${helpers.single_keyword("flex-wrap", "nowrap wrap wrap-reverse", experimental=True)} +${helpers.single_keyword("flex-wrap", "nowrap wrap wrap-reverse", + experimental=True, animatable=False)} // FIXME(stshine): The type of 'justify-content' and 'align-content' is uint16_t in gecko // FIXME(stshine): Its higher bytes are used to store fallback value. Disable them in geckolib for now ${helpers.single_keyword("justify-content", "flex-start flex-end center space-between space-around", experimental=True, gecko_constant_prefix="NS_STYLE_JUSTIFY", - products="servo")} + products="servo", + animatable=False)} ${helpers.single_keyword("align-items", "stretch flex-start flex-end center baseline", experimental=True, need_clone=True, - gecko_constant_prefix="NS_STYLE_ALIGN")} + gecko_constant_prefix="NS_STYLE_ALIGN", + animatable=False)} ${helpers.single_keyword("align-content", "stretch flex-start flex-end center space-between space-around", experimental=True, gecko_constant_prefix="NS_STYLE_ALIGN", - products="servo")} + products="servo", + animatable=False)} // Flex item properties -${helpers.predefined_type("flex-grow", "Number", "0.0", "parse_non_negative", experimental=True)} +${helpers.predefined_type("flex-grow", "Number", + "0.0", "parse_non_negative", + experimental=True, animatable=True)} -${helpers.predefined_type("flex-shrink", "Number", "1.0", "parse_non_negative", experimental=True)} +${helpers.predefined_type("flex-shrink", "Number", + "1.0", "parse_non_negative", + experimental=True, animatable=True)} ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center baseline", experimental=True, need_clone=True, - gecko_constant_prefix="NS_STYLE_ALIGN")} + gecko_constant_prefix="NS_STYLE_ALIGN", + animatable=False)} // https://drafts.csswg.org/css-flexbox/#propdef-order -<%helpers:longhand name="order"> +<%helpers:longhand name="order" animatable="True"> use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -115,41 +126,53 @@ ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center } </%helpers:longhand> +// FIXME: This property should be animatable. ${helpers.predefined_type("flex-basis", "LengthOrPercentageOrAutoOrContent", - "computed::LengthOrPercentageOrAutoOrContent::Auto")} + "computed::LengthOrPercentageOrAutoOrContent::Auto", + animatable=False)} ${helpers.predefined_type("width", "LengthOrPercentageOrAuto", "computed::LengthOrPercentageOrAuto::Auto", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} ${helpers.predefined_type("height", "LengthOrPercentageOrAuto", "computed::LengthOrPercentageOrAuto::Auto", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} ${helpers.predefined_type("min-width", "LengthOrPercentage", "computed::LengthOrPercentage::Length(Au(0))", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} + ${helpers.predefined_type("max-width", "LengthOrPercentageOrNone", "computed::LengthOrPercentageOrNone::None", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} ${helpers.predefined_type("min-height", "LengthOrPercentage", "computed::LengthOrPercentage::Length(Au(0))", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} + ${helpers.predefined_type("max-height", "LengthOrPercentageOrNone", "computed::LengthOrPercentageOrNone::None", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} ${helpers.single_keyword("box-sizing", - "content-box border-box")} + "content-box border-box", + animatable=False)} // CSS Image Values and Replaced Content Module Level 3 // https://drafts.csswg.org/css-images-3/ -${helpers.single_keyword("object-fit", "fill contain cover none scale-down", products="gecko")} +${helpers.single_keyword("object-fit", "fill contain cover none scale-down", + products="gecko", animatable=False)} diff --git a/components/style/properties/longhand/svg.mako.rs b/components/style/properties/longhand/svg.mako.rs index a9921e46d7d..49dd2bdbdac 100644 --- a/components/style/properties/longhand/svg.mako.rs +++ b/components/style/properties/longhand/svg.mako.rs @@ -6,36 +6,46 @@ <% data.new_style_struct("SVG", inherited=False, gecko_name="SVGReset") %> +// TODO: Which of these should be animatable properties? ${helpers.single_keyword("dominant-baseline", """auto use-script no-change reset-size ideographic alphabetic hanging mathematical central middle text-after-edge text-before-edge""", - products="gecko")} + products="gecko", + animatable=False)} -${helpers.single_keyword("vector-effect", "none non-scaling-stroke", products="gecko")} +${helpers.single_keyword("vector-effect", "none non-scaling-stroke", + products="gecko", animatable=False)} // Section 13 - Gradients and Patterns ${helpers.predefined_type( "stop-color", "CSSColor", "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })", - products="gecko")} + products="gecko", + animatable=False)} -${helpers.predefined_type("stop-opacity", "Opacity", "1.0", products="gecko")} +${helpers.predefined_type("stop-opacity", "Opacity", "1.0", + products="gecko", + animatable=False)} // Section 15 - Filter Effects ${helpers.predefined_type( "flood-color", "CSSColor", "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })", - products="gecko")} + products="gecko", + animatable=False)} -${helpers.predefined_type("flood-opacity", "Opacity", "1.0", products="gecko")} +${helpers.predefined_type("flood-opacity", "Opacity", + "1.0", products="gecko", animatable=False)} ${helpers.predefined_type( "lighting-color", "CSSColor", "CSSParserColor::RGBA(RGBA { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 })", - products="gecko")} + products="gecko", + animatable=False)} // CSS Masking Module Level 1 // https://www.w3.org/TR/css-masking-1/ -${helpers.single_keyword("mask-type", "luminance alpha", products="gecko")} +${helpers.single_keyword("mask-type", "luminance alpha", + products="gecko", animatable=False)} diff --git a/components/style/properties/longhand/table.mako.rs b/components/style/properties/longhand/table.mako.rs index cfb3050c06d..a83d25c7c29 100644 --- a/components/style/properties/longhand/table.mako.rs +++ b/components/style/properties/longhand/table.mako.rs @@ -6,4 +6,5 @@ <% data.new_style_struct("Table", inherited=False) %> -${helpers.single_keyword("table-layout", "auto fixed", gecko_ffi_name="mLayoutStrategy")} +${helpers.single_keyword("table-layout", "auto fixed", + gecko_ffi_name="mLayoutStrategy", animatable=False)} diff --git a/components/style/properties/longhand/text.mako.rs b/components/style/properties/longhand/text.mako.rs index 2e34a0c6243..50a4ef3476c 100644 --- a/components/style/properties/longhand/text.mako.rs +++ b/components/style/properties/longhand/text.mako.rs @@ -12,12 +12,16 @@ Method("has_overline", "bool"), Method("has_line_through", "bool")]) %> -${helpers.single_keyword("text-overflow", "clip ellipsis")} +${helpers.single_keyword("text-overflow", "clip ellipsis", animatable=False)} -${helpers.single_keyword("unicode-bidi", "normal embed isolate bidi-override isolate-override plaintext")} +${helpers.single_keyword("unicode-bidi", + "normal embed isolate bidi-override isolate-override plaintext", + animatable=False)} +// FIXME: This prop should be animatable. <%helpers:longhand name="${'text-decoration' if product == 'servo' else 'text-decoration-line'}" - custom_cascade="${product == 'servo'}"> + custom_cascade="${product == 'servo'}" + animatable="False"> use cssparser::ToCss; use std::fmt; use values::computed::ComputedValueAsSpecified; @@ -116,9 +120,11 @@ ${helpers.single_keyword("unicode-bidi", "normal embed isolate bidi-override iso ${helpers.single_keyword("text-decoration-style", "solid double dotted dashed wavy -moz-none", - products="gecko")} + products="gecko", + animatable=False)} ${helpers.predefined_type( "text-decoration-color", "CSSColor", "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })", - products="gecko")} + products="gecko", + animatable=True)} diff --git a/components/style/properties/longhand/ui.mako.rs b/components/style/properties/longhand/ui.mako.rs index 6e2fdb345fc..b78222ab560 100644 --- a/components/style/properties/longhand/ui.mako.rs +++ b/components/style/properties/longhand/ui.mako.rs @@ -9,8 +9,11 @@ // https://drafts.csswg.org/css-ui-3/ <% data.new_style_struct("UI", inherited=False, gecko_name="UIReset") %> -${helpers.single_keyword("ime-mode", "normal auto active disabled inactive", products="gecko", - gecko_ffi_name="mIMEMode")} +${helpers.single_keyword("ime-mode", "normal auto active disabled inactive", + products="gecko", gecko_ffi_name="mIMEMode", + animatable=False)} ${helpers.single_keyword("-moz-user-select", "auto text none all", products="gecko", - gecko_ffi_name="mUserSelect", gecko_constant_prefix="NS_STYLE_USER_SELECT")} + gecko_ffi_name="mUserSelect", + gecko_constant_prefix="NS_STYLE_USER_SELECT", + animatable=False)} diff --git a/components/style/properties/longhand/xul.mako.rs b/components/style/properties/longhand/xul.mako.rs index cb5db00717c..06af2c2b1a4 100644 --- a/components/style/properties/longhand/xul.mako.rs +++ b/components/style/properties/longhand/xul.mako.rs @@ -8,8 +8,11 @@ // Non-standard properties that Gecko uses for XUL elements. <% data.new_style_struct("XUL", inherited=False) %> -${helpers.single_keyword("-moz-box-align", "stretch start center baseline end", products="gecko", - gecko_ffi_name="mBoxAlign", gecko_constant_prefix="NS_STYLE_BOX_ALIGN")} +${helpers.single_keyword("-moz-box-align", "stretch start center baseline end", + products="gecko", gecko_ffi_name="mBoxAlign", + gecko_constant_prefix="NS_STYLE_BOX_ALIGN", + animatable=False)} -${helpers.predefined_type("-moz-box-flex", "Number", "0.0", "parse_non_negative", products="gecko", - gecko_ffi_name="mBoxFlex")} +${helpers.predefined_type("-moz-box-flex", "Number", "0.0", "parse_non_negative", + products="gecko", gecko_ffi_name="mBoxFlex", + animatable=False)} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 2e5ab399482..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; @@ -132,6 +132,10 @@ pub mod shorthands { <%include file="/shorthand/text.mako.rs" /> } +pub mod animated_properties { + <%include file="/helpers/animated_properties.mako.rs" /> +} + // TODO(SimonSapin): Convert this to a syntax extension rather than a Mako template. // Maybe submit for inclusion in libstd? @@ -1044,13 +1048,31 @@ impl PropertyDeclaration { PropertyDeclaration::Custom(_, _) => &[] } } + + /// Returns true if this property is one of the animable properties, false + /// otherwise. + pub fn is_animatable(&self) -> bool { + match *self { + % for property in data.longhands: + PropertyDeclaration::${property.camel_case}(_) => { + % if property.animatable: + true + % else: + false + % endif + } + % endfor + PropertyDeclaration::Custom(..) => false, + } + } } 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); @@ -1079,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} { @@ -1121,32 +1143,45 @@ pub mod style_structs { } % endfor % elif style_struct.trait_name == "Box": + #[inline] fn clone_display(&self) -> longhands::display::computed_value::T { self.display.clone() } + #[inline] fn clone_position(&self) -> longhands::position::computed_value::T { self.position.clone() } + #[inline] fn clone_float(&self) -> longhands::float::computed_value::T { self.float.clone() } + #[inline] fn clone_overflow_x(&self) -> longhands::overflow_x::computed_value::T { self.overflow_x.clone() } + #[inline] fn clone_overflow_y(&self) -> longhands::overflow_y::computed_value::T { self.overflow_y.clone() } + #[inline] + fn clone_animation_play_state(&self) -> longhands::animation_play_state::computed_value::T { + self.animation_play_state.clone() + } + #[inline] fn transition_count(&self) -> usize { self.transition_property.0.len() } % elif style_struct.trait_name == "Color": + #[inline] fn clone_color(&self) -> longhands::color::computed_value::T { self.color.clone() } % elif style_struct.trait_name == "Font": + #[inline] fn clone_font_size(&self) -> longhands::font_size::computed_value::T { self.font_size.clone() } + #[inline] fn clone_font_weight(&self) -> longhands::font_weight::computed_value::T { self.font_weight.clone() } @@ -1159,42 +1194,53 @@ pub mod style_structs { self.hash = hasher.finish() } % elif style_struct.trait_name == "InheritedBox": + #[inline] fn clone_direction(&self) -> longhands::direction::computed_value::T { self.direction.clone() } + #[inline] fn clone_writing_mode(&self) -> longhands::writing_mode::computed_value::T { self.writing_mode.clone() } + #[inline] fn clone_text_orientation(&self) -> longhands::text_orientation::computed_value::T { self.text_orientation.clone() } % elif style_struct.trait_name == "InheritedText" and product == "servo": + #[inline] fn clone__servo_text_decorations_in_effect(&self) -> longhands::_servo_text_decorations_in_effect::computed_value::T { self._servo_text_decorations_in_effect.clone() } % elif style_struct.trait_name == "Outline": + #[inline] fn clone_outline_style(&self) -> longhands::outline_style::computed_value::T { self.outline_style.clone() } + #[inline] fn outline_has_nonzero_width(&self) -> bool { self.outline_width != ::app_units::Au(0) } % elif style_struct.trait_name == "Position": + #[inline] fn clone_align_items(&self) -> longhands::align_items::computed_value::T { self.align_items.clone() } + #[inline] fn clone_align_self(&self) -> longhands::align_self::computed_value::T { self.align_self.clone() } % elif style_struct.trait_name == "Text": <% text_decoration_field = 'text_decoration' if product == 'servo' else 'text_decoration_line' %> + #[inline] fn has_underline(&self) -> bool { self.${text_decoration_field}.underline } + #[inline] fn has_overline(&self) -> bool { self.${text_decoration_field}.overline } + #[inline] fn has_line_through(&self) -> bool { self.${text_decoration_field}.line_through } @@ -1204,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 @@ -1247,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/properties/shorthand/box.mako.rs b/components/style/properties/shorthand/box.mako.rs index 48b49ca4811..27e923ba1aa 100644 --- a/components/style/properties/shorthand/box.mako.rs +++ b/components/style/properties/shorthand/box.mako.rs @@ -32,7 +32,7 @@ let (mut timing_function, mut delay) = (None, None); loop { if property.is_none() { - if let Ok(value) = input.try(|input| transition_property::parse_one(input)) { + if let Ok(value) = input.try(transition_property::SingleSpecifiedValue::parse) { property = Some(value); continue } diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index 29e3fa36927..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; @@ -90,6 +93,7 @@ pub trait SelectorImplExt : SelectorImpl + Sized { }) } + fn pseudo_is_before_or_after(pseudo: &Self::PseudoElement) -> bool; fn pseudo_class_state_flag(pc: &Self::NonTSPseudoClass) -> ElementState; @@ -110,6 +114,15 @@ pub enum PseudoElement { impl PseudoElement { #[inline] + pub fn is_before_or_after(&self) -> bool { + match *self { + PseudoElement::Before | + PseudoElement::After => true, + _ => false, + } + } + + #[inline] pub fn cascade_type(&self) -> PseudoElementCascadeType { match *self { PseudoElement::Before | @@ -250,6 +263,11 @@ impl SelectorImplExt for ServoSelectorImpl { } #[inline] + fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool { + pseudo.is_before_or_after() + } + + #[inline] fn get_user_or_user_agent_stylesheets() -> &'static [Stylesheet<Self>] { &*USER_OR_USER_AGENT_STYLESHEETS } diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 79fd8c1ae32..35a38d5ed14 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -7,6 +7,7 @@ use dom::PresentationalHintsSynthetizer; use element_state::*; use error_reporting::StdoutErrorReporter; +use keyframes::KeyframesAnimation; use media_queries::{Device, MediaType}; use parser::ParserContextExtraData; use properties::{self, PropertyDeclaration, PropertyDeclarationBlock}; @@ -22,8 +23,9 @@ use std::collections::HashMap; use std::hash::BuildHasherDefault; use std::process; use std::sync::Arc; +use string_cache::Atom; use style_traits::viewport::ViewportConstraints; -use stylesheets::{CSSRuleIteratorExt, Origin, Stylesheet}; +use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet}; use url::Url; use util::opts; use util::resource_files::read_resource_file; @@ -126,6 +128,9 @@ pub struct Stylist<Impl: SelectorImplExt> { PerPseudoElementSelectorMap<Impl>, BuildHasherDefault<::fnv::FnvHasher>>, + /// A map with all the animations indexed by name. + animations: HashMap<Atom, KeyframesAnimation>, + /// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// These are eagerly computed once, and then used to resolve the new /// computed values on the fly on layout. @@ -150,6 +155,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { element_map: PerPseudoElementSelectorMap::new(), pseudos_map: HashMap::with_hasher(Default::default()), + animations: HashMap::with_hasher(Default::default()), precomputed_pseudo_element_decls: HashMap::with_hasher(Default::default()), rules_source_order: 0, state_deps: DependencySet::new(), @@ -173,6 +179,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { self.element_map = PerPseudoElementSelectorMap::new(); self.pseudos_map = HashMap::with_hasher(Default::default()); + self.animations = HashMap::with_hasher(Default::default()); Impl::each_eagerly_cascaded_pseudo_element(|pseudo| { self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new()); }); @@ -233,17 +240,36 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { }; ); - for style_rule in stylesheet.effective_rules(&self.device).style() { - append!(style_rule, normal); - append!(style_rule, important); - rules_source_order += 1; - for selector in &style_rule.selectors { - self.state_deps.note_selector(selector.compound_selectors.clone()); + for rule in stylesheet.effective_rules(&self.device) { + match *rule { + CSSRule::Style(ref style_rule) => { + append!(style_rule, normal); + append!(style_rule, important); + rules_source_order += 1; + for selector in &style_rule.selectors { + self.state_deps.note_selector(selector.compound_selectors.clone()); + } + + self.rules_source_order = rules_source_order; + } + CSSRule::Keyframes(ref keyframes_rule) => { + debug!("Found valid keyframes rule: {:?}", keyframes_rule); + if let Some(animation) = KeyframesAnimation::from_keyframes(&keyframes_rule.keyframes) { + debug!("Found valid keyframe animation: {:?}", animation); + self.animations.insert(keyframes_rule.name.clone(), + animation); + } else { + // If there's a valid keyframes rule, even if it doesn't + // produce an animation, should shadow other animations + // with the same name. + self.animations.remove(&keyframes_rule.name); + } + } + // We don't care about any other rule. + _ => {} } } - self.rules_source_order = rules_source_order; - Impl::each_precomputed_pseudo_element(|pseudo| { // TODO: Consider not doing this and just getting the rules on the // fly. It should be a bit slower, but we'd take rid of the @@ -270,7 +296,8 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { let (computed, _) = properties::cascade(self.device.au_viewport_size(), &declarations, false, - parent.map(|p| &**p), None, + parent.map(|p| &**p), + None, Box::new(StdoutErrorReporter)); Some(Arc::new(computed)) } else { @@ -437,6 +464,11 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { pub fn is_device_dirty(&self) -> bool { self.is_device_dirty } + + #[inline] + pub fn animations(&self) -> &HashMap<Atom, KeyframesAnimation> { + &self.animations + } } /// Map that contains the CSS rules for a given origin. 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<ServoSelectorImpl>; pub type PrivateStyleData = data::PrivateStyleData<ServoSelectorImpl, ServoComputedValues>; pub type Stylist = selector_matching::Stylist<ServoSelectorImpl>; pub type SharedStyleContext = context::SharedStyleContext<ServoSelectorImpl>; +pub type Animation = animation::Animation<ServoSelectorImpl>; diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index bdea2426405..98864868233 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -5,10 +5,11 @@ //! 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}; +use keyframes::{Keyframe, parse_keyframe_list}; use media_queries::{Device, MediaQueryList, parse_media_query_list}; use parser::{ParserContext, ParserContextExtraData, log_css_error}; use properties::{PropertyDeclarationBlock, parse_property_declaration_list}; @@ -62,6 +63,14 @@ pub enum CSSRule<Impl: SelectorImpl> { Media(MediaRule<Impl>), FontFace(FontFaceRule), Viewport(ViewportRule), + Keyframes(KeyframesRule), +} + + +#[derive(Debug, HeapSizeOf, PartialEq)] +pub struct KeyframesRule { + pub name: Atom, + pub keyframes: Vec<Keyframe>, } #[derive(Debug, PartialEq)] @@ -71,6 +80,7 @@ pub struct MediaRule<Impl: SelectorImpl> { pub rules: Vec<CSSRule<Impl>>, } + impl<Impl: SelectorImpl> MediaRule<Impl> { #[inline] pub fn evaluate(&self, device: &Device) -> bool { @@ -127,7 +137,7 @@ impl<Impl: SelectorImpl> Stylesheet<Impl> { let mut input = Parser::new(css); input.look_for_viewport_percentages(); - let mut rules = Vec::new(); + let mut rules = vec![]; { let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser); while let Some(result) = iter.next() { @@ -142,6 +152,7 @@ impl<Impl: SelectorImpl> Stylesheet<Impl> { Some(namespace.clone()); } } + rules.push(rule); } Err(range) => { @@ -153,6 +164,7 @@ impl<Impl: SelectorImpl> Stylesheet<Impl> { } } } + Stylesheet { origin: origin, rules: rules, @@ -253,7 +265,7 @@ pub mod rule_filter { use std::marker::PhantomData; use super::super::font_face::FontFaceRule; use super::super::viewport::ViewportRule; - use super::{CSSRule, MediaRule, StyleRule}; + use super::{CSSRule, KeyframesRule, MediaRule, StyleRule}; macro_rules! rule_filter { ($variant:ident -> $value:ty) => { @@ -266,6 +278,7 @@ pub mod rule_filter { impl<'a, I, Impl: SelectorImpl + 'a> $variant<'a, I> where I: Iterator<Item=&'a CSSRule<Impl>> { + #[inline] pub fn new(iter: I) -> $variant<'a, I> { $variant { iter: iter, @@ -300,6 +313,7 @@ pub mod rule_filter { rule_filter!(Style -> StyleRule<Impl>); rule_filter!(FontFace -> FontFaceRule); rule_filter!(Viewport -> ViewportRule); + rule_filter!(Keyframes -> KeyframesRule); } /// Extension methods for `CSSRule` iterators. @@ -315,6 +329,9 @@ pub trait CSSRuleIteratorExt<'a, Impl: SelectorImpl + 'a>: Iterator<Item=&'a CSS /// Yield only @viewport rules. fn viewport(self) -> rule_filter::Viewport<'a, Self>; + + /// Yield only @keyframes rules. + fn keyframes(self) -> rule_filter::Keyframes<'a, Self>; } impl<'a, I, Impl: SelectorImpl + 'a> CSSRuleIteratorExt<'a, Impl> for I where I: Iterator<Item=&'a CSSRule<Impl>> { @@ -337,6 +354,11 @@ impl<'a, I, Impl: SelectorImpl + 'a> CSSRuleIteratorExt<'a, Impl> for I where I: fn viewport(self) -> rule_filter::Viewport<'a, I> { rule_filter::Viewport::new(self) } + + #[inline] + fn keyframes(self) -> rule_filter::Keyframes<'a, I> { + rule_filter::Keyframes::new(self) + } } fn parse_nested_rules<Impl: SelectorImpl>(context: &ParserContext, input: &mut Parser) -> Vec<CSSRule<Impl>> { @@ -376,9 +398,14 @@ enum State { enum AtRulePrelude { + /// A @font-face rule prelude. FontFace, + /// A @media rule prelude, with its media queries. Media(MediaQueryList), + /// A @viewport rule prelude. Viewport, + /// A @keyframes rule, with its animation name. + Keyframes(Atom), } @@ -478,6 +505,15 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> Err(()) } }, + "keyframes" => { + 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(()) } } @@ -496,11 +532,16 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> AtRulePrelude::Viewport => { ViewportRule::parse(input, self.context).map(CSSRule::Viewport) } + AtRulePrelude::Keyframes(name) => { + Ok(CSSRule::Keyframes(KeyframesRule { + name: name, + keyframes: parse_keyframe_list(&self.context, input), + })) + } } } } - impl<'a, 'b, Impl: SelectorImpl> QualifiedRuleParser for NestedRuleParser<'a, 'b, Impl> { type Prelude = Vec<Selector<Impl>>; type QualifiedRule = CSSRule<Impl>; diff --git a/components/style/traversal.rs b/components/style/traversal.rs index 281806e2263..5e5b7d31948 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -219,10 +219,9 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C, // Perform the CSS cascade. unsafe { node.cascade_node(&context.shared_context(), + &context.local_context(), parent_opt, - &applicable_declarations, - &mut context.local_context().applicable_declarations_cache.borrow_mut(), - &context.shared_context().new_animations_sender); + &applicable_declarations); } // Add ourselves to the LRU cache. diff --git a/components/style/values.rs b/components/style/values.rs index f515a403ab4..5dea6f3cb7a 100644 --- a/components/style/values.rs +++ b/components/style/values.rs @@ -1391,16 +1391,13 @@ pub mod specified { pub fn parse_border_radius(input: &mut Parser) -> Result<BorderRadiusSize, ()> { input.try(BorderRadiusSize::parse).or_else(|()| { match_ignore_ascii_case! { try!(input.expect_ident()), - "thin" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(1.)))), - "medium" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(3.)))), - "thick" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(5.)))), - _ => Err(()) + "thin" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(1.)))), + "medium" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(3.)))), + "thick" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(5.)))), + _ => Err(()) } }) } @@ -1752,7 +1749,7 @@ pub mod computed { } - #[derive(PartialEq, Clone, Copy)] + #[derive(Debug, PartialEq, Clone, Copy)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct BorderRadiusSize(pub Size2D<LengthOrPercentage>); diff --git a/components/style/viewport.rs b/components/style/viewport.rs index f763600d399..06411425922 100644 --- a/components/style/viewport.rs +++ b/components/style/viewport.rs @@ -562,8 +562,8 @@ pub trait MaybeNew { impl MaybeNew for ViewportConstraints { fn maybe_new(initial_viewport: TypedSize2D<ViewportPx, f32>, - rule: &ViewportRule) - -> Option<ViewportConstraints> + rule: &ViewportRule) + -> Option<ViewportConstraints> { use std::cmp; diff --git a/ports/geckolib/data.rs b/ports/geckolib/data.rs index dd0fa4dc18d..0f07ef83c57 100644 --- a/ports/geckolib/data.rs +++ b/ports/geckolib/data.rs @@ -6,12 +6,11 @@ use euclid::Size2D; use euclid::size::TypedSize2D; use gecko_bindings::bindings::RawServoStyleSet; use num_cpus; -use selector_impl::{Stylist, Stylesheet, SharedStyleContext}; +use selector_impl::{GeckoSelectorImpl, Stylist, Stylesheet, SharedStyleContext}; use std::cmp; use std::collections::HashMap; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, RwLock}; -use style::animation::Animation; use style::dom::OpaqueNode; use style::media_queries::{Device, MediaType}; use style::parallel::WorkQueueData; @@ -19,6 +18,8 @@ use util::geometry::ViewportPx; use util::thread_state; use util::workqueue::WorkQueue; +pub type Animation = ::style::animation::Animation<GeckoSelectorImpl>; + pub struct PerDocumentStyleData { /// Rule processor. pub stylist: Arc<Stylist>, diff --git a/ports/geckolib/properties.mako.rs b/ports/geckolib/properties.mako.rs index 7a3f79e6748..6493a399ea9 100644 --- a/ports/geckolib/properties.mako.rs +++ b/ports/geckolib/properties.mako.rs @@ -39,7 +39,7 @@ use values::{StyleCoordHelpers, ToGeckoStyleCoord, convert_nscolor_to_rgba}; use values::{convert_rgba_to_nscolor, debug_assert_unit_is_safe_to_copy}; use values::round_border_to_device_pixels; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct GeckoComputedValues { % for style_struct in data.style_structs: ${style_struct.ident}: Arc<${style_struct.gecko_struct_name}>, @@ -389,6 +389,12 @@ impl Debug for ${style_struct.gecko_struct_name} { force_stub += ["list-style-type", "text-overflow"] # These are booleans. force_stub += ["page-break-after", "page-break-before"] + # In a nsTArray, have to be done manually, but probably not too much work + # (the "filling them", not the "making them work") + force_stub += ["animation-name", "animation-duration", + "animation-timing-function", "animation-iteration-count", + "animation-direction", "animation-play-state", + "animation-fill-mode", "animation-delay"] # Types used with predefined_type()-defined properties that we can auto-generate. predefined_types = { diff --git a/ports/geckolib/selector_impl.rs b/ports/geckolib/selector_impl.rs index 1c315df24c1..5c5e1731b3b 100644 --- a/ports/geckolib/selector_impl.rs +++ b/ports/geckolib/selector_impl.rs @@ -16,6 +16,7 @@ pub type PrivateStyleData = style::data::PrivateStyleData<GeckoSelectorImpl, Gec #[cfg(feature = "servo_features")] known_heap_size!(0, GeckoSelectorImpl, PseudoElement, NonTSPseudoClass); +#[derive(Debug, Clone)] pub struct GeckoSelectorImpl; #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -380,6 +381,15 @@ impl SelectorImplExt for GeckoSelectorImpl { } #[inline] + fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool { + match *pseudo { + PseudoElement::Before | + PseudoElement::After => true, + _ => false, + } + } + + #[inline] fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState { pc.state_flag() } diff --git a/ports/geckolib/string_cache/lib.rs b/ports/geckolib/string_cache/lib.rs index 91f81697dbf..0486e7efb7b 100644 --- a/ports/geckolib/string_cache/lib.rs +++ b/ports/geckolib/string_cache/lib.rs @@ -15,6 +15,7 @@ use gecko_bindings::structs::nsIAtom; use heapsize::HeapSizeOf; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::Cow; +use std::char; use std::fmt; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; @@ -206,6 +207,15 @@ impl fmt::Debug for Atom { } } +impl fmt::Display for Atom { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + for c in char::decode_utf16(self.as_slice().iter().cloned()) { + try!(write!(w, "{}", c.unwrap_or(char::REPLACEMENT_CHARACTER))) + } + Ok(()) + } +} + impl<'a> From<&'a str> for Atom { #[inline] fn from(string: &str) -> Atom { diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index a771cae0800..dc04712d1a8 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -9,11 +9,13 @@ use std::borrow::ToOwned; use std::sync::Arc; use std::sync::Mutex; use string_cache::{Atom, Namespace}; +use style::error_reporting::ParseErrorReporter; +use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage}; use style::parser::ParserContextExtraData; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue, longhands}; -use style::stylesheets::{CSSRule, StyleRule, Origin}; -use style::error_reporting::ParseErrorReporter; use style::servo::Stylesheet; +use style::stylesheets::{CSSRule, StyleRule, KeyframesRule, Origin}; +use style::values::specified::{LengthOrPercentageOrAuto, Percentage}; use url::Url; #[test] @@ -24,7 +26,10 @@ fn test_parse_stylesheet() { input[type=hidden i] { display: none !important; } html , body /**/ { display: block; } #d1 > .ok { background: blue; } - "; + @keyframes foo { + from { width: 0% } + to { width: 100%} + }"; let url = Url::parse("about::test").unwrap(); let stylesheet = Stylesheet::from_str(css, url, Origin::UserAgent, Box::new(CSSErrorReporterTest), @@ -145,6 +150,28 @@ fn test_parse_stylesheet() { important: Arc::new(vec![]), }, }), + CSSRule::Keyframes(KeyframesRule { + name: "foo".into(), + keyframes: vec![ + Keyframe { + selector: KeyframeSelector::new_for_unit_testing( + vec![KeyframePercentage::new(0.)]), + declarations: Arc::new(vec![ + PropertyDeclaration::Width(DeclaredValue::Value( + LengthOrPercentageOrAuto::Percentage(Percentage(0.)))), + ]), + }, + Keyframe { + selector: KeyframeSelector::new_for_unit_testing( + vec![KeyframePercentage::new(1.)]), + declarations: Arc::new(vec![ + PropertyDeclaration::Width(DeclaredValue::Value( + LengthOrPercentageOrAuto::Percentage(Percentage(1.)))), + ]), + }, + ] + }) + ], }); } |