author | Hiroyuki Ikezoe <hikezoe@mozilla.com> |
Tue, 31 Oct 2017 09:45:41 +0900 | |
changeset 389217 | fa94f7205173d34f23975c6af9cb95237b28c8b8 |
parent 389216 | 401840a241b9861cac205bd866ba24e69d11b7e2 |
child 389218 | bf4fd832f591878b0c8838179f6f62e34fd573e3 |
push id | 32779 |
push user | ebalazs@mozilla.com |
push date | Tue, 31 Oct 2017 10:45:04 +0000 |
treeherder | mozilla-central@a16cc603d061 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | birtles |
bugs | 1190721 |
milestone | 58.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
|
--- a/dom/animation/KeyframeEffectReadOnly.cpp +++ b/dom/animation/KeyframeEffectReadOnly.cpp @@ -728,16 +728,34 @@ KeyframeEffectReadOnly::ComposeStyle( prop.mSegments.Length(), "out of array bounds"); ComposeStyleRule(Forward<ComposeAnimationResult>(aComposeResult), prop, *segment, computedTiming); } + + // If the animation produces any transform change hint, we need to record the + // current time to unthrottle the animation periodically when the animation is + // being throttled because it's scrolled out of view. + if (mCumulativeChangeHint & (nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_AddOrRemoveTransform | + nsChangeHint_UpdateTransformLayer)) { + nsPresContext* presContext = + nsContentUtils::GetContextForContent(mTarget->mElement); + if (presContext) { + TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh(); + EffectSet* effectSet = + EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); + MOZ_ASSERT(effectSet, "ComposeStyle should only be called on an effect " + "that is part of an effect set"); + effectSet->UpdateLastTransformSyncTime(now); + } + } } bool KeyframeEffectReadOnly::IsRunningOnCompositor() const { // We consider animation is running on compositor if there is at least // one property running on compositor. // Animation.IsRunningOnCompotitor will return more fine grained @@ -1387,18 +1405,27 @@ KeyframeEffectReadOnly::CanThrottle() co return true; } // Unless we are newly in-effect, we can throttle the animation if the // animation is paint only and the target frame is out of view or the document // is in background tabs. if (mInEffectOnLastAnimationTimingUpdate && CanIgnoreIfNotVisible()) { nsIPresShell* presShell = GetPresShell(); - if ((presShell && !presShell->IsActive()) || - frame->IsScrolledOutOfView()) { + if (presShell && !presShell->IsActive()) { + return true; + } + if (frame->IsScrolledOutOfView()) { + // If there are transform change hints, unthrottle the animation + // periodically since it might affect the overflow region. + if (mCumulativeChangeHint & (nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_AddOrRemoveTransform | + nsChangeHint_UpdateTransformLayer)) { + return CanThrottleTransformChanges(*frame); + } return true; } } // First we need to check layer generation and transform overflow // prior to the property.mIsRunningOnCompositor check because we should // occasionally unthrottle these animations even if the animations are // already running on compositor.
--- a/dom/animation/test/mozilla/file_restyles.html +++ b/dom/animation/test/mozilla/file_restyles.html @@ -295,16 +295,59 @@ waitForAllPaints(function() { is(markers.length, 0, 'Animations running on the compositor for elements ' + 'which are scrolled out should never cause restyles'); await ensureElementRemoval(parentElement); }); + add_task( + async function restyling_transform_animations_in_scrolled_out_element() { + if (!offscreenThrottlingEnabled) { + return; + } + + await SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] }); + + var parentElement = addDiv(null, + { style: 'overflow-y: scroll; height: 20px;' }); + var div = addDiv(null, + { style: 'animation: rotate 100s; position: relative; top: 100px;' }); + parentElement.appendChild(div); + var animation = div.getAnimations()[0]; + var timeAtStart = document.timeline.currentTime; + + ok(!animation.isRunningOnCompositor, + 'The transform animation is not running on the compositor'); + + var markers; + var now; + while (true) { + markers = await observeStyling(1); + // Check restyle markers until 200ms is elapsed. + now = document.timeline.currentTime; + if ((now - timeAtStart) >= 200) { + break; + } + + is(markers.length, 0, + 'Transform animation running on the element which is scrolled out ' + + 'should be throttled until 200ms is elapsed'); + } + + is(markers.length, 1, + 'Transform animation running on the element which is scrolled out ' + + 'should be unthrottled after around 200ms have elapsed. now: ' + + now + ' start time: ' + timeAtStart); + + await ensureElementRemoval(parentElement); + } + ); + add_task(async function restyling_main_thread_animations_in_scrolled_out_element() { if (!offscreenThrottlingEnabled) { return; } /* On Android throttled animations are left behind on the main thread in some frames, We will fix this in bug 1247800.
--- a/layout/base/nsChangeHint.h +++ b/layout/base/nsChangeHint.h @@ -444,21 +444,23 @@ static_assert(!(nsChangeHint_Hints_Alway #define nsChangeHint_ReflowHintsForFloatAreaChange \ nsChangeHint(nsChangeHint_AllReflowHints & \ ~(nsChangeHint_ClearDescendantIntrinsics | \ nsChangeHint_NeedDirtyReflow)) #define NS_STYLE_HINT_REFLOW \ nsChangeHint(NS_STYLE_HINT_VISUAL | nsChangeHint_AllReflowHints) -#define nsChangeHint_Hints_CanIgnoreIfNotVisible \ - nsChangeHint(NS_STYLE_HINT_VISUAL | \ - nsChangeHint_NeutralChange | \ - nsChangeHint_UpdateOpacityLayer | \ - nsChangeHint_UpdateTransformLayer | \ +#define nsChangeHint_Hints_CanIgnoreIfNotVisible \ + nsChangeHint(NS_STYLE_HINT_VISUAL | \ + nsChangeHint_NeutralChange | \ + nsChangeHint_UpdateOpacityLayer | \ + nsChangeHint_AddOrRemoveTransform | \ + nsChangeHint_UpdatePostTransformOverflow | \ + nsChangeHint_UpdateTransformLayer | \ nsChangeHint_UpdateUsesOpacity) // NB: Once we drop support for the old style system, this logic should be // inlined in the Servo style system to eliminate the FFI call. inline nsChangeHint NS_HintsNotHandledForDescendantsIn(nsChangeHint aChangeHint) { nsChangeHint result = aChangeHint & nsChangeHint_Hints_NeverHandledForDescendants;
--- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -679,20 +679,16 @@ AddAnimationsForProperty(nsIFrame* aFram } else if (aProperty == eCSSProperty_opacity) { data = null_t(); } MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR), "inconsistent property flags"); - EffectSet* effects = EffectSet::GetEffectSet(aFrame); - MOZ_ASSERT(effects); - - bool sentAnimations = false; // Add from first to last (since last overrides) for (size_t animIdx = 0; animIdx < compositorAnimations.Length(); animIdx++) { dom::Animation* anim = compositorAnimations[animIdx]; if (!anim->IsRelevant()) { continue; } dom::KeyframeEffectReadOnly* keyframeEffect = @@ -706,17 +702,17 @@ AddAnimationsForProperty(nsIFrame* aFram } // Note that if the property is overridden by !important rules, // GetEffectiveAnimationOfProperty returns null instead. // This is what we want, since if we have animations overridden by // !important rules, we don't want to send them to the compositor. MOZ_ASSERT(anim->CascadeLevel() != EffectCompositor::CascadeLevel::Animations || - !effects->PropertiesWithImportantRules() + !EffectSet::GetEffectSet(aFrame)->PropertiesWithImportantRules() .HasProperty(aProperty), "GetEffectiveAnimationOfProperty already tested the property " "is not overridden by !important rules"); // Don't add animations that are pending if their timeline does not // track wallclock time. This is because any pending animations on layers // will have their start time updated with the current wallclock time. // If we can't convert that wallclock time back to an equivalent timeline @@ -729,22 +725,16 @@ AddAnimationsForProperty(nsIFrame* aFram if (anim->PlayState() == AnimationPlayState::Pending && (anim->GetTimeline() && !anim->GetTimeline()->TracksWallclockTime())) { continue; } AddAnimationForProperty(aFrame, *property, anim, aAnimationInfo, data, aPending); keyframeEffect->SetIsRunningOnCompositor(aProperty, true); - sentAnimations = true; - } - - if (sentAnimations && aProperty == eCSSProperty_transform) { - TimeStamp now = aFrame->PresContext()->RefreshDriver()->MostRecentRefresh(); - effects->UpdateLastTransformSyncTime(now); } } static bool GenerateAndPushTextMask(nsIFrame* aFrame, gfxContext* aContext, const nsRect& aFillRect, nsDisplayListBuilder* aBuilder) { if (aBuilder->IsForGenerateGlyphMask() ||