Bug 1196114 - Part 3: Set AnimationPerformanceWarning messages. r?birtles draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Fri, 12 Feb 2016 10:46:01 +0900
changeset 330607 06ae879caf7b6d5b7e3adf4ba7896c2770fc6a2f
parent 330606 2b21e2fcd7100cb69753225a924bd2eeac85373e
child 330608 53ef7cd72afe5b842090116a8ee9bcbb350dc729
push id10781
push userhiikezoe@mozilla-japan.org
push dateFri, 12 Feb 2016 01:47:31 +0000
reviewersbirtles
bugs1196114, 1186204
milestone47.0a1
Bug 1196114 - Part 3: Set AnimationPerformanceWarning messages. r?birtles Test cases for async transform animations of frames with SVG transforms can not be written here because FrameLayerBuilder::GetDedicatedLayer fails in such frame's case. Message fixes: * Insert a space in warning message related to geometric properties. * Remove a period after 'bug 1186204'. MozReview-Commit-ID: CIqwMZPrmxS
dom/animation/EffectCompositor.cpp
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeEffect.h
dom/animation/test/chrome/test_running_status.html
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -102,20 +102,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
@@ -2017,109 +2017,101 @@ KeyframeEffectReadOnly::IsGeometricPrope
     case eCSSProperty_top:
     case eCSSProperty_width:
       return true;
     default:
       return false;
   }
 }
 
-/* static */ bool
+bool
 KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
   const nsIFrame* aFrame,
-  const nsIContent* aContent)
+  nsAString& aOutPerformanceWarning) const
 {
   // 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);
-    }
+    aOutPerformanceWarning.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);
-    }
+    aOutPerformanceWarning.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);
-    }
+    aOutPerformanceWarning.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& aOutPerformanceWarning) 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());
-      }
+      aOutPerformanceWarning.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)) {
+                                           aOutPerformanceWarning)) {
         return true;
       }
     }
   }
 
   return false;
 }
 
 void
 KeyframeEffectReadOnly::SetPerformanceWarning(nsCSSProperty aProperty,
                                               nsAString &aMessage)
 {
   for (AnimationProperty& property : mProperties) {
     if (property.mProperty == aProperty) {
       property.mWarningMessage = aMessage;
+
+      if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
+        nsAutoCString logMessage = NS_ConvertUTF16toUTF8(aMessage);
+        AnimationUtils::LogAsyncAnimationFailure(logMessage, mTarget);
+      }
       return;
     }
   }
 }
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -320,17 +320,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;
+  //
+  // Whenever returning true, |aOutPerformanceWarning| stores the reason why
+  // we shouldn't run the compositor animations.
+  bool ShouldBlockCompositorAnimations(const nsIFrame* aFrame,
+                                       nsAString& aOutPerformanceWarning) const;
 
   nsIDocument* GetRenderedDocument() const;
   nsPresContext* GetPresContext() const;
 
   inline AnimationCollection* GetCollection() const;
 
   void SetPerformanceWarning(nsCSSProperty aProperty,
                              nsAString& aMessage);
@@ -377,21 +381,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
-  static bool CanAnimateTransformOnCompositor(const nsIFrame* aFrame,
-                                              const nsIContent* aContent);
+  // animations for |aFrame|. The reason of the limitation is stored in
+  // |aOutPerformanceWarning|.
+  bool CanAnimateTransformOnCompositor(const nsIFrame* aFrame,
+                                       nsAString& aOutPerformanceWarning) const;
   static bool IsGeometricProperty(const nsCSSProperty aProperty);
 
   static const TimeDuration OverflowRegionRefreshInterval();
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/animation/test/chrome/test_running_status.html
+++ b/dom/animation/test/chrome/test_running_status.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>
@@ -43,16 +43,22 @@ function assert_running_status_equals(ac
   for (var i = 0; i < sortedActual.length; i++) {
     assert_equals(sortedActual[i].property,
                   sortedExpected[i].property,
                   'CSS property name should be ' + sortedExpected[i].property);
     assert_equals(sortedActual[i].runningOnCompositor,
                   sortedExpected[i].runningOnCompositor,
                   'runningOnCompositor property should be ' +
                   sortedExpected[i].runningOnCompositor);
+    if (sortedExpected[i].warning) {
+      assert_equals(sortedActual[i].warning.message,
+                    sortedExpected[i].warning.message,
+                    'warning message should be "' +
+                    sortedExpected[i].warning.message + '"');
+    }
   }
 }
 
 var gAnimationsTests = [
   {
     desc: 'animations on compositor',
     frames: {
       opacity: [0, 1]
@@ -109,19 +115,118 @@ var gAnimationsTests = [
         runningOnCompositor: true
       }
     ]
   }
 ];
 
 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_running_status_equals(animation.effect.runningStatus(),
                                    subtest.expected);
     }));
   }, subtest.desc);
 });
 
+var gPerformanceWarningTests = [
+  {
+    desc: 'preserve-3d transform',
+    frames: {
+      transform: ['translate(0px)', 'translate(100px)']
+    },
+    styles: {
+      style: 'transform-style: preserve-3d',
+      class: 'compositable'
+    },
+    expected: [
+      {
+        property: 'transform',
+        runningOnCompositor: false,
+        warning: {
+          message: "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)']
+    },
+    styles: {
+      style: 'backface-visibility: hidden;',
+      class: 'compositable'
+    },
+    expected: [
+      {
+        property: 'transform',
+        runningOnCompositor: false,
+        warning: {
+          message: "Gecko bug: Async animation of " +
+                   "'backface-visibility: hidden' transforms is not " +
+                   "supported.  See bug 1186204"
+        }
+      }
+    ]
+  },
+  {
+    desc: 'animation on compositor with animation of geometric properties',
+    frames: {
+      width: ['100px', '200px'],
+      transform: ['translate(0px)', 'translate(100px)']
+    },
+    styles: {
+      class: 'compositable'
+    },
+    expected: [
+      {
+        property: 'width',
+        runningOnCompositor: false
+      },
+      {
+        property: 'transform',
+        runningOnCompositor: false,
+        warning: {
+          message: "Performance warning: Async animation of " +
+                   "'transform' or 'opacity' not possible due to animation " +
+                   "of geometric properties on the same element"
+        }
+      }
+    ]
+  },
+];
+
+gPerformanceWarningTests.forEach(function(subtest) {
+  promise_test(function(t) {
+    var div = addDiv(t, subtest.styles);
+    var animation = div.animate(subtest.frames, 100000);
+    return animation.ready.then(t.step_func(function() {
+      assert_running_status_equals(animation.effect.runningStatus(),
+                                   subtest.expected);
+    }));
+  }, subtest.desc);
+});
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'width: 10000px; height: 10000px;' });
+  var animation = div.animate(
+    { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
+  return animation.ready.then(t.step_func(function() {
+    assert_running_status_equals(animation.effect.runningStatus(),
+                                 [ { property: 'transform',
+                                     runningOnCompositor: false } ]);
+    // viewport depends on test environemt.
+    var expected = 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_regexp_match(animation.effect.runningStatus()[0].warning.message,
+                        expected);
+  }));
+}, 'transform on too big element');
+
 </script>
 </body>
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -5587,21 +5587,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;
   }
@@ -5622,38 +5625,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);
@@ -5662,37 +5666,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
@@ -4099,18 +4099,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.
    */