Bug 1244590 - Part 12: Revise ComputeDistance for eUnit_Calc. draft
authorBoris Chiou <boris.chiou@gmail.com>
Thu, 26 May 2016 17:17:12 +0800
changeset 372070 b414baaeaece33511b3a900d9f30dfdf3cc72882
parent 372069 1807224aa6a531dc8ab079a2d7aa713efab0e75e
child 372071 d3752c790c9079c116887ee22d4c208042a4b8d9
push id19429
push userbmo:boris.chiou@gmail.com
push dateFri, 27 May 2016 10:09:46 +0000
bugs1244590
milestone49.0a1
Bug 1244590 - Part 12: Revise ComputeDistance for eUnit_Calc. In order to computed cumulative distances for paced spacing correctly, we need to get used values, instead of computed values, if the common unit is eUnitCalc. MozReview-Commit-ID: J8d442aJP6O
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeUtils.cpp
dom/animation/KeyframeUtils.h
dom/base/nsDOMWindowUtils.cpp
dom/smil/nsSMILCSSValueType.cpp
layout/style/StyleAnimationValue.cpp
layout/style/StyleAnimationValue.h
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -512,17 +512,18 @@ KeyframeEffectReadOnly::UpdateProperties
   if (mTarget) {
     nsTArray<ComputedKeyframeValues> computedValues =
       KeyframeUtils::GetComputedKeyframeValues(mKeyframes, mTarget->mElement,
                                                aStyleContext);
 
     if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
       KeyframeUtils::ApplySpacing(mKeyframes, SpacingMode::paced,
                                   mEffectOptions.mPacedProperty,
-                                  computedValues);
+                                  computedValues,
+                                  mTarget->mElement);
     }
 
     properties =
       KeyframeUtils::GetAnimationPropertiesFromKeyframes(mKeyframes,
                                                          computedValues);
   }
 
   if (mProperties == properties) {
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -386,17 +386,18 @@ RequiresAdditiveAnimation(const nsTArray
 
 static void
 DistributeRange(const Range<Keyframe>& aKeyframes,
                 const Range<Keyframe>& aFilteredKeyframes);
 
 static void
 PaceRange(const Range<Keyframe>& aKeyframes,
           const Range<ComputedKeyframeValues>& aPacedValues,
-          nsCSSProperty aProperty);
+          nsCSSProperty aProperty,
+          dom::Element* aElement);
 
 static nsTArray<ComputedKeyframeValues>
 GetPacedPropertyKeyframeValues(const nsTArray<ComputedKeyframeValues>& aValues,
                                nsCSSProperty aProperty);
 
 // ------------------------------------------------------------------
 //
 // Public API
@@ -460,17 +461,18 @@ KeyframeUtils::GetKeyframesFromObject(JS
 
   return keyframes;
 }
 
 /* static */ void
 KeyframeUtils::ApplySpacing(nsTArray<Keyframe>& aKeyframes,
                             SpacingMode aSpacingMode,
                             nsCSSProperty aProperty,
-                            nsTArray<ComputedKeyframeValues>& aComputedValues)
+                            nsTArray<ComputedKeyframeValues>& aComputedValues,
+                            dom::Element* aElement)
 {
   if (aKeyframes.IsEmpty()) {
     return;
   }
 
   nsTArray<ComputedKeyframeValues> pacedValues;
   if (aSpacingMode == SpacingMode::paced) {
     MOZ_ASSERT(IsAnimatableProperty(aProperty),
@@ -537,17 +539,17 @@ KeyframeUtils::ApplySpacing(nsTArray<Key
                       Range<Keyframe>(keyframeA.get(), pacedA - keyframeA + 1));
       DistributeRange(Range<Keyframe>(keyframeA.get(), rangeLen),
                       Range<Keyframe>(pacedB.get(), keyframeB - pacedB + 1));
       // c) Apply paced offsets in (Paced A, Paced B).
       const size_t idx = pacedA - begin;
       const size_t pacedLen = pacedB - pacedA + 1;
       PaceRange(Range<Keyframe>(pacedA.get(), pacedLen),
                 Range<ComputedKeyframeValues>(&pacedValues[idx], pacedLen),
-                aProperty);
+                aProperty, aElement);
       // d) Fill null computed offsets in (Paced A, Paced B).
       for (RangedPtr<Keyframe> frame = pacedA + 1; frame < pacedB; ++frame) {
         if ((*frame).mComputedOffset != Keyframe::kComputedOffsetNotSet) {
           continue;
         }
 
         RangedPtr<Keyframe> start = frame - 1;
         RangedPtr<Keyframe> end = frame + 1;
@@ -1318,21 +1320,24 @@ DistributeRange(const Range<Keyframe>& a
  * Apply paced computed offsets in (Paced A, Paced B).
  *
  * @param aKeyframes The sequence of keyframes between whose endpoints we should
  *   apply paced distribute, [Paced A, Paced B], and both Paced A & Paced B
  *   should be paceable.
  * @param aPacedValues The sequence of computed values of the paced property.
  *   We get this by GetPacedPropertyKeyframeValues().
  * @param aProperty The paced property.
+ * @param aElement The context element for computing the distance between each
+ *   pair of keyframes.
  */
 static void
 PaceRange(const Range<Keyframe>& aKeyframes,
           const Range<ComputedKeyframeValues>& aPacedValues,
-          nsCSSProperty aProperty)
+          nsCSSProperty aProperty,
+          dom::Element* aElement)
 {
   const size_t len = aKeyframes.length();
   if (len < 3) {
     return;
   }
 
   auto IsPaceable = [](ComputedKeyframeValues& aComputedValues) {
     return !aComputedValues.IsEmpty();
@@ -1379,17 +1384,18 @@ PaceRange(const Range<Keyframe>& aKeyfra
         const StyleAnimationValue& preValue = prePairs[subIdx].mValue;
         const StyleAnimationValue& curValue = curPairs[subIdx].mValue;
         nsCSSProperty subProperty = prePairs[subIdx].mProperty;
         MOZ_ASSERT(curPairs[subIdx].mProperty == subProperty,
                    "subProperty mismatch");
 
         double componentDist = 0.0;
         if (!StyleAnimationValue::ComputeDistance(subProperty, preValue,
-                                                  curValue, componentDist)) {
+                                                  curValue, aElement,
+                                                  componentDist)) {
           failed = true;
           break;
         }
 
         // FIXME: Any way to avoid overflow?
         dist += componentDist * componentDist;
       }
 
@@ -1398,17 +1404,18 @@ PaceRange(const Range<Keyframe>& aKeyfra
       }
 
       dist = sqrt(dist);
     } else {
       // If the property is longhand, we just use the 1st value.
       const StyleAnimationValue& preValue = aPacedValues[preIdx][0].mValue;
       const StyleAnimationValue& curValue = aPacedValues[i][0].mValue;
       if (!StyleAnimationValue::ComputeDistance(aProperty, preValue,
-                                                curValue, dist)) {
+                                                curValue, aElement,
+                                                dist)) {
         failed = true;
         break;
       }
     }
     cumulativeDist[i] = cumulativeDist[i - 1] + dist;
     preIdx = i;
   }
 
--- a/dom/animation/KeyframeUtils.h
+++ b/dom/animation/KeyframeUtils.h
@@ -84,21 +84,23 @@ public:
    *
    * https://w3c.github.io/web-animations/#spacing-keyframes
    *
    * @param aKeyframes The set of keyframes to adjust.
    * @param aSpacingMode The spacing mode to apply.
    * @param aProperty The paced property.
    * @param aComputedValues The set of computed keyframe values got by
    *   GetComputedKeyframeValues.
+   * @param aElement The context element for computing the cumulative distance.
    */
   static void ApplySpacing(nsTArray<Keyframe>& aKeyframes,
                            SpacingMode aSpacingMode,
                            nsCSSProperty aProperty,
-                           nsTArray<ComputedKeyframeValues>& aComputedValues);
+                           nsTArray<ComputedKeyframeValues>& aComputedValues,
+                           dom::Element* aElement = nullptr);
 
   /**
    * Fills in the mComputedOffset member of each keyframe in the given array
    * using distribute spacing mode.
    *
    * @param aKeyframes The set of keyframes to adjust.
    */
   static void ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes);
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2531,17 +2531,18 @@ nsDOMWindowUtils::ComputeAnimationDistan
 
   StyleAnimationValue v1, v2;
   if (property == eCSSProperty_UNKNOWN ||
       !ComputeAnimationValue(property, content->AsElement(), aValue1, v1) ||
       !ComputeAnimationValue(property, content->AsElement(), aValue2, v2)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
-  if (!StyleAnimationValue::ComputeDistance(property, v1, v2, *aResult)) {
+  if (!StyleAnimationValue::ComputeDistance(property, v1, v2, nullptr,
+                                            *aResult)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 nsresult
 nsDOMWindowUtils::RenderDocument(const nsRect& aRect,
--- a/dom/smil/nsSMILCSSValueType.cpp
+++ b/dom/smil/nsSMILCSSValueType.cpp
@@ -273,16 +273,17 @@ nsSMILCSSValueType::ComputeDistance(cons
     &fromWrapper->mCSSValue : nullptr;
   const StyleAnimationValue* toCSSValue = &toWrapper->mCSSValue;
   if (!FinalizeStyleAnimationValues(fromCSSValue, toCSSValue)) {
     return NS_ERROR_FAILURE;
   }
 
   return StyleAnimationValue::ComputeDistance(toWrapper->mPropID,
                                               *fromCSSValue, *toCSSValue,
+                                              nullptr,
                                               aDistance) ?
     NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 nsSMILCSSValueType::Interpolate(const nsSMILValue& aStartVal,
                                 const nsSMILValue& aEndVal,
                                 double aUnitDistance,
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -16,16 +16,17 @@
 #include "nsIStyleRule.h"
 #include "mozilla/css/StyleRule.h"
 #include "nsString.h"
 #include "nsStyleContext.h"
 #include "nsStyleSet.h"
 #include "nsComputedDOMStyle.h"
 #include "nsCSSParser.h"
 #include "nsCSSPseudoElements.h"
+#include "nsROCSSPrimitiveValue.h"
 #include "mozilla/css/Declaration.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Likely.h"
 #include "gfxMatrix.h"
 #include "gfxQuaternion.h"
 #include "nsIDocument.h"
 #include "nsIFrame.h"
@@ -470,23 +471,132 @@ CalcPositionCoordSquareDistance(const ns
   PixelCalcValue calcVal1 = CalcBackgroundCoord(aPos1);
   PixelCalcValue calcVal2 = CalcBackgroundCoord(aPos2);
 
   float difflen = calcVal2.mLength - calcVal1.mLength;
   float diffpct = calcVal2.mPercent - calcVal1.mPercent;
   return difflen * difflen + diffpct * diffpct;
 }
 
+bool
+GetContainingBlockPixelValue(const nsAString& aProperty,
+                             dom::Element* aElement,
+                             double& aResult)
+{
+  dom::Element* parentElement = aElement->GetParentElement();
+  if (!parentElement) {
+    return false;
+  }
+
+  nsIDocument* doc = parentElement->GetUncomposedDoc();
+  if (!doc) {
+    return false;
+  }
+
+  RefPtr<nsComputedDOMStyle> style =
+    NS_NewComputedDOMStyle(parentElement, EmptyString(), doc->GetShell());
+  if (!style) {
+    return false;
+  }
+
+  ErrorResult rv;
+  RefPtr<dom::CSSValue> cssValue = style->GetPropertyCSSValue(aProperty, rv);
+  if (rv.Failed()) {
+    return false;
+  }
+
+  nsROCSSPrimitiveValue* value = cssValue->AsPrimitiveValue();
+  if (!value || value->PrimitiveType() != nsIDOMCSSPrimitiveValue::CSS_PX) {
+    return false;
+  }
+
+  aResult = (double)value->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX, rv);
+  return !rv.Failed();
+}
+
+bool
+GetUsedAppUnitValue(nsCSSProperty aProperty,
+                    const StyleAnimationValue& aValue,
+                    dom::Element* aElement,
+                    double& aResult)
+{
+  if (aValue.GetUnit() == StyleAnimationValue::eUnit_Coord) {
+    aResult = (double)aValue.GetCoordValue();
+    return true;
+  }
+
+  MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Percent ||
+             aValue.GetUnit() == StyleAnimationValue::eUnit_Calc,
+             "Unsupported unit");
+
+  if (!aElement) {
+    return false;
+  }
+
+  double length = 0.0;
+  double percent = 0.0;
+  if (aValue.GetUnit() == StyleAnimationValue::eUnit_Percent) {
+    percent = aValue.GetPercentValue();
+  } else {
+    nsCSSValue *val = aValue.GetCSSValueValue();
+    PixelCalcValue pValue = ExtractCalcValueInternal(*val);
+    length = pValue.mLength;
+    percent = pValue.mPercent;
+  }
+
+  double base = 0.0;
+  switch (aProperty) {
+    case eCSSProperty_width:
+    case eCSSProperty_left:
+    case eCSSProperty_right:
+    case eCSSProperty_margin_left:
+    case eCSSProperty_margin_right:
+    case eCSSProperty_padding_left:
+    case eCSSProperty_padding_right:
+    case eCSSProperty_min_width:
+    case eCSSProperty_max_width:
+      if (!GetContainingBlockPixelValue(NS_LITERAL_STRING("width"),
+                                        aElement, base)) {
+        return false;
+      }
+      break;
+
+    case eCSSProperty_height:
+    case eCSSProperty_top:
+    case eCSSProperty_bottom:
+    case eCSSProperty_margin_top:
+    case eCSSProperty_margin_bottom:
+    case eCSSProperty_padding_top:
+    case eCSSProperty_padding_bottom:
+    case eCSSProperty_min_height:
+    case eCSSProperty_max_height:
+      if (!GetContainingBlockPixelValue(NS_LITERAL_STRING("height"),
+                                        aElement, base)) {
+        return false;
+      }
+      break;
+
+    default:
+      // Not supported.
+      return false;
+  }
+
+  aResult = length +
+            percent * (double)nsPresContext::CSSPixelsToAppUnits((float)base);
+  return true;
+}
+
 // CLASS METHODS
 // -------------
 
 bool
 StyleAnimationValue::ComputeDistance(nsCSSProperty aProperty,
                                      const StyleAnimationValue& aStartValue,
                                      const StyleAnimationValue& aEndValue,
+                                     dom::Element* aElement,
                                      double& aDistance)
 {
   Unit commonUnit =
     GetCommonUnit(aProperty, aStartValue.GetUnit(), aEndValue.GetUnit());
 
   switch (commonUnit) {
     case eUnit_Null:
     case eUnit_Auto:
@@ -580,16 +690,29 @@ StyleAnimationValue::ComputeDistance(nsC
       double diffR = startR - endR;
       double diffG = startG - endG;
       double diffB = startB - endB;
       aDistance = sqrt(diffA * diffA + diffR * diffR +
                        diffG * diffG + diffB * diffB);
       return true;
     }
     case eUnit_Calc: {
+      // If passing a valid element, we will try to get used values of
+      // aStartValue and aEndValue and then calculate the distance
+      // like eUnit_Coord.
+      if (aElement) {
+        double v1 = 0.0;
+        double v2 = 0.0;
+        if (GetUsedAppUnitValue(aProperty, aStartValue, aElement, v1) &&
+            GetUsedAppUnitValue(aProperty, aEndValue, aElement, v2)) {
+          aDistance = Abs(v2 - v1);
+          return true;
+        }
+      }
+
       PixelCalcValue v1 = ExtractCalcValue(aStartValue);
       PixelCalcValue v2 = ExtractCalcValue(aEndValue);
       float difflen = v2.mLength - v1.mLength;
       float diffpct = v2.mPercent - v1.mPercent;
       aDistance = sqrt(difflen * difflen + diffpct * diffpct);
       return true;
     }
     case eUnit_ObjectPosition: {
@@ -844,16 +967,17 @@ StyleAnimationValue::ComputeDistance(nsC
             (color2.GetColorValue(), StyleAnimationValue::ColorConstructor);
           double colorDistance;
 
         #ifdef DEBUG
           bool ok =
         #endif
             StyleAnimationValue::ComputeDistance(eCSSProperty_color,
                                                  color1Value, color2Value,
+                                                 nullptr,
                                                  colorDistance);
           MOZ_ASSERT(ok, "should not fail");
           squareDistance += colorDistance * colorDistance;
         }
 
         shadow1 = shadow1->mNext;
         shadow2 = shadow2->mNext;
         MOZ_ASSERT(!shadow1 == !shadow2, "lists should be same length");
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -68,22 +68,27 @@ public:
    *
    * If this method succeeds, the returned distance value is guaranteed to be
    * non-negative.
    *
    * @param aStartValue The start of the interval for which the distance
    *                    should be calculated.
    * @param aEndValue   The end of the interval for which the distance
    *                    should be calculated.
+   * @param aElement    The context element for eUnit_Calc. If it is non-null,
+   *                    we may use this context element to get the used values
+   *                    for aProperty and compute the distance by used values,
+   *                    instead of computed calc values.
    * @param aDistance   The result of the calculation.
    * @return true on success, false on failure.
    */
   static bool ComputeDistance(nsCSSProperty aProperty,
                               const StyleAnimationValue& aStartValue,
                               const StyleAnimationValue& aEndValue,
+                              dom::Element* aElement,
                               double& aDistance);
 
   /**
    * Calculates an interpolated value that is the specified |aPortion| between
    * the two given values.
    *
    * This really just does the following calculation:
    *   aResultValue = (1.0 - aPortion) * aStartValue + aPortion * aEndValue