2010-03-05 Dean Jackson <dino@apple.com>
authordino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 5 Mar 2010 12:58:54 +0000 (12:58 +0000)
committerdino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 5 Mar 2010 12:58:54 +0000 (12:58 +0000)
        Reviewed by Simon Fraser and Chris Marrin.

        Bug 26869: Add fill modes for CSS Animations
        https://bugs.webkit.org/show_bug.cgi?id=26869

        Tests: animations/fill-mode-removed.html
               animations/fill-mode-transform.html
               animations/fill-mode.html

        * css/CSSComputedStyleDeclaration.cpp:
        * css/CSSParser.cpp:
        * css/CSSParser.h:
        * css/CSSPropertyNames.in:
        * css/CSSStyleSelector.cpp:
        * css/CSSStyleSelector.h:
        * css/CSSValueKeywords.in:
            - parse, assign and retrieve the value of the new
              -webkit-animation-fill-mode property
        * page/animation/AnimationBase.cpp:
        * page/animation/AnimationBase.h:
            - new state in animation engine for a finished animation
              that is "filling" forwards in time. This allows the
              engine to keep the animation around and not revert to the
              old style.
            - update the timer code to indicate it doesn't need to
              keep animating if it is filling
            - now that animations can extend beyond their elapsed time,
              make sure progress works correctly with iteration counts
        * page/animation/KeyframeAnimation.cpp:
        (WebCore::KeyframeAnimation::animate):
            - ensure correct style value is returned at the right
              time by checking for fill mode
        (WebCore::KeyframeAnimation::onAnimationEnd):
            - continue to send the end event, but only remove the
              animation if it isn't filling forwards
        * platform/animation/Animation.cpp:
        * platform/animation/Animation.h:
            - new fill mode member property
        * platform/animation/AnimationList.cpp:
            - ensure the fill mode is propagated to a list of style valus
        * platform/graphics/mac/GraphicsLayerCA.mm:
            - make hardware layers use Core Animation's fill mode

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

31 files changed:
LayoutTests/ChangeLog
LayoutTests/animations/computed-style-expected.txt
LayoutTests/animations/computed-style.html
LayoutTests/animations/fill-mode-expected.txt [new file with mode: 0644]
LayoutTests/animations/fill-mode-removed-expected.txt [new file with mode: 0644]
LayoutTests/animations/fill-mode-removed.html [new file with mode: 0644]
LayoutTests/animations/fill-mode-transform-expected.txt [new file with mode: 0644]
LayoutTests/animations/fill-mode-transform.html [new file with mode: 0644]
LayoutTests/animations/fill-mode.html [new file with mode: 0644]
LayoutTests/animations/fill-unset-properties-expected.txt
LayoutTests/animations/fill-unset-properties.html
LayoutTests/fast/css/getComputedStyle/computed-style-expected.txt
LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-expected.txt
LayoutTests/platform/mac/fast/css/getComputedStyle/computed-style-expected.txt
LayoutTests/platform/mac/fast/css/getComputedStyle/computed-style-without-renderer-expected.txt
LayoutTests/svg/css/getComputedStyle-basic-expected.txt
WebCore/ChangeLog
WebCore/css/CSSComputedStyleDeclaration.cpp
WebCore/css/CSSParser.cpp
WebCore/css/CSSParser.h
WebCore/css/CSSPropertyNames.in
WebCore/css/CSSStyleSelector.cpp
WebCore/css/CSSStyleSelector.h
WebCore/css/CSSValueKeywords.in
WebCore/page/animation/AnimationBase.cpp
WebCore/page/animation/AnimationBase.h
WebCore/page/animation/KeyframeAnimation.cpp
WebCore/platform/animation/Animation.cpp
WebCore/platform/animation/Animation.h
WebCore/platform/animation/AnimationList.cpp
WebCore/platform/graphics/mac/GraphicsLayerCA.mm

index c315b21..2ac7493 100644 (file)
@@ -1,3 +1,30 @@
+2010-03-05  Dean Jackson  <dino@apple.com>
+
+        Reviewed by Simon Fraser and Chris Marrin.
+
+        Bug 26869: Add fill modes for CSS Animations
+        https://bugs.webkit.org/show_bug.cgi?id=26869
+
+        Add tests for -webkit-animation-fill-mode property,
+        and update existing computed style tests with the
+        new property.
+
+        * animations/computed-style-expected.txt:
+        * animations/computed-style.html:
+        * animations/fill-mode-expected.txt: Added.
+        * animations/fill-mode-removed-expected.txt: Added.
+        * animations/fill-mode-removed.html: Added.
+        * animations/fill-mode-transform-expected.txt: Added.
+        * animations/fill-mode-transform.html: Added.
+        * animations/fill-mode.html: Added.
+        * animations/fill-unset-properties-expected.txt:
+        * animations/fill-unset-properties.html:
+        * fast/css/getComputedStyle/computed-style-expected.txt:
+        * fast/css/getComputedStyle/computed-style-without-renderer-expected.txt:
+        * platform/mac/fast/css/getComputedStyle/computed-style-expected.txt:
+        * platform/mac/fast/css/getComputedStyle/computed-style-without-renderer-expected.txt:
+        * svg/css/getComputedStyle-basic-expected.txt:
+
 2010-03-05  Fumitoshi Ukai  <ukai@chromium.org>
 
         Reviewed by Alexey Proskuryakov.
index eb40a7d..68b0921 100644 (file)
@@ -7,6 +7,8 @@ PASS test1Style.webkitAnimationName is 'anim1'
 PASS test2Style.webkitAnimationName is 'anim2, anim3'
 PASS test1Style.webkitAnimationDuration is '10s'
 PASS test2Style.webkitAnimationDuration is '5s, 2.5s'
+PASS test1Style.webkitAnimationFillMode is 'backwards'
+PASS test2Style.webkitAnimationFillMode is 'forwards, both'
 PASS test1Style.webkitAnimationIterationCount is '10'
 PASS test2Style.webkitAnimationIterationCount is '10, infinite'
 PASS test1Style.webkitAnimationTimingFunction is 'cubic-bezier(0, 0, 1, 1)'
index 47f3b9d..89c8eed 100644 (file)
@@ -9,6 +9,7 @@
         position: relative;
         -webkit-animation-name: anim1;
         -webkit-animation-duration: 10s;
+        -webkit-animation-fill-mode: backwards;
         -webkit-animation-iteration-count: 10;
         -webkit-animation-timing-function: linear;
         -webkit-animation-direction: normal;
