--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -298,38 +298,42 @@ EffectCompositor::RequestRestyle(dom::El
effectSet->UpdateAnimationGeneration(mPresContext);
}
}
}
void
EffectCompositor::PostRestyleForAnimation(dom::Element* aElement,
CSSPseudoElementType aPseudoType,
- CascadeLevel aCascadeLevel)
+ CascadeLevel aCascadeLevel,
+ bool aThrottled)
{
if (!mPresContext) {
return;
}
dom::Element* element = GetElementToRestyle(aElement, aPseudoType);
if (!element) {
return;
}
nsRestyleHint hint = aCascadeLevel == CascadeLevel::Transitions ?
eRestyle_CSSTransitions :
eRestyle_CSSAnimations;
+ if (aThrottled) {
+ hint = nsRestyleHint(hint | eRestyle_CSSThrottledAnimations);
+ }
if (mPresContext->StyleSet()->IsServo()) {
MOZ_ASSERT(NS_IsMainThread(),
"Restyle request during restyling should be requested only on "
"the main-thread. e.g. after the parallel traversal");
if (ServoStyleSet::IsInServoTraversal() || mIsInPreTraverse) {
- MOZ_ASSERT(hint == eRestyle_CSSAnimations ||
- hint == eRestyle_CSSTransitions);
+ MOZ_ASSERT(hint & eRestyle_CSSAnimations ||
+ hint & eRestyle_CSSTransitions);
// We can't call Servo_NoteExplicitHints here since AtomicRefCell does not
// allow us mutate ElementData of the |aElement| in SequentialTask.
// Instead we call Servo_NoteExplicitHints for the element in PreTraverse()
// which will be called right before the second traversal that we do for
// updating CSS animations.
// In that case PreTraverse() will return true so that we know to do the
// second traversal so we don't need to post any restyle requests to the
@@ -352,17 +356,17 @@ EffectCompositor::PostRestyleForThrottle
for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
bool& postedRestyle = iter.Data();
if (postedRestyle) {
continue;
}
PostRestyleForAnimation(iter.Key().mElement,
iter.Key().mPseudoType,
- cascadeLevel);
+ cascadeLevel, true);
postedRestyle = true;
}
}
}
template<typename StyleType>
void
EffectCompositor::UpdateEffectProperties(StyleType* aStyleType,
--- a/dom/animation/EffectCompositor.h
+++ b/dom/animation/EffectCompositor.h
@@ -105,17 +105,18 @@ public:
CascadeLevel aCascadeLevel);
// Schedule an animation restyle. This is called automatically by
// RequestRestyle when necessary. However, it is exposed here since we also
// need to perform this step when triggering transitions *without* also
// invalidating the animation style rule (which RequestRestyle would do).
void PostRestyleForAnimation(dom::Element* aElement,
CSSPseudoElementType aPseudoType,
- CascadeLevel aCascadeLevel);
+ CascadeLevel aCascadeLevel,
+ bool aThrottled = false);
// Posts an animation restyle for any elements whose animation style rule
// is out of date but for which an animation restyle has not yet been
// posted because updates on the main thread are throttled.
void PostRestyleForThrottledAnimations();
// Called when computed style on the specified (pseudo-) element might
// have changed so that any context-sensitive values stored within
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -131,16 +131,17 @@ KeyframeEffectReadOnly::NotifyAnimationT
}
// Request restyle if necessary.
if (mAnimation && !mProperties.IsEmpty() && HasComputedTimingChanged()) {
EffectCompositor::RestyleType restyleType =
CanThrottle() ?
EffectCompositor::RestyleType::Throttled :
EffectCompositor::RestyleType::Standard;
+
RequestRestyle(restyleType);
}
// Detect changes to "in effect" status since we need to recalculate the
// animation cascade for this element whenever that changes.
// Note that updating mInEffectOnLastAnimationTimingUpdate has to be done
// after above CanThrottle() call since the function uses the flag inside it.
bool inEffect = IsInEffect();
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1592,16 +1592,31 @@ nsDOMWindowUtils::GetIsMozAfterPaintPend
nsPresContext* presContext = GetPresContext();
if (!presContext)
return NS_OK;
*aResult = presContext->IsDOMPaintEventPending();
return NS_OK;
}
NS_IMETHODIMP
+nsDOMWindowUtils::GetIsMozAfterAnimationPending(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext)
+ return NS_OK;
+
+ *aResult = presContext->GetRootPresContext()->RefreshDriver()->OnlyAnimationFlush();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
nsDOMWindowUtils::DisableNonTestMouseEvents(bool aDisable)
{
nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
nsIDocShell *docShell = window->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -849,16 +849,18 @@ interface nsIDOMWindowUtils : nsISupport
out unsigned long aMaxDifference);
/**
* Returns true if a MozAfterPaint event has been queued but not yet
* fired.
*/
readonly attribute boolean isMozAfterPaintPending;
+ readonly attribute boolean isMozAfterAnimationPending;
+
/**
* Suppresses/unsuppresses user initiated event handling in window's document
* and subdocuments.
*
* @throw NS_ERROR_DOM_SECURITY_ERR if called without chrome privileges and
* NS_ERROR_FAILURE if window doesn't have a document.
*/
void suppressEventHandling(in boolean aSuppress);
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -612,16 +612,17 @@ GeckoRestyleManager::ProcessPendingResty
PresContext()->TransitionManager()->SetInAnimationOnlyStyleUpdate(false);
}
mIsProcessingRestyles = false;
NS_ASSERTION(haveNonAnimation || !mHavePendingNonAnimationRestyles,
"should not have added restyles");
mHavePendingNonAnimationRestyles = false;
+ mOnlyThrottledAnimation = false;
if (mDoRebuildAllStyleData) {
// We probably wasted a lot of work up above, but this seems safest
// and it should be rarely used.
// This might add us as a refresh observer again; that's ok.
ProcessPendingRestyles();
NS_ASSERTION(!mDoRebuildAllStyleData,
@@ -747,16 +748,18 @@ GeckoRestyleManager::PostRestyleEvent(El
aRestyleHintData);
// Set mHavePendingNonAnimationRestyles for any restyle that could
// possibly contain non-animation styles (i.e., those that require us
// to do an animation-only style flush before processing style changes
// to ensure correct initialization of CSS transitions).
if (aRestyleHint & ~eRestyle_AllHintsWithAnimations) {
mHavePendingNonAnimationRestyles = true;
+ } else if (aRestyleHint & eRestyle_CSSThrottledAnimations) {
+ mOnlyThrottledAnimation = true;
}
PostRestyleEventInternal();
}
void
GeckoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint)
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -20,16 +20,17 @@ RestyleManager::RestyleManager(StyleBack
nsPresContext* aPresContext)
: mPresContext(aPresContext)
, mRestyleGeneration(1)
, mUndisplayedRestyleGeneration(1)
, mHoverGeneration(0)
, mType(aType)
, mInStyleRefresh(false)
, mAnimationGeneration(0)
+ , mOnlyThrottledAnimation(false)
{
MOZ_ASSERT(mPresContext);
}
void
RestyleManager::ContentInserted(nsINode* aContainer, nsIContent* aChild)
{
RestyleForInsertOrChange(aContainer, aChild);
@@ -1738,16 +1739,18 @@ RestyleManager::ProcessRestyledFrames(ns
} else if (!data.mFrame || !data.mFrame->IsViewportFrame()) {
NS_WARNING("Unable to test style tree integrity -- no content node "
"(and not a viewport frame)");
}
}
#endif
aChangeList.Clear();
+
+ mOnlyThrottledAnimation = false;
}
/* static */ uint64_t
RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aFrame)
{
EffectSet* effectSet = EffectSet::GetEffectSet(aFrame);
return effectSet ? effectSet->GetAnimationGeneration() : 0;
}
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -83,16 +83,18 @@ public:
// ProcessRestyledFrames call in a view update batch and a script blocker.
// This function does not call ProcessAttachedQueue() on the binding manager.
// If the caller wants that to happen synchronously, it needs to handle that
// itself.
void ProcessRestyledFrames(nsStyleChangeList& aChangeList);
bool IsInStyleRefresh() const { return mInStyleRefresh; }
+ bool OnlyThrottledAnimation() const { return mOnlyThrottledAnimation; }
+
// AnimationsWithDestroyedFrame is used to stop animations and transitions
// on elements that have no frame at the end of the restyling process.
// It only lives during the restyling process.
class MOZ_STACK_CLASS AnimationsWithDestroyedFrame final {
public:
// Construct a AnimationsWithDestroyedFrame object. The caller must
// ensure that aRestyleManager lives at least as long as the
// object. (This is generally easy since the caller is typically a
@@ -281,15 +283,17 @@ protected:
// The total number of animation flushes by this frame constructor.
// Used to keep the layer and animation manager in sync.
uint64_t mAnimationGeneration;
OverflowChangedTracker mOverflowChangedTracker;
AnimationsWithDestroyedFrame* mAnimationsWithDestroyedFrame = nullptr;
+ bool mOnlyThrottledAnimation;
+
friend class mozilla::GeckoRestyleManager;
friend class mozilla::ServoRestyleManager;
};
} // namespace mozilla
#endif
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -348,16 +348,18 @@ ServoRestyleManager::PostRestyleEvent(El
// hints manually to avoid re-traversing the DOM to find them.
if (mReentrantChanges && !aRestyleHint) {
mReentrantChanges->AppendElement(ReentrantChange { aElement, aMinChangeHint });
return;
}
if (aRestyleHint & ~eRestyle_AllHintsWithAnimations) {
mHaveNonAnimationRestyles = true;
+ } else if (aRestyleHint & eRestyle_CSSThrottledAnimations) {
+ mOnlyThrottledAnimation = true;
}
if (aRestyleHint & eRestyle_LaterSiblings) {
aRestyleHint &= ~eRestyle_LaterSiblings;
nsRestyleHint siblingHint = eRestyle_Subtree;
Element* current = aElement->GetNextElementSibling();
while (current) {
@@ -1197,16 +1199,17 @@ ServoRestyleManager::DoProcessPendingRes
doc->ClearServoRestyleRoot();
FlushOverflowChangedTracker();
ClearSnapshots();
styleSet->AssertTreeIsClean();
mHaveNonAnimationRestyles = false;
+ mOnlyThrottledAnimation = false;
mRestyleForCSSRuleChanges = false;
mInStyleRefresh = false;
// Now that everything has settled, see if we have enough free rule nodes in
// the tree to warrant sweeping them.
styleSet->MaybeGCRuleTree();
// Note: We are in the scope of |animationsWithDestroyedFrame|, so
@@ -1412,16 +1415,17 @@ ServoRestyleManager::TakeSnapshotForAttr
// undisplayed elements, because we don't actually restyle those elements
// during the restyle traversal. So just assume that the attribute change can
// cause the style to change.
IncrementUndisplayedRestyleGeneration();
// Some other random attribute changes may also affect the transitions,
// so we also set this true here.
mHaveNonAnimationRestyles = true;
+ mOnlyThrottledAnimation = false;
ServoElementSnapshot& snapshot = SnapshotFor(aElement);
snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
if (influencesOtherPseudoClassState) {
snapshot.AddOtherPseudoClassState(aElement);
}
}
@@ -1494,16 +1498,17 @@ ServoRestyleManager::AttributeChanged(El
if (restyleHint) {
// Assuming we need to invalidate cached style in getComputedStyle for
// undisplayed elements, since we don't know if it is needed.
IncrementUndisplayedRestyleGeneration();
// If we change attributes, we have to mark this to be true, so we will
// increase the animation generation for the new created transition if any.
mHaveNonAnimationRestyles = true;
+ mOnlyThrottledAnimation = false;
}
}
nsresult
ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
{
// This is only called when moving frames in or out of the first-line
// pseudo-element (or one of its descendants). We can't say much about
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -598,19 +598,22 @@ enum nsRestyleHint : uint32_t {
// Continue the restyling process to all of the current frame's
// descendants, even if any frame's restyling resulted in no style
// changes. (Implies eRestyle_Force.) Note that this is weaker than
// eRestyle_Subtree, which makes us rerun selector matching on all
// descendants rather than just continuing the restyling process.
eRestyle_ForceDescendants = 1 << 9,
+ eRestyle_CSSThrottledAnimations = 1 << 10,
+
// Useful unions:
eRestyle_AllHintsWithAnimations = eRestyle_CSSTransitions |
eRestyle_CSSAnimations |
+ eRestyle_CSSThrottledAnimations |
eRestyle_StyleAttribute_Animations,
};
// The functions below need an integral type to cast to to avoid
// infinite recursion.
typedef decltype(nsRestyleHint(0) + nsRestyleHint(0)) nsRestyleHint_size_t;
inline constexpr nsRestyleHint operator|(nsRestyleHint aLeft,
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1153,16 +1153,17 @@ nsRefreshDriver::nsRefreshDriver(nsPresC
mThrottledFrameRequestInterval(TimeDuration::FromMilliseconds(
GetThrottledTimerInterval())),
mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
mThrottled(false),
mNeedToRecomputeVisibility(false),
mTestControllingRefreshes(false),
mViewManagerFlushIsPending(false),
mHasScheduleFlush(false),
+ mOnlyAnimationFlush(false),
mInRefresh(false),
mWaitingForTransaction(false),
mSkippedPaints(false),
mResizeSuppressed(false),
mWarningThreshold(REFRESH_WAIT_WARNING)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mPresContext,
@@ -2329,16 +2330,21 @@ nsRefreshDriver::IsRefreshObserver(nsARe
void
nsRefreshDriver::ScheduleViewManagerFlush()
{
NS_ASSERTION(mPresContext->IsRoot(),
"Should only schedule view manager flush on root prescontexts");
mViewManagerFlushIsPending = true;
mHasScheduleFlush = true;
+ mOnlyAnimationFlush = false;
+ if (mPresContext->RestyleManager() && !mPresContext->RestyleManager()->OnlyThrottledAnimation()) {
+ } else {
+ mOnlyAnimationFlush = true;
+ }
EnsureTimerStarted(eNeverAdjustTimer);
}
void
nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument)
{
NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
mFrameRequestCallbackDocs.NoIndex &&
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -216,16 +216,19 @@ public:
mViewManagerFlushIsPending = false;
}
bool ViewManagerFlushIsPending() {
return mViewManagerFlushIsPending;
}
bool HasScheduleFlush() {
return mHasScheduleFlush;
}
+ bool OnlyAnimationFlush() {
+ return mOnlyAnimationFlush;
+ }
/**
* Add a document for which we have FrameRequestCallbacks
*/
void ScheduleFrameRequestCallbacks(nsIDocument* aDocument);
/**
* Remove a document for which we have FrameRequestCallbacks
@@ -442,16 +445,17 @@ private:
bool mThrottled;
bool mNeedToRecomputeVisibility;
bool mTestControllingRefreshes;
bool mViewManagerFlushIsPending;
// True if the view manager needs a flush. Layers-free mode uses this value
// to know when to notify invalidation.
bool mHasScheduleFlush;
+ bool mOnlyAnimationFlush;
bool mInRefresh;
// True if the refresh driver is suspended waiting for transaction
// id's to be returned and shouldn't do any work during Tick().
bool mWaitingForTransaction;
// True if Tick() was skipped because of mWaitingForTransaction and
// we should schedule a new Tick immediately when resumed instead
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2461,16 +2461,19 @@ already_AddRefed<LayerManager> nsDisplay
}
aBuilder->SetIsCompositingCheap(temp);
if (document && widgetTransaction) {
TriggerPendingAnimations(document, layerManager->GetAnimationReadyTime());
}
if (presContext->RefreshDriver()->HasScheduleFlush()) {
+ if (XRE_IsContentProcess()) {
+ //printf_stderr("@@@NotifyInvalidation!\n");
+ }
presContext->NotifyInvalidation(layerManager->GetLastTransactionId(), frame->GetRect());
}
return layerManager.forget();
}
NotifySubDocInvalidationFunc computeInvalidFunc =
presContext->MayHavePaintEventListenerInSubDocument() ? nsPresContext::NotifySubDocInvalidation : 0;
--- a/layout/reftests/bugs/1078262-1.html
+++ b/layout/reftests/bugs/1078262-1.html
@@ -1,12 +1,12 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE html>
-<html lang="en">
+<html lang="en" class="reftest-wait-except-animation">
<meta charset="utf-8">
<title>Test for bug 1078262</title>
<style>
#outer {
perspective: 10000px;
width: 200px;
height: 4000px;
@@ -27,8 +27,17 @@
from { transform: rotateX(10.01deg); }
to { transform: rotateX(10.00deg); }
}
</style>
<div id="outer">
<div id="inner"></div>
</div>
+<script>
+document.addEventListener('MozReftestInvalidate', () => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove('reftest-wait-except-animation');
+ });
+ });
+}, false);
+</script>
--- a/layout/reftests/css-animations/opacity-animation-in-fixed-opacity-parent.html
+++ b/layout/reftests/css-animations/opacity-animation-in-fixed-opacity-parent.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait reftest-no-flush">
+<html class="reftest-wait-except-animation reftest-no-flush">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Testcase, bug 1395151</title>
<style type="text/css">
@keyframes test-anim {
from { opacity : 1 }
to { opacity : 1 }
}
@@ -34,15 +34,15 @@
<body>
<div class="ok">
<div class="error"><span></span></div>
</div>
<script>
document.addEventListener('MozReftestInvalidate', () => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
- document.documentElement.classList.remove('reftest-wait');
+ document.documentElement.classList.remove('reftest-wait-except-animation');
});
});
}, false);
</script>
</body>
</html>
--- a/layout/reftests/css-animations/stacking-context-opacity-wins-over-transition.html
+++ b/layout/reftests/css-animations/stacking-context-opacity-wins-over-transition.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait">
+<html class="reftest-wait-except-animation">
<title>
Opacity animation winning over opacity transition creates a stacking context
for correct style.
</title>
<style>
span {
height: 100px;
width: 100px;
@@ -29,12 +29,12 @@ span {
window.addEventListener("load", () => {
var target = document.getElementById("test");
getComputedStyle(target).opacity;
// CSS animation wins over transitions, so transition won't be visible during
// the CSS animation.
target.style.opacity = 0;
requestAnimationFrame(() => {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/css-animations/stacking-context-transform-wins-over-transition.html
+++ b/layout/reftests/css-animations/stacking-context-transform-wins-over-transition.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait">
+<html class="reftest-wait-except-animation">
<title>
Transform animation winning over transition creates a stacking context
for correct style
</title>
<style>
span {
height: 100px;
width: 100px;
@@ -29,12 +29,12 @@ span {
window.addEventListener("load", () => {
var target = document.getElementById("test");
getComputedStyle(target).transform;
// CSS animation wins over transition, so transition won't be visible during
// the CSS animation.
target.style.transform = "translateX(100px)";
requestAnimationFrame(() => {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/css-transitions/stacking-context-opacity-wins-over-important-style.html
+++ b/layout/reftests/css-transitions/stacking-context-opacity-wins-over-important-style.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait">
+<html class="reftest-wait-except-animation">
<title>
Opacity transition winning over !important rule creates a stacking context
</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
@@ -31,12 +31,12 @@ span {
window.addEventListener("load", () => {
var target = document.getElementById("test");
getComputedStyle(target).opacity;
target.style.setProperty("opacity", "0", "important");
getComputedStyle(target).opacity;
requestAnimationFrame(() => {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/css-transitions/stacking-context-transform-wins-over-important-style.html
+++ b/layout/reftests/css-transitions/stacking-context-transform-wins-over-important-style.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait">
+<html class="reftest-wait-except-animation">
<title>
Transform transition winning over !important rule creates a stacking context
</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
@@ -31,12 +31,12 @@ span {
window.addEventListener("load", () => {
var target = document.getElementById("test");
getComputedStyle(target).transform;
target.style.setProperty("transform", "translateX(200px)", "important");
getComputedStyle(target).transform;
requestAnimationFrame(() => {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/web-animations/stacking-context-opacity-changing-effect.html
+++ b/layout/reftests/web-animations/stacking-context-opacity-changing-effect.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait reftest-no-flush">
+<html class="reftest-wait-except-animation reftest-no-flush">
<title>
Opacity animation creates a stacking context after changing effects
</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
@@ -21,13 +21,13 @@ div {
<div id="test"></div>
<script>
var elem = document.getElementById("test");
var anim = elem.animate(null, { duration: 100000 });
var newEffect = new KeyframeEffect(elem, { opacity: [1, 1] }, 100000);
anim.ready.then(function() {
anim.effect = newEffect;
requestAnimationFrame(function() {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
</html>
--- a/layout/reftests/web-animations/stacking-context-opacity-changing-keyframe.html
+++ b/layout/reftests/web-animations/stacking-context-opacity-changing-keyframe.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait reftest-no-flush">
+<html class="reftest-wait-except-animation reftest-no-flush">
<title>Changing keyframes to opacity frames creates a stacking context</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
background: green;
top: 50px;
@@ -17,12 +17,12 @@ span {
<span></span>
<div id="test"></div>
<script>
var anim = document.getElementById("test")
.animate({ }, { duration: 100000 });
anim.ready.then(function() {
anim.effect.setKeyframes({ opacity: [1, 1] });
requestAnimationFrame(function() {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/web-animations/stacking-context-opacity-changing-target.html
+++ b/layout/reftests/web-animations/stacking-context-opacity-changing-target.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait reftest-no-flush">
+<html class="reftest-wait-except-animation reftest-no-flush">
<title>
Opacity animation creates a stacking context when changing its target
</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
@@ -20,12 +20,12 @@ div {
<div id="test"></div>
<div id="another"></div>
<script>
var anim = document.getElementById("test")
.animate({ opacity: [1, 1] }, { duration: 100000 });
anim.ready.then(function() {
anim.effect.target = document.getElementById("another");
requestAnimationFrame(function() {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/web-animations/stacking-context-transform-changing-display-property.html
+++ b/layout/reftests/web-animations/stacking-context-transform-changing-display-property.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait reftest-no-flush">
+<html class="reftest-wait-except-animation reftest-no-flush">
<title>
Transform animation creates a stacking context when changing its display style
</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
@@ -18,14 +18,15 @@ div {
}
</style>
<span></span>
<div id="test"></div>
<script>
var anim = document.getElementById("test")
.animate({ transform: ['none', 'none'] }, { duration: 100000 });
anim.ready.then(function() {
+ dump("@@@change color black\n");
anim.effect.target.style.display = 'block';
requestAnimationFrame(function() {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/web-animations/stacking-context-transform-changing-effect.html
+++ b/layout/reftests/web-animations/stacking-context-transform-changing-effect.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait reftest-no-flush">
+<html class="reftest-wait-except-animation reftest-no-flush">
<title>
Transform animation creates a stacking context after changing effects
</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
@@ -23,13 +23,13 @@ div {
var elem = document.getElementById("test");
var anim = elem.animate(null, { duration: 100000 });
var newEffect = new KeyframeEffect(elem,
{ transform: ['none', 'none']},
100000);
anim.ready.then(function() {
anim.effect = newEffect;
requestAnimationFrame(function() {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
</html>
--- a/layout/reftests/web-animations/stacking-context-transform-changing-keyframe.html
+++ b/layout/reftests/web-animations/stacking-context-transform-changing-keyframe.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait reftest-no-flush">
+<html class="reftest-wait-except-animation reftest-no-flush">
<title>Changing keyframes to transform frames creates a stacking context</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
background: green;
top: 50px;
@@ -17,12 +17,12 @@ span {
<span></span>
<div id="test"></div>
<script>
var anim = document.getElementById("test")
.animate({ }, { duration: 100000 });
anim.ready.then(function() {
anim.effect.setKeyframes({ transform: ['none', 'none'] });
requestAnimationFrame(function() {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/web-animations/stacking-context-transform-changing-target.html
+++ b/layout/reftests/web-animations/stacking-context-transform-changing-target.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait reftest-no-flush">
+<html class="reftest-wait-except-animation reftest-no-flush">
<title>
Transform animation creates a stacking context when changing its target
</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
@@ -20,12 +20,12 @@ div {
<div id="test"></div>
<div id="another"></div>
<script>
var anim = document.getElementById("test")
.animate({ transform: ['none', 'none'] }, { duration: 100000 });
anim.ready.then(function() {
anim.effect.target = document.getElementById("another");
requestAnimationFrame(function() {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
});
</script>
--- a/layout/reftests/web-animations/stacking-context-transform-none-animation-before-appending-element.html
+++ b/layout/reftests/web-animations/stacking-context-transform-none-animation-before-appending-element.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html class="reftest-wait">
+<html class="reftest-wait-except-animation">
<title>Transform animation whose target is not initially associated with any document creates a stacking context even if it has only 'transform:none' in its keyframe</title>
<style>
span {
height: 100px;
width: 100px;
position: fixed;
background: green;
top: 50px;
@@ -16,11 +16,11 @@ div {
</style>
<span></span>
<script>
var div = document.createElement("div")
var anim = div.animate({ transform: ['none', 'none'] },
{ duration: 100000 });
document.body.appendChild(div);
anim.ready.then(function() {
- document.documentElement.classList.remove("reftest-wait");
+ document.documentElement.classList.remove("reftest-wait-except-animation");
});
</script>
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -1496,22 +1496,23 @@ nsStyleSet::RuleNodeWithReplacement(Elem
MOZ_ASSERT(!aPseudoElement ==
(aPseudoType >= CSSPseudoElementType::Count ||
!(nsCSSPseudoElements::PseudoElementSupportsStyleAttribute(aPseudoType) ||
nsCSSPseudoElements::PseudoElementSupportsUserActionState(aPseudoType))),
"should have aPseudoElement only for certain pseudo elements");
// Remove the Force bits, which we don't need and which could confuse
// the remainingReplacements code below.
- aReplacements &= ~(eRestyle_Force | eRestyle_ForceDescendants);
+ aReplacements &= ~(eRestyle_Force | eRestyle_ForceDescendants | eRestyle_CSSThrottledAnimations);
MOZ_ASSERT(!(aReplacements & ~(eRestyle_CSSTransitions |
eRestyle_CSSAnimations |
eRestyle_StyleAttribute |
- eRestyle_StyleAttribute_Animations)),
+ eRestyle_StyleAttribute_Animations |
+ eRestyle_CSSThrottledAnimations)),
"unexpected replacement bits");
// This array can be hot and often grows to ~20 elements, so inline storage
// is best.
AutoTArray<RuleNodeInfo, 30> rules;
const CascadeLevel* startingLevel = gCascadeLevels;
nsRuleNode* startingNode = mRuleTree;
@@ -1755,17 +1756,17 @@ nsStyleSet::ResolveStyleWithReplacement(
// because at this point the parameter is more than just the element
// for animation; it's also used for the SetBodyTextColor call when
// it's the body element.
// However, we only want to set the flag to call UpdateAnimations
// if we're dealing with a replacement (such as style attribute
// replacement) that could lead to the animation property changing,
// and we explicitly do NOT want to call UpdateAnimations when
// we're trying to do an animation-only update.
- if (aReplacements & ~(eRestyle_CSSTransitions | eRestyle_CSSAnimations)) {
+ if (aReplacements & ~(eRestyle_CSSTransitions | eRestyle_CSSAnimations | eRestyle_CSSThrottledAnimations)) {
flags |= eDoAnimation;
}
elementForAnimation = aElement;
#ifdef DEBUG
{
nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(elementForAnimation);
NS_ASSERTION(pseudoType == CSSPseudoElementType::NotPseudo ||
!styleFrame ||
--- a/layout/tools/reftest/reftest-content.js
+++ b/layout/tools/reftest/reftest-content.js
@@ -23,16 +23,18 @@ const BLANK_URL_FOR_CLEARING = "data:tex
CU.import("resource://gre/modules/Timer.jsm");
CU.import("chrome://reftest/content/AsyncSpellCheckTestHelper.jsm");
CU.import("resource://gre/modules/Services.jsm");
var gBrowserIsRemote;
var gIsWebRenderEnabled;
var gHaveCanvasSnapshot = false;
+var gWaitForAnimationPaint = true;
+var gAnimationCounter = 0;
// Plugin layers can be updated asynchronously, so to make sure that all
// layer surfaces have the right content, we need to listen for explicit
// "MozPaintWait" and "MozPaintWaitFinished" events that signal when it's OK
// to take snapshots. We cannot take a snapshot while the number of
// "MozPaintWait" events fired exceeds the number of "MozPaintWaitFinished"
// events fired. We count the number of such excess events here. When
// the counter reaches zero we call gExplicitPendingPaintsCompleteHook.
var gExplicitPendingPaintCount = 0;
@@ -423,27 +425,51 @@ function resetDisplayportAndViewport() {
function shouldWaitForExplicitPaintWaiters() {
return gExplicitPendingPaintCount > 0;
}
function shouldWaitForPendingPaints() {
// if gHaveCanvasSnapshot is false, we're not taking snapshots so
// there is no need to wait for pending paints to be flushed.
- return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending;
+ if (gWaitForAnimationPaint) {
+ return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending;
+ } else {
+ if (hasAnimationPending()) {
+ gAnimationCounter++;
+ } else {
+ gAnimationCounter = 0;
+ }
+ //return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending && gAnimationCounter < 2;
+ return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending && !windowUtils().isMozAfterAnimationPending;
+ }
+}
+
+function hasAnimationPending() {
+ // if gHaveCanvasSnapshot is false, we're not taking snapshots so
+ // there is no need to wait for pending paints to be flushed.
+ return gHaveCanvasSnapshot && windowUtils().isMozAfterAnimationPending;
}
function shouldWaitForReftestWaitRemoval(contentRootElement) {
// use getAttribute because className works differently in HTML and SVG
return contentRootElement &&
contentRootElement.hasAttribute('class') &&
contentRootElement.getAttribute('class').split(/\s+/)
.indexOf("reftest-wait") != -1;
}
+function shouldWaitForReftestWaitRemovalExceptAnimation(contentRootElement) {
+ // use getAttribute because className works differently in HTML and SVG
+ return contentRootElement &&
+ contentRootElement.hasAttribute('class') &&
+ contentRootElement.getAttribute('class').split(/\s+/)
+ .indexOf("reftest-wait-except-animation") != -1;
+}
+
function shouldSnapshotWholePage(contentRootElement) {
// use getAttribute because className works differently in HTML and SVG
return contentRootElement &&
contentRootElement.hasAttribute('class') &&
contentRootElement.getAttribute('class').split(/\s+/)
.indexOf("reftest-snapshot-all") != -1;
}
@@ -537,16 +563,20 @@ function WaitForTestEnd(contentRootEleme
function AfterPaintListener(event) {
LogInfo("AfterPaintListener in " + event.target.document.location.href);
if (event.target.document != currentDoc) {
// ignore paint events for subframes or old documents in the window.
// Invalidation in subframes will cause invalidation in the toplevel document anyway.
return;
}
+ //if (!gWaitForAnimationPaint && hasAnimationPending()) {
+ // return;
+ //}
+
SendUpdateCanvasForEvent(event, contentRootElement);
// These events are fired immediately after a paint. Don't
// confuse ourselves by firing synchronously if we triggered the
// paint ourselves.
setTimeout(MakeProgress, 0);
}
function AttrModifiedListener() {
@@ -586,32 +616,33 @@ function WaitForTestEnd(contentRootEleme
LogInfo("MakeProgress: STATE_COMPLETED");
return;
}
FlushRendering();
switch (state) {
case STATE_WAITING_TO_FIRE_INVALIDATE_EVENT: {
- LogInfo("MakeProgress: STATE_WAITING_TO_FIRE_INVALIDATE_EVENT");
+ LogInfo("MakeProgress: STATE_WAITING_TO_FIRE_INVALIDATE_EVENT: " + shouldWaitForExplicitPaintWaiters() + ", " + shouldWaitForPendingPaints());
if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) {
gFailureReason = "timed out waiting for pending paint count to reach zero";
if (shouldWaitForExplicitPaintWaiters()) {
gFailureReason += " (waiting for MozPaintWaitFinished)";
LogInfo("MakeProgress: waiting for MozPaintWaitFinished");
}
if (shouldWaitForPendingPaints()) {
gFailureReason += " (waiting for MozAfterPaint)";
LogInfo("MakeProgress: waiting for MozAfterPaint");
}
return;
}
state = STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL;
var hasReftestWait = shouldWaitForReftestWaitRemoval(contentRootElement);
+ var hasReftestWaitExceptAnimation = shouldWaitForReftestWaitRemovalExceptAnimation(contentRootElement);
// Notify the test document that now is a good time to test some invalidation
LogInfo("MakeProgress: dispatching MozReftestInvalidate");
if (contentRootElement) {
var elements = getNoPaintElements(contentRootElement);
for (var i = 0; i < elements.length; ++i) {
windowUtils().checkAndClearPaintedState(elements[i]);
}
elements = getNoDisplayListElements(contentRootElement);
@@ -635,29 +666,39 @@ function WaitForTestEnd(contentRootEleme
if (hasReftestWait && !shouldWaitForReftestWaitRemoval(contentRootElement)) {
// MozReftestInvalidate handler removed reftest-wait.
// We expect something to have been invalidated...
FlushRendering();
if (!shouldWaitForPendingPaints() && !shouldWaitForExplicitPaintWaiters()) {
LogWarning("MozInvalidateEvent didn't invalidate");
}
}
+
+ if (hasReftestWaitExceptAnimation && !shouldWaitForReftestWaitRemovalExceptAnimation(contentRootElement)) {
+ //@@@ todo
+ }
// Try next state
MakeProgress();
return;
}
case STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL:
LogInfo("MakeProgress: STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL");
if (shouldWaitForReftestWaitRemoval(contentRootElement)) {
gFailureReason = "timed out waiting for reftest-wait to be removed";
LogInfo("MakeProgress: waiting for reftest-wait to be removed");
return;
}
+ if (shouldWaitForReftestWaitRemovalExceptAnimation(contentRootElement)) {
+ gFailureReason = "timed out waiting for reftest-wait-except-animation to be removed";
+ LogInfo("MakeProgress: waiting for reftest-wait-except-animation to be removed");
+ return;
+ }
+
// Try next state
state = STATE_WAITING_FOR_SPELL_CHECKS;
MakeProgress();
return;
case STATE_WAITING_FOR_SPELL_CHECKS:
LogInfo("MakeProgress: STATE_WAITING_FOR_SPELL_CHECKS");
if (numPendingSpellChecks) {
@@ -695,17 +736,17 @@ function WaitForTestEnd(contentRootEleme
case STATE_WAITING_FOR_APZ_FLUSH:
LogInfo("MakeProgress: STATE_WAITING_FOR_APZ_FLUSH");
// Nothing to do here; once we get the apz-repaints-flushed event
// we will go to STATE_WAITING_TO_FINISH
return;
case STATE_WAITING_TO_FINISH:
- LogInfo("MakeProgress: STATE_WAITING_TO_FINISH");
+ LogInfo("MakeProgress: STATE_WAITING_TO_FINISH: " + shouldWaitForExplicitPaintWaiters() + ", " + shouldWaitForPendingPaints());
if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) {
gFailureReason = "timed out waiting for pending paint count to " +
"reach zero (after reftest-wait removed and switch to print mode)";
if (shouldWaitForExplicitPaintWaiters()) {
gFailureReason += " (waiting for MozPaintWaitFinished)";
LogInfo("MakeProgress: waiting for MozPaintWaitFinished");
}
if (shouldWaitForPendingPaints()) {
@@ -854,17 +895,21 @@ function OnDocumentLoad(event)
WaitForTestEnd(contentRootElement, inPrintMode, []);
} else {
CheckLayerAssertions(contentRootElement);
CheckForProcessCrashExpectation(contentRootElement);
RecordResult();
}
}
- if (shouldWaitForReftestWaitRemoval(contentRootElement) ||
+ gAnimationCounter = 0;
+ gWaitForAnimationPaint = !shouldWaitForReftestWaitRemovalExceptAnimation(contentRootElement);
+
+ if (shouldWaitForReftestWaitRemovalExceptAnimation(contentRootElement) ||
+ shouldWaitForReftestWaitRemoval(contentRootElement) ||
shouldWaitForExplicitPaintWaiters() ||
spellCheckedElements.length) {
// Go into reftest-wait mode immediately after painting has been
// unsuppressed, after the onload event has finished dispatching.
gFailureReason = "timed out waiting for test to complete (trying to get into WaitForTestEnd)";
LogInfo("OnDocumentLoad triggering WaitForTestEnd");
setTimeout(function () { WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements); }, 0);
} else {