Title: [276272] trunk
Revision
276272
Author
[email protected]
Date
2021-04-19 13:45:57 -0700 (Mon, 19 Apr 2021)

Log Message

Web Inspector: Graphics: add support for `steps()`/`spring()` CSS timing functions
https://bugs.webkit.org/show_bug.cgi?id=224654

Reviewed by BJ Burg.

Source/WebInspectorUI:

* UserInterface/Models/Geometry.js:
(WI.StepsFunction): Added.
(WI.StepsFunction.fromString): Added.
(WI.StepsFunction.prototype.get type): Added.
(WI.StepsFunction.prototype.get count): Added.
(WI.StepsFunction.prototype.copy): Added.
(WI.StepsFunction.prototype.toString): Added.
Create a model object for `steps()` CSS timing function.

* UserInterface/Models/Animation.js:
(WI.Animation.prototype._updateEffect):
Also support `effect.timingFunction`/`keyframe.easing` being a `steps()`/`spring()` CSS timing function.

* UserInterface/Views/AnimationContentView.js:
(WI.AnimationContentView.prototype._refreshPreview):
Create a UI for `steps()`/`spring()` CSS timing functions.

LayoutTests:

* inspector/animation/lifecycle-web-animation.html:
* inspector/animation/lifecycle-web-animation-expected.txt:
* inspector/animation/resources/lifecycle-utilities.js:
* inspector/unit-tests/geometry.html: Added.
* inspector/unit-tests/geometry-expected.txt: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (276271 => 276272)


--- trunk/LayoutTests/ChangeLog	2021-04-19 20:35:54 UTC (rev 276271)
+++ trunk/LayoutTests/ChangeLog	2021-04-19 20:45:57 UTC (rev 276272)
@@ -1,3 +1,16 @@
+2021-04-19  Devin Rousso  <[email protected]>
+
+        Web Inspector: Graphics: add support for `steps()`/`spring()` CSS timing functions
+        https://bugs.webkit.org/show_bug.cgi?id=224654
+
+        Reviewed by BJ Burg.
+
+        * inspector/animation/lifecycle-web-animation.html:
+        * inspector/animation/lifecycle-web-animation-expected.txt:
+        * inspector/animation/resources/lifecycle-utilities.js:
+        * inspector/unit-tests/geometry.html: Added.
+        * inspector/unit-tests/geometry-expected.txt: Added.
+
 2021-04-19  Zalan Bujtas  <[email protected]>
 
         [LFC][IFC] LineCandidate.inlineContent should be ignored when reverting

Modified: trunk/LayoutTests/inspector/animation/lifecycle-web-animation-expected.txt (276271 => 276272)


--- trunk/LayoutTests/inspector/animation/lifecycle-web-animation-expected.txt	2021-04-19 20:35:54 UTC (rev 276271)
+++ trunk/LayoutTests/inspector/animation/lifecycle-web-animation-expected.txt	2021-04-19 20:45:57 UTC (rev 276272)
@@ -31,7 +31,7 @@
   },
   {
     "offset": 0.75,
-    "easing": "cubic-bezier(0.6, 0.7, 0.8, 0.9)",
+    "easing": "steps(5, jump-end)",
     "style": "color: blue;\nopacity: 1;"
   }
 ]

Modified: trunk/LayoutTests/inspector/animation/lifecycle-web-animation.html (276271 => 276272)


--- trunk/LayoutTests/inspector/animation/lifecycle-web-animation.html	2021-04-19 20:35:54 UTC (rev 276271)
+++ trunk/LayoutTests/inspector/animation/lifecycle-web-animation.html	2021-04-19 20:45:57 UTC (rev 276272)
@@ -25,7 +25,7 @@
                     },
                     {
                         offset: 0.75,
-                        easing: "cubic-bezier(0.6, 0.7, 0.8, 0.9)",
+                        easing: "steps(5, jump-end)",
                         color: "blue",
                         opacity: 1,
                     },

