Add support for the frames() timing function
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 Jan 2018 13:26:50 +0000 (13:26 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 Jan 2018 13:26:50 +0000 (13:26 +0000)
https://bugs.webkit.org/show_bug.cgi?id=181585
<rdar://problem/36463317>

Reviewed by Dean.

Source/WebCore:

Implement the frames() timing function as specified in the CSS Timing Functions Level 1
specification, specifically https://www.w3.org/TR/css-timing-1/#frames-timing-functions.
A frames timing function is a type of timing function that divides the input time into a
specified number of intervals of equal length.

Test: transitions/frames-timing-function.html

* css/CSSComputedStyleDeclaration.cpp:
(WebCore::createTimingFunctionValue):
* css/CSSTimingFunctionValue.cpp:
(WebCore::CSSFramesTimingFunctionValue::customCSSText const):
(WebCore::CSSFramesTimingFunctionValue::equals const):
* css/CSSTimingFunctionValue.h:
* css/CSSToStyleMap.cpp:
(WebCore::CSSToStyleMap::mapAnimationTimingFunction):
* css/CSSValue.cpp:
(WebCore::CSSValue::equals const):
(WebCore::CSSValue::cssText const):
(WebCore::CSSValue::destroy):
* css/CSSValue.h:
(WebCore::CSSValue::isFramesTimingFunctionValue const):
* css/CSSValueKeywords.in:
* css/parser/CSSPropertyParser.cpp:
(WebCore::consumeSteps):
(WebCore::consumeFrames):
(WebCore::consumeAnimationTimingFunction):
* platform/animation/TimingFunction.cpp:
(WebCore::operator<<):
(WebCore::TimingFunction::transformTime const):
* platform/animation/TimingFunction.h:
(WebCore::TimingFunction::isFramesTimingFunction const):
* platform/graphics/ca/GraphicsLayerCA.cpp:
(WebCore::animationHasFramesTimingFunction):
(WebCore::GraphicsLayerCA::animationCanBeAccelerated const):

Source/WebKit:

Add the ability to endode and decode the frames() timing function.

* Shared/WebCoreArgumentCoders.cpp:
(IPC::ArgumentCoder<FramesTimingFunction>::encode):
(IPC::ArgumentCoder<FramesTimingFunction>::decode):
* Shared/WebCoreArgumentCoders.h:
* WebProcess/WebPage/RemoteLayerTree/PlatformCAAnimationRemote.mm:
(WebKit::PlatformCAAnimationRemote::Properties::encode const):
(WebKit::PlatformCAAnimationRemote::Properties::decode):

LayoutTests:

Add a new test that checks that the frames() timing function applies as expected
and expand an existing test to check that the frames() timing function is parsed
correctly. We also mark progressions in imported WPT tests.

* imported/w3c/web-platform-tests/css-timing-1/frames-timing-functions-output-expected.txt:
* imported/w3c/web-platform-tests/css-timing-1/frames-timing-functions-syntax-expected.txt:
* transitions/frames-timing-function-expected.txt: Added.
* transitions/frames-timing-function.html: Added.
* transitions/transitions-parsing-expected.txt:
* transitions/transitions-parsing.html:

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

23 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/css-timing-1/frames-timing-functions-output-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css-timing-1/frames-timing-functions-syntax-expected.txt
LayoutTests/transitions/frames-timing-function-expected.txt [new file with mode: 0644]
LayoutTests/transitions/frames-timing-function.html [new file with mode: 0644]
LayoutTests/transitions/transitions-parsing-expected.txt
LayoutTests/transitions/transitions-parsing.html
Source/WebCore/ChangeLog
Source/WebCore/css/CSSComputedStyleDeclaration.cpp
Source/WebCore/css/CSSTimingFunctionValue.cpp
Source/WebCore/css/CSSTimingFunctionValue.h
Source/WebCore/css/CSSToStyleMap.cpp
Source/WebCore/css/CSSValue.cpp
Source/WebCore/css/CSSValue.h
Source/WebCore/css/CSSValueKeywords.in
Source/WebCore/css/parser/CSSPropertyParser.cpp
Source/WebCore/platform/animation/TimingFunction.cpp
Source/WebCore/platform/animation/TimingFunction.h
Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebCoreArgumentCoders.cpp
Source/WebKit/Shared/WebCoreArgumentCoders.h
Source/WebKit/WebProcess/WebPage/RemoteLayerTree/PlatformCAAnimationRemote.mm

index 19041a7..14000dd 100644 (file)
@@ -1,3 +1,22 @@
+2018-01-12  Antoine Quint  <graouts@apple.com>
+
+        Add support for the frames() timing function
+        https://bugs.webkit.org/show_bug.cgi?id=181585
+        <rdar://problem/36463317>
+
+        Reviewed by Dean.
+
+        Add a new test that checks that the frames() timing function applies as expected
+        and expand an existing test to check that the frames() timing function is parsed
+        correctly. We also mark progressions in imported WPT tests.
+
+        * imported/w3c/web-platform-tests/css-timing-1/frames-timing-functions-output-expected.txt:
+        * imported/w3c/web-platform-tests/css-timing-1/frames-timing-functions-syntax-expected.txt:
+        * transitions/frames-timing-function-expected.txt: Added.
+        * transitions/frames-timing-function.html: Added.
+        * transitions/transitions-parsing-expected.txt:
+        * transitions/transitions-parsing.html:
+
 2018-01-11  Chris Dumez  <cdumez@apple.com>
 
         Setting Window.opener to null should disown its opener
 2018-01-11  Chris Dumez  <cdumez@apple.com>
 
         Setting Window.opener to null should disown its opener
index 9d92b82..6cef842 100644 (file)
@@ -1,9 +1,9 @@
 
 
-FAIL For an input progress of 0.0, the output of a frames timing function is the first frame assert_equals: expected "0px" but got "auto"
-FAIL At a frame boundary, the output of a frames timing function is the next frame assert_equals: expected "0px" but got "auto"
-FAIL For an input progress of 1.0, the output of a frames timing function is the final frame assert_equals: expected "100px" but got "auto"
-FAIL The number of frames is correctly reflected in the frames timing function output assert_equals: expected "0px" but got "auto"
-FAIL The number of frames is correctly reflected in the frames timing function output on CSS Transitions assert_equals: expected "0px" but got "100px"
+PASS For an input progress of 0.0, the output of a frames timing function is the first frame 
+FAIL At a frame boundary, the output of a frames timing function is the next frame assert_equals: expected "100px" but got "0px"
+FAIL For an input progress of 1.0, the output of a frames timing function is the final frame assert_equals: expected "100px" but got "0px"
+FAIL The number of frames is correctly reflected in the frames timing function output assert_equals: expected "10px" but got "0px"
+FAIL The number of frames is correctly reflected in the frames timing function output on CSS Transitions assert_equals: expected "10px" but got "0px"
 FAIL frames easing with input progress greater than 1 assert_equals: expected "100px" but got "0px"
 FAIL frames easing with input progress greater than 1.5 assert_equals: expected "300px" but got "0px"
 FAIL frames easing with input progress less than 0 assert_equals: expected "-100px" but got "0px"
 FAIL frames easing with input progress greater than 1 assert_equals: expected "100px" but got "0px"
 FAIL frames easing with input progress greater than 1.5 assert_equals: expected "300px" but got "0px"
 FAIL frames easing with input progress less than 0 assert_equals: expected "-100px" but got "0px"
index a12008e..17a8b48 100644 (file)
@@ -1,4 +1,4 @@
 
 PASS The number of frames must be a positive integer greater than 1, or we fallback to the previously-set easing 
 
 PASS The number of frames must be a positive integer greater than 1, or we fallback to the previously-set easing 
-FAIL The serialization of frames is 'frames(n)', n is the number of frames assert_equals: expected "frames(2)" but got "ease"
+PASS The serialization of frames is 'frames(n)', n is the number of frames 
 
 
diff --git a/LayoutTests/transitions/frames-timing-function-expected.txt b/LayoutTests/transitions/frames-timing-function-expected.txt
new file mode 100644 (file)
index 0000000..08cf6a9
--- /dev/null
@@ -0,0 +1,6 @@
+The box should move horizontally 200px over 1s, in 4 equal increments.
+
+PASS - "-webkit-transform.4" property for "box" element at 0.25s saw something close to: 50
+PASS - "-webkit-transform.4" property for "box" element at 0.5s saw something close to: 100
+PASS - "-webkit-transform.4" property for "box" element at 0.75s saw something close to: 150
+
diff --git a/LayoutTests/transitions/frames-timing-function.html b/LayoutTests/transitions/frames-timing-function.html
new file mode 100644 (file)
index 0000000..e8b1621
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+  <style>
+    #box {
+      height: 100px;
+      width: 100px;
+      background-color: blue;
+      -webkit-transform: translateX(0px);
+      -webkit-transition-duration: 1s;
+      -webkit-transition-timing-function: frames(5);
+      -webkit-transition-property: -webkit-transform;
+    }
+  </style>
+  <script src="resources/transition-test-helpers.js"></script>
+  <script type="text/javascript">
+
+    const expectedValues = [
+      // [time, element-id, property, expected-value, tolerance]
+      [0.25, "box", "-webkit-transform.4", 50, 5],
+      [0.5, "box", "-webkit-transform.4", 100, 5],
+      [0.75, "box", "-webkit-transform.4", 150, 5],
+    ];
+
+    function setupTest()
+    {
+      var box = document.getElementById('box');
+      box.style.webkitTransform = 'translateX(200px)';
+    }
+
+    runTransitionTest(expectedValues, setupTest, usePauseAPI);
+  </script>
+</head>
+<body>
+
+<p>
+The box should move horizontally 200px over 1s, in 4 equal increments.
+</p>
+<div id="box">
+</div>
+<div id="result">
+</div>
+</body>
+</html>
index 24b9897..6e317fc 100644 (file)
@@ -220,6 +220,10 @@ PASS style.transitionTimingFunction is 'steps(5, start)'
 PASS computedStyle.transitionTimingFunction is 'steps(5, start)'
 PASS style.webkitTransitionTimingFunction is 'steps(5, start)'
 PASS computedStyle.webkitTransitionTimingFunction is 'steps(5, start)'
 PASS computedStyle.transitionTimingFunction is 'steps(5, start)'
 PASS style.webkitTransitionTimingFunction is 'steps(5, start)'
 PASS computedStyle.webkitTransitionTimingFunction is 'steps(5, start)'
