Bug 1334036 - Part 11: Trigger animation-only restyle when we handle an event with coordinates. draft
authorBoris Chiou <boris.chiou@gmail.com>
Fri, 19 May 2017 16:16:41 +0800
changeset 581944 84c8765bc20eb5b325e53024ef9c1c7f1ae982a1
parent 581943 287ef9520b1ce20dd1eb59b9ec2736a17c537e2b
child 581945 17183be80ffc58d5263f1d75f955da3ca98bbbd2
child 581946 2625af2fad74f9410a52a143c7b1c4a2997018d8
push id59916
push userbmo:boris.chiou@gmail.com
push dateSat, 20 May 2017 06:48:29 +0000
bugs1334036
milestone55.0a1
Bug 1334036 - Part 11: 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/RestyleManager.h
layout/base/RestyleManagerInlines.h
layout/base/ServoRestyleManager.cpp
layout/base/ServoRestyleManager.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
layout/style/ServoTypes.h
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -952,33 +952,38 @@ EffectCompositor::SetPerformanceWarning(
   }
 
   for (KeyframeEffectReadOnly* effect : *effects) {
     effect->SetPerformanceWarning(aProperty, aWarning);
   }
 }
 
 bool
-EffectCompositor::PreTraverse()
+EffectCompositor::PreTraverse(AnimationRestyleType aRestyleType)
 {
-  return PreTraverseInSubtree(nullptr);
+  return PreTraverseInSubtree(nullptr, aRestyleType);
 }
 
 bool
-EffectCompositor::PreTraverseInSubtree(Element* aRoot)
+EffectCompositor::PreTraverseInSubtree(Element* aRoot,
+                                       AnimationRestyleType aRestyleType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mPresContext->RestyleManager()->IsServo());
 
   AutoRestore<bool> guard(mIsInPreTraverse);
   mIsInPreTraverse = true;
 
