author | Martin Robinson <mrobinson@igalia.com> |
Thu, 18 Jun 2020 18:11:57 +0000 | |
changeset 536345 | 400228984077938439d5e7520341ca3a44e732ea |
parent 536344 | 1743d20e0d029ea47bb57884c678afae4c15b953 |
child 536346 | f6e4935b184b705639042d91e25042807c717c8d |
push id | 37520 |
push user | dluca@mozilla.com |
push date | Fri, 19 Jun 2020 04:04:08 +0000 |
treeherder | mozilla-central@d1a4f9157858 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 1646811 |
milestone | 79.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/servo/components/style/animation.rs +++ b/servo/components/style/animation.rs @@ -12,17 +12,17 @@ use crate::context::SharedStyleContext; use crate::dom::{OpaqueNode, TElement, TNode}; use crate::font_metrics::FontMetricsProvider; use crate::properties::animated_properties::AnimationValue; use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection; use crate::properties::longhands::animation_fill_mode::computed_value::single_value::T as AnimationFillMode; use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState; use crate::properties::LonghandIdSet; 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::values::animated::{Animate, Procedure}; use crate::values::computed::Time; use crate::values::computed::TimingFunction; use crate::values::generics::box_::AnimationIterationCount; use crate::values::generics::easing::{StepPosition, TimingFunction as GenericTimingFunction}; use crate::Atom; use servo_arc::Arc; @@ -167,27 +167,127 @@ impl AnimationState { #[derive(Clone, Debug, MallocSizeOf)] pub enum KeyframesIterationState { /// Infinite iterations with the current iteration count. Infinite(f64), /// Current and max iterations. 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 #[derive(Clone, MallocSizeOf)] pub struct Animation { /// The node associated with this animation. pub node: OpaqueNode, /// The name of this animation as defined by the style. pub name: Atom, - /// The internal animation from the style system. - pub keyframes_animation: KeyframesAnimation, + /// The properties that change in this animation. + 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 /// timeline when this animation was created plus any animation delay. pub started_at: f64, /// The duration of this animation. pub duration: f64, @@ -372,36 +472,29 @@ impl Animation { // avoid sending multiple animationstart events. if self.state == Pending && self.started_at <= now && old_state != Pending { self.state = Running; } } /// Update the given style to reflect the values specified by this `Animation` /// at the time provided by the given `SharedStyleContext`. - fn update_style<E>( - &self, - context: &SharedStyleContext, - style: &mut Arc<ComputedValues>, - font_metrics_provider: &dyn FontMetricsProvider, - ) where - E: TElement, - { + fn update_style(&self, context: &SharedStyleContext, style: &mut Arc<ComputedValues>) { let duration = self.duration; let started_at = self.started_at; let now = match self.state { AnimationState::Running | AnimationState::Pending | AnimationState::Finished => { context.current_time_for_animations }, AnimationState::Paused(progress) => started_at + duration * progress, 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; if total_progress < 0. && self.fill_mode != AnimationFillMode::Backwards && self.fill_mode != AnimationFillMode::Both { return; } @@ -412,149 +505,110 @@ impl Animation { { return; } total_progress = total_progress.min(1.0).max(0.0); // Get the indices of the previous (from) keyframe and the next (to) keyframe. let next_keyframe_index; let prev_keyframe_index; + let num_steps = self.computed_steps.len(); + debug_assert!(num_steps > 0); match self.current_direction { AnimationDirection::Normal => { next_keyframe_index = self - .keyframes_animation - .steps + .computed_steps .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 .and_then(|pos| if pos != 0 { Some(pos - 1) } else { None }) .unwrap_or(0); }, AnimationDirection::Reverse => { next_keyframe_index = self - .keyframes_animation - .steps + .computed_steps .iter() .rev() - .position(|step| total_progress as f32 <= 1. - step.start_percentage.0) - .map(|pos| self.keyframes_animation.steps.len() - pos - 1); + .position(|step| total_progress as f32 <= 1. - step.step.start_percentage.0) + .map(|pos| num_steps - pos - 1); prev_keyframe_index = next_keyframe_index .and_then(|pos| { - if pos != self.keyframes_animation.steps.len() - 1 { + if pos != num_steps - 1 { Some(pos + 1) } else { None } }) - .unwrap_or(self.keyframes_animation.steps.len() - 1) + .unwrap_or(num_steps - 1) }, _ => unreachable!(), } debug!( "Animation::update_style: keyframe from {:?} to {:?}", 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 { - Some(target) => &self.keyframes_animation.steps[target], + Some(index) => &self.computed_steps[index], None => return, }; let update_with_single_keyframe_style = |style, computed_style: &Arc<ComputedValues>| { let mutable_style = Arc::make_mut(style); - for property in self - .keyframes_animation - .properties_changed - .iter() - .filter_map(|longhand| { - AnimationValue::from_computed_values(longhand, &**computed_style) - }) - { + for property in self.properties_changed.iter().filter_map(|longhand| { + AnimationValue::from_computed_values(longhand, &**computed_style) + }) { property.set_in_style_for_servo(mutable_style); } }; // TODO: How could we optimise it? Is it such a big deal? - let prev_keyframe_style = compute_style_for_animation_step::<E>( - context, - prev_keyframe, - style, - &self.cascade_style, - font_metrics_provider, - ); + let prev_keyframe_style = &prev_keyframe.style; + let next_keyframe_style = &next_keyframe.style; if total_progress <= 0.0 { update_with_single_keyframe_style(style, &prev_keyframe_style); 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 { update_with_single_keyframe_style(style, &next_keyframe_style); return; } 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 last_keyframe_ended_at = match self.current_direction { 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 => { - 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!(), }; 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 update_style_for_longhand = |longhand| { let from = AnimationValue::from_computed_values(longhand, &prev_keyframe_style)?; let to = AnimationValue::from_computed_values(longhand, &next_keyframe_style)?; PropertyAnimation { from, to, - timing_function, + timing_function: prev_keyframe.timing_function, duration: relative_duration as f64, } .update(&mut new_style, relative_progress); None::<()> }; - for property in self.keyframes_animation.properties_changed.iter() { + for property in self.properties_changed.iter() { update_style_for_longhand(property); } *Arc::make_mut(style) = new_style; } } impl fmt::Debug for Animation { @@ -719,26 +773,23 @@ impl ElementAnimationSet { for animation in self.animations.iter_mut() { animation.state = AnimationState::Canceled; } for transition in self.transitions.iter_mut() { transition.state = AnimationState::Canceled; } } - pub(crate) fn apply_active_animations<E>( + pub(crate) fn apply_active_animations( &mut self, context: &SharedStyleContext, style: &mut Arc<ComputedValues>, - font_metrics: &dyn crate::font_metrics::FontMetricsProvider, - ) where - E: TElement, - { + ) { for animation in &self.animations { - animation.update_style::<E>(context, style, font_metrics); + animation.update_style(context, style); } for transition in &self.transitions { transition.update_style(context, style); } } /// Clear all canceled animations and transitions from this `ElementAnimationSet`. @@ -773,70 +824,73 @@ impl ElementAnimationSet { .filter(|animation| animation.state.needs_to_be_ticked()) .count() + self.transitions .iter() .filter(|transition| transition.state.needs_to_be_ticked()) .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 .iter() - .any(|animation| animation.state != AnimationState::Canceled) || - self.transitions - .iter() - .any(|transition| transition.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 + .iter() + .any(|transition| transition.state != AnimationState::Canceled) } /// Update our animations given a new style, canceling or starting new animations /// when appropriate. pub fn update_animations_for_new_style<E>( &mut self, element: E, context: &SharedStyleContext, new_style: &Arc<ComputedValues>, + font_metrics: &dyn crate::font_metrics::FontMetricsProvider, ) where E: TElement, { for animation in self.animations.iter_mut() { if animation.is_cancelled_in_new_style(new_style) { animation.state = AnimationState::Canceled; } } - 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 /// when appropriate. - pub fn update_transitions_for_new_style<E>( + pub fn update_transitions_for_new_style( &mut self, context: &SharedStyleContext, opaque_node: OpaqueNode, old_style: Option<&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 // there were no previously triggered transitions. let mut before_change_style = match old_style { Some(old_style) => Arc::clone(old_style), None => return, }; // We convert old values into `before-change-style` here. // 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 // 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(); - 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( context, opaque_node, &before_change_style, after_change_style, self, @@ -956,100 +1010,52 @@ pub fn start_transitions_if_applicable( old_style, new_style, ); } 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 /// values. pub fn maybe_start_animations<E>( element: E, context: &SharedStyleContext, new_style: &Arc<ComputedValues>, animation_state: &mut ElementAnimationSet, + font_metrics_provider: &dyn FontMetricsProvider, ) where E: TElement, { let box_style = new_style.get_box(); for (i, name) in box_style.animation_name_iter().enumerate() { let name = match name.as_atom() { Some(atom) => atom, None => continue, }; debug!("maybe_start_animations: name={}", name); let duration = box_style.animation_duration_mod(i).seconds(); if duration == 0. { continue; } - let anim = match context.stylist.get_animation(name, element) { + let keyframe_animation = match context.stylist.get_animation(name, element) { Some(animation) => animation, None => continue, }; debug!("maybe_start_animations: animation {} found", name); // If this animation doesn't have any keyframe, we can just continue // without submitting it to the compositor, since both the first and // the second keyframes would be synthetised from the computed // values. - if anim.steps.is_empty() { + if keyframe_animation.steps.is_empty() { continue; } let delay = box_style.animation_delay_mod(i).seconds(); let animation_start = context.current_time_for_animations + delay as f64; let iteration_state = match box_style.animation_iteration_count_mod(i) { AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0), AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()), @@ -1066,20 +1072,30 @@ pub fn maybe_start_animations<E>( }, }; let state = match box_style.animation_play_state_mod(i) { AnimationPlayState::Paused => AnimationState::Paused(0.), 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 { node: element.as_node().opaque(), name: name.clone(), - keyframes_animation: anim.clone(), + properties_changed: keyframe_animation.properties_changed, + computed_steps, started_at: animation_start, duration: duration as f64, fill_mode: box_style.animation_fill_mode_mod(i), delay: delay as f64, iteration_state, state, direction: animation_direction, current_direction: initial_direction,
--- a/servo/components/style/dom.rs +++ b/servo/components/style/dom.rs @@ -3,20 +3,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Types and traits used to access the DOM from style calculation. #![allow(unsafe_code)] #![deny(missing_docs)] use crate::applicable_declarations::ApplicableDeclarationBlock; +use crate::context::SharedStyleContext; #[cfg(feature = "gecko")] -use crate::context::PostAnimationTasks; -#[cfg(feature = "gecko")] -use crate::context::UpdateAnimationsTasks; +use crate::context::{PostAnimationTasks, UpdateAnimationsTasks}; use crate::data::ElementData; use crate::element_state::ElementState; use crate::font_metrics::FontMetricsProvider; use crate::media_queries::Device; use crate::properties::{AnimationRules, ComputedValues, PropertyDeclarationBlock}; use crate::selector_parser::{AttrValue, Lang, PseudoElement, SelectorImpl}; use crate::shared_lock::Locked; use crate::stylist::CascadeData; @@ -744,17 +743,17 @@ pub trait TElement: fn process_post_animation(&self, tasks: PostAnimationTasks); /// Returns true if the element has relevant animations. Relevant /// animations are those animations that are affecting the element's style /// or are scheduled to do so in the future. fn has_animations(&self) -> bool; /// 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 /// completed transitions). fn has_css_transitions(&self) -> bool; /// Returns true if the element has animation restyle hints. fn has_animation_restyle_hints(&self) -> bool { let data = match self.borrow_data() {
--- a/servo/components/style/gecko/wrapper.rs +++ b/servo/components/style/gecko/wrapper.rs @@ -1511,17 +1511,17 @@ impl<'le> TElement for GeckoElement<'le> ); } } fn has_animations(&self) -> bool { 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) } } fn has_css_transitions(&self) -> bool { self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) } } fn might_need_transitions_update(
--- a/servo/components/style/matching.rs +++ b/servo/components/style/matching.rs @@ -228,27 +228,26 @@ trait PrivateMatchMethods: TElement { RuleInclusion::All, PseudoElementResolution::IfApplicable, ) .cascade_style_and_visited_with_default_parents(inputs); Some(style.0) } - #[cfg(feature = "gecko")] fn needs_animations_update( &self, context: &mut StyleContext<Self>, old_style: Option<&ComputedValues>, new_style: &ComputedValues, ) -> bool { let new_box_style = new_style.get_box(); 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 { return false; } let old_style = match old_style { Some(old) => old, // If we have no old style but have animations, we may be a // pseudo-element which was re-created without style changes. @@ -437,47 +436,63 @@ trait PrivateMatchMethods: TElement { context: &mut StyleContext<Self>, old_values: &mut Option<Arc<ComputedValues>>, new_values: &mut Arc<ComputedValues>, _restyle_hint: RestyleHint, _important_rules_changed: bool, ) { 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 shared_context = context.shared; - let mut animation_states = shared_context.animation_states.write(); - let mut animation_state = animation_states.remove(&this_opaque).unwrap_or_default(); + let mut animation_set = shared_context + .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, this_opaque, old_values.as_ref(), new_values, - &context.thread_local.font_metrics_provider, ); - animation_state.apply_active_animations::<Self>( - shared_context, - new_values, - &context.thread_local.font_metrics_provider, - ); + animation_set.apply_active_animations(shared_context, new_values); // We clear away any finished transitions, but retain animations, because they // might still be used for proper calculation of `animation-fill-mode`. - animation_state + animation_set .transitions .retain(|transition| transition.state != AnimationState::Finished); // If the ElementAnimationSet is empty, and don't store it in order to // save memory and to avoid extra processing later. - if !animation_state.is_empty() { - animation_states.insert(this_opaque, animation_state); + if !animation_set.is_empty() { + shared_context + .animation_states + .write() + .insert(this_opaque, animation_set); } } /// Computes and applies non-redundant damage. fn accumulate_damage_for( &self, shared_context: &SharedStyleContext, damage: &mut RestyleDamage,
--- a/servo/components/style/properties/properties.mako.rs +++ b/servo/components/style/properties/properties.mako.rs @@ -2824,20 +2824,37 @@ pub mod style_structs { /// animation-name other than `none`. pub fn specifies_animations(&self) -> bool { self.animation_name_iter().any(|name| name.0.is_some()) } /// Returns whether there are any transitions specified. #[cfg(feature = "servo")] pub fn specifies_transitions(&self) -> bool { + // TODO(mrobinson): This should check the combined duration and not just + // the duration. self.transition_duration_iter() .take(self.transition_property_count()) .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": /// Whether this is a multicol style. #[cfg(feature = "servo")] pub fn is_multicol(&self) -> bool { !self.column_width.is_auto() || !self.column_count.is_auto() } % endif } @@ -2920,16 +2937,22 @@ pub struct ComputedValues { impl ComputedValues { /// Returns the pseudo-element that this style represents. #[cfg(feature = "servo")] pub fn pseudo(&self) -> Option<<&PseudoElement> { 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. pub fn is_display_contents(&self) -> bool { self.get_box().clone_display().is_contents() } /// Gets a reference to the rule node. Panic if no rule node exists. pub fn rules(&self) -> &StrongRuleNode { self.rules.as_ref().unwrap()