Bug 1334036 - Part 11: Trigger animation-only restyle when we handle an event with coordinates. r=birtles,heycam
authorBoris Chiou <boris.chiou@gmail.com>
Fri, 19 May 2017 16:16:41 +0800
changeset 407882 096add92a79f793e2a9ed6388be1576d1ae62d57
parent 407881 db95d6d01b0bf2e752b22c858041e4f0908ea61c
child 407883 3ca6e6b80f4754b8f9182248a7a2188654561915
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles, heycam
bugs1334036
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1334036 - Part 11: Trigger animation-only restyle when we handle an event with coordinates. r=birtles,heycam 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
@@ -509,17 +509,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
@@ -531,32 +532,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);
     }
@@ -577,30 +583,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,