Support transitions/animations of background-position with right/bottom-relative...
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 2 Oct 2016 01:05:14 +0000 (01:05 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 2 Oct 2016 01:05:14 +0000 (01:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=162048

Reviewed by Dean Jackson.
Source/WebCore:

Make transitions between "background-position: 10px 20px" and "background-position: right 10px bottom 20px"
work. We do this by by converting "right 10px" to "calc(100% - 10px)" when blending.

Also improve logging of calculated lengths, and better animation logging for FillLayer properties.

Test: transitions/background-position-transitions.html

* page/animation/CSSPropertyAnimation.cpp:
(WebCore::FillLayerAnimationPropertyWrapperBase::FillLayerAnimationPropertyWrapperBase): Keep the propertyID
around so logging can use it.
(WebCore::FillLayerAnimationPropertyWrapperBase::property):
(WebCore::FillLayerPropertyWrapperGetter::FillLayerPropertyWrapperGetter):
(WebCore::FillLayerPropertyWrapperGetter::value):
(WebCore::FillLayerPropertyWrapper::FillLayerPropertyWrapper):
(WebCore::createCalculatedLength):
(WebCore::FillLayerPositionPropertyWrapper::FillLayerPositionPropertyWrapper):
(WebCore::FillLayerRefCountedPropertyWrapper::FillLayerRefCountedPropertyWrapper):
(WebCore::FillLayerStyleImagePropertyWrapper::FillLayerStyleImagePropertyWrapper):
(WebCore::FillLayersPropertyWrapper::FillLayersPropertyWrapper):
(WebCore::CSSPropertyAnimation::blendProperties): Blend then log, so that the logging
can show the result.
* platform/CalculationValue.cpp:
(WebCore::CalcExpressionNumber::dump):
(WebCore::CalcExpressionBinaryOperation::dump):
(WebCore::CalcExpressionLength::dump):
(WebCore::CalcExpressionBlendLength::dump):
(WebCore::operator<<):
* platform/CalculationValue.h:
* platform/Length.cpp:
(WebCore::operator<<):

LayoutTests:

* transitions/background-position-transitions-expected.txt: Added.
* transitions/background-position-transitions.html: Added.
* transitions/resources/transition-test-helpers.js:
* transitions/svg-transitions-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@206713 268f45cc-cd09-0410-ab3c-d52691b4dbfc

16 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/shapes/shape-outside-floats/shape-outside-animation-expected.txt
LayoutTests/fast/shapes/shape-outside-floats/shape-outside-animation.html
LayoutTests/transitions/background-position-transitions-expected.txt [new file with mode: 0644]
LayoutTests/transitions/background-position-transitions.html [new file with mode: 0644]
LayoutTests/transitions/resources/transition-test-helpers.js
LayoutTests/transitions/svg-transitions-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/css/StyleBuilderConverter.h
Source/WebCore/page/animation/CSSPropertyAnimation.cpp
Source/WebCore/platform/CalculationValue.cpp
Source/WebCore/platform/CalculationValue.h
Source/WebCore/platform/Length.cpp
Source/WebCore/platform/Length.h
Source/WebCore/rendering/style/BasicShapes.cpp
Tools/TestWebKitAPI/Tests/WebCore/CalculationValue.cpp

index 255ddeb..47b136d 100644 (file)
@@ -1,5 +1,17 @@
 2016-10-01  Simon Fraser  <simon.fraser@apple.com>
 
+        Support transitions/animations of background-position with right/bottom-relative values
+        https://bugs.webkit.org/show_bug.cgi?id=162048
+
+        Reviewed by Dean Jackson.
+
+        * transitions/background-position-transitions-expected.txt: Added.
+        * transitions/background-position-transitions.html: Added.
+        * transitions/resources/transition-test-helpers.js:
+        * transitions/svg-transitions-expected.txt:
+
+2016-10-01  Simon Fraser  <simon.fraser@apple.com>
+
         Bad cast when CSS position programmatically changed from -webkit-sticky to fixed
         https://bugs.webkit.org/show_bug.cgi?id=160826
 
index 8afe5cf..b8a30e5 100644 (file)
@@ -22,6 +22,6 @@ PASS - "webkitShapeOutside" property for "circle-to-topleft-box" element at 1s s
 PASS - "webkitShapeOutside" property for "circle-to-bottomright-using-keyword-box" element at 1s saw something close to: circle(35% at 75% 75%)
 PASS - "webkitShapeOutside" property for "circle-to-bottomright-using-keyword-box" element at 1s saw something close to: circle(35% at 75% 75%)
 PASS - "webkitShapeOutside" property for "circle-to-bottomright-extended-box" element at 1s saw something close to: circle(35% at 75% 75%)
-PASS - "webkitShapeOutside" property for "circle-to-bottomright-extended-using-keyword-box" element at 1s saw something close to: circle(35% at calc((50% * 0.5) + ((100% - 10%) * 0.5)) calc((50% * 0.5) + ((100% - 20px) * 0.5)))
-PASS - "webkitShapeOutside" property for "circle-to-bottomright-extended-using-keyword-2-box" element at 1s saw something close to: circle(35% at calc((50% * 0.5) + ((100% - 10%) * 0.5)) calc((50% * 0.5) + ((100% - 10px) * 0.5)))
+PASS - "webkitShapeOutside" property for "circle-to-bottomright-extended-using-keyword-box" element at 1s saw something close to: circle(35% at 70% calc((50% * 0.5) + ((100% - 20px) * 0.5)))
+PASS - "webkitShapeOutside" property for "circle-to-bottomright-extended-using-keyword-2-box" element at 1s saw something close to: circle(35% at 70% calc((50% * 0.5) + ((100% - 10px) * 0.5)))
 
index 82e701e..e89f4c4 100644 (file)
       ["circle-to-bottomright-using-keyword-anim",  1, "circle-to-bottomright-using-keyword-box", "webkitShapeOutside", "circle(35% at 75% 75%)", 0.05],
       ["circle-to-bottomright-using-keyword-anim",  1, "circle-to-bottomright-using-keyword-box", "webkitShapeOutside", "circle(35% at 75% 75%)", 0.05],
       ["circle-to-bottomright-extended-anim",  1, "circle-to-bottomright-extended-box", "webkitShapeOutside", "circle(35% at 75% 75%)", 0.05],
-      ["circle-to-bottomright-extended-using-keyword-anim",  1, "circle-to-bottomright-extended-using-keyword-box", "webkitShapeOutside", "circle(35% at calc((50% * 0.5) + ((100% - 10%) * 0.5)) calc((50% * 0.5) + ((100% - 20px) * 0.5)))", 0.01],
-      ["circle-to-bottomright-extended-using-keyword-2-anim",  1, "circle-to-bottomright-extended-using-keyword-2-box", "webkitShapeOutside", "circle(35% at calc((50% * 0.5) + ((100% - 10%) * 0.5)) calc((50% * 0.5) + ((100% - 10px) * 0.5)))", 0.01],
+      ["circle-to-bottomright-extended-using-keyword-anim",  1, "circle-to-bottomright-extended-using-keyword-box", "webkitShapeOutside", "circle(35% at 70% calc((50% * 0.5) + ((100% - 20px) * 0.5)))", 0.01],
+      ["circle-to-bottomright-extended-using-keyword-2-anim",  1, "circle-to-bottomright-extended-using-keyword-2-box", "webkitShapeOutside", "circle(35% at 70% calc((50% * 0.5) + ((100% - 10px) * 0.5)))", 0.01],
     ];
     
     runAnimationTest(expectedValues);
diff --git a/LayoutTests/transitions/background-position-transitions-expected.txt b/LayoutTests/transitions/background-position-transitions-expected.txt
new file mode 100644 (file)
index 0000000..ac9ba48
--- /dev/null
@@ -0,0 +1,4 @@
+PASS - "background-position" property for "box1" element at 0.5s saw something close to: 45,60
+PASS - "background-position" property for "box2" element at 0.5s saw something close to: 45,60
+PASS - "background-position" property for "box3" element at 0.5s saw something close to: 10,0.5,100,80,0,0.5,20,0.5,100,100,0,0.5
+
diff --git a/LayoutTests/transitions/background-position-transitions.html b/LayoutTests/transitions/background-position-transitions.html
new file mode 100644 (file)
index 0000000..cdf7494
--- /dev/null
@@ -0,0 +1,66 @@
+<!DOCTYPE>
+
+<html>
+<head>
+    <style>
+        .box {
+            height: 100px;
+            width: 100px;
+            margin: 10px;
+            background: url('../fast/backgrounds/repeat/resources/gradient.gif') repeat 0 0;
+            transition-duration: 1s;
+            transition-timing-function: linear;
+            transition-property: background-position;
+        }
+
+        #box1 {
+            background-position: 10px 20px;
+        }
+
+        body.final #box1 {
+            background-position: left 80px top 100px;
+        }
+
+        #box2 {
+            background-position: right 10px bottom 20px;
+        }
+
+        body.final #box2 {
+            background-position: right 80px bottom 100px;
+        }
+
+        #box3 {
+            background-position: 10px 20px;
+        }
+
+        body.final #box3 {
+            background-position: right 80px bottom 100px;
+        }
+    </style>
+    <script src="resources/transition-test-helpers.js" type="text/javascript"></script>
+    <script>
+        const expectedValues = [
+        // [time, element-id, property, expected-value, tolerance]
+        [0.5, 'box1', 'background-position', [45, 60], 2],
+        [0.5, 'box2', 'background-position', [45, 60], 2],
+        [0.5, 'box3', 'background-position', [10,0.5,100,80,0,0.5,20,0.5,100,100,0,0.5], 2], // Numbers extracted from a calc() expression.
+        ];
+
+        function setupTest()
+        {
+            document.body.classList.add('final');
+        }
+
+        runTransitionTest(expectedValues, setupTest, usePauseAPI);
+    </script>
+</head>
+<body>
+
+    <div id="box1" class="box"></div>
+    <div id="box2" class="box"></div>
+    <div id="box3" class="box"></div>
+
+    <pre id="result"></pre>
+
+</body>
+</html>
index 069986e..f0b7d72 100644 (file)
@@ -110,6 +110,77 @@ function parseClipPath(s)
     return null;
 }
 
