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
--- 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,