Bug 1334036 - Part 12: Trigger animation-only restyle when we handle an event with coordinates. draft
authorBoris Chiou <boris.chiou@gmail.com>
Wed, 17 May 2017 16:49:56 +0800
changeset 579539 2ee2a064f6e6cec8b87fb16d90a475d2e83186f4
parent 579536 cce85f574e058cc1798337ab199cc07079629bdd
child 579540 ef346a6212649e352b2ab572422c0eb02c80fca4
push id59276
push userbmo:boris.chiou@gmail.com
push dateWed, 17 May 2017 11:39:57 +0000
bugs1334036
milestone55.0a1
Bug 1334036 - Part 12: Trigger animation-only restyle when we handle an event with coordinates. We need to request an animation-only restyle to force flush all throttled animations on main thread when we handle an event with coordinates (e.g. mouse event). MozReview-Commit-ID: KkjeQVsLgTl
dom/animation/EffectCompositor.cpp
dom/animation/EffectCompositor.h
layout/base/PresShell.cpp
layout/base/ServoRestyleManager.cpp
layout/base/ServoRestyleManager.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
layout/style/ServoTypes.h
servo/ports/geckolib/glue.rs
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -951,41 +951,46 @@ EffectCompositor::SetPerformanceWarning(
   }
 
   for (KeyframeEffectReadOnly* effect : *effects) {
     effect->SetPerformanceWarning(aProperty, aWarning);
   }
 }
 
 bool
-EffectCompositor::PreTraverse()
+EffectCompositor::PreTraverse(bool aFlushThrottledAnimations)
 {
-  return PreTraverseInSubtree(nullptr);
+  return PreTraverseInSubtree(nullptr, aFlushThrottledAnimations);
 }
 
 bool
