[Web Animations] Implement CSS Animations and CSS Transitions as Web Animations
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 12 Mar 2018 12:56:14 +0000 (12:56 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 12 Mar 2018 12:56:14 +0000 (12:56 +0000)
https://bugs.webkit.org/show_bug.cgi?id=183504
<rdar://problem/38372965>

LayoutTests/imported/w3c:

Reviewed by Dean Jackson and Jon Lee.

Since we've improved our implementation of getAnimations() we updated the expectations to mark
the progressions. Both tests for getAnimations() now pass 100%. Another test now fails at a later
stage and needed its expectation updated.

* web-platform-tests/web-animations/interfaces/Animatable/animate-expected.txt:
* web-platform-tests/web-animations/interfaces/Animatable/getAnimations-expected.txt:
* web-platform-tests/web-animations/interfaces/Document/getAnimations-expected.txt:

Source/WebCore:

Reviewed by Dean Jackson and Jon Lee.

Tests: webanimations/css-animations.html
       webanimations/css-transitions.html

This patch implements CSS Animations and CSS Transitions as Web Animations. The main changes are:

* StyleTreeResolver: StyleTreeResolver now has a code path to add CSSAnimation and CSSTransition objects onto the DocumentTimeline
to be picked up by the Web Animations engine. The previous CSSAnimationController code path is preserved if the runtime flag is disabled.

* AnimationTimeline: we add two new methods, updateCSSAnimationsForElement() and updateCSSTransitionsForElement() which are called from
TreeResolver::createAnimatedElementUpdate(). These look at the AnimationList for the old and new RenderStyle objects and create, update
and remove matching CSSAnimation and CSSTransition instances.

* DeclarativeAnimation: a new superclass to both CSSAnimation and CSSTransition which introduces the concept of a backingAnimation(),
which is an Animation held by the RenderStyle objects, and two virtual methods with base implementations, initialize() which is called
upon creating by create() methods in subclasses, and syncPropertiesWithBackingAnimation() which ensures that properties on the
DeclarativeAnimation objects (Web Animations side) match the backing animation (CSS side).

* KeyframeEffectReadOnly: two new important methods to create blending keyframes (KeyframeList) based on backing Animation objects,
computeCSSAnimationBlendingKeyframes() and computeCSSTransitionBlendingKeyframes().

* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* animation/AnimationEffectReadOnly.h:
(WebCore::AnimationEffectReadOnly::isKeyframeEffectReadOnly const): We fix this method such that calling it on a KeyframeEffect, which
is a subclass of KeyframeEffectReadOnly, returns true.
* animation/AnimationEffectTimingReadOnly.cpp: In order for DeclarativeAnimation::syncPropertiesWithBackingAnimation() to set the timing
function for a declarative animation's effect, we need a public method to set an effect's timing function outside of just the "easing"
property setter exposed via the JS API. So we introduce a setTimingFunction() method and call it from setEasing().
(WebCore::AnimationEffectTimingReadOnly::setEasing):
(WebCore::AnimationEffectTimingReadOnly::setTimingFunction):
* animation/AnimationEffectTimingReadOnly.h:
* animation/AnimationTimeline.cpp:
(WebCore::AnimationTimeline::~AnimationTimeline): Clear all maps and sets containing WebAnimation references to ensure these get destructed
when the AnimationTimeline is being destructed and should no longer hold a reference to them.
(WebCore::AnimationTimeline::relevantMapForAnimation): We store various subclasses of WebAnimation in dedicated maps so we can composite
animations in the correct order when animating. This function returns the correct map for a given animation such that animationWasAddedToElement()
and animationWasRemovedFromElement() mutate the right map.
(WebCore::AnimationTimeline::animationWasAddedToElement):
(WebCore::AnimationTimeline::animationWasRemovedFromElement):
(WebCore::AnimationTimeline::animationsForElement): Make sure to look for animations in the lists of CSS Animations and CSS Transitions as well
as Web Animations.
(WebCore::AnimationTimeline::updateCSSAnimationsForElement): This method is called by TreeResolver::createAnimatedElementUpdate() during style
resolution. It compares the AnimationList of the previous style and the new style for a given element, checks that animations with a given name
that were not present in the old AnimationList have a new matching CSSAnimation object for them added to the AnimationTimeline, that animations
with a given name that are no longer present in the new AnimationList have their matching CSSAnimation object removed from the AnimationTimeline,
and that animations with a given name that are present in both the old and new AnimationList have their matching CSSAnimation updated to match
the current state of the animation in the AnimationList.
(WebCore::AnimationTimeline::updateCSSTransitionsForElement): Similarly to updateCSSAnimationsForElement(), this method is called during style
resolution by TreeResolver::createAnimatedElementUpdate(). Its role is to create or remove CSSTransition objects based on the AnimationList found
in the old and new styles for a given element. It follows a slightly different logic than updateCSSAnimationsForElement() since for CSS Transitions,
there is no need to update CSSTransition objects for a CSS property existing in both the old and new AnimationList, since when a CSS transitions
property is changed, a whole new transition is initiated. However, it's important to check that different Animation objects and styles would actually
result in different timing properties and blending keyframes, so check for this as well before creating new CSSTransition objects.
* animation/AnimationTimeline.h:
(WebCore::AnimationTimeline::animations const): Change the m_animations type from HashSet to ListHashSet to guarantee we preserve the insertion order which is
required by getAnimations().
(WebCore::AnimationTimeline::hasElementAnimations const): Indicates to DocumentTimeline::updateAnimations() that there are animations targeting the provided element.
(WebCore::AnimationTimeline::elementToAnimationsMap):
(WebCore::AnimationTimeline::elementToCSSAnimationsMap):
(WebCore::AnimationTimeline::elementToCSSTransitionsMap):
* animation/CSSAnimation.cpp: CSSAnimation is now a subclass of DeclarativeAnimation and subclasses initialize() and syncPropertiesWithBackingAnimation()
to perform work specific to CSS Animations.
(WebCore::CSSAnimation::create): Set the animationName property based on the provided backing animation.
(WebCore::CSSAnimation::CSSAnimation):
(WebCore::CSSAnimation::initialize): Create the blending keyframes for this CSSAnimation.
(WebCore::CSSAnimation::syncPropertiesWithBackingAnimation): Reflect the animation-fill-mode, animation-direction, animation-iteration-count and
animation-play-state CSS properties on the AnimationEffectTimingReadOnly object associated with this CSSAnimation.
* animation/CSSAnimation.h:
* animation/CSSTransition.cpp: CSSTransition is now a subclass of DeclarativeAnimation.
(WebCore::CSSTransition::create): Set the transitionProperty property based on the provided backing animation.
(WebCore::CSSTransition::CSSTransition):
(WebCore::CSSTransition::matchesBackingAnimationAndStyles const):
(WebCore::CSSTransition::canBeListed const): Subclass this method such that we also check that we have blending keyframes for a CSSTransition to be
listed by calls to getAnimations().
* animation/CSSTransition.h:
* animation/DeclarativeAnimation.cpp: Added. This new WebAnimation subclass now is the common base class for both CSSAnimation and CSSTransition.
It establishes a relationship with a "backing animation", which is an Animation obtained from a style's AnimationList while resolving styles.
These backing animations contain all of the parsed CSS styles related to CSS Animations and CSS Transitions and we use those to set matching properties
of the Web Animations timing model in the new syncPropertiesWithBackingAnimation() virtual method, which subclasses can override to perform further
work that is specific to a given declarative animation type. The initialize() method is called during create() methods to perform common animation
setup work. Note that while both initialize() and syncPropertiesWithBackingAnimation() are called, we suspend invalidation to that animation's effect
since these methods are meant to be called during style invalidation and we would hit an assertion if we followed the usual route of calling
updateStyleIfNeeded() on the target's document during invalidation.
(WebCore::DeclarativeAnimation::DeclarativeAnimation):
(WebCore::DeclarativeAnimation::setBackingAnimation):
(WebCore::DeclarativeAnimation::initialize): Create a KeyframeEffectReadOnly for this animation and set the provided element as its target, set that
element's document's timeline and play the animation if the backing animation's play state is playing.
(WebCore::DeclarativeAnimation::syncPropertiesWithBackingAnimation): Reflect the {animation|transition}-delay, {animation|transition}-duration and
{animation|transition}-timing-function properties as set on the backing animation.
* animation/DeclarativeAnimation.h: Added.
(WebCore::DeclarativeAnimation::backingAnimation const):
* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::updateAnimations): Trigger style invalidation for elements targeted not just by WebAnimation instances, but also by any
of the DeclarativeAnimation subclasses. We also remove the call to updateFinishedState() which should have been removed when we implemented correct
support for asynchronous WebAnimation operations.
(WebCore::DocumentTimeline::animatedStyleForRenderer): Declarative animations are backed by KeyframeEffectReadOnly effects, so make sure we check
for KeyframeEffectReadOnly or one of its subclasses and not just KeyframeEffect since there now are animation types that use the ReadOnly variant.
(WebCore::DocumentTimeline::runningAnimationsForElementAreAllAccelerated): Same as for animatedStyleForRenderer, check for KeyframeEffectReadOnly
and not simply KeyframeEffect.
* animation/KeyframeEffectReadOnly.cpp:
(WebCore::invalidateElement): Stop forcing a style resolution as we invalidate element, marking them as dirty is sufficient. Calls to getAnimations()
already force a style resolution as needed.
(WebCore::KeyframeEffectReadOnly::create): Add a new create() method that only provides a target and which is used by DeclarativeAnimation::initialize().
(WebCore::KeyframeEffectReadOnly::getKeyframes): The previous implementation of getKeyframes() used the ParsedKeyframe list held as m_parsedKeyframes
to compute keyframes. In the case of declarative animations, there are no ParsedKeyframe since the JS API was not involved, so we use the blending keyframes
to look for keyframe data.
(WebCore::KeyframeEffectReadOnly::computeCSSAnimationBlendingKeyframes): Called by CSSAnimation::initialize(), this function creates blending keyframes by
looking up the keyframes date obtained from the @keyframes rule with this backing animation's name.
(WebCore::KeyframeEffectReadOnly::computeCSSTransitionBlendingKeyframes): Called by CSSTransition::create(), this function creates blending keyframes by
creating a 0-offset keyframe with the old style and a 1-offset keyframe with the new style as provided during TreeResolver::createAnimatedElementUpdate().
(WebCore::KeyframeEffectReadOnly::stylesWouldYieldNewCSSTransitionsBlendingKeyframes const): Called by AnimationTimeline::updateCSSTransitionsForElement()
to check that a provided backing Animation and a pair of old and new RenderStyles that may be different objects actually would yield different timing
properties and keyframe CSS values for a given CSS transition to avoid the deletion and creation of CSSTransition objects.
(WebCore::KeyframeEffectReadOnly::shouldRunAccelerated): We mistakenly assumed we always had blending keyframes, which is not always the case with a
CSSTransition where the transition style itself might be set first, but the target value after. So we should only run accelerated provided there are blending
keyframes at least, the function already returning false if it finds a blending keyframe animating a non-accelerated CSS property.
(WebCore::KeyframeEffectReadOnly::setAnimatedPropertiesInStyle): Check that there actually is a matching ParsedKeyframe to read the timing function from.
* animation/KeyframeEffectReadOnly.h:
(WebCore::KeyframeEffectReadOnly::hasBlendingKeyframes const):
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::~WebAnimation): We used to do something very wrong when a WebAnimation was destroyed which uncovered crashes when dealing with
declarative animations. In AnimationTimeline's updateCSSAnimationsForElement() and updateCSSTransitionsForElement(), when we identify that a DeclarativeAnimation
no longer matches an Animation from the current style's AnimationList, we set that DeclarativeAnimation's effect to null and call removeAnimation() on
the timeline. This removes all references from AnimationTimeline to this DeclarativeAnimation and leads to ~WebAnimation being called. Calling removeAnimation()
again in the destructor means that we'd hit ASSERT_WITH_SECURITY_IMPLICATION(!m_deletionHasBegun) in ref(). It was also meaningless to perform this work in
the WebAnimation destructor since an animation could never be destroyed if it were still registered on a timeline.
(WebCore::WebAnimation::suspendEffectInvalidation): DeclarativeAnimation instances have their timing model properties set during style invalidation, so we need
a mechanism to allow the usual effect invalidation to be suspended in this case. We now maintain a simple m_suspendCount count that increases and decreases with
calls to this method and unsuspendEffectInvalidation() and a isEffectInvalidationSuspended() method returning true whenever that count is positive.
(WebCore::WebAnimation::unsuspendEffectInvalidation):
(WebCore::WebAnimation::timingModelDidChange): Check that effect invalidation is not suspended before proceeding with invalidating the effect.
(WebCore::WebAnimation::setEffect): Check for KeyframeEffectReadOnly and not just KeyframeEffect since declarative animations have ReadOnly effects.
(WebCore::WebAnimation::setTimeline): Check for KeyframeEffectReadOnly and not just KeyframeEffect since declarative animations have ReadOnly effects.
(WebCore::WebAnimation::scheduleMicrotaskIfNeeded): Ensure that the WebAnimation's lifecycle is extended at least to the completion of the scheduled microtask.
This would otherwise cause crashes after declarative animations were destroyed when they were no longer applied.
(WebCore::WebAnimation::runPendingPlayTask): Only fulfill the "ready" promise if it hasn't already been, which might have been the case if multiple calls to play()
are made as a result of updating the animation play state in CSSAnimation::syncPropertiesWithBackingAnimation().
(WebCore::WebAnimation::runPendingPauseTask): Same as above but with multiple pause() calls.
(WebCore::WebAnimation::startOrStopAccelerated): Check for KeyframeEffectReadOnly and not just KeyframeEffect since declarative animations have ReadOnly effects.
(WebCore::WebAnimation::canBeListed const): This new method is called by {Document|Element}::getAnimations() to check that an animation is in the correct state to
be listed. The Web Animations spec explains that only animations "that have an associated target effect which is current or in effect" can be listed. We implement
this behavior as specified.
* animation/WebAnimation.h:
(WebCore::WebAnimation::isDeclarativeAnimation const):
(WebCore::WebAnimation::isEffectInvalidationSuspended):
* dom/Document.cpp:
(WebCore::Document::getAnimations): Ensure that the document's pending styles are resolved before returning animations to ensure that any pending declarative
animations are created. Additionally, we ensure that we only list qualifying animations that have effects targeting elements that are children of thi document.
* dom/Element.cpp:
(WebCore::Element::getAnimations): Same as Document::getAnimations().
* style/StyleTreeResolver.cpp:
(WebCore::Style::TreeResolver::createAnimatedElementUpdate): When resolving styles, call into the AnimationTimeline if the runtime flag to enable CSS Animations and
CSS Transitions as Web Animations is on. Otherwise, use CSSAnimationController.

Source/WebKitLegacy/mac:

Reviewed by Dean Jackson and Jon Lee.

Add the missing WebKitLegacy support the cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled flag
which is required for the matching <!-- webkit-test-runner --> flag to work in DumpRenderTree.

* WebView/WebPreferenceKeysPrivate.h:
* WebView/WebPreferences.mm:
(+[WebPreferences initialize]):
(-[WebPreferences setModernMediaControlsEnabled:]):
(-[WebPreferences cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled]):
(-[WebPreferences setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled:]):
* WebView/WebPreferencesPrivate.h:
* WebView/WebView.mm:
(-[WebView _preferencesChanged:]):

Source/WebKitLegacy/win:

Reviewed by Dean Jackson and Jon Lee.

Add the missing WebKitLegacy support the cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled flag
which is required for the matching <!-- webkit-test-runner --> flag to work in DumpRenderTree.

* Interfaces/IWebPreferencesPrivate.idl:
* WebPreferences.cpp:
(WebPreferences::cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled):
(WebPreferences::setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled):
* WebPreferenceKeysPrivate.h
* WebPreferences.h:
* WebView.cpp:
(WebView::notifyPreferencesChanged):

Tools:

Reviewed by Jon Lee.

Add a new <!-- webkit-test-runner --> flag to enable the CSS Animations and CSS Transitions
as Web Animations runtime flag in the new tests we've created for this feature.

* DumpRenderTree/TestOptions.h:
* DumpRenderTree/TestOptions.mm:
(TestOptions::TestOptions):
* DumpRenderTree/mac/DumpRenderTree.mm:
(setWebPreferencesForTestOptions):
* WebKitTestRunner/TestController.cpp:
(WTR::TestController::resetPreferencesToConsistentValues):
(WTR::updateTestOptionsFromTestHeader):
* WebKitTestRunner/TestOptions.h:
(WTR::TestOptions::hasSameInitializationOptions const):

LayoutTests:

Reviewed by Dean Jackson and Jon Lee.

Add a series of new tests to check CSSAnimation and CSSTransition objects are correctly created
as CSS animation-* and CSS transition-* properties are used. We also update some existing tests
to use a more concise API since we've implement Element.animate() since their creation.

* webanimations/animation-opacity-animation-crash.html:
* webanimations/css-animations-expected.txt: Added.
* webanimations/css-animations.html: Added.
* webanimations/css-transitions-expected.txt: Added.
* webanimations/css-transitions.html: Added.
* webanimations/opacity-animation-no-longer-composited-upon-completion.html:
* webanimations/opacity-animation-yields-compositing.html:

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