+PASS style.transitionTimingFunction is 'frames(2)'
+PASS computedStyle.transitionTimingFunction is 'frames(2)'
+PASS style.webkitTransitionTimingFunction is 'frames(2)'
+PASS computedStyle.webkitTransitionTimingFunction is 'frames(2)'
 PASS style.transitionTimingFunction is 'ease-in-out, ease-in'
 PASS computedStyle.transitionTimingFunction is 'ease-in-out, ease-in'
 PASS style.webkitTransitionTimingFunction is 'ease-in-out, ease-in'
 PASS style.transitionTimingFunction is 'ease-in-out, ease-in'
 PASS computedStyle.transitionTimingFunction is 'ease-in-out, ease-in'
 PASS style.webkitTransitionTimingFunction is 'ease-in-out, ease-in'
@@ -466,6 +470,60 @@ PASS style.transition is ''
 PASS computedStyle.transition is 'all 0s ease 0s'
 PASS style.webkitTransition is ''
 PASS computedStyle.webkitTransition is 'all 0s ease 0s'
 PASS computedStyle.transition is 'all 0s ease 0s'
 PASS style.webkitTransition is ''
 PASS computedStyle.webkitTransition is 'all 0s ease 0s'
+
+Testing frames() invalid values: frames(-10)
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
+
+Testing frames() invalid values: frames(0)
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
+
+Testing frames() invalid values: frames(1)
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
+
+Testing frames() invalid values: frames(2.5)
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
+
+Testing frames() invalid values: frames()
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
+
+Testing frames() invalid values: frames(eggs)
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
+
+Testing frames() invalid values: frames(NaN)
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
+
+Testing frames() invalid values: frames()
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
+
+Testing frames() invalid values: frames([object Object])
+PASS framesInvalidElement.style.transitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).transitionTimingFunction is 'ease'
+PASS framesInvalidElement.style.webkitTransitionTimingFunction is ''
+PASS getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction is 'ease'
 PASS successfullyParsed is true
 
 TEST COMPLETE
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 6e0d532..f458451 100644 (file)
@@ -359,6 +359,12 @@ shouldBe("computedStyle.transitionTimingFunction", "'steps(5, start)'");
 shouldBe("style.webkitTransitionTimingFunction", "'steps(5, start)'");
 shouldBe("computedStyle.webkitTransitionTimingFunction", "'steps(5, start)'");
 
 shouldBe("style.webkitTransitionTimingFunction", "'steps(5, start)'");
 shouldBe("computedStyle.webkitTransitionTimingFunction", "'steps(5, start)'");
 