Modified: trunk/LayoutTests/inspector/animation/resources/lifecycle-utilities.js (276271 => 276272)


--- trunk/LayoutTests/inspector/animation/resources/lifecycle-utilities.js	2021-04-19 20:35:54 UTC (rev 276271)
+++ trunk/LayoutTests/inspector/animation/resources/lifecycle-utilities.js	2021-04-19 20:45:57 UTC (rev 276272)
@@ -40,7 +40,7 @@
 
 TestPage.registerInitializer(() => {
     function jsonKeyframeFilter(key, value) {
-        if (key === "easing" && value instanceof WI.CubicBezier)
+        if ((key === "easing" || key === "timingFunction") && (value instanceof WI.CubicBezier || value instanceof WI.StepsFunction || value instanceof WI.Spring))
             return value.toString();
         return value;
     }

Added: trunk/LayoutTests/inspector/unit-tests/geometry-expected.txt (0 => 276272)


--- trunk/LayoutTests/inspector/unit-tests/geometry-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/geometry-expected.txt	2021-04-19 20:45:57 UTC (rev 276272)
@@ -0,0 +1,56 @@
+Tests for WI.Geometry classes.
+
+
+== Running test suite: WI.Geometry
+-- Running test case: WI.CubicBezier
+PASS: Should parse 'cubic-bezier(1, 2, 3, 4)' as 'cubic-bezier(1, 2, 3, 4)'.
+PASS: Should parse 'cubic-bezier(0.1, 0.2, 0.3, 0.4)' as 'cubic-bezier(0.1, 0.2, 0.3, 0.4)'.
+PASS: Should parse 'cubic-bezier(0.25, 0.1, 0.25, 1)' as 'ease'.
+PASS: Should parse 'cubic-bezier(0.42, 0, 1, 1)' as 'ease-in'.
+PASS: Should parse 'cubic-bezier(0, 0, 0.58, 1)' as 'ease-out'.
+PASS: Should parse 'cubic-bezier(0.42, 0, 0.58, 1)' as 'ease-in-out'.
+PASS: Should parse 'cubic-bezier(0, 0, 1, 1)' as 'linear'.
+PASS: Should parse 'ease' as 'ease'.
+PASS: Should parse 'ease-in' as 'ease-in'.
+PASS: Should parse 'ease-out' as 'ease-out'.
+PASS: Should parse 'ease-in-out' as 'ease-in-out'.
+PASS: Should parse 'linear' as 'linear'.
+PASS: Should not parse invalid 'cubic-bezier()'.
+PASS: Should not parse invalid 'cubic-bezier(0)'.
+PASS: Should not parse invalid 'cubic-bezier(0, 1)'.
+PASS: Should not parse invalid 'cubic-bezier(0, 1, 2)'.
+PASS: Should not parse invalid 'cubic-bezier("test")'.
+PASS: Should not parse invalid 'cubic-bezier(0, "test")'.
+PASS: Should not parse invalid 'cubic-bezier(0, 1, "test")'.
+PASS: Should not parse invalid 'cubic-bezier(0, 1, 2, "test")'.
+
+-- Running test case: WI.Spring
+PASS: Should parse 'spring(1 2 3 4)' as 'spring(1 2 3 4)'.
+PASS: Should parse 'spring(0.1 0.2 0.3 0.4)' as 'spring(1 1 0.3 0.4)'.
+PASS: Should not parse invalid 'spring()'.
+PASS: Should not parse invalid 'spring(0)'.
+PASS: Should not parse invalid 'spring(0 1)'.
+PASS: Should not parse invalid 'spring(0 1 2)'.
+PASS: Should not parse invalid 'spring("test")'.
+PASS: Should not parse invalid 'spring(0 "test")'.
+PASS: Should not parse invalid 'spring(0 1 "test")'.
+PASS: Should not parse invalid 'spring(0 1 2 "test")'.
+
+-- Running test case: WI.StepFunction
+PASS: Should parse 'steps(1)' as 'step-end'.
+PASS: Should parse 'steps(2, jump-start)' as 'steps(2, jump-start)'.
+PASS: Should parse 'steps(3, jump-end)' as 'steps(3, jump-end)'.
+PASS: Should parse 'steps(4, jump-none)' as 'steps(4, jump-none)'.
+PASS: Should parse 'steps(5, jump-both)' as 'steps(5, jump-both)'.
+PASS: Should parse 'steps(6, end)' as 'steps(6, end)'.
+PASS: Should parse 'steps(7, start)' as 'steps(7, start)'.
+PASS: Should parse 'step-start' as 'step-start'.
+PASS: Should parse 'step-end' as 'step-end'.
+PASS: Should not parse invalid 'steps()'.
+PASS: Should not parse invalid 'steps(0)'.
+PASS: Should not parse invalid 'steps(0, 1)'.
+PASS: Should not parse invalid 'steps(0, 1, 2)'.
+PASS: Should not parse invalid 'steps("test")'.
+PASS: Should not parse invalid 'steps(0, "test")'.
+PASS: Should not parse invalid 'steps(0, 1, "test")'.
+

Added: trunk/LayoutTests/inspector/unit-tests/geometry.html (0 => 276272)


--- trunk/LayoutTests/inspector/unit-tests/geometry.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/geometry.html	2021-04-19 20:45:57 UTC (rev 276272)
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test() {
+    let suite = InspectorTest.createSyncSuite("WI.Geometry");
+
+    suite.addTestCase({
+        name: "WI.CubicBezier",
+        test(resolve, reject) {
+            let testPass = (inputString, expectedString) => {
+                expectedString ||= inputString;
+                InspectorTest.expectEqual(WI.CubicBezier.fromString(inputString).toString(), expectedString, `Should parse '${inputString}' as '${expectedString}'.`);
+            };
+
+            testPass("cubic-bezier(1, 2, 3, 4)");
+            testPass("cubic-bezier(0.1, 0.2, 0.3, 0.4)");
+            testPass("cubic-bezier(0.25, 0.1, 0.25, 1)", "ease");
+            testPass("cubic-bezier(0.42, 0, 1, 1)", "ease-in");
+            testPass("cubic-bezier(0, 0, 0.58, 1)", "ease-out");
+            testPass("cubic-bezier(0.42, 0, 0.58, 1)", "ease-in-out");
+            testPass("cubic-bezier(0, 0, 1, 1)", "linear");
+            testPass("ease");
+            testPass("ease-in");
+            testPass("ease-out");
+            testPass("ease-in-out");
+            testPass("linear");
+
+            let testFail = (inputString) => {
+                InspectorTest.expectNull(WI.CubicBezier.fromString(inputString), `Should not parse invalid '${inputString}'.`);
+            };
+
+            testFail("cubic-bezier()");
+            testFail("cubic-bezier(0)");
+            testFail("cubic-bezier(0, 1)");
+            testFail("cubic-bezier(0, 1, 2)");
+            testFail("cubic-bezier(\"test\")");
+            testFail("cubic-bezier(0, \"test\")");
+            testFail("cubic-bezier(0, 1, \"test\")");
+            testFail("cubic-bezier(0, 1, 2, \"test\")");
+        }
+    });
+
+    suite.addTestCase({
+        name: "WI.Spring",
+        test(resolve, reject) {
+            let testPass = (inputString, expectedString) => {
+                expectedString ||= inputString;
+                InspectorTest.expectEqual(WI.Spring.fromString(inputString).toString(), expectedString, `Should parse '${inputString}' as '${expectedString}'.`);
+            };
+
+            testPass("spring(1 2 3 4)");
+            testPass("spring(0.1 0.2 0.3 0.4)", "spring(1 1 0.3 0.4)");
+
+            let testFail = (inputString) => {
+                InspectorTest.expectNull(WI.Spring.fromString(inputString), `Should not parse invalid '${inputString}'.`);
+            };
+
+            testFail("spring()");
+            testFail("spring(0)");
+            testFail("spring(0 1)");
+            testFail("spring(0 1 2)");
+            testFail("spring(\"test\")");
+            testFail("spring(0 \"test\")");
+            testFail("spring(0 1 \"test\")");
+            testFail("spring(0 1 2 \"test\")");
+        }
+    });
+
+    suite.addTestCase({
+        name: "WI.StepFunction",
+        test(resolve, reject) {
+            let testPass = (inputString, expectedString) => {
+                expectedString ||= inputString;
+                InspectorTest.expectEqual(WI.StepsFunction.fromString(inputString).toString(), expectedString, `Should parse '${inputString}' as '${expectedString}'.`);
+            };
+
+            testPass("steps(1)", "step-end");
+            testPass("steps(2, jump-start)");
+            testPass("steps(3, jump-end)");
+            testPass("steps(4, jump-none)");
+            testPass("steps(5, jump-both)");
+            testPass("steps(6, end)");
+            testPass("steps(7, start)");
+            testPass("step-start");
+            testPass("step-end");
+
+            let testFail = (inputString) => {
+                InspectorTest.expectNull(WI.StepsFunction.fromString(inputString), `Should not parse invalid '${inputString}'.`);
+            };
+
+            testFail("steps()");
+            testFail("steps(0)");
+            testFail("steps(0, 1)");
+            testFail("steps(0, 1, 2)");
+            testFail("steps(\"test\")");
+            testFail("steps(0, \"test\")");
+            testFail("steps(0, 1, \"test\")");
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Tests for WI.Geometry classes.</p>
+</body>
+</html>

Modified: trunk/Source/WebInspectorUI/ChangeLog (276271 => 276272)


--- trunk/Source/WebInspectorUI/ChangeLog	2021-04-19 20:35:54 UTC (rev 276271)
+++ trunk/Source/WebInspectorUI/ChangeLog	2021-04-19 20:45:57 UTC (rev 276272)
@@ -1,5 +1,29 @@
 2021-04-19  Devin Rousso  <[email protected]>
 
+        Web Inspector: Graphics: add support for `steps()`/`spring()` CSS timing functions
+        https://bugs.webkit.org/show_bug.cgi?id=224654
+
+        Reviewed by BJ Burg.
+
+        * UserInterface/Models/Geometry.js:
+        (WI.StepsFunction): Added.
+        (WI.StepsFunction.fromString): Added.
+        (WI.StepsFunction.prototype.get type): Added.
+        (WI.StepsFunction.prototype.get count): Added.
+        (WI.StepsFunction.prototype.copy): Added.
+        (WI.StepsFunction.prototype.toString): Added.
+        Create a model object for `steps()` CSS timing function.
+
+        * UserInterface/Models/Animation.js:
+        (WI.Animation.prototype._updateEffect):
+        Also support `effect.timingFunction`/`keyframe.easing` being a `steps()`/`spring()` CSS timing function.
+
+        * UserInterface/Views/AnimationContentView.js:
+        (WI.AnimationContentView.prototype._refreshPreview):
+        Create a UI for `steps()`/`spring()` CSS timing functions.
+
+2021-04-19  Devin Rousso  <[email protected]>
+
         Web Inspector: REGRESSION(?): Network: Request (Object Tree) is sometimes empty
         https://bugs.webkit.org/show_bug.cgi?id=224768
         <rdar://problem/76783636>

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Animation.js (276271 => 276272)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Animation.js	2021-04-19 20:35:54 UTC (rev 276271)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Animation.js	2021-04-19 20:45:57 UTC (rev 276272)
@@ -254,13 +254,19 @@
             }
         }
 
-        if ("timingFunction" in this._effect)
-            this._effect.timingFunction = WI.CubicBezier.fromString(this._effect.timingFunction);
+        if ("timingFunction" in this._effect) {
+            let timingFunction = this._effect.timingFunction;
+            this._effect.timingFunction = WI.CubicBezier.fromString(timingFunction) || WI.StepsFunction.fromString(timingFunction) || WI.Spring.fromString(timingFunction);
+            console.assert(this._effect.timingFunction, timingFunction);
+        }
 
         if ("keyframes" in this._effect) {
             for (let keyframe of this._effect.keyframes) {
-                if (keyframe.easing)
-                    keyframe.easing = WI.CubicBezier.fromString(keyframe.easing);
+                if (keyframe.easing) {
+                    let easing = keyframe.easing;
+                    keyframe.easing = WI.CubicBezier.fromString(easing) || WI.StepsFunction.fromString(easing) || WI.Spring.fromString(easing);
+                    console.assert(keyframe.easing, easing);
+                }
 
                 if (keyframe.style)
                     keyframe.style = keyframe.style.replaceAll(/;\s+/g, ";\n");

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Geometry.js (276271 => 276272)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Geometry.js	2021-04-19 20:35:54 UTC (rev 276271)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Geometry.js	2021-04-19 20:45:57 UTC (rev 276272)
@@ -581,3 +581,80 @@
         return t;
     }
 };
+
+WI.StepsFunction = class StepsFunction
+{
+    constructor(type, count)
+    {
+        console.assert(Object.values(WI.StepsFunction.Type).includes(type), type);
+        console.assert(count > 0, count);
+
+        this._type = type;
+        this._count = count;
+    }
+
+    // Static
+
+    static fromString(text)
+    {
+        if (!text?.length)
+            return null;
+
+        let trimmedText = text.toLowerCase().replace(/\s/g, "");
+        if (!trimmedText.length)
+            return null;
+
+        let keywordValue = WI.StepsFunction.keywordValues[trimmedText];
+        if (keywordValue)
+            return new WI.StepsFunction(...keywordValue);
+
+        let matches = trimmedText.match(/^steps\((\d+)(?:,([a-z-]+))?\)$/);
+        if (!matches)
+            return null;
+
+        let type = matches[2] || WI.StepsFunction.Type.JumpEnd;
+        if (Object.values(WI.StepsFunction).includes(type))
+            return null;
+
+        let count = Number(matches[1]);
+        if (isNaN(count) || count <= 0)
+            return null;
+
+        return new WI.StepsFunction(type, count);
+    }
+
+    // Public
+
+    get type() { return this._type; }
+    get count() { return this._count; }
+
+    copy()
+    {
+        return new WI.StepsFunction(this._type, this._count);
+    }
+
+    toString()
+    {
+        if (this._type === WI.StepsFunction.Type.JumpStart && this._count === 1)
+            return "step-start";
+
+        if (this._type === WI.StepsFunction.Type.JumpEnd && this._count === 1)
+            return "step-end";
+
+        return `steps(${this._count}, ${this._type})`;
+    }
+};
+
+WI.StepsFunction.Type = {
+    JumpStart: "jump-start",
+    JumpEnd: "jump-end",
+    JumpNone: "jump-none",
+    JumpBoth: "jump-both",
+    Start: "start",
+    End: "end",
+};
+
+WI.StepsFunction.keywordValues = {
+    "step-start": [WI.StepsFunction.Type.JumpStart, 1],
+    "step-end": [WI.StepsFunction.Type.JumpEnd, 1],
+};

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AnimationContentView.js (276271 => 276272)


--- trunk/Source/WebInspectorUI/UserInterface/Views/AnimationContentView.js	2021-04-19 20:35:54 UTC (rev 276271)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AnimationContentView.js	2021-04-19 20:45:57 UTC (rev 276272)
@@ -202,9 +202,13 @@
         const squeezeXEnd = (iterationDuration && endDelay) ? 0 : markerHeadRadius + markerHeadPadding;
         const squeezeYStart = markerHeadRadius + (markerHeadPadding * 2);
 
+        // Ensure that at least a line is drawn if the output of the timing function is `0`.
+        const adjustEasingHeight = -1;
+
         // Move the easing line down to cut off the bottom border.
-        const adjustEasingY = 0.5;
+        const adjustEasingBottomY = 0.5;
 
+
         let secondsPerPixel = this._cachedWidth / totalDuration;
 
         startDelay *= secondsPerPixel;
@@ -276,14 +280,66 @@
                 easingContainer.classList.add("easing");
                 easingContainer.setAttribute("transform", `translate(${startX}, ${startY})`);
 
-                let x1 = easing.inPoint.x * width;
-                let y1 = ((1 - easing.inPoint.y) * height) + adjustEasingY;
-                let x2 = easing.outPoint.x * width;
-                let y2 = ((1 - easing.outPoint.y) * height) + adjustEasingY;
-
                 let easingPath = easingContainer.appendChild(createSVGElement("path"));
-                easingPath.setAttribute("d", `M 0 ${height + adjustEasingY} C ${x1} ${y1} ${x2} ${y2} ${width} ${adjustEasingY} V ${height + adjustEasingY} Z`);
 
+                let pathSteps = [];
+                if (easing instanceof WI.CubicBezier) {
+                    pathSteps.push("C");
+                    pathSteps.push(easing.inPoint.x * width); // x1
+                    pathSteps.push((1 - easing.inPoint.y) * (height + adjustEasingHeight)); // y1
+                    pathSteps.push(easing.outPoint.x * width); // x2
+                    pathSteps.push((1 - easing.outPoint.y) * (height + adjustEasingHeight)); // y2
+                    pathSteps.push(width); // x
+                    pathSteps.push(0); // y
+                } else if (easing instanceof WI.StepsFunction) {
+                    let goUpFirst = false;
+                    let stepStartAdjust = 0;
+                    let stepCountAdjust = 0;
+
+                    switch (easing.type) {
+                    case WI.StepsFunction.Type.JumpStart:
+                    case WI.StepsFunction.Type.Start:
+                        goUpFirst = true;
+                        break;
+
+                    case WI.StepsFunction.Type.JumpNone:
+                        --stepCountAdjust;
+                        break;
+
+                    case WI.StepsFunction.Type.JumpBoth:
+                        ++stepStartAdjust;
+                        ++stepCountAdjust;
+                        break;
+                    }
+
+                    let stepCount = easing.count + stepCountAdjust;
+                    let stepX = width / easing.count; // Always use the defined number of steps to divide the duration.
+                    let stepY = (height + adjustEasingHeight) / stepCount;
+
+                    for (let i = stepStartAdjust; i <= stepCount; ++i) {
+                        let x = stepX * (i - stepStartAdjust);
+                        let y = height + adjustEasingHeight - (stepY * i);
+
+                        if (goUpFirst) {
+                            if (i)
+                                pathSteps.push("H", x);
+
+                            if (i < stepCount)
+                                pathSteps.push("V", y - stepY);
+                        } else {
+                            pathSteps.push("V", y);
+
+                            if (i < stepCount || stepCountAdjust < 0)
+                                pathSteps.push("H", x + stepX);
+                        }
+                    }
+                } else if (easing instanceof WI.Spring) {
+                    let duration = easing.calculateDuration();
+                    for (let i = 0; i < width; i += 1 / window.devicePixelRatio)
+                        pathSteps.push("L", i, easing.solve(duration * i / width) * (height + adjustEasingHeight));
+                }
+                easingPath.setAttribute("d", `M 0 ${height + adjustEasingBottomY} ${pathSteps.join(" ")} V ${height + adjustEasingBottomY} Z`);
+
                 let titleRect = easingContainer.appendChild(createSVGElement("rect"));
                 titleRect.setAttribute("width", width);
                 titleRect.setAttribute("height", height);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to