-  // We need to force flush all throttled animations if there are any
-  // non-animation restyles.
-  bool flushThrottledRestyles = aRoot && aRoot->HasDirtyDescendantsForServo();
+  // We need to force flush all throttled animations if we also have
+  // non-animation restyles (since we'll want the up-to-date animation style
+  // when we go to process them so we can trigger transitions correctly), and
+  // if we are currently flushing all throttled animation restyles.
+  bool flushThrottledRestyles =
+    (aRoot && aRoot->HasDirtyDescendantsForServo()) ||
+    aRestyleType == AnimationRestyleType::Full;
 
   using ElementsToRestyleIterType =
     nsDataHashtable<PseudoElementHashEntry, bool>::Iterator;
   auto getNeededRestyleTarget = [&](const ElementsToRestyleIterType& aIter)
                                 -> NonOwningAnimationTarget {
     NonOwningAnimationTarget returnTarget;
 
     // If aIter.Data() is false, the element only requested a throttled
@@ -1087,18 +1092,19 @@ EffectCompositor::PreTraverse(dom::Eleme
     return found;
   }
 
   AutoRestore<bool> guard(mIsInPreTraverse);
   mIsInPreTraverse = true;
 
   PseudoElementHashEntry::KeyType key = { aElement, aPseudoType };
 
-  // We need to flush all throttled animation restyles too if there are
-  // any non-animation restyles.
+  // We need to flush all throttled animation restyles too if we also have
+  // non-animation restyles (since we'll want the up-to-date animation style
+  // when we go to process them so we can trigger transitions correctly).
   Element* elementToRestyle = GetElementToRestyle(aElement, aPseudoType);
   bool flushThrottledRestyles = elementToRestyle &&
                                 elementToRestyle->HasDirtyDescendantsForServo();
 
   for (size_t i = 0; i < kCascadeLevelCount; ++i) {
     CascadeLevel cascadeLevel = CascadeLevel(i);
     auto& elementSet = mElementsToRestyle[cascadeLevel];
 
--- a/dom/animation/EffectCompositor.h
+++ b/dom/animation/EffectCompositor.h
@@ -222,28 +222,37 @@ public:
 
   // Associates a performance warning with effects on |aFrame| that animates
   // |aProperty|.
   static void SetPerformanceWarning(
     const nsIFrame* aFrame,
     nsCSSPropertyID aProperty,
     const AnimationPerformanceWarning& aWarning);
 
+  // The type which represents what kind of animation restyle we want.
+  enum class AnimationRestyleType {
+    Throttled, // Restyle elements that have posted animation restyles.
+    Full       // Restyle all elements with animations (i.e. even if the
+               // animations are throttled).
+  };
+
   // 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.
+  //
   // Returns true if there are elements needing a restyle for animation.
-  bool PreTraverse();
+  bool PreTraverse(AnimationRestyleType aRestyleType);
 
   // Similar to the above but only for the (pseudo-)element.
   bool PreTraverse(dom::Element* aElement, CSSPseudoElementType aPseudoType);
 
   // Similar to the above but for all elements in the subtree rooted
   // at aElement.
-  bool PreTraverseInSubtree(dom::Element* aElement);
+  bool PreTraverseInSubtree(dom::Element* aElement,
+                            AnimationRestyleType aRestyleType);
 
 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
@@ -6941,20 +6941,17 @@ 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();
-      }
+      presContext->RestyleManager()->UpdateOnlyAnimationStyles();
     }
   }
 
   aDocument->EnumerateSubDocuments(FlushThrottledStyles, nullptr);
   return true;
 }
 
 /*
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -175,16 +175,18 @@ public:
                                   const nsAttrValue* aNewValue);
   inline void AttributeChanged(dom::Element* aElement,
                                int32_t aNameSpaceID,
                                nsIAtom* aAttribute,
                                int32_t aModType,
                                const nsAttrValue* aOldValue);
   inline nsresult ReparentStyleContext(nsIFrame* aFrame);
 
+  inline void UpdateOnlyAnimationStyles();
+
   // Get a counter that increments on every style change, that we use to
   // track whether off-main-thread animations are up-to-date.
   uint64_t GetAnimationGeneration() const { return mAnimationGeneration; }
 
   static uint64_t GetAnimationGenerationForFrame(nsIFrame* aFrame);
 
   // Update the animation generation count to mark that animation state
   // has changed.
--- a/layout/base/RestyleManagerInlines.h
+++ b/layout/base/RestyleManagerInlines.h
@@ -74,11 +74,17 @@ RestyleManager::AttributeChanged(dom::El
 }
 
 nsresult
 RestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
   MOZ_STYLO_FORWARD(ReparentStyleContext, (aFrame));
 }
 
+void
+RestyleManager::UpdateOnlyAnimationStyles()
+{
+  MOZ_STYLO_FORWARD(UpdateOnlyAnimationStyles, ());
+}
+
 } // namespace mozilla
 
 #endif // mozilla_RestyleManagerInlines_h
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -435,17 +435,18 @@ ServoRestyleManager::FrameForPseudoEleme
   }
 
   MOZ_CRASH("Unkown pseudo-element given to "
             "ServoRestyleManager::FrameForPseudoElement");
   return nullptr;
 }
 
 void
-ServoRestyleManager::ProcessPendingRestyles()
+ServoRestyleManager::DoProcessPendingRestyles(TraversalRestyleBehavior
+                                                aRestyleBehavior)
 {
   MOZ_ASSERT(PresContext()->Document(), "No document?  Pshaw!");
   MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
   MOZ_ASSERT(!mInStyleRefresh, "Reentrant call?");
 
   if (MOZ_UNLIKELY(!PresContext()->PresShell()->DidInitialize())) {
     // PresShell::FlushPendingNotifications doesn't early-return in the case
     // where the PreShell hasn't yet been initialized (and therefore we haven't
@@ -457,32 +458,37 @@ ServoRestyleManager::ProcessPendingResty
 
   // Create a AnimationsWithDestroyedFrame during restyling process to
   // stop animations and transitions on elements that have no frame at the end
   // of the restyling process.
   AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this);
 
   ServoStyleSet* styleSet = StyleSet();
   nsIDocument* doc = PresContext()->Document();
+  bool animationOnly = aRestyleBehavior ==
+                         TraversalRestyleBehavior::ForAnimationOnly;
 
   // Ensure the refresh driver is active during traversal to avoid mutating
   // mActiveTimer and mMostRecentRefresh time.
   PresContext()->RefreshDriver()->MostRecentRefresh();
 
 
   // Perform the Servo traversal, and the post-traversal if required. We do this
   // in a loop because certain rare paths in the frame constructor (like
   // uninstalling XBL bindings) can trigger additional style validations.
   mInStyleRefresh = true;
-  if (mHaveNonAnimationRestyles) {
+  if (mHaveNonAnimationRestyles && !animationOnly) {
     ++mAnimationGeneration;
   }
 
-  while (styleSet->StyleDocument()) {
-    ClearSnapshots();
+  while (animationOnly ? styleSet->StyleDocumentForAnimationOnly()
+                       : styleSet->StyleDocument()) {
+    if (!animationOnly) {
+      ClearSnapshots();
+    }
 
     // Recreate style contexts, and queue up change hints (which also handle
     // lazy frame construction).
     nsStyleChangeList currentChanges(StyleBackendType::Servo);
     DocumentStyleRootIterator iter(doc);
     while (Element* root = iter.GetNextStyleRoot()) {
       ProcessPostTraversal(root, nullptr, styleSet, currentChanges);
     }
@@ -503,30 +509,50 @@ ServoRestyleManager::ProcessPendingResty
       }
       newChanges.Clear();
     }
     mReentrantChanges = nullptr;
 
     IncrementRestyleGeneration();
   }
 
-  ClearSnapshots();
   FlushOverflowChangedTracker();
 
-  mHaveNonAnimationRestyles = false;
+  if (!animationOnly) {
+    ClearSnapshots();
+    styleSet->AssertTreeIsClean();
+    mHaveNonAnimationRestyles = false;
+  }
   mInStyleRefresh = false;
-  styleSet->AssertTreeIsClean();
 
   // Note: We are in the scope of |animationsWithDestroyedFrame|, so
   //       |mAnimationsWithDestroyedFrame| is still valid.
   MOZ_ASSERT(mAnimationsWithDestroyedFrame);
   mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames();
 }
 
 void
+ServoRestyleManager::ProcessPendingRestyles()
+{
+  DoProcessPendingRestyles(TraversalRestyleBehavior::Normal);
+}
+
+void
+ServoRestyleManager::UpdateOnlyAnimationStyles()
+{
+  // Bug 1365855: We also need to implement this for SMIL.
+  bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
+  if (!doCSS) {
+    return;
+  }
+
+  DoProcessPendingRestyles(TraversalRestyleBehavior::ForAnimationOnly);
+}
+
+void
 ServoRestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
                                               nsIContent* aChild)
 {
   //
   // XXXbholley: We need the Gecko logic here to correctly restyle for things
   // like :empty and positional selectors (though we may not need to post
   // restyle events as agressively as the Gecko path does).
   //
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -45,16 +45,18 @@ public:
                         nsChangeHint aMinChangeHint);
   void PostRestyleEventForLazyConstruction();
   void RebuildAllStyleData(nsChangeHint aExtraHint,
                            nsRestyleHint aRestyleHint);
   void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                     nsRestyleHint aRestyleHint);
   void ProcessPendingRestyles();
 
+  void UpdateOnlyAnimationStyles();
+
   void ContentInserted(nsINode* aContainer, nsIContent* aChild);
   void ContentAppended(nsIContent* aContainer,
                        nsIContent* aFirstNewContent);
   void ContentRemoved(nsINode* aContainer,
                       nsIContent* aOldChild,
                       nsIContent* aFollowingSibling);
 
   void RestyleForInsertOrChange(nsINode* aContainer,
@@ -133,16 +135,18 @@ private:
                "style backend");
     return PresContext()->StyleSet()->AsServo();
   }
 
   const SnapshotTable& Snapshots() const { return mSnapshots; }
   void ClearSnapshots();
   ServoElementSnapshot& SnapshotFor(mozilla::dom::Element* aElement);
 
+  void DoProcessPendingRestyles(TraversalRestyleBehavior aRestyleBehavior);
+
   // We use a separate data structure from nsStyleChangeList because we need a
   // frame to create nsStyleChangeList entries, and the primary frame may not be
   // attached yet.
   struct ReentrantChange {
     nsCOMPtr<nsIContent> mContent;
     nsChangeHint mHint;
   };
   typedef AutoTArray<ReentrantChange, 10> ReentrantChangeList;
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -265,31 +265,33 @@ ServoStyleSet::PreTraverseSync()
 
   // Ensure that the @font-face data is not stale
   mPresContext->Document()->GetUserFontSet();
 
   UpdateStylistIfNeeded();
 }
 
 void
-ServoStyleSet::PreTraverse(Element* aRoot)
+ServoStyleSet::PreTraverse(Element* aRoot,
+                           EffectCompositor::AnimationRestyleType aRestyleType)
 {
   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, aRestyleType);
     if (smilController) {
       smilController->PreTraverseInSubtree(aRoot);
     }
   } else {
-    mPresContext->EffectCompositor()->PreTraverse();
+    mPresContext->EffectCompositor()->PreTraverse(aRestyleType);
     if (smilController) {
       smilController->PreTraverse();
     }
   }
 }
 
 bool
 ServoStyleSet::PrepareAndTraverseSubtree(
@@ -305,32 +307,42 @@ ServoStyleSet::PrepareAndTraverseSubtree
   MOZ_ASSERT(!StylistNeedsUpdate());
   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);
 
+  // Don't need to trigger a second traversal if this restyle only needs
+  // animation-only restyle.
+  if (forAnimationOnly) {
+    return postTraversalRequired;
+  }
+
   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
   // traversal caused, for example, the font-size to change, the SMIL style
   // won't be updated until the next tick anyway.
   EffectCompositor* compositor = mPresContext->EffectCompositor();
-  if (forReconstruct ? compositor->PreTraverseInSubtree(root)
-                     : compositor->PreTraverse()) {
+  EffectCompositor::AnimationRestyleType restyleType =
+    EffectCompositor::AnimationRestyleType::Throttled;
+  if (forReconstruct ? compositor->PreTraverseInSubtree(root, restyleType)
+                     : compositor->PreTraverse(restyleType)) {
     if (Servo_TraverseSubtree(
           aRoot, mRawSet.get(), &snapshots, aRootBehavior, aRestyleBehavior)) {
       MOZ_ASSERT(!forReconstruct);
       if (isInitial) {
         // We're doing initial styling, and the additional animation
         // traversal changed the styles that were set by the first traversal.
         // This would normally require a post-traversal to update the style
         // contexts, and the DOM now has dirty descendant bits and RestyleData
@@ -846,16 +858,33 @@ ServoStyleSet::StyleDocument()
                                   TraversalRootBehavior::Normal,
                                   TraversalRestyleBehavior::Normal)) {
       postTraversalRequired = true;
     }
   }
   return postTraversalRequired;
 }
 
+bool
+ServoStyleSet::StyleDocumentForAnimationOnly()
+{
+  PreTraverse(nullptr, EffectCompositor::AnimationRestyleType::Full);
+
+  bool postTraversalRequired = false;
+  DocumentStyleRootIterator iter(mPresContext->Document());
+  while (Element* root = iter.GetNextStyleRoot()) {
+    if (PrepareAndTraverseSubtree(root,
+                                  TraversalRootBehavior::Normal,
+                                  TraversalRestyleBehavior::ForAnimationOnly)) {
+      postTraversalRequired = true;
+    }
+  }
+  return postTraversalRequired;
+}
+
 void
 ServoStyleSet::StyleNewSubtree(Element* aRoot)
 {
   MOZ_ASSERT(!aRoot->HasServoData());
 
   PreTraverse();
 
   DebugOnly<bool> postTraversalRequired =
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ServoStyleSet_h
 #define mozilla_ServoStyleSet_h
 
+#include "mozilla/EffectCompositor.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/PostTraversalTask.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/ServoUtils.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/SheetType.h"
@@ -241,16 +242,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).
+   */
+  bool 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.
@@ -417,17 +427,19 @@ 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,
+                   EffectCompositor::AnimationRestyleType =
+                     EffectCompositor::AnimationRestyleType::Throttled);
   // Subset of the pre-traverse steps that involve syncing up data
   void PreTraverseSync();
 
   /**
    * A tri-state used to track which kind of stylist state we may need to
    * update.
    */
   enum class StylistState : uint8_t {
--- a/layout/style/ServoTypes.h
+++ b/layout/style/ServoTypes.h
@@ -48,24 +48,26 @@ enum class LazyComputeBehavior {
 // Indicates whether the Servo style system should perform normal processing or
 // 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 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.
+// Indicates whether the Servo style system should perform normal processing,
+// animation-only processing (so we can flush any throttled animation styles),
+// 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,