+style.transitionTimingFunction = "frames(2)";
+shouldBe("style.transitionTimingFunction", "'frames(2)'");
+shouldBe("computedStyle.transitionTimingFunction", "'frames(2)'");
+shouldBe("style.webkitTransitionTimingFunction", "'frames(2)'");
+shouldBe("computedStyle.webkitTransitionTimingFunction", "'frames(2)'");
+
 style.transitionProperty = "opacity, width";
 
 style.transitionTimingFunction = "ease-in-out, ease-in";
 style.transitionProperty = "opacity, width";
 
 style.transitionTimingFunction = "ease-in-out, ease-in";
@@ -735,6 +741,21 @@ shouldBe("style.webkitTransition", "''");
 shouldBe("computedStyle.webkitTransition", "'all 0s ease 0s'");
 
 document.body.removeChild(testContainer);
 shouldBe("computedStyle.webkitTransition", "'all 0s ease 0s'");
 
 document.body.removeChild(testContainer);
+
+let framesInvalidElement;
+[-10, 0, 1, 2.5, "", "eggs", NaN, new Array, {}].forEach(invalidValue => {
+    framesInvalidElement = document.body.appendChild(document.createElement("div"));
+    const value = `frames(${invalidValue})`;
+    debug("");
+    debug(`Testing frames() invalid values: ${value}`);
+    framesInvalidElement.style.transitionTimingFunction = value;
+    shouldBe("framesInvalidElement.style.transitionTimingFunction", "''");
+    shouldBe("getComputedStyle(framesInvalidElement).transitionTimingFunction", "'ease'");
+    shouldBe("framesInvalidElement.style.webkitTransitionTimingFunction", "''");
+    shouldBe("getComputedStyle(framesInvalidElement).webkitTransitionTimingFunction", "'ease'");
+    framesInvalidElement.remove();
+});
+
 </script>
 <script src="../resources/js-test-post.js"></script>
 </body>
 </script>
 <script src="../resources/js-test-post.js"></script>
 </body>
index b52f2f0..e028400 100644 (file)
@@ -1,3 +1,46 @@
+2018-01-12  Antoine Quint  <graouts@apple.com>
+
+        Add support for the frames() timing function
+        https://bugs.webkit.org/show_bug.cgi?id=181585
+        <rdar://problem/36463317>
+
+        Reviewed by Dean.
+
+        Implement the frames() timing function as specified in the CSS Timing Functions Level 1
+        specification, specifically https://www.w3.org/TR/css-timing-1/#frames-timing-functions.
+        A frames timing function is a type of timing function that divides the input time into a
+        specified number of intervals of equal length.
+
+        Test: transitions/frames-timing-function.html
+
+        * css/CSSComputedStyleDeclaration.cpp:
+        (WebCore::createTimingFunctionValue):
+        * css/CSSTimingFunctionValue.cpp:
+        (WebCore::CSSFramesTimingFunctionValue::customCSSText const):
+        (WebCore::CSSFramesTimingFunctionValue::equals const):
+        * css/CSSTimingFunctionValue.h:
+        * css/CSSToStyleMap.cpp:
+        (WebCore::CSSToStyleMap::mapAnimationTimingFunction):
+        * css/CSSValue.cpp:
+        (WebCore::CSSValue::equals const):
+        (WebCore::CSSValue::cssText const):
+        (WebCore::CSSValue::destroy):
+        * css/CSSValue.h:
+        (WebCore::CSSValue::isFramesTimingFunctionValue const):
+        * css/CSSValueKeywords.in:
+        * css/parser/CSSPropertyParser.cpp:
+        (WebCore::consumeSteps):
+        (WebCore::consumeFrames):
+        (WebCore::consumeAnimationTimingFunction):
+        * platform/animation/TimingFunction.cpp:
+        (WebCore::operator<<):
+        (WebCore::TimingFunction::transformTime const):
+        * platform/animation/TimingFunction.h:
+        (WebCore::TimingFunction::isFramesTimingFunction const):
+        * platform/graphics/ca/GraphicsLayerCA.cpp:
+        (WebCore::animationHasFramesTimingFunction):
+        (WebCore::GraphicsLayerCA::animationCanBeAccelerated const):
+
 2018-01-12  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r226721.
 2018-01-12  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r226721.
