Bug 1344966 - Process animation-only traversal. r?heycam draft
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Sun, 26 Mar 2017 09:12:35 +0900
changeset 551381 565d39059401a9435e3049e3d96f0319496c841e
parent 551281 49cc48fe9978bc4759742ef68784cb7e107cb47c
child 551382 8865c6e7e459a9a3de7b7bc279460dd1e5f9f36f
push id51042
push userhikezoe@mozilla.com
push dateSun, 26 Mar 2017 00:16:20 +0000
reviewersheycam
bugs1344966
milestone55.0a1
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
servo/components/style/traversal.rs
servo/ports/geckolib/glue.rs
--- 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);