mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
Cache animation computed values when animations change
Instead of recalculating the animation style every tick of an animation, cache the computed values when animations change. In addition to being more efficient, this will allow us to return animation rules as property declarations because we don't need to consult the final style to produce them.
This commit is contained in:
parent
7df4655b60
commit
83fa1b9eaa
9 changed files with 246 additions and 169 deletions
|
@ -584,8 +584,13 @@ impl<'le> TElement for ServoLayoutElement<'le> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_css_animations(&self) -> bool {
|
fn has_css_animations(&self, context: &SharedStyleContext) -> bool {
|
||||||
unreachable!("this should be only called on gecko");
|
context
|
||||||
|
.animation_states
|
||||||
|
.read()
|
||||||
|
.get(&self.as_node().opaque())
|
||||||
|
.map(|set| set.has_active_animation())
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_css_transitions(&self) -> bool {
|
fn has_css_transitions(&self) -> bool {
|
||||||
|
|
|
@ -610,7 +610,13 @@ impl LayoutThread {
|
||||||
origin: ImmutableOrigin,
|
origin: ImmutableOrigin,
|
||||||
animation_timeline_value: f64,
|
animation_timeline_value: f64,
|
||||||
animation_states: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
|
animation_states: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
|
||||||
|
stylesheets_changed: bool,
|
||||||
) -> LayoutContext<'a> {
|
) -> LayoutContext<'a> {
|
||||||
|
let traversal_flags = match stylesheets_changed {
|
||||||
|
true => TraversalFlags::ForCSSRuleChanges,
|
||||||
|
false => TraversalFlags::empty(),
|
||||||
|
};
|
||||||
|
|
||||||
LayoutContext {
|
LayoutContext {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
origin,
|
origin,
|
||||||
|
@ -622,7 +628,7 @@ impl LayoutThread {
|
||||||
animation_states,
|
animation_states,
|
||||||
registered_speculative_painters: &self.registered_painters,
|
registered_speculative_painters: &self.registered_painters,
|
||||||
current_time_for_animations: animation_timeline_value,
|
current_time_for_animations: animation_timeline_value,
|
||||||
traversal_flags: TraversalFlags::empty(),
|
traversal_flags,
|
||||||
snapshot_map: snapshot_map,
|
snapshot_map: snapshot_map,
|
||||||
},
|
},
|
||||||
image_cache: self.image_cache.clone(),
|
image_cache: self.image_cache.clone(),
|
||||||
|
@ -1405,6 +1411,7 @@ impl LayoutThread {
|
||||||
origin,
|
origin,
|
||||||
data.animation_timeline_value,
|
data.animation_timeline_value,
|
||||||
data.animations.clone(),
|
data.animations.clone(),
|
||||||
|
data.stylesheets_changed,
|
||||||
);
|
);
|
||||||
|
|
||||||
let pool;
|
let pool;
|
||||||
|
|
|
@ -592,8 +592,13 @@ impl<'le> TElement for ServoLayoutElement<'le> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_css_animations(&self) -> bool {
|
fn has_css_animations(&self, context: &SharedStyleContext) -> bool {
|
||||||
unreachable!("this should be only called on gecko");
|
context
|
||||||
|
.animation_states
|
||||||
|
.read()
|
||||||
|
.get(&self.as_node().opaque())
|
||||||
|
.map(|set| set.has_active_animation())
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_css_transitions(&self) -> bool {
|
fn has_css_transitions(&self) -> bool {
|
||||||
|
|
|
@ -574,7 +574,13 @@ impl LayoutThread {
|
||||||
origin: ImmutableOrigin,
|
origin: ImmutableOrigin,
|
||||||
animation_timeline_value: f64,
|
animation_timeline_value: f64,
|
||||||
animation_states: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
|
animation_states: ServoArc<RwLock<FxHashMap<OpaqueNode, ElementAnimationSet>>>,
|
||||||
|
stylesheets_changed: bool,
|
||||||
) -> LayoutContext<'a> {
|
) -> LayoutContext<'a> {
|
||||||
|
let traversal_flags = match stylesheets_changed {
|
||||||
|
true => TraversalFlags::ForCSSRuleChanges,
|
||||||
|
false => TraversalFlags::empty(),
|
||||||
|
};
|
||||||
|
|
||||||
LayoutContext {
|
LayoutContext {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
origin,
|
origin,
|
||||||
|
@ -586,7 +592,7 @@ impl LayoutThread {
|
||||||
animation_states,
|
animation_states,
|
||||||
registered_speculative_painters: &self.registered_painters,
|
registered_speculative_painters: &self.registered_painters,
|
||||||
current_time_for_animations: animation_timeline_value,
|
current_time_for_animations: animation_timeline_value,
|
||||||
traversal_flags: TraversalFlags::empty(),
|
traversal_flags,
|
||||||
snapshot_map: snapshot_map,
|
snapshot_map: snapshot_map,
|
||||||
},
|
},
|
||||||
image_cache: self.image_cache.clone(),
|
image_cache: self.image_cache.clone(),
|
||||||
|
@ -1064,6 +1070,7 @@ impl LayoutThread {
|
||||||
origin,
|
origin,
|
||||||
data.animation_timeline_value,
|
data.animation_timeline_value,
|
||||||
data.animations.clone(),
|
data.animations.clone(),
|
||||||
|
data.stylesheets_changed,
|
||||||
);
|
);
|
||||||
|
|
||||||
let dirty_root = unsafe {
|
let dirty_root = unsafe {
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::properties::longhands::animation_fill_mode::computed_value::single_va
|
||||||
use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
|
use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
|
||||||
use crate::properties::LonghandIdSet;
|
use crate::properties::LonghandIdSet;
|
||||||
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
|
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
|
||||||
use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
|
use crate::stylesheets::keyframes_rule::{KeyframesStep, KeyframesStepValue};
|
||||||
use crate::stylesheets::Origin;
|
use crate::stylesheets::Origin;
|
||||||
use crate::values::animated::{Animate, Procedure};
|
use crate::values::animated::{Animate, Procedure};
|
||||||
use crate::values::computed::Time;
|
use crate::values::computed::Time;
|
||||||
|
@ -172,6 +172,103 @@ pub enum KeyframesIterationState {
|
||||||
Finite(f64, f64),
|
Finite(f64, f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single computed keyframe for a CSS Animation.
|
||||||
|
#[derive(Clone, MallocSizeOf)]
|
||||||
|
struct ComputedKeyframeStep {
|
||||||
|
step: KeyframesStep,
|
||||||
|
|
||||||
|
#[ignore_malloc_size_of = "ComputedValues"]
|
||||||
|
style: Arc<ComputedValues>,
|
||||||
|
|
||||||
|
timing_function: TimingFunction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComputedKeyframeStep {
|
||||||
|
fn generate_for_keyframes<E>(
|
||||||
|
element: E,
|
||||||
|
steps: &[KeyframesStep],
|
||||||
|
context: &SharedStyleContext,
|
||||||
|
base_style: &Arc<ComputedValues>,
|
||||||
|
font_metrics_provider: &dyn FontMetricsProvider,
|
||||||
|
default_timing_function: TimingFunction,
|
||||||
|
) -> Vec<Self>
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
{
|
||||||
|
let mut previous_style = base_style.clone();
|
||||||
|
steps
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|step| match step.value {
|
||||||
|
KeyframesStepValue::ComputedValues => ComputedKeyframeStep {
|
||||||
|
step,
|
||||||
|
style: base_style.clone(),
|
||||||
|
timing_function: default_timing_function,
|
||||||
|
},
|
||||||
|
KeyframesStepValue::Declarations {
|
||||||
|
block: ref declarations,
|
||||||
|
} => {
|
||||||
|
let guard = declarations.read_with(context.guards.author);
|
||||||
|
|
||||||
|
let iter = || {
|
||||||
|
// It's possible to have !important properties in keyframes
|
||||||
|
// so we have to filter them out.
|
||||||
|
// See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
|
||||||
|
// Also we filter our non-animatable properties.
|
||||||
|
guard
|
||||||
|
.normal_declaration_iter()
|
||||||
|
.filter(|declaration| declaration.is_animatable())
|
||||||
|
.map(|decl| (decl, Origin::Author))
|
||||||
|
};
|
||||||
|
|
||||||
|
// This currently ignores visited styles, which seems acceptable,
|
||||||
|
// as existing browsers don't appear to animate visited styles.
|
||||||
|
//
|
||||||
|
// TODO(mrobinson): We shouldn't be calling `apply_declarations`
|
||||||
|
// here because it doesn't really produce the correct values (for
|
||||||
|
// instance for keyframes that are missing animating properties).
|
||||||
|
// Instead we should do something like what Gecko does in
|
||||||
|
// Servo_StyleSet_GetKeyframesForName.
|
||||||
|
let computed_style = properties::apply_declarations::<E, _, _>(
|
||||||
|
context.stylist.device(),
|
||||||
|
/* pseudo = */ None,
|
||||||
|
previous_style.rules(),
|
||||||
|
&context.guards,
|
||||||
|
iter,
|
||||||
|
Some(&previous_style),
|
||||||
|
Some(&previous_style),
|
||||||
|
Some(&previous_style),
|
||||||
|
font_metrics_provider,
|
||||||
|
CascadeMode::Unvisited {
|
||||||
|
visited_rules: None,
|
||||||
|
},
|
||||||
|
context.quirks_mode(),
|
||||||
|
/* rule_cache = */ None,
|
||||||
|
&mut Default::default(),
|
||||||
|
Some(element),
|
||||||
|
);
|
||||||
|
|
||||||
|
// NB: The spec says that the timing function can be overwritten
|
||||||
|
// from the keyframe style. `animation_timing_function` can never
|
||||||
|
// be empty, always has at least the default value (`ease`).
|
||||||
|
let timing_function = if step.declared_timing_function {
|
||||||
|
computed_style.get_box().animation_timing_function_at(0)
|
||||||
|
} else {
|
||||||
|
default_timing_function
|
||||||
|
};
|
||||||
|
|
||||||
|
previous_style = computed_style.clone();
|
||||||
|
ComputedKeyframeStep {
|
||||||
|
step,
|
||||||
|
style: computed_style,
|
||||||
|
timing_function,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A CSS Animation
|
/// A CSS Animation
|
||||||
#[derive(Clone, MallocSizeOf)]
|
#[derive(Clone, MallocSizeOf)]
|
||||||
pub struct Animation {
|
pub struct Animation {
|
||||||
|
@ -181,8 +278,11 @@ pub struct Animation {
|
||||||
/// The name of this animation as defined by the style.
|
/// The name of this animation as defined by the style.
|
||||||
pub name: Atom,
|
pub name: Atom,
|
||||||
|
|
||||||
/// The internal animation from the style system.
|
/// The properties that change in this animation.
|
||||||
pub keyframes_animation: KeyframesAnimation,
|
properties_changed: LonghandIdSet,
|
||||||
|
|
||||||
|
/// The computed style for each keyframe of this animation.
|
||||||
|
computed_steps: Vec<ComputedKeyframeStep>,
|
||||||
|
|
||||||
/// The time this animation started at, which is the current value of the animation
|
/// The time this animation started at, which is the current value of the animation
|
||||||
/// timeline when this animation was created plus any animation delay.
|
/// timeline when this animation was created plus any animation delay.
|
||||||
|
@ -377,14 +477,7 @@ impl Animation {
|
||||||
|
|
||||||
/// Update the given style to reflect the values specified by this `Animation`
|
/// Update the given style to reflect the values specified by this `Animation`
|
||||||
/// at the time provided by the given `SharedStyleContext`.
|
/// at the time provided by the given `SharedStyleContext`.
|
||||||
fn update_style<E>(
|
fn update_style(&self, context: &SharedStyleContext, style: &mut Arc<ComputedValues>) {
|
||||||
&self,
|
|
||||||
context: &SharedStyleContext,
|
|
||||||
style: &mut Arc<ComputedValues>,
|
|
||||||
font_metrics_provider: &dyn FontMetricsProvider,
|
|
||||||
) where
|
|
||||||
E: TElement,
|
|
||||||
{
|
|
||||||
let duration = self.duration;
|
let duration = self.duration;
|
||||||
let started_at = self.started_at;
|
let started_at = self.started_at;
|
||||||
|
|
||||||
|
@ -396,7 +489,7 @@ impl Animation {
|
||||||
AnimationState::Canceled => return,
|
AnimationState::Canceled => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_assert!(!self.keyframes_animation.steps.is_empty());
|
debug_assert!(!self.computed_steps.is_empty());
|
||||||
|
|
||||||
let mut total_progress = (now - started_at) / duration;
|
let mut total_progress = (now - started_at) / duration;
|
||||||
if total_progress < 0. &&
|
if total_progress < 0. &&
|
||||||
|
@ -417,34 +510,34 @@ impl Animation {
|
||||||
// Get the indices of the previous (from) keyframe and the next (to) keyframe.
|
// Get the indices of the previous (from) keyframe and the next (to) keyframe.
|
||||||
let next_keyframe_index;
|
let next_keyframe_index;
|
||||||
let prev_keyframe_index;
|
let prev_keyframe_index;
|
||||||
|
let num_steps = self.computed_steps.len();
|
||||||
|
debug_assert!(num_steps > 0);
|
||||||
match self.current_direction {
|
match self.current_direction {
|
||||||
AnimationDirection::Normal => {
|
AnimationDirection::Normal => {
|
||||||
next_keyframe_index = self
|
next_keyframe_index = self
|
||||||
.keyframes_animation
|
.computed_steps
|
||||||
.steps
|
|
||||||
.iter()
|
.iter()
|
||||||
.position(|step| total_progress as f32 <= step.start_percentage.0);
|
.position(|step| total_progress as f32 <= step.step.start_percentage.0);
|
||||||
prev_keyframe_index = next_keyframe_index
|
prev_keyframe_index = next_keyframe_index
|
||||||
.and_then(|pos| if pos != 0 { Some(pos - 1) } else { None })
|
.and_then(|pos| if pos != 0 { Some(pos - 1) } else { None })
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
},
|
},
|
||||||
AnimationDirection::Reverse => {
|
AnimationDirection::Reverse => {
|
||||||
next_keyframe_index = self
|
next_keyframe_index = self
|
||||||
.keyframes_animation
|
.computed_steps
|
||||||
.steps
|
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.position(|step| total_progress as f32 <= 1. - step.start_percentage.0)
|
.position(|step| total_progress as f32 <= 1. - step.step.start_percentage.0)
|
||||||
.map(|pos| self.keyframes_animation.steps.len() - pos - 1);
|
.map(|pos| num_steps - pos - 1);
|
||||||
prev_keyframe_index = next_keyframe_index
|
prev_keyframe_index = next_keyframe_index
|
||||||
.and_then(|pos| {
|
.and_then(|pos| {
|
||||||
if pos != self.keyframes_animation.steps.len() - 1 {
|
if pos != num_steps - 1 {
|
||||||
Some(pos + 1)
|
Some(pos + 1)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or(self.keyframes_animation.steps.len() - 1)
|
.unwrap_or(num_steps - 1)
|
||||||
},
|
},
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -454,87 +547,48 @@ impl Animation {
|
||||||
prev_keyframe_index, next_keyframe_index
|
prev_keyframe_index, next_keyframe_index
|
||||||
);
|
);
|
||||||
|
|
||||||
let prev_keyframe = &self.keyframes_animation.steps[prev_keyframe_index];
|
let prev_keyframe = &self.computed_steps[prev_keyframe_index];
|
||||||
let next_keyframe = match next_keyframe_index {
|
let next_keyframe = match next_keyframe_index {
|
||||||
Some(target) => &self.keyframes_animation.steps[target],
|
Some(index) => &self.computed_steps[index],
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let update_with_single_keyframe_style = |style, computed_style: &Arc<ComputedValues>| {
|
let update_with_single_keyframe_style = |style, computed_style: &Arc<ComputedValues>| {
|
||||||
let mutable_style = Arc::make_mut(style);
|
let mutable_style = Arc::make_mut(style);
|
||||||
for property in self
|
for property in self.properties_changed.iter().filter_map(|longhand| {
|
||||||
.keyframes_animation
|
|
||||||
.properties_changed
|
|
||||||
.iter()
|
|
||||||
.filter_map(|longhand| {
|
|
||||||
AnimationValue::from_computed_values(longhand, &**computed_style)
|
AnimationValue::from_computed_values(longhand, &**computed_style)
|
||||||
})
|
}) {
|
||||||
{
|
|
||||||
property.set_in_style_for_servo(mutable_style);
|
property.set_in_style_for_servo(mutable_style);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: How could we optimise it? Is it such a big deal?
|
// TODO: How could we optimise it? Is it such a big deal?
|
||||||
let prev_keyframe_style = compute_style_for_animation_step::<E>(
|
let prev_keyframe_style = &prev_keyframe.style;
|
||||||
context,
|
let next_keyframe_style = &next_keyframe.style;
|
||||||
prev_keyframe,
|
|
||||||
style,
|
|
||||||
&self.cascade_style,
|
|
||||||
font_metrics_provider,
|
|
||||||
);
|
|
||||||
if total_progress <= 0.0 {
|
if total_progress <= 0.0 {
|
||||||
update_with_single_keyframe_style(style, &prev_keyframe_style);
|
update_with_single_keyframe_style(style, &prev_keyframe_style);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_keyframe_style = compute_style_for_animation_step::<E>(
|
|
||||||
context,
|
|
||||||
next_keyframe,
|
|
||||||
&prev_keyframe_style,
|
|
||||||
&self.cascade_style,
|
|
||||||
font_metrics_provider,
|
|
||||||
);
|
|
||||||
if total_progress >= 1.0 {
|
if total_progress >= 1.0 {
|
||||||
update_with_single_keyframe_style(style, &next_keyframe_style);
|
update_with_single_keyframe_style(style, &next_keyframe_style);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let relative_timespan =
|
let relative_timespan =
|
||||||
(next_keyframe.start_percentage.0 - prev_keyframe.start_percentage.0).abs();
|
(next_keyframe.step.start_percentage.0 - prev_keyframe.step.start_percentage.0).abs();
|
||||||
let relative_duration = relative_timespan as f64 * duration;
|
let relative_duration = relative_timespan as f64 * duration;
|
||||||
let last_keyframe_ended_at = match self.current_direction {
|
let last_keyframe_ended_at = match self.current_direction {
|
||||||
AnimationDirection::Normal => {
|
AnimationDirection::Normal => {
|
||||||
self.started_at + (duration * prev_keyframe.start_percentage.0 as f64)
|
self.started_at + (duration * prev_keyframe.step.start_percentage.0 as f64)
|
||||||
},
|
},
|
||||||
AnimationDirection::Reverse => {
|
AnimationDirection::Reverse => {
|
||||||
self.started_at + (duration * (1. - prev_keyframe.start_percentage.0 as f64))
|
self.started_at + (duration * (1. - prev_keyframe.step.start_percentage.0 as f64))
|
||||||
},
|
},
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
|
let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
|
||||||
|
|
||||||
// NB: The spec says that the timing function can be overwritten
|
|
||||||
// from the keyframe style.
|
|
||||||
let timing_function = if prev_keyframe.declared_timing_function {
|
|
||||||
// NB: animation_timing_function can never be empty, always has
|
|
||||||
// at least the default value (`ease`).
|
|
||||||
prev_keyframe_style
|
|
||||||
.get_box()
|
|
||||||
.animation_timing_function_at(0)
|
|
||||||
} else {
|
|
||||||
// TODO(mrobinson): It isn't optimal to have to walk this list every
|
|
||||||
// time. Perhaps this should be stored in the animation.
|
|
||||||
let index = match style
|
|
||||||
.get_box()
|
|
||||||
.animation_name_iter()
|
|
||||||
.position(|animation_name| Some(&self.name) == animation_name.as_atom())
|
|
||||||
{
|
|
||||||
Some(index) => index,
|
|
||||||
None => return warn!("Tried to update a style with a cancelled animation."),
|
|
||||||
};
|
|
||||||
style.get_box().animation_timing_function_mod(index)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut new_style = (**style).clone();
|
let mut new_style = (**style).clone();
|
||||||
let mut update_style_for_longhand = |longhand| {
|
let mut update_style_for_longhand = |longhand| {
|
||||||
let from = AnimationValue::from_computed_values(longhand, &prev_keyframe_style)?;
|
let from = AnimationValue::from_computed_values(longhand, &prev_keyframe_style)?;
|
||||||
|
@ -542,14 +596,14 @@ impl Animation {
|
||||||
PropertyAnimation {
|
PropertyAnimation {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
timing_function,
|
timing_function: prev_keyframe.timing_function,
|
||||||
duration: relative_duration as f64,
|
duration: relative_duration as f64,
|
||||||
}
|
}
|
||||||
.update(&mut new_style, relative_progress);
|
.update(&mut new_style, relative_progress);
|
||||||
None::<()>
|
None::<()>
|
||||||
};
|
};
|
||||||
|
|
||||||
for property in self.keyframes_animation.properties_changed.iter() {
|
for property in self.properties_changed.iter() {
|
||||||
update_style_for_longhand(property);
|
update_style_for_longhand(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,16 +778,13 @@ impl ElementAnimationSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_active_animations<E>(
|
pub(crate) fn apply_active_animations(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &SharedStyleContext,
|
context: &SharedStyleContext,
|
||||||
style: &mut Arc<ComputedValues>,
|
style: &mut Arc<ComputedValues>,
|
||||||
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
|
) {
|
||||||
) where
|
|
||||||
E: TElement,
|
|
||||||
{
|
|
||||||
for animation in &self.animations {
|
for animation in &self.animations {
|
||||||
animation.update_style::<E>(context, style, font_metrics);
|
animation.update_style(context, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
for transition in &self.transitions {
|
for transition in &self.transitions {
|
||||||
|
@ -778,10 +829,15 @@ impl ElementAnimationSet {
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_active_transition_or_animation(&self) -> bool {
|
/// If this `ElementAnimationSet` has any any active animations.
|
||||||
|
pub fn has_active_animation(&self) -> bool {
|
||||||
self.animations
|
self.animations
|
||||||
.iter()
|
.iter()
|
||||||
.any(|animation| animation.state != AnimationState::Canceled) ||
|
.any(|animation| animation.state != AnimationState::Canceled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this `ElementAnimationSet` has any any active transitions.
|
||||||
|
pub fn has_active_transition(&self) -> bool {
|
||||||
self.transitions
|
self.transitions
|
||||||
.iter()
|
.iter()
|
||||||
.any(|transition| transition.state != AnimationState::Canceled)
|
.any(|transition| transition.state != AnimationState::Canceled)
|
||||||
|
@ -794,6 +850,7 @@ impl ElementAnimationSet {
|
||||||
element: E,
|
element: E,
|
||||||
context: &SharedStyleContext,
|
context: &SharedStyleContext,
|
||||||
new_style: &Arc<ComputedValues>,
|
new_style: &Arc<ComputedValues>,
|
||||||
|
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
{
|
{
|
||||||
|
@ -803,21 +860,18 @@ impl ElementAnimationSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_start_animations(element, &context, &new_style, self);
|
maybe_start_animations(element, &context, &new_style, self, font_metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update our transitions given a new style, canceling or starting new animations
|
/// Update our transitions given a new style, canceling or starting new animations
|
||||||
/// when appropriate.
|
/// when appropriate.
|
||||||
pub fn update_transitions_for_new_style<E>(
|
pub fn update_transitions_for_new_style(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &SharedStyleContext,
|
context: &SharedStyleContext,
|
||||||
opaque_node: OpaqueNode,
|
opaque_node: OpaqueNode,
|
||||||
old_style: Option<&Arc<ComputedValues>>,
|
old_style: Option<&Arc<ComputedValues>>,
|
||||||
after_change_style: &Arc<ComputedValues>,
|
after_change_style: &Arc<ComputedValues>,
|
||||||
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
|
) {
|
||||||
) where
|
|
||||||
E: TElement,
|
|
||||||
{
|
|
||||||
// If this is the first style, we don't trigger any transitions and we assume
|
// If this is the first style, we don't trigger any transitions and we assume
|
||||||
// there were no previously triggered transitions.
|
// there were no previously triggered transitions.
|
||||||
let mut before_change_style = match old_style {
|
let mut before_change_style = match old_style {
|
||||||
|
@ -829,9 +883,9 @@ impl ElementAnimationSet {
|
||||||
// See https://drafts.csswg.org/css-transitions/#starting. We need to clone the
|
// See https://drafts.csswg.org/css-transitions/#starting. We need to clone the
|
||||||
// style because this might still be a reference to the original `old_style` and
|
// style because this might still be a reference to the original `old_style` and
|
||||||
// we want to preserve that so that we can later properly calculate restyle damage.
|
// we want to preserve that so that we can later properly calculate restyle damage.
|
||||||
if self.has_active_transition_or_animation() {
|
if self.has_active_transition() || self.has_active_animation() {
|
||||||
before_change_style = before_change_style.clone();
|
before_change_style = before_change_style.clone();
|
||||||
self.apply_active_animations::<E>(context, &mut before_change_style, font_metrics);
|
self.apply_active_animations(context, &mut before_change_style);
|
||||||
}
|
}
|
||||||
|
|
||||||
let transitioning_properties = start_transitions_if_applicable(
|
let transitioning_properties = start_transitions_if_applicable(
|
||||||
|
@ -961,55 +1015,6 @@ pub fn start_transitions_if_applicable(
|
||||||
properties_that_transition
|
properties_that_transition
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_style_for_animation_step<E>(
|
|
||||||
context: &SharedStyleContext,
|
|
||||||
step: &KeyframesStep,
|
|
||||||
previous_style: &ComputedValues,
|
|
||||||
style_from_cascade: &Arc<ComputedValues>,
|
|
||||||
font_metrics_provider: &dyn FontMetricsProvider,
|
|
||||||
) -> Arc<ComputedValues>
|
|
||||||
where
|
|
||||||
E: TElement,
|
|
||||||
{
|
|
||||||
match step.value {
|
|
||||||
KeyframesStepValue::ComputedValues => style_from_cascade.clone(),
|
|
||||||
KeyframesStepValue::Declarations {
|
|
||||||
block: ref declarations,
|
|
||||||
} => {
|
|
||||||
let guard = declarations.read_with(context.guards.author);
|
|
||||||
|
|
||||||
// This currently ignores visited styles, which seems acceptable,
|
|
||||||
// as existing browsers don't appear to animate visited styles.
|
|
||||||
let computed = properties::apply_declarations::<E, _>(
|
|
||||||
context.stylist.device(),
|
|
||||||
/* pseudo = */ None,
|
|
||||||
previous_style.rules(),
|
|
||||||
&context.guards,
|
|
||||||
// It's possible to have !important properties in keyframes
|
|
||||||
// so we have to filter them out.
|
|
||||||
// See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
|
|
||||||
// Also we filter our non-animatable properties.
|
|
||||||
guard
|
|
||||||
.normal_declaration_iter()
|
|
||||||
.filter(|declaration| declaration.is_animatable())
|
|
||||||
.map(|decl| (decl, Origin::Author)),
|
|
||||||
Some(previous_style),
|
|
||||||
Some(previous_style),
|
|
||||||
Some(previous_style),
|
|
||||||
font_metrics_provider,
|
|
||||||
CascadeMode::Unvisited {
|
|
||||||
visited_rules: None,
|
|
||||||
},
|
|
||||||
context.quirks_mode(),
|
|
||||||
/* rule_cache = */ None,
|
|
||||||
&mut Default::default(),
|
|
||||||
/* element = */ None,
|
|
||||||
);
|
|
||||||
computed
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Triggers animations for a given node looking at the animation property
|
/// Triggers animations for a given node looking at the animation property
|
||||||
/// values.
|
/// values.
|
||||||
pub fn maybe_start_animations<E>(
|
pub fn maybe_start_animations<E>(
|
||||||
|
@ -1017,6 +1022,7 @@ pub fn maybe_start_animations<E>(
|
||||||
context: &SharedStyleContext,
|
context: &SharedStyleContext,
|
||||||
new_style: &Arc<ComputedValues>,
|
new_style: &Arc<ComputedValues>,
|
||||||
animation_state: &mut ElementAnimationSet,
|
animation_state: &mut ElementAnimationSet,
|
||||||
|
font_metrics_provider: &dyn FontMetricsProvider,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
{
|
{
|
||||||
|
@ -1033,7 +1039,7 @@ pub fn maybe_start_animations<E>(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let anim = match context.stylist.get_animation(name, element) {
|
let keyframe_animation = match context.stylist.get_animation(name, element) {
|
||||||
Some(animation) => animation,
|
Some(animation) => animation,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
@ -1044,7 +1050,7 @@ pub fn maybe_start_animations<E>(
|
||||||
// without submitting it to the compositor, since both the first and
|
// without submitting it to the compositor, since both the first and
|
||||||
// the second keyframes would be synthetised from the computed
|
// the second keyframes would be synthetised from the computed
|
||||||
// values.
|
// values.
|
||||||
if anim.steps.is_empty() {
|
if keyframe_animation.steps.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1071,10 +1077,20 @@ pub fn maybe_start_animations<E>(
|
||||||
AnimationPlayState::Running => AnimationState::Pending,
|
AnimationPlayState::Running => AnimationState::Pending,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let computed_steps = ComputedKeyframeStep::generate_for_keyframes::<E>(
|
||||||
|
element,
|
||||||
|
&keyframe_animation.steps,
|
||||||
|
context,
|
||||||
|
new_style,
|
||||||
|
font_metrics_provider,
|
||||||
|
new_style.get_box().animation_timing_function_mod(i),
|
||||||
|
);
|
||||||
|
|
||||||
let new_animation = Animation {
|
let new_animation = Animation {
|
||||||
node: element.as_node().opaque(),
|
node: element.as_node().opaque(),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
keyframes_animation: anim.clone(),
|
properties_changed: keyframe_animation.properties_changed,
|
||||||
|
computed_steps,
|
||||||
started_at: animation_start,
|
started_at: animation_start,
|
||||||
duration: duration as f64,
|
duration: duration as f64,
|
||||||
fill_mode: box_style.animation_fill_mode_mod(i),
|
fill_mode: box_style.animation_fill_mode_mod(i),
|
||||||
|
|
|
@ -8,10 +8,9 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use crate::applicable_declarations::ApplicableDeclarationBlock;
|
use crate::applicable_declarations::ApplicableDeclarationBlock;
|
||||||
|
use crate::context::SharedStyleContext;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use crate::context::PostAnimationTasks;
|
use crate::context::{PostAnimationTasks, UpdateAnimationsTasks};
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
use crate::context::UpdateAnimationsTasks;
|
|
||||||
use crate::data::ElementData;
|
use crate::data::ElementData;
|
||||||
use crate::element_state::ElementState;
|
use crate::element_state::ElementState;
|
||||||
use crate::font_metrics::FontMetricsProvider;
|
use crate::font_metrics::FontMetricsProvider;
|
||||||
|
@ -749,7 +748,7 @@ pub trait TElement:
|
||||||
fn has_animations(&self) -> bool;
|
fn has_animations(&self) -> bool;
|
||||||
|
|
||||||
/// Returns true if the element has a CSS animation.
|
/// Returns true if the element has a CSS animation.
|
||||||
fn has_css_animations(&self) -> bool;
|
fn has_css_animations(&self, context: &SharedStyleContext) -> bool;
|
||||||
|
|
||||||
/// Returns true if the element has a CSS transition (including running transitions and
|
/// Returns true if the element has a CSS transition (including running transitions and
|
||||||
/// completed transitions).
|
/// completed transitions).
|
||||||
|
|
|
@ -1516,7 +1516,7 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
self.may_have_animations() && unsafe { Gecko_ElementHasAnimations(self.0) }
|
self.may_have_animations() && unsafe { Gecko_ElementHasAnimations(self.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_css_animations(&self) -> bool {
|
fn has_css_animations(&self, _: &SharedStyleContext) -> bool {
|
||||||
self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) }
|
self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,6 @@ trait PrivateMatchMethods: TElement {
|
||||||
Some(style.0)
|
Some(style.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
fn needs_animations_update(
|
fn needs_animations_update(
|
||||||
&self,
|
&self,
|
||||||
context: &mut StyleContext<Self>,
|
context: &mut StyleContext<Self>,
|
||||||
|
@ -243,7 +242,7 @@ trait PrivateMatchMethods: TElement {
|
||||||
let new_box_style = new_style.get_box();
|
let new_box_style = new_style.get_box();
|
||||||
let new_style_specifies_animations = new_box_style.specifies_animations();
|
let new_style_specifies_animations = new_box_style.specifies_animations();
|
||||||
|
|
||||||
let has_animations = self.has_css_animations();
|
let has_animations = self.has_css_animations(&context.shared);
|
||||||
if !new_style_specifies_animations && !has_animations {
|
if !new_style_specifies_animations && !has_animations {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -439,37 +438,53 @@ trait PrivateMatchMethods: TElement {
|
||||||
) {
|
) {
|
||||||
use crate::animation::AnimationState;
|
use crate::animation::AnimationState;
|
||||||
|
|
||||||
|
// We need to call this before accessing the `ElementAnimationSet` from the
|
||||||
|
// map because this call will do a RwLock::read().
|
||||||
|
let needs_animations_update =
|
||||||
|
self.needs_animations_update(context, old_values.as_ref().map(|s| &**s), new_values);
|
||||||
|
|
||||||
let this_opaque = self.as_node().opaque();
|
let this_opaque = self.as_node().opaque();
|
||||||
let shared_context = context.shared;
|
let shared_context = context.shared;
|
||||||
let mut animation_states = shared_context.animation_states.write();
|
let mut animation_set = shared_context
|
||||||
let mut animation_state = animation_states.remove(&this_opaque).unwrap_or_default();
|
.animation_states
|
||||||
|
.write()
|
||||||
|
.remove(&this_opaque)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
animation_state.update_animations_for_new_style(*self, &shared_context, &new_values);
|
// Starting animations is expensive, because we have to recalculate the style
|
||||||
|
// for all the keyframes. We only want to do this if we think that there's a
|
||||||
|
// chance that the animations really changed.
|
||||||
|
if needs_animations_update {
|
||||||
|
animation_set.update_animations_for_new_style::<Self>(
|
||||||
|
*self,
|
||||||
|
&shared_context,
|
||||||
|
&new_values,
|
||||||
|
&context.thread_local.font_metrics_provider,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
animation_state.update_transitions_for_new_style::<Self>(
|
animation_set.update_transitions_for_new_style(
|
||||||
&shared_context,
|
&shared_context,
|
||||||
this_opaque,
|
this_opaque,
|
||||||
old_values.as_ref(),
|
old_values.as_ref(),
|
||||||
new_values,
|
new_values,
|
||||||
&context.thread_local.font_metrics_provider,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
animation_state.apply_active_animations::<Self>(
|
animation_set.apply_active_animations(shared_context, new_values);
|
||||||
shared_context,
|
|
||||||
new_values,
|
|
||||||
&context.thread_local.font_metrics_provider,
|
|
||||||
);
|
|
||||||
|
|
||||||
// We clear away any finished transitions, but retain animations, because they
|
// We clear away any finished transitions, but retain animations, because they
|
||||||
// might still be used for proper calculation of `animation-fill-mode`.
|
// might still be used for proper calculation of `animation-fill-mode`.
|
||||||
animation_state
|
animation_set
|
||||||
.transitions
|
.transitions
|
||||||
.retain(|transition| transition.state != AnimationState::Finished);
|
.retain(|transition| transition.state != AnimationState::Finished);
|
||||||
|
|
||||||
// If the ElementAnimationSet is empty, and don't store it in order to
|
// If the ElementAnimationSet is empty, and don't store it in order to
|
||||||
// save memory and to avoid extra processing later.
|
// save memory and to avoid extra processing later.
|
||||||
if !animation_state.is_empty() {
|
if !animation_set.is_empty() {
|
||||||
animation_states.insert(this_opaque, animation_state);
|
shared_context
|
||||||
|
.animation_states
|
||||||
|
.write()
|
||||||
|
.insert(this_opaque, animation_set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2828,10 +2828,27 @@ pub mod style_structs {
|
||||||
/// Returns whether there are any transitions specified.
|
/// Returns whether there are any transitions specified.
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
pub fn specifies_transitions(&self) -> bool {
|
pub fn specifies_transitions(&self) -> bool {
|
||||||
|
// TODO(mrobinson): This should check the combined duration and not just
|
||||||
|
// the duration.
|
||||||
self.transition_duration_iter()
|
self.transition_duration_iter()
|
||||||
.take(self.transition_property_count())
|
.take(self.transition_property_count())
|
||||||
.any(|t| t.seconds() > 0.)
|
.any(|t| t.seconds() > 0.)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if animation properties are equal between styles, but without
|
||||||
|
/// considering keyframe data.
|
||||||
|
#[cfg(feature = "servo")]
|
||||||
|
pub fn animations_equals(&self, other: &Self) -> bool {
|
||||||
|
self.animation_name_iter().eq(other.animation_name_iter()) &&
|
||||||
|
self.animation_delay_iter().eq(other.animation_delay_iter()) &&
|
||||||
|
self.animation_direction_iter().eq(other.animation_direction_iter()) &&
|
||||||
|
self.animation_duration_iter().eq(other.animation_duration_iter()) &&
|
||||||
|
self.animation_fill_mode_iter().eq(other.animation_fill_mode_iter()) &&
|
||||||
|
self.animation_iteration_count_iter().eq(other.animation_iteration_count_iter()) &&
|
||||||
|
self.animation_play_state_iter().eq(other.animation_play_state_iter()) &&
|
||||||
|
self.animation_timing_function_iter().eq(other.animation_timing_function_iter())
|
||||||
|
}
|
||||||
|
|
||||||
% elif style_struct.name == "Column":
|
% elif style_struct.name == "Column":
|
||||||
/// Whether this is a multicol style.
|
/// Whether this is a multicol style.
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
|
@ -2924,6 +2941,12 @@ impl ComputedValues {
|
||||||
self.pseudo.as_ref()
|
self.pseudo.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this is the style for a pseudo-element.
|
||||||
|
#[cfg(feature = "servo")]
|
||||||
|
pub fn is_pseudo_style(&self) -> bool {
|
||||||
|
self.pseudo().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether this style's display value is equal to contents.
|
/// Returns whether this style's display value is equal to contents.
|
||||||
pub fn is_display_contents(&self) -> bool {
|
pub fn is_display_contents(&self) -> bool {
|
||||||
self.get_box().clone_display().is_contents()
|
self.get_box().clone_display().is_contents()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue