Title: [287524] trunk
Revision
287524
Author
[email protected]
Date
2022-01-02 15:42:27 -0800 (Sun, 02 Jan 2022)

Log Message

LayoutTests/imported/w3c:
[Web Animations] getKeyframes() should handle multiple 0% and 100% keyframes
https://bugs.webkit.org/show_bug.cgi?id=234799

Reviewed by Dean Jackson.

Mark a WPT progression.

* web-platform-tests/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt:

Source/WebCore:
[Web Animations] getKeyframes() should handle multiple keyframes for the same offset
https://bugs.webkit.org/show_bug.cgi?id=234799

Reviewed by Dean Jackson.

A @keyframes rule may specify multiple keyframes for the same offset. We currently have support
for merging such keyframes into consolidated keyframes in Style::Resolver::keyframeStylesForAnimation()
but WPT shows that it's lacking since it doesn't account for animation-timing-function. Indeed, we
need to be careful not to merge keyframes with the same offset but a different timing function.

So we now use a std::pair<> using an offset and a timing function to compute de-duplicated keyframes.
But this showed an issue with StepsTimingFunction::operator== where we would not return true for
steps(1) and steps(1, end) since we only checked for equality for the optional keyword and did not
account for the default "end" value should the keyword not be explicit.

This ensured we correctly de-duplicated keyframes accounting for not only the offset but also
the timing function. But this then highlighted a different issue which was that our getKeyframes()
only ever expected one keyframe respectively for the 0% or 100% offsets when filling in implicit
values, which we'd just added support for in bug 234795.

So in KeyframeEffect::getKeyframes() we now compile a list of properties not explicitly specified
on any of the 0% or 100% keyframes and, as we process the first of a 0% or 100% keyframe, we set
the implicit properties.

* animation/KeyframeEffect.cpp:
(WebCore::KeyframeEffect::getKeyframes):
* platform/animation/TimingFunction.h:
* style/StyleResolver.cpp:
(WebCore::Style::Resolver::keyframeStylesForAnimation):

Modified Paths

Diff

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (287523 => 287524)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2022-01-02 21:37:07 UTC (rev 287523)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2022-01-02 23:42:27 UTC (rev 287524)
@@ -1,5 +1,16 @@
 2022-01-02  Antoine Quint  <[email protected]>
 
+        [Web Animations] getKeyframes() should handle multiple 0% and 100% keyframes
+        https://bugs.webkit.org/show_bug.cgi?id=234799
+
+        Reviewed by Dean Jackson.
+
+        Mark a WPT progression.
+
+        * web-platform-tests/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt:
+
+2022-01-02  Antoine Quint  <[email protected]>
+
         [Web Animations] getKeyframes() should ensure that all properties are present on 0% and 100% keyframes
         https://bugs.webkit.org/show_bug.cgi?id=234795
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt (287523 => 287524)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt	2022-01-02 21:37:07 UTC (rev 287523)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt	2022-01-02 23:42:27 UTC (rev 287524)
@@ -12,7 +12,7 @@
 PASS KeyframeEffect.getKeyframes() returns expected frames for an animation with different properties on different keyframes, all with the same easing function
 PASS KeyframeEffect.getKeyframes() returns expected frames for an animation with different properties on different keyframes, with a different easing function on each
 PASS KeyframeEffect.getKeyframes() returns expected frames for an animation with multiple keyframes for the same time, and all with the same easing function
-FAIL KeyframeEffect.getKeyframes() returns expected frames for an animation with multiple keyframes for the same time and with different easing functions assert_equals: Number of keyframes should match expected 3 but got 2
+PASS KeyframeEffect.getKeyframes() returns expected frames for an animation with multiple keyframes for the same time and with different easing functions
 PASS KeyframeEffect.getKeyframes() returns expected frames for an animation with multiple keyframes for the same time and with different but equivalent easing functions
 PASS KeyframeEffect.getKeyframes() returns expected frames for overlapping keyframes
 FAIL KeyframeEffect.getKeyframes() returns expected values for animations with filter properties and missing keyframes assert_equals: value for 'filter' on Keyframe #1 should match expected "blur(5px) sepia(60%) saturate(30%)" but got "blur(5px) sepia(0.6) saturate(0.3)"

Modified: trunk/Source/WebCore/ChangeLog (287523 => 287524)


