From 9ff99d4608d2c096ccb04bef05bb24c99d6cfa1a Mon Sep 17 00:00:00 2001 From: Hiroyuki Ikezoe Date: Fri, 10 Mar 2017 11:10:12 +0900 Subject: [PATCH] Update CSS animations in a SequentialTask. We create the SequentialTask only if: * We have no old computed values and we have animation name style in the new computed values. * Any animation properties is changed. * display property is changed from 'none' and we have animation name style. * display property is changed to 'none'. In a subsequent patch we skip the SequentialTask if we have no running animations and the display propery is changed to 'none'. --- components/script/layout_wrapper.rs | 4 +++ components/style/context.rs | 14 ++++++++ components/style/dom.rs | 4 +++ components/style/gecko/wrapper.rs | 22 ++++++++++++ components/style/gecko_bindings/bindings.rs | 8 +++++ components/style/matching.rs | 40 ++++++++++++++++----- 6 files changed, 84 insertions(+), 8 deletions(-) diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index 994f77aa1d5..a9b175ab060 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -446,6 +446,10 @@ impl<'le> TElement for ServoLayoutElement<'le> { fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { self.element.has_selector_flags(flags) } + + fn update_animations(&self, _pseudo: Option<&PseudoElement>) { + panic!("this should be only called on gecko"); + } } impl<'le> PartialEq for ServoLayoutElement<'le> { diff --git a/components/style/context.rs b/components/style/context.rs index e8adc79faa9..764d68ea1de 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -15,6 +15,7 @@ use euclid::Size2D; use matching::StyleSharingCandidateCache; use parking_lot::RwLock; use properties::ComputedValues; +use selector_parser::PseudoElement; use selectors::matching::ElementSelectorFlags; use servo_config::opts; use std::collections::HashMap; @@ -181,6 +182,10 @@ pub enum SequentialTask { /// Sets selector flags. This is used when we need to set flags on an /// element that we don't have exclusive access to (i.e. the parent). SetSelectorFlags(SendElement, ElementSelectorFlags), + + /// Marks that we need to create/remove/update CSS animations after the + /// first traversal. + UpdateAnimations(SendElement, Option), } impl SequentialTask { @@ -192,6 +197,9 @@ impl SequentialTask { SetSelectorFlags(el, flags) => { unsafe { el.set_selector_flags(flags) }; } + UpdateAnimations(el, pseudo) => { + unsafe { el.update_animations(pseudo.as_ref()) }; + } } } @@ -200,6 +208,12 @@ impl SequentialTask { use self::SequentialTask::*; SetSelectorFlags(unsafe { SendElement::new(el) }, flags) } + + /// Creates a task to update CSS Animations on a given (pseudo-)element. + pub fn update_animations(el: E, pseudo: Option) -> Self { + use self::SequentialTask::*; + UpdateAnimations(unsafe { SendElement::new(el) }, pseudo) + } } /// A thread-local style context. diff --git a/components/style/dom.rs b/components/style/dom.rs index 7a327116746..ebd60ee6c91 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -335,6 +335,10 @@ pub trait TElement : PartialEq + Debug + Sized + Copy + Clone + ElementExt + Pre /// Returns true if the element has all the specified selector flags. fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool; + + /// Creates a task to update CSS Animations on a given (pseudo-)element. + /// Note: Gecko only. + fn update_animations(&self, _pseudo: Option<&PseudoElement>); } /// TNode and TElement aren't Send because we want to be careful and explicit diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 6744d742ab4..7ddd5938f74 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -33,12 +33,14 @@ use gecko_bindings::bindings::Gecko_GetAnimationRule; use gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock; use gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock; use gecko_bindings::bindings::Gecko_GetStyleContext; +use gecko_bindings::bindings::Gecko_UpdateAnimations; use gecko_bindings::structs; use gecko_bindings::structs::{RawGeckoElement, RawGeckoNode}; use gecko_bindings::structs::{nsIAtom, nsIContent, nsStyleContext}; use gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel; use gecko_bindings::structs::NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO; use gecko_bindings::structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE; +use gecko_bindings::sugar::ownership::HasArcFFI; use parking_lot::RwLock; use parser::ParserContextExtraData; use properties::{ComputedValues, parse_style_attribute}; @@ -504,6 +506,26 @@ impl<'le> TElement for GeckoElement<'le> { let node_flags = selector_flags_to_node_flags(flags); (self.flags() & node_flags) == node_flags } + + fn update_animations(&self, pseudo: Option<&PseudoElement>) { + let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); + + let computed_data = self.borrow_data().unwrap(); + let computed_values = computed_data.styles().primary.values(); + + let parent_element = self.parent_element(); + let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data()); + let parent_values = parent_data.as_ref().map(|d| d.styles().primary.values()); + let parent_values_opt = parent_values.map(|v| + *HasArcFFI::arc_as_borrowed(v) + ); + + unsafe { + Gecko_UpdateAnimations(self.0, atom_ptr, + HasArcFFI::arc_as_borrowed(&computed_values), + parent_values_opt); + } + } } impl<'le> PartialEq for GeckoElement<'le> { diff --git a/components/style/gecko_bindings/bindings.rs b/components/style/gecko_bindings/bindings.rs index c3234d04543..417a15ab5d9 100644 --- a/components/style/gecko_bindings/bindings.rs +++ b/components/style/gecko_bindings/bindings.rs @@ -541,6 +541,14 @@ extern "C" { RawGeckoStyleAnimationListBorrowed) -> bool; } +extern "C" { + pub fn Gecko_UpdateAnimations(aElement: RawGeckoElementBorrowed, + aPseudoTagOrNull: *mut nsIAtom, + aComputedValues: + ServoComputedValuesBorrowed, + aParentComputedValues: + ServoComputedValuesBorrowedOrNull); +} extern "C" { pub fn Gecko_Atomize(aString: *const ::std::os::raw::c_char, aLength: u32) -> *mut nsIAtom; diff --git a/components/style/matching.rs b/components/style/matching.rs index d0649ee181e..3b8ff1dad0a 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -556,7 +556,7 @@ trait PrivateMatchMethods: TElement { /// Computes values and damage for the primary or pseudo style of an element, /// setting them on the ElementData. fn cascade_primary_or_pseudo<'a>(&self, - context: &StyleContext, + context: &mut StyleContext, data: &mut ElementData, pseudo: Option<&PseudoElement>, possibly_expired_animations: &mut Vec, @@ -575,7 +575,7 @@ trait PrivateMatchMethods: TElement { // Handle animations. if booleans.animate { - self.process_animations(&context, + self.process_animations(context, &mut old_values, &mut new_values, pseudo, @@ -597,16 +597,40 @@ trait PrivateMatchMethods: TElement { #[cfg(feature = "gecko")] fn process_animations(&self, - _context: &StyleContext, - _old_values: &mut Option>, - _new_values: &mut Arc, - _pseudo: Option<&PseudoElement>, + context: &mut StyleContext, + old_values: &mut Option>, + new_values: &mut Arc, + pseudo: Option<&PseudoElement>, _possibly_expired_animations: &mut Vec) { + let ref new_box_style = new_values.get_box(); + let has_new_animation_style = new_box_style.animation_name_count() >= 1 && + new_box_style.animation_name_at(0).0.len() != 0; + let needs_update_animations = + old_values.as_ref().map_or(has_new_animation_style, |ref old| { + let ref old_box_style = old.get_box(); + let old_display_style = old_box_style.clone_display(); + let new_display_style = new_box_style.clone_display(); + // FIXME: If we know that the element has no running CSS animations, + // we can also skip the case with checking no_animations. + // + // FIXME: Bug 1344581: We still need to compare keyframe rules. + !old_box_style.animations_equals(&new_box_style) || + (old_display_style == display::T::none && + new_display_style != display::T::none && + has_new_animation_style) || + (old_display_style != display::T::none && + new_display_style == display::T::none) + }); + if needs_update_animations { + let task = SequentialTask::update_animations(self.as_node().as_element().unwrap(), + pseudo.cloned()); + context.thread_local.tasks.push(task); + } } #[cfg(feature = "servo")] fn process_animations(&self, - context: &StyleContext, + context: &mut StyleContext, old_values: &mut Option>, new_values: &mut Arc, _pseudo: Option<&PseudoElement>, @@ -1033,7 +1057,7 @@ pub trait MatchMethods : TElement { /// Run the CSS cascade and compute values for the element, potentially /// starting any new transitions or animations. fn cascade_element(&self, - context: &StyleContext, + context: &mut StyleContext, mut data: &mut AtomicRefMut, primary_is_shareable: bool) {