aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2016-06-28 17:31:01 -0500
committerGitHub <noreply@github.com>2016-06-28 17:31:01 -0500
commitd3a81373e44634c30d31da0457e1c1e86c0911ed (patch)
treefbefd4e32ec3adff49b2ffec316f8efff6c647d4
parent7b2080c5b7c933f74c0d92249a66f8602340ebbe (diff)
parent392f243ca7dd08a34da4ca15dfc5596f69adf4d8 (diff)
downloadservo-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 -->
-rw-r--r--components/constellation/constellation.rs2
-rw-r--r--components/layout/animation.rs118
-rw-r--r--components/layout_thread/lib.rs10
-rw-r--r--components/script/dom/webidls/CSSStyleDeclaration.webidl17
-rw-r--r--components/script/script_thread.rs2
-rw-r--r--components/style/animation.rs1426
-rw-r--r--components/style/context.rs6
-rw-r--r--components/style/keyframes.rs242
-rw-r--r--components/style/lib.rs1
-rw-r--r--components/style/matching.rs89
-rw-r--r--components/style/parallel.rs2
-rw-r--r--components/style/properties/data.py14
-rw-r--r--components/style/properties/helpers.mako.rs63
-rw-r--r--components/style/properties/helpers/animated_properties.mako.rs878
-rw-r--r--components/style/properties/longhand/background.mako.rs29
-rw-r--r--components/style/properties/longhand/border.mako.rs22
-rw-r--r--components/style/properties/longhand/box.mako.rs453
-rw-r--r--components/style/properties/longhand/color.mako.rs2
-rw-r--r--components/style/properties/longhand/column.mako.rs9
-rw-r--r--components/style/properties/longhand/counters.mako.rs6
-rw-r--r--components/style/properties/longhand/effects.mako.rs43
-rw-r--r--components/style/properties/longhand/font.mako.rs27
-rw-r--r--components/style/properties/longhand/inherited_box.mako.rs22
-rw-r--r--components/style/properties/longhand/inherited_svg.mako.rs44
-rw-r--r--components/style/properties/longhand/inherited_table.mako.rs14
-rw-r--r--components/style/properties/longhand/inherited_text.mako.rs49
-rw-r--r--components/style/properties/longhand/list.mako.rs9
-rw-r--r--components/style/properties/longhand/margin.mako.rs3
-rw-r--r--components/style/properties/longhand/outline.mako.rs13
-rw-r--r--components/style/properties/longhand/padding.mako.rs3
-rw-r--r--components/style/properties/longhand/pointing.mako.rs22
-rw-r--r--components/style/properties/longhand/position.mako.rs63
-rw-r--r--components/style/properties/longhand/svg.mako.rs26
-rw-r--r--components/style/properties/longhand/table.mako.rs3
-rw-r--r--components/style/properties/longhand/text.mako.rs16
-rw-r--r--components/style/properties/longhand/ui.mako.rs9
-rw-r--r--components/style/properties/longhand/xul.mako.rs11
-rw-r--r--components/style/properties/properties.mako.rs56
-rw-r--r--components/style/properties/shorthand/box.mako.rs2
-rw-r--r--components/style/selector_impl.rs20
-rw-r--r--components/style/selector_matching.rs52
-rw-r--r--components/style/servo.rs3
-rw-r--r--components/style/stylesheets.rs49
-rw-r--r--components/style/traversal.rs5
-rw-r--r--components/style/values.rs19
-rw-r--r--components/style/viewport.rs4
-rw-r--r--ports/geckolib/data.rs5
-rw-r--r--ports/geckolib/properties.mako.rs8
-rw-r--r--ports/geckolib/selector_impl.rs10
-rw-r--r--ports/geckolib/string_cache/lib.rs10
-rw-r--r--tests/unit/style/stylesheets.rs33
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.)))),
+ ]),
+ },
+ ]
+ })
+
],
});
}