@@ -20,6 +21,7 @@
         position: relative;
         -webkit-animation-name: anim2, anim3;
         -webkit-animation-duration: 5s, 2500ms;
+        -webkit-animation-fill-mode: forwards, both;
         -webkit-animation-iteration-count: 10, infinite;
         -webkit-animation-timing-function: linear, ease-in-out;
         -webkit-animation-direction: normal, alternate;
@@ -60,6 +62,9 @@ shouldBe("test2Style.webkitAnimationName", "'anim2, anim3'");
 shouldBe("test1Style.webkitAnimationDuration", "'10s'");
 shouldBe("test2Style.webkitAnimationDuration", "'5s, 2.5s'");
 
+shouldBe("test1Style.webkitAnimationFillMode", "'backwards'");
+shouldBe("test2Style.webkitAnimationFillMode", "'forwards, both'");
+
 shouldBe("test1Style.webkitAnimationIterationCount", "'10'");
 shouldBe("test2Style.webkitAnimationIterationCount", "'10, infinite'");
 
diff --git a/LayoutTests/animations/fill-mode-expected.txt b/LayoutTests/animations/fill-mode-expected.txt
new file mode 100644 (file)
index 0000000..eb22102
--- /dev/null
@@ -0,0 +1,17 @@
+This test performs an animation of the left property with four different fill modes. It animates over 0.1 second with a 0.1 second delay. It takes snapshots at document load and the end of the animation.
+None
+Backwards
+Forwards
+Both
+Both iterating
+PASS - start of animation - id: a expected: 100 actual: 100
+PASS - start of animation - id: b expected: 200 actual: 200
+PASS - start of animation - id: c expected: 100 actual: 100
+PASS - start of animation - id: d expected: 200 actual: 200
+PASS - start of animation - id: e expected: 200 actual: 200
+PASS - end of animation - id: a expected: 100 actual: 100
+PASS - end of animation - id: b expected: 100 actual: 100
+PASS - end of animation - id: c expected: 300 actual: 300
+PASS - end of animation - id: d expected: 300 actual: 300
+PASS - end of animation - id: e expected: 200 actual: 200
+
diff --git a/LayoutTests/animations/fill-mode-removed-expected.txt b/LayoutTests/animations/fill-mode-removed-expected.txt
new file mode 100644 (file)
index 0000000..09c6784
--- /dev/null
@@ -0,0 +1,10 @@
+This test performs an animation of the left property with four different fill modes. It animates over 0.1 second with a 0.1 second delay. At the end of the animations, we remove the animation name which should cause the value to jump back to the unanimated style.
+None
+Backwards
+Forwards
+Both
+PASS - id: a expected: 100 actual: 100
+PASS - id: b expected: 100 actual: 100
+PASS - id: c expected: 100 actual: 100
+PASS - id: d expected: 100 actual: 100
+
diff --git a/LayoutTests/animations/fill-mode-removed.html b/LayoutTests/animations/fill-mode-removed.html
new file mode 100644 (file)
index 0000000..6635464
--- /dev/null
@@ -0,0 +1,110 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+
+<html lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <title>Test end values with fill modes after animation is removed</title>
+  <style type="text/css" media="screen">
+    .box {
+      position: relative;
+      left: 100px;
+      top: 10px;
+      height: 100px;
+      width: 100px;
+      -webkit-animation-delay: 0.1s;
+      -webkit-animation-duration: 0.1s;
+      -webkit-animation-timing-function: linear;
+      -webkit-animation-name: "anim";
+    }
+    @-webkit-keyframes "anim" {
+        from { left: 200px; }
+        to   { left: 300px; }
+    }
+    #a {
+      background-color: blue;
+      -webkit-animation-fill-mode: none;
+    }
+    #b {
+      background-color: red;
+      -webkit-animation-fill-mode: backwards;
+    }
+    #c {
+      background-color: green;
+      -webkit-animation-fill-mode: forwards;
+    }
+    #d {
+      background-color: yellow;
+      -webkit-animation-fill-mode: both;
+    }
+  </style>
+  <script type="text/javascript" charset="utf-8">
+    const numAnims = 4;
+    var animsFinished = 0;
+    const expectedEndValues = [
+      {id: "a", value: 100},
+      {id: "b", value: 100},
+      {id: "c", value: 100},
+      {id: "d", value: 100}
+    ];
+  
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+  
+    function animationEnded(event) {
+        event.target.style.webkitAnimationName = "none";
+        if (++animsFinished == numAnims) {
+            setTimeout(endTest, 0); // this set timeout should be ok in the test environment
+                                    // since we're just giving style a chance to resolve
+        }
+    };
+  
+    function endTest() {
+
+        var result = "";
+        for (var i=0; i < expectedEndValues.length; i++) {
+            var el = document.getElementById(expectedEndValues[i].id);
+            var expectedValue = expectedEndValues[i].value;
+            var realValue = window.getComputedStyle(el).getPropertyCSSValue("left").getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
+            if (Math.abs(expectedValue - realValue) < 5) {
+              result += "PASS";
+            } else {
+              result += "FAIL";
+            }
+            result += " - id: " + expectedEndValues[i].id + " expected: " + expectedValue + " actual: " + realValue + "<br>";
+        }
+        document.getElementById('result').innerHTML = result;
+
+        if (window.layoutTestController)
+            layoutTestController.notifyDone();
+    }
+  
+    window.onload = function () {
+        document.addEventListener("webkitAnimationEnd", animationEnded, false);
+    };
+  
+  </script>
+</head>
+<body>
+This test performs an animation of the left property with four different
+fill modes. It animates over 0.1 second with a 0.1 second delay.
+At the end of the animations, we remove the animation name which should
+cause the value to jump back to the unanimated style.
+<div id="a" class="box">
+  None
+</div>
+<div id="b" class="box">
+  Backwards
+</div>
+<div id="c" class="box">
+  Forwards
+</div>
+<div id="d" class="box">
+  Both
+</div>
+<div id="result">
+</div>
+</body>
+</html>
diff --git a/LayoutTests/animations/fill-mode-transform-expected.txt b/LayoutTests/animations/fill-mode-transform-expected.txt
new file mode 100644 (file)
index 0000000..a88da5f
--- /dev/null
@@ -0,0 +1,19 @@
+This test performs an animation of the transform property with four different fill modes. It animates over 0.1 second with a 0.1 second delay. It takes snapshots at document load and the end of the animations.
+None
+Backwards
+Forwards
+Both
+Both Iterate
+Note that due to Bug 35714 the expected values for the start of the animation on elements "a" and "c" are intentionally incorrect. The expected values should be 100, not 200. If this test fails, then it's likely that bug has been fixed and the expected values here should be updated.
+
+PASS - start of animation - id: a expected: 200 actual: 200
+PASS - start of animation - id: b expected: 200 actual: 200
+PASS - start of animation - id: c expected: 200 actual: 200
+PASS - start of animation - id: d expected: 200 actual: 200
+PASS - start of animation - id: e expected: 200 actual: 200
+PASS - end of animation - id: a expected: 100 actual: 100
+PASS - end of animation - id: b expected: 100 actual: 100
+PASS - end of animation - id: c expected: 300 actual: 300
+PASS - end of animation - id: d expected: 300 actual: 300
+PASS - end of animation - id: e expected: 200 actual: 200
+
diff --git a/LayoutTests/animations/fill-mode-transform.html b/LayoutTests/animations/fill-mode-transform.html
new file mode 100644 (file)
index 0000000..71c5c68
--- /dev/null
@@ -0,0 +1,140 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+
+<html lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <title>Test simple fill mode on transform</title>
+  <style type="text/css" media="screen">
+    .box {
+      position: relative;
+      left: 10px;
+      top: 10px;
+      height: 100px;
+      width: 100px;
+      -webkit-transform: translate3d(100px, 0, 0);
+      -webkit-animation-delay: 0.1s;
+      -webkit-animation-duration: 0.1s;
+      -webkit-animation-timing-function: linear;
+      -webkit-animation-name: anim;
+    }
+    @-webkit-keyframes anim {
+        from { -webkit-transform: translate3d(200px, 0, 0); }
+        to   { -webkit-transform: translate3d(300px, 0, 0); }
+    }
+    #a {
+      background-color: blue;
+      -webkit-animation-fill-mode: none;
+    }
+    #b {
+      background-color: red;
+      -webkit-animation-fill-mode: backwards;
+    }
+    #c {
+      background-color: green;
+      -webkit-animation-fill-mode: forwards;
+    }
+    #d {
+      background-color: yellow;
+      -webkit-animation-fill-mode: both;
+    }
+    #e {
+      background-color: #999;
+      -webkit-animation-fill-mode: both;
+      -webkit-animation-iteration-count: 2;
+      -webkit-animation-direction: alternate;
+    }
+  </style>
+  <script type="text/javascript" charset="utf-8">
+    const numAnims = 5;
+    var animsFinished = 0;
+    const allowance = 5;
+    const expectedValues = [
+      {id: "a", start: 200, end: 100},
+      {id: "b", start: 200, end: 100},
+      {id: "c", start: 200, end: 300},
+      {id: "d", start: 200, end: 300},
+      {id: "e", start: 200, end: 200}
+    ];
+    var result = "";
+
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+
+    function animationEnded(event) {
+        if (++animsFinished == numAnims) {
+            setTimeout(endTest, 0); // this set timeout should be ok in the test environment
+                                    // since we're just giving style a chance to resolve
+        }
+    };
+
+    function endTest() {
+
+        for (var i=0; i < expectedValues.length; i++) {
+            var el = document.getElementById(expectedValues[i].id);
+            var expectedValue = expectedValues[i].end;
+            var computedValue = window.getComputedStyle(el).webkitTransform;
+            var realValue = parseFloat(computedValue.split("(")[1].split(",")[4]);
+            if (Math.abs(expectedValue - realValue) < allowance) {
+              result += "PASS";
+            } else {
+              result += "FAIL";
+            }
+            result += " - end of animation - id: " + expectedValues[i].id + " expected: " + expectedValue + " actual: " + realValue + "<br>";
+        }
+        document.getElementById('result').innerHTML = result;
+
+        if (window.layoutTestController)
+            layoutTestController.notifyDone();
+    }
+    
+    window.onload = function () {
+      for (var i=0; i < expectedValues.length; i++) {
+          var el = document.getElementById(expectedValues[i].id);
+          var expectedValue = expectedValues[i].start;
+          var computedValue = window.getComputedStyle(el).webkitTransform;
+          var realValue = parseFloat(computedValue.split("(")[1].split(",")[4]);
+          if (Math.abs(expectedValue - realValue) < allowance) {
+            result += "PASS";
+          } else {
+            result += "FAIL";
+          }
+          result += " - start of animation - id: " + expectedValues[i].id + " expected: " + expectedValue + " actual: " + realValue + "<br>";
+      }
+      document.addEventListener("webkitAnimationEnd", animationEnded, false);
+    };
+
+  </script>
+</head>
+<body>
+This test performs an animation of the transform property with four different
+fill modes. It animates over 0.1 second with a 0.1 second delay.
+It takes snapshots at document load and the end of the animations.
+<div id="a" class="box">
+  None
+</div>
+<div id="b" class="box">
+  Backwards
+</div>
+<div id="c" class="box">
+  Forwards
+</div>
+<div id="d" class="box">
+  Both
+</div>
+<div id="e" class="box">
+  Both Iterate
+</div>
+<p>
+  Note that due to <a href="https://bugs.webkit.org/show_bug.cgi?id=35714">Bug 35714</a>
+  the expected values for the start of the animation on elements "a" and "c" are
+  intentionally incorrect. The expected values should be 100, not 200. If this
+  test fails, then it's likely that bug has been fixed and the expected values here
+  should be updated.
+</p>
+<div id="result">
+</div>
+</body>
+</html>
diff --git a/LayoutTests/animations/fill-mode.html b/LayoutTests/animations/fill-mode.html
new file mode 100644 (file)
index 0000000..6c741a5
--- /dev/null
@@ -0,0 +1,130 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+
+<html lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <title>Test simple animation with fill modes</title>
+  <style type="text/css" media="screen">
+    .box {
+      position: relative;
+      left: 100px;
+      top: 10px;
+      height: 100px;
+      width: 100px;
+      -webkit-animation-delay: 0.1s;
+      -webkit-animation-duration: 0.1s;
+      -webkit-animation-timing-function: linear;
+      -webkit-animation-name: anim;
+    }
+    @-webkit-keyframes anim {
+        from { left: 200px; }
+        to   { left: 300px; }
+    }
+    #a {
+      background-color: blue;
+      -webkit-animation-fill-mode: none;
+    }
+    #b {
+      background-color: red;
+      -webkit-animation-fill-mode: backwards;
+    }
+    #c {
+      background-color: green;
+      -webkit-animation-fill-mode: forwards;
+    }
+    #d {
+      background-color: yellow;
+      -webkit-animation-fill-mode: both;
+    }
+    #e {
+      background-color: #999;
+      -webkit-animation-fill-mode: both;
+      -webkit-animation-iteration-count: 2;
+      -webkit-animation-direction: alternate;
+    }
+  </style>
+  <script type="text/javascript" charset="utf-8">
+    const numAnims = 5;
+    var animsFinished = 0;
+    const allowance = 5;
+    const expectedValues = [
+      {id: "a", start: 100, end: 100},
+      {id: "b", start: 200, end: 100},
+      {id: "c", start: 100, end: 300},
+      {id: "d", start: 200, end: 300},
+      {id: "e", start: 200, end: 200}
+    ];
+    var result = "";
+
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+
+    function animationEnded(event) {
+        if (++animsFinished == numAnims) {
+            setTimeout(endTest, 0); // this set timeout should be ok in the test environment
+                                    // since we're just giving style a chance to resolve
+        }
+    };
+
+    function endTest() {
+
+        for (var i=0; i < expectedValues.length; i++) {
+            var el = document.getElementById(expectedValues[i].id);
+            var expectedValue = expectedValues[i].end;
+            var realValue = window.getComputedStyle(el).getPropertyCSSValue("left").getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
+            if (Math.abs(expectedValue - realValue) < allowance) {
+              result += "PASS";
+            } else {
+              result += "FAIL";
+            }
+            result += " - end of animation - id: " + expectedValues[i].id + " expected: " + expectedValue + " actual: " + realValue + "<br>";
+        }
+        document.getElementById('result').innerHTML = result;
+
+        if (window.layoutTestController)
+            layoutTestController.notifyDone();
+    }
+
+    window.onload = function () {
+        for (var i=0; i < expectedValues.length; i++) {
+            var el = document.getElementById(expectedValues[i].id);
+            var expectedValue = expectedValues[i].start;
+            var realValue = window.getComputedStyle(el).getPropertyCSSValue("left").getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
+            if (Math.abs(expectedValue - realValue) < allowance) {
+              result += "PASS";
+            } else {
+              result += "FAIL";
+            }
+            result += " - start of animation - id: " + expectedValues[i].id + " expected: " + expectedValue + " actual: " + realValue + "<br>";
+        }
+        document.addEventListener("webkitAnimationEnd", animationEnded, false);
+    };
+
+  </script>
+</head>
+<body>
+This test performs an animation of the left property with four different
+fill modes. It animates over 0.1 second with a 0.1 second delay.
+It takes snapshots at document load and the end of the animation.
+<div id="a" class="box">
+  None
+</div>
+<div id="b" class="box">
+  Backwards
+</div>
+<div id="c" class="box">
+  Forwards
+</div>
+<div id="d" class="box">
+  Both
+</div>
+<div id="e" class="box">
+  Both iterating
+</div>
+<div id="result">
+</div>
+</body>
+</html>
index a57c5da..c3dffa5 100644 (file)
@@ -5,4 +5,5 @@ Testing webkitTransitionTimingFunction: PASS
 Testing webkitAnimationName: PASS
 Testing webkitAnimationDuration: PASS
 Testing webkitAnimationDelay: PASS
