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);