--- trunk/Source/WebCore/ChangeLog	2022-01-02 21:37:07 UTC (rev 287523)
+++ trunk/Source/WebCore/ChangeLog	2022-01-02 23:42:27 UTC (rev 287524)
@@ -1,3 +1,35 @@
+2022-01-02  Antoine Quint  <[email protected]>
+
+        [Web Animations] getKeyframes() should handle multiple keyframes for the same offset
+        https://bugs.webkit.org/show_bug.cgi?id=234799
+
+        Reviewed by Dean Jackson.
+
+        A @keyframes rule may specify multiple keyframes for the same offset. We currently have support
+        for merging such keyframes into consolidated keyframes in Style::Resolver::keyframeStylesForAnimation()
+        but WPT shows that it's lacking since it doesn't account for animation-timing-function. Indeed, we
+        need to be careful not to merge keyframes with the same offset but a different timing function.
+
+        So we now use a std::pair<> using an offset and a timing function to compute de-duplicated keyframes.
+        But this showed an issue with StepsTimingFunction::operator== where we would not return true for
+        steps(1) and steps(1, end) since we only checked for equality for the optional keyword and did not
+        account for the default "end" value should the keyword not be explicit.
+
+        This ensured we correctly de-duplicated keyframes accounting for not only the offset but also
+        the timing function. But this then highlighted a different issue which was that our getKeyframes()
+        only ever expected one keyframe respectively for the 0% or 100% offsets when filling in implicit
+        values, which we'd just added support for in bug 234795.
+
+        So in KeyframeEffect::getKeyframes() we now compile a list of properties not explicitly specified
+        on any of the 0% or 100% keyframes and, as we process the first of a 0% or 100% keyframe, we set
+        the implicit properties.
+
+        * animation/KeyframeEffect.cpp:
+        (WebCore::KeyframeEffect::getKeyframes):
+        * platform/animation/TimingFunction.h:
+        * style/StyleResolver.cpp:
+        (WebCore::Style::Resolver::keyframeStylesForAnimation):
+
 2022-01-02  Wenson Hsieh  <[email protected]>
 
         Followup to r287494

Modified: trunk/Source/WebCore/animation/KeyframeEffect.cpp (287523 => 287524)


--- trunk/Source/WebCore/animation/KeyframeEffect.cpp	2022-01-02 21:37:07 UTC (rev 287523)
+++ trunk/Source/WebCore/animation/KeyframeEffect.cpp	2022-01-02 23:42:27 UTC (rev 287524)
@@ -639,7 +639,23 @@
 
         auto computedStyleExtractor = ComputedStyleExtractor(target, false, m_pseudoId);
 