+Testing webkitAnimationFillMode: PASS
 
index b47a286..a49deca 100644 (file)
@@ -13,6 +13,7 @@
     -webkit-animation-name: a, b, c, d, e;
     -webkit-animation-duration: 10s, 20s;
     -webkit-animation-delay: 1s;
+    -webkit-animation-fill-mode: forwards, backwards;
 }
 @-webkit-keyframes a { }
 @-webkit-keyframes b { }
@@ -32,6 +33,7 @@
       { 'property': 'webkitAnimationName',      'value': 'a, b, c, d, e' },
       { 'property': 'webkitAnimationDuration',  'value': '10s, 20s, 10s, 20s, 10s' },
       { 'property': 'webkitAnimationDelay',     'value': '1s, 1s, 1s, 1s, 1s' },
+      { 'property': 'webkitAnimationFillMode',  'value': 'forwards, backwards, forwards, backwards, forwards' },
     ];
     
     function start()
index e61e20b..32fe3d4 100644 (file)
@@ -86,6 +86,7 @@ zoom: 1;
 -webkit-animation-delay: 0s;
 -webkit-animation-direction: normal;
 -webkit-animation-duration: 0s;
+-webkit-animation-fill-mode: none;
 -webkit-animation-iteration-count: 1;
 -webkit-animation-name: none;
 -webkit-animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
index c9fa942..a6890e6 100644 (file)
@@ -85,6 +85,7 @@ Computed style of an element whose parent's 'display' value is 'none':
     -webkit-animation-delay: 0s
     -webkit-animation-direction: normal
     -webkit-animation-duration: 0s
+    -webkit-animation-fill-mode: none
     -webkit-animation-iteration-count: 1
     -webkit-animation-name: none
     -webkit-animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1)
index ff486e5..f87c4aa 100644 (file)
@@ -96,6 +96,7 @@ zoom: 1;
 -webkit-animation-delay: 0s;
 -webkit-animation-direction: normal;
 -webkit-animation-duration: 0s;
+-webkit-animation-fill-mode: none;
 -webkit-animation-iteration-count: 1;
 -webkit-animation-name: none;
 -webkit-animation-play-state: running;
index c1291a1..23774cc 100644 (file)
@@ -95,6 +95,7 @@ Computed style of an element whose parent's 'display' value is 'none':
     -webkit-animation-delay: 0s
     -webkit-animation-direction: normal
     -webkit-animation-duration: 0s
+    -webkit-animation-fill-mode: none
     -webkit-animation-iteration-count: 1
     -webkit-animation-name: none
     -webkit-animation-play-state: running
index bb8c0b3..a1c53dd 100644 (file)
@@ -190,6 +190,8 @@ rect: style.getPropertyValue(-webkit-animation-direction) : normal
 rect: style.getPropertyCSSValue(-webkit-animation-direction) : [object CSSValueList]
 rect: style.getPropertyValue(-webkit-animation-duration) : 0s
 rect: style.getPropertyCSSValue(-webkit-animation-duration) : [object CSSValueList]
+rect: style.getPropertyValue(-webkit-animation-fill-mode) : none
+rect: style.getPropertyCSSValue(-webkit-animation-fill-mode) : [object CSSValueList]
 rect: style.getPropertyValue(-webkit-animation-iteration-count) : 1
 rect: style.getPropertyCSSValue(-webkit-animation-iteration-count) : [object CSSValueList]
 rect: style.getPropertyValue(-webkit-animation-name) : none
