Bug 1344966 - Process animation-only traversal. r?heycam draft
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Wed, 22 Mar 2017 12:51:08 +0900
changeset 502619 ed7ef5e321168ce4978f094905245d42fe78a4cf
parent 502532 c46324f61a1d4c209e474aac104f8ef069d0689f
child 502620 4832e3537868553b3b00880184bcfa94965256fc
push id50341
push userhikezoe@mozilla.com
push dateWed, 22 Mar 2017 03:55:51 +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 be updated animation style as a result of 3 (second animation-only traversal) MozReview-Commit-ID: DTlUtN0wBXb * * * [mq]: pp MozReview-Commit-ID: CoeRW64X1Zg
servo/components/style/parallel.rs
servo/components/style/sequential.rs
servo/components/style/traversal.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/parallel.rs
+++ b/servo/components/style/parallel.rs
@@ -68,17 +68,17 @@ pub fn traverse_dom<E, D>(traversal: &D,
     let traversal_data = PerLevelTraversalData {
         current_dom_depth: depth,
     };
     let tls = ScopedTLS::<D::ThreadLocalContext>::new(queue);
     let root = root.as_node().opaque();
 
     queue.install(|| {
         rayon::scope(|scope| {
-            traverse_nodes(nodes, root, traversal_data, scope, traversal, &tls);
+            traverse_nodes(nodes, root, traversal_data, scope, traversal, &tls, token.traverse_animation_only());
         });
     });
 
     // Dump statistics to stdout if requested.
     if dump_stats {
         let slots = unsafe { tls.unsafe_get() };
         let mut aggregate = slots.iter().fold(TraversalStatistics::default(), |acc, t| {
             match *t.borrow() {
@@ -94,33 +94,34 @@ pub fn traverse_dom<E, D>(traversal: &D,
 /// A parallel top-down DOM traversal.
 #[inline(always)]
 #[allow(unsafe_code)]
 fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode<E::ConcreteNode>],
                                   root: OpaqueNode,
                                   mut traversal_data: PerLevelTraversalData,
                                   scope: &'a rayon::Scope<'scope>,
                                   traversal: &'scope D,
-                                  tls: &'scope ScopedTLS<'scope, D::ThreadLocalContext>)
+                                  tls: &'scope ScopedTLS<'scope, D::ThreadLocalContext>,
+                                  animation_only: bool)
     where E: TElement + 'scope,
           D: DomTraversal<E>,
 {
     let mut discovered_child_nodes = vec![];
     {
         // Scope the borrow of the TLS so that the borrow is dropped before
         // potentially traversing a child on this thread.
         let mut tlc = tls.ensure(|| traversal.create_thread_local_context());
 
         for n in nodes {
             // Perform the appropriate traversal.
             let node = **n;
             let mut children_to_process = 0isize;
             traversal.process_preorder(&mut traversal_data, &mut *tlc, node);
             if let Some(el) = node.as_element() {
-                traversal.traverse_children(&mut *tlc, el, |_tlc, kid| {
+                traversal.traverse_children(&mut *tlc, el, animation_only, |_tlc, kid| {
                     children_to_process += 1;
                     discovered_child_nodes.push(unsafe { SendNode::new(kid) })
                 });
             }
 
             // Reset the count of children if we need to do a bottom-up traversal
             // after the top up.
             if D::needs_postorder_traversal() {
@@ -135,46 +136,47 @@ fn top_down_dom<'a, 'scope, E, D>(nodes:
             }
         }
     }
 
     if let Some(ref mut depth) = traversal_data.current_dom_depth {
         *depth += 1;
     }
 
-    traverse_nodes(discovered_child_nodes, root, traversal_data, scope, traversal, tls);
+    traverse_nodes(discovered_child_nodes, root, traversal_data, scope, traversal, tls, animation_only);
 }
 
 fn traverse_nodes<'a, 'scope, E, D>(nodes: Vec<SendNode<E::ConcreteNode>>, root: OpaqueNode,
                                     traversal_data: PerLevelTraversalData,
                                     scope: &'a rayon::Scope<'scope>,
                                     traversal: &'scope D,
-                                    tls: &'scope ScopedTLS<'scope, D::ThreadLocalContext>)
+                                    tls: &'scope ScopedTLS<'scope, D::ThreadLocalContext>,
+                                    animation_only: bool)
     where E: TElement + 'scope,
           D: DomTraversal<E>,
 {
     if nodes.is_empty() {
         return;
     }
 
     // Optimization: traverse directly and avoid a heap-allocating spawn() call if
     // we're only pushing one work unit.
     if nodes.len() <= CHUNK_SIZE {
         let nodes = nodes.into_boxed_slice();
-        top_down_dom(&nodes, root, traversal_data, scope, traversal, tls);
+        top_down_dom(&nodes, root, traversal_data, scope, traversal, tls, animation_only);
         return;
     }
 
     // General case.
     for chunk in nodes.chunks(CHUNK_SIZE) {
         let nodes = chunk.iter().cloned().collect::<Vec<_>>().into_boxed_slice();
         let traversal_data = traversal_data.clone();
         scope.spawn(move |scope| {
             let nodes = nodes;
-            top_down_dom(&nodes, root, traversal_data, scope, traversal, tls)
+            top_down_dom(&nodes, root, traversal_data, scope, traversal, tls, animation_only)
         })
     }
 }
 
 /// Process current node and potentially traverse its ancestors.
 ///
 /// If we are the last child that finished processing, recursively process
 /// our parent. Else, stop. Also, stop at the root.
--- a/servo/components/style/sequential.rs
+++ b/servo/components/style/sequential.rs
@@ -20,29 +20,32 @@ pub fn traverse_dom<E, D>(traversal: &D,
           D: DomTraversal<E>,
 {
     let dump_stats = TraversalStatistics::should_dump();
     let start_time = if dump_stats { Some(time::precise_time_s()) } else { None };
 
     debug_assert!(!traversal.is_parallel());
     debug_assert!(token.should_traverse());
 
+    let animation_only = token.traverse_animation_only();
+
     fn doit<E, D>(traversal: &D, traversal_data: &mut PerLevelTraversalData,
-                  thread_local: &mut D::ThreadLocalContext, node: E::ConcreteNode)
+                  thread_local: &mut D::ThreadLocalContext, node: E::ConcreteNode,
+                  animation_only: bool)
         where E: TElement,
               D: DomTraversal<E>
     {
         traversal.process_preorder(traversal_data, thread_local, node);
         if let Some(el) = node.as_element() {
             if let Some(ref mut depth) = traversal_data.current_dom_depth {
                 *depth += 1;
             }
 
-            traversal.traverse_children(thread_local, el, |tlc, kid| {
-                doit(traversal, traversal_data, tlc, kid)
+            traversal.traverse_children(thread_local, el, animation_only, |tlc, kid| {
+                doit(traversal, traversal_data, tlc, kid, animation_only)
             });
 
             if let Some(ref mut depth) = traversal_data.current_dom_depth {
                 *depth -= 1;
             }
         }
 
         if D::needs_postorder_traversal() {
@@ -53,21 +56,21 @@ pub fn traverse_dom<E, D>(traversal: &D,
     let mut traversal_data = PerLevelTraversalData {
         current_dom_depth: None,
     };
 
     let mut tlc = traversal.create_thread_local_context();
     if token.traverse_unstyled_children_only() {
         for kid in root.as_node().children() {
             if kid.as_element().map_or(false, |el| el.get_data().is_none()) {
-                doit(traversal, &mut traversal_data, &mut tlc, kid);
+                doit(traversal, &mut traversal_data, &mut tlc, kid, animation_only);
             }
         }
     } else {
-        doit(traversal, &mut traversal_data, &mut tlc, root.as_node());
+        doit(traversal, &mut traversal_data, &mut tlc, root.as_node(), animation_only);
     }
 
     // Dump statistics to stdout if requested.
     if dump_stats {
         let tlsc = tlc.borrow_mut();
         tlsc.statistics.finish(traversal, start_time.unwrap());
         println!("{}", tlsc.statistics);
     }
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -137,40 +137,46 @@ 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(), root.has_animating_descendants()),
             unstyled_children_only: false,
             animation_only: root.has_animating_descendants(),
         }
     }
 
     /// 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) => {
+                // If the traversal is animation-only and the element has
+                // animating descendants bit, we need to traverse it.
+                if animation_only && el.has_animating_descendants() {
+                    return true;
+                }
+
                 // 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.
@@ -260,30 +266,34 @@ pub trait DomTraversal<E: TElement> : Sy
         }
 
         return true;
 
     }
 
     /// Helper for the traversal implementations to select the children that
     /// should be enqueued for processing.
-    fn traverse_children<F>(&self, thread_local: &mut Self::ThreadLocalContext, parent: E, mut f: F)
+    fn traverse_children<F>(&self, thread_local:
+                            &mut Self::ThreadLocalContext,
+                            parent: E,
+                            animation_only: bool,
+                            mut f: F)
         where F: FnMut(&mut Self::ThreadLocalContext, E::ConcreteNode)
     {
         // Check if we're allowed to traverse past this element.
         let should_traverse =
             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, animation_only) {
                 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);
             }
@@ -451,31 +461,45 @@ 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() {
+                // 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) {
+       (element.has_dirty_descendants() || element.has_animating_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_animating_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
@@ -188,16 +188,22 @@ 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);
+
+    if element.has_animating_descendants() {
+        traverse_subtree(element, raw_data,
+                         behavior == structs::TraversalRootBehavior::UnstyledChildrenOnly);
+    }
+
     traverse_subtree(element, raw_data,
                      behavior == structs::TraversalRootBehavior::UnstyledChildrenOnly);
 
     element.has_dirty_descendants() || element.mutate_data().unwrap().has_restyle()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_AnimationValues_Interpolate(from: RawServoAnimationValueBorrowed,
@@ -1608,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_animating_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);