51 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animatable/animate-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animatable/getAnimations-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Document/getAnimations-expected.txt
LayoutTests/webanimations/animation-opacity-animation-crash.html
LayoutTests/webanimations/css-animations-expected.txt [new file with mode: 0644]
LayoutTests/webanimations/css-animations.html [new file with mode: 0644]
LayoutTests/webanimations/css-transitions-expected.txt [new file with mode: 0644]
LayoutTests/webanimations/css-transitions.html [new file with mode: 0644]
LayoutTests/webanimations/opacity-animation-no-longer-composited-upon-completion.html
LayoutTests/webanimations/opacity-animation-yields-compositing.html
Source/WebCore/ChangeLog
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/animation/AnimationEffectReadOnly.h
Source/WebCore/animation/AnimationEffectTimingReadOnly.cpp
Source/WebCore/animation/AnimationEffectTimingReadOnly.h
Source/WebCore/animation/AnimationTimeline.cpp
Source/WebCore/animation/AnimationTimeline.h
Source/WebCore/animation/CSSAnimation.cpp
Source/WebCore/animation/CSSAnimation.h
Source/WebCore/animation/CSSTransition.cpp
Source/WebCore/animation/CSSTransition.h
Source/WebCore/animation/DeclarativeAnimation.cpp [new file with mode: 0644]
Source/WebCore/animation/DeclarativeAnimation.h [new file with mode: 0644]
Source/WebCore/animation/DocumentTimeline.cpp
Source/WebCore/animation/KeyframeEffectReadOnly.cpp
Source/WebCore/animation/KeyframeEffectReadOnly.h
Source/WebCore/animation/WebAnimation.cpp
Source/WebCore/animation/WebAnimation.h
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Element.cpp
Source/WebCore/style/StyleTreeResolver.cpp
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebView/WebPreferenceKeysPrivate.h
Source/WebKitLegacy/mac/WebView/WebPreferences.mm
Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h
Source/WebKitLegacy/mac/WebView/WebView.mm
Source/WebKitLegacy/win/ChangeLog
Source/WebKitLegacy/win/Interfaces/IWebPreferencesPrivate.idl
Source/WebKitLegacy/win/WebPreferenceKeysPrivate.h
Source/WebKitLegacy/win/WebPreferences.cpp
Source/WebKitLegacy/win/WebPreferences.h
Source/WebKitLegacy/win/WebView.cpp
Tools/ChangeLog
Tools/DumpRenderTree/TestOptions.h
Tools/DumpRenderTree/TestOptions.mm
Tools/DumpRenderTree/mac/DumpRenderTree.mm
Tools/WebKitTestRunner/TestController.cpp
Tools/WebKitTestRunner/TestOptions.h

index b31462f..fb17a58 100644 (file)
@@ -1,3 +1,23 @@
+2018-03-12  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement CSS Animations and CSS Transitions as Web Animations
+        https://bugs.webkit.org/show_bug.cgi?id=183504
+        <rdar://problem/38372965>
+
+        Reviewed by Dean Jackson and Jon Lee.
+
+        Add a series of new tests to check CSSAnimation and CSSTransition objects are correctly created
+        as CSS animation-* and CSS transition-* properties are used. We also update some existing tests
+        to use a more concise API since we've implement Element.animate() since their creation.
+
+        * webanimations/animation-opacity-animation-crash.html:
+        * webanimations/css-animations-expected.txt: Added.
+        * webanimations/css-animations.html: Added.
+        * webanimations/css-transitions-expected.txt: Added.
+        * webanimations/css-transitions.html: Added.
+        * webanimations/opacity-animation-no-longer-composited-upon-completion.html:
+        * webanimations/opacity-animation-yields-compositing.html:
+
 2018-03-09  Zalan Bujtas  <zalan@apple.com>
 
         Turn off offset*/scroll* optimization for input elements with shadow content
index d4699b9..a53d9fc 100644 (file)
@@ -1,3 +1,19 @@
+2018-03-12  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement CSS Animations and CSS Transitions as Web Animations
+        https://bugs.webkit.org/show_bug.cgi?id=183504
+        <rdar://problem/38372965>
+
+        Reviewed by Dean Jackson and Jon Lee.
+
+        Since we've improved our implementation of getAnimations() we updated the expectations to mark
+        the progressions. Both tests for getAnimations() now pass 100%. Another test now fails at a later
+        stage and needed its expectation updated.
+
+        * web-platform-tests/web-animations/interfaces/Animatable/animate-expected.txt:
+        * web-platform-tests/web-animations/interfaces/Animatable/getAnimations-expected.txt:
+        * web-platform-tests/web-animations/interfaces/Document/getAnimations-expected.txt:
+
 2018-03-02  Youenn Fablet  <youenn@apple.com>
 
         Loads for a Document controlled by a Service Worker should not use AppCache
index 7f03af8..f2a5e73 100644 (file)
@@ -118,6 +118,6 @@ PASS Element.animate() correctly sets the id attribute
 PASS Element.animate() correctly sets the Animation's timeline 
 PASS Element.animate() correctly sets the Animation's timeline when triggered on an element in a different document 
 PASS Element.animate() calls play on the Animation 
-FAIL CSSPseudoElement.animate() creates an Animation object assert_equals: expected Element node <div class="pseudo"></div> but got null
-FAIL CSSPseudoElement.animate() creates an Animation object targeting to the correct CSSPseudoElement object assert_equals: expected Element node <div class="pseudo"></div> but got null
+FAIL CSSPseudoElement.animate() creates an Animation object assert_true: expected true got false
+FAIL CSSPseudoElement.animate() creates an Animation object targeting to the correct CSSPseudoElement object assert_true: expected true got false
 
index c4cb823..a186636 100644 (file)
@@ -3,11 +3,11 @@ PASS Returns an empty array for an element with no animations
 PASS Returns both animations for an element with two animations 
 PASS Returns only the animations specific to each sibling element 
 PASS Returns only the animations specific to each parent/child element 
-FAIL Does not return finished animations that do not fill forwards assert_array_equals: lengths differ, expected 0 got 1
+PASS Does not return finished animations that do not fill forwards 
 PASS Returns finished animations that fill forwards 
 PASS Returns animations in their delay phase 
-FAIL Returns animations based on dynamic changes to individual animations' duration assert_array_equals: Animation should not be returned when it is finished lengths differ, expected 0 got 1
-FAIL Returns animations based on dynamic changes to individual animations' end delay assert_array_equals: Animation should not be returned after setting a negative end delay such that the end time is less than the current time lengths differ, expected 0 got 1
-FAIL Returns animations based on dynamic changes to individual animations' iteration count assert_array_equals: Animation should not be returned when it is finished lengths differ, expected 0 got 1
-FAIL Returns animations based on dynamic changes to individual animations' current time assert_array_equals: Animation should not be returned after seeking to the clipped end of the active interval lengths differ, expected 0 got 1
+PASS Returns animations based on dynamic changes to individual animations' duration 
+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 
 
index bc76685..f44e33c 100644 (file)
@@ -1,6 +1,6 @@
 
 PASS Test document.getAnimations for non-animated content 
-FAIL Test document.getAnimations for script-generated animations assert_equals: getAnimation only returns running animations expected 0 but got 2
-FAIL Test the order of document.getAnimations with script generated animations assert_array_equals: getAnimations() returns running animations lengths differ, expected 2 got 4
-FAIL Test document.getAnimations with null target assert_equals: document.getAnimations() only returns animations targeting elements in this document expected 0 but got 5
+PASS Test document.getAnimations for script-generated animations 
+PASS Test the order of document.getAnimations with script generated animations 
+PASS Test document.getAnimations with null target 
 
index ccb6570..31fe1d0 100644 (file)
@@ -1,11 +1,9 @@
 <div id="target" style="width: 100px; height: 100px; background-color: black;"></div>
 <script>
 
-const animation = new Animation(new KeyframeEffect(document.getElementById("target"), [
+document.getElementById("target").animate([
     { opacity: 1 },
     { opacity: 0 }
-]));
-animation.startTime = 0;
-animation.effect.timing.duration = 1000;
+], 1000);
 
 </script>