@@ -604,6 +606,8 @@ g: style.getPropertyValue(-webkit-animation-direction) : normal
 g: style.getPropertyCSSValue(-webkit-animation-direction) : [object CSSValueList]
 g: style.getPropertyValue(-webkit-animation-duration) : 0s
 g: style.getPropertyCSSValue(-webkit-animation-duration) : [object CSSValueList]
+g: style.getPropertyValue(-webkit-animation-fill-mode) : none
+g: style.getPropertyCSSValue(-webkit-animation-fill-mode) : [object CSSValueList]
 g: style.getPropertyValue(-webkit-animation-iteration-count) : 1
 g: style.getPropertyCSSValue(-webkit-animation-iteration-count) : [object CSSValueList]
 g: style.getPropertyValue(-webkit-animation-name) : none
index 61a441d..9f3ac4d 100644 (file)
@@ -1,3 +1,48 @@
+2010-03-05  Dean Jackson  <dino@apple.com>
+
+        Reviewed by Simon Fraser and Chris Marrin.
+
+        Bug 26869: Add fill modes for CSS Animations
+        https://bugs.webkit.org/show_bug.cgi?id=26869
+
+        Tests: animations/fill-mode-removed.html
+               animations/fill-mode-transform.html
+               animations/fill-mode.html
+
+        * css/CSSComputedStyleDeclaration.cpp:
+        * css/CSSParser.cpp:
+        * css/CSSParser.h:
+        * css/CSSPropertyNames.in:
+        * css/CSSStyleSelector.cpp:
+        * css/CSSStyleSelector.h:
+        * css/CSSValueKeywords.in:
+            - parse, assign and retrieve the value of the new
+              -webkit-animation-fill-mode property
+        * page/animation/AnimationBase.cpp:
+        * page/animation/AnimationBase.h:
+            - new state in animation engine for a finished animation
+              that is "filling" forwards in time. This allows the
+              engine to keep the animation around and not revert to the
+              old style.
+            - update the timer code to indicate it doesn't need to
+              keep animating if it is filling
+            - now that animations can extend beyond their elapsed time,
+              make sure progress works correctly with iteration counts
+        * page/animation/KeyframeAnimation.cpp:
+        (WebCore::KeyframeAnimation::animate):
+            - ensure correct style value is returned at the right
+              time by checking for fill mode
+        (WebCore::KeyframeAnimation::onAnimationEnd):
+            - continue to send the end event, but only remove the
+              animation if it isn't filling forwards
+        * platform/animation/Animation.cpp:
+        * platform/animation/Animation.h:
+            - new fill mode member property
+        * platform/animation/AnimationList.cpp:
+            - ensure the fill mode is propagated to a list of style valus
+        * platform/graphics/mac/GraphicsLayerCA.mm:
+            - make hardware layers use Core Animation's fill mode
+
 2010-03-05  Ilya Tikhonovsky  <loislo@chromium.org>
 
         Reviewed by Pavel Feldman.