-EffectCompositor::PreTraverseInSubtree(Element* aRoot)
+EffectCompositor::PreTraverseInSubtree(Element* aRoot,
+                                       bool aFlushThrottledAnimations)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mPresContext->RestyleManager()->IsServo());
 
   AutoRestore<bool> guard(mIsInPreTraverse);
   mIsInPreTraverse = true;
 
   bool foundElementsNeedingRestyle = false;
 
   nsTArray<NonOwningAnimationTarget> elementsWithCascadeUpdates;
   for (size_t i = 0; i < kCascadeLevelCount; ++i) {
     CascadeLevel cascadeLevel = CascadeLevel(i);
     auto& elementSet = mElementsToRestyle[cascadeLevel];
     for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
       const NonOwningAnimationTarget& target = iter.Key();
 
-      // Ignore throttled restyle.
-      if (!iter.Data() && !target.mElement->HasDirtyDescendantsForServo()) {
+      // When there are non-animation restyles *OR* |aFlushThrottledAniamtions|
+      // is set (e.g. handle an event with coordinates), we should restyle
+      // all elements with animations; otherwise, ignore throttled restyle.
+      if (!iter.Data() &&
+          !target.mElement->HasDirtyDescendantsForServo() &&
+          !aFlushThrottledAnimations) {
         continue;
       }
 
       // Ignore restyles that aren't in the flattened tree subtree rooted at
       // aRoot.
       if (aRoot &&
           !nsContentUtils::ContentIsFlattenedTreeDescendantOf(target.mElement,
                                                               aRoot)) {
@@ -1011,18 +1016,22 @@ EffectCompositor::PreTraverseInSubtree(E
   elementsWithCascadeUpdates.Clear();
 
   for (size_t i = 0; i < kCascadeLevelCount; ++i) {
     CascadeLevel cascadeLevel = CascadeLevel(i);
     auto& elementSet = mElementsToRestyle[cascadeLevel];
     for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
       NonOwningAnimationTarget target = iter.Key();
 
-      // Ignore throttled restyle.
-      if (!iter.Data() && !target.mElement->HasDirtyDescendantsForServo()) {
+      // When there are non-animation restyles *OR* |aFlushThrottledAniamtions|
+      // is set (e.g. we handle event with coordinates), we should restyle
+      // all elements with animations; otherwise, ignore throttled restyle.
+      if (!iter.Data() &&
+          !target.mElement->HasDirtyDescendantsForServo() &&
+          !aFlushThrottledAnimations) {
         continue;
       }
 
       // Ignore restyles that aren't in the flattened tree subtree rooted at
       // aRoot.
       if (aRoot &&
           !nsContentUtils::ContentIsFlattenedTreeDescendantOf(target.mElement,
                                                               aRoot)) {
@@ -1057,17 +1066,18 @@ EffectCompositor::PreTraverseInSubtree(E
       // about to restyle it.
       iter.Remove();
     }
   }
   return foundElementsNeedingRestyle;
 }
 
 bool
-EffectCompositor::PreTraverse(dom::Element* aElement, nsIAtom* aPseudoTagOrNull)
+EffectCompositor::PreTraverse(dom::Element* aElement,
+                              nsIAtom* aPseudoTagOrNull)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mPresContext->RestyleManager()->IsServo());
 
   bool found = false;
   if (aPseudoTagOrNull &&
       aPseudoTagOrNull != nsCSSPseudoElements::before &&
       aPseudoTagOrNull != nsCSSPseudoElements::after) {
--- a/dom/animation/EffectCompositor.h
+++ b/dom/animation/EffectCompositor.h
@@ -225,25 +225,28 @@ public:
   static void SetPerformanceWarning(
     const nsIFrame* aFrame,
     nsCSSPropertyID aProperty,
     const AnimationPerformanceWarning& aWarning);
 
   // Do a bunch of stuff that we should avoid doing during the parallel
   // traversal (e.g. changing member variables) for all elements that we expect
   // to restyle on the next traversal.
+  // When |aFlushThrottledAnimations| is true, we force to restyle throttled
+  // animations.
   // Returns true if there are elements needing a restyle for animation.
-  bool PreTraverse();
+  bool PreTraverse(bool aFlushThrottledAnimations = false);
 
   // Similar to the above but only for the (pseudo-)element.
   bool PreTraverse(dom::Element* aElement, nsIAtom* aPseudoTagOrNull);
 
   // Similar to the above but for all elements in the subtree rooted
   // at aElement.
-  bool PreTraverseInSubtree(dom::Element* aElement);
+  bool PreTraverseInSubtree(dom::Element* aElement,
+                            bool aFlushThrottledAnimations = false);
 
 private:
   ~EffectCompositor() = default;
 
   // Rebuilds the animation rule corresponding to |aCascadeLevel| on the
   // EffectSet associated with the specified (pseudo-)element.
   static void ComposeAnimationRule(dom::Element* aElement,
                                    CSSPseudoElementType aPseudoType,
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -6942,18 +6942,19 @@ nsIFrame* GetNearestFrameContainingPresS
 static bool
 FlushThrottledStyles(nsIDocument *aDocument, void *aData)
 {
   nsIPresShell* shell = aDocument->GetShell();
   if (shell && shell->IsVisible()) {
     nsPresContext* presContext = shell->GetPresContext();
     if (presContext) {
       if (presContext->RestyleManager()->IsGecko()) {
-        // XXX stylo: ServoRestyleManager doesn't support animations yet.
         presContext->RestyleManager()->AsGecko()->UpdateOnlyAnimationStyles();
+      } else {
+        presContext->RestyleManager()->AsServo()->FlushPendingAnimationStyles();
       }
     }
   }
 
   aDocument->EnumerateSubDocuments(FlushThrottledStyles, nullptr);
   return true;
 }
 
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -662,9 +662,23 @@ ServoRestyleManager::AttributeChanged(El
 
 nsresult
 ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
   NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
   return NS_OK;
 }
 
+void
+ServoRestyleManager::FlushPendingAnimationStyles()
+{
+  // Bug 1302948: We also need to implement this for SMIL.
+  bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
+  if (!doCSS) {
+    return;
+  }
+
+  mInStyleRefresh = true;
+  StyleSet()->StyleDocumentForAnimationOnly();
+  mInStyleRefresh = false;
+}
+
 } // namespace mozilla
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -69,16 +69,18 @@ public:
                            const nsAttrValue* aNewValue);
 
   void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
                         nsIAtom* aAttribute, int32_t aModType,
                         const nsAttrValue* aOldValue);
 
   nsresult ReparentStyleContext(nsIFrame* aFrame);
 
+  void FlushPendingAnimationStyles();
+
   /**
    * Gets the appropriate frame given a content and a pseudo-element tag.
    *
    * Right now only supports a null tag, before or after. If the pseudo-element
    * is not null, the content needs to be an element.
    */
   static nsIFrame* FrameForPseudoElement(const nsIContent* aContent,
                                          nsIAtom* aPseudoTagOrNull);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -264,31 +264,33 @@ ServoStyleSet::PreTraverseSync()
   // it so force computation early.
   mPresContext->Document()->GetDocumentState();
 
   // Ensure that the @font-face data is not stale
   mPresContext->Document()->GetUserFontSet();
 }
 
 void
-ServoStyleSet::PreTraverse(Element* aRoot)
+ServoStyleSet::PreTraverse(Element* aRoot,
+                           bool aFlushThrottledAnimation)
 {
   PreTraverseSync();
 
   // Process animation stuff that we should avoid doing during the parallel
   // traversal.
   nsSMILAnimationController* smilController =
     mPresContext->Document()->GetAnimationController();
   if (aRoot) {
-    mPresContext->EffectCompositor()->PreTraverseInSubtree(aRoot);
+    mPresContext->EffectCompositor()
+                ->PreTraverseInSubtree(aRoot, aFlushThrottledAnimation);
     if (smilController) {
       smilController->PreTraverseInSubtree(aRoot);
     }
   } else {
-    mPresContext->EffectCompositor()->PreTraverse();
+    mPresContext->EffectCompositor()->PreTraverse(aFlushThrottledAnimation);
     if (smilController) {
       smilController->PreTraverse();
     }
   }
 }
 
 bool
 ServoStyleSet::PrepareAndTraverseSubtree(
@@ -304,19 +306,28 @@ ServoStyleSet::PrepareAndTraverseSubtree
   MOZ_ASSERT(!mStylistMayNeedRebuild);
   AutoSetInServoTraversal guard(this);
 
   const SnapshotTable& snapshots = Snapshots();
 
   bool isInitial = !aRoot->HasServoData();
   bool forReconstruct =
     aRestyleBehavior == TraversalRestyleBehavior::ForReconstruct;
+  bool forAnimationOnly =
+    aRestyleBehavior == TraversalRestyleBehavior::ForAnimationOnly;
   bool postTraversalRequired = Servo_TraverseSubtree(
     aRoot, mRawSet.get(), &snapshots, aRootBehavior, aRestyleBehavior);
-  MOZ_ASSERT_IF(isInitial || forReconstruct, !postTraversalRequired);
+  MOZ_ASSERT_IF(isInitial || forReconstruct || forAnimationOnly,
+                !postTraversalRequired);
+
+  // Don't need to trigger a second traversal if this restyle only needs
+  // animation-only restyle.
+  if (forAnimationOnly) {
+    return false;
+  }
 
   auto root = const_cast<Element*>(aRoot);
 
   // If there are still animation restyles needed, trigger a second traversal to
   // update CSS animations or transitions' styles.
   //
   // We don't need to do this for SMIL since SMIL only updates its animation
   // values once at the begin of a tick. As a result, even if the previous
@@ -846,16 +857,31 @@ ServoStyleSet::StyleDocument()
                                   TraversalRestyleBehavior::Normal)) {
       postTraversalRequired = true;
     }
   }
   return postTraversalRequired;
 }
 
 void
