mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Auto merge of #16496 - BorisChiou:stylo/transition/trigger, r=heycam
stylo: Bug 1341372 - Detect new transitions and let it run. These are interdependent patches of Bug 1341372. We let animation-only restyle also work for RESTYLE_CSS_TRANSITIONS, and check if we need to update transitions by each transition property. If it is necessary to create/replace/cancel transitions, we create a SequentialTask for CSS_TRANSITIONS. --- - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix Bug 1341372 - [X] These changes do not require tests because there are tests in Gecko already. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16496) <!-- Reviewable:end -->
This commit is contained in:
commit
4d8c9c10cb
11 changed files with 461 additions and 70 deletions
|
@ -468,6 +468,10 @@ impl<'le> TElement for ServoLayoutElement<'le> {
|
|||
fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool {
|
||||
panic!("this should be only called on gecko");
|
||||
}
|
||||
|
||||
fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool {
|
||||
panic!("this should be only called on gecko");
|
||||
}
|
||||
}
|
||||
|
||||
impl<'le> PartialEq for ServoLayoutElement<'le> {
|
||||
|
|
|
@ -19,6 +19,7 @@ use font_metrics::FontMetricsProvider;
|
|||
#[cfg(feature = "gecko")] use gecko_bindings::structs;
|
||||
use matching::StyleSharingCandidateCache;
|
||||
use parking_lot::RwLock;
|
||||
#[cfg(feature = "gecko")] use properties::ComputedValues;
|
||||
#[cfg(feature = "gecko")] use selector_parser::PseudoElement;
|
||||
use selectors::matching::ElementSelectorFlags;
|
||||
#[cfg(feature = "servo")] use servo_config::opts;
|
||||
|
@ -285,10 +286,22 @@ bitflags! {
|
|||
pub enum SequentialTask<E: TElement> {
|
||||
/// Entry to avoid an unused type parameter error on servo.
|
||||
Unused(SendElement<E>),
|
||||
|
||||
/// Performs one of a number of possible tasks related to updating animations based on the
|
||||
/// |tasks| field. These include updating CSS animations/transitions that changed as part
|
||||
/// of the non-animation style traversal, and updating the computed effect properties.
|
||||
#[cfg(feature = "gecko")]
|
||||
/// Marks that we need to update CSS animations, update effect properties of
|
||||
/// any type of animations after the normal traversal.
|
||||
UpdateAnimations(SendElement<E>, Option<PseudoElement>, UpdateAnimationsTasks),
|
||||
UpdateAnimations {
|
||||
/// The target element.
|
||||
el: SendElement<E>,
|
||||
/// The target pseudo element.
|
||||
pseudo: Option<PseudoElement>,
|
||||
/// The before-change style for transitions. We use before-change style as the initial
|
||||
/// value of its Keyframe. Required if |tasks| includes CSSTransitions.
|
||||
before_change_style: Option<Arc<ComputedValues>>,
|
||||
/// The tasks which are performed in this SequentialTask.
|
||||
tasks: UpdateAnimationsTasks
|
||||
},
|
||||
}
|
||||
|
||||
impl<E: TElement> SequentialTask<E> {
|
||||
|
@ -299,18 +312,24 @@ impl<E: TElement> SequentialTask<E> {
|
|||
match self {
|
||||
Unused(_) => unreachable!(),
|
||||
#[cfg(feature = "gecko")]
|
||||
UpdateAnimations(el, pseudo, tasks) => {
|
||||
unsafe { el.update_animations(pseudo.as_ref(), tasks) };
|
||||
UpdateAnimations { el, pseudo, before_change_style, tasks } => {
|
||||
unsafe { el.update_animations(pseudo.as_ref(), before_change_style, tasks) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a task to update various animation-related state on
|
||||
/// a given (pseudo-)element.
|
||||
#[cfg(feature = "gecko")]
|
||||
/// Creates a task to update various animation state on a given (pseudo-)element.
|
||||
pub fn update_animations(el: E, pseudo: Option<PseudoElement>,
|
||||
pub fn update_animations(el: E,
|
||||
pseudo: Option<PseudoElement>,
|
||||
before_change_style: Option<Arc<ComputedValues>>,
|
||||
tasks: UpdateAnimationsTasks) -> Self {
|
||||
use self::SequentialTask::*;
|
||||
UpdateAnimations(unsafe { SendElement::new(el) }, pseudo, tasks)
|
||||
UpdateAnimations { el: unsafe { SendElement::new(el) },
|
||||
pseudo: pseudo,
|
||||
before_change_style: before_change_style,
|
||||
tasks: tasks }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
use dom::TElement;
|
||||
use properties::ComputedValues;
|
||||
use properties::longhands::display::computed_value as display;
|
||||
use restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
|
||||
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
|
||||
use rule_tree::StrongRuleNode;
|
||||
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage, Snapshot};
|
||||
#[cfg(feature = "servo")] use std::collections::HashMap;
|
||||
|
@ -199,13 +199,13 @@ impl StoredRestyleHint {
|
|||
// In the middle of an animation only restyle, we don't need to
|
||||
// propagate any restyle hints, and we need to remove ourselves.
|
||||
if traversal_flags.for_animation_only() {
|
||||
if self.0.contains(RESTYLE_CSS_ANIMATIONS) {
|
||||
self.0.remove(RESTYLE_CSS_ANIMATIONS);
|
||||
if self.0.intersects(RestyleHint::for_animations()) {
|
||||
self.0.remove(RestyleHint::for_animations());
|
||||
}
|
||||
return Self::empty();
|
||||
}
|
||||
|
||||
debug_assert!(!self.0.contains(RESTYLE_CSS_ANIMATIONS),
|
||||
debug_assert!(!self.0.intersects(RestyleHint::for_animations()),
|
||||
"There should not be any animation restyle hints \
|
||||
during normal traversal");
|
||||
|
||||
|
@ -259,7 +259,7 @@ impl StoredRestyleHint {
|
|||
|
||||
/// Returns true if the hint has animation-only restyle.
|
||||
pub fn has_animation_hint(&self) -> bool {
|
||||
self.0.contains(RESTYLE_CSS_ANIMATIONS)
|
||||
self.0.intersects(RestyleHint::for_animations())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,11 +14,15 @@ use data::ElementData;
|
|||
use element_state::ElementState;
|
||||
use font_metrics::FontMetricsProvider;
|
||||
use properties::{ComputedValues, PropertyDeclarationBlock};
|
||||
#[cfg(feature = "gecko")] use properties::animated_properties::AnimationValue;
|
||||
#[cfg(feature = "gecko")] use properties::animated_properties::TransitionProperty;
|
||||
use rule_tree::CascadeLevel;
|
||||
use selector_parser::{ElementExt, PreExistingComputedValues, PseudoElement};
|
||||
use selectors::matching::ElementSelectorFlags;
|
||||
use shared_lock::Locked;
|
||||
use sink::Push;
|
||||
use std::fmt;
|
||||
#[cfg(feature = "gecko")] use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
|
@ -320,6 +324,14 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
|
|||
AnimationRules(None, None)
|
||||
}
|
||||
|
||||
/// Get this element's animation rule by the cascade level.
|
||||
fn get_animation_rule_by_cascade(&self,
|
||||
_pseudo: Option<&PseudoElement>,
|
||||
_cascade_level: CascadeLevel)
|
||||
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get this element's animation rule.
|
||||
fn get_animation_rule(&self, _pseudo: Option<&PseudoElement>)
|
||||
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
|
||||
|
@ -453,6 +465,7 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
|
|||
/// Creates a task to update various animation state on a given (pseudo-)element.
|
||||
#[cfg(feature = "gecko")]
|
||||
fn update_animations(&self, _pseudo: Option<&PseudoElement>,
|
||||
before_change_style: Option<Arc<ComputedValues>>,
|
||||
tasks: UpdateAnimationsTasks);
|
||||
|
||||
/// Returns true if the element has relevant animations. Relevant
|
||||
|
@ -463,6 +476,10 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
|
|||
/// Returns true if the element has a CSS animation.
|
||||
fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool;
|
||||
|
||||
/// Returns true if the element has a CSS transition (including running transitions and
|
||||
/// completed transitions).
|
||||
fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool;
|
||||
|
||||
/// Returns true if the element has animation restyle hints.
|
||||
fn has_animation_restyle_hints(&self) -> bool {
|
||||
let data = match self.borrow_data() {
|
||||
|
@ -472,6 +489,44 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
|
|||
return data.get_restyle()
|
||||
.map_or(false, |r| r.hint.has_animation_hint());
|
||||
}
|
||||
|
||||
/// Gets the current existing CSS transitions, by |property, end value| pairs in a HashMap.
|
||||
#[cfg(feature = "gecko")]
|
||||
fn get_css_transitions_info(&self,
|
||||
pseudo: Option<&PseudoElement>)
|
||||
-> HashMap<TransitionProperty, Arc<AnimationValue>>;
|
||||
|
||||
/// Does a rough (and cheap) check for whether or not transitions might need to be updated that
|
||||
/// will quickly return false for the common case of no transitions specified or running. If
|
||||
/// this returns false, we definitely don't need to update transitions but if it returns true
|
||||
/// we can perform the more thoroughgoing check, needs_transitions_update, to further
|
||||
/// reduce the possibility of false positives.
|
||||
#[cfg(feature = "gecko")]
|
||||
fn might_need_transitions_update(&self,
|
||||
old_values: &Option<&Arc<ComputedValues>>,
|
||||
new_values: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>) -> bool;
|
||||
|
||||
/// Returns true if one of the transitions needs to be updated on this element. We check all
|
||||
/// the transition properties to make sure that updating transitions is necessary.
|
||||
/// This method should only be called if might_needs_transitions_update returns true when
|
||||
/// passed the same parameters.
|
||||
#[cfg(feature = "gecko")]
|
||||
fn needs_transitions_update(&self,
|
||||
before_change_style: &Arc<ComputedValues>,
|
||||
after_change_style: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>) -> bool;
|
||||
|
||||
/// Returns true if we need to update transitions for the specified property on this element.
|
||||
#[cfg(feature = "gecko")]
|
||||
fn needs_transitions_update_per_property(&self,
|
||||
property: TransitionProperty,
|
||||
combined_duration: f32,
|
||||
before_change_style: &Arc<ComputedValues>,
|
||||
after_change_style: &Arc<ComputedValues>,
|
||||
existing_transitions: &HashMap<TransitionProperty,
|
||||
Arc<AnimationValue>>)
|
||||
-> bool;
|
||||
}
|
||||
|
||||
/// Trait abstracting over different kinds of dirty-descendants bits.
|
||||
|
|
|
@ -34,6 +34,7 @@ use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
|
|||
use gecko_bindings::bindings::Gecko_ClassOrClassList;
|
||||
use gecko_bindings::bindings::Gecko_ElementHasAnimations;
|
||||
use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
|
||||
use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
|
||||
use gecko_bindings::bindings::Gecko_GetAnimationRule;
|
||||
use gecko_bindings::bindings::Gecko_GetExtraContentStyleDeclarations;
|
||||
use gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock;
|
||||
|
@ -56,7 +57,7 @@ use media_queries::Device;
|
|||
use parking_lot::RwLock;
|
||||
use properties::{ComputedValues, parse_style_attribute};
|
||||
use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
|
||||
use properties::animated_properties::AnimationValueMap;
|
||||
use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
|
||||
use properties::style_structs::Font;
|
||||
use rule_tree::CascadeLevel as ServoCascadeLevel;
|
||||
use selector_parser::{ElementExt, Snapshot};
|
||||
|
@ -66,6 +67,7 @@ use selectors::parser::{AttrSelector, NamespaceConstraint};
|
|||
use shared_lock::Locked;
|
||||
use sink::Push;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ptr;
|
||||
|
@ -528,6 +530,17 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
self.get_transition_rule(pseudo))
|
||||
}
|
||||
|
||||
fn get_animation_rule_by_cascade(&self,
|
||||
pseudo: Option<&PseudoElement>,
|
||||
cascade_level: ServoCascadeLevel)
|
||||
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
|
||||
match cascade_level {
|
||||
ServoCascadeLevel::Animations => self.get_animation_rule(pseudo),
|
||||
ServoCascadeLevel::Transitions => self.get_transition_rule(pseudo),
|
||||
_ => panic!("Unsupported cascade level for getting the animation rule")
|
||||
}
|
||||
}
|
||||
|
||||
fn get_animation_rule(&self, pseudo: Option<&PseudoElement>)
|
||||
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
|
||||
get_animation_rule(self, pseudo, CascadeLevel::Animations)
|
||||
|
@ -653,7 +666,9 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
(self.flags() & node_flags) == node_flags
|
||||
}
|
||||
|
||||
fn update_animations(&self, pseudo: Option<&PseudoElement>,
|
||||
fn update_animations(&self,
|
||||
pseudo: Option<&PseudoElement>,
|
||||
before_change_style: Option<Arc<ComputedValues>>,
|
||||
tasks: UpdateAnimationsTasks) {
|
||||
// We have to update animations even if the element has no computed style
|
||||
// since it means the element is in a display:none subtree, we should destroy
|
||||
|
@ -680,9 +695,10 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
);
|
||||
|
||||
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
|
||||
let before_change_values = before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v));
|
||||
unsafe {
|
||||
Gecko_UpdateAnimations(self.0, atom_ptr,
|
||||
None,
|
||||
before_change_values,
|
||||
computed_values_opt,
|
||||
parent_values_opt,
|
||||
tasks.bits());
|
||||
|
@ -698,6 +714,167 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
|
||||
unsafe { Gecko_ElementHasCSSAnimations(self.0, atom_ptr) }
|
||||
}
|
||||
|
||||
fn has_css_transitions(&self, pseudo: Option<&PseudoElement>) -> bool {
|
||||
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
|
||||
unsafe { Gecko_ElementHasCSSTransitions(self.0, atom_ptr) }
|
||||
}
|
||||
|
||||
fn get_css_transitions_info(&self,
|
||||
pseudo: Option<&PseudoElement>)
|
||||
-> HashMap<TransitionProperty, Arc<AnimationValue>> {
|
||||
use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt;
|
||||
use gecko_bindings::bindings::Gecko_ElementTransitions_Length;
|
||||
use gecko_bindings::bindings::Gecko_ElementTransitions_PropertyAt;
|
||||
|
||||
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
|
||||
let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0, atom_ptr) };
|
||||
let mut map = HashMap::with_capacity(collection_length);
|
||||
for i in 0..collection_length {
|
||||
let (property, raw_end_value) = unsafe {
|
||||
(Gecko_ElementTransitions_PropertyAt(self.0, atom_ptr, i as usize).into(),
|
||||
Gecko_ElementTransitions_EndValueAt(self.0, atom_ptr, i as usize))
|
||||
};
|
||||
let end_value = AnimationValue::arc_from_borrowed(&raw_end_value);
|
||||
debug_assert!(end_value.is_some());
|
||||
map.insert(property, end_value.unwrap().clone());
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
fn might_need_transitions_update(&self,
|
||||
old_values: &Option<&Arc<ComputedValues>>,
|
||||
new_values: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>) -> bool {
|
||||
use properties::longhands::display::computed_value as display;
|
||||
|
||||
if old_values.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let ref new_box_style = new_values.get_box();
|
||||
let transition_not_running = !self.has_css_transitions(pseudo) &&
|
||||
new_box_style.transition_property_count() == 1 &&
|
||||
new_box_style.transition_combined_duration_at(0) <= 0.0f32;
|
||||
let new_display_style = new_box_style.clone_display();
|
||||
let old_display_style = old_values.map(|ref old| old.get_box().clone_display()).unwrap();
|
||||
|
||||
new_box_style.transition_property_count() > 0 &&
|
||||
!transition_not_running &&
|
||||
(new_display_style != display::T::none &&
|
||||
old_display_style != display::T::none)
|
||||
}
|
||||
|
||||
// Detect if there are any changes that require us to update transitions. This is used as a
|
||||
// more thoroughgoing check than the, cheaper might_need_transitions_update check.
|
||||
// The following logic shadows the logic used on the Gecko side
|
||||
// (nsTransitionManager::DoUpdateTransitions) where we actually perform the update.
|
||||
// https://drafts.csswg.org/css-transitions/#starting
|
||||
fn needs_transitions_update(&self,
|
||||
before_change_style: &Arc<ComputedValues>,
|
||||
after_change_style: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>) -> bool {
|
||||
use gecko_bindings::structs::nsCSSPropertyID;
|
||||
use properties::animated_properties;
|
||||
use std::collections::HashSet;
|
||||
|
||||
debug_assert!(self.might_need_transitions_update(&Some(before_change_style),
|
||||
after_change_style,
|
||||
pseudo),
|
||||
"We should only call needs_transitions_update if \
|
||||
might_need_transitions_update returns true");
|
||||
|
||||
let ref after_change_box_style = after_change_style.get_box();
|
||||
let transitions_count = after_change_box_style.transition_property_count();
|
||||
let existing_transitions = self.get_css_transitions_info(pseudo);
|
||||
let mut transitions_to_keep = if !existing_transitions.is_empty() &&
|
||||
(after_change_box_style.transition_nscsspropertyid_at(0) !=
|
||||
nsCSSPropertyID::eCSSPropertyExtra_all_properties) {
|
||||
Some(HashSet::<TransitionProperty>::with_capacity(transitions_count))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Check if this property is none, custom or unknown.
|
||||
let is_none_or_custom_property = |property: nsCSSPropertyID| -> bool {
|
||||
return property == nsCSSPropertyID::eCSSPropertyExtra_no_properties ||
|
||||
property == nsCSSPropertyID::eCSSPropertyExtra_variable ||
|
||||
property == nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||
};
|
||||
|
||||
for i in 0..transitions_count {
|
||||
let property = after_change_box_style.transition_nscsspropertyid_at(i);
|
||||
let combined_duration = after_change_box_style.transition_combined_duration_at(i);
|
||||
|
||||
// We don't need to update transition for none/custom properties.
|
||||
if is_none_or_custom_property(property) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut property_check_helper = |property: TransitionProperty| -> bool {
|
||||
if self.needs_transitions_update_per_property(property,
|
||||
combined_duration,
|
||||
before_change_style,
|
||||
after_change_style,
|
||||
&existing_transitions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(set) = transitions_to_keep.as_mut() {
|
||||
set.insert(property);
|
||||
}
|
||||
false
|
||||
};
|
||||
// FIXME: Bug 1353628: Shorthand properties are parsed failed now, so after fixing
|
||||
// that, we have to handle shorthand.
|
||||
if property == nsCSSPropertyID::eCSSPropertyExtra_all_properties {
|
||||
if TransitionProperty::any(property_check_helper) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if animated_properties::nscsspropertyid_is_animatable(property) &&
|
||||
property_check_helper(property.into()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have to cancel the running transition because this is not a matching
|
||||
// transition-property value.
|
||||
transitions_to_keep.map_or(false, |set| {
|
||||
existing_transitions.keys().any(|property| !set.contains(property))
|
||||
})
|
||||
}
|
||||
|
||||
fn needs_transitions_update_per_property(&self,
|
||||
property: TransitionProperty,
|
||||
combined_duration: f32,
|
||||
before_change_style: &Arc<ComputedValues>,
|
||||
after_change_style: &Arc<ComputedValues>,
|
||||
existing_transitions: &HashMap<TransitionProperty,
|
||||
Arc<AnimationValue>>)
|
||||
-> bool {
|
||||
use properties::animated_properties::AnimatedProperty;
|
||||
|
||||
// We don't allow transitions on properties that are not interpolable.
|
||||
if property.is_discrete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if existing_transitions.contains_key(&property) {
|
||||
// If there is an existing transition, update only if the end value differs.
|
||||
// If the end value has not changed, we should leave the currently running
|
||||
// transition as-is since we don't want to interrupt its timing function.
|
||||
let after_value =
|
||||
Arc::new(AnimationValue::from_computed_values(&property, after_change_style));
|
||||
return existing_transitions.get(&property).unwrap() != &after_value;
|
||||
}
|
||||
|
||||
combined_duration > 0.0f32 &&
|
||||
AnimatedProperty::from_transition_property(&property,
|
||||
before_change_style,
|
||||
after_change_style).does_animate()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'le> PartialEq for GeckoElement<'le> {
|
||||
|
|
|
@ -648,6 +648,30 @@ extern "C" {
|
|||
aPseudoTagOrNull: *mut nsIAtom)
|
||||
-> bool;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn Gecko_ElementHasCSSTransitions(aElement: RawGeckoElementBorrowed,
|
||||
aPseudoTagOrNull: *mut nsIAtom)
|
||||
-> bool;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn Gecko_ElementTransitions_Length(aElement: RawGeckoElementBorrowed,
|
||||
aPseudoTagOrNull: *mut nsIAtom)
|
||||
-> usize;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn Gecko_ElementTransitions_PropertyAt(aElement:
|
||||
RawGeckoElementBorrowed,
|
||||
aPseudoTagOrNull: *mut nsIAtom,
|
||||
aIndex: usize)
|
||||
-> nsCSSPropertyID;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn Gecko_ElementTransitions_EndValueAt(aElement:
|
||||
RawGeckoElementBorrowed,
|
||||
aPseudoTagOrNull: *mut nsIAtom,
|
||||
aIndex: usize)
|
||||
-> RawServoAnimationValueBorrowedOrNull;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn Gecko_GetProgressFromComputedTiming(aComputedTiming:
|
||||
RawGeckoComputedTimingBorrowed)
|
||||
|
|
|
@ -19,7 +19,7 @@ use dom::{AnimationRules, SendElement, TElement, TNode};
|
|||
use font_metrics::FontMetricsProvider;
|
||||
use properties::{CascadeFlags, ComputedValues, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
|
||||
use properties::longhands::display::computed_value as display;
|
||||
use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_CSS_ANIMATIONS, RestyleHint};
|
||||
use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS, RestyleHint};
|
||||
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode};
|
||||
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
|
||||
use selectors::bloom::BloomFilter;
|
||||
|
@ -477,10 +477,14 @@ trait PrivateMatchMethods: TElement {
|
|||
|
||||
// Handle animations.
|
||||
if animate && !context.shared.traversal_flags.for_animation_only() {
|
||||
let pseudo_style = pseudo_style.as_ref().map(|&(ref pseudo, ref computed_style)| {
|
||||
(*pseudo, &**computed_style)
|
||||
});
|
||||
self.process_animations(context,
|
||||
&mut old_values,
|
||||
&mut new_values,
|
||||
pseudo);
|
||||
primary_style,
|
||||
&pseudo_style);
|
||||
}
|
||||
|
||||
// Accumulate restyle damage.
|
||||
|
@ -496,32 +500,59 @@ trait PrivateMatchMethods: TElement {
|
|||
}
|
||||
}
|
||||
|
||||
/// get_after_change_style removes the transition rules from the ComputedValues.
|
||||
/// If there is no transition rule in the ComputedValues, it returns None.
|
||||
#[cfg(feature = "gecko")]
|
||||
fn get_after_change_style(&self,
|
||||
context: &mut StyleContext<Self>,
|
||||
primary_style: &ComputedStyle,
|
||||
pseudo_style: &Option<(&PseudoElement, &ComputedStyle)>)
|
||||
-> Arc<ComputedValues> {
|
||||
-> Option<Arc<ComputedValues>> {
|
||||
let style = &pseudo_style.as_ref().map_or(primary_style, |p| &*p.1);
|
||||
let rule_node = &style.rules;
|
||||
let without_transition_rules =
|
||||
context.shared.stylist.rule_tree.remove_transition_rule_if_applicable(rule_node);
|
||||
if without_transition_rules == *rule_node {
|
||||
// Note that unwrapping here is fine, because the style is
|
||||
// only incomplete during the styling process.
|
||||
return style.values.as_ref().unwrap().clone();
|
||||
// We don't have transition rule in this case, so return None to let the caller
|
||||
// use the original ComputedValues.
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut cascade_flags = CascadeFlags::empty();
|
||||
if self.skip_root_and_item_based_display_fixup() {
|
||||
cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP)
|
||||
cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP);
|
||||
}
|
||||
self.cascade_with_rules(context.shared,
|
||||
&context.thread_local.font_metrics_provider,
|
||||
&without_transition_rules,
|
||||
primary_style,
|
||||
cascade_flags,
|
||||
pseudo_style.is_some())
|
||||
Some(self.cascade_with_rules(context.shared,
|
||||
&context.thread_local.font_metrics_provider,
|
||||
&without_transition_rules,
|
||||
primary_style,
|
||||
cascade_flags,
|
||||
pseudo_style.is_some()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
fn needs_animations_update(&self,
|
||||
old_values: &Option<Arc<ComputedValues>>,
|
||||
new_values: &Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>) -> bool {
|
||||
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 has_animations = self.has_css_animations(pseudo);
|
||||
|
||||
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: 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 &&
|
||||
has_animations)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
|
@ -529,39 +560,63 @@ trait PrivateMatchMethods: TElement {
|
|||
context: &mut StyleContext<Self>,
|
||||
old_values: &mut Option<Arc<ComputedValues>>,
|
||||
new_values: &mut Arc<ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>) {
|
||||
use context::{CSS_ANIMATIONS, EFFECT_PROPERTIES};
|
||||
primary_style: &ComputedStyle,
|
||||
pseudo_style: &Option<(&PseudoElement, &ComputedStyle)>) {
|
||||
use context::{CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES};
|
||||
use context::UpdateAnimationsTasks;
|
||||
|
||||
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 has_animations = self.has_css_animations(pseudo);
|
||||
|
||||
let pseudo = pseudo_style.map(|(p, _)| p);
|
||||
let mut tasks = UpdateAnimationsTasks::empty();
|
||||
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: 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 &&
|
||||
has_animations)
|
||||
});
|
||||
if needs_update_animations {
|
||||
if self.needs_animations_update(old_values, new_values, pseudo) {
|
||||
tasks.insert(CSS_ANIMATIONS);
|
||||
}
|
||||
|
||||
let before_change_style = if self.might_need_transitions_update(&old_values.as_ref(),
|
||||
new_values,
|
||||
pseudo) {
|
||||
let after_change_style = if self.has_css_transitions(pseudo) {
|
||||
self.get_after_change_style(context, primary_style, pseudo_style)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// In order to avoid creating a SequentialTask for transitions which may not be updated,
|
||||
// we check it per property to make sure Gecko side will really update transition.
|
||||
let needs_transitions_update = {
|
||||
// We borrow new_values here, so need to add a scope to make sure we release it
|
||||
// before assigning a new value to it.
|
||||
let after_change_style_ref = match after_change_style {
|
||||
Some(ref value) => value,
|
||||
None => &new_values
|
||||
};
|
||||
self.needs_transitions_update(old_values.as_ref().unwrap(),
|
||||
after_change_style_ref,
|
||||
pseudo)
|
||||
};
|
||||
|
||||
if needs_transitions_update {
|
||||
if let Some(values_without_transitions) = after_change_style {
|
||||
*new_values = values_without_transitions;
|
||||
}
|
||||
tasks.insert(CSS_TRANSITIONS);
|
||||
|
||||
// We need to clone old_values into SequentialTask, so we can use it later.
|
||||
old_values.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.has_animations(pseudo) {
|
||||
tasks.insert(EFFECT_PROPERTIES);
|
||||
}
|
||||
|
||||
if !tasks.is_empty() {
|
||||
let task = ::context::SequentialTask::update_animations(*self,
|
||||
pseudo.cloned(),
|
||||
before_change_style,
|
||||
tasks);
|
||||
context.thread_local.tasks.push(task);
|
||||
}
|
||||
|
@ -572,7 +627,8 @@ trait PrivateMatchMethods: TElement {
|
|||
context: &mut StyleContext<Self>,
|
||||
old_values: &mut Option<Arc<ComputedValues>>,
|
||||
new_values: &mut Arc<ComputedValues>,
|
||||
_pseudo: Option<&PseudoElement>) {
|
||||
_primary_style: &ComputedStyle,
|
||||
_pseudo_style: &Option<(&PseudoElement, &ComputedStyle)>) {
|
||||
let possibly_expired_animations =
|
||||
&mut context.thread_local.current_element_info.as_mut().unwrap()
|
||||
.possibly_expired_animations;
|
||||
|
@ -984,24 +1040,43 @@ pub trait MatchMethods : TElement {
|
|||
}
|
||||
};
|
||||
|
||||
// RESTYLE_CSS_ANIMATIONS is processed prior to other restyle hints
|
||||
// in the name of animation-only traversal. Rest of restyle hints
|
||||
// RESTYLE_CSS_ANIMATIONS or RESTYLE_CSS_TRANSITIONS is processed prior to other
|
||||
// restyle hints in the name of animation-only traversal. Rest of restyle hints
|
||||
// will be processed in a subsequent normal traversal.
|
||||
if hint.contains(RESTYLE_CSS_ANIMATIONS) {
|
||||
if hint.intersects(RestyleHint::for_animations()) {
|
||||
debug_assert!(context.shared.traversal_flags.for_animation_only());
|
||||
|
||||
let animation_rule = self.get_animation_rule(None);
|
||||
replace_rule_node(CascadeLevel::Animations,
|
||||
animation_rule.as_ref(),
|
||||
primary_rules);
|
||||
|
||||
let pseudos = &mut element_styles.pseudos;
|
||||
for pseudo in pseudos.keys().iter().filter(|p| p.is_before_or_after()) {
|
||||
let animation_rule = self.get_animation_rule(Some(&pseudo));
|
||||
let pseudo_rules = &mut pseudos.get_mut(&pseudo).unwrap().rules;
|
||||
replace_rule_node(CascadeLevel::Animations,
|
||||
use data::EagerPseudoStyles;
|
||||
let mut replace_rule_node_for_animation = |level: CascadeLevel,
|
||||
primary_rules: &mut StrongRuleNode,
|
||||
pseudos: &mut EagerPseudoStyles| {
|
||||
let animation_rule = self.get_animation_rule_by_cascade(None, level);
|
||||
replace_rule_node(level,
|
||||
animation_rule.as_ref(),
|
||||
pseudo_rules);
|
||||
primary_rules);
|
||||
|
||||
for pseudo in pseudos.keys().iter().filter(|p| p.is_before_or_after()) {
|
||||
let animation_rule = self.get_animation_rule_by_cascade(Some(&pseudo), level);
|
||||
let pseudo_rules = &mut pseudos.get_mut(&pseudo).unwrap().rules;
|
||||
replace_rule_node(level,
|
||||
animation_rule.as_ref(),
|
||||
pseudo_rules);
|
||||
}
|
||||
};
|
||||
|
||||
// Apply Transition rules and Animation rules if the corresponding restyle hint
|
||||
// is contained.
|
||||
let pseudos = &mut element_styles.pseudos;
|
||||
if hint.contains(RESTYLE_CSS_TRANSITIONS) {
|
||||
replace_rule_node_for_animation(CascadeLevel::Transitions,
|
||||
primary_rules,
|
||||
pseudos);
|
||||
}
|
||||
|
||||
if hint.contains(RESTYLE_CSS_ANIMATIONS) {
|
||||
replace_rule_node_for_animation(CascadeLevel::Animations,
|
||||
primary_rules,
|
||||
pseudos);
|
||||
}
|
||||
} else if hint.contains(RESTYLE_STYLE_ATTRIBUTE) {
|
||||
let style_attribute = self.style_attribute();
|
||||
|
|
|
@ -44,6 +44,7 @@ use gecko_bindings::bindings::ServoComputedValuesBorrowedOrNull;
|
|||
use gecko_bindings::bindings::{Gecko_ResetFilters, Gecko_CopyFiltersFrom};
|
||||
use gecko_bindings::bindings::RawGeckoPresContextBorrowed;
|
||||
use gecko_bindings::structs::{self, StyleComplexColor};
|
||||
use gecko_bindings::structs::nsCSSPropertyID;
|
||||
use gecko_bindings::structs::nsStyleVariables;
|
||||
use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordData, CoordDataMut};
|
||||
use gecko_bindings::sugar::ownership::HasArcFFI;
|
||||
|
@ -1986,6 +1987,11 @@ fn static_assert() {
|
|||
${impl_transition_time_value('duration', 'Duration')}
|
||||
${impl_transition_timing_function()}
|
||||
|
||||
pub fn transition_combined_duration_at(&self, index: usize) -> f32 {
|
||||
// https://drafts.csswg.org/css-transitions/#transition-combined-duration
|
||||
self.gecko.mTransitions[index].mDuration.max(0.0) + self.gecko.mTransitions[index].mDelay
|
||||
}
|
||||
|
||||
pub fn set_transition_property(&mut self, v: longhands::transition_property::computed_value::T) {
|
||||
use gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
||||
|
||||
|
@ -2019,6 +2025,10 @@ fn static_assert() {
|
|||
self.gecko.mTransitions[index].mProperty.into()
|
||||
}
|
||||
|
||||
pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID {
|
||||
self.gecko.mTransitions[index].mProperty
|
||||
}
|
||||
|
||||
pub fn copy_transition_property_from(&mut self, other: &Self) {
|
||||
unsafe { self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len()) };
|
||||
|
||||
|
|
|
@ -66,6 +66,19 @@ impl TransitionProperty {
|
|||
% endfor
|
||||
}
|
||||
|
||||
/// Iterates over every property that is not TransitionProperty::All, stopping and returning
|
||||
/// true when the provided callback returns true for the first time.
|
||||
pub fn any<F: FnMut(TransitionProperty) -> bool>(mut cb: F) -> bool {
|
||||
% for prop in data.longhands:
|
||||
% if prop.animatable:
|
||||
if cb(TransitionProperty::${prop.camel_case}) {
|
||||
return true;
|
||||
}
|
||||
% endif
|
||||
% endfor
|
||||
false
|
||||
}
|
||||
|
||||
/// Parse a transition-property value.
|
||||
pub fn parse(input: &mut Parser) -> Result<Self, ()> {
|
||||
match_ignore_ascii_case! { &try!(input.expect_ident()),
|
||||
|
|
|
@ -48,6 +48,11 @@ bitflags! {
|
|||
/// of their descendants.
|
||||
const RESTYLE_LATER_SIBLINGS = 0x08,
|
||||
|
||||
/// Replace the style data coming from CSS transitions without updating
|
||||
/// any other style data. This hint is only processed in animation-only
|
||||
/// traversal which is prior to normal traversal.
|
||||
const RESTYLE_CSS_TRANSITIONS = 0x10,
|
||||
|
||||
/// Replace the style data coming from CSS animations without updating
|
||||
/// any other style data. This hint is only processed in animation-only
|
||||
/// traversal which is prior to normal traversal.
|
||||
|
@ -87,6 +92,7 @@ pub fn assert_restyle_hints_match() {
|
|||
// (RESTYLE_SELF | RESTYLE_DESCENDANTS).
|
||||
nsRestyleHint_eRestyle_Subtree => RESTYLE_DESCENDANTS,
|
||||
nsRestyleHint_eRestyle_LaterSiblings => RESTYLE_LATER_SIBLINGS,
|
||||
nsRestyleHint_eRestyle_CSSTransitions => RESTYLE_CSS_TRANSITIONS,
|
||||
nsRestyleHint_eRestyle_CSSAnimations => RESTYLE_CSS_ANIMATIONS,
|
||||
nsRestyleHint_eRestyle_StyleAttribute => RESTYLE_STYLE_ATTRIBUTE,
|
||||
}
|
||||
|
@ -96,7 +102,12 @@ impl RestyleHint {
|
|||
/// The subset hints that affect the styling of a single element during the
|
||||
/// traversal.
|
||||
pub fn for_self() -> Self {
|
||||
RESTYLE_SELF | RESTYLE_STYLE_ATTRIBUTE | RESTYLE_CSS_ANIMATIONS
|
||||
RESTYLE_SELF | RESTYLE_STYLE_ATTRIBUTE | RESTYLE_CSS_ANIMATIONS | RESTYLE_CSS_TRANSITIONS
|
||||
}
|
||||
|
||||
/// The subset hints that are used for animation restyle.
|
||||
pub fn for_animations() -> Self {
|
||||
RESTYLE_CSS_ANIMATIONS | RESTYLE_CSS_TRANSITIONS
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1788,12 +1788,15 @@ pub extern "C" fn Servo_NoteExplicitHints(element: RawGeckoElementBorrowed,
|
|||
debug!("Servo_NoteExplicitHints: {:?}, restyle_hint={:?}, change_hint={:?}",
|
||||
element, restyle_hint, change_hint);
|
||||
debug_assert!(restyle_hint == structs::nsRestyleHint_eRestyle_CSSAnimations ||
|
||||
(restyle_hint.0 & structs::nsRestyleHint_eRestyle_CSSAnimations.0) == 0,
|
||||
"eRestyle_CSSAnimations should only appear by itself");
|
||||
restyle_hint == structs::nsRestyleHint_eRestyle_CSSTransitions ||
|
||||
(restyle_hint.0 & (structs::nsRestyleHint_eRestyle_CSSAnimations.0 |
|
||||
structs::nsRestyleHint_eRestyle_CSSTransitions.0)) == 0,
|
||||
"eRestyle_CSSAnimations or eRestyle_CSSTransitions should only appear by itself");
|
||||
|
||||
let mut maybe_data = element.mutate_data();
|
||||
let maybe_restyle_data = maybe_data.as_mut().and_then(|d| unsafe {
|
||||
maybe_restyle(d, element, restyle_hint == structs::nsRestyleHint_eRestyle_CSSAnimations)
|
||||
maybe_restyle(d, element, restyle_hint == structs::nsRestyleHint_eRestyle_CSSAnimations ||
|
||||
restyle_hint == structs::nsRestyleHint_eRestyle_CSSTransitions)
|
||||
});
|
||||
if let Some(restyle_data) = maybe_restyle_data {
|
||||
let restyle_hint: RestyleHint = restyle_hint.into();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue