[Web Animations] Implement replaced animations
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 2 Oct 2019 11:47:10 +0000 (11:47 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 2 Oct 2019 11:47:10 +0000 (11:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=202190
<rdar://55697719>

Reviewed by Dean Jackson.

LayoutTests/imported/w3c:

Mark WPT progressions.

* web-platform-tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations-expected.txt:
* web-platform-tests/web-animations/interfaces/Animatable/getAnimations-expected.txt:
* web-platform-tests/web-animations/interfaces/Animation/commitStyles-expected.txt:
* web-platform-tests/web-animations/interfaces/Animation/persist-expected.txt:
* web-platform-tests/web-animations/timing-model/timelines/update-and-send-events-replacement-expected.txt:

Source/WebCore:

Implementing section "5.5 Replacing Animations" (https://drafts.csswg.org/web-animations/#replacing-animations) of the Web Animations
specification which allows for Web Animations to be destroyed when they are superseded by another animation and the developer doesn't
explicitly opt into persisting them using the persist() method.

An animation is marked as replaceable (to sum up) when it's finished and another animation for the same property takes precedence.
As part of DocumentTimeline::internalUpdateAnimationsAndSendEvents(), we'll go through all replaceable animations, dispatch a "remove"
DOM event, and remove them from our list of animations.

We also make a couple of fixes in this patch that were uncovered while working on the WPT tests for replaced animations:

- we would incorrectly parse single values for a property that allows lists (change in KeyframeEffect's processKeyframeLikeObject())
- we didn't account for the position in the global animation list when sorted animations by composite order (AnimationTimeline::animationsForElement())

Finally, to get more readable results, we implement a stub of commitStyles(). Its full implementation is tracked by http://wkb.ug/202193.

* animation/AnimationTimeline.cpp:
(WebCore::AnimationTimeline::animationTimingDidChange): Mark the position of the animation in the global animation list, to which it may only be added once.
(WebCore::AnimationTimeline::animationsForElement const): Account for the animation's position in the global animation when sorting.
* animation/DeclarativeAnimation.cpp:
(WebCore::DeclarativeAnimation::bindingsReplaceState const): Flush pending styles when querying the ready state for a declarative animation.
* animation/DeclarativeAnimation.h:
* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::internalUpdateAnimationsAndSendEvents): Run the "remove replaced animations" procedure as the second step in the "update animations
and send events" procedure.
(WebCore::DocumentTimeline::animationCanBeRemoved): Determine whether a given animation may be removed based on its finished state, replace state and whether
it is fully superseded by another animation targeting the same property on the same target element.
(WebCore::DocumentTimeline::removeReplacedAnimations): Remove any removable animation and dispatch a "remove" DOM event for each removed animation.
* animation/DocumentTimeline.h:
* animation/KeyframeEffect.cpp:
(WebCore::processKeyframeLikeObject): Fix an issue found in a replaced animations WPT test that showed that we didn't record the value of an animation that allows lists.
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::isReplaceable const):
(WebCore::WebAnimation::persist): Mark the replace state as "persisted" and ensure the animation is set on the animation list for its target element in case it had already
been removed based on its persisted state.
(WebCore::WebAnimation::commitStyles): Stub for a new function.
* animation/WebAnimation.h:
(WebCore::WebAnimation::replaceState const):
(WebCore::WebAnimation::setReplaceState):
(WebCore::WebAnimation::bindingsReplaceState const):
(WebCore::WebAnimation::globalPosition const):
(WebCore::WebAnimation::setGlobalPosition):
* animation/WebAnimation.idl:
* dom/EventNames.h: Add the new "remove" event so that the "onremove" DOM property is available on Animation objects.

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

17 files changed:
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animatable/getAnimations-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animation/commitStyles-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animation/persist-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/timing-model/timelines/update-and-send-events-replacement-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/animation/AnimationTimeline.cpp
Source/WebCore/animation/DeclarativeAnimation.cpp
Source/WebCore/animation/DeclarativeAnimation.h
Source/WebCore/animation/DocumentTimeline.cpp
Source/WebCore/animation/DocumentTimeline.h
Source/WebCore/animation/KeyframeEffect.cpp
Source/WebCore/animation/WebAnimation.cpp
Source/WebCore/animation/WebAnimation.h
Source/WebCore/animation/WebAnimation.idl
Source/WebCore/dom/EventNames.h

index ae15c33..d91b824 100644 (file)
@@ -1,3 +1,19 @@
+2019-10-02  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement replaced animations
+        https://bugs.webkit.org/show_bug.cgi?id=202190
+        <rdar://55697719>
+
+        Reviewed by Dean Jackson.
+
+        Mark WPT progressions.
+
+        * web-platform-tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations-expected.txt:
+        * web-platform-tests/web-animations/interfaces/Animatable/getAnimations-expected.txt:
+        * web-platform-tests/web-animations/interfaces/Animation/commitStyles-expected.txt:
+        * web-platform-tests/web-animations/interfaces/Animation/persist-expected.txt:
+        * web-platform-tests/web-animations/timing-model/timelines/update-and-send-events-replacement-expected.txt:
+
 2019-10-01  Antti Koivisto  <antti@apple.com>
 
         [CSS Shadow Parts] Parse 'part' attribute
index 9dd6b89..e0a7569 100644 (file)
@@ -1,7 +1,7 @@
 
-FAIL Removed animations do not contribute to animated style assert_equals: expected (string) "removed" but got (undefined) undefined
-FAIL Removed animations do not contribute to the effect stack assert_equals: expected (string) "removed" but got (undefined) undefined
-FAIL Persisted animations contribute to animated style promise_test: Unhandled rejection with value: object "TypeError: animA.persist is not a function. (In 'animA.persist()', 'animA.persist' is undefined)"
-FAIL Persisted animations contribute to the effect stack assert_approx_equals: Opacity value should NOT include the contribution of the removed animation expected 0.4 +/- 0.0001 but got 0.10000000149011612
-FAIL Animations persisted before they would be removed contribute to the effect stack promise_test: Unhandled rejection with value: object "TypeError: animA.persist is not a function. (In 'animA.persist()', 'animA.persist' is undefined)"
+PASS Removed animations do not contribute to animated style 
+FAIL Removed animations do not contribute to the effect stack assert_approx_equals: Opacity value should not include the removed animation expected 0.4 +/- 0.0001 but got 0.30000001192092896
+PASS Persisted animations contribute to animated style 
+FAIL Persisted animations contribute to the effect stack assert_approx_equals: Opacity value should NOT include the contribution of the removed animation expected 0.4 +/- 0.0001 but got 0.30000001192092896
+FAIL Animations persisted before they would be removed contribute to the effect stack assert_approx_equals: Opacity value should include the contribution of the persisted animation expected 0.5 +/- 0.0001 but got 0.30000001192092896
 
index f34e3bf..cf6ae6c 100644 (file)
@@ -19,7 +19,7 @@ PASS Returns animations based on dynamic changes to individual animations' durat
 PASS Returns animations based on dynamic changes to individual animations' end delay 
 PASS Returns animations based on dynamic changes to individual animations' iteration count 
 PASS Returns animations based on dynamic changes to individual animations' current time 
-FAIL Does not return an animation that has been removed assert_array_equals: lengths differ, expected 1 got 2
-FAIL Returns an animation that has been persisted promise_test: Unhandled rejection with value: object "TypeError: animA.persist is not a function. (In 'animA.persist()', 'animA.persist' is undefined)"
+PASS Does not return an animation that has been removed 
+PASS Returns an animation that has been persisted 
 PASS Triggers a style change event 
 
index 90adece..663fe33 100644 (file)
@@ -1,32 +1,32 @@
 
-FAIL Commits styles animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)
-FAIL Commits styles for an animation that has been removed promise_test: Unhandled rejection with value: object "TypeError: animA.commitStyles is not a function. (In 'animA.commitStyles()', 'animA.commitStyles' is undefined)"
-FAIL Commits shorthand styles animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)
-FAIL Commits logical properties animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)
-FAIL Commits values calculated mid-interval animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)
-FAIL Commits variables as their computed values animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)
-FAIL Commits em units as pixel values animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)
-FAIL Commits the intermediate value of an animation in the middle of stack promise_test: Unhandled rejection with value: object "TypeError: animA.persist is not a function. (In 'animA.persist()', 'animA.persist' is undefined)"
-FAIL Triggers mutation observers when updating style promise_test: Unhandled rejection with value: object "TypeError: animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)"
-FAIL Does NOT trigger mutation observers when the change to style is redundant promise_test: Unhandled rejection with value: object "TypeError: animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)"
+FAIL Commits styles assert_approx_equals: expected 0.2 +/- 0.0001 but got 0.10000000149011612
+FAIL Commits styles for an animation that has been removed assert_approx_equals: expected 0.2 +/- 0.0001 but got 0.10000000149011612
+FAIL Commits shorthand styles assert_equals: expected "20px" but got "10px"
+FAIL Commits logical properties assert_equals: expected "20px" but got "10px"
+FAIL Commits values calculated mid-interval assert_approx_equals: expected 0.45 +/- 0.0001 but got 1
+FAIL Commits variables as their computed values assert_approx_equals: expected 0.5 +/- 0.0001 but got 1
+FAIL Commits em units as pixel values assert_approx_equals: expected 100 +/- 0.0001 but got 784
+FAIL Commits the intermediate value of an animation in the middle of stack assert_approx_equals: expected 0.4 +/- 0.0001 but got 0.10000000149011612
+FAIL Triggers mutation observers when updating style assert_equals: Should have one mutation record expected 1 but got 0
+PASS Does NOT trigger mutation observers when the change to style is redundant 
 FAIL Throws if the target element is a pseudo element assert_throws: function "() => {
     animation.commitStyles();
-  }" threw object "TypeError: animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)" that is not a DOMException NoModificationAllowedError: property "code" is equal to undefined, expected 7
+  }" did not throw
 FAIL Throws if the target element is not something with a style attribute assert_throws: function "() => {
     animation.commitStyles();
-  }" threw object "TypeError: animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)" that is not a DOMException NoModificationAllowedError: property "code" is equal to undefined, expected 7
+  }" did not throw
 FAIL Throws if the target effect is display:none assert_throws: function "() => {
     animation.commitStyles();
-  }" threw object "TypeError: animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)" that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
+  }" did not throw
 FAIL Throws if the target effect's ancestor is display:none assert_throws: function "() => {
     animation.commitStyles();
-  }" threw object "TypeError: animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)" that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
-FAIL Treats display:contents as rendered animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)
+  }" did not throw
+PASS Treats display:contents as rendered 
 FAIL Treats display:contents in a display:none subtree as not rendered assert_throws: function "() => {
     animation.commitStyles();
-  }" threw object "TypeError: animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)" that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
+  }" did not throw
 FAIL Throws if the target effect is disconnected assert_throws: function "() => {
     animation.commitStyles();
-  }" threw object "TypeError: animation.commitStyles is not a function. (In 'animation.commitStyles()', 'animation.commitStyles' is undefined)" that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
+  }" did not throw
 FAIL Checks the pseudo element condition before the not rendered condition undefined is not an object (evaluating 'pseudo.element.remove')
 
index 01e1891..c333c59 100644 (file)
@@ -1,6 +1,4 @@
 
-Harness Error (TIMEOUT), message = null
-
-TIMEOUT Allows an animation to be persisted after being removed Test timed out
-FAIL Allows an animation to be persisted before being removed promise_test: Unhandled rejection with value: object "TypeError: animA.persist is not a function. (In 'animA.persist()', 'animA.persist' is undefined)"
+PASS Allows an animation to be persisted after being removed 
+PASS Allows an animation to be persisted before being removed 
 
index 077f279..f1b486d 100644 (file)
@@ -1,46 +1,46 @@
 
 Harness Error (TIMEOUT), message = null
 
-FAIL Removes an animation when another covers the same properties assert_equals: expected (string) "removed" but got (undefined) undefined
-FAIL Removes an animation after another animation finishes assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after multiple other animations finish assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after it finishes assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after seeking another animation assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after seeking it assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating the fill mode of another animation assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating its fill mode assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating another animation's effect to one with different timing assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating its effect to one with different timing assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating another animation's timeline assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating its timeline assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating another animation's effect's properties assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating its effect's properties assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating another animation's effect to one with different properties assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating its effect to one with different properties assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation when another animation uses a shorthand assert_equals: expected (string) "removed" but got (undefined) undefined
-FAIL Removes an animation that uses a shorthand assert_equals: expected (string) "removed" but got (undefined) undefined
-FAIL Removes an animation by another animation using logical properties assert_equals: expected (string) "removed" but got (undefined) undefined
-FAIL Removes an animation using logical properties assert_equals: expected (string) "removed" but got (undefined) undefined
-FAIL Removes an animation by another animation using logical properties after updating the context assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating another animation's effect's target assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating its effect's target assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating another animation's effect to one with a different target assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes an animation after updating its effect to one with a different target assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Does NOT remove a CSS animation tied to markup assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes a CSS animation no longer tied to markup assert_equals: expected (string) "removed" but got (undefined) undefined
-FAIL Does NOT remove a CSS transition tied to markup assert_equals: expected (string) "active" but got (undefined) undefined
-FAIL Removes a CSS transition no longer tied to markup assert_equals: expected (string) "removed" but got (undefined) undefined
-TIMEOUT Dispatches an event when removing Test timed out
-NOTRUN Does NOT dispatch a remove event twice 
-NOTRUN Does NOT remove an animation after making a redundant change to another animation's current time 
-NOTRUN Does NOT remove an animation after making a redundant change to its current time 
-NOTRUN Does NOT remove an animation after making a redundant change to another animation's timeline 
-NOTRUN Does NOT remove an animation after making a redundant change to its timeline 
-NOTRUN Does NOT remove an animation after making a redundant change to another animation's effect's properties 
-NOTRUN Does NOT remove an animation after making a redundant change to its effect's properties 
-NOTRUN Updates ALL timelines before checking for replacement 
-NOTRUN Dispatches remove events after finish events 
-NOTRUN Fires remove event before requestAnimationFrame 
-NOTRUN Queues all remove events before running them 
-NOTRUN Performs removal in deeply nested iframes 
+PASS Removes an animation when another covers the same properties 
+PASS Removes an animation after another animation finishes 
+PASS Removes an animation after multiple other animations finish 
+PASS Removes an animation after it finishes 
+PASS Removes an animation after seeking another animation 
+PASS Removes an animation after seeking it 
+PASS Removes an animation after updating the fill mode of another animation 
+PASS Removes an animation after updating its fill mode 
+PASS Removes an animation after updating another animation's effect to one with different timing 
+PASS Removes an animation after updating its effect to one with different timing 
+FAIL Removes an animation after updating another animation's timeline assert_equals: expected "removed" but got "active"
+PASS Removes an animation after updating its timeline 
+PASS Removes an animation after updating another animation's effect's properties 
+PASS Removes an animation after updating its effect's properties 
+PASS Removes an animation after updating another animation's effect to one with different properties 
+PASS Removes an animation after updating its effect to one with different properties 
+PASS Removes an animation when another animation uses a shorthand 
+PASS Removes an animation that uses a shorthand 
+FAIL Removes an animation by another animation using logical properties assert_equals: expected "removed" but got "active"
+PASS Removes an animation using logical properties 
+FAIL Removes an animation by another animation using logical properties after updating the context assert_equals: expected "active" but got "removed"
+PASS Removes an animation after updating another animation's effect's target 
+PASS Removes an animation after updating its effect's target 
+PASS Removes an animation after updating another animation's effect to one with a different target 
+PASS Removes an animation after updating its effect to one with a different target 
+PASS Does NOT remove a CSS animation tied to markup 
+PASS Removes a CSS animation no longer tied to markup 
+PASS Does NOT remove a CSS transition tied to markup 
+FAIL Removes a CSS transition no longer tied to markup assert_equals: expected "removed" but got "active"
+PASS Dispatches an event when removing 
+PASS Does NOT dispatch a remove event twice 
+PASS Does NOT remove an animation after making a redundant change to another animation's current time 
+PASS Does NOT remove an animation after making a redundant change to its current time 
+PASS Does NOT remove an animation after making a redundant change to another animation's timeline 
+PASS Does NOT remove an animation after making a redundant change to its timeline 
+PASS Does NOT remove an animation after making a redundant change to another animation's effect's properties 
+PASS Does NOT remove an animation after making a redundant change to its effect's properties 
+FAIL Updates ALL timelines before checking for replacement assert_equals: expected "removed" but got "active"
+PASS Dispatches remove events after finish events 
+PASS Fires remove event before requestAnimationFrame 
+PASS Queues all remove events before running them 
+TIMEOUT Performs removal in deeply nested iframes Test timed out
 
index bd5d8d3..5a5844f 100644 (file)
@@ -1,3 +1,55 @@
+2019-10-02  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement replaced animations
+        https://bugs.webkit.org/show_bug.cgi?id=202190
+        <rdar://55697719>
+
+        Reviewed by Dean Jackson.
+
+        Implementing section "5.5 Replacing Animations" (https://drafts.csswg.org/web-animations/#replacing-animations) of the Web Animations
+        specification which allows for Web Animations to be destroyed when they are superseded by another animation and the developer doesn't
+        explicitly opt into persisting them using the persist() method.
+
+        An animation is marked as replaceable (to sum up) when it's finished and another animation for the same property takes precedence.
+        As part of DocumentTimeline::internalUpdateAnimationsAndSendEvents(), we'll go through all replaceable animations, dispatch a "remove"
+        DOM event, and remove them from our list of animations.
+
+        We also make a couple of fixes in this patch that were uncovered while working on the WPT tests for replaced animations:
+        
+        - we would incorrectly parse single values for a property that allows lists (change in KeyframeEffect's processKeyframeLikeObject())
+        - we didn't account for the position in the global animation list when sorted animations by composite order (AnimationTimeline::animationsForElement())
+
+        Finally, to get more readable results, we implement a stub of commitStyles(). Its full implementation is tracked by http://wkb.ug/202193.
+
+        * animation/AnimationTimeline.cpp:
+        (WebCore::AnimationTimeline::animationTimingDidChange): Mark the position of the animation in the global animation list, to which it may only be added once.
+        (WebCore::AnimationTimeline::animationsForElement const): Account for the animation's position in the global animation when sorting.
+        * animation/DeclarativeAnimation.cpp:
+        (WebCore::DeclarativeAnimation::bindingsReplaceState const): Flush pending styles when querying the ready state for a declarative animation.
+        * animation/DeclarativeAnimation.h:
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::internalUpdateAnimationsAndSendEvents): Run the "remove replaced animations" procedure as the second step in the "update animations
+        and send events" procedure.
+        (WebCore::DocumentTimeline::animationCanBeRemoved): Determine whether a given animation may be removed based on its finished state, replace state and whether
+        it is fully superseded by another animation targeting the same property on the same target element.
+        (WebCore::DocumentTimeline::removeReplacedAnimations): Remove any removable animation and dispatch a "remove" DOM event for each removed animation. 
+        * animation/DocumentTimeline.h:
+        * animation/KeyframeEffect.cpp:
+        (WebCore::processKeyframeLikeObject): Fix an issue found in a replaced animations WPT test that showed that we didn't record the value of an animation that allows lists.
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::isReplaceable const):
+        (WebCore::WebAnimation::persist): Mark the replace state as "persisted" and ensure the animation is set on the animation list for its target element in case it had already
+        been removed based on its persisted state.
+        (WebCore::WebAnimation::commitStyles): Stub for a new function.
+        * animation/WebAnimation.h:
+        (WebCore::WebAnimation::replaceState const):
+        (WebCore::WebAnimation::setReplaceState):
+        (WebCore::WebAnimation::bindingsReplaceState const):
+        (WebCore::WebAnimation::globalPosition const):
+        (WebCore::WebAnimation::setGlobalPosition):
+        * animation/WebAnimation.idl:
+        * dom/EventNames.h: Add the new "remove" event so that the "onremove" DOM property is available on Animation objects. 
+
 2019-10-02  Zan Dobersek  <zdobersek@igalia.com>
 
         Unreviewed build fix in Nicosia's ScrollingTreePositionedNode class.
index 1100102..35f71bd 100644 (file)
@@ -62,6 +62,7 @@ void AnimationTimeline::forgetAnimation(WebAnimation* animation)
 void AnimationTimeline::animationTimingDidChange(WebAnimation& animation)
 {
     if (m_animations.add(&animation)) {
+        animation.setGlobalPosition(m_allAnimations.size());
         m_allAnimations.append(makeWeakPtr(&animation));
         auto* timeline = animation.timeline();
         if (timeline && timeline != this)
@@ -169,7 +170,7 @@ Vector<RefPtr<WebAnimation>> AnimationTimeline::animationsForElement(Element& el
     if (m_elementToCSSTransitionsMap.contains(&element)) {
         const auto& cssTransitions = m_elementToCSSTransitionsMap.get(&element);
         if (ordering == Ordering::Sorted) {
-            Vector<RefPtr<WebAnimation>> sortedCSSTransitions;
+            Vector<RefPtr<WebAnimation>> sortedCSSTransitions(cssTransitions.size());
             sortedCSSTransitions.appendRange(cssTransitions.begin(), cssTransitions.end());
             std::sort(sortedCSSTransitions.begin(), sortedCSSTransitions.end(), [](auto& lhs, auto& rhs) {
                 // Sort transitions first by their generation time, and then by transition-property.
@@ -190,7 +191,15 @@ Vector<RefPtr<WebAnimation>> AnimationTimeline::animationsForElement(Element& el
     }
     if (m_elementToAnimationsMap.contains(&element)) {
         const auto& webAnimations = m_elementToAnimationsMap.get(&element);
-        animations.appendRange(webAnimations.begin(), webAnimations.end());
+        if (ordering == Ordering::Sorted) {
+            Vector<RefPtr<WebAnimation>> sortedWebAnimations(webAnimations.size());
+            sortedWebAnimations.appendRange(webAnimations.begin(), webAnimations.end());
+            std::sort(sortedWebAnimations.begin(), sortedWebAnimations.end(), [](auto& lha, auto& rha) {
+                return lha->globalPosition() < rha->globalPosition();
+            });
+            animations.appendVector(sortedWebAnimations);
+        } else
+            animations.appendRange(webAnimations.begin(), webAnimations.end());
     }
     return animations;
 }
index f4b6978..bb8ca84 100644 (file)
@@ -153,6 +153,12 @@ WebAnimation::PlayState DeclarativeAnimation::bindingsPlayState() const
     return WebAnimation::bindingsPlayState();
 }
 
+WebAnimation::ReplaceState DeclarativeAnimation::bindingsReplaceState() const
+{
+    flushPendingStyleChanges();
+    return WebAnimation::bindingsReplaceState();
+}
+
 bool DeclarativeAnimation::bindingsPending() const
 {
     flushPendingStyleChanges();
index 8bfae21..31d0cfb 100644 (file)
@@ -54,6 +54,7 @@ public:
     Optional<double> bindingsCurrentTime() const final;
     ExceptionOr<void> setBindingsCurrentTime(Optional<double>) final;
     WebAnimation::PlayState bindingsPlayState() const final;
+    WebAnimation::ReplaceState bindingsReplaceState() const final;
     bool bindingsPending() const final;
     WebAnimation::ReadyPromise& bindingsReady() final;
     WebAnimation::FinishedPromise& bindingsFinished() final;
index caf0a21..d5a5bb2 100644 (file)
@@ -378,14 +378,17 @@ void DocumentTimeline::internalUpdateAnimationsAndSendEvents()
         }
     }
 
-    // 2. Perform a microtask checkpoint.
+    // 2. Remove replaced animations for doc.
+    removeReplacedAnimations();
+
+    // 3. Perform a microtask checkpoint.
     MicrotaskQueue::mainThreadQueue().performMicrotaskCheckpoint();
 
-    // 3. Let events to dispatch be a copy of doc's pending animation event queue.
-    // 4. Clear doc's pending animation event queue.
+    // 4. Let events to dispatch be a copy of doc's pending animation event queue.
+    // 5. Clear doc's pending animation event queue.
     auto pendingAnimationEvents = WTFMove(m_pendingAnimationEvents);
 
-    // 5. Perform a stable sort of the animation events in events to dispatch as follows.
+    // 6. Perform a stable sort of the animation events in events to dispatch as follows.
     std::stable_sort(pendingAnimationEvents.begin(), pendingAnimationEvents.end(), [] (const Ref<AnimationPlaybackEvent>& lhs, const Ref<AnimationPlaybackEvent>& rhs) {
         // 1. Sort the events by their scheduled event time such that events that were scheduled to occur earlier, sort before events scheduled to occur later
         // and events whose scheduled event time is unresolved sort before events with a resolved scheduled event time.
@@ -399,7 +402,7 @@ void DocumentTimeline::internalUpdateAnimationsAndSendEvents()
         return lhs->timelineTime().value() < rhs->timelineTime().value();
     });
 
-    // 6. Dispatch each of the events in events to dispatch at their corresponding target using the order established in the previous step.
+    // 7. Dispatch each of the events in events to dispatch at their corresponding target using the order established in the previous step.
     for (auto& pendingEvent : pendingAnimationEvents)
         pendingEvent->target()->dispatchEvent(pendingEvent);
 
@@ -418,6 +421,81 @@ void DocumentTimeline::internalUpdateAnimationsAndSendEvents()
         transitionDidComplete(completedTransition);
 }
 
+bool DocumentTimeline::animationCanBeRemoved(WebAnimation& animation)
+{
+    // https://drafts.csswg.org/web-animations/#removing-replaced-animations
+
+    ASSERT(m_document);
+
+    // - is replaceable, and
+    if (!animation.isReplaceable())
+        return false;
+
+    // - has a replace state of active, and
+    if (animation.replaceState() != WebAnimation::ReplaceState::Active)
+        return false;
+
+    // - has an associated animation effect whose target element is a descendant of doc, and
+    auto* effect = animation.effect();
+    if (!is<KeyframeEffect>(effect))
+        return false;
+
+    auto* keyframeEffect = downcast<KeyframeEffect>(effect);
+    auto* target = keyframeEffect->target();
+    if (!target || !target->isDescendantOf(*m_document))
+        return false;
+
+    HashSet<CSSPropertyID> propertiesToMatch = keyframeEffect->animatedProperties();
+    auto animations = animationsForElement(*target, AnimationTimeline::Ordering::Sorted);
+    for (auto& animationWithHigherCompositeOrder : WTF::makeReversedRange(animations)) {
+        if (&animation == animationWithHigherCompositeOrder)
+            break;
+
+        if (animationWithHigherCompositeOrder && animationWithHigherCompositeOrder->isReplaceable()) {
+            auto* effectWithHigherCompositeOrder = animationWithHigherCompositeOrder->effect();
+            if (is<KeyframeEffect>(effectWithHigherCompositeOrder)) {
+                auto* keyframeEffectWithHigherCompositeOrder = downcast<KeyframeEffect>(effectWithHigherCompositeOrder);
+                for (auto cssPropertyId : keyframeEffectWithHigherCompositeOrder->animatedProperties()) {
+                    if (propertiesToMatch.remove(cssPropertyId) && propertiesToMatch.isEmpty())
+                        break;
+                }
+            }
+        }
+    }
+
+    return propertiesToMatch.isEmpty();
+}
+
+void DocumentTimeline::removeReplacedAnimations()
+{
+    // https://drafts.csswg.org/web-animations/#removing-replaced-animations
+
+    Vector<RefPtr<WebAnimation>> animationsToRemove;
+
+    // When asked to remove replaced animations for a Document, doc, then for every animation, animation
+    for (auto& animation : m_allAnimations) {
+        if (animation && animationCanBeRemoved(*animation)) {
+            // perform the following steps:
+            // 1. Set animation's replace state to removed.
+            animation->setReplaceState(WebAnimation::ReplaceState::Removed);
+            // 2. Create an AnimationPlaybackEvent, removeEvent.
+            // 3. Set removeEvent's type attribute to remove.
+            // 4. Set removeEvent's currentTime attribute to the current time of animation.
+            // 5. Set removeEvent's timelineTime attribute to the current time of the timeline with which animation is associated.
+            // 6. If animation has a document for timing, then append removeEvent to its document for timing's pending animation
+            //    event queue along with its target, animation. For the scheduled event time, use the result of applying the procedure
+            //    to convert timeline time to origin-relative time to the current time of the timeline with which animation is associated.
+            //    Otherwise, queue a task to dispatch removeEvent at animation. The task source for this task is the DOM manipulation task source.
+            animation->enqueueAnimationPlaybackEvent(eventNames().removeEvent, animation->currentTime(), currentTime());
+
+            animationsToRemove.append(animation.get());
+        }
+    }
+
+    for (auto& animation : animationsToRemove)
+        removeAnimation(*animation);
+}
+
 void DocumentTimeline::transitionDidComplete(RefPtr<CSSTransition> transition)
 {
     ASSERT(transition);
index 49a1110..43007cc 100644 (file)
@@ -98,6 +98,8 @@ private:
     void updateListOfElementsWithRunningAcceleratedAnimationsForElement(Element&);
     void transitionDidComplete(RefPtr<CSSTransition>);
     void scheduleNextTick();
+    void removeReplacedAnimations();
+    bool animationCanBeRemoved(WebAnimation&);
 
     Timer m_tickScheduleTimer;
     GenericTaskQueue<Timer> m_currentTimeClearingTaskQueue;
index a93a4e2..c07a02f 100644 (file)
@@ -243,10 +243,10 @@ static inline ExceptionOr<KeyframeEffect::KeyframeLikeObject> processKeyframeLik
             // using the procedures defined for converting an ECMAScript value to an IDL value [WEBIDL].
             // If property values is a single DOMString, replace property values with a sequence of DOMStrings with the original value of property
             // Values as the only element.
-            if (rawValue.isString())
-                propertyValues = { rawValue.toWTFString(&state) };
-            else if (rawValue.isObject())
+            if (rawValue.isObject())
                 propertyValues = convert<IDLSequence<IDLDOMString>>(state, rawValue);
+            else
+                propertyValues = { rawValue.toWTFString(&state) };
         } else {
             // Otherwise,
             // Let property values be the result of converting raw value to a DOMString using the procedure for converting an ECMAScript value to a DOMString.
index e6dea96..504a2f3 100644 (file)
@@ -29,6 +29,7 @@
 #include "AnimationEffect.h"
 #include "AnimationPlaybackEvent.h"
 #include "AnimationTimeline.h"
+#include "DeclarativeAnimation.h"
 #include "Document.h"
 #include "DocumentTimeline.h"
 #include "EventNames.h"
@@ -1203,6 +1204,58 @@ bool WebAnimation::computeRelevance()
     return timing.phase == AnimationEffectPhase::Before || (timing.phase == AnimationEffectPhase::Active && playState() != PlayState::Finished);
 }
 
+bool WebAnimation::isReplaceable() const
+{
+    // An animation is replaceable if all of the following conditions are true:
+    // https://drafts.csswg.org/web-animations/#removing-replaced-animations
+
+    // The existence of the animation is not prescribed by markup. That is, it is not a CSS animation with an owning element,
+    // nor a CSS transition with an owning element.
+    if (isDeclarativeAnimation() && downcast<DeclarativeAnimation>(this)->owningElement())
+        return false;
+
+    // The animation's play state is finished.
+    if (playState() != PlayState::Finished)
+        return false;
+
+    // The animation's replace state is not removed.
+    if (m_replaceState == ReplaceState::Removed)
+        return false;
+
+    // The animation is associated with a monotonically increasing timeline.
+    if (!m_timeline)
+        return false;
+
+    // The animation has an associated target effect.
+    if (!m_effect)
+        return false;
+
+    // The target effect associated with the animation is in effect.
+    if (!m_effect->getBasicTiming().activeTime)
+        return false;
+
+    // The target effect has an associated target element.
+    if (!is<KeyframeEffect>(m_effect) || !downcast<KeyframeEffect>(m_effect.get())->target())
+        return false;
+
+    return true;
+}
+
+void WebAnimation::persist()
+{
+    auto previousReplaceState = std::exchange(m_replaceState, ReplaceState::Persisted);
+
+    if (previousReplaceState == ReplaceState::Removed && m_timeline) {
+        if (is<KeyframeEffect>(m_effect))
+            m_timeline->animationWasAddedToElement(*this, *downcast<KeyframeEffect>(m_effect.get())->target());
+    }
+}
+
+ExceptionOr<void> WebAnimation::commitStyles()
+{
+    return { };
+}
+
 Seconds WebAnimation::timeToNextTick() const
 {
     ASSERT(isRunningAccelerated());
index a4fa9d1..3592d62 100644 (file)
@@ -74,6 +74,10 @@ public:
     enum class PlayState : uint8_t { Idle, Running, Paused, Finished };
     PlayState playState() const;
 
+    enum class ReplaceState : uint8_t { Active, Removed, Persisted };
+    ReplaceState replaceState() const { return m_replaceState; }
+    void setReplaceState(ReplaceState replaceState) { m_replaceState = replaceState; }
+
     bool pending() const { return hasPendingPauseTask() || hasPendingPlayTask(); }
 
     using ReadyPromise = DOMPromiseProxyWithResolveCallback<IDLInterface<WebAnimation>>;
@@ -89,12 +93,15 @@ public:
     void updatePlaybackRate(double);
     ExceptionOr<void> pause();
     ExceptionOr<void> reverse();
+    void persist();
+    ExceptionOr<void> commitStyles();
 
     virtual Optional<double> startTime() const;
     virtual void setStartTime(Optional<double>);
     virtual Optional<double> bindingsCurrentTime() const;
     virtual ExceptionOr<void> setBindingsCurrentTime(Optional<double>);
     virtual PlayState bindingsPlayState() const { return playState(); }
+    virtual ReplaceState bindingsReplaceState() const { return replaceState(); }
     virtual bool bindingsPending() const { return pending(); }
     virtual ReadyPromise& bindingsReady() { return ready(); }
     virtual FinishedPromise& bindingsFinished() { return finished(); }
@@ -116,7 +123,12 @@ public:
     void unsuspendEffectInvalidation();
     void setSuspended(bool);
     bool isSuspended() const { return m_isSuspended; }
+    bool isReplaceable() const;
     virtual void remove();
+    void enqueueAnimationPlaybackEvent(const AtomString&, Optional<Seconds>, Optional<Seconds>);
+
+    unsigned globalPosition() const { return m_globalPosition; }
+    void setGlobalPosition(unsigned globalPosition) { m_globalPosition = globalPosition; }
 
     bool hasPendingActivity() const final;
 
@@ -137,7 +149,6 @@ private:
 
     void timingDidChange(DidSeek, SynchronouslyNotify);
     void updateFinishedState(DidSeek, SynchronouslyNotify);
-    void enqueueAnimationPlaybackEvent(const AtomString&, Optional<Seconds>, Optional<Seconds>);
     Seconds effectEndTime() const;
     WebAnimation& readyPromiseResolve();
     WebAnimation& finishedPromiseResolve();
@@ -179,6 +190,8 @@ private:
     bool m_shouldSkipUpdatingFinishedStateWhenResolving;
     TimeToRunPendingTask m_timeToRunPendingPlayTask { TimeToRunPendingTask::NotScheduled };
     TimeToRunPendingTask m_timeToRunPendingPauseTask { TimeToRunPendingTask::NotScheduled };
+    ReplaceState m_replaceState { ReplaceState::Active };
+    unsigned m_globalPosition;
 
     // ActiveDOMObject.
     const char* activeDOMObjectName() const final;
index 8813fb5..2949e07 100644 (file)
@@ -30,6 +30,12 @@ enum AnimationPlayState {
     "finished"
 };
 
+enum AnimationReplaceState {
+    "active",
+    "removed",
+    "persisted"
+};
+
 [
     ActiveDOMObject,
     EnabledAtRuntime=WebAnimations,
@@ -44,9 +50,11 @@ enum AnimationPlayState {
     [MayThrowException, ImplementedAs=bindingsCurrentTime] attribute double? currentTime;
     attribute double playbackRate;
     [ImplementedAs=bindingsPlayState] readonly attribute AnimationPlayState playState;
+    [ImplementedAs=bindingsReplaceState] readonly attribute AnimationReplaceState replaceState;
     [ImplementedAs=bindingsPending] readonly attribute boolean pending;
     attribute EventHandler onfinish;
     attribute EventHandler oncancel;
+    attribute EventHandler onremove;
     [ImplementedAs=bindingsReady] readonly attribute Promise<WebAnimation> ready;
     [ImplementedAs=bindingsFinished] readonly attribute Promise<WebAnimation> finished;
     void cancel();
@@ -55,4 +63,6 @@ enum AnimationPlayState {
     [MayThrowException, ImplementedAs=bindingsPause] void pause();
     void updatePlaybackRate(double playbackRate);
     [MayThrowException] void reverse();
+    void persist();
+    [MayThrowException] void commitStyles();
 };
index fc4fd5c..7188ae3 100644 (file)
@@ -213,6 +213,7 @@ namespace WebCore {
     macro(ratechange) \
     macro(readystatechange) \
     macro(rejectionhandled) \
+    macro(remove) \
     macro(removesourcebuffer) \
     macro(removestream) \
     macro(removetrack) \