diff --git a/LayoutTests/webanimations/css-animations-expected.txt b/LayoutTests/webanimations/css-animations-expected.txt
new file mode 100644 (file)
index 0000000..782354f
--- /dev/null
@@ -0,0 +1,15 @@
+
+PASS A CSS Animation should be reflected entirely as a CSSAnimation object on the timeline. 
+PASS Web Animations should reflect the animation-delay property. 
+PASS Web Animations should reflect the animation-direction property. 
+PASS Web Animations should reflect the animation-duration property. 
+PASS Web Animations should reflect the animation-fill-mode property. 
+PASS Web Animations should reflect the animation-iteration-count property. 
+PASS Web Animations should reflect the animation-name property. 
+PASS Web Animations should reflect the animation-play-state property. 
+PASS Web Animations should reflect the animation-timing-function property. 
+PASS Calling finish() on the animation no longer lists the animation after it has been running. 
+PASS Seeking the animation to its end time no longer lists the animation after it has been running. 
+PASS Setting the target's animation-name to none no longer lists the animation after it has been running. 
+PASS Setting the target's animation-duration to 0 no longer lists the animation after it has been running. 
+
diff --git a/LayoutTests/webanimations/css-animations.html b/LayoutTests/webanimations/css-animations.html
new file mode 100644 (file)
index 0000000..479772e
--- /dev/null
@@ -0,0 +1,229 @@
+<!DOCTYPE html><!-- webkit-test-runner [ enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations=true ] -->
+<meta charset=utf-8>
+<title>CSS Animations</title>
+<style type="text/css" media="screen">
+
+@keyframes animation {
+    from { left: 50px; }
+    to { left: 100px; }
+}
+
+@keyframes animation-top {
+    from { top: 50px; }
+    to { top: 100px; }
+}
+
+</style>
+<body>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script>
+
+'use strict';
+
+function targetTest(testCallback, description)
+{
+    test(() => {
+        const target = document.body.appendChild(document.createElement("div"));
+        testCallback(target);
+        target.remove();
+    }, description);
+}
+
+targetTest(target => {
+    assert_array_equals(target.getAnimations(), [], "An element should not have any animations initially.");
+
+    target.style.animationDelay = "-3s";
+    target.style.animationDuration = "2s";
+    target.style.animationTimingFunction = "linear";
+    target.style.animationFillMode = "forwards";
+    target.style.animationDirection = "alternate";
+    target.style.animationIterationCount = "2";
+    target.style.animationName = "animation";
+
+    const animation = target.getAnimations()[0];
+    assert_true(animation instanceof Animation, "The animation is an Animation.");
+    assert_true(animation instanceof CSSAnimation, "The animation is a CSSAnimation.");
+    assert_equals(animation.timeline, target.ownerDocument.timeline, "The animation's timeline is set to the element's document timeline.");
+    assert_equals(animation.playState, "running", "The animation is running as soon as it's created.");
+    assert_equals(animation.animationName, "animation", "The animation's animationName is set.");
+
+    const effect = animation.effect;
+    assert_true(effect instanceof KeyframeEffectReadOnly, "The animation's effect is a KeyframeEffectReadOnly.");
+    assert_false(effect instanceof KeyframeEffect, "The animation's effect is not a KeyframeEffect.");
+    assert_equals(effect.target, target, "The animation's effect is targeting the target.");
+
+    const timing = animation.effect.timing;
+    assert_equals(timing.fill, "forwards", "The animation's fill property matches the animation-fill-mode property.");
+    assert_equals(timing.delay, -3000, "The animation's delay property matches the animation-delay property.");
+    assert_equals(timing.duration, 2000, "The animation's duration property matches the animation-duration property.");
+    assert_equals(timing.iterations, 2, "The animation's iterations property matches the animation-iteration-count property.");
+    assert_equals(timing.direction, "alternate", "The animation's direction property matches the animation-direction property.");
+    assert_equals(timing.easing, "linear", "The animation's easing property matches the animation-timing-function property.");
+
+    const computedTiming = animation.effect.getComputedTiming();
+    assert_equals(computedTiming.activeDuration, 4000, "The animations's computed timing activeDuration property matches the properties set by CSS");
+    assert_equals(computedTiming.currentIteration, 1, "The animations's computed timing currentIteration property matches the properties set by CSS");
+    assert_equals(computedTiming.delay, -3000, "The animations's computed timing delay property matches the properties set by CSS");
+    assert_equals(computedTiming.duration, 2000, "The animations's computed timing duration property matches the properties set by CSS");
+    assert_equals(computedTiming.endDelay, 0, "The animations's computed timing endDelay property matches the properties set by CSS");
+    assert_equals(computedTiming.endTime, 1000, "The animations's computed timing endTime property matches the properties set by CSS");
+    assert_equals(computedTiming.iterationStart, 0, "The animations's computed timing iterationStart property matches the properties set by CSS");
+    assert_equals(computedTiming.iterations, 2, "The animations's computed timing iterations property matches the properties set by CSS");
+    assert_equals(computedTiming.localTime, 0, "The animations's computed timing localTime property matches the properties set by CSS");
+    assert_equals(computedTiming.progress, 0.5, "The animations's computed timing progress property matches the properties set by CSS");
+    assert_equals(computedTiming.fill, "forwards", "The animations's computed timing fill property matches the properties set by CSS");
+    assert_equals(computedTiming.easing, "linear", "The animations's computed timing easing property matches the properties set by CSS");
+    assert_equals(computedTiming.direction, "alternate", "The animations's computed timing direction property matches the properties set by CSS");
+
+    const keyframes = animation.effect.getKeyframes();
+    assert_equals(keyframes.length, 2, "The animation's effect has two keyframes.");
+    assert_equals(keyframes[0].offset, 0, "The animation's effect's first keyframe has a 0 offset.");
+    assert_equals(keyframes[0].left, "50px", "The animation's effect's first keyframe has its left property set to 50px.");
+    assert_equals(keyframes[1].offset, 1, "The animation's effect's first keyframe has a 1 offset.");
+    assert_equals(keyframes[1].left, "100px", "The animation's effect's first keyframe has its left property set to 100px.");
+
+    assert_equals(getComputedStyle(effect.target).left, "75px", "The animation's target's computed style reflects the animation state.");
+}, "A CSS Animation should be reflected entirely as a CSSAnimation object on the timeline.");
+
+targetTest(target => {
+    target.style.animationDelay = "-1s";
+    target.style.animationDuration = "2s";
+    target.style.animationName = "animation";
+
+    assert_equals(target.getAnimations()[0].effect.timing.delay, -1000, "The animation's delay matches the initial animation-delay property.");
+
+    target.style.animationDelay = 0;
+    assert_equals(target.getAnimations()[0].effect.timing.delay, 0, "The animation's delay matches the updated animation-delay property.");
+}, "Web Animations should reflect the animation-delay property.");
+
+targetTest(target => {
+    target.style.animationDuration = "2s";
+    target.style.animationName = "animation";
+
+    for (let direction of ["reverse", "alternate", "normal", "alternate-reverse"]) {
+        target.style.animationDirection = direction;
+        assert_equals(target.getAnimations()[0].effect.timing.direction, direction, `The animation's direction matches the "${direction}" CSS value.`);
+    }
+
+    target.style.removeProperty("animation-direction");
+    assert_equals(target.getAnimations()[0].effect.timing.direction, "normal", "The animation's easing matches the default animation-direction value when the property is not set.");
+}, "Web Animations should reflect the animation-direction property.");
+
+targetTest(target => {
+    target.style.animationDuration = "2s";
+    target.style.animationName = "animation";
+
+    assert_equals(target.getAnimations()[0].effect.timing.duration, 2000, "The animation's duration matches the initial animation-duration property.");
+
+    target.style.animationDuration = "1s";
+    assert_equals(target.getAnimations()[0].effect.timing.duration, 1000, "The animation's duration matches the updated animation-duration property.");
+}, "Web Animations should reflect the animation-duration property.");
+
+targetTest(target => {
+    target.style.animationDuration = "2s";
+    target.style.animationName = "animation";
+
+    for (let fillMode of ["forwards", "backwards", "none", "both"]) {
+        target.style.animationFillMode = fillMode;
+        assert_equals(target.getAnimations()[0].effect.timing.fill, fillMode, `The animation's fill mode matches the "${fillMode}" CSS value.`);
+    }
+
+    target.style.removeProperty("animation-fill-mode");
+    assert_equals(target.getAnimations()[0].effect.timing.fill, "none", "The animation's easing matches the default animation-fill-mode value when the property is not set.");
+}, "Web Animations should reflect the animation-fill-mode property.");
+
+targetTest(target => {
+    target.style.animationDuration = "2s";
+    target.style.animationName = "animation";
+
+    target.style.animationIterationCount = 2;
+    const initialAnimation = target.getAnimations()[0];
+    assert_equals(target.getAnimations()[0].effect.timing.iterations, 2, "The animation's duration matches the initial animation-iteration-count property.");
+
+    target.style.animationIterationCount = 1;
+    assert_equals(target.getAnimations()[0].effect.timing.iterations, 1, "The animation's duration matches the updated animation-iteration-count property.");
+
+    assert_equals(target.getAnimations()[0], initialAnimation, "The animation object remained the same instance throughout the test.");
+}, "Web Animations should reflect the animation-iteration-count property.");
+
+targetTest(target => {
+    target.style.animationDuration = "2s";
+    target.style.animationName = "animation";
+
+    const initialAnimation = target.getAnimations()[0];
+    assert_equals(target.getAnimations()[0].animationName, "animation", "The animation's name matches the initial animation-name CSS property.");
+
+    const initialKeyframes = initialAnimation.effect.getKeyframes();
+    assert_equals(initialKeyframes.length, 2);
+    assert_equals(initialKeyframes[0].offset, 0);
+    assert_equals(initialKeyframes[0].left, "50px");
+    assert_equals(initialKeyframes[1].offset, 1);
+    assert_equals(initialKeyframes[1].left, "100px");
+
+    target.style.animationName = "animation-top";
+    const updatedAnimation = target.getAnimations()[0];
+    assert_not_equals(updatedAnimation, initialAnimation, "Changing the animation-name property generates a different CSSAnimation object.");
+    assert_equals(updatedAnimation.animationName, "animation-top", "The animation's name matches the updated animation-name CSS property.");
+
+    const updatedKeyframes = updatedAnimation.effect.getKeyframes();
+    assert_equals(updatedKeyframes.length, 2);
+    assert_equals(updatedKeyframes[0].offset, 0);
+    assert_equals(updatedKeyframes[0].top, "50px");
+    assert_equals(updatedKeyframes[1].offset, 1);
+    assert_equals(updatedKeyframes[1].top, "100px");
+}, "Web Animations should reflect the animation-name property.");
+
+targetTest(target => {
+    target.style.animationDelay = "-1s";
+    target.style.animationDuration = "2s";
+    target.style.animationName = "animation";
+
+    const initialAnimation = target.getAnimations()[0];
+    assert_equals(target.getAnimations()[0].playState, "running", "Setting animation-play-state to running should resume the animation.");
+
+    target.style.animationPlayState = "paused";
+    assert_equals(target.getAnimations()[0].playState, "paused", "Setting animation-play-state back to paused should pause the animation.");
+
+    target.style.removeProperty("animation-play-state");
+    assert_equals(target.getAnimations()[0].playState, "running", "Removing the animation-play-state property should resume the animation.");
+
+    assert_equals(target.getAnimations()[0], initialAnimation, "The animation object remained the same instance throughout the test.");
+}, "Web Animations should reflect the animation-play-state property.");
+
+targetTest(target => {
+    target.style.animationDuration = "2s";
+    target.style.animationTimingFunction = "ease-out";
+    target.style.animationName = "animation";
+
+    assert_equals(target.getAnimations()[0].effect.timing.easing, "ease-out", "The animation's easing matches the initial animation-timing-function property.");
+
+    target.style.animationTimingFunction = "ease-in";
+    assert_equals(target.getAnimations()[0].effect.timing.easing, "ease-in", "The animation's easing matches the updated animation-timing-function property.");
+
+    target.style.removeProperty("animation-timing-function");
+    assert_equals(target.getAnimations()[0].effect.timing.easing, "ease", "The animation's easing matches the default animation-timing-function value when the property is not set.");
+}, "Web Animations should reflect the animation-timing-function property.");
+
+function runAnimationCompletionTest(finalAssertionCallback, description)
+{
+    targetTest(target => {
+        target.style.animationName = "animation";
+        assert_array_equals(target.getAnimations(), [], "Seting the animation-name property alone does not yield a CSSAnimation.");
+
+        target.style.animationDuration = "1s";
+        assert_equals(target.getAnimations().length, 1, "Seting the animation-duration property on top of the animation-name yields a CSSAnimation.");
+        assert_equals(target.getAnimations()[0].playState, "running", "Seting the animation-duration property on top of the animation-name yields a running CSSAnimation.");
+
+        finalAssertionCallback(target.getAnimations()[0]);
+        assert_array_equals(target.getAnimations(), [], `${description} no longer lists the animation.`);
+    }, `${description} no longer lists the animation after it has been running.`);
+}
+
+runAnimationCompletionTest(animation => animation.finish(), "Calling finish() on the animation");
+runAnimationCompletionTest(animation => animation.currentTime = animation.effect.timing.duration, "Seeking the animation to its end time");
+runAnimationCompletionTest(animation => animation.effect.target.style.animationName = "none", "Setting the target's animation-name to none");
+runAnimationCompletionTest(animation => animation.effect.target.style.animationDuration = 0, "Setting the target's animation-duration to 0");
+
+</script>
+</body>
\ No newline at end of file
diff --git a/LayoutTests/webanimations/css-transitions-expected.txt b/LayoutTests/webanimations/css-transitions-expected.txt
new file mode 100644 (file)
index 0000000..8586e7a
--- /dev/null
@@ -0,0 +1,11 @@
+
+PASS A CSS Transition should be reflected entirely as a CSSTransition object on the timeline. 
+PASS Web Animations should reflect the transition-delay property. 
+PASS Web Animations should reflect the transition-duration property. 
+PASS Web Animations should reflect the transition-property property. 
+PASS Web Animations should reflect the transition-timing-function property. 
+PASS Calling finish() on the animation no longer lists the animation after it has been running. 
+PASS Seeking the animation to its end time no longer lists the animation after it has been running. 
+PASS Setting the target's transition-property to none no longer lists the animation after it has been running. 
+PASS Seeking the target's transition-duration to 0 no longer lists the animation after it has been running. 
+
diff --git a/LayoutTests/webanimations/css-transitions.html b/LayoutTests/webanimations/css-transitions.html
new file mode 100644 (file)
index 0000000..7be8607
--- /dev/null
@@ -0,0 +1,192 @@
+<!DOCTYPE html><!-- webkit-test-runner [ enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations=true ] -->
+<meta charset=utf-8>
+<title>CSS Transitions</title>
+<body>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script>
+
+'use strict';
+
+function targetTest(testCallback, description)
+{
+    test(() => {
+        const target = document.body.appendChild(document.createElement("div"));
+        testCallback(target);
+        target.remove();
+    }, description);
+}
+
+function forceStyleUpdate(element)
+{
+    element.offsetLeft;
+}
+
+targetTest(target => {
+    target.style.left = "50px";
+
+    assert_array_equals(target.getAnimations(), [], "An element should not have any animations initially.");
+
+    target.style.transitionProperty = "left";
+    target.style.transitionDelay = "-1s";
+    target.style.transitionDuration = "2s";
+    target.style.transitionTimingFunction = "linear";
+
+    assert_array_equals(target.getAnimations(), [], "Setting CSS transitions properties should not yield a CSSTransition until a new target value is specified.");
+
+    forceStyleUpdate(target);
+    target.style.left = "100px";
+
+    const animation = target.getAnimations()[0];
+    assert_true(animation instanceof CSSTransition, "The animation is a CSSTransition.");
+    assert_true(animation instanceof Animation, "The animation is an Animation.");
+    assert_equals(animation.timeline, target.ownerDocument.timeline, "The animation's timeline is set to the element's document timeline.");
+    assert_equals(animation.playState, "running", "The animation is running as soon as it's created.");
+    assert_equals(animation.transitionProperty, "left", "The animation's transitionProperty is set.");
+
+    const effect = animation.effect;
+    assert_true(effect instanceof KeyframeEffectReadOnly, "The animation's effect is a KeyframeEffectReadOnly.");
+    assert_false(effect instanceof KeyframeEffect, "The animation's effect is not a KeyframeEffect.");
+    assert_equals(effect.target, target, "The animation's effect is targeting the target.");
+
+    const timing = animation.effect.timing;
+    assert_equals(timing.delay, -1000, "The animation's delay property matches the transition-delay property.");
+    assert_equals(timing.duration, 2000, "The animation's duration property matches the transition-duration property.");
+    assert_equals(timing.easing, "linear", "The animation's easing property matches the transition-timing-function property.");
+
+    const computedTiming = animation.effect.getComputedTiming();
+    assert_equals(computedTiming.activeDuration, 2000, "The animations's computed timing activeDuration property matches the properties set by CSS");
+    assert_equals(computedTiming.currentIteration, 0, "The animations's computed timing currentIteration property matches the properties set by CSS");
+    assert_equals(computedTiming.delay, -1000, "The animations's computed timing delay property matches the properties set by CSS");
+    assert_equals(computedTiming.duration, 2000, "The animations's computed timing duration property matches the properties set by CSS");
+    assert_equals(computedTiming.endDelay, 0, "The animations's computed timing endDelay property matches the properties set by CSS");
+    assert_equals(computedTiming.endTime, 1000, "The animations's computed timing endTime property matches the properties set by CSS");
+    assert_equals(computedTiming.iterationStart, 0, "The animations's computed timing iterationStart property matches the properties set by CSS");
+    assert_equals(computedTiming.iterations, 1, "The animations's computed timing iterations property matches the properties set by CSS");
+    assert_equals(computedTiming.localTime, 0, "The animations's computed timing localTime property matches the properties set by CSS");
+    assert_equals(computedTiming.progress, 0.5, "The animations's computed timing progress property matches the properties set by CSS");
+    assert_equals(computedTiming.fill, "none", "The animations's computed timing fill property matches the properties set by CSS");
+    assert_equals(computedTiming.easing, "linear", "The animations's computed timing easing property matches the properties set by CSS");
+    assert_equals(computedTiming.direction, "normal", "The animations's computed timing direction property matches the properties set by CSS");
+
+    const keyframes = animation.effect.getKeyframes();
+    assert_equals(keyframes.length, 2, "The animation's effect has two keyframes.");
+    assert_equals(keyframes[0].offset, 0, "The animation's effect's first keyframe has a 0 offset.");
+    assert_equals(keyframes[0].left, "50px", "The animation's effect's first keyframe has its left property set to 50px.");
+    assert_equals(keyframes[1].offset, 1, "The animation's effect's first keyframe has a 1 offset.");
+    assert_equals(keyframes[1].left, "100px", "The animation's effect's first keyframe has its left property set to 100px.");
+
+    assert_equals(getComputedStyle(effect.target).left, "75px", "The animation's target's computed style reflects the animation state.");
+}, "A CSS Transition should be reflected entirely as a CSSTransition object on the timeline.");
+
+function transitionProperty(target, propertyName, from, to)
+{
+    target.style[propertyName] = from;
+    forceStyleUpdate(target);
+    target.style[propertyName] = to;
+}
+
+function transitionPropertyUpdateTest(testCallback, description)
+{
+    targetTest(target => {
+        target.style.transitionProperty = "left";
+        target.style.transitionDelay = "-500ms";
+        target.style.transitionDuration = "2s";
+        transitionProperty(target, "left", "50px", "100px");
+        testCallback(target);
+    }, description);
+}
+
+transitionPropertyUpdateTest(target => {
+    const initialAnimation = target.getAnimations()[0];
+    assert_equals(initialAnimation.effect.timing.delay, -500, "The animation's delay matches the initial transition-delay property.");
+
+    target.style.transitionDelay = 0;
+    const updatedAnimation = target.getAnimations()[0];
+    assert_equals(updatedAnimation.effect.timing.delay, 0, "The animation's delay matches the updated transition-delay property.");
+
+    assert_not_equals(initialAnimation, updatedAnimation, "The animations before and after updating the transition-delay property differ.");
+}, "Web Animations should reflect the transition-delay property.");
+
+transitionPropertyUpdateTest(target => {
+    const initialAnimation = target.getAnimations()[0];
+    assert_equals(initialAnimation.effect.timing.duration, 2000, "The animation's duration matches the initial transition-duration property.");
+
+    target.style.transitionDuration = "1s";
+    const updatedAnimation = target.getAnimations()[0];
+    assert_equals(updatedAnimation.effect.timing.duration, 1000, "The animation's duration matches the updated transition-duration property.");
+
+    assert_not_equals(initialAnimation, updatedAnimation, "The animations before and after updating the transition-duration property differ.");
+}, "Web Animations should reflect the transition-duration property.");
+
+targetTest(target => {
+    target.style.transitionDuration = "2s";
+
+    target.style.transitionProperty = "left";
+    transitionProperty(target, "left", "50px", "100px");
+
+    const initialAnimation = target.getAnimations()[0];
+    assert_equals(target.getAnimations()[0].transitionProperty, "left", "The animation's property matches the initial transition-property CSS property.");
+
+    const initialKeyframes = initialAnimation.effect.getKeyframes();
+    assert_equals(initialKeyframes.length, 2);
+    assert_equals(initialKeyframes[0].offset, 0);
+    assert_equals(initialKeyframes[0].left, "50px");
+    assert_equals(initialKeyframes[1].offset, 1);
+    assert_equals(initialKeyframes[1].left, "100px");
+
+    target.style.transitionProperty = "top";
+    transitionProperty(target, "top", "50px", "100px");
+
+    const updatedAnimation = target.getAnimations()[0];
+    assert_equals(updatedAnimation.transitionProperty, "top", "The animation's property matches the updated transition-property CSS property.");
+
+    const updatedKeyframes = updatedAnimation.effect.getKeyframes();
+    assert_equals(updatedKeyframes.length, 2);
+    assert_equals(updatedKeyframes[0].offset, 0);
+    assert_equals(updatedKeyframes[0].top, "50px");
+    assert_equals(updatedKeyframes[1].offset, 1);
+    assert_equals(updatedKeyframes[1].top, "100px");
+
+    assert_not_equals(updatedAnimation, initialAnimation, "Changing the transition-property property generates a different CSSTransition object.");
+}, "Web Animations should reflect the transition-property property.");
+
+transitionPropertyUpdateTest(target => {
+    const initialAnimation = target.getAnimations()[0];
+    assert_equals(initialAnimation.effect.timing.easing, "ease", "The animation's easing matches the default transition-timing-function property.");
+
+    target.style.transitionTimingFunction = "ease-in";
+    const updatedAnimation = target.getAnimations()[0];
+    assert_equals(updatedAnimation.effect.timing.easing, "ease-in", "The animation's easing matches the updated transition-timing-function property.");
+
+    target.style.removeProperty("transition-timing-function");
+    const finalAnimation = target.getAnimations()[0];
+    assert_equals(target.getAnimations()[0].effect.timing.easing, "ease", "The animation's easing matches the default transition-timing-function value when the property is not set.");
+
+    assert_not_equals(initialAnimation, updatedAnimation, "The animations before and after updating the transition-timing-function property differ.");
+    assert_not_equals(updatedAnimation, finalAnimation, "The animations before and after updating the transition-timing-function property differ.");
+}, "Web Animations should reflect the transition-timing-function property.");
+
+function runAnimationCompletionTest(finalAssertionCallback, description)
+{
+    targetTest(target => {
+        target.style.transitionProperty = "left";
+        target.style.transitionDuration = "1s";
+        transitionProperty(target, "left", "50px", "100px");
+
+        assert_equals(target.getAnimations().length, 1, "Seting the transition-duration property on top of the transition-property yields a CSSTransition.");
+        assert_equals(target.getAnimations()[0].playState, "running", "Seting the transition-duration property on top of the transition-property yields a running CSSTransition.");
+
+        finalAssertionCallback(target.getAnimations()[0]);
+
+        assert_array_equals(target.getAnimations(), [], `${description} no longer lists the animation.`);
+    }, `${description} no longer lists the animation after it has been running.`);
+}
+
+runAnimationCompletionTest(animation => animation.finish(), "Calling finish() on the animation");
+runAnimationCompletionTest(animation => animation.currentTime = animation.effect.timing.duration, "Seeking the animation to its end time");
+runAnimationCompletionTest(animation => animation.effect.target.style.transitionProperty = "none", "Setting the target's transition-property to none");
+runAnimationCompletionTest(animation => animation.effect.target.style.transitionDuration = 0, "Seeking the target's transition-duration to 0");
+
+</script>
+</body>
\ No newline at end of file
index 9126f33..e6148c2 100644 (file)
@@ -2,12 +2,10 @@
 <div id="target" style="width: 100px; height: 100px; background-color: black;"></div>
 <script>
 
-const animation = new Animation(new KeyframeEffect(document.getElementById("target"), [
+const animation = document.getElementById("target").animate([
     { opacity: 1 },
     { opacity: 0 }
-]));
-animation.startTime = 0;
-animation.effect.timing.duration = 100;
+], 100);
 
 testRunner.waitUntilDone();
 testRunner.dumpAsText();
index 9932c87..a0782ae 100644 (file)
@@ -2,12 +2,10 @@
 <div id="target" style="width: 100px; height: 100px; background-color: black;"></div>
 <script>
 
-const animation = new Animation(new KeyframeEffect(document.getElementById("target"), [
+document.getElementById("target").animate([
     { opacity: 1 },
     { opacity: 0 }
-]));
-animation.startTime = 0;
-animation.effect.timing.duration = 1000;
+], 1000);
 
 testRunner.dumpAsText();
 document.getElementById("results").innerText = internals.layerTreeAsText(document);