index 3bff5ae..ab2a178 100644 (file)
@@ -1568,6 +1568,10 @@ static Ref<CSSValue> createTimingFunctionValue(const TimingFunction& timingFunct
         auto& function = static_cast<const StepsTimingFunction&>(timingFunction);
         return CSSStepsTimingFunctionValue::create(function.numberOfSteps(), function.stepAtStart());
     }
         auto& function = static_cast<const StepsTimingFunction&>(timingFunction);
         return CSSStepsTimingFunctionValue::create(function.numberOfSteps(), function.stepAtStart());
     }
+    case TimingFunction::FramesFunction: {
+        auto& function = static_cast<const FramesTimingFunction&>(timingFunction);
+        return CSSFramesTimingFunctionValue::create(function.numberOfFrames());
+    }
     case TimingFunction::SpringFunction: {
         auto& function = static_cast<const SpringTimingFunction&>(timingFunction);
         return CSSSpringTimingFunctionValue::create(function.mass(), function.stiffness(), function.damping(), function.initialVelocity());
     case TimingFunction::SpringFunction: {
         auto& function = static_cast<const SpringTimingFunction&>(timingFunction);
         return CSSSpringTimingFunctionValue::create(function.mass(), function.stiffness(), function.damping(), function.initialVelocity());
index 2dd6195..90ef6cd 100644 (file)
@@ -67,6 +67,20 @@ bool CSSStepsTimingFunctionValue::equals(const CSSStepsTimingFunctionValue& othe
     return m_steps == other.m_steps && m_stepAtStart == other.m_stepAtStart;
 }
 
     return m_steps == other.m_steps && m_stepAtStart == other.m_stepAtStart;
 }
 
+String CSSFramesTimingFunctionValue::customCSSText() const
+{
+    StringBuilder builder;
+    builder.appendLiteral("frames(");
+    builder.appendNumber(m_frames);
+    builder.appendLiteral(")");
+    return builder.toString();
+}
+
+bool CSSFramesTimingFunctionValue::equals(const CSSFramesTimingFunctionValue& other) const
+{
+    return m_frames == other.m_frames;
+}
+
 String CSSSpringTimingFunctionValue::customCSSText() const
 {
     StringBuilder builder;
 String CSSSpringTimingFunctionValue::customCSSText() const
 {
     StringBuilder builder;
index a9bf4ee..2283b34 100644 (file)
@@ -87,6 +87,29 @@ private:
     bool m_stepAtStart;
 };
 
     bool m_stepAtStart;
 };
 
+class CSSFramesTimingFunctionValue final : public CSSValue {
+public:
+    static Ref<CSSFramesTimingFunctionValue> create(unsigned frames)
+    {
+        return adoptRef(*new CSSFramesTimingFunctionValue(frames));
+    }
+
+    unsigned numberOfFrames() const { return m_frames; }
+
+    String customCSSText() const;
+
+    bool equals(const CSSFramesTimingFunctionValue&) const;
+
+private:
+    CSSFramesTimingFunctionValue(int frames)
+        : CSSValue(FramesTimingFunctionClass)
+        , m_frames(frames)
+    {
+    }
+
+    unsigned m_frames;
+};
+
 class CSSSpringTimingFunctionValue final : public CSSValue {
 public:
     static Ref<CSSSpringTimingFunctionValue> create(double mass, double stiffness, double damping, double initialVelocity)
 class CSSSpringTimingFunctionValue final : public CSSValue {
 public:
     static Ref<CSSSpringTimingFunctionValue> create(double mass, double stiffness, double damping, double initialVelocity)
@@ -123,4 +146,5 @@ private:
 
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSCubicBezierTimingFunctionValue, isCubicBezierTimingFunctionValue())
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSStepsTimingFunctionValue, isStepsTimingFunctionValue())
 
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSCubicBezierTimingFunctionValue, isCubicBezierTimingFunctionValue())
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSStepsTimingFunctionValue, isStepsTimingFunctionValue())
+SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSFramesTimingFunctionValue, isFramesTimingFunctionValue())
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSSpringTimingFunctionValue, isSpringTimingFunctionValue())
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSSpringTimingFunctionValue, isSpringTimingFunctionValue())
index f9e4589..3520551 100644 (file)
@@ -501,6 +501,9 @@ void CSSToStyleMap::mapAnimationTimingFunction(Animation& animation, const CSSVa
     } else if (is<CSSStepsTimingFunctionValue>(value)) {
         auto& stepsTimingFunction = downcast<CSSStepsTimingFunctionValue>(value);
         animation.setTimingFunction(StepsTimingFunction::create(stepsTimingFunction.numberOfSteps(), stepsTimingFunction.stepAtStart()));
     } else if (is<CSSStepsTimingFunctionValue>(value)) {
         auto& stepsTimingFunction = downcast<CSSStepsTimingFunctionValue>(value);
         animation.setTimingFunction(StepsTimingFunction::create(stepsTimingFunction.numberOfSteps(), stepsTimingFunction.stepAtStart()));
+    } else if (is<CSSFramesTimingFunctionValue>(value)) {
+        auto& framesTimingFunction = downcast<CSSFramesTimingFunctionValue>(value);
+        animation.setTimingFunction(FramesTimingFunction::create(framesTimingFunction.numberOfFrames()));
     } else if (is<CSSSpringTimingFunctionValue>(value)) {
         auto& springTimingFunction = downcast<CSSSpringTimingFunctionValue>(value);
         animation.setTimingFunction(SpringTimingFunction::create(springTimingFunction.mass(), springTimingFunction.stiffness(), springTimingFunction.damping(), springTimingFunction.initialVelocity()));
     } else if (is<CSSSpringTimingFunctionValue>(value)) {
         auto& springTimingFunction = downcast<CSSSpringTimingFunctionValue>(value);
         animation.setTimingFunction(SpringTimingFunction::create(springTimingFunction.mass(), springTimingFunction.stiffness(), springTimingFunction.damping(), springTimingFunction.initialVelocity()));
index 4e18f0e..d828445 100644 (file)
@@ -186,6 +186,8 @@ bool CSSValue::equals(const CSSValue& other) const
             return compareCSSValues<CSSCubicBezierTimingFunctionValue>(*this, other);
         case StepsTimingFunctionClass:
             return compareCSSValues<CSSStepsTimingFunctionValue>(*this, other);
             return compareCSSValues<CSSCubicBezierTimingFunctionValue>(*this, other);
         case StepsTimingFunctionClass:
             return compareCSSValues<CSSStepsTimingFunctionValue>(*this, other);
+        case FramesTimingFunctionClass:
+            return compareCSSValues<CSSFramesTimingFunctionValue>(*this, other);
         case SpringTimingFunctionClass:
             return compareCSSValues<CSSSpringTimingFunctionValue>(*this, other);
         case UnicodeRangeClass:
         case SpringTimingFunctionClass:
             return compareCSSValues<CSSSpringTimingFunctionValue>(*this, other);
         case UnicodeRangeClass:
@@ -286,6 +288,8 @@ String CSSValue::cssText() const
         return downcast<CSSCubicBezierTimingFunctionValue>(*this).customCSSText();
     case StepsTimingFunctionClass:
         return downcast<CSSStepsTimingFunctionValue>(*this).customCSSText();
         return downcast<CSSCubicBezierTimingFunctionValue>(*this).customCSSText();
     case StepsTimingFunctionClass:
         return downcast<CSSStepsTimingFunctionValue>(*this).customCSSText();
+    case FramesTimingFunctionClass:
+        return downcast<CSSFramesTimingFunctionValue>(*this).customCSSText();
     case SpringTimingFunctionClass:
         return downcast<CSSSpringTimingFunctionValue>(*this).customCSSText();
     case UnicodeRangeClass:
     case SpringTimingFunctionClass:
         return downcast<CSSSpringTimingFunctionValue>(*this).customCSSText();
     case UnicodeRangeClass:
@@ -408,6 +412,9 @@ void CSSValue::destroy()
     case StepsTimingFunctionClass:
         delete downcast<CSSStepsTimingFunctionValue>(this);
         return;
     case StepsTimingFunctionClass:
         delete downcast<CSSStepsTimingFunctionValue>(this);
         return;
+    case FramesTimingFunctionClass:
+        delete downcast<CSSFramesTimingFunctionValue>(this);
+        return;
     case SpringTimingFunctionClass:
         delete downcast<CSSSpringTimingFunctionValue>(this);
         return;
     case SpringTimingFunctionClass:
         delete downcast<CSSSpringTimingFunctionValue>(this);
         return;
index c86c33e..d44b075 100644 (file)
@@ -101,6 +101,7 @@ public:
     bool isShadowValue() const { return m_classType == ShadowClass; }
     bool isCubicBezierTimingFunctionValue() const { return m_classType == CubicBezierTimingFunctionClass; }
     bool isStepsTimingFunctionValue() const { return m_classType == StepsTimingFunctionClass; }
     bool isShadowValue() const { return m_classType == ShadowClass; }
     bool isCubicBezierTimingFunctionValue() const { return m_classType == CubicBezierTimingFunctionClass; }
     bool isStepsTimingFunctionValue() const { return m_classType == StepsTimingFunctionClass; }
+    bool isFramesTimingFunctionValue() const { return m_classType == FramesTimingFunctionClass; }
     bool isSpringTimingFunctionValue() const { return m_classType == SpringTimingFunctionClass; }
     bool isLineBoxContainValue() const { return m_classType == LineBoxContainClass; }
     bool isCalcValue() const {return m_classType == CalculationClass; }
     bool isSpringTimingFunctionValue() const { return m_classType == SpringTimingFunctionClass; }
     bool isLineBoxContainValue() const { return m_classType == LineBoxContainClass; }
     bool isCalcValue() const {return m_classType == CalculationClass; }
@@ -150,6 +151,7 @@ protected:
         // Timing function classes.
         CubicBezierTimingFunctionClass,
         StepsTimingFunctionClass,
         // Timing function classes.
         CubicBezierTimingFunctionClass,
         StepsTimingFunctionClass,
+        FramesTimingFunctionClass,
         SpringTimingFunctionClass,
 
         // Other class types.
         SpringTimingFunctionClass,
 
         // Other class types.
index 7b2852a..d1287ba 100644 (file)
@@ -1207,6 +1207,7 @@ url
 cubic-bezier
 spring
 steps
 cubic-bezier
 spring
 steps
+frames
 
 // colors
 rgb
 
 // colors
 rgb
index 9f35c7d..888ee88 100644 (file)
@@ -1446,7 +1446,8 @@ static RefPtr<CSSValue> consumeTransitionProperty(CSSParserTokenRange& range)
 }
 
     
 }
 
     
