Title: [260139] trunk
Revision
260139
Author
[email protected]
Date
2020-04-15 10:39:57 -0700 (Wed, 15 Apr 2020)

Log Message

[Web Animations] Add support for `pseudoElement` on `KeyframeEffect` and `KeyframeEffectOptions`
https://bugs.webkit.org/show_bug.cgi?id=207290
<rdar://problem/59199003>

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

Mark 23 additional WPT tests as PASS.

* web-platform-tests/css/css-animations/Document-getAnimations.tentative-expected.txt:
* web-platform-tests/css/css-animations/Element-getAnimations.tentative-expected.txt:
* web-platform-tests/css/css-transitions/Document-getAnimations.tentative-expected.txt:
* web-platform-tests/web-animations/idlharness.window-expected.txt:
* web-platform-tests/web-animations/interfaces/Animatable/animate-expected.txt:
* web-platform-tests/web-animations/interfaces/Animation/commitStyles-expected.txt:
* web-platform-tests/web-animations/interfaces/KeyframeEffect/style-change-events-expected.txt:
* web-platform-tests/web-animations/interfaces/KeyframeEffect/target-expected.txt:

Source/WebCore:

We add the required IDL bindings such that JS-originated Web Animations can target pseudo-elements, either via the KeyframeEffect.pseudoElement
property, or via the KeyframeEffectOptions.pseudoElement property, which is set on the object passed to the KeyframeEffect constrcutor and
Element.animate().

This means that a PseudoElement can be targeted by an animation even if it's not been created through style resolution by virtue of a ::before
or ::after selector and a "content" style rule. This means that when either the "target" or "pseudoElement" property of KeyframeEffect is set,
we ensure a PseudoElement is created and set on the host element if required. And additionally, we ensure that during style resolution, animations
are applied to such pseudo-elements with a new PseudoElement::isTargetedByKeyframeEffectRequiringPseudoElement() method that indicates that a
JS-originated KeyframeEffect targets this pseudo-element.

* animation/KeyframeEffect.cpp:
(WebCore::KeyframeEffect::create): Handle the new KeyframeEffectOptions.pseudoElement property in the KeyframeEffect constructor.
(WebCore::KeyframeEffect::targetsPseudoElement const): Indicates whether this effect targets a pseudo-element and not a regular
element or a null target.
(WebCore::KeyframeEffect::targetElementOrPseudoElement const): Use the new targetsPseudoElement() method to determine whether a
pseudo-element is targeted. We also remove an assertion that only made sense when m_pseudoId could only be set via a CSS-originated
animation and another one when the only possible m_pseudoId values were PseudoId::Before and PseudoId::After.
(WebCore::KeyframeEffect::setTarget): Call the new didChangeTargetElementOrPseudoElement() method if the provided value differs
from the stored value for m_target.
(WebCore::KeyframeEffect::pseudoElement const): Return the matching normalized string with a `::` prefix for m_pseudoId if the target
is a pseudo-element. Note that PseudoElement::pseudoElementNameForEvents() will only return a string for "::before" and "::after" since
we only know how to animate these pseudo-elements.
(WebCore::KeyframeEffect::setPseudoElement): Determine a matching PseudoId, if any, for the provided string, and call the new
didChangeTargetElementOrPseudoElement() method if the provided value differs from the stored value for m_pseudoId.
(WebCore::KeyframeEffect::didChangeTargetElementOrPseudoElement): New method called when either m_target or m_pseudoId is changed
such that we can ensure the required PseudoElement is created if the animation targets a pseudo-element. Then we run the same logic
that we used to in KeyframeEffect::setTarget().
(WebCore::KeyframeEffect::requiresPseudoElement const): Indicates whether a PseudoElement must remain created for this KeyframeEffect,
which is only necessary for JS-originated effects targeting a pseudo-element.
* animation/KeyframeEffect.h:
* animation/KeyframeEffect.idl:
* animation/KeyframeEffectOptions.h:
* animation/KeyframeEffectOptions.idl:
* animation/KeyframeEffectStack.cpp:
(WebCore::KeyframeEffectStack::requiresPseudoElement const): Indicates whether one or more JS-originated keyframe effects in the stack target
the PseudoElement owning this stack.
* animation/KeyframeEffectStack.h:
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::commitStyles): Use KeyframeEffect::targetsPseudoElement() to determine whether the animation's effect's target is a
pseudo-element, in which case we need to throw a NoModificationAllowedError exception.
* dom/PseudoElement.cpp:
(WebCore::PseudoElement::rendererIsNeeded): Return true also when one or more JS-originated keyframe effects in the stack target this pseudo-element.
(WebCore::PseudoElement::isTargetedByKeyframeEffectRequiringPseudoElement): Return true when one or more JS-originated keyframe effects in the stack
target this pseudo-element.
* dom/PseudoElement.h:
* rendering/updating/RenderTreeUpdaterGeneratedContent.cpp:
(WebCore::createContentRenderers): Remove the assertion that the "content" property was set since it's valid for this function to now be called
due to JS-originated keyframe effects targeting the given pseudo-element. Instead we add an assertion that there are such keyframe effects in
case no "content" property was set.
(WebCore::RenderTreeUpdater::GeneratedContent::updatePseudoElement): Only remove pseudo-elements if there are no JS-originated keyframe effects
targeting the specified pseudo-element.
* style/StyleTreeResolver.cpp:
(WebCore::Style::TreeResolver::resolvePseudoStyle): Allow animated style resolution for pseudo-elements targeted by JS-originated keyframe effects.