index 5f2749d..4f31084 100644 (file)
@@ -1,3 +1,165 @@
+2018-03-12  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement CSS Animations and CSS Transitions as Web Animations
+        https://bugs.webkit.org/show_bug.cgi?id=183504
+        <rdar://problem/38372965>
+
+        Reviewed by Dean Jackson and Jon Lee.
+
+        Tests: webanimations/css-animations.html
+               webanimations/css-transitions.html
+
+        This patch implements CSS Animations and CSS Transitions as Web Animations. The main changes are:
+
+        * StyleTreeResolver: StyleTreeResolver now has a code path to add CSSAnimation and CSSTransition objects onto the DocumentTimeline
+        to be picked up by the Web Animations engine. The previous CSSAnimationController code path is preserved if the runtime flag is disabled.
+
+        * AnimationTimeline: we add two new methods, updateCSSAnimationsForElement() and updateCSSTransitionsForElement() which are called from
+        TreeResolver::createAnimatedElementUpdate(). These look at the AnimationList for the old and new RenderStyle objects and create, update
+        and remove matching CSSAnimation and CSSTransition instances.
+
+        * DeclarativeAnimation: a new superclass to both CSSAnimation and CSSTransition which introduces the concept of a backingAnimation(),
+        which is an Animation held by the RenderStyle objects, and two virtual methods with base implementations, initialize() which is called
+        upon creating by create() methods in subclasses, and syncPropertiesWithBackingAnimation() which ensures that properties on the
+        DeclarativeAnimation objects (Web Animations side) match the backing animation (CSS side).
+
+        * KeyframeEffectReadOnly: two new important methods to create blending keyframes (KeyframeList) based on backing Animation objects,
+        computeCSSAnimationBlendingKeyframes() and computeCSSTransitionBlendingKeyframes().
+
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * animation/AnimationEffectReadOnly.h:
+        (WebCore::AnimationEffectReadOnly::isKeyframeEffectReadOnly const): We fix this method such that calling it on a KeyframeEffect, which
+        is a subclass of KeyframeEffectReadOnly, returns true.
+        * animation/AnimationEffectTimingReadOnly.cpp: In order for DeclarativeAnimation::syncPropertiesWithBackingAnimation() to set the timing
+        function for a declarative animation's effect, we need a public method to set an effect's timing function outside of just the "easing"
+        property setter exposed via the JS API. So we introduce a setTimingFunction() method and call it from setEasing().
+        (WebCore::AnimationEffectTimingReadOnly::setEasing):
+        (WebCore::AnimationEffectTimingReadOnly::setTimingFunction):
+        * animation/AnimationEffectTimingReadOnly.h:
+        * animation/AnimationTimeline.cpp:
+        (WebCore::AnimationTimeline::~AnimationTimeline): Clear all maps and sets containing WebAnimation references to ensure these get destructed
+        when the AnimationTimeline is being destructed and should no longer hold a reference to them.
+        (WebCore::AnimationTimeline::relevantMapForAnimation): We store various subclasses of WebAnimation in dedicated maps so we can composite
+        animations in the correct order when animating. This function returns the correct map for a given animation such that animationWasAddedToElement()
+        and animationWasRemovedFromElement() mutate the right map.
+        (WebCore::AnimationTimeline::animationWasAddedToElement):
+        (WebCore::AnimationTimeline::animationWasRemovedFromElement):
+        (WebCore::AnimationTimeline::animationsForElement): Make sure to look for animations in the lists of CSS Animations and CSS Transitions as well
+        as Web Animations.
+        (WebCore::AnimationTimeline::updateCSSAnimationsForElement): This method is called by TreeResolver::createAnimatedElementUpdate() during style
+        resolution. It compares the AnimationList of the previous style and the new style for a given element, checks that animations with a given name
+        that were not present in the old AnimationList have a new matching CSSAnimation object for them added to the AnimationTimeline, that animations
+        with a given name that are no longer present in the new AnimationList have their matching CSSAnimation object removed from the AnimationTimeline,
+        and that animations with a given name that are present in both the old and new AnimationList have their matching CSSAnimation updated to match
+        the current state of the animation in the AnimationList.
+        (WebCore::AnimationTimeline::updateCSSTransitionsForElement): Similarly to updateCSSAnimationsForElement(), this method is called during style
+        resolution by TreeResolver::createAnimatedElementUpdate(). Its role is to create or remove CSSTransition objects based on the AnimationList found
+        in the old and new styles for a given element. It follows a slightly different logic than updateCSSAnimationsForElement() since for CSS Transitions,
+        there is no need to update CSSTransition objects for a CSS property existing in both the old and new AnimationList, since when a CSS transitions
+        property is changed, a whole new transition is initiated. However, it's important to check that different Animation objects and styles would actually
+        result in different timing properties and blending keyframes, so check for this as well before creating new CSSTransition objects.
+        * animation/AnimationTimeline.h:
+        (WebCore::AnimationTimeline::animations const): Change the m_animations type from HashSet to ListHashSet to guarantee we preserve the insertion order which is
+        required by getAnimations().
+        (WebCore::AnimationTimeline::hasElementAnimations const): Indicates to DocumentTimeline::updateAnimations() that there are animations targeting the provided element.
+        (WebCore::AnimationTimeline::elementToAnimationsMap):
+        (WebCore::AnimationTimeline::elementToCSSAnimationsMap):
+        (WebCore::AnimationTimeline::elementToCSSTransitionsMap):
+        * animation/CSSAnimation.cpp: CSSAnimation is now a subclass of DeclarativeAnimation and subclasses initialize() and syncPropertiesWithBackingAnimation()
+        to perform work specific to CSS Animations.
+        (WebCore::CSSAnimation::create): Set the animationName property based on the provided backing animation.
+        (WebCore::CSSAnimation::CSSAnimation):
+        (WebCore::CSSAnimation::initialize): Create the blending keyframes for this CSSAnimation.
+        (WebCore::CSSAnimation::syncPropertiesWithBackingAnimation): Reflect the animation-fill-mode, animation-direction, animation-iteration-count and
+        animation-play-state CSS properties on the AnimationEffectTimingReadOnly object associated with this CSSAnimation.
+        * animation/CSSAnimation.h:
+        * animation/CSSTransition.cpp: CSSTransition is now a subclass of DeclarativeAnimation.
+        (WebCore::CSSTransition::create): Set the transitionProperty property based on the provided backing animation.
+        (WebCore::CSSTransition::CSSTransition):
+        (WebCore::CSSTransition::matchesBackingAnimationAndStyles const):
+        (WebCore::CSSTransition::canBeListed const): Subclass this method such that we also check that we have blending keyframes for a CSSTransition to be
+        listed by calls to getAnimations().
+        * animation/CSSTransition.h:
+        * animation/DeclarativeAnimation.cpp: Added. This new WebAnimation subclass now is the common base class for both CSSAnimation and CSSTransition.
+        It establishes a relationship with a "backing animation", which is an Animation obtained from a style's AnimationList while resolving styles.
+        These backing animations contain all of the parsed CSS styles related to CSS Animations and CSS Transitions and we use those to set matching properties
+        of the Web Animations timing model in the new syncPropertiesWithBackingAnimation() virtual method, which subclasses can override to perform further
+        work that is specific to a given declarative animation type. The initialize() method is called during create() methods to perform common animation
+        setup work. Note that while both initialize() and syncPropertiesWithBackingAnimation() are called, we suspend invalidation to that animation's effect
+        since these methods are meant to be called during style invalidation and we would hit an assertion if we followed the usual route of calling
+        updateStyleIfNeeded() on the target's document during invalidation.
+        (WebCore::DeclarativeAnimation::DeclarativeAnimation):
+        (WebCore::DeclarativeAnimation::setBackingAnimation):
+        (WebCore::DeclarativeAnimation::initialize): Create a KeyframeEffectReadOnly for this animation and set the provided element as its target, set that
+        element's document's timeline and play the animation if the backing animation's play state is playing.
+        (WebCore::DeclarativeAnimation::syncPropertiesWithBackingAnimation): Reflect the {animation|transition}-delay, {animation|transition}-duration and
+        {animation|transition}-timing-function properties as set on the backing animation.
+        * animation/DeclarativeAnimation.h: Added.
+        (WebCore::DeclarativeAnimation::backingAnimation const):
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::updateAnimations): Trigger style invalidation for elements targeted not just by WebAnimation instances, but also by any
+        of the DeclarativeAnimation subclasses. We also remove the call to updateFinishedState() which should have been removed when we implemented correct
+        support for asynchronous WebAnimation operations.
+        (WebCore::DocumentTimeline::animatedStyleForRenderer): Declarative animations are backed by KeyframeEffectReadOnly effects, so make sure we check
+        for KeyframeEffectReadOnly or one of its subclasses and not just KeyframeEffect since there now are animation types that use the ReadOnly variant.
+        (WebCore::DocumentTimeline::runningAnimationsForElementAreAllAccelerated): Same as for animatedStyleForRenderer, check for KeyframeEffectReadOnly
+        and not simply KeyframeEffect.
+        * animation/KeyframeEffectReadOnly.cpp:
+        (WebCore::invalidateElement): Stop forcing a style resolution as we invalidate element, marking them as dirty is sufficient. Calls to getAnimations()
+        already force a style resolution as needed.
+        (WebCore::KeyframeEffectReadOnly::create): Add a new create() method that only provides a target and which is used by DeclarativeAnimation::initialize().
+        (WebCore::KeyframeEffectReadOnly::getKeyframes): The previous implementation of getKeyframes() used the ParsedKeyframe list held as m_parsedKeyframes
+        to compute keyframes. In the case of declarative animations, there are no ParsedKeyframe since the JS API was not involved, so we use the blending keyframes
+        to look for keyframe data.
+        (WebCore::KeyframeEffectReadOnly::computeCSSAnimationBlendingKeyframes): Called by CSSAnimation::initialize(), this function creates blending keyframes by
+        looking up the keyframes date obtained from the @keyframes rule with this backing animation's name.
+        (WebCore::KeyframeEffectReadOnly::computeCSSTransitionBlendingKeyframes): Called by CSSTransition::create(), this function creates blending keyframes by
+        creating a 0-offset keyframe with the old style and a 1-offset keyframe with the new style as provided during TreeResolver::createAnimatedElementUpdate().
+        (WebCore::KeyframeEffectReadOnly::stylesWouldYieldNewCSSTransitionsBlendingKeyframes const): Called by AnimationTimeline::updateCSSTransitionsForElement()
+        to check that a provided backing Animation and a pair of old and new RenderStyles that may be different objects actually would yield different timing
+        properties and keyframe CSS values for a given CSS transition to avoid the deletion and creation of CSSTransition objects.
+        (WebCore::KeyframeEffectReadOnly::shouldRunAccelerated): We mistakenly assumed we always had blending keyframes, which is not always the case with a
+        CSSTransition where the transition style itself might be set first, but the target value after. So we should only run accelerated provided there are blending
+        keyframes at least, the function already returning false if it finds a blending keyframe animating a non-accelerated CSS property.
+        (WebCore::KeyframeEffectReadOnly::setAnimatedPropertiesInStyle): Check that there actually is a matching ParsedKeyframe to read the timing function from.
+        * animation/KeyframeEffectReadOnly.h:
+        (WebCore::KeyframeEffectReadOnly::hasBlendingKeyframes const):
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::~WebAnimation): We used to do something very wrong when a WebAnimation was destroyed which uncovered crashes when dealing with
+        declarative animations. In AnimationTimeline's updateCSSAnimationsForElement() and updateCSSTransitionsForElement(), when we identify that a DeclarativeAnimation
+        no longer matches an Animation from the current style's AnimationList, we set that DeclarativeAnimation's effect to null and call removeAnimation() on
+        the timeline. This removes all references from AnimationTimeline to this DeclarativeAnimation and leads to ~WebAnimation being called. Calling removeAnimation()
+        again in the destructor means that we'd hit ASSERT_WITH_SECURITY_IMPLICATION(!m_deletionHasBegun) in ref(). It was also meaningless to perform this work in
+        the WebAnimation destructor since an animation could never be destroyed if it were still registered on a timeline.
+        (WebCore::WebAnimation::suspendEffectInvalidation): DeclarativeAnimation instances have their timing model properties set during style invalidation, so we need
+        a mechanism to allow the usual effect invalidation to be suspended in this case. We now maintain a simple m_suspendCount count that increases and decreases with
+        calls to this method and unsuspendEffectInvalidation() and a isEffectInvalidationSuspended() method returning true whenever that count is positive.
+        (WebCore::WebAnimation::unsuspendEffectInvalidation):
+        (WebCore::WebAnimation::timingModelDidChange): Check that effect invalidation is not suspended before proceeding with invalidating the effect.
+        (WebCore::WebAnimation::setEffect): Check for KeyframeEffectReadOnly and not just KeyframeEffect since declarative animations have ReadOnly effects.
+        (WebCore::WebAnimation::setTimeline): Check for KeyframeEffectReadOnly and not just KeyframeEffect since declarative animations have ReadOnly effects.
+        (WebCore::WebAnimation::scheduleMicrotaskIfNeeded): Ensure that the WebAnimation's lifecycle is extended at least to the completion of the scheduled microtask.
+        This would otherwise cause crashes after declarative animations were destroyed when they were no longer applied.
+        (WebCore::WebAnimation::runPendingPlayTask): Only fulfill the "ready" promise if it hasn't already been, which might have been the case if multiple calls to play()
+        are made as a result of updating the animation play state in CSSAnimation::syncPropertiesWithBackingAnimation().
+        (WebCore::WebAnimation::runPendingPauseTask): Same as above but with multiple pause() calls.
+        (WebCore::WebAnimation::startOrStopAccelerated): Check for KeyframeEffectReadOnly and not just KeyframeEffect since declarative animations have ReadOnly effects.
+        (WebCore::WebAnimation::canBeListed const): This new method is called by {Document|Element}::getAnimations() to check that an animation is in the correct state to
+        be listed. The Web Animations spec explains that only animations "that have an associated target effect which is current or in effect" can be listed. We implement
+        this behavior as specified.
+        * animation/WebAnimation.h:
+        (WebCore::WebAnimation::isDeclarativeAnimation const):
+        (WebCore::WebAnimation::isEffectInvalidationSuspended):
+        * dom/Document.cpp:
+        (WebCore::Document::getAnimations): Ensure that the document's pending styles are resolved before returning animations to ensure that any pending declarative
+        animations are created. Additionally, we ensure that we only list qualifying animations that have effects targeting elements that are children of thi document.
+        * dom/Element.cpp:
+        (WebCore::Element::getAnimations): Same as Document::getAnimations().
+        * style/StyleTreeResolver.cpp:
+        (WebCore::Style::TreeResolver::createAnimatedElementUpdate): When resolving styles, call into the AnimationTimeline if the runtime flag to enable CSS Animations and
+        CSS Transitions as Web Animations is on. Otherwise, use CSSAnimationController.
+
 2018-03-12  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         [GTK] Crash in WebCore::PlatformDisplayWayland::~PlatformDisplayWayland
index 20004b6..9f2aecb 100644 (file)
@@ -338,6 +338,7 @@ animation/AnimationPlaybackEvent.cpp
 animation/AnimationTimeline.cpp
 animation/CSSAnimation.cpp
 animation/CSSTransition.cpp
+animation/DeclarativeAnimation.cpp
 animation/DocumentTimeline.cpp
 animation/KeyframeEffect.cpp
 animation/KeyframeEffectReadOnly.cpp
index 25373ca..322a362 100644 (file)
                71556CB41F9F09BA00E78D08 /* KeyframeEffect.h in Headers */ = {isa = PBXBuildFile; fileRef = 71556CAA1F9F099B00E78D08 /* KeyframeEffect.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71556CBD1F9F0A4900E78D08 /* JSAnimationEffectTiming.h in Headers */ = {isa = PBXBuildFile; fileRef = 71556CB91F9F09FD00E78D08 /* JSAnimationEffectTiming.h */; };
                71556CBE1F9F0A4900E78D08 /* JSKeyframeEffect.h in Headers */ = {isa = PBXBuildFile; fileRef = 71556CB71F9F09FC00E78D08 /* JSKeyframeEffect.h */; };
+               715AD7202050513200D592DC /* DeclarativeAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 715AD71D2050512400D592DC /* DeclarativeAnimation.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               715AD7212050513F00D592DC /* CSSTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 7123C186204739BA00789392 /* CSSTransition.h */; };
                71A1B6081DEE5AD70073BCFB /* modern-media-controls-localized-strings.js in Resources */ = {isa = PBXBuildFile; fileRef = 71A1B6061DEE5A820073BCFB /* modern-media-controls-localized-strings.js */; };
                71A57DF2154BE25C0009D120 /* SVGPathUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 71A57DF0154BE25C0009D120 /* SVGPathUtilities.h */; };
                71B28427203CEC4C0036AA5D /* JSCSSAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B28426203CEC0D0036AA5D /* JSCSSAnimation.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71556CBB1F9F09FE00E78D08 /* JSAnimationEffectTiming.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSAnimationEffectTiming.cpp; sourceTree = "<group>"; };
                7157E3D11DC1EE4B0094550E /* scrubbing-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "scrubbing-support.js"; sourceTree = "<group>"; };
                7157F061150B6564006EAABD /* SVGAnimatedTransformList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SVGAnimatedTransformList.cpp; sourceTree = "<group>"; };
+               715AD71D2050512400D592DC /* DeclarativeAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DeclarativeAnimation.h; sourceTree = "<group>"; };
+               715AD71F2050512400D592DC /* DeclarativeAnimation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DeclarativeAnimation.cpp; sourceTree = "<group>"; };
                715DA5D3201BB902002EF2B0 /* JSWebAnimationCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSWebAnimationCustom.cpp; sourceTree = "<group>"; };
                716C8DF11E48B269005BD0DA /* volume-down-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "volume-down-support.js"; sourceTree = "<group>"; };
                716C8DF21E48B269005BD0DA /* volume-up-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "volume-up-support.js"; sourceTree = "<group>"; };
                                7123C187204739BB00789392 /* CSSTransition.cpp */,
                                7123C186204739BA00789392 /* CSSTransition.h */,
                                7123C185204739B900789392 /* CSSTransition.idl */,
