Bug 1344966 - Process animation-only traversal. r?heycam
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)
MozReview-Commit-ID: DTlUtN0wBXb
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -152,39 +152,55 @@ pub trait DomTraversal<E: TElement> : Sy
if let Some(mut data) = root.mutate_data() {
if let Some(r) = data.get_restyle_mut() {
debug_assert!(root.next_sibling_element().is_none());
let _later_siblings = r.compute_final_hint(root, stylist);
}
}
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,
}
}
/// Returns true if traversal should visit a text node. The style system never
/// processes text nodes, but Servo overrides this to visit them for flow
/// construction when necessary.
fn text_node_needs_traversal(node: E::ConcreteNode) -> bool {
debug_assert!(node.is_text_node());
false
}
/// 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.
if cfg!(feature = "servo") && opts::get().nonincremental_layout {
return true;
}
match node.as_element() {
None => Self::text_node_needs_traversal(node),
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
// matter what. Skip examining the ElementData.
if el.has_dirty_descendants() {
return true;
}
// Check the element data. If it doesn't exist, we need to visit
// the element.
@@ -287,17 +303,17 @@ pub trait DomTraversal<E: TElement> : Sy
self.should_traverse_children(thread_local.borrow_mut(), parent,
&parent.borrow_data().unwrap(), MayLog);
thread_local.borrow_mut().end_element(parent);
if !should_traverse {
return;
}
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();
if el.as_ref().and_then(|el| el.borrow_data())
.map_or(false, |d| d.has_styles())
{
unsafe { parent.set_dirty_descendants(); }
}
f(thread_local, kid);
}
@@ -465,31 +481,48 @@ pub fn recalc_style_at<E, D>(traversal:
// Now that matching and cascading is done, clear the bits corresponding to
// those operations and compute the propagated restyle hint.
let empty_hint = StoredRestyleHint::empty();
let propagated_hint = match data.get_restyle_mut() {
None => empty_hint,
Some(r) => {
r.recascade = false;
- mem::replace(&mut r.hint, empty_hint).propagate()
+ 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()
+ }
},
};
- 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);
// Preprocess children, propagating restyle hints and handling sibling relationships.
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| {
r.damage_handled() | r.damage.handled_for_descendants()
});
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
// display:none subtree, even if the style didn't change (since, if
// the style did change, we'd have already cleared it above).
//
// This keeps the tree in a valid state without requiring the DOM to
// check display:none on the parent when inserting new children (which
// can be moderately expensive). Instead, DOM implementations can
// unconditionally set the dirty descendants bit on any styled parent,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -191,18 +191,30 @@ fn traverse_subtree(element: GeckoElemen
/// Gecko post-traversal (to perform lazy frame construction, or consume any
/// RestyleData, or drop any ElementData) is required.
#[no_mangle]
pub extern "C" fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
raw_data: RawServoStyleSetBorrowed,
behavior: structs::TraversalRootBehavior) -> bool {
let element = GeckoElement(root);
debug!("Servo_TraverseSubtree: {:?}", element);
- traverse_subtree(element, raw_data,
- behavior == structs::TraversalRootBehavior::UnstyledChildrenOnly);
+
+ let mut traversal_flags = TraversalFlags::empty();
+ if behavior == structs::TraversalRootBehavior::UnstyledChildrenOnly {
+ traversal_flags.insert(UNSTYLED_CHILDREN_ONLY);
+ }
+
+ if element.has_animation_only_dirty_descendants() ||
+ element.has_animation_restyle_hints() {
+ traversal_flags.insert(ANIMATION_ONLY);
+ traverse_subtree(element, raw_data, &traversal_flags);
+ traversal_flags.remove(ANIMATION_ONLY);
+ }
+
+ traverse_subtree(element, raw_data, &traversal_flags);
element.has_dirty_descendants() || element.mutate_data().unwrap().has_restyle()
}
#[no_mangle]
pub extern "C" fn Servo_AnimationValues_Interpolate(from: RawServoAnimationValueBorrowed,
to: RawServoAnimationValueBorrowed,
progress: f64)
@@ -1602,17 +1614,17 @@ pub extern "C" fn Servo_GetComputedKeyfr
#[no_mangle]
pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) {
if !cfg!(debug_assertions) {
panic!("Calling Servo_AssertTreeIsClean in release build");
}
let root = GeckoElement(root);
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() {
if let Some(child) = child.as_element() {
assert_subtree_is_clean(child);
}
}
}
assert_subtree_is_clean(root);