Modified Paths

Diff

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2020-04-15 17:39:57 UTC (rev 260139)
@@ -1,3 +1,22 @@
+2020-04-15  Antoine Quint  <[email protected]>
+
+        [Web Animations] Add support for `pseudoElement` on `KeyframeEffect` and `KeyframeEffectOptions`
+        https://bugs.webkit.org/show_bug.cgi?id=207290
+        <rdar://problem/59199003>
+
+        Reviewed by Antti Koivisto.
+
+        Mark 23 additional WPT tests as PASS.
+
+        * web-platform-tests/css/css-animations/Document-getAnimations.tentative-expected.txt:
+        * web-platform-tests/css/css-animations/Element-getAnimations.tentative-expected.txt:
+        * web-platform-tests/css/css-transitions/Document-getAnimations.tentative-expected.txt:
+        * web-platform-tests/web-animations/idlharness.window-expected.txt:
+        * web-platform-tests/web-animations/interfaces/Animatable/animate-expected.txt:
+        * web-platform-tests/web-animations/interfaces/Animation/commitStyles-expected.txt:
+        * web-platform-tests/web-animations/interfaces/KeyframeEffect/style-change-events-expected.txt:
+        * web-platform-tests/web-animations/interfaces/KeyframeEffect/target-expected.txt:
+
 2020-04-14  Youenn Fablet  <[email protected]>
 
         Resync featurepolicy.js to fix WPT mediacapture-streams/MediaStream-default-feature-policy.https.html

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/Document-getAnimations.tentative-expected.txt (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/Document-getAnimations.tentative-expected.txt	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/Document-getAnimations.tentative-expected.txt	2020-04-15 17:39:57 UTC (rev 260139)
@@ -13,5 +13,5 @@
 PASS Yet-to-start CSS Animations are returned 
 PASS CSS Animations canceled via the API are not returned 
 PASS CSS Animations canceled and restarted via the API are returned 
-FAIL CSS Animations targetting (pseudo-)elements should have correct order after sorting assert_equals: Animation #1 has null pseudo type expected (object) null but got (undefined) undefined
+PASS CSS Animations targetting (pseudo-)elements should have correct order after sorting 
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/Element-getAnimations.tentative-expected.txt (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/Element-getAnimations.tentative-expected.txt	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-animations/Element-getAnimations.tentative-expected.txt	2020-04-15 17:39:57 UTC (rev 260139)
@@ -17,8 +17,8 @@
 PASS getAnimations for CSS Animations that are canceled 
 PASS getAnimations for CSS Animations follows animation-name order 
 PASS { subtree: false } on a leaf element returns the element's animations and ignore pseudo-elements 
-FAIL { subtree: true } on a leaf element returns the element's animations and its pseudo-elements' animations assert_equals: The animation targeting the parent element should be returned first expected (object) null but got (undefined) undefined
+PASS { subtree: true } on a leaf element returns the element's animations and its pseudo-elements' animations 
 PASS { subtree: false } on an element with a child returns only the element's animations 
-FAIL { subtree: true } on an element with a child returns animations from the element, its pseudo-elements, its child and its child pseudo-elements assert_equals: The animation targeting the parent element should be returned first expected (object) null but got (undefined) undefined
+FAIL { subtree: true } on an element with a child returns animations from the element, its pseudo-elements, its child and its child pseudo-elements assert_equals: The animation targeting the ::after pesudo-element should be returned third expected (string) "::after" but got (object) null
 FAIL { subtree: true } on an element with many descendants returns animations from all the descendants assert_equals: The animation targeting the child1 element should be returned second expected Element node <div id="child1" style="animation: anim1 100s;"><div id="... but got Element node <div id="grandchild1" style="animation: anim1 100s;"></div>
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/Document-getAnimations.tentative-expected.txt (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/Document-getAnimations.tentative-expected.txt	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-transitions/Document-getAnimations.tentative-expected.txt	2020-04-15 17:39:57 UTC (rev 260139)
@@ -1,6 +1,6 @@
 
 PASS getAnimations for non-animated content 
 PASS getAnimations for CSS Transitions 
-FAIL CSS Transitions targetting (pseudo-)elements should have correct order after sorting assert_equals: Transition #1 has null pseudo type expected (object) null but got (undefined) undefined
+PASS CSS Transitions targetting (pseudo-)elements should have correct order after sorting 
 PASS Transitions are not returned after they have finished 
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/idlharness.window-expected.txt (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/idlharness.window-expected.txt	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/idlharness.window-expected.txt	2020-04-15 17:39:57 UTC (rev 260139)
@@ -108,7 +108,7 @@
 PASS KeyframeEffect interface: existence and properties of interface prototype object's "constructor" property 
 PASS KeyframeEffect interface: existence and properties of interface prototype object's @@unscopables property 
 PASS KeyframeEffect interface: attribute target 
-FAIL KeyframeEffect interface: attribute pseudoElement assert_true: The prototype object must have a property "pseudoElement" expected true got false
+PASS KeyframeEffect interface: attribute pseudoElement 
 PASS KeyframeEffect interface: attribute composite 
 PASS KeyframeEffect interface: operation getKeyframes() 
 PASS KeyframeEffect interface: operation setKeyframes(object) 
@@ -115,7 +115,7 @@
 PASS KeyframeEffect must be primary interface of new KeyframeEffect(null, null) 
 PASS Stringification of new KeyframeEffect(null, null) 
 PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "target" with the proper type 
-FAIL KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "pseudoElement" with the proper type assert_inherits: property "pseudoElement" not found in prototype chain
+PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "pseudoElement" with the proper type 
 PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "composite" with the proper type 
 PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "getKeyframes()" with the proper type 
 PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "setKeyframes(object)" with the proper type 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animatable/animate-expected.txt (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animatable/animate-expected.txt	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animatable/animate-expected.txt	2020-04-15 17:39:57 UTC (rev 260139)
@@ -137,8 +137,8 @@
 PASS animate() with pseudoElement parameter without content creates an Animation object 
 PASS animate() with pseudoElement parameter  creates an Animation object for ::marker 
 PASS animate() with pseudoElement parameter  creates an Animation object for ::first-line 
-FAIL animate() with pseudoElement an Animation object targeting the correct pseudo-element assert_equals: The returned Animation targets the correct selector expected (string) "::before" but got (undefined) undefined
-FAIL animate() with pseudoElement without content creates an Animation object targeting the correct pseudo-element assert_equals: The returned Animation targets the correct selector expected (string) "::before" but got (undefined) undefined
-FAIL animate() with pseudoElement an Animation object targeting the correct pseudo-element for ::marker assert_equals: The returned Animation targets the correct selector expected (string) "::marker" but got (undefined) undefined
-FAIL animate() with pseudoElement an Animation object targeting the correct pseudo-element for ::first-line assert_equals: The returned Animation targets the correct selector expected (string) "::first-line" but got (undefined) undefined
+PASS animate() with pseudoElement an Animation object targeting the correct pseudo-element 
+PASS animate() with pseudoElement without content creates an Animation object targeting the correct pseudo-element 
+FAIL animate() with pseudoElement an Animation object targeting the correct pseudo-element for ::marker assert_equals: The returned Animation targets the correct selector expected "::marker" but got ""
+FAIL animate() with pseudoElement an Animation object targeting the correct pseudo-element for ::first-line assert_equals: The returned Animation targets the correct selector expected "::first-line" but got ""
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animation/commitStyles-expected.txt (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animation/commitStyles-expected.txt	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animation/commitStyles-expected.txt	2020-04-15 17:39:57 UTC (rev 260139)
@@ -11,9 +11,7 @@
 FAIL Commit composites on top of the underlying value assert_approx_equals: expected 0.5 +/- 0.0001 but got 0.20000000298023224
 PASS Triggers mutation observers when updating style 
 FAIL Does NOT trigger mutation observers when the change to style is redundant assert_equals: Should have no mutation records expected 0 but got 1
-FAIL Throws if the target element is a pseudo element assert_throws_dom: function "() => {
-    animation.commitStyles();
-  }" did not throw
+PASS Throws if the target element is a pseudo element 
 PASS Throws if the target element is not something with a style attribute 
 PASS Throws if the target effect is display:none 
 PASS Throws if the target effect's ancestor is display:none 
@@ -20,7 +18,5 @@
 PASS Treats display:contents as rendered 
 PASS Treats display:contents in a display:none subtree as not rendered 
 PASS Throws if the target effect is disconnected 
-FAIL Checks the pseudo element condition before the not rendered condition assert_throws_dom: function "() => {
-    animation.commitStyles();
-  }" threw object "InvalidStateError: The object is in an invalid state." that is not a DOMException NoModificationAllowedError: property "code" is equal to 11, expected 7
+PASS Checks the pseudo element condition before the not rendered condition 
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/KeyframeEffect/style-change-events-expected.txt (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/KeyframeEffect/style-change-events-expected.txt	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/KeyframeEffect/style-change-events-expected.txt	2020-04-15 17:39:57 UTC (rev 260139)
@@ -1,9 +1,10 @@
 
-FAIL All property keys are recognized assert_in_array: Test property 'pseudoElement' should be one of the properties on  KeyframeEffect value "pseudoElement" not in array ["getTiming", "getComputedTiming", "updateTiming", "target", "iterationComposite", "composite", "getKeyframes", "setKeyframes", "KeyframeEffect constructor", "KeyframeEffect copy constructor"]
+PASS All property keys are recognized 
 PASS KeyframeEffect.getTiming does NOT trigger a style change event 
 PASS KeyframeEffect.getComputedTiming does NOT trigger a style change event 
 PASS KeyframeEffect.updateTiming does NOT trigger a style change event 
 PASS KeyframeEffect.target does NOT trigger a style change event 
+PASS KeyframeEffect.pseudoElement does NOT trigger a style change event 
 PASS KeyframeEffect.iterationComposite does NOT trigger a style change event 
 PASS KeyframeEffect.composite does NOT trigger a style change event 
 PASS KeyframeEffect.getKeyframes does NOT trigger a style change event 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/KeyframeEffect/target-expected.txt (260138 => 260139)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/KeyframeEffect/target-expected.txt	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/KeyframeEffect/target-expected.txt	2020-04-15 17:39:57 UTC (rev 260139)
@@ -4,18 +4,18 @@
 PASS Test setting target from a valid target to null 
 PASS Test setting target from a valid target to another target 
 PASS Target element can be set to a foreign element 
-FAIL Change target from null to an existing pseudoElement setting target first. assert_equals: Value at 50% progress after setting new target expected "50px" but got "10px"
-FAIL Change target from null to an existing pseudoElement setting pseudoElement first. assert_equals: Value at 50% progress after setting new target expected "50px" but got "10px"
+PASS Change target from null to an existing pseudoElement setting target first. 
+PASS Change target from null to an existing pseudoElement setting pseudoElement first. 
 PASS Change target from an existing pseudo-element to the originating element. 
-FAIL Change target from an existing to a different existing pseudo-element by setting target. assert_equals: Animation targets specified pseudo-element (pseudo-selector) expected (string) "::before" but got (undefined) undefined
-FAIL Change target from an existing to a different existing pseudo-element by setting pseudoElement. assert_equals: Value of 2nd element (currently targeted) after changing the effect target expected "50px" but got "20px"
-FAIL Change target from a non-existing to a different existing pseudo-element by setting target. assert_equals: Animation targets specified pseudo-element (pseudo-selector) expected (string) "::before" but got (undefined) undefined
-FAIL Change target from a non-existing to a different existing pseudo-element by setting pseudoElement. assert_equals: Value of 2nd element (currently targeted) after changing the effect target expected "50px" but got "20px"
-FAIL Change target from null to a non-existing pseudoElement setting target first. assert_equals: Value at 50% progress after setting new target expected "50px" but got "10px"
-FAIL Change target from null to a non-existing pseudoElement setting pseudoElement first. assert_equals: Value at 50% progress after setting new target expected "50px" but got "10px"
+PASS Change target from an existing to a different existing pseudo-element by setting target. 
+PASS Change target from an existing to a different existing pseudo-element by setting pseudoElement. 
+PASS Change target from a non-existing to a different existing pseudo-element by setting target. 
+PASS Change target from a non-existing to a different existing pseudo-element by setting pseudoElement. 
+PASS Change target from null to a non-existing pseudoElement setting target first. 
+PASS Change target from null to a non-existing pseudoElement setting pseudoElement first. 
 PASS Change target from a non-existing pseudo-element to the originating element. 
-FAIL Change target from an existing to a different non-existing pseudo-element by setting target. assert_equals: Animation targets specified pseudo-element (pseudo-selector) expected (string) "::before" but got (undefined) undefined
-FAIL Change target from an existing to a different non-existing pseudo-element by setting pseudoElement. assert_equals: Value of 2nd element (currently targeted) after changing the effect target expected "50px" but got "20px"
-FAIL Change target from a non-existing to a different non-existing pseudo-element by setting target. assert_equals: Animation targets specified pseudo-element (pseudo-selector) expected (string) "::before" but got (undefined) undefined
-FAIL Change target from a non-existing to a different non-existing pseudo-element by setting pseudoElement. assert_equals: Value of 2nd element (currently targeted) after changing the effect target expected "50px" but got "20px"
+PASS Change target from an existing to a different non-existing pseudo-element by setting target. 
+PASS Change target from an existing to a different non-existing pseudo-element by setting pseudoElement. 
+PASS Change target from a non-existing to a different non-existing pseudo-element by setting target. 
+PASS Change target from a non-existing to a different non-existing pseudo-element by setting pseudoElement. 
 

Modified: trunk/Source/WebCore/ChangeLog (260138 => 260139)


--- trunk/Source/WebCore/ChangeLog	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/ChangeLog	2020-04-15 17:39:57 UTC (rev 260139)
@@ -1,3 +1,65 @@
+2020-04-15  Antoine Quint  <[email protected]>
+
+        [Web Animations] Add support for `pseudoElement` on `KeyframeEffect` and `KeyframeEffectOptions`
+        https://bugs.webkit.org/show_bug.cgi?id=207290
+        <rdar://problem/59199003>
+
+        Reviewed by Antti Koivisto.
+
+        We add the required IDL bindings such that JS-originated Web Animations can target pseudo-elements, either via the KeyframeEffect.pseudoElement
+        property, or via the KeyframeEffectOptions.pseudoElement property, which is set on the object passed to the KeyframeEffect constrcutor and
+        Element.animate().
+
+        This means that a PseudoElement can be targeted by an animation even if it's not been created through style resolution by virtue of a ::before
+        or ::after selector and a "content" style rule. This means that when either the "target" or "pseudoElement" property of KeyframeEffect is set,
+        we ensure a PseudoElement is created and set on the host element if required. And additionally, we ensure that during style resolution, animations
+        are applied to such pseudo-elements with a new PseudoElement::isTargetedByKeyframeEffectRequiringPseudoElement() method that indicates that a
+        JS-originated KeyframeEffect targets this pseudo-element.
+
+        * animation/KeyframeEffect.cpp:
+        (WebCore::KeyframeEffect::create): Handle the new KeyframeEffectOptions.pseudoElement property in the KeyframeEffect constructor.
+        (WebCore::KeyframeEffect::targetsPseudoElement const): Indicates whether this effect targets a pseudo-element and not a regular
+        element or a null target.
+        (WebCore::KeyframeEffect::targetElementOrPseudoElement const): Use the new targetsPseudoElement() method to determine whether a
+        pseudo-element is targeted. We also remove an assertion that only made sense when m_pseudoId could only be set via a CSS-originated
+        animation and another one when the only possible m_pseudoId values were PseudoId::Before and PseudoId::After.
+        (WebCore::KeyframeEffect::setTarget): Call the new didChangeTargetElementOrPseudoElement() method if the provided value differs
+        from the stored value for m_target.
+        (WebCore::KeyframeEffect::pseudoElement const): Return the matching normalized string with a `::` prefix for m_pseudoId if the target
+        is a pseudo-element. Note that PseudoElement::pseudoElementNameForEvents() will only return a string for "::before" and "::after" since
+        we only know how to animate these pseudo-elements.
+        (WebCore::KeyframeEffect::setPseudoElement): Determine a matching PseudoId, if any, for the provided string, and call the new
+        didChangeTargetElementOrPseudoElement() method if the provided value differs from the stored value for m_pseudoId.
+        (WebCore::KeyframeEffect::didChangeTargetElementOrPseudoElement): New method called when either m_target or m_pseudoId is changed
+        such that we can ensure the required PseudoElement is created if the animation targets a pseudo-element. Then we run the same logic
+        that we used to in KeyframeEffect::setTarget().
+        (WebCore::KeyframeEffect::requiresPseudoElement const): Indicates whether a PseudoElement must remain created for this KeyframeEffect,
+        which is only necessary for JS-originated effects targeting a pseudo-element.
+        * animation/KeyframeEffect.h:
+        * animation/KeyframeEffect.idl:
+        * animation/KeyframeEffectOptions.h:
+        * animation/KeyframeEffectOptions.idl:
+        * animation/KeyframeEffectStack.cpp:
+        (WebCore::KeyframeEffectStack::requiresPseudoElement const): Indicates whether one or more JS-originated keyframe effects in the stack target
+        the PseudoElement owning this stack.
+        * animation/KeyframeEffectStack.h:
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::commitStyles): Use KeyframeEffect::targetsPseudoElement() to determine whether the animation's effect's target is a
+        pseudo-element, in which case we need to throw a NoModificationAllowedError exception.
+        * dom/PseudoElement.cpp:
+        (WebCore::PseudoElement::rendererIsNeeded): Return true also when one or more JS-originated keyframe effects in the stack target this pseudo-element.
+        (WebCore::PseudoElement::isTargetedByKeyframeEffectRequiringPseudoElement): Return true when one or more JS-originated keyframe effects in the stack
+        target this pseudo-element.
+        * dom/PseudoElement.h:
+        * rendering/updating/RenderTreeUpdaterGeneratedContent.cpp:
+        (WebCore::createContentRenderers): Remove the assertion that the "content" property was set since it's valid for this function to now be called
+        due to JS-originated keyframe effects targeting the given pseudo-element. Instead we add an assertion that there are such keyframe effects in
+        case no "content" property was set.
+        (WebCore::RenderTreeUpdater::GeneratedContent::updatePseudoElement): Only remove pseudo-elements if there are no JS-originated keyframe effects
+        targeting the specified pseudo-element.
+        * style/StyleTreeResolver.cpp:
+        (WebCore::Style::TreeResolver::resolvePseudoStyle): Allow animated style resolution for pseudo-elements targeted by JS-originated keyframe effects.
+
 2020-04-15  Carlos Garcia Campos  <[email protected]>
 
         [GTK4] Fix use of gtk init functions

Modified: trunk/Source/WebCore/animation/KeyframeEffect.cpp (260138 => 260139)


--- trunk/Source/WebCore/animation/KeyframeEffect.cpp	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/animation/KeyframeEffect.cpp	2020-04-15 17:39:57 UTC (rev 260139)
@@ -32,6 +32,7 @@
 #include "CSSKeyframeRule.h"
 #include "CSSPropertyAnimation.h"
 #include "CSSPropertyNames.h"
+#include "CSSSelector.h"
 #include "CSSStyleDeclaration.h"
 #include "CSSTimingFunctionValue.h"
 #include "CSSTransition.h"
@@ -486,6 +487,11 @@
             timing.duration = duration;
         } else {
             auto keyframeEffectOptions = WTF::get<KeyframeEffectOptions>(optionsValue);
+
+            auto setPseudoElementResult = keyframeEffect->setPseudoElement(keyframeEffectOptions.pseudoElement);
+            if (setPseudoElementResult.hasException())
+                return setPseudoElementResult.releaseException();
+
             timing = {
                 keyframeEffectOptions.duration,
                 keyframeEffectOptions.iterations,
@@ -1072,13 +1078,16 @@
     updateEffectStackMembership();
 }
 
+bool KeyframeEffect::targetsPseudoElement() const
+{
+    return m_target.get() && m_pseudoId != PseudoId::None;
+}
+
 Element* KeyframeEffect::targetElementOrPseudoElement() const
 {
-    if (m_pseudoId == PseudoId::None)
+    if (!targetsPseudoElement())
         return m_target.get();
 
-    ASSERT(m_target.get());
-
     if (m_pseudoId == PseudoId::Before)
         return m_target->beforePseudoElement();
 
@@ -1086,24 +1095,76 @@
         return m_target->afterPseudoElement();
 
     // We only support targeting ::before and ::after pseudo-elements at the moment.
-    ASSERT_NOT_REACHED();
     return nullptr;
 }
 
 void KeyframeEffect::setTarget(RefPtr<Element>&& newTarget)
 {
-    auto* previousTarget = targetElementOrPseudoElement();
-    if (previousTarget == newTarget.get())
+    if (m_target.get() == newTarget.get())
         return;
 
+    auto* previousTargetElementOrPseudoElement = targetElementOrPseudoElement();
     m_target = makeWeakPtr(newTarget.get());
+    didChangeTargetElementOrPseudoElement(previousTargetElementOrPseudoElement);
+}
 
-    // Until we support pseudo-elements via the Web Animations API, changing target means we should reset m_pseudoId.
-    // https://bugs.webkit.org/show_bug.cgi?id=207290
-    m_pseudoId = PseudoId::None;
+const String KeyframeEffect::pseudoElement() const
+{
+    // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-pseudoelement
 
+    // The target pseudo-selector. null if this effect has no effect target or if the effect target is an element (i.e. not a pseudo-element).
+    // When the effect target is a pseudo-element, this specifies the pseudo-element selector (e.g. ::before).
+    if (targetsPseudoElement())
+        return PseudoElement::pseudoElementNameForEvents(m_pseudoId);
+    return { };
+}
+
+ExceptionOr<void> KeyframeEffect::setPseudoElement(const String& pseudoElement)
+{
+    // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-pseudoelement
+
+    // On setting, sets the target pseudo-selector of the animation effect to the provided value after applying the following exceptions:
+    //
+    // - If the provided value is not null and is an invalid <pseudo-element-selector>, the user agent must throw a DOMException with error
+    // name SyntaxError and leave the target pseudo-selector of this animation effect unchanged. Note, that invalid in this context follows
+    // the definition of an invalid selector defined in [SELECTORS-4] such that syntactically invalid pseudo-elements as well as pseudo-elements
+    // for which the user agent has no usable level of support are both deemed invalid.
+    // - If one of the legacy Selectors Level 2 single-colon selectors (':before', ':after', ':first-letter', or ':first-line') is specified,
+    // the target pseudo-selector must be set to the equivalent two-colon selector (e.g. '::before').
+    auto pseudoId = PseudoId::None;
+    if (!pseudoElement.isNull()) {
+        auto isLegacy = pseudoElement == ":before" || pseudoElement == ":after" || pseudoElement == ":first-letter" || pseudoElement == ":first-line";
+        if (!isLegacy && !pseudoElement.startsWith("::"))
+            return Exception { SyntaxError };
+        auto pseudoType = CSSSelector::parsePseudoElementType(pseudoElement.substring(isLegacy ? 1 : 2));
+        if (pseudoType == CSSSelector::PseudoElementUnknown)
+            return Exception { SyntaxError };
+        pseudoId = CSSSelector::pseudoId(pseudoType);
+    }
+
+    if (pseudoId == m_pseudoId)
+        return { };
+
+    auto* previousTargetElementOrPseudoElement = targetElementOrPseudoElement();
+    m_pseudoId = pseudoId;
+    didChangeTargetElementOrPseudoElement(previousTargetElementOrPseudoElement);
+
+    return { };
+}
+
+void KeyframeEffect::didChangeTargetElementOrPseudoElement(Element* previousTargetElementOrPseudoElement)
+{
+    auto* newTargetElementOrPseudoElement = targetElementOrPseudoElement();
+
+    // We must ensure a PseudoElement exists for this m_target / m_pseudoId pair if both are specified.
+    if (!newTargetElementOrPseudoElement && m_target.get() && m_pseudoId != PseudoId::None) {
+        // We only support targeting ::before and ::after pseudo-elements at the moment.
+        if (m_pseudoId == PseudoId::Before || m_pseudoId == PseudoId::After)
+            newTargetElementOrPseudoElement = &m_target->ensurePseudoElement(m_pseudoId);
+    }
+
     if (auto* effectAnimation = animation())
-        effectAnimation->effectTargetDidChange(previousTarget, targetElementOrPseudoElement());
+        effectAnimation->effectTargetDidChange(previousTargetElementOrPseudoElement, newTargetElementOrPseudoElement);
 
     clearBlendingKeyframes();
 
@@ -1113,14 +1174,14 @@
 
     // Likewise, we need to invalidate styles on the previous target so that
     // any animated styles are removed immediately.
-    invalidateElement(previousTarget);
+    invalidateElement(previousTargetElementOrPseudoElement);
 
-    if (previousTarget) {
-        previousTarget->ensureKeyframeEffectStack().removeEffect(*this);
+    if (previousTargetElementOrPseudoElement) {
+        previousTargetElementOrPseudoElement->ensureKeyframeEffectStack().removeEffect(*this);
         m_inTargetEffectStack = false;
     }
-    if (targetElementOrPseudoElement())
-        m_inTargetEffectStack = targetElementOrPseudoElement()->ensureKeyframeEffectStack().addEffect(*this);
+    if (newTargetElementOrPseudoElement)
+        m_inTargetEffectStack = newTargetElementOrPseudoElement->ensureKeyframeEffectStack().addEffect(*this);
 }
 
 void KeyframeEffect::apply(RenderStyle& targetStyle)
@@ -1698,4 +1759,9 @@
     return true;
 }
 
+bool KeyframeEffect::requiresPseudoElement() const
+{
+    return m_blendingKeyframesSource == BlendingKeyframesSource::WebAnimation && targetsPseudoElement();
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/animation/KeyframeEffect.h (260138 => 260139)


--- trunk/Source/WebCore/animation/KeyframeEffect.h	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/animation/KeyframeEffect.h	2020-04-15 17:39:57 UTC (rev 260139)
@@ -105,6 +105,10 @@
     Element* targetElementOrPseudoElement() const;
     void setTarget(RefPtr<Element>&&);
 
+    bool targetsPseudoElement() const;
+    const String pseudoElement() const;
+    ExceptionOr<void> setPseudoElement(const String&);
+
     Vector<JSC::Strong<JSC::JSObject>> getKeyframes(JSC::JSGlobalObject&);
     ExceptionOr<void> setKeyframes(JSC::JSGlobalObject&, JSC::Strong<JSC::JSObject>&&);
 
@@ -158,6 +162,8 @@
 
     const RenderStyle* unanimatedStyle() const { return m_unanimatedStyle.get(); }
 
+    bool requiresPseudoElement() const;
+
 private:
     KeyframeEffect(Element*, PseudoId);
 
@@ -168,6 +174,7 @@
     Document* document() const;
     void updateEffectStackMembership();
     void copyPropertiesFromSource(Ref<KeyframeEffect>&&);
+    void didChangeTargetElementOrPseudoElement(Element*);
     ExceptionOr<void> processKeyframes(JSC::JSGlobalObject&, JSC::Strong<JSC::JSObject>&&);
     void addPendingAcceleratedAction(AcceleratedAction);
     void updateAcceleratedActions();

Modified: trunk/Source/WebCore/animation/KeyframeEffect.idl (260138 => 260139)


--- trunk/Source/WebCore/animation/KeyframeEffect.idl	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/animation/KeyframeEffect.idl	2020-04-15 17:39:57 UTC (rev 260139)
@@ -23,6 +23,8 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+typedef USVString CSSOMString;
+
 [
     EnabledAtRuntime=WebAnimations,
     Exposed=Window,
@@ -33,6 +35,7 @@
     Constructor(KeyframeEffect source)
 ] interface KeyframeEffect : AnimationEffect {
     attribute Element? target;
+    [MayThrowException] attribute CSSOMString? pseudoElement;
     [EnabledAtRuntime=WebAnimationsCompositeOperations] attribute IterationCompositeOperation iterationComposite;
     [EnabledAtRuntime=WebAnimationsCompositeOperations] attribute CompositeOperation composite;
     [CallWith=GlobalObject] sequence<object> getKeyframes();

Modified: trunk/Source/WebCore/animation/KeyframeEffectOptions.h (260138 => 260139)


--- trunk/Source/WebCore/animation/KeyframeEffectOptions.h	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/animation/KeyframeEffectOptions.h	2020-04-15 17:39:57 UTC (rev 260139)
@@ -35,6 +35,7 @@
 struct KeyframeEffectOptions : EffectTiming {
     IterationCompositeOperation iterationComposite { IterationCompositeOperation::Replace };
     CompositeOperation composite { CompositeOperation::Replace };
+    String pseudoElement;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/animation/KeyframeEffectOptions.idl (260138 => 260139)


--- trunk/Source/WebCore/animation/KeyframeEffectOptions.idl	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/animation/KeyframeEffectOptions.idl	2020-04-15 17:39:57 UTC (rev 260139)
@@ -23,7 +23,10 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+typedef USVString CSSOMString;
+
 dictionary KeyframeEffectOptions : EffectTiming {
     IterationCompositeOperation iterationComposite = "replace";
     CompositeOperation composite = "replace";
+    CSSOMString? pseudoElement = null;
 };

Modified: trunk/Source/WebCore/animation/KeyframeEffectStack.cpp (260138 => 260139)


--- trunk/Source/WebCore/animation/KeyframeEffectStack.cpp	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/animation/KeyframeEffectStack.cpp	2020-04-15 17:39:57 UTC (rev 260139)
@@ -59,6 +59,15 @@
     m_effects.removeFirst(&effect);
 }
 
+bool KeyframeEffectStack::requiresPseudoElement() const
+{
+    for (auto& effect : m_effects) {
+        if (effect->requiresPseudoElement())
+            return true;
+    }
+    return false;
+}
+
 bool KeyframeEffectStack::isCurrentlyAffectingProperty(CSSPropertyID property) const
 {
     for (auto& effect : m_effects) {

Modified: trunk/Source/WebCore/animation/KeyframeEffectStack.h (260138 => 260139)


--- trunk/Source/WebCore/animation/KeyframeEffectStack.h	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/animation/KeyframeEffectStack.h	2020-04-15 17:39:57 UTC (rev 260139)
@@ -47,6 +47,7 @@
     const AnimationList* cssAnimationList() const { return m_cssAnimationList.get(); }
     void setCSSAnimationList(RefPtr<const AnimationList>&&);
     bool isCurrentlyAffectingProperty(CSSPropertyID) const;
+    bool requiresPseudoElement() const;
 
 private:
     void ensureEffectsAreSorted();

Modified: trunk/Source/WebCore/animation/WebAnimation.cpp (260138 => 260139)


--- trunk/Source/WebCore/animation/WebAnimation.cpp	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/animation/WebAnimation.cpp	2020-04-15 17:39:57 UTC (rev 260139)
@@ -1367,7 +1367,7 @@
     //
     // 2.1 If target is not an element capable of having a style attribute (for example, it is a pseudo-element or is an element in a
     // document format for which style attributes are not defined) throw a "NoModificationAllowedError" DOMException and abort these steps.
-    if (!is<StyledElement>(target))
+    if (!is<StyledElement>(target) || effect->targetsPseudoElement())
         return Exception { NoModificationAllowedError };
 
     auto& styledElement = downcast<StyledElement>(*target);

Modified: trunk/Source/WebCore/dom/PseudoElement.cpp (260138 => 260139)


--- trunk/Source/WebCore/dom/PseudoElement.cpp	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/dom/PseudoElement.cpp	2020-04-15 17:39:57 UTC (rev 260139)
@@ -32,6 +32,7 @@
 #include "ContentData.h"
 #include "DocumentTimeline.h"
 #include "InspectorInstrumentation.h"
+#include "KeyframeEffectStack.h"
 #include "RenderElement.h"
 #include "RenderImage.h"
 #include "RenderQuote.h"
@@ -100,7 +101,14 @@
 
 bool PseudoElement::rendererIsNeeded(const RenderStyle& style)
 {
-    return pseudoElementRendererIsNeeded(&style);
+    return pseudoElementRendererIsNeeded(&style) || isTargetedByKeyframeEffectRequiringPseudoElement();
 }
 
+bool PseudoElement::isTargetedByKeyframeEffectRequiringPseudoElement()
+{
+    if (auto* stack = keyframeEffectStack())
+        return stack->requiresPseudoElement();
+    return false;
+}
+
 } // namespace

Modified: trunk/Source/WebCore/dom/PseudoElement.h (260138 => 260139)


--- trunk/Source/WebCore/dom/PseudoElement.h	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/dom/PseudoElement.h	2020-04-15 17:39:57 UTC (rev 260139)
@@ -41,6 +41,7 @@
     void clearHostElement();
 
     bool rendererIsNeeded(const RenderStyle&) override;
+    bool isTargetedByKeyframeEffectRequiringPseudoElement();
 
     bool canStartSelection() const override { return false; }
     bool canContainRangeEndPoint() const override { return false; }

Modified: trunk/Source/WebCore/rendering/updating/RenderTreeUpdaterGeneratedContent.cpp (260138 => 260139)


--- trunk/Source/WebCore/rendering/updating/RenderTreeUpdaterGeneratedContent.cpp	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/rendering/updating/RenderTreeUpdaterGeneratedContent.cpp	2020-04-15 17:39:57 UTC (rev 260139)
@@ -71,12 +71,16 @@
 
 static void createContentRenderers(RenderTreeBuilder& builder, RenderElement& pseudoRenderer, const RenderStyle& style)
 {
-    ASSERT(style.contentData());
-
-    for (const ContentData* content = style.contentData(); content; content = content->next()) {
-        auto child = content->createContentRenderer(pseudoRenderer.document(), style);
-        if (pseudoRenderer.isChildAllowed(*child, style))
-            builder.attach(pseudoRenderer, WTFMove(child));
+    if (auto* contentData = style.contentData()) {
+        for (const ContentData* content = contentData; content; content = content->next()) {
+            auto child = content->createContentRenderer(pseudoRenderer.document(), style);
+            if (pseudoRenderer.isChildAllowed(*child, style))
+                builder.attach(pseudoRenderer, WTFMove(child));
+        }
+    } else {
+        // The only valid scenario where this method is called without the "content" property being set
+        // is the case where a pseudo-element has animations set on it via the Web Animations API.
+        ASSERT(is<PseudoElement>(pseudoRenderer.element()) && downcast<PseudoElement>(*pseudoRenderer.element()).isTargetedByKeyframeEffectRequiringPseudoElement());
     }
 }
 
@@ -97,7 +101,7 @@
     if (auto* renderer = pseudoElement ? pseudoElement->renderer() : nullptr)
         m_updater.renderTreePosition().invalidateNextSibling(*renderer);
 
-    if (!needsPseudoElement(update)) {
+    if (!needsPseudoElement(update) && (!pseudoElement || !pseudoElement->isTargetedByKeyframeEffectRequiringPseudoElement())) {
         if (pseudoElement) {
             if (pseudoId == PseudoId::Before)
                 removeBeforePseudoElement(current, m_updater.m_builder);

Modified: trunk/Source/WebCore/style/StyleTreeResolver.cpp (260138 => 260139)


--- trunk/Source/WebCore/style/StyleTreeResolver.cpp	2020-04-15 17:27:51 UTC (rev 260138)
+++ trunk/Source/WebCore/style/StyleTreeResolver.cpp	2020-04-15 17:39:57 UTC (rev 260139)
@@ -265,9 +265,14 @@
         return { };
 
     auto pseudoStyle = scope().resolver.pseudoStyleForElement(element, { pseudoId }, *elementUpdate.style, parentBoxStyleForPseudo(elementUpdate), &scope().selectorFilter);
-    if (!pseudoElementRendererIsNeeded(pseudoStyle.get()))
+    if (!pseudoStyle)
         return { };
 
+    auto* pseudoElement = pseudoId == PseudoId::Before ? element.beforePseudoElement() : element.afterPseudoElement();
+    bool hasAnimations = pseudoElement && pseudoElement->isTargetedByKeyframeEffectRequiringPseudoElement();
+    if (!pseudoElementRendererIsNeeded(pseudoStyle.get()) && !hasAnimations)
+        return { };
+
     return createAnimatedElementUpdate(WTFMove(pseudoStyle), element.ensurePseudoElement(pseudoId), elementUpdate.change);
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to