Process animation-only traversal.

All traversal processes are like this:

1. Traverse only elements that have this restyle hint (animation-only traversal)
   RESTYLE_CSS_ANIMATIONS is stripped off from restyle hints of the elements
2. Traverse all dirty elements (normal traversal)
3. Create a SequentialTask if we have updated CSS Animations properties in
   the normal traversal
4. Traverse elements that need to have updated animation style as a result of 3
   (second animation-only traversal)
This commit is contained in:
Hiroyuki Ikezoe 2017-03-26 18:47:34 +09:00
parent be332fe5a4
commit e4fe416c9d
2 changed files with 48 additions and 8 deletions

View file

@ -134,6 +134,8 @@ pub trait DomTraversal<E: TElement> : Sync {
/// The traversal_flag is used in Gecko. /// The traversal_flag is used in Gecko.
/// If traversal_flag::UNSTYLED_CHILDREN_ONLY is specified, style newly- /// If traversal_flag::UNSTYLED_CHILDREN_ONLY is specified, style newly-
/// appended children without restyling the parent. /// appended children without restyling the parent.
/// If traversal_flag::ANIMATION_ONLY is specified, style only elements for
/// animations.
fn pre_traverse(root: E, stylist: &Stylist, traversal_flags: TraversalFlags) fn pre_traverse(root: E, stylist: &Stylist, traversal_flags: TraversalFlags)
-> PreTraverseToken -> PreTraverseToken
{ {
@ -158,7 +160,7 @@ pub trait DomTraversal<E: TElement> : Sync {
} }
PreTraverseToken { PreTraverseToken {
traverse: Self::node_needs_traversal(root.as_node()), traverse: Self::node_needs_traversal(root.as_node(), traversal_flags.for_animation_only()),
unstyled_children_only: false, unstyled_children_only: false,
} }
} }
@ -172,7 +174,7 @@ pub trait DomTraversal<E: TElement> : Sync {
} }
/// Returns true if traversal is needed for the given node and subtree. /// Returns true if traversal is needed for the given node and subtree.
fn node_needs_traversal(node: E::ConcreteNode) -> bool { fn node_needs_traversal(node: E::ConcreteNode, animation_only: bool) -> bool {
// Non-incremental layout visits every node. // Non-incremental layout visits every node.
if cfg!(feature = "servo") && opts::get().nonincremental_layout { if cfg!(feature = "servo") && opts::get().nonincremental_layout {
return true; return true;
@ -181,6 +183,22 @@ pub trait DomTraversal<E: TElement> : Sync {
match node.as_element() { match node.as_element() {
None => Self::text_node_needs_traversal(node), None => Self::text_node_needs_traversal(node),
Some(el) => { Some(el) => {
// In case of animation-only traversal we need to traverse
// the element if the element has animation only dirty
// descendants bit, animation-only restyle hint or recascade.
if animation_only {
if el.has_animation_only_dirty_descendants() {
return true;
}
let data = match el.borrow_data() {
Some(d) => d,
None => return false,
};
return data.get_restyle()
.map_or(false, |r| r.hint.has_animation_hint() || r.recascade);
}
// If the dirty descendants bit is set, we need to traverse no // If the dirty descendants bit is set, we need to traverse no
// matter what. Skip examining the ElementData. // matter what. Skip examining the ElementData.
if el.has_dirty_descendants() { if el.has_dirty_descendants() {
@ -293,7 +311,7 @@ pub trait DomTraversal<E: TElement> : Sync {
} }
for kid in parent.as_node().children() { for kid in parent.as_node().children() {
if Self::node_needs_traversal(kid) { if Self::node_needs_traversal(kid, self.shared_context().animation_only_restyle) {
let el = kid.as_element(); let el = kid.as_element();
if el.as_ref().and_then(|el| el.borrow_data()) if el.as_ref().and_then(|el| el.borrow_data())
.map_or(false, |d| d.has_styles()) .map_or(false, |d| d.has_styles())
@ -471,21 +489,38 @@ pub fn recalc_style_at<E, D>(traversal: &D,
None => empty_hint, None => empty_hint,
Some(r) => { Some(r) => {
r.recascade = false; r.recascade = false;
if r.hint.has_animation_hint() {
debug_assert!(context.shared.animation_only_restyle,
"animation restyle hint should be handled during animation-only restyles");
// Drop animation restyle hint.
let propagated_hint = r.hint.propagate();
r.hint.remove_animation_hint();
propagated_hint
} else {
mem::replace(&mut r.hint, empty_hint).propagate() mem::replace(&mut r.hint, empty_hint).propagate()
}
}, },
}; };
debug_assert!(data.has_current_styles()); debug_assert!(data.has_current_styles() || context.shared.animation_only_restyle,
"Should have computed style or haven't yet valid computed style in case of animation-only restyle");
trace!("propagated_hint={:?}, inherited_style_changed={:?}", propagated_hint, inherited_style_changed); trace!("propagated_hint={:?}, inherited_style_changed={:?}", propagated_hint, inherited_style_changed);
// Preprocess children, propagating restyle hints and handling sibling relationships. // Preprocess children, propagating restyle hints and handling sibling relationships.
if traversal.should_traverse_children(&mut context.thread_local, element, &data, DontLog) && if traversal.should_traverse_children(&mut context.thread_local, element, &data, DontLog) &&
(element.has_dirty_descendants() || !propagated_hint.is_empty() || inherited_style_changed) { ((!context.shared.animation_only_restyle && element.has_dirty_descendants()) ||
(context.shared.animation_only_restyle && element.has_animation_only_dirty_descendants()) ||
!propagated_hint.is_empty() ||
inherited_style_changed) {
let damage_handled = data.get_restyle().map_or(RestyleDamage::empty(), |r| { let damage_handled = data.get_restyle().map_or(RestyleDamage::empty(), |r| {
r.damage_handled() | r.damage.handled_for_descendants() r.damage_handled() | r.damage.handled_for_descendants()
}); });
preprocess_children(traversal, element, propagated_hint, damage_handled, inherited_style_changed); preprocess_children(traversal, element, propagated_hint, damage_handled, inherited_style_changed);
} }
if context.shared.animation_only_restyle {
unsafe { element.unset_animation_only_dirty_descendants(); }
}
// Make sure the dirty descendants bit is not set for the root of a // Make sure the dirty descendants bit is not set for the root of a
// display:none subtree, even if the style didn't change (since, if // display:none subtree, even if the style didn't change (since, if
// the style did change, we'd have already cleared it above). // the style did change, we'd have already cleared it above).

View file

@ -83,8 +83,8 @@ use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
use style::supports::parse_condition_or_declaration; use style::supports::parse_condition_or_declaration;
use style::thread_state; use style::thread_state;
use style::timer::Timer; use style::timer::Timer;
use style::traversal::{ANIMATION_ONLY, UNSTYLED_CHILDREN_ONLY};
use style::traversal::{resolve_style, DomTraversal, TraversalDriver, TraversalFlags}; use style::traversal::{resolve_style, DomTraversal, TraversalDriver, TraversalFlags};
use style::traversal::UNSTYLED_CHILDREN_ONLY;
use style_traits::ToCss; use style_traits::ToCss;
use super::stylesheet_loader::StylesheetLoader; use super::stylesheet_loader::StylesheetLoader;
@ -202,6 +202,11 @@ pub extern "C" fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
_ => TraversalFlags::empty(), _ => TraversalFlags::empty(),
}; };
if element.has_animation_only_dirty_descendants() ||
element.has_animation_restyle_hints() {
traverse_subtree(element, raw_data, traversal_flags | ANIMATION_ONLY);
}
traverse_subtree(element, raw_data, traversal_flags); traverse_subtree(element, raw_data, traversal_flags);
element.has_dirty_descendants() || element.mutate_data().unwrap().has_restyle() element.has_dirty_descendants() || element.mutate_data().unwrap().has_restyle()
@ -1612,7 +1617,7 @@ pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) {
let root = GeckoElement(root); let root = GeckoElement(root);
fn assert_subtree_is_clean<'le>(el: GeckoElement<'le>) { fn assert_subtree_is_clean<'le>(el: GeckoElement<'le>) {
debug_assert!(!el.has_dirty_descendants()); debug_assert!(!el.has_dirty_descendants() && !el.has_animation_only_dirty_descendants());
for child in el.as_node().children() { for child in el.as_node().children() {
if let Some(child) = child.as_element() { if let Some(child) = child.as_element() {
assert_subtree_is_clean(child); assert_subtree_is_clean(child);