+function hasFloatValue(value)
+{
+    switch (value.primitiveType) {
+    case CSSPrimitiveValue.CSS_FR:
+    case CSSPrimitiveValue.CSS_NUMBER:
+    case CSSPrimitiveValue.CSS_PARSER_INTEGER:
+    case CSSPrimitiveValue.CSS_PERCENTAGE:
+    case CSSPrimitiveValue.CSS_EMS:
+    case CSSPrimitiveValue.CSS_EXS:
+    case CSSPrimitiveValue.CSS_CHS:
+    case CSSPrimitiveValue.CSS_REMS:
+    case CSSPrimitiveValue.CSS_PX:
+    case CSSPrimitiveValue.CSS_CM:
+    case CSSPrimitiveValue.CSS_MM:
+    case CSSPrimitiveValue.CSS_IN:
+    case CSSPrimitiveValue.CSS_PT:
+    case CSSPrimitiveValue.CSS_PC:
+    case CSSPrimitiveValue.CSS_DEG:
+    case CSSPrimitiveValue.CSS_RAD:
+    case CSSPrimitiveValue.CSS_GRAD:
+    case CSSPrimitiveValue.CSS_TURN:
+    case CSSPrimitiveValue.CSS_MS:
+    case CSSPrimitiveValue.CSS_S:
+    case CSSPrimitiveValue.CSS_HZ:
+    case CSSPrimitiveValue.CSS_KHZ:
+    case CSSPrimitiveValue.CSS_DIMENSION:
+    case CSSPrimitiveValue.CSS_VW:
+    case CSSPrimitiveValue.CSS_VH:
+    case CSSPrimitiveValue.CSS_VMIN:
+    case CSSPrimitiveValue.CSS_VMAX:
+    case CSSPrimitiveValue.CSS_DPPX:
+    case CSSPrimitiveValue.CSS_DPI:
+    case CSSPrimitiveValue.CSS_DPCM:
+        return true;
+    }
+    return false;
+}
+
+function getNumericValue(cssValue)
+{
+    if (hasFloatValue(cssValue.primitiveType))
+        return cssValue.getFloatValue(cssValue.primitiveType);
+
+    return -1;
+}
+
+function isCalcPrimitiveValue(value)
+{
+    switch (value.primitiveType) {
+    case 113: // CSSPrimitiveValue.CSS_CALC:
+    case 114: // CSSPrimitiveValue.CSS_CALC_PERCENTAGE_WITH_NUMBER:
+    case 115: // CSSPrimitiveValue.CSS_CALC_PERCENTAGE_WITH_LENGTH:
+    return true;
+    }
+    return false;
+}
+
+function extractNumbersFromCalcExpression(value, values)
+{
+    var calcRegexp = /^calc\((.+)\)$/;
+    var result = calcRegexp.exec(value.cssText);
+    var numberMatch = /([^\.\-0-9]*)(-?[\.0-9]+)/;
+    var remainder = result[1];
+    var match;
+    while ((match = numberMatch.exec(remainder)) !== null) {
+        var skipLength = match[1].length + match[2].length;
+        values.push(parseFloat(match[2]))
+        remainder = remainder.substr(skipLength + 1);
+    }
+}
+
 function checkExpectedValue(expected, index)
 {
     var time = expected[index][0];
@@ -194,23 +265,27 @@ function checkExpectedValue(expected, index)
         if (computedStyle.cssValueType == CSSValue.CSS_VALUE_LIST) {
             var values = [];
             for (var i = 0; i < computedStyle.length; ++i) {
-                switch (computedStyle[i].cssValueType) {
+                var styleValue = computedStyle[i];
+                switch (styleValue.cssValueType) {
                   case CSSValue.CSS_PRIMITIVE_VALUE:
-                    values.push(computedStyle[i].getFloatValue(CSSPrimitiveValue.CSS_NUMBER));
+                    if (hasFloatValue(styleValue))
+                        values.push(styleValue.getFloatValue(CSSPrimitiveValue.CSS_NUMBER));
+                    else if (isCalcPrimitiveValue(styleValue))
+                        extractNumbersFromCalcExpression(styleValue, values);
                     break;
                   case CSSValue.CSS_CUSTOM:
                     // arbitrarily pick shadow-x and shadow-y
                     if (isShadow) {
-                      var shadowXY = getShadowXY(computedStyle[i]);
+                      var shadowXY = getShadowXY(styleValue);
                       values.push(shadowXY[0]);
                       values.push(shadowXY[1]);
                     } else
-                      values.push(computedStyle[i].cssText);
+                      values.push(styleValue.cssText);
                     break;
                 }
             }
             computedValue = values.join(',');
-            pass = true;
+            pass = values.length > 0;
             for (var i = 0; i < values.length; ++i)
                 pass &= isCloseEnough(values[i], expectedValue[i], tolerance);
         } else if (computedStyle.cssValueType == CSSValue.CSS_PRIMITIVE_VALUE) {
index 58505ea..c11447a 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 293: Failed to pause 'fill' transition on element 'rect7'
+CONSOLE MESSAGE: line 368: Failed to pause 'fill' transition on element 'rect7'
 Example
 PASS - "fill-opacity" property for "rect1" element at 1s saw something close to: 0.6
 PASS - "stroke-width" property for "rect1" element at 1s saw something close to: 3
index bdd5578..b181536 100644 (file)
@@ -1,5 +1,43 @@
 2016-10-01  Simon Fraser  <simon.fraser@apple.com>
 
+        Support transitions/animations of background-position with right/bottom-relative values
+        https://bugs.webkit.org/show_bug.cgi?id=162048
+
+        Reviewed by Dean Jackson.
+        
+        Make transitions between "background-position: 10px 20px" and "background-position: right 10px bottom 20px"
+        work. We do this by by converting "right 10px" to "calc(100% - 10px)" when blending.
+        
+        Also improve logging of calculated lengths, and better animation logging for FillLayer properties.
+
+        Test: transitions/background-position-transitions.html
+
+        * page/animation/CSSPropertyAnimation.cpp:
+        (WebCore::FillLayerAnimationPropertyWrapperBase::FillLayerAnimationPropertyWrapperBase): Keep the propertyID
+        around so logging can use it.
+        (WebCore::FillLayerAnimationPropertyWrapperBase::property):
+        (WebCore::FillLayerPropertyWrapperGetter::FillLayerPropertyWrapperGetter):
+        (WebCore::FillLayerPropertyWrapperGetter::value):
+        (WebCore::FillLayerPropertyWrapper::FillLayerPropertyWrapper):
+        (WebCore::createCalculatedLength):
+        (WebCore::FillLayerPositionPropertyWrapper::FillLayerPositionPropertyWrapper):
+        (WebCore::FillLayerRefCountedPropertyWrapper::FillLayerRefCountedPropertyWrapper):
+        (WebCore::FillLayerStyleImagePropertyWrapper::FillLayerStyleImagePropertyWrapper):
+        (WebCore::FillLayersPropertyWrapper::FillLayersPropertyWrapper):
+        (WebCore::CSSPropertyAnimation::blendProperties): Blend then log, so that the logging
+        can show the result.
+        * platform/CalculationValue.cpp:
+        (WebCore::CalcExpressionNumber::dump):
+        (WebCore::CalcExpressionBinaryOperation::dump):
+        (WebCore::CalcExpressionLength::dump):
+        (WebCore::CalcExpressionBlendLength::dump):
+        (WebCore::operator<<):
+        * platform/CalculationValue.h:
+        * platform/Length.cpp:
+        (WebCore::operator<<):
+
+2016-10-01  Simon Fraser  <simon.fraser@apple.com>
+
         Bad cast when CSS position programmatically changed from -webkit-sticky to fixed
         https://bugs.webkit.org/show_bug.cgi?id=160826
 
index 050d673..a50f23b 100644 (file)
@@ -157,7 +157,6 @@ private:
     static Length parseSnapCoordinate(StyleResolver&, const CSSValue&);
 #endif
 
-    static Length convertTo100PercentMinusLength(const Length&);
     static Length convertPositionComponent(StyleResolver&, const CSSPrimitiveValue&);
 
 #if ENABLE(CSS_GRID_LAYOUT)
@@ -311,18 +310,6 @@ inline LengthSize StyleBuilderConverter::convertRadius(StyleResolver& styleResol
     return LengthSize(radiusWidth, radiusHeight);
 }
 
-inline Length StyleBuilderConverter::convertTo100PercentMinusLength(const Length& length)
-{
-    if (length.isPercent())
-        return Length(100 - length.value(), Percent);
-    
-    // Turn this into a calc expression: calc(100% - length)
-    auto lhs = std::make_unique<CalcExpressionLength>(Length(100, Percent));
-    auto rhs = std::make_unique<CalcExpressionLength>(length);
-    auto op = std::make_unique<CalcExpressionBinaryOperation>(WTFMove(lhs), WTFMove(rhs), CalcSubtract);
-    return Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
-}
-
 inline Length StyleBuilderConverter::convertPositionComponent(StyleResolver& styleResolver, const CSSPrimitiveValue& value)
 {
     Length length;
index 0755c0d..71b93e9 100644 (file)
@@ -39,6 +39,7 @@
 #include "CSSPrimitiveValue.h"
 #include "CSSPropertyNames.h"
 #include "CachedImage.h"
+#include "CalculationValue.h"
 #include "ClipPathOperation.h"
 #include "FloatConversion.h"
 #include "FontTaggedSettings.h"
@@ -958,14 +959,23 @@ private:
 class FillLayerAnimationPropertyWrapperBase {
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    FillLayerAnimationPropertyWrapperBase()
+    FillLayerAnimationPropertyWrapperBase(CSSPropertyID property)
+        : m_property(property)
     {
     }
+    
+    CSSPropertyID property() const { return m_property; }
 
     virtual ~FillLayerAnimationPropertyWrapperBase() { }
 
     virtual bool equals(const FillLayer*, const FillLayer*) const = 0;
     virtual void blend(const AnimationBase*, FillLayer*, const FillLayer*, const FillLayer*, double) const = 0;
+
+#if !LOG_DISABLED
+    virtual void logBlend(const FillLayer* result, const FillLayer*, const FillLayer*, double) const = 0;
+#endif
+private:
+    CSSPropertyID m_property;
 };
 
 template <typename T>
@@ -973,8 +983,9 @@ class FillLayerPropertyWrapperGetter : public FillLayerAnimationPropertyWrapperB
     WTF_MAKE_FAST_ALLOCATED;
     WTF_MAKE_NONCOPYABLE(FillLayerPropertyWrapperGetter);
 public:
-    FillLayerPropertyWrapperGetter(T (FillLayer::*getter)() const)
-        : m_getter(getter)
+    FillLayerPropertyWrapperGetter(CSSPropertyID property, T (FillLayer::*getter)() const)
+        : FillLayerAnimationPropertyWrapperBase(property)
+        , m_getter(getter)
     {
     }
 
@@ -987,6 +998,18 @@ public:
         return (a->*m_getter)() == (b->*m_getter)();
     }
 
+    T value(const FillLayer* layer) const
+    {
+        return (layer->*m_getter)();
+    }
+
+#if !LOG_DISABLED
+    void logBlend(const FillLayer* result, const FillLayer* a, const FillLayer* b, double progress) const override
+    {
+        LOG_WITH_STREAM(Animations, stream << "  blending " << getPropertyName(property()) << " from " << value(a) << " to " << value(b) << " at " << TextStream::FormatNumberRespectingIntegers(progress) << " -> " << value(result));
+    }
+#endif
+
 protected:
     T (FillLayer::*m_getter)() const;
 };
@@ -995,36 +1018,119 @@ template <typename T>
 class FillLayerPropertyWrapper : public FillLayerPropertyWrapperGetter<const T&> {
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    FillLayerPropertyWrapper(const T& (FillLayer::*getter)() const, void (FillLayer::*setter)(T))
-        : FillLayerPropertyWrapperGetter<const T&>(getter)
+    FillLayerPropertyWrapper(CSSPropertyID property, const T& (FillLayer::*getter)() const, void (FillLayer::*setter)(T))
+        : FillLayerPropertyWrapperGetter<const T&>(property, getter)
         , m_setter(setter)
     {
     }
 
-    virtual void blend(const AnimationBase* anim, FillLayer* dst, const FillLayer* a, const FillLayer* b, double progress) const
+    void blend(const AnimationBase* anim, FillLayer* dst, const FillLayer* a, const FillLayer* b, double progress) const override
     {
         (dst->*m_setter)(blendFunc(anim, (a->*FillLayerPropertyWrapperGetter<const T&>::m_getter)(), (b->*FillLayerPropertyWrapperGetter<const T&>::m_getter)(), progress));
     }
 
+#if !LOG_DISABLED
+    void logBlend(const FillLayer* result, const FillLayer* a, const FillLayer* b, double progress) const override
+    {
+        LOG_WITH_STREAM(Animations, stream << "  blending " << getPropertyName(FillLayerPropertyWrapperGetter<const T&>::property())
+            << " from " << FillLayerPropertyWrapperGetter<const T&>::value(a)
+            << " to " << FillLayerPropertyWrapperGetter<const T&>::value(b)
+            << " at " << TextStream::FormatNumberRespectingIntegers(progress) << " -> " << FillLayerPropertyWrapperGetter<const T&>::value(result));
+    }
+#endif
+
 protected:
     void (FillLayer::*m_setter)(T);
 };
 
+class FillLayerPositionPropertyWrapper : public FillLayerPropertyWrapperGetter<const Length&> {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    FillLayerPositionPropertyWrapper(CSSPropertyID property, const Length& (FillLayer::*lengthGetter)() const, void (FillLayer::*lengthSetter)(Length), Edge (FillLayer::*originGetter)() const, void (FillLayer::*originSetter)(Edge), Edge farEdge)
+        : FillLayerPropertyWrapperGetter<const Length&>(property, lengthGetter)
+        , m_lengthSetter(lengthSetter)
+        , m_originGetter(originGetter)
+        , m_originSetter(originSetter)
+        , m_farEdge(farEdge)
+    {
+    }
+
+    bool equals(const FillLayer* a, const FillLayer* b) const override
+    {
+        if (a == b)
+            return true;
+        if (!a || !b)
+            return false;
+
+        Length fromLength = (a->*FillLayerPropertyWrapperGetter<const Length&>::m_getter)();
+        Length toLength = (b->*FillLayerPropertyWrapperGetter<const Length&>::m_getter)();
+        
+        Edge fromEdge = (a->*m_originGetter)();
+        Edge toEdge = (b->*m_originGetter)();
+        
+        return fromLength == toLength && fromEdge == toEdge;
+    }
+
+    void blend(const AnimationBase* anim, FillLayer* dst, const FillLayer* a, const FillLayer* b, double progress) const override
+    {
+        Length fromLength = (a->*FillLayerPropertyWrapperGetter<const Length&>::m_getter)();
+        Length toLength = (b->*FillLayerPropertyWrapperGetter<const Length&>::m_getter)();
+        
+        Edge fromEdge = (a->*m_originGetter)();
+        Edge toEdge = (b->*m_originGetter)();
+        
+        if (fromEdge != toEdge) {
+            // Convert the right/bottom into a calc expression,
+            if (fromEdge == m_farEdge)
+                fromLength = convertTo100PercentMinusLength(fromLength);
+            else if (toEdge == m_farEdge) {
+                toLength = convertTo100PercentMinusLength(toLength);
+                (dst->*m_originSetter)(fromEdge); // Now we have a calc(100% - l), it's relative to the left/top edge.
+            }
+        }
+
+        (dst->*m_lengthSetter)(blendFunc(anim, fromLength, toLength, progress));
+    }
+
+#if !LOG_DISABLED
+    void logBlend(const FillLayer* result, const FillLayer* a, const FillLayer* b, double progress) const override
+    {
+        LOG_WITH_STREAM(Animations, stream << "  blending " << getPropertyName(property()) << " from " << value(a) << " to " << value(b) << " at " << TextStream::FormatNumberRespectingIntegers(progress) << " -> " << value(result));
+    }
+#endif
+
+protected:
+    void (FillLayer::*m_lengthSetter)(Length);
+    Edge (FillLayer::*m_originGetter)() const;
+    void (FillLayer::*m_originSetter)(Edge);
+    Edge m_farEdge;
+};
+
 template <typename T>
 class FillLayerRefCountedPropertyWrapper : public FillLayerPropertyWrapperGetter<T*> {
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    FillLayerRefCountedPropertyWrapper(T* (FillLayer::*getter)() const, void (FillLayer::*setter)(PassRefPtr<T>))
-        : FillLayerPropertyWrapperGetter<T*>(getter)
+    FillLayerRefCountedPropertyWrapper(CSSPropertyID property, T* (FillLayer::*getter)() const, void (FillLayer::*setter)(PassRefPtr<T>))
+        : FillLayerPropertyWrapperGetter<T*>(property, getter)
         , m_setter(setter)
     {
     }
 
-    virtual void blend(const AnimationBase* anim, FillLayer* dst, const FillLayer* a, const FillLayer* b, double progress) const
+    void blend(const AnimationBase* anim, FillLayer* dst, const FillLayer* a, const FillLayer* b, double progress) const override
     {
         (dst->*m_setter)(blendFunc(anim, (a->*FillLayerPropertyWrapperGetter<T*>::m_getter)(), (b->*FillLayerPropertyWrapperGetter<T*>::m_getter)(), progress));
     }
 
+#if !LOG_DISABLED
+    void logBlend(const FillLayer* result, const FillLayer* a, const FillLayer* b, double progress) const override
+    {
+        LOG_WITH_STREAM(Animations, stream << "  blending " << getPropertyName(FillLayerPropertyWrapperGetter<T*>::property())
+            << " from " << FillLayerPropertyWrapperGetter<T*>::value(a)
+            << " to " << FillLayerPropertyWrapperGetter<T*>::value(b)
+            << " at " << TextStream::FormatNumberRespectingIntegers(progress) << " -> " << FillLayerPropertyWrapperGetter<T*>::value(result));
+    }
+#endif
+
 protected:
     void (FillLayer::*m_setter)(PassRefPtr<T>);
 };
@@ -1032,8 +1138,8 @@ protected:
 class FillLayerStyleImagePropertyWrapper : public FillLayerRefCountedPropertyWrapper<StyleImage> {
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    FillLayerStyleImagePropertyWrapper(StyleImage* (FillLayer::*getter)() const, void (FillLayer::*setter)(PassRefPtr<StyleImage>))
-        : FillLayerRefCountedPropertyWrapper<StyleImage>(getter, setter)
+    FillLayerStyleImagePropertyWrapper(CSSPropertyID property, StyleImage* (FillLayer::*getter)() const, void (FillLayer::*setter)(PassRefPtr<StyleImage>))
+        : FillLayerRefCountedPropertyWrapper<StyleImage>(property, getter, setter)
     {
     }
 
@@ -1048,6 +1154,13 @@ public:
         StyleImage* imageB = (b->*m_getter)();
         return arePointingToEqualData(imageA, imageB);
     }
+
+#if !LOG_DISABLED
+    void logBlend(const FillLayer* result, const FillLayer* a, const FillLayer* b, double progress) const override
+    {
+        LOG_WITH_STREAM(Animations, stream << "  blending " << getPropertyName(property()) << " from " << value(a) << " to " << value(b) << " at " << TextStream::FormatNumberRespectingIntegers(progress) << " -> " << value(result));
+    }
+#endif
 };
 
 class FillLayersPropertyWrapper : public AnimationPropertyWrapperBase {
@@ -1056,27 +1169,27 @@ public:
     typedef const FillLayer* (RenderStyle::*LayersGetter)() const;
     typedef FillLayer& (RenderStyle::*LayersAccessor)();
 
-    FillLayersPropertyWrapper(CSSPropertyID prop, LayersGetter getter, LayersAccessor accessor)
-        : AnimationPropertyWrapperBase(prop)
+    FillLayersPropertyWrapper(CSSPropertyID property, LayersGetter getter, LayersAccessor accessor)
+        : AnimationPropertyWrapperBase(property)
         , m_layersGetter(getter)
         , m_layersAccessor(accessor)
     {
-        switch (prop) {
+        switch (property) {
         case CSSPropertyBackgroundPositionX:
         case CSSPropertyWebkitMaskPositionX:
-            m_fillLayerPropertyWrapper = std::make_unique<FillLayerPropertyWrapper<Length>>(&FillLayer::xPosition, &FillLayer::setXPosition);
+            m_fillLayerPropertyWrapper = std::make_unique<FillLayerPositionPropertyWrapper>(property, &FillLayer::xPosition, &FillLayer::setXPosition, &FillLayer::backgroundXOrigin, &FillLayer::setBackgroundXOrigin, Edge::Right);
             break;
         case CSSPropertyBackgroundPositionY:
         case CSSPropertyWebkitMaskPositionY:
-            m_fillLayerPropertyWrapper = std::make_unique<FillLayerPropertyWrapper<Length>>(&FillLayer::yPosition, &FillLayer::setYPosition);
+            m_fillLayerPropertyWrapper = std::make_unique<FillLayerPositionPropertyWrapper>(property, &FillLayer::yPosition, &FillLayer::setYPosition, &FillLayer::backgroundYOrigin, &FillLayer::setBackgroundYOrigin, Edge::Bottom);
             break;
         case CSSPropertyBackgroundSize:
         case CSSPropertyWebkitBackgroundSize:
         case CSSPropertyWebkitMaskSize:
-            m_fillLayerPropertyWrapper = std::make_unique<FillLayerPropertyWrapper<LengthSize>>(&FillLayer::sizeLength, &FillLayer::setSizeLength);
+            m_fillLayerPropertyWrapper = std::make_unique<FillLayerPropertyWrapper<LengthSize>>(property, &FillLayer::sizeLength, &FillLayer::setSizeLength);
             break;
         case CSSPropertyBackgroundImage:
-            m_fillLayerPropertyWrapper = std::make_unique<FillLayerStyleImagePropertyWrapper>(&FillLayer::image, &FillLayer::setImage);
+            m_fillLayerPropertyWrapper = std::make_unique<FillLayerStyleImagePropertyWrapper>(property, &FillLayer::image, &FillLayer::setImage);
             break;
         default:
             break;
@@ -1119,10 +1232,18 @@ public:
     }
 
 #if !LOG_DISABLED
-    void logBlend(const RenderStyle*, const RenderStyle*, const RenderStyle*, double progress) const final
+    void logBlend(const RenderStyle* from, const RenderStyle* to, const RenderStyle* result, double progress) const final
     {
-        // FIXME: better logging.
-        LOG_WITH_STREAM(Animations, stream << "  blending FillLayers at " << TextStream::FormatNumberRespectingIntegers(progress));
+        const FillLayer* aLayer = (from->*m_layersGetter)();
+        const FillLayer* bLayer = (to->*m_layersGetter)();
+        const FillLayer* dstLayer = (result->*m_layersGetter)();
+
+        while (aLayer && bLayer && dstLayer) {
+            m_fillLayerPropertyWrapper->logBlend(dstLayer, aLayer, bLayer, progress);
+            aLayer = aLayer->next();
+            bLayer = bLayer->next();
+            dstLayer = dstLayer->next();
+        }
     }
 #endif
 
@@ -1165,10 +1286,10 @@ public:
     }
 
 #if !LOG_DISABLED
-    void logBlend(const RenderStyle*, const RenderStyle*, const RenderStyle*, double progress) const final
+    void logBlend(const RenderStyle* a, const RenderStyle* b, const RenderStyle* dst, double progress) const final
     {
-        // FIXME: better logging.
-        LOG_WITH_STREAM(Animations, stream << "  blending shorthand property " << getPropertyName(property()) << " at " << TextStream::FormatNumberRespectingIntegers(progress));
+        for (auto& wrapper : m_propertyWrappers)
+            wrapper->logBlend(a, b, dst, progress);
     }
 #endif
 
@@ -1579,10 +1700,10 @@ bool CSSPropertyAnimation::blendProperties(const AnimationBase* anim, CSSPropert
 
     AnimationPropertyWrapperBase* wrapper = CSSPropertyAnimationWrapperMap::singleton().wrapperForProperty(prop);
     if (wrapper) {
+        wrapper->blend(anim, dst, a, b, progress);
 #if !LOG_DISABLED
         wrapper->logBlend(a, b, dst, progress);
 #endif
-        wrapper->blend(anim, dst, a, b, progress);
         return !wrapper->animationIsAccelerated() || !anim->isAccelerated();
     }
     return false;
index 59f3a84..65246ce 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "config.h"
 #include "CalculationValue.h"
+#include "TextStream.h"
 
 #include <limits>
 
@@ -46,6 +47,11 @@ float CalcExpressionNumber::evaluate(float) const
     return m_value;
 }
 
+void CalcExpressionNumber::dump(TextStream& ts) const
+{
+    ts << TextStream::FormatNumberRespectingIntegers(m_value);
+}
+
 bool CalcExpressionNumber::operator==(const CalcExpressionNode& other) const
 {
     return other.type() == CalcExpressionNodeNumber && *this == toCalcExpressionNumber(other);
@@ -86,6 +92,11 @@ bool CalcExpressionBinaryOperation::operator==(const CalcExpressionNode& other)
     return other.type() == CalcExpressionNodeBinaryOperation && *this == toCalcExpressionBinaryOperation(other);
 }
 
+void CalcExpressionBinaryOperation::dump(TextStream& ts) const
+{
+    ts << *m_leftSide << " " << m_operator << " " << *m_rightSide;
+}
+
 float CalcExpressionLength::evaluate(float maxValue) const
 {
     return floatValueForLength(m_length, maxValue);
@@ -96,6 +107,11 @@ bool CalcExpressionLength::operator==(const CalcExpressionNode& other) const
     return other.type() == CalcExpressionNodeLength && *this == toCalcExpressionLength(other);
 }
 
+void CalcExpressionLength::dump(TextStream& ts) const
+{
+    ts << m_length;
+}
+
 float CalcExpressionBlendLength::evaluate(float maxValue) const
 {
     return (1.0f - m_progress) * floatValueForLength(m_from, maxValue) + m_progress * floatValueForLength(m_to, maxValue);
@@ -106,4 +122,34 @@ bool CalcExpressionBlendLength::operator==(const CalcExpressionNode& other) cons
     return other.type() == CalcExpressionNodeBlendLength && *this == toCalcExpressionBlendLength(other);
 }
 
+void CalcExpressionBlendLength::dump(TextStream& ts) const
+{
+    ts << "blend(" << m_from << ", " << m_to << ", " << m_progress << ")";
+}
+
+TextStream& operator<<(TextStream& ts, CalcOperator op)
+{
+    switch (op) {
+    case CalcAdd: ts << "+"; break;
+    case CalcSubtract: ts << "-"; break;
+    case CalcMultiply: ts << "*"; break;
+    case CalcDivide: ts << "/"; break;
+    }
+    return ts;
+}
+
+TextStream& operator<<(TextStream& ts, const CalculationValue& value)
+{
+    ts << "calc(";
+    ts << value.expression();
+    ts << ")";
+    return ts;
+}
+
+TextStream& operator<<(TextStream& ts, const CalcExpressionNode& expressionNode)
+{
+    expressionNode.dump(ts);
+    return ts;
+}
+
 } // namespace WebCore
index 77c8f2f..4530099 100644 (file)
@@ -39,6 +39,8 @@
 
 namespace WebCore {
 
+class TextStream;
+
 enum CalcOperator {
     CalcAdd = '+',
     CalcSubtract = '-',
@@ -64,6 +66,7 @@ public:
 
     virtual float evaluate(float maxValue) const = 0;
     virtual bool operator==(const CalcExpressionNode&) const = 0;
+    virtual void dump(TextStream&) const = 0;
 
 private:
     CalcExpressionNodeType m_type;
@@ -78,6 +81,7 @@ public:
 private:
     float evaluate(float) const override;
     bool operator==(const CalcExpressionNode&) const override;
+    void dump(TextStream&) const override;
 
     float m_value;
 };
@@ -91,6 +95,7 @@ public:
 private:
     float evaluate(float maxValue) const override;
     bool operator==(const CalcExpressionNode&) const override;
+    void dump(TextStream&) const override;
 
     Length m_length;
 };
@@ -106,6 +111,7 @@ public:
 private:
     float evaluate(float maxValue) const override;
     bool operator==(const CalcExpressionNode&) const override;
+    void dump(TextStream&) const override;
 
     std::unique_ptr<CalcExpressionNode> m_leftSide;
     std::unique_ptr<CalcExpressionNode> m_rightSide;
@@ -123,6 +129,7 @@ public:
 private:
     float evaluate(float maxValue) const override;
     bool operator==(const CalcExpressionNode&) const override;
+    void dump(TextStream&) const override;
 
     Length m_from;
     Length m_to;
@@ -232,6 +239,10 @@ inline const CalcExpressionBlendLength& toCalcExpressionBlendLength(const CalcEx
     return static_cast<const CalcExpressionBlendLength&>(value);
 }
 
+TextStream& operator<<(TextStream&, const CalculationValue&);
+TextStream& operator<<(TextStream&, const CalcExpressionNode&);
+TextStream& operator<<(TextStream&, CalcOperator);
+
 } // namespace WebCore
 
 #endif // CalculationValue_h
index eb5afbb..cd14fc5 100644 (file)
@@ -284,6 +284,18 @@ bool Length::isCalculatedEqual(const Length& other) const
     return calculationValue() == other.calculationValue();
 }
 