+ServoStyleSet::StyleDocumentForAnimationOnly()
+{
+  PreTraverse(nullptr, true);
+
+  DocumentStyleRootIterator iter(mPresContext->Document());
+  while (Element* root = iter.GetNextStyleRoot()) {
+    DebugOnly<bool> PostTraversalRequired =
+      PrepareAndTraverseSubtree(root,
+                                TraversalRootBehavior::Normal,
+                                TraversalRestyleBehavior::ForAnimationOnly);
+    MOZ_ASSERT(!PostTraversalRequired);
+  }
+}
+
+void
 ServoStyleSet::StyleNewSubtree(Element* aRoot)
 {
   MOZ_ASSERT(!aRoot->HasServoData());
 
   PreTraverse();
 
   DebugOnly<bool> postTraversalRequired =
     PrepareAndTraverseSubtree(aRoot,
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -243,16 +243,25 @@ public:
    * This will traverse all of the document's style roots (that is, its document
    * element, and the roots of the document-level native anonymous content).
    *
    * Returns true if a post-traversal is required.
    */
   bool StyleDocument();
 
   /**
+   * Performs a Servo animation-only traversal to compute style for all nodes
+   * with the animation-only dirty bit in the document.
+   *
+   * This will traverse all of the document's style roots (that is, its document
+   * element, and the roots of the document-level native anonymous content).
+   */
+  void StyleDocumentForAnimationOnly();
+
+  /**
    * Eagerly styles a subtree of unstyled nodes that was just appended to the
    * tree. This is used in situations where we need the style immediately and
    * cannot wait for a future batch restyle.
    */
   void StyleNewSubtree(dom::Element* aRoot);
 
   /**
    * Like the above, but skips the root node, and only styles unstyled children.
@@ -409,17 +418,18 @@ private:
   void ClearNonInheritingStyleContexts();
 
   /**
    * Perform processes that we should do before traversing.
    *
    * When aRoot is null, the entire document is pre-traversed.  Otherwise,
    * only the subtree rooted at aRoot is pre-traversed.
    */
-  void PreTraverse(dom::Element* aRoot = nullptr);
+  void PreTraverse(dom::Element* aRoot = nullptr,
+                   bool aFlushThrottledAnimation = false);
   // Subset of the pre-traverse steps that involve syncing up data
   void PreTraverseSync();
 
   /**
    * Rebuild the stylist.  This should only be called if mStylistMayNeedRebuild
    * is true.
    */
   void RebuildStylist();
--- a/layout/style/ServoTypes.h
+++ b/layout/style/ServoTypes.h
@@ -49,23 +49,25 @@ enum class LazyComputeBehavior {
 // whether it should only process unstyled children of the root and their
 // descendants.
 enum class TraversalRootBehavior {
   Normal,
   UnstyledChildrenOnly,
 };
 
 // Indicates whether the Servo style system should perform normal processing or
+// whether it should perform only animation-only processing, or
 // whether it should traverse in a mode that doesn't generate any change hints,
 // which is what's required when handling frame reconstruction.  The change
 // hints in this case are unneeded, since the old frames have already been
 // destroyed.
 enum class TraversalRestyleBehavior {
   Normal,
   ForReconstruct,
+  ForAnimationOnly,
 };
 
 // Represents which tasks are performed in a SequentialTask of UpdateAnimations.
 enum class UpdateAnimationsTasks : uint8_t {
   CSSAnimations    = 1 << 0,
   CSSTransitions   = 1 << 1,
   EffectProperties = 1 << 2,
   CascadeResults   = 1 << 3,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -240,31 +240,40 @@ pub extern "C" fn Servo_TraverseSubtree(
                                         -> bool {
     use self::structs::TraversalRestyleBehavior as Restyle;
     use self::structs::TraversalRootBehavior as Root;
     debug_assert!(!snapshots.is_null());
 
     let element = GeckoElement(root);
     debug!("Servo_TraverseSubtree: {:?}", element);
 
+    let (without_normal_restyle, restyle_behavior) = match restyle_behavior {
+        Restyle::ForAnimationOnly => (true, Restyle::Normal),
+        other => (false, other)
+    };
+
     let traversal_flags = match (root_behavior, restyle_behavior) {
         (Root::Normal, Restyle::Normal) => TraversalFlags::empty(),
         (Root::UnstyledChildrenOnly, Restyle::Normal) => UNSTYLED_CHILDREN_ONLY,
         (Root::Normal, Restyle::ForReconstruct) => FOR_RECONSTRUCT,
         _ => panic!("invalid combination of TraversalRootBehavior and TraversalRestyleBehavior"),
     };
 
     if element.has_animation_only_dirty_descendants() ||
        element.has_animation_restyle_hints() {
         traverse_subtree(element,
                          raw_data,
                          traversal_flags | ANIMATION_ONLY,
                          unsafe { &*snapshots });
     }
 
+    if without_normal_restyle {
+        return false;
+    }
+
     traverse_subtree(element,
                      raw_data,
                      traversal_flags,
                      unsafe { &*snapshots });
 
     element.has_dirty_descendants() || element.borrow_data().unwrap().has_restyle()
 }