Bug 1196114 - Part 3: Set AnimationPerformanceWarning messages. r?birtles
Those message will be modified in part 4 (localization).
MozReview-Commit-ID: 6TMUxemVLcu
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -104,20 +104,24 @@ FindAnimationsForCompositor(const nsIFra
for (KeyframeEffectReadOnly* effect : *effects) {
MOZ_ASSERT(effect && effect->GetAnimation());
Animation* animation = effect->GetAnimation();
if (!animation->IsPlaying()) {
continue;
}
- if (effect->ShouldBlockCompositorAnimations(aFrame)) {
+ nsAutoString performanceWarning;
+ if (effect->ShouldBlockCompositorAnimations(aFrame,
+ performanceWarning)) {
if (aMatches) {
aMatches->Clear();
}
+ effect->SetPerformanceWarning(aProperty,
+ performanceWarning);
return false;
}
if (!effect->HasAnimationOfProperty(aProperty)) {
continue;
}
if (aMatches) {
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -2094,108 +2094,101 @@ KeyframeEffectReadOnly::IsGeometricPrope
default:
return false;
}
}
/* static */ bool
KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
const nsIFrame* aFrame,
- const nsIContent* aContent)
+ nsAString& aPerformanceWarning)
{
// Disallow OMTA for preserve-3d transform. Note that we check the style property
// rather than Extend3DContext() since that can recurse back into this function
// via HasOpacity().
if (aFrame->Combines3DTransformWithAncestors() ||
aFrame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) {
- if (aContent) {
- nsCString message;
- message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' "
- "transforms is not supported. See bug 779598");
- AnimationUtils::LogAsyncAnimationFailure(message, aContent);
- }
+ aPerformanceWarning.AssignLiteral(
+ "Gecko bug: Async animation of 'preserve-3d' "
+ "transforms is not supported. See bug 779598");
return false;
}
// Note that testing BackfaceIsHidden() is not a sufficient test for
// what we need for animating backface-visibility correctly if we
// remove the above test for Extend3DContext(); that would require
// looking at backface-visibility on descendants as well.
if (aFrame->StyleDisplay()->BackfaceIsHidden()) {
- if (aContent) {
- nsCString message;
- message.AppendLiteral("Gecko bug: Async animation of "
- "'backface-visibility: hidden' transforms is not supported."
- " See bug 1186204.");
- AnimationUtils::LogAsyncAnimationFailure(message, aContent);
- }
+ aPerformanceWarning.AssignLiteral(
+ "Gecko bug: Async animation of "
+ "'backface-visibility: hidden' transforms is not supported."
+ " See bug 1186204");
return false;
}
if (aFrame->IsSVGTransformed()) {
- if (aContent) {
- nsCString message;
- message.AppendLiteral("Gecko bug: Async 'transform' animations of "
- "aFrames with SVG transforms is not supported. See bug 779599");
- AnimationUtils::LogAsyncAnimationFailure(message, aContent);
- }
+ aPerformanceWarning.AssignLiteral(
+ "Gecko bug: Async 'transform' animations of "
+ "aFrames with SVG transforms is not supported. See bug 779599");
return false;
}
return true;
}
bool
-KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame*
- aFrame) const
+KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(
+ const nsIFrame* aFrame,
+ nsAString& aPerformanceWarning) const
{
// We currently only expect this method to be called when this effect
// is attached to a playing Animation. If that ever changes we'll need
// to update this to only return true when that is the case since paused,
// filling, cancelled Animations etc. shouldn't stop other Animations from
// running on the compositor.
MOZ_ASSERT(mAnimation && mAnimation->IsPlaying());
- bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled();
-
for (const AnimationProperty& property : mProperties) {
// If a property is overridden in the CSS cascade, it should not block other
// animations from running on the compositor.
if (!property.mWinsInCascade) {
continue;
}
// Check for geometric properties
if (IsGeometricProperty(property.mProperty)) {
- if (shouldLog) {
- nsCString message;
- message.AppendLiteral("Performance warning: Async animation of "
- "'transform' or 'opacity' not possible due to animation of geometric"
- "properties on the same element");
- AnimationUtils::LogAsyncAnimationFailure(message, aFrame->GetContent());
- }
+ aPerformanceWarning.AssignLiteral(
+ "Performance warning: Async animation of "
+ "'transform' or 'opacity' not possible due to animation of geometric "
+ "properties on the same element");
return true;
}
// Check for unsupported transform animations
if (property.mProperty == eCSSProperty_transform) {
if (!CanAnimateTransformOnCompositor(aFrame,
- shouldLog ? aFrame->GetContent() : nullptr)) {
+ aPerformanceWarning)) {
return true;
}
}
}
return false;
}
void
KeyframeEffectReadOnly::SetPerformanceWarning(nsCSSProperty aProperty,
const nsAString &aMessage)
{
for (AnimationProperty& property : mProperties) {
if (property.mProperty == aProperty) {
+ property.mPerformanceWarning.reset();
property.mPerformanceWarning.emplace(aMessage);
+
+ if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
+ nsAutoCString logMessage = NS_ConvertUTF16toUTF8(aMessage);
+ AnimationUtils::LogAsyncAnimationFailure(logMessage, mTarget);
+ }
return;
}
}
}
//---------------------------------------------------------------------
//
// KeyframeEffect
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -319,17 +319,21 @@ public:
// running at the same time on the same element to run on the main thread.
//
// Similarly, some transform animations cannot be run on the compositor and
// when that is the case we simply disable all compositor animations
// on the same element.
//
// Bug 1218620 - It seems like we don't need to be this restrictive. Wouldn't
// it be ok to do 'opacity' animations on the compositor in either case?
- bool ShouldBlockCompositorAnimations(const nsIFrame* aFrame) const;
+ //
+ // When returning true, |aOutPerformanceWarning| stores the reason why
+ // we shouldn't run the compositor animations.
+ bool ShouldBlockCompositorAnimations(const nsIFrame* aFrame,
+ nsAString& aPerformanceWarning) const;
nsIDocument* GetRenderedDocument() const;
nsPresContext* GetPresContext() const;
// Associates a warning string with the animated property on the specified
// frame indicating why, for example, the property could not be animated
// on the compositor.
void SetPerformanceWarning(nsCSSProperty aProperty,
@@ -391,21 +395,20 @@ protected:
private:
nsIFrame* GetAnimationFrame() const;
bool CanThrottle() const;
bool CanThrottleTransformChanges(nsIFrame& aFrame) const;
// Returns true unless Gecko limitations prevent performing transform
- // animations for |aFrame|. Any limitations that are encountered are
- // logged using |aContent| to describe the affected content.
- // If |aContent| is nullptr, no logging is performed
+ // animations for |aFrame|. When returning true, the reason for the
+ // limitation is stored in |aOutPerformanceWarning|.
static bool CanAnimateTransformOnCompositor(const nsIFrame* aFrame,
- const nsIContent* aContent);
+ nsAString& aPerformanceWarning);
static bool IsGeometricProperty(const nsCSSProperty aProperty);
static const TimeDuration OverflowRegionRefreshInterval();
};
class KeyframeEffect : public KeyframeEffectReadOnly
{
public:
--- a/dom/animation/test/chrome/test_animation_property_state.html
+++ b/dom/animation/test/chrome/test_animation_property_state.html
@@ -2,17 +2,17 @@
<head>
<meta charset=utf-8>
<title>Bug 1196114 - Animation property which indicates
running on the compositor or not</title>
<script type="application/javascript" src="../testharness.js"></script>
<script type="application/javascript" src="../testharnessreport.js"></script>
<script type="application/javascript" src="../testcommon.js"></script>
<style>
-div {
+.compositable {
/* Element needs geometry to be eligible for layerization */
width: 100px;
height: 100px;
background-color: white;
}
</style>
</head>
<body>
@@ -42,16 +42,45 @@ function assert_animation_property_state
for (var i = 0; i < sortedActual.length; i++) {
assert_equals(sortedActual[i].property,
sortedExpected[i].property,
'CSS property name should match');
assert_equals(sortedActual[i].runningOnCompositor,
sortedExpected[i].runningOnCompositor,
'runningOnCompositor property should match');
+ if (sortedExpected[i].warning instanceof RegExp) {
+ assert_regexp_match(sortedActual[i].warning,
+ sortedExpected[i].warning,
+ 'warning message should match');
+ } else if (sortedExpected[i].warning) {
+ assert_equals(sortedActual[i].warning,
+ sortedExpected[i].warning,
+ 'warning message should match');
+ }
+ }
+}
+
+// Check that the animation is running on compositor and
+// warning property is not set for the CSS property regardless
+// expected values.
+function assert_property_state_on_compositor(actual, expected) {
+ assert_equals(actual.length, expected.length);
+
+ var sortedActual = actual.sort(compare_property_state);
+ var sortedExpected = expected.sort(compare_property_state);
+
+ for (var i = 0; i < sortedActual.length; i++) {
+ assert_equals(sortedActual[i].property,
+ sortedExpected[i].property,
+ 'CSS property name should match');
+ assert_true(sortedActual[i].runningOnCompositor,
+ 'runningOnCompositor property should be true');
+ assert_not_exists(sortedActual[i], 'warning',
+ 'warning property should not be set');
}
}
var gAnimationsTests = [
{
desc: 'animations on compositor',
frames: {
opacity: [0, 1]
@@ -103,25 +132,181 @@ var gAnimationsTests = [
property: 'opacity',
runningOnCompositor: true
},
{
property: 'transform',
runningOnCompositor: true
}
]
- }
+ },
+ {
+ // FIXME: Once we have KeyframeEffect.setFrames, we should rewrite
+ // this test case to check that runningOnCompositor is restored to true
+ // after 'width' keyframe is removed from the keyframes.
+ desc: 'animation on compositor with animation of geometric properties',
+ frames: {
+ width: ['100px', '200px'],
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: [
+ {
+ property: 'width',
+ runningOnCompositor: false
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: "Performance warning: Async animation of " +
+ "'transform' or 'opacity' not possible due to animation " +
+ "of geometric properties on the same element"
+ }
+ ]
+ },
];
gAnimationsTests.forEach(function(subtest) {
promise_test(function(t) {
- var div = addDiv(t);
+ var div = addDiv(t, { class: 'compositable' });
var animation = div.animate(subtest.frames, 100000);
return animation.ready.then(t.step_func(function() {
assert_animation_property_state_equals(
animation.effect.getPropertyState(),
subtest.expected);
}));
}, subtest.desc);
});
+var gPerformanceWarningTests = [
+ {
+ desc: 'preserve-3d transform',
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ style: 'transform-style: preserve-3d',
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: "Gecko bug: Async animation of 'preserve-3d' " +
+ "transforms is not supported. See bug 779598"
+ }
+ ]
+ },
+ {
+ desc: 'transform with backface-visibility:hidden',
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ style: 'backface-visibility: hidden;',
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: "Gecko bug: Async animation of " +
+ "'backface-visibility: hidden' transforms is not " +
+ "supported. See bug 1186204"
+ }
+ ]
+ },
+];
+
+gPerformanceWarningTests.forEach(function(subtest) {
+ promise_test(function(t) {
+ var div = addDiv(t, { class: 'compositable' });
+ var animation = div.animate(subtest.frames, 100000);
+ return animation.ready.then(t.step_func(function() {
+ assert_property_state_on_compositor(
+ animation.effect.getPropertyState(),
+ subtest.expected);
+ div.style = subtest.style;
+ return waitForFrame();
+ })).then(t.step_func(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getPropertyState(),
+ subtest.expected);
+ div.style = '';
+ return waitForFrame();
+ })).then(t.step_func(function() {
+ assert_property_state_on_compositor(
+ animation.effect.getPropertyState(),
+ subtest.expected);
+ }));
+ }, subtest.desc);
+});
+
+promise_test(function(t) {
+ var div = addDiv(t, { class: 'compositable' });
+ var animation = div.animate(
+ { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
+ return animation.ready.then(t.step_func(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getPropertyState(),
+ [ { property: 'transform', runningOnCompositor: true } ]);
+ div.style = 'width: 10000px; height: 10000px';
+ return waitForFrame();
+ })).then(t.step_func(function() {
+ // viewport depends on test environment.
+ var expectedWarning = new RegExp(
+ "Performance warning: Async animation disabled because frame size " +
+ "\\(10000, 10000\\) is bigger than the viewport \\(\\d+, \\d+\\) " +
+ "or the visual rectangle \\(10000, 10000\\) is larger than the max " +
+ "allowable value \\(\\d+\\)");
+ assert_animation_property_state_equals(
+ animation.effect.getPropertyState(),
+ [ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: expectedWarning
+ } ]);
+ div.style = 'width: 100px; height: 100px';
+ return waitForFrame();
+ })).then(t.step_func(function() {
+ // FIXME: Bug 1253164: the animation should get back on compositor.
+ assert_animation_property_state_equals(
+ animation.effect.getPropertyState(),
+ [ { property: 'transform', runningOnCompositor: false } ]);
+ }));
+}, 'transform on too big element');
+
+promise_test(function(t) {
+ var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute('width', '100');
+ svg.setAttribute('height', '100');
+ var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+ rect.setAttribute('width', '100');
+ rect.setAttribute('height', '100');
+ rect.setAttribute('fill', 'red');
+ svg.appendChild(rect);
+ document.body.appendChild(svg);
+ t.add_cleanup(function() {
+ svg.remove();
+ });
+
+ var animation = svg.animate(
+ { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
+ return animation.ready.then(t.step_func(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getPropertyState(),
+ [ { property: 'transform', runningOnCompositor: true } ]);
+ svg.setAttribute('transform', 'translate(10, 20)');
+ return waitForFrame();
+ })).then(t.step_func(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getPropertyState(),
+ [ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: "Gecko bug: Async 'transform' animations of aFrames " +
+ "with SVG transforms is not supported. See bug 779599"
+ } ]);
+ svg.removeAttribute('transform');
+ return waitForFrame();
+ })).then(t.step_func(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getPropertyState(),
+ [ { property: 'transform', runningOnCompositor: true } ]);
+ }));
+}, 'transform of nsIFrame with SVG transform');
+
</script>
+
</body>
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -5583,21 +5583,24 @@ nsDisplayTransform::GetResultingTransfor
bool
nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder)
{
if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, eCSSProperty_opacity)) {
return true;
}
- if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
- nsCString message;
- message.AppendLiteral("Performance warning: Async animation disabled because frame was not marked active for opacity animation");
- AnimationUtils::LogAsyncAnimationFailure(message, Frame()->GetContent());
- }
+ nsString message;
+ message.AppendLiteral(
+ "Performance warning: Async animation disabled because frame was not "
+ "marked active for opacity animation");
+ EffectCompositor::SetPerformanceWarning(mFrame,
+ eCSSProperty_opacity,
+ message);
+
return false;
}
bool
nsDisplayTransform::ShouldPrerender(nsDisplayListBuilder* aBuilder) {
if (!mMaybePrerender) {
return false;
}
@@ -5618,38 +5621,39 @@ nsDisplayTransform::ShouldPrerender(nsDi
bool
nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder)
{
if (mMaybePrerender) {
// TODO We need to make sure that if we use async animation we actually
// pre-render even if we're out of will change budget.
return true;
}
- DebugOnly<bool> prerender = ShouldPrerenderTransformedContent(aBuilder, mFrame, true);
+ DebugOnly<bool> prerender = ShouldPrerenderTransformedContent(aBuilder, mFrame);
NS_ASSERTION(!prerender, "Something changed under us!");
return false;
}
/* static */ bool
nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder,
- nsIFrame* aFrame,
- bool aLogAnimations)
+ nsIFrame* aFrame)
{
// Elements whose transform has been modified recently, or which
// have a compositor-animated transform, can be prerendered. An element
// might have only just had its transform animated in which case
// the ActiveLayerManager may not have been notified yet.
if (!ActiveLayerTracker::IsStyleMaybeAnimated(aFrame, eCSSProperty_transform) &&
!EffectCompositor::HasAnimationsForCompositor(aFrame,
eCSSProperty_transform)) {
- if (aLogAnimations) {
- nsCString message;
- message.AppendLiteral("Performance warning: Async animation disabled because frame was not marked active for transform animation");
- AnimationUtils::LogAsyncAnimationFailure(message, aFrame->GetContent());
- }
+ nsString message;
+ message.AppendLiteral(
+ "Performance warning: Async animation disabled because frame was not "
+ "marked active for transform animation");
+ EffectCompositor::SetPerformanceWarning(aFrame,
+ eCSSProperty_transform,
+ message);
return false;
}
nsSize refSize = aBuilder->RootReferenceFrame()->GetSize();
// Only prerender if the transformed frame's size is <= the
// reference frame size (~viewport), allowing a 1/8th fuzz factor
// for shadows, borders, etc.
refSize += nsSize(refSize.width / 8, refSize.height / 8);
@@ -5658,37 +5662,38 @@ nsDisplayTransform::ShouldPrerenderTrans
if (frameSize <= refSize) {
maxInAppUnits = aFrame->PresContext()->DevPixelsToAppUnits(4096);
nsRect visual = aFrame->GetVisualOverflowRect();
if (visual.width <= maxInAppUnits && visual.height <= maxInAppUnits) {
return true;
}
}
- if (aLogAnimations) {
- nsRect visual = aFrame->GetVisualOverflowRect();
-
- nsCString message;
- message.AppendLiteral("Performance warning: Async animation disabled because frame size (");
- message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(frameSize.width));
- message.AppendLiteral(", ");
- message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(frameSize.height));
- message.AppendLiteral(") is bigger than the viewport (");
- message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(refSize.width));
- message.AppendLiteral(", ");
- message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(refSize.height));
- message.AppendLiteral(") or the visual rectangle (");
- message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(visual.width));
- message.AppendLiteral(", ");
- message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(visual.height));
- message.AppendLiteral(") is larger than the max allowable value (");
- message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(maxInAppUnits));
- message.Append(')');
- AnimationUtils::LogAsyncAnimationFailure(message, aFrame->GetContent());
- }
+ nsRect visual = aFrame->GetVisualOverflowRect();
+
+ nsAutoString message;
+ message.AppendLiteral(
+ "Performance warning: Async animation disabled because frame size (");
+ message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(frameSize.width));
+ message.AppendLiteral(", ");
+ message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(frameSize.height));
+ message.AppendLiteral(") is bigger than the viewport (");
+ message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(refSize.width));
+ message.AppendLiteral(", ");
+ message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(refSize.height));
+ message.AppendLiteral(") or the visual rectangle (");
+ message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(visual.width));
+ message.AppendLiteral(", ");
+ message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(visual.height));
+ message.AppendLiteral(") is larger than the max allowable value (");
+ message.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(maxInAppUnits));
+ message.Append(')');
+ EffectCompositor::SetPerformanceWarning(aFrame,
+ eCSSProperty_transform,
+ message);
return false;
}
/* If the matrix is singular, or a hidden backface is shown, the frame won't be visible or hit. */
static bool IsFrameVisible(nsIFrame* aFrame, const Matrix4x4& aMatrix)
{
if (aMatrix.IsSingular()) {
return false;
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -4080,18 +4080,17 @@ public:
uint32_t aFlags,
const nsRect* aBoundsOverride = nullptr,
nsIFrame** aOutAncestor = nullptr);
/**
* Return true when we should try to prerender the entire contents of the
* transformed frame even when it's not completely visible (yet).
*/
static bool ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder,
- nsIFrame* aFrame,
- bool aLogAnimations = false);
+ nsIFrame* aFrame);
bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
bool MayBeAnimated(nsDisplayListBuilder* aBuilder);
/**
* This will return if it's possible for this element to be prerendered.
* This should never return false if we're going to prerender.
*/