-static RefPtr<CSSValue> consumeSteps(CSSParserTokenRange& range) {
+static RefPtr<CSSValue> consumeSteps(CSSParserTokenRange& range)
+{
     ASSERT(range.peek().functionId() == CSSValueSteps);
     CSSParserTokenRange rangeCopy = range;
     CSSParserTokenRange args = consumeFunction(rangeCopy);
     ASSERT(range.peek().functionId() == CSSValueSteps);
     CSSParserTokenRange rangeCopy = range;
     CSSParserTokenRange args = consumeFunction(rangeCopy);
@@ -1477,6 +1478,27 @@ static RefPtr<CSSValue> consumeSteps(CSSParserTokenRange& range) {
     return CSSStepsTimingFunctionValue::create(steps->intValue(), stepAtStart);
 }
 
     return CSSStepsTimingFunctionValue::create(steps->intValue(), stepAtStart);
 }
 
+static RefPtr<CSSValue> consumeFrames(CSSParserTokenRange& range)
+{
+    ASSERT(range.peek().functionId() == CSSValueFrames);
+    CSSParserTokenRange rangeCopy = range;
+    CSSParserTokenRange args = consumeFunction(rangeCopy);
+    
+    RefPtr<CSSPrimitiveValue> frames = consumePositiveInteger(args);
+    if (!frames)
+        return nullptr;
+
+    auto numberOfFrames = frames->intValue();
+    if (numberOfFrames < 2)
+        return nullptr;
+    
+    if (!args.atEnd())
+        return nullptr;
+    
+    range = rangeCopy;
+    return CSSFramesTimingFunctionValue::create(numberOfFrames);
+}
+
 static RefPtr<CSSValue> consumeCubicBezier(CSSParserTokenRange& range)
 {
     ASSERT(range.peek().functionId() == CSSValueCubicBezier);
 static RefPtr<CSSValue> consumeCubicBezier(CSSParserTokenRange& range)
 {
     ASSERT(range.peek().functionId() == CSSValueCubicBezier);
@@ -1547,6 +1569,8 @@ static RefPtr<CSSValue> consumeAnimationTimingFunction(CSSParserTokenRange& rang
         return consumeCubicBezier(range);
     if (function == CSSValueSteps)
         return consumeSteps(range);
         return consumeCubicBezier(range);
     if (function == CSSValueSteps)
         return consumeSteps(range);
+    if (function == CSSValueFrames)
+        return consumeFrames(range);
     if (context.springTimingFunctionEnabled && function == CSSValueSpring)
         return consumeSpringFunction(range);
     return nullptr;
     if (context.springTimingFunctionEnabled && function == CSSValueSpring)
         return consumeSpringFunction(range);
     return nullptr;
index 7765b68..30d6e69 100644 (file)
@@ -48,6 +48,11 @@ TextStream& operator<<(TextStream& ts, const TimingFunction& timingFunction)
         ts << "steps(" << function.numberOfSteps() << ", " << (function.stepAtStart() ? "start" : "end") << ")";
         break;
     }
         ts << "steps(" << function.numberOfSteps() << ", " << (function.stepAtStart() ? "start" : "end") << ")";
         break;
     }
+    case TimingFunction::FramesFunction: {
+        auto& function = static_cast<const FramesTimingFunction&>(timingFunction);
+        ts << "frames(" << function.numberOfFrames() << ")";
+        break;
+    }
     case TimingFunction::SpringFunction: {
         auto& function = static_cast<const SpringTimingFunction&>(timingFunction);
         ts << "spring(" << function.mass() << " " << function.stiffness() << " " <<  function.damping() << " " << function.initialVelocity() << ")";
     case TimingFunction::SpringFunction: {
         auto& function = static_cast<const SpringTimingFunction&>(timingFunction);
         ts << "spring(" << function.mass() << " " << function.stiffness() << " " <<  function.damping() << " " << function.initialVelocity() << ")";
@@ -74,6 +79,16 @@ double TimingFunction::transformTime(double inputTime, double duration) const
             return std::min(1.0, (std::floor(numberOfSteps * inputTime) + 1) / numberOfSteps);
         return std::floor(numberOfSteps * inputTime) / numberOfSteps;
     }
             return std::min(1.0, (std::floor(numberOfSteps * inputTime) + 1) / numberOfSteps);
         return std::floor(numberOfSteps * inputTime) / numberOfSteps;
     }
+    case TimingFunction::FramesFunction: {
+        // https://drafts.csswg.org/css-timing/#frames-timing-functions
+        auto& function = *static_cast<const FramesTimingFunction*>(this);
+        auto numberOfFrames = function.numberOfFrames();
+        ASSERT(numberOfFrames > 1);
+        auto outputTime = std::floor(inputTime * numberOfFrames) / (numberOfFrames - 1);
+        if (inputTime <= 1 && outputTime > 1)
+            return 1;
+        return outputTime;
+    }
     case TimingFunction::SpringFunction: {
         auto& function = *static_cast<const SpringTimingFunction*>(this);
         return SpringSolver(function.mass(), function.stiffness(), function.damping(), function.initialVelocity()).solve(inputTime * duration);
     case TimingFunction::SpringFunction: {
         auto& function = *static_cast<const SpringTimingFunction*>(this);
         return SpringSolver(function.mass(), function.stiffness(), function.damping(), function.initialVelocity()).solve(inputTime * duration);
index bcf8c00..dcc0896 100644 (file)
@@ -39,12 +39,13 @@ public:
 
     virtual ~TimingFunction() = default;
 
 
     virtual ~TimingFunction() = default;
 
-    enum TimingFunctionType { LinearFunction, CubicBezierFunction, StepsFunction, SpringFunction };
+    enum TimingFunctionType { LinearFunction, CubicBezierFunction, StepsFunction, FramesFunction, SpringFunction };
     TimingFunctionType type() const { return m_type; }
 
     bool isLinearTimingFunction() const { return m_type == LinearFunction; }
     bool isCubicBezierTimingFunction() const { return m_type == CubicBezierFunction; }
     bool isStepsTimingFunction() const { return m_type == StepsFunction; }
     TimingFunctionType type() const { return m_type; }
 
     bool isLinearTimingFunction() const { return m_type == LinearFunction; }
     bool isCubicBezierTimingFunction() const { return m_type == CubicBezierFunction; }
     bool isStepsTimingFunction() const { return m_type == StepsFunction; }
+    bool isFramesTimingFunction() const { return m_type == FramesFunction; }
     bool isSpringTimingFunction() const { return m_type == SpringFunction; }
 
     virtual bool operator==(const TimingFunction&) const = 0;
     bool isSpringTimingFunction() const { return m_type == SpringFunction; }
 
     virtual bool operator==(const TimingFunction&) const = 0;
@@ -222,6 +223,43 @@ private:
     bool m_stepAtStart;
 };
 
     bool m_stepAtStart;
 };
 
+class FramesTimingFunction final : public TimingFunction {
+public:
+    static Ref<FramesTimingFunction> create(unsigned frames)
+    {
+        return adoptRef(*new FramesTimingFunction(frames));
+    }
+    static Ref<FramesTimingFunction> create()
+    {
+        return adoptRef(*new FramesTimingFunction(2));
+    }
+
+    bool operator==(const TimingFunction& other) const final
+    {
+        if (!other.isFramesTimingFunction())
+            return false;
+        auto& otherFrames = static_cast<const FramesTimingFunction&>(other);
+        return m_frames == otherFrames.m_frames;
+    }
+    
+    unsigned numberOfFrames() const { return m_frames; }
+    void setNumberOfFrames(unsigned frames) { m_frames = frames; }
+
+private:
+    FramesTimingFunction(unsigned frames)
+        : TimingFunction(FramesFunction)
+        , m_frames(frames)
+    {
+    }
+
+    Ref<TimingFunction> clone() const final
+    {
+        return adoptRef(*new FramesTimingFunction(m_frames));
+    }
+    
+    unsigned m_frames;
+};
+
 class SpringTimingFunction final : public TimingFunction {
 public:
     static Ref<SpringTimingFunction> create(double mass, double stiffness, double damping, double initialVelocity)
 class SpringTimingFunction final : public TimingFunction {
 public:
     static Ref<SpringTimingFunction> create(double mass, double stiffness, double damping, double initialVelocity)
index 0840a78..122bec7 100644 (file)
@@ -283,6 +283,21 @@ static bool animationHasStepsTimingFunction(const KeyframeValueList& valueList,
     return false;
 }
 
     return false;
 }
 
+static bool animationHasFramesTimingFunction(const KeyframeValueList& valueList, const Animation* anim)
+{
+    if (anim->timingFunction()->isFramesTimingFunction())
+        return true;
+    
+    for (unsigned i = 0; i < valueList.size(); ++i) {
+        if (const TimingFunction* timingFunction = valueList.at(i).timingFunction()) {
+            if (timingFunction->isFramesTimingFunction())
+                return true;
+        }
+    }
+
+    return false;
+}
+
 static inline bool supportsAcceleratedFilterAnimations()
 {
 #if PLATFORM(COCOA)
 static inline bool supportsAcceleratedFilterAnimations()
 {
 #if PLATFORM(COCOA)
@@ -977,6 +992,9 @@ bool GraphicsLayerCA::animationCanBeAccelerated(const KeyframeValueList& valueLi
     if (animationHasStepsTimingFunction(valueList, anim))
         return false;
 
     if (animationHasStepsTimingFunction(valueList, anim))
         return false;
 
+    if (animationHasFramesTimingFunction(valueList, anim))
+        return false;
+
 #if ENABLE(CSS_ANIMATIONS_LEVEL_2)
     // If there is a trigger that depends on the scroll position, we cannot accelerate the animation.
     if (is<ScrollAnimationTrigger>(anim->trigger())) {
 #if ENABLE(CSS_ANIMATIONS_LEVEL_2)
     // If there is a trigger that depends on the scroll position, we cannot accelerate the animation.
     if (is<ScrollAnimationTrigger>(anim->trigger())) {
index 39217f1..3d9beb5 100644 (file)
@@ -1,3 +1,21 @@
+2018-01-12  Antoine Quint  <graouts@apple.com>
+
+        Add support for the frames() timing function
+        https://bugs.webkit.org/show_bug.cgi?id=181585
+        <rdar://problem/36463317>
+
+        Reviewed by Dean.
+
+        Add the ability to endode and decode the frames() timing function.
+
+        * Shared/WebCoreArgumentCoders.cpp:
+        (IPC::ArgumentCoder<FramesTimingFunction>::encode):
+        (IPC::ArgumentCoder<FramesTimingFunction>::decode):
+        * Shared/WebCoreArgumentCoders.h:
+        * WebProcess/WebPage/RemoteLayerTree/PlatformCAAnimationRemote.mm:
+        (WebKit::PlatformCAAnimationRemote::Properties::encode const):
+        (WebKit::PlatformCAAnimationRemote::Properties::decode):
+
 2018-01-11  Keith Miller  <keith_miller@apple.com>
 
         Rename ENABLE_ASYNC_ITERATION to ENABLE_JS_ASYNC_ITERATION
 2018-01-11  Keith Miller  <keith_miller@apple.com>
 
         Rename ENABLE_ASYNC_ITERATION to ENABLE_JS_ASYNC_ITERATION
index f8e357b..72960b8 100644 (file)
@@ -534,6 +534,25 @@ bool ArgumentCoder<StepsTimingFunction>::decode(Decoder& decoder, StepsTimingFun
     return true;
 }
 
     return true;
 }
 
+void ArgumentCoder<FramesTimingFunction>::encode(Encoder& encoder, const FramesTimingFunction& timingFunction)
+{
+    encoder.encodeEnum(timingFunction.type());
+    
+    encoder << timingFunction.numberOfFrames();
+}
+
+bool ArgumentCoder<FramesTimingFunction>::decode(Decoder& decoder, FramesTimingFunction& timingFunction)
+{
+    // Type is decoded by the caller.
+    int numFrames;
+    if (!decoder.decode(numFrames))
+        return false;
+
+    timingFunction.setNumberOfFrames(numFrames);
+
+    return true;
+}
+
 void ArgumentCoder<SpringTimingFunction>::encode(Encoder& encoder, const SpringTimingFunction& timingFunction)
 {
     encoder.encodeEnum(timingFunction.type());
 void ArgumentCoder<SpringTimingFunction>::encode(Encoder& encoder, const SpringTimingFunction& timingFunction)
 {
     encoder.encodeEnum(timingFunction.type());
index 4a0d940..6b1c43a 100644 (file)
@@ -83,6 +83,7 @@ class ResourceRequest;
 class ResourceResponse;
 class SpringTimingFunction;
 class StepsTimingFunction;
 class ResourceResponse;
 class SpringTimingFunction;
 class StepsTimingFunction;
+class FramesTimingFunction;
 class StickyPositionViewportConstraints;
 class TextCheckingRequestData;
 class TransformationMatrix;
 class StickyPositionViewportConstraints;
 class TextCheckingRequestData;
 class TransformationMatrix;
@@ -216,6 +217,11 @@ template<> struct ArgumentCoder<WebCore::StepsTimingFunction> {
     static bool decode(Decoder&, WebCore::StepsTimingFunction&);
 };
 
     static bool decode(Decoder&, WebCore::StepsTimingFunction&);
 };
 
+template<> struct ArgumentCoder<WebCore::FramesTimingFunction> {
+    static void encode(Encoder&, const WebCore::FramesTimingFunction&);
+    static bool decode(Decoder&, WebCore::FramesTimingFunction&);
+};
+
 template<> struct ArgumentCoder<WebCore::SpringTimingFunction> {
     static void encode(Encoder&, const WebCore::SpringTimingFunction&);
     static bool decode(Decoder&, WebCore::SpringTimingFunction&);
 template<> struct ArgumentCoder<WebCore::SpringTimingFunction> {
     static void encode(Encoder&, const WebCore::SpringTimingFunction&);
     static bool decode(Decoder&, WebCore::SpringTimingFunction&);
index 0d2e9f3..85f49e2 100644 (file)
@@ -198,6 +198,10 @@ void PlatformCAAnimationRemote::Properties::encode(IPC::Encoder& encoder) const
             encoder << *static_cast<StepsTimingFunction*>(timingFunction.get());
             break;
 
             encoder << *static_cast<StepsTimingFunction*>(timingFunction.get());
             break;
 
+        case TimingFunction::FramesFunction:
+            encoder << *static_cast<FramesTimingFunction*>(timingFunction.get());
+            break;
+
         case TimingFunction::SpringFunction:
             encoder << *static_cast<SpringTimingFunction*>(timingFunction.get());
             break;
         case TimingFunction::SpringFunction:
             encoder << *static_cast<SpringTimingFunction*>(timingFunction.get());
             break;
@@ -289,6 +293,12 @@ std::optional<PlatformCAAnimationRemote::Properties> PlatformCAAnimationRemote::
                     return std::nullopt;
                 break;
 
                     return std::nullopt;
                 break;
 
+            case TimingFunction::FramesFunction:
+                timingFunction = FramesTimingFunction::create();
+                if (!decoder.decode(*static_cast<FramesTimingFunction*>(timingFunction.get())))
+                    return std::nullopt;
+                break;
+
             case TimingFunction::SpringFunction:
                 timingFunction = SpringTimingFunction::create();
                 if (!decoder.decode(*static_cast<SpringTimingFunction*>(timingFunction.get())))
             case TimingFunction::SpringFunction:
                 timingFunction = SpringTimingFunction::create();
                 if (!decoder.decode(*static_cast<SpringTimingFunction*>(timingFunction.get())))