+Length convertTo100PercentMinusLength(const Length& length)
+{
+    if (length.isPercent())
+        return Length(100 - length.value(), Percent);
+    
+    // Turn this into a calc expression: calc(100% - length)
+    auto lhs = std::make_unique<CalcExpressionLength>(Length(100, Percent));
+    auto rhs = std::make_unique<CalcExpressionLength>(length);
+    auto op = std::make_unique<CalcExpressionBinaryOperation>(WTFMove(lhs), WTFMove(rhs), CalcSubtract);
+    return Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
+}
+
 static Length blendMixedTypes(const Length& from, const Length& to, double progress)
 {
     if (progress <= 0.0)
@@ -370,8 +382,7 @@ TextStream& operator<<(TextStream& ts, Length length)
         ts << TextStream::FormatNumberRespectingIntegers(length.percent()) << "%";
         break;
     case Calculated:
-        // FIXME: dump CalculationValue.
-        ts << "calc(...)";
+        ts << length.calculationValue();
         break;
     }
     
index dacf1ee..8c45117 100644 (file)
@@ -415,6 +415,8 @@ inline bool Length::isFitContent() const
     return type() == FitContent;
 }
 
+Length convertTo100PercentMinusLength(const Length&);
+
 TextStream& operator<<(TextStream&, Length);
 
 } // namespace WebCore