+                               715AD71F2050512400D592DC /* DeclarativeAnimation.cpp */,
+                               715AD71D2050512400D592DC /* DeclarativeAnimation.h */,
                                71025EC41F99F096004A250C /* DocumentTimeline.cpp */,
                                71025EC51F99F096004A250C /* DocumentTimeline.h */,
                                71025ECA1F99F096004A250C /* DocumentTimeline.idl */,
                                946D373C1D6CDFC00077084F /* CSSTokenizerInputStream.h in Headers */,
                                9AB1F38018E2489A00534743 /* CSSToLengthConversionData.h in Headers */,
                                A882DA231593848D000115ED /* CSSToStyleMap.h in Headers */,
+                               715AD7212050513F00D592DC /* CSSTransition.h in Headers */,
                                371F53E90D2704F900ECE0D5 /* CSSUnicodeRangeValue.h in Headers */,
                                DD7CDF250A23CF9800069928 /* CSSUnknownRule.h in Headers */,
                                BC7D8FF01BD03B6400FFE540 /* CSSUnsetValue.h in Headers */,
                                F55B3DBA1251F12D003EF269 /* DateTimeLocalInputType.h in Headers */,
                                0F6A12BE1A00923700C6DE72 /* DebugPageOverlays.h in Headers */,
                                45FEA5D0156DDE8C00654101 /* Decimal.h in Headers */,
+                               715AD7202050513200D592DC /* DeclarativeAnimation.h in Headers */,
                                A8C228A111D5722E00D5A7D3 /* DecodedDataDocumentParser.h in Headers */,
                                CECCFC3B141973D5002A0AC1 /* DecodeEscapeSequences.h in Headers */,
                                555130011E7CCCCB00A69E38 /* DecodingOptions.h in Headers */,
index 630feaf..98bde84 100644 (file)
@@ -41,7 +41,7 @@ public:
     virtual ~AnimationEffectReadOnly();
 
     bool isKeyframeEffect() const { return m_classType == KeyframeEffectClass; }
-    bool isKeyframeEffectReadOnly() const { return m_classType == KeyframeEffectReadOnlyClass; }
+    bool isKeyframeEffectReadOnly() const { return isKeyframeEffect() || m_classType == KeyframeEffectReadOnlyClass; }
     AnimationEffectTimingReadOnly* timing() const { return m_timing.get(); }
     ComputedTimingProperties getComputedTiming();
     virtual void apply(RenderStyle&) = 0;
index bae3f35..154f378 100644 (file)
@@ -170,8 +170,7 @@ ExceptionOr<void> AnimationEffectTimingReadOnly::setEasing(const String& easing)
     auto timingFunctionResult = TimingFunction::createFromCSSText(easing);
     if (timingFunctionResult.hasException())
         return timingFunctionResult.releaseException();
-    m_timingFunction = timingFunctionResult.returnValue();
-    propertyDidChange();
+    setTimingFunction(timingFunctionResult.returnValue());
     return { };
 }
 
@@ -230,6 +229,12 @@ void AnimationEffectTimingReadOnly::setDirection(PlaybackDirection direction)
     propertyDidChange();
 }
 
+void AnimationEffectTimingReadOnly::setTimingFunction(const RefPtr<TimingFunction>& timingFunction)
+{
+    m_timingFunction = timingFunction;
+    propertyDidChange();
+}
+
 Seconds AnimationEffectTimingReadOnly::activeDuration() const
 {
     // 3.8.2. Calculating the active duration
index afbf793..4749cf9 100644 (file)
@@ -87,6 +87,8 @@ public:
     ExceptionOr<void> setEasing(const String&);
 
     TimingFunction* timingFunction() const { return m_timingFunction.get(); }
+    void setTimingFunction(const RefPtr<TimingFunction>&);
+
     Seconds endTime() const;
     Seconds activeDuration() const;
 
index f3a1305..1b1061d 100644 (file)
 #include "config.h"
 #include "AnimationTimeline.h"
 
+#include "Animation.h"
+#include "AnimationList.h"
+#include "CSSAnimation.h"
+#include "CSSPropertyAnimation.h"
+#include "CSSTransition.h"
 #include "DocumentTimeline.h"
+#include "Element.h"
+#include "KeyframeEffectReadOnly.h"
+#include "RenderStyle.h"
+#include "RenderView.h"
 #include "WebAnimationUtilities.h"
 #include <wtf/text/TextStream.h>
 #include <wtf/text/WTFString.h>
@@ -41,6 +50,12 @@ AnimationTimeline::AnimationTimeline(ClassType classType)
 
 AnimationTimeline::~AnimationTimeline()
 {
+    m_animations.clear();
+    m_elementToAnimationsMap.clear();
+    m_elementToCSSAnimationsMap.clear();
+    m_elementToCSSTransitionsMap.clear();
+    m_elementToCSSAnimationByName.clear();
+    m_elementToCSSTransitionByCSSPropertyID.clear();
 }
 
 void AnimationTimeline::addAnimation(Ref<WebAnimation>&& animation)
@@ -69,34 +84,174 @@ void AnimationTimeline::setCurrentTime(Seconds currentTime)
     timingModelDidChange();
 }
 