+        // We need to establish which properties are implicit for 0% and 100%.
+        HashSet<CSSPropertyID> zeroKeyframeProperties = m_blendingKeyframes.properties();
+        HashSet<CSSPropertyID> _oneKeyframeProperties_ = m_blendingKeyframes.properties();
+        zeroKeyframeProperties.remove(CSSPropertyCustom);
+        oneKeyframeProperties.remove(CSSPropertyCustom);
         for (size_t i = 0; i < m_blendingKeyframes.size(); ++i) {
+            auto& keyframe = m_blendingKeyframes[i];
+            if (!keyframe.key()) {
+                for (auto cssPropertyId : keyframe.properties())
+                    zeroKeyframeProperties.remove(cssPropertyId);
+            } else if (keyframe.key() == 1) {
+                for (auto cssPropertyId : keyframe.properties())
+                    oneKeyframeProperties.remove(cssPropertyId);
+            }
+        }
+
+        for (size_t i = 0; i < m_blendingKeyframes.size(); ++i) {
             // 1. Initialize a dictionary object, output keyframe, using the following definition:
             //
             // dictionary BaseComputedKeyframe {
@@ -661,29 +677,45 @@
 
             auto outputKeyframe = convertDictionaryToJS(lexicalGlobalObject, *jsCast<JSDOMGlobalObject*>(&lexicalGlobalObject), computedKeyframe);
 
-            // 3. For each animation property-value pair specified on keyframe, declaration, perform the following steps:
-            auto isFirstOrLastKeyframe = !keyframe.key() || keyframe.key() == 1;
-            auto& properties = isFirstOrLastKeyframe ? m_blendingKeyframes.properties() : keyframe.properties();
-            auto& style = *keyframe.style();
-            for (auto cssPropertyId : properties) {
-                if (cssPropertyId == CSSPropertyCustom)
-                    continue;
+            auto addPropertyToKeyframe = [&](CSSPropertyID cssPropertyId, String idlValue) {
                 // 1. Let property name be the result of applying the animation property name to IDL attribute name algorithm to the property name of declaration.
                 auto propertyName = CSSPropertyIDToIDLAttributeName(cssPropertyId);
                 // 2. Let IDL value be the result of serializing the property value of declaration by passing declaration to the algorithm to serialize a CSS value.
-                String idlValue = "";
-                if (isFirstOrLastKeyframe && !keyframe.properties().contains(cssPropertyId) && lastStyleChangeEventStyle) {
-                    if (auto cssValue = computedStyleExtractor.valueForPropertyInStyle(*lastStyleChangeEventStyle, cssPropertyId, renderer))
-                        idlValue = cssValue->cssText();
-                } else if (auto cssValue = computedStyleExtractor.valueForPropertyInStyle(style, cssPropertyId, renderer))
-                    idlValue = cssValue->cssText();
                 // 3. Let value be the result of converting IDL value to an ECMAScript String value.
                 auto value = toJS<IDLDOMString>(lexicalGlobalObject, idlValue);
                 // 4. Call the [[DefineOwnProperty]] internal method on output keyframe with property name property name,
                 //    Property Descriptor { [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true, [[Value]]: value } and Boolean flag false.
                 JSObject::defineOwnProperty(outputKeyframe, &lexicalGlobalObject, AtomString(propertyName).impl(), PropertyDescriptor(value, 0), false);
+            };
+
+            // 3. For each animation property-value pair specified on keyframe, declaration, perform the following steps:
+            auto& style = *keyframe.style();
+            for (auto cssPropertyId : keyframe.properties()) {
+                if (cssPropertyId == CSSPropertyCustom)
+                    continue;
+                String idlValue = "";
+                if (auto cssValue = computedStyleExtractor.valueForPropertyInStyle(style, cssPropertyId, renderer))
+                    idlValue = cssValue->cssText();
+                addPropertyToKeyframe(cssPropertyId, idlValue);
             }
 
+            // Now add the implicit properties in case there are any and we're dealing with a 0% or 100% keyframe.
+            if (lastStyleChangeEventStyle) {
+                if (!keyframe.key()) {
+                    for (auto cssPropertyId : zeroKeyframeProperties) {
+                        if (auto cssValue = computedStyleExtractor.valueForPropertyInStyle(*lastStyleChangeEventStyle, cssPropertyId, renderer))
+                            addPropertyToKeyframe(cssPropertyId, cssValue->cssText());
+                    }
+                    zeroKeyframeProperties.clear();
+                } else if (keyframe.key() == 1) {
+                    for (auto cssPropertyId : oneKeyframeProperties) {
+                        if (auto cssValue = computedStyleExtractor.valueForPropertyInStyle(*lastStyleChangeEventStyle, cssPropertyId, renderer))
+                            addPropertyToKeyframe(cssPropertyId, cssValue->cssText());
+                    }
+                    oneKeyframeProperties.clear();
+                }
+            }
+
             // 5. Append output keyframe to result.
             result.append(JSC::Strong<JSC::JSObject> { lexicalGlobalObject.vm(), outputKeyframe });
         }

Modified: trunk/Source/WebCore/platform/animation/TimingFunction.h (287523 => 287524)


--- trunk/Source/WebCore/platform/animation/TimingFunction.h	2022-01-02 21:37:07 UTC (rev 287523)
+++ trunk/Source/WebCore/platform/animation/TimingFunction.h	2022-01-02 23:42:27 UTC (rev 287524)
@@ -222,7 +222,20 @@
         if (!is<StepsTimingFunction>(other))
             return false;
         auto& otherSteps = downcast<StepsTimingFunction>(other);
-        return m_steps == otherSteps.m_steps && m_stepPosition == otherSteps.m_stepPosition;
+
+        if (m_steps != otherSteps.m_steps)
+            return false;
+
+        if (m_stepPosition == otherSteps.m_stepPosition)
+            return true;
+
+        if (!m_stepPosition && *otherSteps.m_stepPosition == StepPosition::End)
+            return true;
+
+        if (*m_stepPosition == StepPosition::End && !otherSteps.m_stepPosition)
+            return true;
+
+        return false;
     }
     
     int numberOfSteps() const { return m_steps; }

Modified: trunk/Source/WebCore/style/StyleResolver.cpp (287523 => 287524)


--- trunk/Source/WebCore/style/StyleResolver.cpp	2022-01-02 21:37:07 UTC (rev 287523)
+++ trunk/Source/WebCore/style/StyleResolver.cpp	2022-01-02 23:42:27 UTC (rev 287524)
@@ -321,46 +321,61 @@
     if (it == m_keyframesRuleMap.end())
         return;
 