index 290c6d8..d6ef238 100644 (file)
@@ -52,15 +52,13 @@ void BasicShapeCenterCoordinate::updateComputedLength()
         m_computedLength = m_length.isUndefined() ? Length(0, Fixed) : m_length;
         return;
     }
+
     if (m_length.isUndefined()) {
         m_computedLength = Length(100, Percent);
         return;
     }
-
-    auto lhs = std::make_unique<CalcExpressionLength>(Length(100, Percent));
-    auto rhs = std::make_unique<CalcExpressionLength>(m_length);
-    auto op = std::make_unique<CalcExpressionBinaryOperation>(WTFMove(lhs), WTFMove(rhs), CalcSubtract);
-    m_computedLength = Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
+    
+    m_computedLength = convertTo100PercentMinusLength(m_length);
 }
 
 struct SVGPathTranslatedByteStream {
index 07fbdce..5e09c63 100644 (file)
 
 #include <WebCore/CalculationValue.h>
 
+namespace WebCore {
+class TextStream;
+};
+
 namespace TestWebKitAPI {
 
 static unsigned deletionCount;
@@ -40,6 +44,9 @@ public:
 
     float evaluate(float) const override { return 0; }
     bool operator==(const CalcExpressionNode&) const override { ASSERT_NOT_REACHED(); return false; }
+
+private:
+    void dump(WebCore::TextStream&) const override { };
 };
 
 static Ref<WebCore::CalculationValue> createTestValue()