+HashMap<Element*, Vector<RefPtr<WebAnimation>>>& AnimationTimeline::relevantMapForAnimation(WebAnimation& animation)
+{
+    if (animation.isCSSAnimation())
+        return m_elementToCSSAnimationsMap;
+    if (animation.isCSSTransition())
+        return m_elementToCSSTransitionsMap;
+    return m_elementToAnimationsMap;
+}
+
 void AnimationTimeline::animationWasAddedToElement(WebAnimation& animation, Element& element)
 {
-    auto result = m_elementToAnimationsMap.ensure(&element, [] {
-        return Vector<RefPtr<WebAnimation>>();
+    auto result = relevantMapForAnimation(animation).ensure(&element, [] {
+        return Vector<RefPtr<WebAnimation>> { };
     });
     result.iterator->value.append(&animation);
 }
 
 void AnimationTimeline::animationWasRemovedFromElement(WebAnimation& animation, Element& element)
 {
-    auto iterator = m_elementToAnimationsMap.find(&element);
-    if (iterator == m_elementToAnimationsMap.end())
+    auto& map = relevantMapForAnimation(animation);
+    auto iterator = map.find(&element);
+    if (iterator == map.end())
         return;
 
     auto& animations = iterator->value;
     animations.removeFirst(&animation);
     if (!animations.size())
-        m_elementToAnimationsMap.remove(iterator);
+        map.remove(iterator);
 }
 
 Vector<RefPtr<WebAnimation>> AnimationTimeline::animationsForElement(Element& element)
 {
     Vector<RefPtr<WebAnimation>> animations;
+    if (m_elementToCSSAnimationsMap.contains(&element))
+        animations.appendVector(m_elementToCSSAnimationsMap.get(&element));
+    if (m_elementToCSSTransitionsMap.contains(&element))
+        animations.appendVector(m_elementToCSSTransitionsMap.get(&element));
     if (m_elementToAnimationsMap.contains(&element))
-        animations = m_elementToAnimationsMap.get(&element);
+        animations.appendVector(m_elementToAnimationsMap.get(&element));
     return animations;
 }
 
+void AnimationTimeline::updateCSSAnimationsForElement(Element& element, const RenderStyle& newStyle, const RenderStyle* oldStyle)
+{
+    if (element.document().pageCacheState() != Document::NotInPageCache)
+        return;
+
+    if (element.document().renderView()->printing())
+        return;
+
+    // First, compile the list of animation names that were applied to this element up to this point.
+    HashSet<String> namesOfPreviousAnimations;
+    if (oldStyle && oldStyle->hasAnimations()) {
+        auto* previousAnimations = oldStyle->animations();
+        for (size_t i = 0; i < previousAnimations->size(); ++i) {
+            auto& previousAnimation = previousAnimations->animation(i);
+            if (previousAnimation.isValidAnimation())
+                namesOfPreviousAnimations.add(previousAnimation.name());
+        }
+    }
+
+    // Create or get the CSSAnimations by animation name map for this element.
+    auto& cssAnimationsByName = m_elementToCSSAnimationByName.ensure(&element, [] {
+        return HashMap<String, RefPtr<CSSAnimation>> { };
+    }).iterator->value;
+
+    if (auto* currentAnimations = newStyle.animations()) {
+        for (size_t i = 0; i < currentAnimations->size(); ++i) {
+            auto& currentAnimation = currentAnimations->animation(i);
+            auto& name = currentAnimation.name();
+            if (namesOfPreviousAnimations.contains(name)) {
+                // We've found the name of this animation in our list of previous animations, this means we've already
+                // created a CSSAnimation object for it and need to ensure that this CSSAnimation is backed by the current
+                // animation object for this animation name.
+                cssAnimationsByName.get(name)->setBackingAnimation(currentAnimation);
+            } else if (currentAnimation.isValidAnimation()) {
+                // Otherwise we are dealing with a new animation name and must create a CSSAnimation for it.
+                cssAnimationsByName.set(name, CSSAnimation::create(element, currentAnimation));
+            }
+            // Remove the name of this animation from our list since it's now known to be current.
+            namesOfPreviousAnimations.remove(name);
+        }
+    }
+
+    // The animations names left in namesOfPreviousAnimations are now known to no longer apply so we need to
+    // remove the CSSAnimation object created for them.
+    for (const auto& nameOfAnimationToRemove : namesOfPreviousAnimations) {
+        auto cssAnimationToRemove = cssAnimationsByName.take(nameOfAnimationToRemove);
+        cssAnimationToRemove->setEffect(nullptr);
+        removeAnimation(cssAnimationToRemove.releaseNonNull());
+    }
+
+    // Remove the map of CSSAnimations by animation name for this element if it's now empty.
+    if (cssAnimationsByName.isEmpty())
+        m_elementToCSSAnimationByName.remove(&element);
+}
+
+void AnimationTimeline::updateCSSTransitionsForElement(Element& element, const RenderStyle& newStyle, const RenderStyle* oldStyle)
+{
+    if (element.document().pageCacheState() != Document::NotInPageCache)
+        return;
+
+    if (element.document().renderView()->printing())
+        return;
+
+    // FIXME: We do not handle "all" transitions yet.
+
+    // First, compile the list of backing animations and properties that were applied to this element up to this point.
+    HashSet<CSSPropertyID> previousProperties;
+    HashSet<const Animation*> previousBackingAnimations;
+    if (oldStyle && oldStyle->hasTransitions()) {
+        auto* previousTransitions = oldStyle->transitions();
+        for (size_t i = 0; i < previousTransitions->size(); ++i) {
+            auto& animation = previousTransitions->animation(i);
+            auto previousTransitionProperty = animation.property();
+            if (previousTransitionProperty != CSSPropertyInvalid) {
+                previousProperties.add(previousTransitionProperty);
+                previousBackingAnimations.add(&animation);
+            }
+        }
+    }
+
+    // Create or get the CSSTransitions by CSS property name map for this element.
+    auto& cssTransitionsByProperty = m_elementToCSSTransitionByCSSPropertyID.ensure(&element, [] {
+        return HashMap<CSSPropertyID, RefPtr<CSSTransition>> { };
+    }).iterator->value;
+
+    if (auto* currentTransitions = newStyle.transitions()) {
+        for (size_t i = 0; i < currentTransitions->size(); ++i) {
+            auto& backingAnimation = currentTransitions->animation(i);
+            auto property = backingAnimation.property();
+            if (property == CSSPropertyInvalid)
+                continue;
+            previousProperties.remove(property);
+            // We've found a backing animation that we didn't know about for a valid property.
+            if (!previousBackingAnimations.contains(&backingAnimation)) {
+                // If we already had a CSSTransition for this property, check whether its timing properties match the current backing
+                // animation's properties and whether its blending keyframes match the old and new styles. If they do, move on to the
+                // next transition, otherwise delete the previous CSSTransition object, and create a new one.
+                if (cssTransitionsByProperty.contains(property)) {
+                    if (cssTransitionsByProperty.get(property)->matchesBackingAnimationAndStyles(backingAnimation, oldStyle, newStyle))
+                        continue;
+                    auto cssTransitionToRemove = cssTransitionsByProperty.take(property);
+                    cssTransitionToRemove->setEffect(nullptr);
+                    removeAnimation(cssTransitionToRemove.releaseNonNull());
+                }
+                // Now we can create a new CSSTransition with the new backing animation provided it has a valid
+                // duration and the from and to values are distinct.
+                if (backingAnimation.duration() > 0 && oldStyle && !CSSPropertyAnimation::propertiesEqual(property, oldStyle, &newStyle))
+                    cssTransitionsByProperty.set(property, CSSTransition::create(element, backingAnimation, oldStyle, newStyle));
+            }
+        }
+    }
+
+    // Remaining properties are no longer current and must be removed.
+    for (const auto transitionPropertyToRemove : previousProperties) {
+        if (!cssTransitionsByProperty.contains(transitionPropertyToRemove))
+            continue;
+        auto cssTransitionToRemove = cssTransitionsByProperty.take(transitionPropertyToRemove);
+        cssTransitionToRemove->setEffect(nullptr);
+        removeAnimation(cssTransitionToRemove.releaseNonNull());
+    }
+
+    // Remove the map of CSSTransitions by property for this element if it's now empty.
+    if (cssTransitionsByProperty.isEmpty())
+        m_elementToCSSTransitionByCSSPropertyID.remove(&element);
+}
+
 String AnimationTimeline::description()
 {
     TextStream stream;
index 9246e23..080a5db 100644 (file)
 
 #pragma once
 
+#include "CSSValue.h"
+#include "RenderStyle.h"
 #include "WebAnimation.h"
 #include <wtf/Forward.h>
 #include <wtf/HashMap.h>
-#include <wtf/HashSet.h>
+#include <wtf/ListHashSet.h>
 #include <wtf/Optional.h>
 #include <wtf/Ref.h>
 #include <wtf/RefCounted.h>
@@ -37,8 +39,9 @@
 
 namespace WebCore {
 
+class CSSAnimation;
+class CSSTransition;
 class Element;
-class WebAnimation;
 
 class AnimationTimeline : public RefCounted<AnimationTimeline> {
 public:
@@ -53,11 +56,14 @@ public:
 
     virtual void timingModelDidChange() { };
 
-    const HashSet<RefPtr<WebAnimation>>& animations() const { return m_animations; }
+    const ListHashSet<RefPtr<WebAnimation>>& animations() const { return m_animations; }
     Vector<RefPtr<WebAnimation>> animationsForElement(Element&);
     void animationWasAddedToElement(WebAnimation&, Element&);
     void animationWasRemovedFromElement(WebAnimation&, Element&);
 
+    void updateCSSAnimationsForElement(Element&, const RenderStyle& newStyle, const RenderStyle* oldStyle);
+    void updateCSSTransitionsForElement(Element&, const RenderStyle& newStyle, const RenderStyle* oldStyle);
+
     virtual ~AnimationTimeline();
 
 protected:
@@ -69,13 +75,24 @@ protected:
 
     explicit AnimationTimeline(ClassType);
 
-    const HashMap<RefPtr<Element>, Vector<RefPtr<WebAnimation>>>& elementToAnimationsMap() { return m_elementToAnimationsMap; }
+    bool hasElementAnimations() const { return !m_elementToAnimationsMap.isEmpty() || !m_elementToCSSAnimationsMap.isEmpty() || !m_elementToCSSTransitionsMap.isEmpty(); }
+
+    const HashMap<Element*, Vector<RefPtr<WebAnimation>>>& elementToAnimationsMap() { return m_elementToAnimationsMap; }
+    const HashMap<Element*, Vector<RefPtr<WebAnimation>>>& elementToCSSAnimationsMap() { return m_elementToCSSAnimationsMap; }
+    const HashMap<Element*, Vector<RefPtr<WebAnimation>>>& elementToCSSTransitionsMap() { return m_elementToCSSTransitionsMap; }
 
 private:
+    HashMap<Element*, Vector<RefPtr<WebAnimation>>>& relevantMapForAnimation(WebAnimation&);
+
     ClassType m_classType;
     std::optional<Seconds> m_currentTime;
-    HashMap<RefPtr<Element>, Vector<RefPtr<WebAnimation>>> m_elementToAnimationsMap;
-    HashSet<RefPtr<WebAnimation>> m_animations;
+    HashMap<Element*, Vector<RefPtr<WebAnimation>>> m_elementToAnimationsMap;
+    HashMap<Element*, Vector<RefPtr<WebAnimation>>> m_elementToCSSAnimationsMap;
+    HashMap<Element*, Vector<RefPtr<WebAnimation>>> m_elementToCSSTransitionsMap;
+    ListHashSet<RefPtr<WebAnimation>> m_animations;
+
+    HashMap<Element*, HashMap<String, RefPtr<CSSAnimation>>> m_elementToCSSAnimationByName;
+    HashMap<Element*, HashMap<CSSPropertyID, RefPtr<CSSTransition>>> m_elementToCSSTransitionByCSSPropertyID;
 };
 
 } // namespace WebCore
index 017b004..82f5130 100644 (file)
 
 namespace WebCore {
 
-Ref<CSSAnimation> CSSAnimation::create(Element& target, const Animation&)
+Ref<CSSAnimation> CSSAnimation::create(Element& target, const Animation& backingAnimation)
 {
-    auto& document = target.document();
-
-    auto result = adoptRef(*new CSSAnimation(document));
-
+    auto result = adoptRef(*new CSSAnimation(target.document(), backingAnimation));
+    result->m_animationName = backingAnimation.name();
+    result->initialize(target);
     return result;
 }
 
-CSSAnimation::CSSAnimation(Document& document)
-    : WebAnimation(document)
+CSSAnimation::CSSAnimation(Document& document, const Animation& backingAnimation)
+    : DeclarativeAnimation(document, backingAnimation)
+{
+}
+
+void CSSAnimation::initialize(const Element& target)
 {
+    DeclarativeAnimation::initialize(target);
+
+    downcast<KeyframeEffectReadOnly>(effect())->computeCSSAnimationBlendingKeyframes();
 }
 
+void CSSAnimation::syncPropertiesWithBackingAnimation()
+{
+    DeclarativeAnimation::syncPropertiesWithBackingAnimation();
+
+    suspendEffectInvalidation();
+
+    auto& animation = backingAnimation();
+    auto* timing = effect()->timing();
+
+    switch (animation.fillMode()) {
+    case AnimationFillModeNone:
+        timing->setFill(FillMode::None);
+        break;
+    case AnimationFillModeBackwards:
+        timing->setFill(FillMode::Backwards);
+        break;
+    case AnimationFillModeForwards:
+        timing->setFill(FillMode::Forwards);
+        break;
+    case AnimationFillModeBoth:
+        timing->setFill(FillMode::Both);
+        break;
+    }
+
+    switch (animation.direction()) {
+    case Animation::AnimationDirectionNormal:
+        timing->setDirection(PlaybackDirection::Normal);
+        break;
+    case Animation::AnimationDirectionAlternate:
+        timing->setDirection(PlaybackDirection::Alternate);
+        break;
+    case Animation::AnimationDirectionReverse:
+        timing->setDirection(PlaybackDirection::Reverse);
+        break;
+    case Animation::AnimationDirectionAlternateReverse:
+        timing->setDirection(PlaybackDirection::AlternateReverse);
+        break;
+    }
+
+    timing->setIterations(animation.iterationCount());
+
+    // Synchronize the play state
+    if (backingAnimation().playState() == AnimPlayStatePlaying && playState() == WebAnimation::PlayState::Paused)
+        play();
+    else if (backingAnimation().playState() == AnimPlayStatePaused && playState() == WebAnimation::PlayState::Running)
+        pause();
+
+    unsuspendEffectInvalidation();
+}
 
 } // namespace WebCore
index ca924a6..101b593 100644 (file)
@@ -25,7 +25,7 @@
 
 #pragma once
 
-#include "WebAnimation.h"
+#include "DeclarativeAnimation.h"
 #include <wtf/Ref.h>
 
 namespace WebCore {
@@ -33,7 +33,7 @@ namespace WebCore {
 class Animation;
 class Element;
 
-class CSSAnimation final : public WebAnimation {
+class CSSAnimation final : public DeclarativeAnimation {
 public:
     static Ref<CSSAnimation> create(Element&, const Animation&);
     ~CSSAnimation() = default;
@@ -41,8 +41,12 @@ public:
     bool isCSSAnimation() const override { return true; }
     const String& animationName() const { return m_animationName; }
 
+protected:
+    void initialize(const Element&) final;
+    void syncPropertiesWithBackingAnimation() final;
+
 private:
-    CSSAnimation(Document&);
+    CSSAnimation(Document&, const Animation&);
 
     String m_animationName;
 
index 730442f..97fad3e 100644 (file)
 
 #include "Animation.h"
 #include "Element.h"
+#include "KeyframeEffectReadOnly.h"
 
 namespace WebCore {
 
-Ref<CSSTransition> CSSTransition::create(Element& target, const Animation&)
+Ref<CSSTransition> CSSTransition::create(Element& target, const Animation& backingAnimation, const RenderStyle* oldStyle, const RenderStyle& newStyle)
 {
-    auto& document = target.document();
+    auto result = adoptRef(*new CSSTransition(target.document(), backingAnimation));
+    result->m_transitionProperty = backingAnimation.property();
+    result->initialize(target);
+    downcast<KeyframeEffectReadOnly>(result->effect())->computeCSSTransitionBlendingKeyframes(oldStyle, newStyle);
+    return result;
+}
 
-    auto result = adoptRef(*new CSSTransition(document));
+CSSTransition::CSSTransition(Document& document, const Animation& backingAnimation)
+    : DeclarativeAnimation(document, backingAnimation)
+{
+}
 
-    return result;
+bool CSSTransition::matchesBackingAnimationAndStyles(const Animation& newBackingAnimation, const RenderStyle* oldStyle, const RenderStyle& newStyle) const
+{
+    bool backingAnimationsMatch = backingAnimation() == newBackingAnimation;
+    if (!oldStyle)
+        return backingAnimationsMatch;
+    return backingAnimationsMatch && !downcast<KeyframeEffectReadOnly>(effect())->stylesWouldYieldNewCSSTransitionsBlendingKeyframes(*oldStyle, newStyle);
 }
 
-CSSTransition::CSSTransition(Document& document)
-    : WebAnimation(document)
+bool CSSTransition::canBeListed() const
 {
+    if (!downcast<KeyframeEffectReadOnly>(effect())->hasBlendingKeyframes())
+        return false;
+    return WebAnimation::canBeListed();
 }
 
 } // namespace WebCore
index 1151333..35f0ed0 100644 (file)
 #pragma once
 
 #include "CSSPropertyNames.h"
-#include "WebAnimation.h"
+#include "DeclarativeAnimation.h"
 #include <wtf/Ref.h>
 
 namespace WebCore {
 
 class Animation;
 class Element;
+class RenderStyle;
 
-class CSSTransition final : public WebAnimation {
+class CSSTransition final : public DeclarativeAnimation {
 public:
-    static Ref<CSSTransition> create(Element&, const Animation&);
+    static Ref<CSSTransition> create(Element&, const Animation&, const RenderStyle* oldStyle, const RenderStyle& newStyle);
     ~CSSTransition() = default;
 
     bool isCSSTransition() const override { return true; }
     String transitionProperty() const { return getPropertyNameString(m_transitionProperty); }
 
+    bool matchesBackingAnimationAndStyles(const Animation&, const RenderStyle* oldStyle, const RenderStyle& newStyle) const;
+    bool canBeListed() const final;
+
 private:
-    CSSTransition(Document&);
+    CSSTransition(Document&, const Animation&);
 
     CSSPropertyID m_transitionProperty;
 
diff --git a/Source/WebCore/animation/DeclarativeAnimation.cpp b/Source/WebCore/animation/DeclarativeAnimation.cpp
new file mode 100644 (file)
index 0000000..a072568
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "DeclarativeAnimation.h"
+
+#include "Animation.h"
+#include "AnimationEffectTimingReadOnly.h"
+#include "Element.h"
+#include "KeyframeEffectReadOnly.h"
+
+namespace WebCore {
+
+DeclarativeAnimation::DeclarativeAnimation(Document& document, const Animation& backingAnimation)
+    : WebAnimation(document)
+    , m_backingAnimation(const_cast<Animation&>(backingAnimation))
+{
+}
+
+void DeclarativeAnimation::setBackingAnimation(const Animation& backingAnimation)
+{
+    m_backingAnimation = const_cast<Animation&>(backingAnimation);
+    syncPropertiesWithBackingAnimation();
+}
+
+void DeclarativeAnimation::initialize(const Element& target)
+{
+    // We need to suspend invalidation of the animation's keyframe effect during its creation
+    // as it would otherwise trigger invalidation of the document's style and this would be
+    // incorrect since it would happen during style invalidation.
+    suspendEffectInvalidation();
+
+    setEffect(KeyframeEffectReadOnly::create(target));
+    setTimeline(&target.document().timeline());
+    syncPropertiesWithBackingAnimation();
+    if (backingAnimation().playState() == AnimPlayStatePlaying)
+        play();
+
+    unsuspendEffectInvalidation();
+}
+
+void DeclarativeAnimation::syncPropertiesWithBackingAnimation()
+{
+    suspendEffectInvalidation();
+
+    auto* timing = effect()->timing();
+    timing->setDelay(Seconds(m_backingAnimation->delay()));
+    timing->setIterationDuration(Seconds(m_backingAnimation->duration()));
+    timing->setTimingFunction(m_backingAnimation->timingFunction());
+
+    unsuspendEffectInvalidation();
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/animation/DeclarativeAnimation.h b/Source/WebCore/animation/DeclarativeAnimation.h
new file mode 100644 (file)
index 0000000..7ea285b
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "WebAnimation.h"
+#include <wtf/Ref.h>
+
+namespace WebCore {
+
+class Animation;
+class Element;
+
+class DeclarativeAnimation : public WebAnimation {
+public:
+    ~DeclarativeAnimation() = default;
+
+    bool isDeclarativeAnimation() const final { return true; }
+
+    const Animation& backingAnimation() const { return m_backingAnimation; }
+    void setBackingAnimation(const Animation&);
+
+protected:
+    DeclarativeAnimation(Document&, const Animation&);
+
+    virtual void initialize(const Element&);
+    virtual void syncPropertiesWithBackingAnimation();
+
+private:
+    Ref<Animation> m_backingAnimation;
+
+};
+
+} // namespace WebCore
+
+SPECIALIZE_TYPE_TRAITS_WEB_ANIMATION(DeclarativeAnimation, isDeclarativeAnimation())
index c00d6a8..525813c 100644 (file)
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "DocumentTimeline.h"
 
+#include "AnimationPlaybackEvent.h"
 #include "Chrome.h"
 #include "ChromeClient.h"
 #include "DOMWindow.h"
@@ -162,9 +163,13 @@ void DocumentTimeline::animationResolutionTimerFired()
 
 void DocumentTimeline::updateAnimations()
 {
-    if (m_document && !elementToAnimationsMap().isEmpty()) {
+    if (m_document && hasElementAnimations()) {
         for (const auto& elementToAnimationsMapItem : elementToAnimationsMap())
             elementToAnimationsMapItem.key->invalidateStyleAndLayerComposition();
+        for (const auto& elementToCSSAnimationsMapItem : elementToCSSAnimationsMap())
+            elementToCSSAnimationsMapItem.key->invalidateStyleAndLayerComposition();
+        for (const auto& elementToCSSTransitionsMapItem : elementToCSSTransitionsMap())
+            elementToCSSTransitionsMapItem.key->invalidateStyleAndLayerComposition();
         m_document->updateStyleIfNeeded();
     }
 
@@ -172,9 +177,6 @@ void DocumentTimeline::updateAnimations()
         animation->startOrStopAccelerated();
     m_acceleratedAnimationsPendingRunningStateChange.clear();
 
-    for (const auto& animation : animations())
-        animation->updateFinishedState(WebAnimation::DidSeek::No, WebAnimation::SynchronouslyNotify::No);
-
     // Time has advanced, the timing model requires invalidation now.
     timingModelDidChange();
 }
@@ -185,8 +187,8 @@ std::unique_ptr<RenderStyle> DocumentTimeline::animatedStyleForRenderer(RenderEl
 
     if (auto* element = renderer.element()) {
         for (auto animation : animationsForElement(*element)) {
-            if (animation->effect() && animation->effect()->isKeyframeEffect())
-                downcast<KeyframeEffect>(animation->effect())->getAnimatedStyle(result);
+            if (is<KeyframeEffectReadOnly>(animation->effect()))
+                downcast<KeyframeEffectReadOnly>(animation->effect())->getAnimatedStyle(result);
         }
     }
 
@@ -208,7 +210,7 @@ bool DocumentTimeline::runningAnimationsForElementAreAllAccelerated(Element& ele
     // disabled (webkit.org/b/179974).
     auto animations = animationsForElement(element);
     for (const auto& animation : animations) {
-        if (animation->effect() && animation->effect()->isKeyframeEffect() && !downcast<KeyframeEffect>(animation->effect())->isRunningAccelerated())
+        if (is<KeyframeEffectReadOnly>(animation->effect()) && !downcast<KeyframeEffectReadOnly>(animation->effect())->isRunningAccelerated())
             return false;
     }
     return !animations.isEmpty();
index 68be2c1..f4fb976 100644 (file)
 
 #include "Animation.h"
 #include "AnimationEffectTimingReadOnly.h"
+#include "CSSAnimation.h"
 #include "CSSComputedStyleDeclaration.h"
 #include "CSSPropertyAnimation.h"
 #include "CSSPropertyNames.h"
 #include "CSSStyleDeclaration.h"
 #include "CSSTimingFunctionValue.h"
+#include "CSSTransition.h"
 #include "Element.h"
 #include "FontCascade.h"
 #include "JSCompositeOperation.h"
@@ -40,6 +42,7 @@
 #include "RenderBoxModelObject.h"
 #include "RenderElement.h"
 #include "RenderStyle.h"
+#include "StylePendingResources.h"
 #include "StyleResolver.h"
 #include "TimingFunction.h"
 #include "WillChangeData.h"
@@ -50,11 +53,8 @@ using namespace JSC;
 
 static inline void invalidateElement(Element* element)
 {
-    if (!element)
-        return;
-
-    element->invalidateStyleAndLayerComposition();
-    element->document().updateStyleIfNeeded();
+    if (element)
+        element->invalidateStyleAndLayerComposition();
 }
 
 static inline String CSSPropertyIDToIDLAttributeName(CSSPropertyID cssPropertyId)
@@ -446,6 +446,11 @@ ExceptionOr<Ref<KeyframeEffectReadOnly>> KeyframeEffectReadOnly::create(JSC::Exe
     return WTFMove(keyframeEffect);
 }
 
+Ref<KeyframeEffectReadOnly> KeyframeEffectReadOnly::create(const Element& target)
+{
+    return adoptRef(*new KeyframeEffectReadOnly(KeyframeEffectReadOnlyClass, AnimationEffectTimingReadOnly::create(), const_cast<Element*>(&target)));
+}
+
 KeyframeEffectReadOnly::KeyframeEffectReadOnly(ClassType classType, Ref<AnimationEffectTimingReadOnly>&& timing, Element* target)
     : AnimationEffectReadOnly(classType, WTFMove(timing))
     , m_target(target)
@@ -500,40 +505,81 @@ Vector<Strong<JSObject>> KeyframeEffectReadOnly::getKeyframes(ExecState& state)
     // 2. Let keyframes be the result of applying the procedure to compute missing keyframe offsets to the keyframes for this keyframe effect.
 
     // 3. For each keyframe in keyframes perform the following steps:
-    for (auto& parsedKeyframe : m_parsedKeyframes) {
-        // 1. Initialize a dictionary object, output keyframe, using the following definition:
-        //
-        // dictionary BaseComputedKeyframe {
-        //      double?             offset = null;
-        //      double              computedOffset;
-        //      DOMString           easing = "linear";
-        //      CompositeOperation? composite = null;
-        // };
-
-        // 2. Set offset, computedOffset, easing, composite members of output keyframe to the respective values keyframe offset, computed keyframe
-        // offset, keyframe-specific timing function and keyframe-specific composite operation of keyframe.
-        BaseComputedKeyframe computedKeyframe;
-        computedKeyframe.offset = parsedKeyframe.offset;
-        computedKeyframe.computedOffset = parsedKeyframe.computedOffset;
-        computedKeyframe.easing = parsedKeyframe.timingFunction->cssText();
-        computedKeyframe.composite = parsedKeyframe.composite;
-
-        auto outputKeyframe = convertDictionaryToJS(state, *jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject()), computedKeyframe);
-
-        // 3. For each animation property-value pair specified on keyframe, declaration, perform the following steps:
-        for (auto it = parsedKeyframe.unparsedStyle.begin(), end = parsedKeyframe.unparsedStyle.end(); it != end; ++it) {
-            // 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(it->key);
-            // 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.
-            // 3. Let value be the result of converting IDL value to an ECMAScript String value.
-            auto value = toJS<IDLDOMString>(state, it->value);
-            // 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, &state, AtomicString(propertyName).impl(), PropertyDescriptor(value, 0), false);
+    if (is<DeclarativeAnimation>(animation())) {
+        auto computedStyleExtractor = ComputedStyleExtractor(m_target.get());
+        for (size_t i = 0; i < m_blendingKeyframes.size(); ++i) {
+            // 1. Initialize a dictionary object, output keyframe, using the following definition:
+            //
+            // dictionary BaseComputedKeyframe {
+            //      double?             offset = null;
+            //      double              computedOffset;
+            //      DOMString           easing = "linear";
+            //      CompositeOperation? composite = null;
+            // };
+
+            auto& keyframe = m_blendingKeyframes[i];
+
+            // 2. Set offset, computedOffset, easing members of output keyframe to the respective values keyframe offset, computed keyframe offset,
+            // and keyframe-specific timing function of keyframe.
+            BaseComputedKeyframe computedKeyframe;
+            computedKeyframe.offset = keyframe.key();
+            computedKeyframe.computedOffset = keyframe.key();
+
+            auto outputKeyframe = convertDictionaryToJS(state, *jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject()), computedKeyframe);
+
+            // 3. For each animation property-value pair specified on keyframe, declaration, perform the following steps:
+            auto& style = *keyframe.style();
+            for (auto cssPropertyId : keyframe.properties()) {
+                // 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.
+                auto idlValue = computedStyleExtractor.valueForPropertyinStyle(style, cssPropertyId)->cssText();
+                // 3. Let value be the result of converting IDL value to an ECMAScript String value.
+                auto value = toJS<IDLDOMString>(state, 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, &state, AtomicString(propertyName).impl(), PropertyDescriptor(value, 0), false);
+            }
+
+            // 5. Append output keyframe to result.
+            result.append(JSC::Strong<JSC::JSObject> { state.vm(), outputKeyframe });
         }
+    } else {
+        for (auto& parsedKeyframe : m_parsedKeyframes) {
+            // 1. Initialize a dictionary object, output keyframe, using the following definition:
+            //
+            // dictionary BaseComputedKeyframe {
+            //      double?             offset = null;
+            //      double              computedOffset;
+            //      DOMString           easing = "linear";
+            //      CompositeOperation? composite = null;
+            // };
+
+            // 2. Set offset, computedOffset, easing, composite members of output keyframe to the respective values keyframe offset, computed keyframe
+            // offset, keyframe-specific timing function and keyframe-specific composite operation of keyframe.
+            BaseComputedKeyframe computedKeyframe;
+            computedKeyframe.offset = parsedKeyframe.offset;
+            computedKeyframe.computedOffset = parsedKeyframe.computedOffset;
+            computedKeyframe.easing = parsedKeyframe.timingFunction->cssText();
+            computedKeyframe.composite = parsedKeyframe.composite;
+
+            auto outputKeyframe = convertDictionaryToJS(state, *jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject()), computedKeyframe);
+
+            // 3. For each animation property-value pair specified on keyframe, declaration, perform the following steps:
+            for (auto it = parsedKeyframe.unparsedStyle.begin(), end = parsedKeyframe.unparsedStyle.end(); it != end; ++it) {
+                // 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(it->key);
+                // 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.
+                // 3. Let value be the result of converting IDL value to an ECMAScript String value.
+                auto value = toJS<IDLDOMString>(state, it->value);
+                // 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, &state, AtomicString(propertyName).impl(), PropertyDescriptor(value, 0), false);
+            }
 
-        // 4. Append output keyframe to result.
-        result.append(JSC::Strong<JSC::JSObject> { state.vm(), outputKeyframe });
+            // 4. Append output keyframe to result.
+            result.append(JSC::Strong<JSC::JSObject> { state.vm(), outputKeyframe });
+        }
     }
 
     // 4. Return result.
@@ -641,6 +687,78 @@ void KeyframeEffectReadOnly::updateBlendingKeyframes()
     computeStackingContextImpact();
 }
 
+void KeyframeEffectReadOnly::computeCSSAnimationBlendingKeyframes()
+{
+    ASSERT(is<CSSAnimation>(animation()));
+
+    auto& backingAnimation = downcast<CSSAnimation>(animation())->backingAnimation();
+    if (backingAnimation.name().isEmpty())
+        return;
+
+    auto renderStyle = RenderStyle::createPtr();
+    // We need to call update() on the FontCascade or we'll hit an ASSERT when parsing font-related properties.
+    renderStyle->fontCascade().update(nullptr);
+
+    KeyframeList keyframeList(backingAnimation.name());
+    if (auto* styleScope = Style::Scope::forOrdinal(*m_target, backingAnimation.nameStyleScopeOrdinal()))
+        styleScope->resolver().keyframeStylesForAnimation(*m_target, renderStyle.get(), keyframeList);
+
+    // Ensure resource loads for all the frames.
+    for (auto& keyframe : keyframeList.keyframes()) {
+        if (auto* style = const_cast<RenderStyle*>(keyframe.style()))
+            Style::loadPendingResources(*style, m_target->document(), m_target.get());
+    }
+
+    m_blendingKeyframes = WTFMove(keyframeList);
+
+    computeStackingContextImpact();
+}
+
+void KeyframeEffectReadOnly::computeCSSTransitionBlendingKeyframes(const RenderStyle* oldStyle, const RenderStyle& newStyle)
+{
+    ASSERT(is<CSSTransition>(animation()));
+
+    if (!oldStyle || m_blendingKeyframes.size())
+        return;
+
+    auto& backingAnimation = downcast<CSSTransition>(animation())->backingAnimation();
+
+    auto toStyle = RenderStyle::clonePtr(newStyle);
+    if (m_target)
+        Style::loadPendingResources(*toStyle, m_target->document(), m_target.get());
+
+    KeyframeList keyframeList("keyframe-effect-" + createCanonicalUUIDString());
+    keyframeList.addProperty(backingAnimation.property());
+
+    KeyframeValue fromKeyframeValue(0, RenderStyle::clonePtr(*oldStyle));
+    fromKeyframeValue.addProperty(backingAnimation.property());
+    keyframeList.insert(WTFMove(fromKeyframeValue));
+
+    KeyframeValue toKeyframeValue(1, WTFMove(toStyle));
+    toKeyframeValue.addProperty(backingAnimation.property());
+    keyframeList.insert(WTFMove(toKeyframeValue));
+
+    m_blendingKeyframes = WTFMove(keyframeList);
+
+    computeStackingContextImpact();
+}
+
+bool KeyframeEffectReadOnly::stylesWouldYieldNewCSSTransitionsBlendingKeyframes(const RenderStyle& oldStyle, const RenderStyle& newStyle) const
+{
+    ASSERT(is<CSSTransition>(animation()));
+    auto property = downcast<CSSTransition>(animation())->backingAnimation().property();
+
+    // If we didn't have blending keyframes yet, we would create new blending keyframes provided
+    // the start and end styles hold different values for this property.
+    if (!hasBlendingKeyframes())
+        return !CSSPropertyAnimation::propertiesEqual(property, &oldStyle, &newStyle);
+
+    // Otherwise, we would create new blending keyframes provided the current start keyframe holds
+    // a different value than the new start style or the current end keyframe holds a different value
+    // than the new end style for this property.
+    return !CSSPropertyAnimation::propertiesEqual(property, m_blendingKeyframes[0].style(), &oldStyle) || !CSSPropertyAnimation::propertiesEqual(property, m_blendingKeyframes[1].style(), &newStyle);
+}
+
 void KeyframeEffectReadOnly::computeStackingContextImpact()
 {
     m_triggersStackingContext = false;
@@ -718,7 +836,7 @@ bool KeyframeEffectReadOnly::shouldRunAccelerated()
         if (!CSSPropertyAnimation::animationOfPropertyIsAccelerated(cssPropertyId))
             return false;
     }
-    return true;
+    return hasBlendingKeyframes();
 }
 
 void KeyframeEffectReadOnly::getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle)
@@ -861,7 +979,8 @@ void KeyframeEffectReadOnly::setAnimatedPropertiesInStyle(RenderStyle& targetSty
         // 17. Let transformed distance be the result of evaluating the timing function associated with the first keyframe in interval endpoints
         //     passing interval distance as the input progress.
         auto transformedDistance = intervalDistance;
-        if (startKeyframeIndex) {
+        // In case we're backing a CSSAnimation or CSSTransition we won't actually have parsed keyframes.
+        if (startKeyframeIndex && startKeyframeIndex.value() + 1 <= m_parsedKeyframes.size()) {
             if (auto iterationDuration = timing()->iterationDuration()) {
                 auto rangeDuration = (endOffset - startOffset) * iterationDuration.seconds();
                 transformedDistance = m_parsedKeyframes[startKeyframeIndex.value()].timingFunction->transformTime(intervalDistance, rangeDuration);
index 3c53c0c..a2f421e 100644 (file)
@@ -46,6 +46,7 @@ class KeyframeEffectReadOnly : public AnimationEffectReadOnly
 public:
     static ExceptionOr<Ref<KeyframeEffectReadOnly>> create(JSC::ExecState&, Element*, JSC::Strong<JSC::JSObject>&&, std::optional<Variant<double, KeyframeEffectOptions>>&&);
     static ExceptionOr<Ref<KeyframeEffectReadOnly>> create(JSC::ExecState&, Ref<KeyframeEffectReadOnly>&&);
+    static Ref<KeyframeEffectReadOnly> create(const Element&);
     ~KeyframeEffectReadOnly() { }
 
     struct BasePropertyIndexedKeyframe {
@@ -109,6 +110,11 @@ public:
     bool backdropFilterFunctionListsMatch() const override { return false; }
 #endif
 
+    void computeCSSAnimationBlendingKeyframes();
+    void computeCSSTransitionBlendingKeyframes(const RenderStyle* oldStyle, const RenderStyle& newStyle);
+    bool stylesWouldYieldNewCSSTransitionsBlendingKeyframes(const RenderStyle& oldStyle, const RenderStyle& newStyle) const;
+    bool hasBlendingKeyframes() const { return m_blendingKeyframes.size(); }
+
 protected:
     void copyPropertiesFromSource(Ref<KeyframeEffectReadOnly>&&);
     ExceptionOr<void> processKeyframes(JSC::ExecState&, JSC::Strong<JSC::JSObject>&&);
index 693952f..9570fc6 100644 (file)
@@ -68,13 +68,22 @@ WebAnimation::WebAnimation(Document& document)
 
 WebAnimation::~WebAnimation()
 {
-    if (m_timeline)
-        m_timeline->removeAnimation(*this);
+}
+
+void WebAnimation::suspendEffectInvalidation()
+{
+    ++m_suspendCount;
+}
+
+void WebAnimation::unsuspendEffectInvalidation()
+{
+    ASSERT(m_suspendCount > 0);
+    --m_suspendCount;
 }
 
 void WebAnimation::timingModelDidChange()
 {
-    if (m_effect)
+    if (!isEffectInvalidationSuspended() && m_effect)
         m_effect->invalidate();
     if (m_timeline)
         m_timeline->timingModelDidChange();
@@ -119,16 +128,16 @@ void WebAnimation::setEffect(RefPtr<AnimationEffectReadOnly>&& newEffect)
     // Update the effect-to-animation relationships and the timeline's animation map.
     if (oldEffect) {
         oldEffect->setAnimation(nullptr);
-        if (m_timeline && is<KeyframeEffect>(oldEffect)) {
-            if (auto* target = downcast<KeyframeEffect>(oldEffect.get())->target())
+        if (m_timeline && is<KeyframeEffectReadOnly>(oldEffect)) {
+            if (auto* target = downcast<KeyframeEffectReadOnly>(oldEffect.get())->target())
                 m_timeline->animationWasRemovedFromElement(*this, *target);
         }
     }
 
     if (m_effect) {
         m_effect->setAnimation(this);
-        if (m_timeline && is<KeyframeEffect>(m_effect)) {
-            if (auto* target = downcast<KeyframeEffect>(m_effect.get())->target())
+        if (m_timeline && is<KeyframeEffectReadOnly>(m_effect)) {
+            if (auto* target = downcast<KeyframeEffectReadOnly>(m_effect.get())->target())
                 m_timeline->animationWasAddedToElement(*this, *target);
         }
     }
@@ -155,8 +164,8 @@ void WebAnimation::setTimeline(RefPtr<AnimationTimeline>&& timeline)
     if (timeline)
         timeline->addAnimation(*this);
 
-    if (is<KeyframeEffect>(m_effect)) {
-        auto* keyframeEffect = downcast<KeyframeEffect>(m_effect.get());
+    if (is<KeyframeEffectReadOnly>(m_effect)) {
+        auto* keyframeEffect = downcast<KeyframeEffectReadOnly>(m_effect.get());
         auto* target = keyframeEffect->target();
         if (target) {
             if (m_timeline)
@@ -661,7 +670,9 @@ void WebAnimation::scheduleMicrotaskIfNeeded()
         return;
 
     m_scheduledMicrotask = true;
-    MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>(std::bind(&WebAnimation::performMicrotask, this)));
+    MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>([this, protectedThis = makeRef(*this)] () {
+        this->performMicrotask();
+    }));
 }
 
 void WebAnimation::performMicrotask()
@@ -805,7 +816,8 @@ void WebAnimation::runPendingPlayTask()
     }
 
     // 4. Resolve animation's current ready promise with animation.
-    m_readyPromise->resolve(*this);
+    if (!m_readyPromise->isFulfilled())
+        m_readyPromise->resolve(*this);
 
     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
@@ -924,7 +936,8 @@ void WebAnimation::runPendingPauseTask()
     setStartTime(std::nullopt);
 
     // 4. Resolve animation's current ready promise with animation.
-    m_readyPromise->resolve(*this);
+    if (!m_readyPromise->isFulfilled())
+        m_readyPromise->resolve(*this);
 
     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the
     //    synchronously notify flag set to false.
@@ -990,8 +1003,8 @@ void WebAnimation::acceleratedRunningStateDidChange()
 
 void WebAnimation::startOrStopAccelerated()
 {
-    if (is<KeyframeEffect>(m_effect))
-        downcast<KeyframeEffect>(*m_effect).startOrStopAccelerated();
+    if (is<KeyframeEffectReadOnly>(m_effect))
+        downcast<KeyframeEffectReadOnly>(*m_effect).startOrStopAccelerated();
 }
 
 WebAnimation& WebAnimation::readyPromiseResolve()
@@ -1025,4 +1038,25 @@ void WebAnimation::stop()
     removeAllEventListeners();
 }
 
+bool WebAnimation::canBeListed() const
+{
+    // To be listed in getAnimations() an animation needs a target effect which is current or in effect.
+    if (!m_effect)
+        return false;
+
+    // An animation effect is in effect if its active time is not unresolved.
+    if (m_effect->activeTime())
+        return true;
+
+    // An animation effect is current if either of the following conditions is true:
+    // - the animation effect is in the before phase, or
+    // - the animation effect is in play.
+
+    // An animation effect is in play if all of the following conditions are met:
+    // - the animation effect is in the active phase, and
+    // - the animation effect is associated with an animation that is not finished.
+    auto phase = m_effect->phase();
+    return phase == AnimationEffectReadOnly::Phase::Before || (phase == AnimationEffectReadOnly::Phase::Active && playState() != PlayState::Finished);
+}
+
 } // namespace WebCore
index 9a89097..493f75a 100644 (file)
@@ -52,9 +52,12 @@ public:
     static Ref<WebAnimation> create(Document&, AnimationEffectReadOnly*, AnimationTimeline*);
     ~WebAnimation();
 
+    virtual bool isDeclarativeAnimation() const { return false; }
     virtual bool isCSSAnimation() const { return false; }
     virtual bool isCSSTransition() const { return false; }
 
+    virtual bool canBeListed() const;
+
     const String& id() const { return m_id; }
     void setId(const String& id) { m_id = id; }
 
@@ -105,6 +108,8 @@ public:
     void updateFinishedState(DidSeek, SynchronouslyNotify);
 
     void timingModelDidChange();
+    void suspendEffectInvalidation();
+    void unsuspendEffectInvalidation();
 
     String description();
 
@@ -114,6 +119,8 @@ public:
 protected:
     explicit WebAnimation(Document&);
 
+    bool isEffectInvalidationSuspended() { return m_suspendCount; }
+
 private:
     enum class RespectHoldTime { Yes, No };
     enum class AutoRewind { Yes, No };
@@ -145,6 +152,7 @@ private:
     std::optional<Seconds> m_previousCurrentTime;
     std::optional<Seconds> m_startTime;
     std::optional<Seconds> m_holdTime;
+    int m_suspendCount { 0 };
     double m_playbackRate { 1 };
     bool m_isStopped { false };
     bool m_finishNotificationStepsMicrotaskPending;
index 2bc2065..cec1a49 100644 (file)
 #include "JSDOMPromiseDeferred.h"
 #include "JSLazyEventListener.h"
 #include "KeyboardEvent.h"
+#include "KeyframeEffectReadOnly.h"
 #include "LayoutDisallowedScope.h"
 #include "LoaderStrategy.h"
 #include "Logging.h"
@@ -7643,11 +7644,22 @@ DocumentTimeline& Document::timeline()
 
 Vector<RefPtr<WebAnimation>> Document::getAnimations()
 {
+    // FIXME: Filter and order the list as specified (webkit.org/b/179535).
+
+    // For the list of animations to be current, we need to account for any pending CSS changes,
+    // such as updates to CSS Animations and CSS Transitions.
+    updateStyleIfNeeded();
+
     Vector<RefPtr<WebAnimation>> animations;
     if (m_timeline) {
-        // FIXME: Filter and order the list as specified (webkit.org/b/179535).
-        for (auto& animation : m_timeline->animations())
-            animations.append(animation);
+        for (auto& animation : m_timeline->animations()) {
+            if (animation->canBeListed() && is<KeyframeEffectReadOnly>(animation->effect())) {
+                if (auto* target = downcast<KeyframeEffectReadOnly>(animation->effect())->target()) {
+                    if (target->isDescendantOf(this))
+                        animations.append(animation);
+                }
+            }
+        }
     }
     return animations;
 }
index 5e34aae..31f17d9 100644 (file)
@@ -3768,9 +3768,20 @@ ExceptionOr<Ref<WebAnimation>> Element::animate(JSC::ExecState& state, JSC::Stro
 Vector<RefPtr<WebAnimation>> Element::getAnimations()
 {
     // FIXME: Filter and order the list as specified (webkit.org/b/179535).
-    if (auto timeline = document().existingTimeline())
-        return timeline->animationsForElement(*this);
-    return { };
+
+    // For the list of animations to be current, we need to account for any pending CSS changes,
+    // such as updates to CSS Animations and CSS Transitions.
+    // FIXME: We might be able to use ComputedStyleExtractor which is more optimized.
+    document().updateStyleIfNeeded();
+
+    Vector<RefPtr<WebAnimation>> animations;
+    if (auto timeline = document().existingTimeline()) {
+        for (auto& animation : timeline->animationsForElement(*this)) {
+            if (animation->canBeListed())
+                animations.append(animation);
+        }
+    }
+    return animations;
 }
 
 } // namespace WebCore
index ffa4981..f71ef61 100644 (file)
@@ -44,6 +44,7 @@
 #include "PlatformStrategies.h"
 #include "RenderElement.h"
 #include "RenderView.h"
+#include "RuntimeEnabledFeatures.h"
 #include "Settings.h"
 #include "ShadowRoot.h"
 #include "StyleFontSizeFunctions.h"
@@ -286,7 +287,21 @@ ElementUpdate TreeResolver::createAnimatedElementUpdate(std::unique_ptr<RenderSt
 {
     auto* oldStyle = renderOrDisplayContentsStyle(element);
 
-    if (auto timeline = element.document().existingTimeline()) {
+    // New code path for CSS Animations and CSS Transitions.
+    if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled()) {
+        // First, we need to make sure that any new CSS animation occuring on this element has a matching WebAnimation
+        // on the document timeline. Note that we get timeline() on the Document here because we need a timeline created
+        // in case no Web Animations have been created through the JS API.
+        if ((oldStyle && oldStyle->hasAnimations()) || newStyle->hasAnimations())
+            m_document.timeline().updateCSSAnimationsForElement(element, *newStyle, oldStyle);
+
+        if ((oldStyle && oldStyle->hasTransitions()) || newStyle->hasTransitions())
+            m_document.timeline().updateCSSTransitionsForElement(element, *newStyle, oldStyle);
+    }
+
+    if (auto timeline = m_document.existingTimeline()) {
+        // Now we can update all Web animations, which will include CSS Animations as well
+        // as animations created via the JS API.
         auto webAnimations = timeline->animationsForElement(element);
         if (!webAnimations.isEmpty()) {
             auto animatedStyle = RenderStyle::clonePtr(*newStyle);
@@ -296,12 +311,18 @@ ElementUpdate TreeResolver::createAnimatedElementUpdate(std::unique_ptr<RenderSt
         }
     }
 
-    auto& animationController = m_document.frame()->animation();
+    bool shouldRecompositeLayer = false;
 
-    auto animationUpdate = animationController.updateAnimations(element, *newStyle, oldStyle);
+    // Old code path for CSS Animations and CSS Transitions.
+    if (!RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled()) {
+        auto& animationController = m_document.frame()->animation();
 
-    if (animationUpdate.style)
-        newStyle = WTFMove(animationUpdate.style);
+        auto animationUpdate = animationController.updateAnimations(element, *newStyle, oldStyle);
+        shouldRecompositeLayer = animationUpdate.stateChanged;
+
+        if (animationUpdate.style)
+            newStyle = WTFMove(animationUpdate.style);
+    }
 
     auto change = oldStyle ? determineChange(*oldStyle, *newStyle) : Detach;
 
@@ -309,7 +330,7 @@ ElementUpdate TreeResolver::createAnimatedElementUpdate(std::unique_ptr<RenderSt
     if (validity >= Validity::SubtreeAndRenderersInvalid || parentChange == Detach)
         change = Detach;
 
-    bool shouldRecompositeLayer = element.styleResolutionShouldRecompositeLayer() || animationUpdate.stateChanged;
+    shouldRecompositeLayer |= element.styleResolutionShouldRecompositeLayer();
 
     return { WTFMove(newStyle), change, shouldRecompositeLayer };
 }
index a07b9cd..04f784f 100644 (file)
@@ -1,3 +1,24 @@
+2018-03-12  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement CSS Animations and CSS Transitions as Web Animations
+        https://bugs.webkit.org/show_bug.cgi?id=183504
+        <rdar://problem/38372965>
+
+        Reviewed by Dean Jackson and Jon Lee.
+
+        Add the missing WebKitLegacy support the cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled flag
+        which is required for the matching <!-- webkit-test-runner --> flag to work in DumpRenderTree.
+
+        * WebView/WebPreferenceKeysPrivate.h:
+        * WebView/WebPreferences.mm:
+        (+[WebPreferences initialize]):
+        (-[WebPreferences setModernMediaControlsEnabled:]):
+        (-[WebPreferences cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled]):
+        (-[WebPreferences setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled:]):
+        * WebView/WebPreferencesPrivate.h:
+        * WebView/WebView.mm:
+        (-[WebView _preferencesChanged:]):
+
 2018-03-10  Megan Gardner  <megan_gardner@apple.com>
 
         Media query for default appearance
index 64d672a..c7fc01a 100644 (file)
 #define WebKitViewportFitEnabledPreferenceKey @"WebKitViewportFitEnabled"
 #define WebKitConstantPropertiesEnabledPreferenceKey @"WebKitConstantPropertiesEnabled"
 #define WebKitFetchAPIKeepAliveEnabledPreferenceKey @"WebKitFetchAPIKeepAliveEnabled"
+#define WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabledPreferenceKey @"WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled"
 
 #if !TARGET_OS_IPHONE
 // These are private both because callers should be using the cover methods and because the
index 95dee9c..85dc3e8 100644 (file)
@@ -622,6 +622,8 @@ public:
         [NSNumber numberWithBool:YES], WebKitDataTransferItemsEnabledPreferenceKey,
         [NSNumber numberWithBool:NO], WebKitCustomPasteboardDataEnabledPreferenceKey,
         [NSNumber numberWithBool:YES], WebKitModernMediaControlsEnabledPreferenceKey,
+        [NSNumber numberWithBool:NO], WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabledPreferenceKey,
+
 #if ENABLE(WEBGL2)
         [NSNumber numberWithBool:NO], WebKitWebGL2EnabledPreferenceKey,
 #endif
@@ -3115,6 +3117,16 @@ static NSString *classIBCreatorID = nil;
     [self _setBoolValue:flag forKey:WebKitModernMediaControlsEnabledPreferenceKey];
 }
 
+- (BOOL)cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled
+{
+    return [self _boolValueForKey:WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabledPreferenceKey];
+}
+
+- (void)setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled:(BOOL)flag
+{
+    [self _setBoolValue:flag forKey:WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabledPreferenceKey];
+}
+
 - (BOOL)intersectionObserverEnabled
 {
     return [self _boolValueForKey:WebKitIntersectionObserverEnabledPreferenceKey];
index 4fffc59..6779d04 100644 (file)
@@ -570,6 +570,9 @@ extern NSString *WebPreferencesCacheModelChangedInternalNotification;
 - (void)setModernMediaControlsEnabled:(BOOL)flag;
 - (BOOL)modernMediaControlsEnabled;
 
+- (void)setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled:(BOOL)flag;
+- (BOOL)cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled;
+
 - (void)setWebAuthenticationEnabled:(BOOL)flag;
 - (BOOL)webAuthenticationEnabled;
 
index a011462..2c7d4c7 100644 (file)
@@ -3005,6 +3005,7 @@ static bool needsSelfRetainWhileLoadingQuirk()
 
     RuntimeEnabledFeatures::sharedFeatures().setInteractiveFormValidationEnabled([self interactiveFormValidationEnabled]);
     RuntimeEnabledFeatures::sharedFeatures().setModernMediaControlsEnabled([preferences modernMediaControlsEnabled]);
+    RuntimeEnabledFeatures::sharedFeatures().setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled([preferences cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled]);
 
     RuntimeEnabledFeatures::sharedFeatures().setCacheAPIEnabled([preferences cacheAPIEnabled]);
     RuntimeEnabledFeatures::sharedFeatures().setFetchAPIEnabled([preferences fetchAPIEnabled]);
index 5aab523..c9f279d 100644 (file)
@@ -1,3 +1,23 @@
+2018-03-12  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement CSS Animations and CSS Transitions as Web Animations
+        https://bugs.webkit.org/show_bug.cgi?id=183504
+        <rdar://problem/38372965>
+
+        Reviewed by Dean Jackson and Jon Lee.
+
+        Add the missing WebKitLegacy support the cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled flag
+        which is required for the matching <!-- webkit-test-runner --> flag to work in DumpRenderTree.
+
+        * Interfaces/IWebPreferencesPrivate.idl:
+        * WebPreferences.cpp:
+        (WebPreferences::cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled):
+        (WebPreferences::setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled):
+        * WebPreferenceKeysPrivate.h
+        * WebPreferences.h:
+        * WebView.cpp:
+        (WebView::notifyPreferencesChanged):
+
 2018-03-08  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [JSC] Add inherits<T>(VM&) leveraging JSCast fast path
index 7f3214c..ed41ee1 100644 (file)
@@ -183,6 +183,8 @@ interface IWebPreferencesPrivate3 : IWebPreferencesPrivate2
     HRESULT setCustomElementsEnabled([in] BOOL enabled);
     HRESULT modernMediaControlsEnabled([out, retval] BOOL* enabled);
     HRESULT setModernMediaControlsEnabled([in] BOOL enabled);
+    HRESULT cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled([out, retval] BOOL* enabled);
+    HRESULT setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled([in] BOOL enabled);
 }
 
 [uuid(F9582D72-6348-45B1-AB09-39E33459B5B9)]
index a16ee1c..e50da8c 100644 (file)
 
 #define WebKitWebAnimationsEnabledPreferenceKey "WebKitWebAnimationsEnabled"
 
+#define WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabledPreferenceKey "WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled"
+
 #define WebKitUserTimingEnabledPreferenceKey "WebKitUserTimingEnabled"
 
 #define WebKitResourceTimingEnabledPreferenceKey "WebKitResourceTimingEnabled"
index 37cc020..8725269 100644 (file)
@@ -2029,6 +2029,20 @@ HRESULT WebPreferences::modernMediaControlsEnabled(_Out_ BOOL* enabled)
     return S_OK;
 }
 
+HRESULT WebPreferences::cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled(_Out_ BOOL* enabled)
+{
+    if (!enabled)
+        return E_POINTER;
+    *enabled = boolValueForKey(WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabledPreferenceKey);
+    return S_OK;
+}
+
+HRESULT WebPreferences::setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled(BOOL enabled)
+{
+    setBoolValue(WebKitCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabledPreferenceKey, enabled);
+    return S_OK;
+}
+
 HRESULT WebPreferences::fetchAPIKeepAliveEnabled(_Out_ BOOL* enabled)
 {
     if (!enabled)
index b77a557..d468137 100644 (file)
@@ -240,7 +240,9 @@ public:
     virtual HRESULT STDMETHODCALLTYPE setCustomElementsEnabled(BOOL);
     virtual HRESULT STDMETHODCALLTYPE modernMediaControlsEnabled(_Out_ BOOL*);
     virtual HRESULT STDMETHODCALLTYPE setModernMediaControlsEnabled(BOOL);
-
+    virtual HRESULT STDMETHODCALLTYPE cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled(_Out_ BOOL*);
+    virtual HRESULT STDMETHODCALLTYPE setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled(BOOL);
+    
     // IWebPreferencesPrivate4
     virtual HRESULT STDMETHODCALLTYPE setApplicationId(BSTR);
     virtual HRESULT STDMETHODCALLTYPE webAnimationsEnabled(_Out_ BOOL*);
index 2d957ea..39808a0 100644 (file)
@@ -5217,6 +5217,11 @@ HRESULT WebView::notifyPreferencesChanged(IWebNotification* notification)
         return hr;
     RuntimeEnabledFeatures::sharedFeatures().setWebAnimationsEnabled(!!enabled);
 
+    hr = prefsPrivate->cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled(&enabled);
+    if (FAILED(hr))
+        return hr;
+    RuntimeEnabledFeatures::sharedFeatures().setCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled(!!enabled);
+
     hr = prefsPrivate->userTimingEnabled(&enabled);
     if (FAILED(hr))
         return hr;
index 3f29acf..96d8a6e 100644 (file)
@@ -1,3 +1,25 @@
+2018-03-12  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement CSS Animations and CSS Transitions as Web Animations
+        https://bugs.webkit.org/show_bug.cgi?id=183504
+        <rdar://problem/38372965>
+
+        Reviewed by Jon Lee.
+
+        Add a new <!-- webkit-test-runner --> flag to enable the CSS Animations and CSS Transitions
+        as Web Animations runtime flag in the new tests we've created for this feature.
+
+        * DumpRenderTree/TestOptions.h:
+        * DumpRenderTree/TestOptions.mm:
+        (TestOptions::TestOptions):
+        * DumpRenderTree/mac/DumpRenderTree.mm:
+        (setWebPreferencesForTestOptions):
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::resetPreferencesToConsistentValues):
+        (WTR::updateTestOptionsFromTestHeader):
+        * WebKitTestRunner/TestOptions.h:
+        (WTR::TestOptions::hasSameInitializationOptions const):
+
 2018-03-11  Zalan Bujtas  <zalan@apple.com>
 
         [LayoutReloaded] Add Line class for InlineFormattingContext -and move files around.
index 5d4ca95..df92dee 100644 (file)
@@ -30,6 +30,7 @@
 
 struct TestOptions {
     bool enableAttachmentElement { false };
+    bool enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations { false };
     bool useAcceleratedDrawing { false };
     bool enableIntersectionObserver { false };
     bool enableMenuItemElement { false };
index 2be3c2d..c7a4802 100644 (file)
@@ -104,6 +104,8 @@ TestOptions::TestOptions(NSURL *testURL, const TestCommand& command)
             this->dumpJSConsoleLogInStdErr = parseBooleanTestHeaderValue(value);
         else if (key == "allowCrossOriginSubresourcesToAskForCredentials")
             this->allowCrossOriginSubresourcesToAskForCredentials = parseBooleanTestHeaderValue(value);
+        else if (key == "enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations")
+            this->enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations = parseBooleanTestHeaderValue(value);
         pairStart = pairEnd + 1;
     }
 }
index 2ed38d6..33726cc 100644 (file)
@@ -995,6 +995,7 @@ static void setWebPreferencesForTestOptions(const TestOptions& options)
     preferences.isSecureContextAttributeEnabled = options.enableIsSecureContextAttribute;
     preferences.inspectorAdditionsEnabled = options.enableInspectorAdditions;
     preferences.allowCrossOriginSubresourcesToAskForCredentials = options.allowCrossOriginSubresourcesToAskForCredentials;
+    preferences.CSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled = options.enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations;
 }
 
 // Called once on DumpRenderTree startup.
index 3fae217..994efd5 100644 (file)
@@ -693,6 +693,7 @@ void TestController::resetPreferencesToConsistentValues(const TestOptions& optio
     WKPreferencesSetWebAuthenticationEnabled(preferences, options.enableWebAuthentication);
     WKPreferencesSetIsSecureContextAttributeEnabled(preferences, options.enableIsSecureContextAttribute);
     WKPreferencesSetAllowCrossOriginSubresourcesToAskForCredentials(preferences, options.allowCrossOriginSubresourcesToAskForCredentials);
+    WKPreferencesSetCSSAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled(preferences, options.enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations);
 
     static WKStringRef defaultTextEncoding = WKStringCreateWithUTF8CString("ISO-8859-1");
     WKPreferencesSetDefaultTextEncodingName(preferences, defaultTextEncoding);
@@ -1087,6 +1088,8 @@ static void updateTestOptionsFromTestHeader(TestOptions& testOptions, const std:
             testOptions.applicationManifest = parseStringTestHeaderValueAsRelativePath(value, pathOrURL);
         if (key == "allowCrossOriginSubresourcesToAskForCredentials")
             testOptions.allowCrossOriginSubresourcesToAskForCredentials = parseBooleanTestHeaderValue(value);
+        if (key == "enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations")
+            testOptions.enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations = parseBooleanTestHeaderValue(value);
         pairStart = pairEnd + 1;
     }
 }
index 0132b71..36b53ca 100644 (file)
@@ -55,6 +55,7 @@ struct TestOptions {
     bool shouldShowTouches { false };
     bool dumpJSConsoleLogInStdErr { false };
     bool allowCrossOriginSubresourcesToAskForCredentials { false };
+    bool enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations { false };
 
     float deviceScaleFactor { 1 };
     Vector<String> overrideLanguages;
@@ -84,7 +85,8 @@ struct TestOptions {
             || enableInspectorAdditions != options.enableInspectorAdditions
             || dumpJSConsoleLogInStdErr != options.dumpJSConsoleLogInStdErr
             || applicationManifest != options.applicationManifest
-            || allowCrossOriginSubresourcesToAskForCredentials != options.allowCrossOriginSubresourcesToAskForCredentials)
+            || allowCrossOriginSubresourcesToAskForCredentials != options.allowCrossOriginSubresourcesToAskForCredentials
+            || enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations != options.enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations)
             return false;
 
         return true;