+    auto timingFunctionForKeyframe = [](Ref<StyleRuleKeyframe> keyframe) -> RefPtr<const TimingFunction> {
+        if (auto timingFunctionCSSValue = keyframe->properties().getPropertyCSSValue(CSSPropertyAnimationTimingFunction)) {
+            if (auto timingFunction = TimingFunction::createFromCSSValue(*timingFunctionCSSValue))
+                return timingFunction;
+        }
+        return &CubicBezierTimingFunction::defaultTimingFunction();
+    };
+
+    HashSet<RefPtr<const TimingFunction>> timingFunctions;
+    auto uniqueTimingFunctionForKeyframe = [&](Ref<StyleRuleKeyframe> keyframe) -> RefPtr<const TimingFunction> {
+        auto timingFunction = timingFunctionForKeyframe(keyframe);
+        for (auto existingTimingFunction : timingFunctions) {
+            if (arePointingToEqualData(timingFunction, existingTimingFunction))
+                return existingTimingFunction;
+        }
+        timingFunctions.add(timingFunction);
+        return timingFunction;
+    };
+
     const StyleRuleKeyframes* keyframesRule = it->value.get();
-
     auto* keyframes = &keyframesRule->keyframes();
-    Vector<Ref<StyleRuleKeyframe>> newKeyframesIfNecessary;
 
-    bool hasDuplicateKeys = false;
-    HashSet<double> keyframeKeys;
-    for (auto& keyframe : *keyframes) {
-        for (auto key : keyframe->keys()) {
-            if (!keyframeKeys.add(key)) {
-                hasDuplicateKeys = true;
-                break;
+    using KeyframeUniqueKey = std::pair<double, RefPtr<const TimingFunction>>;
+    auto hasDuplicateKeys = [&]() -> bool {
+        HashSet<KeyframeUniqueKey> uniqueKeyframeKeys;
+        for (auto& keyframe : *keyframes) {
+            auto timingFunction = uniqueTimingFunctionForKeyframe(keyframe);
+            for (auto key : keyframe->keys()) {
+                if (!uniqueKeyframeKeys.add({ key, timingFunction }))
+                    return true;
             }
         }
-        if (hasDuplicateKeys)
-            break;
-    }
+        return false;
+    }();
 
     // FIXME: If HashMaps could have Ref<> as value types, we wouldn't need
     // to copy the HashMap into a Vector.
+    Vector<Ref<StyleRuleKeyframe>> newKeyframesIfNecessary;
     if (hasDuplicateKeys) {
-        // Merge duplicate key times.
-        HashMap<double, RefPtr<StyleRuleKeyframe>> keyframesMap;
-
-        for (auto& originalKeyframe : keyframesRule->keyframes()) {
+        // Merge keyframes with a similar offset and timing function.
+        HashMap<KeyframeUniqueKey, RefPtr<StyleRuleKeyframe>> keyframesMap;
+        for (auto& originalKeyframe : *keyframes) {
+            auto timingFunction = uniqueTimingFunctionForKeyframe(originalKeyframe);
             for (auto key : originalKeyframe->keys()) {
-                if (auto keyframe = keyframesMap.get(key))
+                if (auto keyframe = keyframesMap.get({ key, timingFunction }))
                     keyframe->mutableProperties().mergeAndOverrideOnConflict(originalKeyframe->properties());
                 else {
-                    auto StyleRuleKeyframe = StyleRuleKeyframe::create(MutableStyleProperties::create());
-                    StyleRuleKeyframe.ptr()->setKey(key);
-                    StyleRuleKeyframe.ptr()->mutableProperties().mergeAndOverrideOnConflict(originalKeyframe->properties());
-                    keyframesMap.set(key, StyleRuleKeyframe.ptr());
+                    auto styleRuleKeyframe = StyleRuleKeyframe::create(MutableStyleProperties::create());
+                    styleRuleKeyframe.ptr()->setKey(key);
+                    styleRuleKeyframe.ptr()->mutableProperties().mergeAndOverrideOnConflict(originalKeyframe->properties());
+                    keyframesMap.set({ key, timingFunction }, styleRuleKeyframe.ptr());
+                    newKeyframesIfNecessary.append(styleRuleKeyframe);
                 }
             }
         }
-
-        for (auto& keyframe : keyframesMap.values())
-            newKeyframesIfNecessary.append(*keyframe.get());
-
         keyframes = &newKeyframesIfNecessary;
     }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to