index 8039e35..f7c5dc4 100644 (file)
@@ -146,6 +146,7 @@ static const int computedProperties[] = {
     CSSPropertyWebkitAnimationDelay,
     CSSPropertyWebkitAnimationDirection,
     CSSPropertyWebkitAnimationDuration,
+    CSSPropertyWebkitAnimationFillMode,
     CSSPropertyWebkitAnimationIterationCount,
     CSSPropertyWebkitAnimationName,
     CSSPropertyWebkitAnimationPlayState,
@@ -1198,6 +1199,30 @@ PassRefPtr<CSSValue> CSSComputedStyleDeclaration::getPropertyCSSValue(int proper
         }
         case CSSPropertyWebkitAnimationDuration:
             return getDurationValue(style->animations());
+        case CSSPropertyWebkitAnimationFillMode: {
+            RefPtr<CSSValueList> list = CSSValueList::createCommaSeparated();
+            const AnimationList* t = style->animations();
+            if (t) {
+                for (size_t i = 0; i < t->size(); ++i) {
+                    switch (t->animation(i)->fillMode()) {
+                    case Animation::AnimationFillModeNone:
+                        list->append(CSSPrimitiveValue::createIdentifier(CSSValueNone));
+                        break;
+                    case Animation::AnimationFillModeForwards:
+                        list->append(CSSPrimitiveValue::createIdentifier(CSSValueForwards));
+                        break;
+                    case Animation::AnimationFillModeBackwards:
+                        list->append(CSSPrimitiveValue::createIdentifier(CSSValueBackwards));
+                        break;
+                    case Animation::AnimationFillModeBoth:
+                        list->append(CSSPrimitiveValue::createIdentifier(CSSValueBoth));
+                        break;
+                    }
+                }
+            } else
+                list->append(CSSPrimitiveValue::createIdentifier(CSSValueNone));
+            return list.release();
+        }
         case CSSPropertyWebkitAnimationIterationCount: {
             RefPtr<CSSValueList> list = CSSValueList::createCommaSeparated();
             const AnimationList* t = style->animations();
index a746801..7dc3269 100644 (file)
@@ -1393,6 +1393,7 @@ bool CSSParser::parseValue(int propId, bool important)
     case CSSPropertyWebkitAnimationDelay:
     case CSSPropertyWebkitAnimationDirection:
     case CSSPropertyWebkitAnimationDuration:
+    case CSSPropertyWebkitAnimationFillMode:
     case CSSPropertyWebkitAnimationName:
     case CSSPropertyWebkitAnimationPlayState:
     case CSSPropertyWebkitAnimationIterationCount:
@@ -2571,6 +2572,14 @@ PassRefPtr<CSSValue> CSSParser::parseAnimationDuration()
     return 0;
 }
 
+PassRefPtr<CSSValue> CSSParser::parseAnimationFillMode()
+{
+    CSSParserValue* value = m_valueList->current();
+    if (value->id == CSSValueNone || value->id == CSSValueForwards || value->id == CSSValueBackwards || value->id == CSSValueBoth)
+        return CSSPrimitiveValue::createIdentifier(value->id);
+    return 0;
+}
+
 PassRefPtr<CSSValue> CSSParser::parseAnimationIterationCount()
 {
     CSSParserValue* value = m_valueList->current();
@@ -2713,6 +2722,11 @@ bool CSSParser::parseAnimationProperty(int propId, RefPtr<CSSValue>& result)
                     if (currValue)
                         m_valueList->next();
                     break;
+                case CSSPropertyWebkitAnimationFillMode:
+                    currValue = parseAnimationFillMode();
+                    if (currValue)
+                        m_valueList->next();
+                    break;
                 case CSSPropertyWebkitAnimationIterationCount:
                     currValue = parseAnimationIterationCount();
                     if (currValue)
index 3922a2a..d10da4c 100644 (file)
@@ -95,6 +95,7 @@ namespace WebCore {
         PassRefPtr<CSSValue> parseAnimationDelay();
         PassRefPtr<CSSValue> parseAnimationDirection();
         PassRefPtr<CSSValue> parseAnimationDuration();
+        PassRefPtr<CSSValue> parseAnimationFillMode();
         PassRefPtr<CSSValue> parseAnimationIterationCount();
         PassRefPtr<CSSValue> parseAnimationName();
         PassRefPtr<CSSValue> parseAnimationPlayState();
index 9695d0b..5f7b188 100644 (file)
@@ -151,6 +151,7 @@ zoom
 -webkit-animation-delay
 -webkit-animation-direction
 -webkit-animation-duration
+-webkit-animation-fill-mode
 -webkit-animation-iteration-count
 -webkit-animation-name
 -webkit-animation-play-state
index 49a1a1e..8168ecb 100644 (file)
@@ -5291,6 +5291,9 @@ void CSSStyleSelector::applyProperty(int id, CSSValue *value)
     case CSSPropertyWebkitAnimationDuration:
         HANDLE_ANIMATION_VALUE(duration, Duration, value)
         return;
+    case CSSPropertyWebkitAnimationFillMode:
+        HANDLE_ANIMATION_VALUE(fillMode, FillMode, value)
+        return;
     case CSSPropertyWebkitAnimationIterationCount:
         HANDLE_ANIMATION_VALUE(iterationCount, IterationCount, value)
         return;
@@ -5650,6 +5653,30 @@ void CSSStyleSelector::mapAnimationDuration(Animation* animation, CSSValue* valu
         animation->setDuration(primitiveValue->getFloatValue()/1000.0f);
 }
 
+void CSSStyleSelector::mapAnimationFillMode(Animation* layer, CSSValue* value)
+{
+    if (value->cssValueType() == CSSValue::CSS_INITIAL) {
+        layer->setFillMode(Animation::initialAnimationFillMode());
+        return;
+    }
+
+    CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value);
+    switch (primitiveValue->getIdent()) {
+    case CSSValueNone:
+        layer->setFillMode(Animation::AnimationFillModeNone);
+        break;
+    case CSSValueForwards:
+        layer->setFillMode(Animation::AnimationFillModeForwards);
+        break;
+    case CSSValueBackwards:
+        layer->setFillMode(Animation::AnimationFillModeBackwards);
+        break;
+    case CSSValueBoth:
+        layer->setFillMode(Animation::AnimationFillModeBoth);
+        break;
+    }
+}
+
 void CSSStyleSelector::mapAnimationIterationCount(Animation* animation, CSSValue* value)
 {
     if (value->cssValueType() == CSSValue::CSS_INITIAL) {
index 644051c..284d561 100644 (file)
@@ -235,6 +235,7 @@ public:
         void mapAnimationDelay(Animation*, CSSValue*);
         void mapAnimationDirection(Animation*, CSSValue*);
         void mapAnimationDuration(Animation*, CSSValue*);
+        void mapAnimationFillMode(Animation*, CSSValue*);
         void mapAnimationIterationCount(Animation*, CSSValue*);
         void mapAnimationName(Animation*, CSSValue*);
         void mapAnimationPlayState(Animation*, CSSValue*);
index ca2a47a..87e2569 100644 (file)
@@ -639,6 +639,13 @@ lines
 # alternate
 
 #
+# CSS_PROP__WEBKIT_ANIMATION_FILL_MODE
+#
+# forwards
+# backwards
+# both
+
+#
 # CSS_PROP__WEBKIT_ANIMATION_ITERATION_COUNT
 #
 # infinite
index 2a2ab4b..c2d70ef 100644 (file)
@@ -918,8 +918,8 @@ void AnimationBase::updateStateMachine(AnimStateInput input, double param)
     // Execute state machine
     switch (m_animState) {
         case AnimationStateNew:
-            ASSERT(input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunnning || input == AnimationStateInputPlayStatePaused);
-            if (input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunnning) {
+            ASSERT(input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning || input == AnimationStateInputPlayStatePaused);
+            if (input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning) {
                 m_requestedStartTime = beginAnimationUpdateTime();
                 m_animState = AnimationStateStartWaitTimer;
             }
@@ -1021,6 +1021,7 @@ void AnimationBase::updateStateMachine(AnimStateInput input, double param)
             ASSERT(input == AnimationStateInputEndTimerFired || input == AnimationStateInputPlayStatePaused);
 
             if (input == AnimationStateInputEndTimerFired) {
+
                 ASSERT(param >= 0);
                 // End timer fired, finish up
                 onAnimationEnd(param);
@@ -1028,7 +1029,10 @@ void AnimationBase::updateStateMachine(AnimStateInput input, double param)
                 m_animState = AnimationStateDone;
                 
                 if (m_object) {
-                    resumeOverriddenAnimations();
+                    if (m_animation->fillsForwards())
+                        m_animState = AnimationStateFillingForwards;
+                    else
+                        resumeOverriddenAnimations();
 
                     // Fire off another style change so we can set the final value
                     m_compAnim->animationController()->addNodeChangeToDispatch(m_object->node());
@@ -1042,7 +1046,7 @@ void AnimationBase::updateStateMachine(AnimStateInput input, double param)
             // |this| may be deleted here
             break;
         case AnimationStatePausedWaitTimer:
-            ASSERT(input == AnimationStateInputPlayStateRunnning);
+            ASSERT(input == AnimationStateInputPlayStateRunning);
             ASSERT(paused());
             // Update the times
             m_startTime += beginAnimationUpdateTime() - m_pauseTime;
@@ -1058,7 +1062,7 @@ void AnimationBase::updateStateMachine(AnimStateInput input, double param)
             // AnimationStatePausedWaitResponse, we don't yet have a valid startTime, so we send 0 to startAnimation.
             // When the AnimationStateInputStartTimeSet comes in and we were in AnimationStatePausedRun, we will notice
             // that we have already set the startTime and will ignore it.
-            ASSERT(input == AnimationStateInputPlayStateRunnning || input == AnimationStateInputStartTimeSet);
+            ASSERT(input == AnimationStateInputPlayStateRunning || input == AnimationStateInputStartTimeSet);
             ASSERT(paused());
             
             // If we are paused, but we get the callback that notifies us that an accelerated animation started,
@@ -1093,6 +1097,7 @@ void AnimationBase::updateStateMachine(AnimStateInput input, double param)
                 m_fallbackAnimating = !started;
             }
             break;
+        case AnimationStateFillingForwards:
         case AnimationStateDone:
             // We're done. Stay in this state until we are deleted
             break;
@@ -1152,14 +1157,14 @@ void AnimationBase::fireAnimationEventsIfNeeded()
 void AnimationBase::updatePlayState(bool run)
 {
     if (paused() == run || isNew())
-        updateStateMachine(run ? AnimationStateInputPlayStateRunnning : AnimationStateInputPlayStatePaused, -1);
+        updateStateMachine(run ? AnimationStateInputPlayStateRunning : AnimationStateInputPlayStatePaused, -1);
 }
 
 double AnimationBase::timeToNextService()
 {
     // Returns the time at which next service is required. -1 means no service is required. 0 means 
     // service is required now, and > 0 means service is required that many seconds in the future.
-    if (paused() || isNew())
+    if (paused() || isNew() || m_animState == AnimationStateFillingForwards)
         return -1;
     
     if (m_animState == AnimationStateStartWaitTimer) {
@@ -1184,8 +1189,10 @@ double AnimationBase::progress(double scale, double offset, const TimingFunction
     if (m_animation->iterationCount() > 0)
         dur *= m_animation->iterationCount();
 
-    if (postActive() || !m_animation->duration() || (m_animation->iterationCount() > 0 && elapsedTime >= dur))
+    if (postActive() || !m_animation->duration())
         return 1.0;
+    if (m_animation->iterationCount() > 0 && elapsedTime >= dur)
+        return (m_animation->iterationCount() % 2) ? 1.0 : 0.0;
 
     // Compute the fractional time, taking into account direction.
     // There is no need to worry about iterations, we assume that we would have
index c367e0a..a957119 100644 (file)
@@ -71,7 +71,8 @@ public:
         AnimationStatePausedWaitTimer,      // in pause mode when animation started
         AnimationStatePausedWaitResponse,   // animation paused when in STARTING state
         AnimationStatePausedRun,            // animation paused when in LOOPING or ENDING state
-        AnimationStateDone                  // end timer fired, animation finished and removed
+        AnimationStateDone,                 // end timer fired, animation finished and removed
+        AnimationStateFillingForwards       // animation has ended and is retaining its final value
     };
 
     enum AnimStateInput {
@@ -85,7 +86,7 @@ public:
         AnimationStateInputEndTimerFired,     // end timer fired
         AnimationStateInputPauseOverride,     // pause an animation due to override
         AnimationStateInputResumeOverride,    // resume an overridden animation
-        AnimationStateInputPlayStateRunnning, // play state paused -> running
+        AnimationStateInputPlayStateRunning,  // play state paused -> running
         AnimationStateInputPlayStatePaused,   // play state running -> paused
         AnimationStateInputEndAnimation       // force an end from any state
     };
index c5e3660..0e76ea9 100644 (file)
@@ -120,9 +120,11 @@ void KeyframeAnimation::animate(CompositeAnimation*, RenderObject*, const Render
     }
 
     // If we are waiting for the start timer, we don't want to change the style yet.
-    // Special case - if the delay time is 0, then we do want to set the first frame of the
+    // Special case - if the delay time is 0, then we do want to set the first frame of the
     // animation right away. This avoids a flash when the animation starts.
-    if (waitingToStart() && m_animation->delay() > 0)
+    // Special case 2 - if there is a backwards fill mode, then we want to continue
+    // through to the style blend so that we get the fromStyle.
+    if (waitingToStart() && m_animation->delay() > 0 && !m_animation->fillsBackwards())
         return;
 
     // FIXME: we need to be more efficient about determining which keyframes we are animating between.
@@ -260,7 +262,10 @@ void KeyframeAnimation::onAnimationIteration(double elapsedTime)
 void KeyframeAnimation::onAnimationEnd(double elapsedTime)
 {
     sendAnimationEvent(eventNames().webkitAnimationEndEvent, elapsedTime);
-    endAnimation();
+    // End the animation if we don't fill forwards. Forward filling
+    // animations are ended properly in the class destructor.
+    if (!m_animation->fillsForwards())
+        endAnimation();
 }
 
 bool KeyframeAnimation::sendAnimationEvent(const AtomicString& eventType, double elapsedTime)
index 05761f8..bc33a9e 100644 (file)
@@ -32,10 +32,12 @@ Animation::Animation()
     , m_duration(initialAnimationDuration())
     , m_timingFunction(initialAnimationTimingFunction())
     , m_direction(initialAnimationDirection())
+    , m_fillMode(initialAnimationFillMode())
     , m_playState(initialAnimationPlayState())
     , m_delaySet(false)
     , m_directionSet(false)
     , m_durationSet(false)
+    , m_fillModeSet(false)
     , m_iterationCountSet(false)
     , m_nameSet(false)
     , m_playStateSet(false)
@@ -54,10 +56,12 @@ Animation::Animation(const Animation& o)
     , m_duration(o.m_duration)
     , m_timingFunction(o.m_timingFunction)
     , m_direction(o.m_direction)
+    , m_fillMode(o.m_fillMode)
     , m_playState(o.m_playState)
     , m_delaySet(o.m_delaySet)
     , m_directionSet(o.m_directionSet)
     , m_durationSet(o.m_durationSet)
+    , m_fillModeSet(o.m_fillModeSet)
     , m_iterationCountSet(o.m_iterationCountSet)
     , m_nameSet(o.m_nameSet)
     , m_playStateSet(o.m_playStateSet)
@@ -76,11 +80,13 @@ Animation& Animation::operator=(const Animation& o)
     m_duration = o.m_duration;
     m_timingFunction = o.m_timingFunction;
     m_direction = o.m_direction;
+    m_fillMode = o.m_fillMode;
     m_playState = o.m_playState;
 
     m_delaySet = o.m_delaySet;
     m_directionSet = o.m_directionSet;
     m_durationSet = o.m_durationSet;
+    m_fillModeSet = o.m_fillModeSet;
     m_iterationCountSet = o.m_iterationCountSet;
     m_nameSet = o.m_nameSet;
     m_playStateSet = o.m_playStateSet;
@@ -107,9 +113,11 @@ bool Animation::animationsMatch(const Animation* o, bool matchPlayStates) const
                   m_duration == o->m_duration &&
                   m_timingFunction == o->m_timingFunction &&
                   m_direction == o->m_direction &&
+                  m_fillMode == o->m_fillMode &&
                   m_delaySet == o->m_delaySet &&
                   m_directionSet == o->m_directionSet &&
                   m_durationSet == o->m_durationSet &&
+                  m_fillModeSet == o->m_fillModeSet &&
                   m_iterationCountSet == o->m_iterationCountSet &&
                   m_nameSet == o->m_nameSet &&
                   m_propertySet == o->m_propertySet &&
index 306a1b9..32628e1 100644 (file)
@@ -44,6 +44,7 @@ public:
     bool isDelaySet() const { return m_delaySet; }
     bool isDirectionSet() const { return m_directionSet; }
     bool isDurationSet() const { return m_durationSet; }
+    bool isFillModeSet() const { return m_fillModeSet; }
     bool isIterationCountSet() const { return m_iterationCountSet; }
     bool isNameSet() const { return m_nameSet; }
     bool isPlayStateSet() const { return m_playStateSet; }
@@ -58,8 +59,9 @@ public:
 
     bool isEmpty() const
     {
-        return (!m_directionSet && !m_durationSet && !m_nameSet && !m_playStateSet && 
-               !m_iterationCountSet && !m_delaySet && !m_timingFunctionSet && !m_propertySet);
+        return (!m_directionSet && !m_durationSet && !m_fillModeSet
+                && !m_nameSet && !m_playStateSet && !m_iterationCountSet
+                && !m_delaySet && !m_timingFunctionSet && !m_propertySet);
     }
 
     bool isEmptyOrZeroDuration() const
@@ -70,6 +72,7 @@ public:
     void clearDelay() { m_delaySet = false; }
     void clearDirection() { m_directionSet = false; }
     void clearDuration() { m_durationSet = false; }
+    void clearFillMode() { m_fillModeSet = false; }
     void clearIterationCount() { m_iterationCountSet = false; }
     void clearName() { m_nameSet = false; }
     void clearPlayState() { m_playStateSet = AnimPlayStatePlaying; }
@@ -81,6 +84,9 @@ public:
     enum AnimationDirection { AnimationDirectionNormal, AnimationDirectionAlternate };
     AnimationDirection direction() const { return m_direction; }
 
+    enum AnimationFillMode { AnimationFillModeNone, AnimationFillModeForwards, AnimationFillModeBackwards, AnimationFillModeBoth };
+    AnimationFillMode fillMode() const { return m_fillMode; }
+
     double duration() const { return m_duration; }
 
     enum { IterationCountInfinite = -1 };
@@ -93,6 +99,7 @@ public:
     void setDelay(double c) { m_delay = c; m_delaySet = true; }
     void setDirection(AnimationDirection d) { m_direction = d; m_directionSet = true; }
     void setDuration(double d) { ASSERT(d >= 0); m_duration = d; m_durationSet = true; }
+    void setFillMode(AnimationFillMode f) { m_fillMode = f; m_fillModeSet = true; }
     void setIterationCount(int c) { m_iterationCount = c; m_iterationCountSet = true; }
     void setName(const String& n) { m_name = n; m_nameSet = true; }
     void setPlayState(unsigned d) { m_playState = d; m_playStateSet = true; }
@@ -110,6 +117,9 @@ public:
     bool operator==(const Animation& o) const { return animationsMatch(&o); }
     bool operator!=(const Animation& o) const { return !(*this == o); }
 
+    bool fillsBackwards() const { return m_fillModeSet && (m_fillMode == AnimationFillModeBackwards || m_fillMode == AnimationFillModeBoth); }
+    bool fillsForwards() const { return m_fillModeSet && (m_fillMode == AnimationFillModeForwards || m_fillMode == AnimationFillModeBoth); }
+
 private:
     Animation();
     Animation(const Animation& o);
@@ -121,12 +131,14 @@ private:
     double m_duration;
     TimingFunction m_timingFunction;
     AnimationDirection m_direction : 1;
+    AnimationFillMode m_fillMode : 2;
 
     unsigned m_playState     : 2;
 
     bool m_delaySet          : 1;
     bool m_directionSet      : 1;
     bool m_durationSet       : 1;
+    bool m_fillModeSet       : 1;
     bool m_iterationCountSet : 1;
     bool m_nameSet           : 1;
     bool m_playStateSet      : 1;
@@ -139,6 +151,7 @@ public:
     static float initialAnimationDelay() { return 0; }
     static AnimationDirection initialAnimationDirection() { return AnimationDirectionNormal; }
     static double initialAnimationDuration() { return 0; }
+    static AnimationFillMode initialAnimationFillMode() { return AnimationFillModeNone; }
     static int initialAnimationIterationCount() { return 1; }
     static String initialAnimationName() { return String("none"); }
     static unsigned initialAnimationPlayState() { return AnimPlayStatePlaying; }
index 804dede..bd5fdee 100644 (file)
@@ -37,6 +37,7 @@ void AnimationList::fillUnsetProperties()
     FILL_UNSET_PROPERTY(isDelaySet, delay, setDelay);
     FILL_UNSET_PROPERTY(isDirectionSet, direction, setDirection);
     FILL_UNSET_PROPERTY(isDurationSet, duration, setDuration);
+    FILL_UNSET_PROPERTY(isFillModeSet, fillMode, setFillMode);
     FILL_UNSET_PROPERTY(isIterationCountSet, iterationCount, setIterationCount);
     FILL_UNSET_PROPERTY(isPlayStateSet, playState, setPlayState);
     FILL_UNSET_PROPERTY(isNameSet, name, setName);
index 1362b99..d9ef105 100644 (file)
@@ -1859,12 +1859,28 @@ void GraphicsLayerCA::setupAnimation(CAPropertyAnimation* propertyAnim, const An
     else if (anim->direction() == Animation::AnimationDirectionAlternate)
         repeatCount /= 2;
 
+    NSString* fillMode = 0;
+    switch (anim->fillMode()) {
+    case Animation::AnimationFillModeNone:
+        fillMode = kCAFillModeRemoved;
+        break;
+    case Animation::AnimationFillModeBackwards:
+        fillMode = kCAFillModeBackwards;
+        break;
+    case Animation::AnimationFillModeForwards:
+       fillMode = kCAFillModeForwards;
+       break;
+    case Animation::AnimationFillModeBoth:
+       fillMode = kCAFillModeBoth;
+       break;
+    }
+
     [propertyAnim setDuration:duration];
     [propertyAnim setRepeatCount:repeatCount];
     [propertyAnim setAutoreverses:anim->direction()];
     [propertyAnim setRemovedOnCompletion:NO];
     [propertyAnim setAdditive:additive];
-    [propertyAnim setFillMode:@"extended"];
+    [propertyAnim setFillMode:fillMode];
 
     [propertyAnim setDelegate:m_animationDelegate.get()];
 }