diff --git a/dom/animation/KeyframeEffect.cpp b/dom/animation/KeyframeEffect.cpp index d8e57d1b90f0..339555545808 100644 --- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -1617,9 +1617,25 @@ KeyframeEffect::CalculateCumulativeChangeHint(const ComputedStyle* aComputedStyl // on invisible elements because we can't calculate the change hint for // such properties until we compose it. if (!segment.HasReplaceableValues()) { - mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible; - return; + if (property.mProperty != eCSSProperty_transform) { + mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible; + return; + } + // We try a little harder to optimize transform animations simply + // because they are so common (the second-most commonly animated + // property at the time of writing). So if we encounter a transform + // segment that needs composing with the underlying value, we just add + // all the change hints a transform animation is known to be able to + // generate. + mCumulativeChangeHint |= nsChangeHint_AddOrRemoveTransform | + nsChangeHint_RepaintFrame | + nsChangeHint_UpdateContainingBlock | + nsChangeHint_UpdateOverflow | + nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_UpdateTransformLayer; + continue; } + RefPtr fromContext = CreateComputedStyleForAnimationValue(property.mProperty, segment.mFromValue, diff --git a/dom/animation/test/mozilla/file_restyles.html b/dom/animation/test/mozilla/file_restyles.html index 2e0273ff1fe3..7420f92f0b5e 100644 --- a/dom/animation/test/mozilla/file_restyles.html +++ b/dom/animation/test/mozilla/file_restyles.html @@ -1774,6 +1774,25 @@ waitForAllPaints(() => { await ensureElementRemoval(div); }); + add_task(async function restyling_transform_aimations_on_invisible_element() { + // 'opacity: 0' prevents transform animations to be sent to the compositor. + const div = addDiv(null, { style: 'visibility: hidden; opacity: 0' }); + + const animation = + div.animate([ { transform: 'rotate(360deg)' } ], + { duration: 100 * MS_PER_SEC, + iterations: Infinity }); + + await waitForAnimationReadyToRestyle(animation); + + const markers = await observeStyling(5); + + is(markers.length, 0, + 'Transform animations without 100% keyframe on visibility hidden ' + + 'element should be throttled'); + await ensureElementRemoval(div); + }); + }); diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 09325a5da3f1..7f7f81451a19 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -3694,6 +3694,9 @@ CompareTransformValues(const RefPtr& aList, const RefPtr& aNewList) { nsChangeHint result = nsChangeHint(0); + + // Note: If we add a new change hint for transform changes here, we have to + // modify KeyframeEffect::CalculateCumulativeChangeHint too! if (!aList != !aNewList || (aList && *aList != *aNewList)) { result |= nsChangeHint_UpdateTransformLayer; if (aList && aNewList) { @@ -3818,6 +3821,10 @@ nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const // We do not need to apply nsChangeHint_UpdateTransformLayer since // nsChangeHint_RepaintFrame will forcibly invalidate the frame area and // ensure layers are rebuilt (or removed). + // + // Note: If we add a new change hint for transform changes here or in + // CompareTransformValues(), we have to modify + // KeyframeEffect::CalculateCumulativeChangeHint too! hint |= nsChangeHint_UpdateContainingBlock | nsChangeHint_AddOrRemoveTransform | nsChangeHint_UpdateOverflow |