Bug 1196114 - Part 3: Set AnimationPerformanceWarning messages. r=birtles

Those message will be modified in part 4 (localization).

MozReview-Commit-ID: 6TMUxemVLcu

--HG--
extra : rebase_source : 65ef1879b3e606ae6dc279981b1e995c7b2cd40b
This commit is contained in:
Hiroyuki Ikezoe 2016-03-04 15:07:04 +09:00
parent e3df4388fc
commit d7ac86e556
6 changed files with 265 additions and 76 deletions

View File

@ -109,10 +109,14 @@ FindAnimationsForCompositor(const nsIFrame* aFrame,
continue;
}
if (effect->ShouldBlockCompositorAnimations(aFrame)) {
nsAutoString performanceWarning;
if (effect->ShouldBlockCompositorAnimations(aFrame,
performanceWarning)) {
if (aMatches) {
aMatches->Clear();
}
effect->SetPerformanceWarning(aProperty,
performanceWarning);
return false;
}

View File

@ -2099,19 +2099,16 @@ KeyframeEffectReadOnly::IsGeometricProperty(
/* 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
@ -2119,22 +2116,16 @@ KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
// 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;
}
@ -2142,8 +2133,9 @@ KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
}
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
@ -2152,8 +2144,6 @@ KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame*
// 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.
@ -2162,20 +2152,17 @@ KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame*
}
// 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;
}
}
@ -2190,7 +2177,13 @@ KeyframeEffectReadOnly::SetPerformanceWarning(nsCSSProperty aProperty,
{
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;
}
}

View File

@ -324,7 +324,11 @@ public:
//
// 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;
@ -396,11 +400,10 @@ private:
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();

View File

@ -7,7 +7,7 @@
<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;
@ -47,6 +47,35 @@ function assert_animation_property_state_equals(actual, expected) {
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');
}
}
@ -108,12 +137,35 @@ var gAnimationsTests = [
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(
@ -123,5 +175,138 @@ gAnimationsTests.forEach(function(subtest) {
}, 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>

View File

@ -5585,11 +5585,14 @@ nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder)
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;
}
@ -5620,15 +5623,14 @@ nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder)
// 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
@ -5637,11 +5639,13 @@ nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBui
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;
}
@ -5660,27 +5664,28 @@ nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBui
}
}
if (aLogAnimations) {
nsRect visual = aFrame->GetVisualOverflowRect();
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());
}
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;
}

View File

@ -4080,8 +4080,7 @@ public:
* 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);