summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/css-animations
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--testing/web-platform/tests/css/css-animations/AnimationEffect-getComputedTiming.tentative.html612
-rw-r--r--testing/web-platform/tests/css/css-animations/AnimationEffect-updateTiming.tentative.html174
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-animationName.tentative.html40
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-canceling.tentative.html196
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-compositeOrder.tentative.html53
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-effect.tentative.html225
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-finished.tentative.html85
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-getCurrentTime.tentative.html68
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-getKeyframes-crash.html26
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-id.tentative.html27
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-pausing.tentative.html259
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-playState.tentative.html55
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-ready.tentative.html98
-rw-r--r--testing/web-platform/tests/css/css-animations/CSSAnimation-startTime.tentative.html69
-rw-r--r--testing/web-platform/tests/css/css-animations/Document-getAnimations.tentative.html430
-rw-r--r--testing/web-platform/tests/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html163
-rw-r--r--testing/web-platform/tests/css/css-animations/Element-getAnimations.tentative.html453
-rw-r--r--testing/web-platform/tests/css/css-animations/KeyframeEffect-getKeyframes.tentative.html878
-rw-r--r--testing/web-platform/tests/css/css-animations/KeyframeEffect-setKeyframes.tentative.html122
-rw-r--r--testing/web-platform/tests/css/css-animations/KeyframeEffect-target.tentative.html63
-rw-r--r--testing/web-platform/tests/css/css-animations/META.yml7
-rw-r--r--testing/web-platform/tests/css/css-animations/animate-with-color-mix.html162
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-base-response-001.html75
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-base-response-002.html25
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-base-response-003.html26
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-base-response-004.html35
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-before-initial-box-construction-001.html53
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-canceled-by-parent-details-element-being-closed.html35
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-change-underlying-value-changed-in-flight.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-common-ref.html13
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-composition-keyframes.html113
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-composition.html110
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-css-variable-dependent-property.html55
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-css-variable-in-keyframe-adjusted.html68
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-001-manual.html67
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-002-manual.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-003-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-004-manual.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-005-manual.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-006-manual.html46
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-007-manual.html47
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-008.html37
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-009.html46
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-010.html46
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-delay-011.html24
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-direction-001-manual.html47
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-direction-002-manual.html44
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-direction-003-manual.html46
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-direction-004-manual.html44
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-direction-005-manual.html44
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-direction-006-manual.html44
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-display-manual.html51
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-duration-001-manual.html47
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-duration-002-manual.html39
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-duration-003-manual.html48
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-duration-004-manual.html48
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-duration-005-manual.html50
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-duration-006-manual.html50
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-duration-007-manual.html38
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-duration-008-manual.html38
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-fill-mode-001-manual.html40
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-fill-mode-002-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-fill-mode-003-manual.html45
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-fill-mode-004-manual.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-fill-mode-005-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-fill-mode-006-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-important-001.html144
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-important-002-ref.html9
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-important-002.html21
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-001-manual.html40
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-002-manual.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-003-manual.html40
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-004-manual.html51
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-005-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-006-manual.html49
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-007-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-008-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-009.html46
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-count-calc.html15
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-iteration-event-manual.html60
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-keyframes-001-manual.html46
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-keyframes-002-manual.html52
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-keyframes-003-manual.html44
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-multiple-from-to-keyframes-with-only-timing-function.html27
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-name-001-manual.html47
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-name-002-manual.html40
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-name-003-manual.html46
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-name-004-manual.html47
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-name-005-manual.html39
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-name-006-manual.html39
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-opacity-pause-and-set-time-ref.html17
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-opacity-pause-and-set-time.html36
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-play-state-001-manual.html58
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-play-state-002-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-play-state-003-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-play-state-004-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-play-state-005.tentative.html44
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-pseudo-dynamic-001-ref.html16
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-pseudo-dynamic-001.html37
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-restarted-after-changing-iteration-count-after-completion.html38
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-shorthand-001-manual.html37
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-shorthand-002-manual.html40
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-shorthand-003-manual.html40
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-style-element-replaced-with-keyframes-rule-of-same-name.html36
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-001-manual.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-002-manual.html43
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-003-manual.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-004-manual.html43
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-005-manual.html43
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-006-manual.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-007-manual.html51
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-008-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-009-manual.html67
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-010-manual.html67
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-011-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-timing-function-012-manual.html41
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-transform-pause-and-set-time-ref.html17
-rw-r--r--testing/web-platform/tests/css/css-animations/animation-transform-pause-and-set-time.html35
-rw-r--r--testing/web-platform/tests/css/css-animations/animationevent-interface.html10
-rw-r--r--testing/web-platform/tests/css/css-animations/animationevent-interface.js220
-rw-r--r--testing/web-platform/tests/css/css-animations/animationevent-marker-pseudoelement.html35
-rw-r--r--testing/web-platform/tests/css/css-animations/animationevent-pseudoelement.html29
-rw-r--r--testing/web-platform/tests/css/css-animations/animationevent-types.html84
-rw-r--r--testing/web-platform/tests/css/css-animations/animationstart-and-animationend-events-manual.html54
-rw-r--r--testing/web-platform/tests/css/css-animations/cancel-animation-shadow-slot-invalidation.html27
-rw-r--r--testing/web-platform/tests/css/css-animations/computed-style-animation-parsing.html85
-rw-r--r--testing/web-platform/tests/css/css-animations/crashtests/add-pseudo-while-animating-001.html43
-rw-r--r--testing/web-platform/tests/css/css-animations/crashtests/replace-keyframes-animating-filter-001.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/dialog-animation.html44
-rw-r--r--testing/web-platform/tests/css/css-animations/dialog-backdrop-animation.html44
-rw-r--r--testing/web-platform/tests/css/css-animations/display-interpolation.html79
-rw-r--r--testing/web-platform/tests/css/css-animations/display-none-dont-cancel.tentative.html146
-rw-r--r--testing/web-platform/tests/css/css-animations/event-dispatch.tentative.html437
-rw-r--r--testing/web-platform/tests/css/css-animations/event-order.tentative.html259
-rw-r--r--testing/web-platform/tests/css/css-animations/flip-running-animation-via-variable-ref.html27
-rw-r--r--testing/web-platform/tests/css/css-animations/flip-running-animation-via-variable.html67
-rw-r--r--testing/web-platform/tests/css/css-animations/historical.html18
-rw-r--r--testing/web-platform/tests/css/css-animations/idlharness.html48
-rw-r--r--testing/web-platform/tests/css/css-animations/inheritance-pseudo-element-ref.html26
-rw-r--r--testing/web-platform/tests/css/css-animations/inheritance-pseudo-element.html49
-rw-r--r--testing/web-platform/tests/css/css-animations/inheritance.html28
-rw-r--r--testing/web-platform/tests/css/css-animations/keyframes-remove-documentElement-crash.html28
-rw-r--r--testing/web-platform/tests/css/css-animations/keyframes-unrelated-custom-property.html29
-rw-r--r--testing/web-platform/tests/css/css-animations/keyframes-zero-angle-crash.html13
-rw-r--r--testing/web-platform/tests/css/css-animations/missing-values-first-keyframe.html84
-rw-r--r--testing/web-platform/tests/css/css-animations/missing-values-last-keyframe.html84
-rw-r--r--testing/web-platform/tests/css/css-animations/nested-scale-animations-ref.html4
-rw-r--r--testing/web-platform/tests/css/css-animations/nested-scale-animations.html42
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-composition-computed.tentative.html18
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-composition-invalid.tentative.html21
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-composition-valid.tentative.html20
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-computed.html52
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-computed.html20
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-computed.tentative.html12
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-invalid.tentative.html29
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-valid.tentative.html11
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-invalid.html22
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-shorthand-computed.html16
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-shorthand.html49
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-computed.tentative.html12
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-invalid.tentative.html29
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-valid.tentative.html11
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-delay-valid.html20
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-direction-computed.html18
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-direction-invalid.html21
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-direction-valid.html21
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-duration-computed.html48
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-duration-invalid.html23
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-duration-valid.html19
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-computed.html18
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-invalid.html21
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-valid.html21
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-invalid.html30
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-computed.html18
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-invalid.html22
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-valid.html22
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-name-computed.html32
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-name-invalid.html25
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-name-valid.html29
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-play-state-computed.html18
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-play-state-invalid.html21
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-play-state-valid.html19
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-range-end-computed.html36
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-range-end-invalid.html20
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-range-end-valid.html32
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-range-shorthand.html178
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-range-start-computed.html35
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-range-start-invalid.html16
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-range-start-valid.html32
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-shorthand.html57
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/animation-valid.html38
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/keyframes-allowed-properties.html35
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/keyframes-name-invalid.html46
-rw-r--r--testing/web-platform/tests/css/css-animations/parsing/keyframes-name-valid.html70
-rw-r--r--testing/web-platform/tests/css/css-animations/pending-style-changes-001.html34
-rw-r--r--testing/web-platform/tests/css/css-animations/responsive/column-rule-color-001.html61
-rw-r--r--testing/web-platform/tests/css/css-animations/responsive/column-width-001.html76
-rw-r--r--testing/web-platform/tests/css/css-animations/responsive/line-height.html45
-rw-r--r--testing/web-platform/tests/css/css-animations/sample-on-last-keyframe.html36
-rw-r--r--testing/web-platform/tests/css/css-animations/simultaneous-animations-crash.html19
-rw-r--r--testing/web-platform/tests/css/css-animations/style-animation-parsing.html53
-rw-r--r--testing/web-platform/tests/css/css-animations/support/empty-sheet.css0
-rw-r--r--testing/web-platform/tests/css/css-animations/support/testcommon.js249
-rw-r--r--testing/web-platform/tests/css/css-animations/svg-transform-animation-ref.html3
-rw-r--r--testing/web-platform/tests/css/css-animations/svg-transform-animation.html14
-rw-r--r--testing/web-platform/tests/css/css-animations/transform-animation-under-large-scale-ref.html3
-rw-r--r--testing/web-platform/tests/css/css-animations/transform-animation-under-large-scale.html17
-rw-r--r--testing/web-platform/tests/css/css-animations/transition-properties-not-animatable.html32
-rw-r--r--testing/web-platform/tests/css/css-animations/translation-animation-on-important-property-ref.html12
-rw-r--r--testing/web-platform/tests/css/css-animations/translation-animation-on-important-property.html18
-rw-r--r--testing/web-platform/tests/css/css-animations/translation-animation-subpixel-offset-ref.html12
-rw-r--r--testing/web-platform/tests/css/css-animations/translation-animation-subpixel-offset.html37
-rw-r--r--testing/web-platform/tests/css/css-animations/webkit-writing-mode-crash.html20
213 files changed, 12836 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-animations/AnimationEffect-getComputedTiming.tentative.html b/testing/web-platform/tests/css/css-animations/AnimationEffect-getComputedTiming.tentative.html
new file mode 100644
index 0000000000..46c26fcb99
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/AnimationEffect-getComputedTiming.tentative.html
@@ -0,0 +1,612 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>AnimationEffect.getComputedTiming() for CSS animations</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes moveAnimation {
+ from { margin-left: 100px }
+ to { margin-left: 200px }
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+
+'use strict';
+
+// --------------------
+// delay
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().delay, 0, 'Initial value of delay');
+}, 'delay of a new animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s -10s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().delay, -10 * MS_PER_SEC,
+ 'Initial value of delay');
+}, 'Negative delay of a new animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s 10s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().delay, 10 * MS_PER_SEC,
+ 'Initial value of delay');
+}, 'Positive delay of a new animation');
+
+
+// --------------------
+// endDelay
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().endDelay, 0,
+ 'Initial value of endDelay');
+}, 'endDelay of a new animation');
+
+
+// --------------------
+// fill
+// --------------------
+
+test(t => {
+ const getEffectWithFill = fill => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s ' + fill });
+ return div.getAnimations()[0].effect;
+ };
+
+ let effect = getEffectWithFill('');
+ assert_equals(effect.getComputedTiming().fill, 'none',
+ 'Initial value of fill');
+ effect = getEffectWithFill('forwards');
+ assert_equals(effect.getComputedTiming().fill, 'forwards',
+ 'Fill forwards');
+ effect = getEffectWithFill('backwards');
+ assert_equals(effect.getComputedTiming().fill, 'backwards',
+ 'Fill backwards');
+ effect = getEffectWithFill('both');
+ assert_equals(effect.getComputedTiming().fill, 'both',
+ 'Fill forwards and backwards');
+}, 'fill of a new animation');
+
+
+// --------------------
+// iterationStart
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().iterationStart, 0,
+ 'Initial value of iterationStart');
+}, 'iterationStart of a new animation');
+
+
+// --------------------
+// iterations
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().iterations, 1,
+ 'Initial value of iterations');
+}, 'iterations of a new animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s 2016.5' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().iterations, 2016.5,
+ 'Initial value of iterations');
+}, 'iterations of a finitely repeating animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s infinite' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().iterations, Infinity,
+ 'Initial value of iterations');
+}, 'iterations of an infinitely repeating animation');
+
+
+// --------------------
+// duration
+// --------------------
+
+test(t => {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 100s -10s infinite'
+ });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().duration, 100 * MS_PER_SEC,
+ 'Initial value of duration');
+}, 'duration of a new animation');
+
+
+// --------------------
+// direction
+// --------------------
+
+test(t => {
+ const getEffectWithDir = dir => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s ' + dir });
+ return div.getAnimations()[0].effect;
+ };
+
+ let effect = getEffectWithDir('');
+ assert_equals(effect.getComputedTiming().direction, 'normal',
+ 'Initial value of normal direction');
+ effect = getEffectWithDir('reverse');
+ assert_equals(effect.getComputedTiming().direction, 'reverse',
+ 'Initial value of reverse direction');
+ effect = getEffectWithDir('alternate');
+ assert_equals(effect.getComputedTiming().direction, 'alternate',
+ 'Initial value of alternate direction');
+ effect = getEffectWithDir('alternate-reverse');
+ assert_equals(effect.getComputedTiming().direction, 'alternate-reverse',
+ 'Initial value of alternate-reverse direction');
+}, 'direction of a new animation');
+
+
+// --------------------
+// easing
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().easing, 'linear',
+ 'Initial value of easing');
+}, 'easing of a new animation');
+
+
+// ------------------------------
+// endTime
+// = max(start delay + active duration + end delay, 0)
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().endTime, 100 * MS_PER_SEC,
+ 'Initial value of endTime');
+}, 'endTime of an new animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s -5s' });
+ const effect = div.getAnimations()[0].effect;
+ const answer = (100 - 5) * MS_PER_SEC;
+ assert_equals(effect.getComputedTiming().endTime, answer,
+ 'Initial value of endTime');
+}, 'endTime of an animation with a negative delay');
+
+test(t => {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 10s -100s infinite'
+ });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().endTime, Infinity,
+ 'Initial value of endTime');
+}, 'endTime of an infinitely repeating animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 0s 100s infinite' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().endTime, 100 * MS_PER_SEC,
+ 'Initial value of endTime');
+}, 'endTime of an infinitely repeating zero-duration animation');
+
+test(t => {
+ // Fill forwards so div.getAnimations()[0] won't return an
+ // undefined value.
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 10s -100s forwards'
+ });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().endTime, 0,
+ 'Initial value of endTime');
+}, 'endTime of an animation that finishes before its startTime');
+
+
+// --------------------
+// activeDuration
+// = iteration duration * iteration count
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s 5' });
+ const effect = div.getAnimations()[0].effect;
+ const answer = 100 * MS_PER_SEC * 5;
+ assert_equals(effect.getComputedTiming().activeDuration, answer,
+ 'Initial value of activeDuration');
+}, 'activeDuration of a new animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s infinite' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().activeDuration, Infinity,
+ 'Initial value of activeDuration');
+}, 'activeDuration of an infinitely repeating animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 0s 1s infinite' });
+ const effect = div.getAnimations()[0].effect;
+ // If either the iteration duration or iteration count are zero,
+ // the active duration is zero.
+ assert_equals(effect.getComputedTiming().activeDuration, 0,
+ 'Initial value of activeDuration');
+}, 'activeDuration of an infinitely repeating zero-duration animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s 1s 0' });
+ const effect = div.getAnimations()[0].effect;
+ // If either the iteration duration or iteration count are zero,
+ // the active duration is zero.
+ assert_equals(effect.getComputedTiming().activeDuration, 0,
+ 'Initial value of activeDuration');
+}, 'activeDuration of an animation with zero iterations');
+
+
+// --------------------
+// localTime
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().localTime, 0,
+ 'Initial value of localTime');
+}, 'localTime of a new animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const anim = div.getAnimations()[0];
+ anim.currentTime = 5 * MS_PER_SEC;
+ assert_times_equal(anim.effect.getComputedTiming().localTime,
+ anim.currentTime,
+ 'current localTime after setting currentTime');
+}, 'localTime of an animation is always equal to currentTime');
+
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+
+ const anim = div.getAnimations()[0];
+ anim.playbackRate = 2; // 2 times faster
+
+ await anim.ready;
+
+ assert_times_equal(anim.effect.getComputedTiming().localTime,
+ anim.currentTime,
+ 'localTime is equal to currentTime');
+
+ await waitForFrame();
+
+ assert_times_equal(anim.effect.getComputedTiming().localTime,
+ anim.currentTime,
+ 'localTime is equal to currentTime');
+}, 'localTime reflects playbackRate immediately');
+
+test(t => {
+ const div = addDiv(t);
+ const effect = new KeyframeEffect(div, {left: ["0px", "100px"]});
+
+ assert_equals(effect.getComputedTiming().localTime, null,
+ 'localTime for orphaned effect');
+}, 'localTime of an AnimationEffect without an Animation');
+
+
+// --------------------
+// progress
+//
+// Note: Even though CSS animations have a default animation-timing-function of
+// "ease", this only applies between keyframes (often referred to as the
+// keyframe-level easing). The progress value returned by getComputedTiming(),
+// however, only reflects effect-level easing and this defaults to "linear",
+// even for CSS animations.
+// --------------------
+
+test(t => {
+ const tests = [
+ { fill: '', progress: [null, null] },
+ { fill: 'none', progress: [null, null] },
+ { fill: 'forwards', progress: [null, 1.0] },
+ { fill: 'backwards', progress: [0.0, null] },
+ { fill: 'both', progress: [0.0, 1.0] },
+ ];
+ for (const test of tests) {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 100s 10s ' + test.fill
+ });
+ const anim = div.getAnimations()[0];
+ assert_true(anim.effect.getComputedTiming().progress === test.progress[0],
+ `Initial progress with "${test.fill}" fill`);
+ anim.finish();
+ assert_true(anim.effect.getComputedTiming().progress === test.progress[1],
+ `Initial progress with "${test.fill}" fill`);
+ }
+}, 'progress of an animation with different fill modes');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 10s 10 both' });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 0.0,
+ 'Initial value of progress');
+ anim.currentTime += 2.5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress');
+ anim.currentTime += 5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Value of progress');
+ anim.currentTime += 5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress');
+ anim.finish()
+ assert_equals(anim.effect.getComputedTiming().progress, 1.0,
+ 'Value of progress');
+}, 'progress of an integral repeating animation with normal direction');
+
+test(t => {
+ // Note: FillMode here is "both" because
+ // 1. Since this a zero-duration animation, it will already have finished
+ // so it won't be returned by getAnimations() unless it fills forwards.
+ // 2. Fill backwards, so the progress before phase wouldn't be
+ // unresolved (null value).
+ const div = addDiv(t, { style: 'animation: moveAnimation 0s infinite both' });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 1.0,
+ 'Initial value of progress in after phase');
+
+ // Seek backwards
+ anim.currentTime -= 1 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.0,
+ 'Value of progress before phase');
+}, 'progress of an infinitely repeating zero-duration animation');
+
+test(t => {
+ // Default iterations = 1
+ const div = addDiv(t, { style: 'animation: moveAnimation 0s both' });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 1.0,
+ 'Initial value of progress in after phase');
+
+ // Seek backwards
+ anim.currentTime -= 1 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.0,
+ 'Value of progress before phase');
+}, 'progress of a finitely repeating zero-duration animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 0s 5s 10.25 both' });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 0.0,
+ 'Initial value of progress (before phase)');
+
+ // Using iteration duration of 1 now.
+ // currentIteration now is floor(10.25) = 10, so progress should be 25%.
+ anim.finish();
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress in after phase');
+}, 'progress of a non-integral repeating zero-duration animation');
+
+test(t => {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 0s 5s 10.25 both reverse',
+ });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 1.0,
+ 'Initial value of progress (before phase)');
+
+ // Seek forwards
+ anim.finish();
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Value of progress in after phase');
+}, 'Progress of a non-integral repeating zero-duration animation ' +
+ 'with reversing direction');
+
+test(t => {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 10s 10.25 both alternate',
+ });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 0.0,
+ 'Initial value of progress');
+ anim.currentTime += 2.5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress');
+ anim.currentTime += 5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Value of progress');
+ anim.currentTime += 5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Value of progress');
+ anim.finish()
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress');
+}, 'progress of a non-integral repeating animation ' +
+ 'with alternate direction');
+
+test(t => {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 10s 10.25 both alternate-reverse',
+ });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 1.0,
+ 'Initial value of progress');
+ anim.currentTime += 2.5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Value of progress');
+ anim.currentTime += 5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress');
+ anim.currentTime += 5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress');
+ anim.finish()
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Value of progress');
+}, 'progress of a non-integral repeating animation ' +
+ 'with alternate-reversing direction');
+
+test(t => {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 0s 10.25 both alternate',
+ });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Initial value of progress');
+ anim.currentTime += 2.5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress');
+ anim.currentTime -= 5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.0,
+ 'Value of progress');
+ anim.finish()
+ assert_equals(anim.effect.getComputedTiming().progress, 0.25,
+ 'Value of progress');
+}, 'progress of a non-integral repeating zero-duration animation ' +
+ 'with alternate direction');
+
+test(t => {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 0s 10.25 both alternate-reverse',
+ });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Initial value of progress');
+ anim.currentTime += 2.5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Value of progress');
+ anim.currentTime -= 5 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().progress, 1.0,
+ 'Value of progress');
+ anim.finish()
+ assert_equals(anim.effect.getComputedTiming().progress, 0.75,
+ 'Value of progress');
+}, 'progress of a non-integral repeating zero-duration animation ' +
+ 'with alternate-reverse direction');
+
+
+// --------------------
+// currentIteration
+// --------------------
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s 2s' });
+ const effect = div.getAnimations()[0].effect;
+ assert_equals(effect.getComputedTiming().currentIteration, null,
+ 'Initial value of currentIteration before phase');
+}, 'currentIteration of a new animation with no backwards fill is unresolved ' +
+ 'in before phase');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
+ const anim = div.getAnimations()[0];
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+ 'Initial value of currentIteration');
+}, 'currentIteration of a new animation is zero');
+
+test(t => {
+ // Note: FillMode here is "both" because
+ // 1. Since this a zero-duration animation, it will already have finished
+ // so it won't be returned by getAnimations() unless it fills forwards.
+ // 2. Fill backwards, so the currentIteration (before phase) wouldn't be
+ // unresolved (null value).
+ const div = addDiv(t, { style: 'animation: moveAnimation 0s infinite both' });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().currentIteration, Infinity,
+ 'Initial value of currentIteration in after phase');
+
+ // Seek backwards
+ anim.currentTime -= 2 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+ 'Value of currentIteration count during before phase');
+}, 'currentIteration of an infinitely repeating zero-duration animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 0s 10.5 both' });
+ const anim = div.getAnimations()[0];
+
+ // Note: currentIteration = ceil(iteration start + iteration count) - 1
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 10,
+ 'Initial value of currentIteration');
+
+ // Seek backwards
+ anim.currentTime -= 2 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+ 'Value of currentIteration count during before phase');
+}, 'currentIteration of a finitely repeating zero-duration animation');
+
+test(t => {
+ const div = addDiv(t, {
+ style: 'animation: moveAnimation 100s 5.5 forwards'
+ });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+ 'Initial value of currentIteration');
+ // The 3rd iteration
+ // Note: currentIteration = floor(scaled active time / iteration duration)
+ anim.currentTime = 250 * MS_PER_SEC;
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 2,
+ 'Value of currentIteration during the 3rd iteration');
+ // Finish
+ anim.finish();
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 5,
+ 'Value of currentIteration in after phase');
+}, 'currentIteration of an animation with a non-integral iteration count');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s 2 forwards' });
+ const anim = div.getAnimations()[0];
+
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+ 'Initial value of currentIteration');
+ // Finish
+ anim.finish();
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 1,
+ 'Value of currentIteration in after phase');
+}, 'currentIteration of an animation with an integral iteration count');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: moveAnimation 100s forwards' });
+ const anim = div.getAnimations()[0];
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+ 'Initial value of currentIteration');
+ // Finish
+ anim.finish();
+ assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+ 'Value of currentIteration in after phase');
+}, 'currentIteration of an animation with a default iteration count');
+
+test(t => {
+ const div = addDiv(t);
+ const effect = new KeyframeEffect(div, {left: ["0px", "100px"]});
+
+ assert_equals(effect.getComputedTiming().currentIteration, null,
+ 'currentIteration for orphaned effect');
+}, 'currentIteration of an AnimationEffect without an Animation');
+
+// TODO: If iteration duration is Infinity, currentIteration is 0.
+// However, we cannot set iteration duration to Infinity in CSS Animation now.
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/css/css-animations/AnimationEffect-updateTiming.tentative.html b/testing/web-platform/tests/css/css-animations/AnimationEffect-updateTiming.tentative.html
new file mode 100644
index 0000000000..e6556dac4b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/AnimationEffect-updateTiming.tentative.html
@@ -0,0 +1,174 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>AnimationEffect.updateTiming() for CSS animations</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim-empty { }
+</style>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-empty 100s';
+
+ const animation = div.getAnimations()[0];
+ animation.effect.updateTiming({ duration: 2 * MS_PER_SEC });
+
+ div.style.animationDuration = '4s';
+ div.style.animationDelay = '6s';
+
+ assert_equals(
+ animation.effect.getTiming().duration,
+ 2 * MS_PER_SEC,
+ 'Duration should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().delay,
+ 6 * MS_PER_SEC,
+ 'Delay should be the value set by style'
+ );
+}, 'AnimationEffect.updateTiming({ duration }) causes changes to the'
+ + ' animation-duration to be ignored');
+
+// The above test covers duration (with delay as a control). The remaining
+// properties are:
+//
+// iterations - animation-iteration-count
+// direction - animation-direction
+// delay - animation-delay
+// fill - animation-fill-mode
+//
+// Which we test in two batches, overriding two properties each time and using
+// the remaining two properties as control values to check they are NOT
+// overridden.
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-empty 100s';
+
+ const animation = div.getAnimations()[0];
+ animation.effect.updateTiming({ iterations: 2, direction: 'reverse' });
+
+ div.style.animationIterationCount = '4';
+ div.style.animationDirection = 'alternate';
+ div.style.animationDelay = '6s';
+ div.style.animationFillMode = 'both';
+
+ assert_equals(
+ animation.effect.getTiming().iterations,
+ 2,
+ 'Iterations should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().direction,
+ 'reverse',
+ 'Direction should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().delay,
+ 6 * MS_PER_SEC,
+ 'Delay should be the value set by style'
+ );
+ assert_equals(
+ animation.effect.getTiming().fill,
+ 'both',
+ 'Fill should be the value set by style'
+ );
+}, 'AnimationEffect.updateTiming({ iterations, direction }) causes changes to'
+ + ' the animation-iteration-count and animation-direction to be ignored');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-empty 100s';
+
+ const animation = div.getAnimations()[0];
+ animation.effect.updateTiming({ delay: 2 * MS_PER_SEC, fill: 'both' });
+
+ div.style.animationDelay = '4s';
+ div.style.animationFillMode = 'forwards';
+ div.style.animationIterationCount = '6';
+ div.style.animationDirection = 'reverse';
+
+ assert_equals(
+ animation.effect.getTiming().delay,
+ 2 * MS_PER_SEC,
+ 'Delay should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().fill,
+ 'both',
+ 'Fill should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().iterations,
+ 6,
+ 'Iterations should be the value set by style'
+ );
+ assert_equals(
+ animation.effect.getTiming().direction,
+ 'reverse',
+ 'Direction should be the value set by style'
+ );
+}, 'AnimationEffect.updateTiming({ delay, fill }) causes changes to'
+ + ' the animation-delay and animation-fill-mode to be ignored');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-empty 100s';
+
+ const animation = div.getAnimations()[0];
+ assert_throws_js(TypeError, () => {
+ animation.effect.updateTiming({ duration: 2 * MS_PER_SEC, iterations: -1 });
+ }, 'Negative iteration count should cause an error to be thrown');
+
+ div.style.animationDuration = '4s';
+
+ assert_equals(
+ animation.effect.getTiming().duration,
+ 4 * MS_PER_SEC,
+ 'Duration should be the value set by style'
+ );
+}, 'AnimationEffect.updateTiming() does override to changes from animation-*'
+ + ' properties if there is an error');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-empty 100s';
+
+ const animation = div.getAnimations()[0];
+ animation.effect.updateTiming({
+ easing: 'steps(4)',
+ endDelay: 2 * MS_PER_SEC,
+ iterationStart: 4,
+ });
+
+ div.style.animationDuration = '6s';
+ div.style.animationTimingFunction = 'ease-in';
+
+ assert_equals(
+ animation.effect.getTiming().easing,
+ 'steps(4)',
+ 'endDelay should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().endDelay,
+ 2 * MS_PER_SEC,
+ 'endDelay should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().iterationStart,
+ 4,
+ 'iterationStart should be the value set by style'
+ );
+}, 'AnimationEffect properties that do not map to animation-* properties'
+ + ' should not be changed when animation-* style is updated');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-animationName.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-animationName.tentative.html
new file mode 100644
index 0000000000..370d5ef85e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-animationName.tentative.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.animationName</title>
+<link rel="help"
+ href="https://drafts.csswg.org/css-animations-2/#dom-cssanimation-animationname">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes xyz {
+ to { left: 100px }
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'xyz 100s';
+ assert_equals(div.getAnimations()[0].animationName, 'xyz',
+ 'Animation name matches keyframes rule name');
+}, 'Animation name makes keyframe rule');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'x\\yz 100s';
+ assert_equals(div.getAnimations()[0].animationName, 'xyz',
+ 'Escaped animation name matches keyframes rule name');
+}, 'Escaped animation name');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'x\\79 z 100s';
+ assert_equals(div.getAnimations()[0].animationName, 'xyz',
+ 'Hex-escaped animation name matches keyframes rule'
+ + ' name');
+}, 'Animation name with hex-escape');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-canceling.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-canceling.tentative.html
new file mode 100644
index 0000000000..b76af2c24a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-canceling.tentative.html
@@ -0,0 +1,196 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Canceling a CSS animation</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes translateAnim {
+ to { transform: translate(100px) }
+}
+@keyframes marginLeftAnim {
+ to { margin-left: 100px }
+}
+@keyframes marginLeftAnim100To200 {
+ from { margin-left: 100px }
+ to { margin-left: 200px }
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: translateAnim 100s' });
+ const animation = div.getAnimations()[0];
+
+ await animation.ready;
+
+ assert_not_equals(getComputedStyle(div).transform, 'none',
+ 'transform style is animated before canceling');
+ animation.cancel();
+ assert_equals(getComputedStyle(div).transform, 'none',
+ 'transform style is no longer animated after canceling');
+}, 'Animated style is cleared after canceling a running CSS animation');
+
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: translateAnim 100s forwards' });
+ const animation = div.getAnimations()[0];
+ animation.finish();
+
+ await animation.ready;
+
+ assert_not_equals(getComputedStyle(div).transform, 'none',
+ 'transform style is filling before canceling');
+ animation.cancel();
+ assert_equals(getComputedStyle(div).transform, 'none',
+ 'fill style is cleared after canceling');
+}, 'Animated style is cleared after canceling a filling CSS animation');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: marginLeftAnim 100s linear' });
+ const animation = div.getAnimations()[0];
+ animation.cancel();
+
+ assert_equals(getComputedStyle(div).marginLeft, '0px',
+ 'margin-left style is not animated after canceling');
+
+ animation.currentTime = 50 * 1000;
+ assert_equals(getComputedStyle(div).marginLeft, '50px',
+ 'margin-left style is updated when canceled animation is'
+ + ' seeked');
+}, 'After canceling an animation, it can still be seeked');
+
+promise_test(async t => {
+ const div =
+ addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
+ const animation = div.getAnimations()[0];
+
+ await animation.ready;
+
+ animation.cancel();
+ assert_equals(getComputedStyle(div).marginLeft, '0px',
+ 'margin-left style is not animated after canceling');
+ animation.play();
+ assert_equals(getComputedStyle(div).marginLeft, '100px',
+ 'margin-left style is animated after re-starting animation');
+
+ await animation.ready;
+
+ assert_equals(animation.playState, 'running',
+ 'Animation succeeds in running after being re-started');
+}, 'After canceling an animation, it can still be re-used');
+
+test(t => {
+ const div =
+ addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
+ const animation = div.getAnimations()[0];
+ animation.cancel();
+ assert_equals(getComputedStyle(div).marginLeft, '0px',
+ 'margin-left style is not animated after canceling');
+
+ // Trigger a change to some animation properties and check that this
+ // doesn't cause the animation to become live again
+ div.style.animationDuration = '200s';
+ assert_equals(getComputedStyle(div).marginLeft, '0px',
+ 'margin-left style is still not animated after updating'
+ + ' animation-duration');
+ assert_equals(animation.playState, 'idle',
+ 'Animation is still idle after updating animation-duration');
+}, 'After canceling an animation, updating animation properties doesn\'t make'
+ + ' it live again');
+
+test(t => {
+ const div =
+ addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
+ const animation = div.getAnimations()[0];
+ animation.cancel();
+ assert_equals(getComputedStyle(div).marginLeft, '0px',
+ 'margin-left style is not animated after canceling');
+
+ // Make some changes to animation-play-state and check that the
+ // animation doesn't become live again. This is because it should be
+ // possible to cancel an animation from script such that all future
+ // changes to style are ignored.
+
+ // Redundant change
+ div.style.animationPlayState = 'running';
+ assert_equals(animation.playState, 'idle',
+ 'Animation is still idle after a redundant change to'
+ + ' animation-play-state');
+
+ // Pause
+ div.style.animationPlayState = 'paused';
+ assert_equals(animation.playState, 'idle',
+ 'Animation is still idle after setting'
+ + ' animation-play-state: paused');
+
+ // Play
+ div.style.animationPlayState = 'running';
+ assert_equals(animation.playState, 'idle',
+ 'Animation is still idle after re-setting'
+ + ' animation-play-state: running');
+
+}, 'After canceling an animation, updating animation-play-state doesn\'t'
+ + ' make it live again');
+
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: translateAnim 10s both' });
+ div.style.marginLeft = '0px';
+
+ const animation = div.getAnimations()[0];
+
+ await animation.ready;
+
+ assert_equals(animation.playState, 'running');
+
+ div.style.animationName = 'none';
+ flushComputedStyle(div);
+
+ await waitForFrame();
+
+ assert_equals(animation.playState, 'idle');
+ assert_equals(getComputedStyle(div).marginLeft, '0px');
+}, 'Setting animation-name to \'none\' cancels the animation');
+
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: translateAnim 10s both' });
+ const animation = div.getAnimations()[0];
+
+ await animation.ready;
+
+ assert_equals(animation.playState, 'running');
+
+ div.style.display = 'none';
+
+ await waitForFrame();
+
+ assert_equals(animation.playState, 'idle');
+ assert_equals(getComputedStyle(div).marginLeft, '0px');
+}, 'Setting display:none on an element cancel its animations');
+
+promise_test(async t => {
+ const parentDiv = addDiv(t);
+ const childDiv = document.createElement('div');
+ parentDiv.appendChild(childDiv);
+
+ childDiv.setAttribute('style', 'animation: translateAnim 10s both');
+ flushComputedStyle(childDiv);
+
+ const animation = childDiv.getAnimations()[0];
+
+ await animation.ready;
+
+ assert_equals(animation.playState, 'running');
+
+ parentDiv.style.display = 'none';
+ await waitForFrame();
+
+ assert_equals(animation.playState, 'idle');
+ assert_equals(getComputedStyle(childDiv).marginLeft, '0px');
+}, 'Setting display:none on an ancestor element cancels animations on ' +
+ 'descendants');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-compositeOrder.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-compositeOrder.tentative.html
new file mode 100644
index 0000000000..d55db9a2d1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-compositeOrder.tentative.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Animation composite order</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-composite-order">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes margin50 {
+ from {
+ margin-left: 50px;
+ }
+ to {
+ margin-left: 50px;
+ }
+}
+@keyframes margin100 {
+ from {
+ margin-left: 100px;
+ }
+ to {
+ margin-left: 100px;
+ }
+}
+</style>
+<div id="log"</div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin100 100s';
+ assert_equals(getComputedStyle(div).marginLeft, '100px');
+ div.style.animation = 'margin50 100s, margin100 100s';
+ // The margin should be unaffected by margin50 since it is named earlier
+ // in the animation list.
+ assert_equals(getComputedStyle(div).marginLeft, '100px');
+}, "Animations are composited by their order in the animation-name property.");
+
+promise_test(async t => {
+ const div = addDiv(t);
+ const animA = div.animate({margin: ["100px","100px"]}, 100000);
+ assert_equals(getComputedStyle(div).marginLeft, '100px');
+ div.style.animation = 'margin50 100s';
+ // Wait for animation starts
+ await animA.ready;
+ await waitForAnimationFrames(1);
+ assert_equals(getComputedStyle(div).marginLeft, '100px',
+ "A higher-priority animation is not overriden by a more recent"
+ + "one.");
+}, 'Web-animation replaces CSS animation');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-effect.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-effect.tentative.html
new file mode 100644
index 0000000000..04812d24e5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-effect.tentative.html
@@ -0,0 +1,225 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.effect</title>
+<meta name="timeout" content="long">
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim {
+ from {
+ margin-left: 0px;
+ }
+ to {
+ margin-left: 100px;
+ }
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+
+ const watcher = new EventWatcher(t, div, [ 'animationend',
+ 'animationcancel' ],
+ fastEventsTimeout);
+ const animation = div.getAnimations()[0];
+ await animation.ready;
+
+ animation.currentTime = 50 * MS_PER_SEC;
+ animation.effect = null;
+ assert_equals(animation.playState, 'finished');
+ assert_equals(getComputedStyle(div).marginLeft, '0px');
+ await watcher.wait_for('animationend');
+}, 'Setting a null effect on a running animation fires an animationend event');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+
+ const animation = div.getAnimations()[0];
+ await animation.ready;
+
+ animation.currentTime = 50 * MS_PER_SEC;
+ animation.effect = new KeyframeEffect(div,
+ { left: [ '0px' , '100px'] },
+ 100 * MS_PER_SEC);
+ assert_equals(getComputedStyle(div).marginLeft, '0px');
+ assert_equals(getComputedStyle(div).left, '50px');
+}, 'Replacing an animation\'s effect with an effect that targets a different ' +
+ 'property should update both properties');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+
+ const animation = div.getAnimations()[0];
+ await animation.ready;
+
+ animation.currentTime = 50 * MS_PER_SEC;
+ animation.effect = new KeyframeEffect(div,
+ { left: [ '0px' , '100px'] },
+ 20 * MS_PER_SEC);
+ assert_equals(animation.playState, 'finished');
+}, 'Replacing an animation\'s effect with a shorter one that should have ' +
+ 'already finished, the animation finishes immediately');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+
+ const animation = div.getAnimations()[0];
+ assert_true(animation.pending);
+
+ animation.effect = new KeyframeEffect(div,
+ { left: [ '0px' , '100px'] },
+ 100 * MS_PER_SEC);
+ assert_true(animation.pending);
+
+ await animation.ready;
+
+ assert_false(animation.pending);
+}, 'A play-pending animation\'s effect whose effect is replaced still exits ' +
+ 'the pending state');
+
+promise_test(async t => {
+ const div1 = addDiv(t);
+ const div2 = addDiv(t);
+
+ const watcher1 = new EventWatcher(t, div1, 'animationstart',
+ fastEventsTimeout);
+ // Watch |div2| as well to ensure it does *not* get events.
+ const watcher2 = new EventWatcher(t, div2, 'animationstart');
+
+ div1.style.animation = 'anim 100s';
+
+ const animation = div1.getAnimations()[0];
+ animation.effect = new KeyframeEffect(div2,
+ { left: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+
+ await watcher1.wait_for('animationstart');
+
+ assert_equals(animation.effect.target, div2);
+
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'CSS animation events are dispatched at the original element even after'
+ + ' setting an effect with a different target element');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ const watcher = new EventWatcher(t, div, [ 'animationstart',
+ 'animationend',
+ 'animationcancel' ],
+ fastEventsTimeout);
+ div.style.animation = 'anim 100s';
+ const animation = div.getAnimations()[0];
+ animation.finish();
+
+ await watcher.wait_for([ 'animationstart', 'animationend' ]);
+ // Set a longer effect
+ animation.effect = new KeyframeEffect(div,
+ { left: [ '0px', '100px' ] },
+ 200 * MS_PER_SEC);
+ await watcher.wait_for('animationstart');
+}, 'After replacing a finished animation\'s effect with a longer one ' +
+ 'it fires an animationstart event');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+ div.style.animationComposition = 'add';
+ const animation = div.getAnimations()[0];
+ assert_equals(animation.effect.composite, 'add');
+}, 'Setting animation-composition sets the composite property on the effect');
+
+test(t => {
+ const div = addDiv(t);
+
+ // Create custom keyframes so we can tweak them
+ const stylesheet = document.styleSheets[0];
+ const keyframes = '@keyframes anim-custom { to { left: 100px } }';
+ const ruleIndex = stylesheet.insertRule(keyframes, 0);
+ const keyframesRule = stylesheet.cssRules[ruleIndex];
+
+ t.add_cleanup(function() {
+ stylesheet.deleteRule(ruleIndex);
+ });
+
+ div.style.animation = 'anim-custom 100s';
+
+ // Replace the effect
+ const animation = div.getAnimations()[0];
+ animation.effect = new KeyframeEffect(
+ div,
+ { left: '200px' },
+ 200 * MS_PER_SEC
+ );
+
+ // Update the timing properties
+ div.style.animationDuration = '4s';
+ div.style.animationIterationCount = '6';
+ div.style.animationDirection = 'reverse';
+ div.style.animationDelay = '8s';
+ div.style.animationFillMode = 'both';
+ div.style.animationPlayState = 'paused';
+ div.style.animationComposition = 'add';
+
+ // Update the keyframes
+ keyframesRule.deleteRule(0);
+ keyframesRule.appendRule('to { left: 300px }');
+
+ // Check nothing (except the play state) changed
+ assert_equals(
+ animation.effect.getTiming().duration,
+ 200 * MS_PER_SEC,
+ 'duration should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().iterations,
+ 1,
+ 'iterations should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().direction,
+ 'normal',
+ 'direction should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().delay,
+ 0,
+ 'delay should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getTiming().fill,
+ 'auto',
+ 'fill should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.getKeyframes()[0].left,
+ '200px',
+ 'keyframes should be the value set by the API'
+ );
+ assert_equals(
+ animation.effect.composite,
+ 'replace',
+ 'composite should be the value set by the API'
+ );
+
+ // Unlike the other properties animation-play-state maps to the Animation
+ // not the KeyframeEffect so it should be overridden.
+ assert_equals(
+ animation.playState,
+ 'paused',
+ 'play state should be the value set by style'
+ );
+}, 'Replacing the effect of a CSSAnimation causes subsequent changes to'
+ + ' corresponding animation-* properties to be ignored');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-finished.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-finished.tentative.html
new file mode 100644
index 0000000000..da087b93e0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-finished.tentative.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.finished</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes abc {
+ to { transform: translate(10px) }
+}
+@keyframes def {}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+const ANIM_PROP_VAL = 'abc 100s';
+const ANIM_DURATION = 100 * MS_PER_SEC;
+
+promise_test(async t => {
+ const div = addDiv(t);
+
+ // Set up pending animation
+ div.style.animation = ANIM_PROP_VAL;
+ const animation = div.getAnimations()[0];
+ const originalFinishedPromise = animation.finished;
+
+ // Cancel the animation and flush styles
+ div.style.animation = '';
+ getComputedStyle(div).animation;
+
+ await promise_rejects_dom(t, 'AbortError', originalFinishedPromise,
+ 'finished promise is rejected with AbortError');
+
+ assert_not_equals(animation.finished, originalFinishedPromise,
+ 'Finished promise should change after the original is ' +
+ 'rejected');
+}, 'finished promise is rejected when an animation is canceled by resetting ' +
+ 'the animation property');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ // As before, but this time instead of removing all animations, simply update
+ // the list of animations. At least for Firefox, updating is a different
+ // code path.
+
+ // Set up pending animation
+ div.style.animation = ANIM_PROP_VAL;
+ const animation = div.getAnimations()[0];
+ const originalFinishedPromise = animation.finished;
+
+ // Update the animation and flush styles
+ div.style.animation = 'def 100s';
+ getComputedStyle(div).animation;
+
+ await promise_rejects_dom(t, 'AbortError', originalFinishedPromise,
+ 'finished promise is rejected with AbortError');
+
+ assert_not_equals(animation.finished, originalFinishedPromise,
+ 'Finished promise should change after the original is ' +
+ 'rejected');
+}, 'finished promise is rejected when an animation is canceled by changing ' +
+ 'the animation property');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = ANIM_PROP_VAL;
+ const animation = div.getAnimations()[0];
+ const originalFinishedPromise = animation.finished;
+ animation.currentTime = ANIM_DURATION;
+
+ await animation.finished;
+
+ div.style.animationPlayState = 'running';
+ await waitForAnimationFrames(2);
+
+ assert_equals(animation.finished, originalFinishedPromise,
+ 'The finished promise should NOT be reset');
+ assert_equals(animation.currentTime, ANIM_DURATION,
+ 'Sanity check: the current time should not change');
+}, 'finished promise is not reset when animationPlayState is set to running');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-getCurrentTime.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-getCurrentTime.tentative.html
new file mode 100644
index 0000000000..e4d1f859fd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-getCurrentTime.tentative.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.currentTime</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+
+.animated-div {
+ margin-left: 10px;
+ /* Make it easier to calculate expected values: */
+ animation-timing-function: linear ! important;
+}
+
+@keyframes anim {
+ from { margin-left: 100px; }
+ to { margin-left: 200px; }
+}
+
+</style>
+<div id="log"></div>
+<script type="text/javascript">
+
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t, { class: 'animated-div' });
+ div.style.animation = 'anim 100s';
+ const animation = div.getAnimations()[0];
+
+ assert_equals(
+ animation.currentTime,
+ 0,
+ 'Animation.currentTime should be zero when an animation ' +
+ 'is initially created'
+ );
+
+ await animation.ready;
+
+ animation.currentTime = 50 * MS_PER_SEC;
+
+ assert_time_equals_literal(
+ animation.currentTime,
+ 50 * MS_PER_SEC,
+ 'Check setting of currentTime actually works'
+ );
+ assert_equals(getComputedStyle(div).marginLeft, '150px');
+}, 'currentTime can be used to seek a CSS animation');
+
+promise_test(async t => {
+ const div = addDiv(t, { class: 'animated-div' });
+ div.style.animation = 'anim 100s';
+
+ const animation = div.getAnimations()[0];
+ await animation.ready;
+
+ assert_throws_js(
+ TypeError,
+ () => {
+ animation.currentTime = null;
+ },
+ 'Expect TypeError exception on trying to set Animation.currentTime to null'
+ );
+}, 'Setting currentTime to null on a CSS animation throws');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-getKeyframes-crash.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-getKeyframes-crash.html
new file mode 100644
index 0000000000..72eb5e8819
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-getKeyframes-crash.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="help" href="https://crbug.com/1326225">
+ <title>Crash test calling getKeyframes on an orphaned element</title>
+</head>
+<style type="text/css">
+ @keyframes anim {
+ from { left: 0; }
+ }
+</style>
+<body>
+ <div id="container">
+ <div id="target">
+ </div>
+ </div>
+</body>
+<script type="text/javascript">
+ target.style.animation = "anim 0.01s";
+ var animation = target.getAnimations()[0];
+ container.innerHTML = 1;
+ animation.effect.getKeyframes()[0].hasOwnProperty();
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-id.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-id.tentative.html
new file mode 100644
index 0000000000..623e01d235
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-id.tentative.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.id</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes abc { }
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'abc 100s';
+ const animation = div.getAnimations()[0];
+ assert_equals(animation.id, '', 'id for CSS Animation is initially empty');
+
+ animation.id = 'anim'
+ assert_equals(animation.id, 'anim', 'animation.id reflects the value set');
+}, 'Animation.id for CSS Animations');
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-pausing.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-pausing.tentative.html
new file mode 100644
index 0000000000..156a1afa96
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-pausing.tentative.html
@@ -0,0 +1,259 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Pausing a CSSAnimation</title>
+<link rel="help"
+ href="https://drafts.csswg.org/css-animations-2/#animation-play-state">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim {
+ 0% { margin-left: 0px }
+ 100% { margin-left: 10000px }
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 1000s paused';
+
+ const animation = div.getAnimations()[0];
+ animation.play();
+
+ await animation.ready;
+ await waitForNextFrame();
+
+ assert_equals(
+ animation.playState,
+ 'running',
+ 'Play state is running after calling play()'
+ );
+
+ // Flip the animation-play-state back and forth to check it has no effect
+
+ div.style.animationPlayState = 'running';
+ getComputedStyle(div).animationPlayState;
+ div.style.animationPlayState = 'paused';
+ getComputedStyle(div).animationPlayState;
+
+ assert_equals(
+ animation.playState,
+ 'running',
+ 'Should still be running even after flipping the animation-play-state'
+ );
+}, 'play() overrides animation-play-state');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s infinite paused';
+
+ const animation = div.getAnimations()[0];
+ animation.playbackRate = -1;
+ animation.currentTime = -1;
+
+ assert_throws_dom('InvalidStateError', () => {
+ animation.play();
+ }, 'Trying to play a reversed infinite animation should throw');
+
+ assert_equals(
+ animation.playState,
+ 'paused',
+ 'Animation should still be paused'
+ );
+
+ animation.playbackRate = 1;
+ div.style.animationPlayState = 'running';
+
+ assert_equals(
+ animation.playState,
+ 'running',
+ 'Changing the animation-play-state should play the animation'
+ );
+}, 'play() does NOT override the animation-play-state if there was an error');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 1000s paused';
+
+ const animation = div.getAnimations()[0];
+ animation.pause();
+
+ div.style.animationPlayState = 'running';
+ getComputedStyle(div).animationPlayState;
+
+ await animation.ready;
+ await waitForNextFrame();
+
+ assert_equals(animation.playState, 'paused', 'playState is paused ');
+
+ // Flip the animation-play-state back and forth to check it has no effect
+
+ div.style.animationPlayState = 'paused';
+ getComputedStyle(div).animationPlayState;
+ div.style.animationPlayState = 'running';
+ getComputedStyle(div).animationPlayState;
+
+ assert_equals(
+ animation.playState,
+ 'paused',
+ 'Should still be paused even after flipping the animation-play-state'
+ );
+}, 'pause() overrides animation-play-state');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s paused';
+
+ const animation = div.getAnimations()[0];
+ animation.reverse();
+
+ assert_equals(
+ animation.playState,
+ 'running',
+ 'Play state is running after calling reverse()'
+ );
+
+ // Flip the animation-play-state back and forth to check it has no effect
+
+ div.style.animationPlayState = 'running';
+ getComputedStyle(div).animationPlayState;
+ div.style.animationPlayState = 'paused';
+ getComputedStyle(div).animationPlayState;
+
+ assert_equals(
+ animation.playState,
+ 'running',
+ 'Should still be running even after flipping the animation-play-state'
+ );
+}, 'reverse() overrides animation-play-state when it starts playing the'
+ + ' animation');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+
+ const animation = div.getAnimations()[0];
+ animation.reverse();
+
+ assert_equals(
+ animation.playState,
+ 'running',
+ 'Play state is running after calling reverse()'
+ );
+
+ div.style.animationPlayState = 'paused';
+ getComputedStyle(div).animationPlayState;
+
+ assert_equals(
+ animation.playState,
+ 'paused',
+ 'Should be paused after changing the animation-play-state'
+ );
+}, 'reverse() does NOT override animation-play-state if the animation is'
+ + ' already running');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+
+ const animation = div.getAnimations()[0];
+ animation.startTime = null;
+
+ assert_equals(
+ animation.playState,
+ 'paused',
+ 'Play state is paused after setting the start time to null'
+ );
+
+ // Flip the animation-play-state back and forth to check it has no effect
+
+ div.style.animationPlayState = 'paused';
+ getComputedStyle(div).animationPlayState;
+ div.style.animationPlayState = 'running';
+ getComputedStyle(div).animationPlayState;
+
+ assert_equals(
+ animation.playState,
+ 'paused',
+ 'Should still be paused even after flipping the animation-play-state'
+ );
+}, 'Setting the startTime to null overrides animation-play-state if the'
+ + ' animation is already running');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s paused';
+
+ const animation = div.getAnimations()[0];
+ animation.startTime = document.timeline.currentTime;
+
+ assert_equals(
+ animation.playState,
+ 'running',
+ 'Play state is running after setting the start time to non-null'
+ );
+
+ // Flip the animation-play-state back and forth to check it has no effect
+
+ div.style.animationPlayState = 'running';
+ getComputedStyle(div).animationPlayState;
+ div.style.animationPlayState = 'paused';
+ getComputedStyle(div).animationPlayState;
+
+ assert_equals(
+ animation.playState,
+ 'running',
+ 'Should still be running even after flipping the animation-play-state'
+ );
+}, 'Setting the startTime to non-null overrides animation-play-state if the'
+ + ' animation is paused');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+
+ const animation = div.getAnimations()[0];
+ animation.startTime = document.timeline.currentTime;
+
+ div.style.animationPlayState = 'paused';
+ getComputedStyle(div).animationPlayState;
+
+ assert_equals(
+ animation.playState,
+ 'paused',
+ 'Should be paused after changing the animation-play-state'
+ );
+}, 'Setting the startTime to non-null does NOT override the'
+ + ' animation-play-state if the animation is already running');
+
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: anim 1000s' });
+ const animation = div.getAnimations()[0];
+ let readyPromiseRun = false;
+
+ await animation.ready;
+
+ div.style.animationPlayState = 'paused';
+ assert_true(animation.pending && animation.playState === 'paused',
+ 'Animation is pause-pending');
+
+ // Set current time
+ animation.currentTime = 5 * MS_PER_SEC;
+ assert_equals(animation.playState, 'paused',
+ 'Animation is paused immediately after setting currentTime');
+ assert_equals(animation.startTime, null,
+ 'Animation startTime is unresolved immediately after ' +
+ 'setting currentTime');
+ assert_equals(animation.currentTime, 5 * MS_PER_SEC,
+ 'Animation currentTime does not change when forcing a ' +
+ 'pause operation to complete');
+
+ // The ready promise should now be resolved. If it's not then test will
+ // probably time out before anything else happens that causes it to resolve.
+ await animation.ready;
+}, 'Setting the current time completes a pending pause');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-playState.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-playState.tentative.html
new file mode 100644
index 0000000000..acfdc1348f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-playState.tentative.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.playState</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim { }
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+ const div = addDiv(t, { 'style': 'animation: anim 100s' });
+ const animation = div.getAnimations()[0];
+ assert_true(animation.pending);
+ assert_equals(animation.playState, 'running');
+ assert_equals(animation.startTime, null);
+}, 'A new CSS animation is initially play-pending');
+
+test(t => {
+ const div = addDiv(t, { 'style': 'animation: anim 1000s paused' });
+ const animation = div.getAnimations()[0];
+ assert_equals(animation.playState, 'paused');
+}, 'Animation returns correct playState when paused');
+
+test(t => {
+ const div = addDiv(t, { 'style': 'animation: anim 1000s' });
+ const animation = div.getAnimations()[0];
+ animation.pause();
+ assert_equals(animation.playState, 'paused');
+}, 'Animation.playState updates when paused by script');
+
+test(t => {
+ const div = addDiv(t, { 'style': 'animation: anim 1000s paused' });
+ const animation = div.getAnimations()[0];
+ div.style.animationPlayState = 'running';
+
+ // This test also checks that calling playState flushes style
+ assert_equals(animation.playState, 'running',
+ 'Animation.playState reports running after updating'
+ + ' animation-play-state (got: ' + animation.playState + ')');
+}, 'Animation.playState updates when resumed by setting style');
+
+test(t => {
+ const div = addDiv(t, { 'style': 'animation: anim 1000s' });
+ const animation = div.getAnimations()[0];
+ animation.cancel();
+ assert_equals(animation.playState, 'idle');
+}, 'Animation returns correct playState when canceled');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-ready.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-ready.tentative.html
new file mode 100644
index 0000000000..2a22760693
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-ready.tentative.html
@@ -0,0 +1,98 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.ready</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes abc {
+ to { transform: translate(10px) }
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'abc 100s paused';
+ const animation = div.getAnimations()[0];
+ const originalReadyPromise = animation.ready;
+
+ await animation.ready;
+
+ div.style.animationPlayState = 'running';
+ assert_not_equals(animation.ready, originalReadyPromise,
+ 'After updating animation-play-state a new ready promise'
+ + ' object is created');
+}, 'A new ready promise is created when setting animation-play-state: running');
+
+promise_test(async t => {
+ const div = addDiv(t);
+
+ // Set up pending animation
+ div.style.animation = 'abc 100s';
+ const animation = div.getAnimations()[0];
+ assert_true(animation.pending, 'Animation is initially pending');
+ const readyPromise = animation.ready;
+
+ // Cancel the animation and flush styles
+ div.style.animation = '';
+ getComputedStyle(div).animation;
+
+ await promise_rejects_dom(t, 'AbortError', readyPromise,
+ 'ready promise is rejected with AbortError');
+}, 'ready promise is rejected when an animation is canceled by resetting'
+ + ' the animation property');
+
+promise_test(async t => {
+ const div = addDiv(t);
+
+ // As before, but this time instead of removing all animations, simply update
+ // the list of animations. At least for Firefox, updating is a different
+ // code path.
+
+ // Set up pending animation
+ div.style.animation = 'abc 100s';
+ const animation = div.getAnimations()[0];
+ assert_true(animation.pending, 'Animation is initially pending');
+ const readyPromise = animation.ready;
+
+ // Update the animation and flush styles
+ div.style.animation = 'def 100s';
+ getComputedStyle(div).animation;
+
+ await promise_rejects_dom(t, 'AbortError', readyPromise,
+ 'ready promise is rejected with AbortError');
+}, 'ready promise is rejected when an animation is canceled by updating'
+ + ' the animation property');
+
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: abc 100s' });
+ const animation = div.getAnimations()[0];
+ const originalReadyPromise = animation.ready;
+
+ await animation.ready;
+
+ div.style.animationPlayState = 'paused';
+ assert_not_equals(animation.ready, originalReadyPromise,
+ 'A new Promise object is generated when setting'
+ + ' animation-play-state: paused');
+}, 'A new ready promise is created when setting animation-play-state: paused');
+
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: abc 100s' });
+ const animation = div.getAnimations()[0];
+
+ await animation.ready;
+
+ div.style.animationPlayState = 'paused';
+ const firstReadyPromise = animation.ready;
+ animation.pause();
+ assert_equals(animation.ready, firstReadyPromise,
+ 'Ready promise objects are identical after redundant pause');
+}, 'Pausing twice re-uses the same Promise');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/CSSAnimation-startTime.tentative.html b/testing/web-platform/tests/css/css-animations/CSSAnimation-startTime.tentative.html
new file mode 100644
index 0000000000..edef3b239e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/CSSAnimation-startTime.tentative.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.startTime</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+
+.animated-div {
+ margin-left: 10px;
+ /* Make it easier to calculate expected values: */
+ animation-timing-function: linear ! important;
+}
+
+@keyframes anim {
+ from { margin-left: 100px; }
+ to { margin-left: 200px; }
+}
+
+</style>
+<div id="log"></div>
+<script type="text/javascript">
+
+'use strict';
+
+test(t => {
+ const div = addDiv(t, { 'class': 'animated-div' });
+ div.style.animation = 'anim 100s 100s';
+ const animation = div.getAnimations()[0];
+
+ const timelineTime = animation.timeline.currentTime;
+ animation.startTime = timelineTime;
+
+ assert_times_equal(animation.startTime, timelineTime,
+ 'Check setting of startTime actually works');
+}, 'The start time of a CSS animation can be set');
+
+promise_test(async t => {
+ const div = addDiv(t, { 'class': 'animated-div' });
+ div.style.animation = 'anim 100s 100s';
+ const animation = div.getAnimations()[0];
+
+ // Seek to the half-way point
+ animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
+
+ assert_equals(getComputedStyle(div).marginLeft, '150px');
+}, 'The start time can be set to seek a CSS animation');
+
+promise_test(async t => {
+ const div = addDiv(t, { class: 'animated-div' });
+ const eventWatcher = new EventWatcher(t, div, [
+ 'animationstart',
+ 'animationend',
+ ]);
+ div.style.animation = 'anim 100s 100s';
+ const animation = div.getAnimations()[0];
+
+ await animation.ready;
+
+ animation.startTime = animation.timeline.currentTime - 100 * MS_PER_SEC;
+ await eventWatcher.wait_for('animationstart');
+
+ animation.startTime = animation.timeline.currentTime - 200 * MS_PER_SEC;
+ await eventWatcher.wait_for('animationend');
+}, 'Seeking a CSS animation using the start time dispatches animation events');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/Document-getAnimations.tentative.html b/testing/web-platform/tests/css/css-animations/Document-getAnimations.tentative.html
new file mode 100644
index 0000000000..758f680949
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/Document-getAnimations.tentative.html
@@ -0,0 +1,430 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Document.getAnimations() for CSS animations</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-composite-order">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes animLeft {
+ to { left: 100px }
+}
+@keyframes animTop {
+ to { top: 100px }
+}
+@keyframes animBottom {
+ to { bottom: 100px }
+}
+@keyframes animRight {
+ to { right: 100px }
+}
+
+@keyframes anim1 {
+ to { left: 100px }
+}
+@keyframes anim2 {
+ to { top: 100px }
+}
+@keyframes anim3 {
+ to { bottom: 100px }
+}
+@keyframes anim4 {
+ to { right: 100px }
+}
+
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+ assert_equals(document.getAnimations().length, 0,
+ 'getAnimations returns an empty sequence for a document'
+ + ' with no animations');
+}, 'getAnimations for non-animated content');
+
+test(t => {
+ const div = addDiv(t);
+
+ // Add an animation
+ div.style.animation = 'animLeft 100s';
+ assert_equals(document.getAnimations().length, 1,
+ 'getAnimations returns a running CSS Animation');
+
+ // Add another animation
+ div.style.animation = 'animLeft 100s, animTop 100s';
+ assert_equals(document.getAnimations().length, 2,
+ 'getAnimations returns two running CSS Animations');
+
+ // Remove both
+ div.style.animation = '';
+ assert_equals(document.getAnimations().length, 0,
+ 'getAnimations returns no running CSS Animations');
+}, 'getAnimations for CSS Animations');
+
+test(t => {
+ const div = addDiv(t);
+ const animation1 = 'animLeft 100s'
+ const animation2 = 'animBottom 100s'
+ div.style.animation = animation1;
+ const animations1 = document.getAnimations();
+ assert_equals(animations1.length, 1,
+ 'getAnimations returns all running CSS Animations');
+ div.style.animation = animation2 + ', ' + animation1;
+ const animations = document.getAnimations();
+ assert_equals(animations.length, 2,
+ 'getAnimations returns all running CSS Animations');
+ assert_equals(animations[0].animationName, 'animBottom',
+ 'Order of first animation returned');
+ assert_equals(animations[1].animationName, 'animLeft',
+ 'Order of second animation returned');
+}, 'Order of CSS Animations - within an element unaffected by start time');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'animLeft 100s, animTop 100s, animRight 100s, ' +
+ 'animBottom 100s';
+
+ const animations = document.getAnimations();
+ assert_equals(animations.length, 4,
+ 'getAnimations returns all running CSS Animations');
+ assert_equals(animations[0].animationName, 'animLeft',
+ 'Order of first animation returned');
+ assert_equals(animations[1].animationName, 'animTop',
+ 'Order of second animation returned');
+ assert_equals(animations[2].animationName, 'animRight',
+ 'Order of third animation returned');
+ assert_equals(animations[3].animationName, 'animBottom',
+ 'Order of fourth animation returned');
+}, 'Order of CSS Animations - within an element');
+
+test(t => {
+ const div1 = addDiv(t, { style: 'animation: animLeft 100s' });
+ const div2 = addDiv(t, { style: 'animation: animLeft 100s' });
+ const div3 = addDiv(t, { style: 'animation: animLeft 100s' });
+ const div4 = addDiv(t, { style: 'animation: animLeft 100s' });
+
+ let animations = document.getAnimations();
+ assert_equals(animations.length, 4,
+ 'getAnimations returns all running CSS Animations');
+ assert_equals(animations[0].effect.target, div1,
+ 'Order of first animation returned');
+ assert_equals(animations[1].effect.target, div2,
+ 'Order of second animation returned');
+ assert_equals(animations[2].effect.target, div3,
+ 'Order of third animation returned');
+ assert_equals(animations[3].effect.target, div4,
+ 'Order of fourth animation returned');
+
+ // Order should be depth-first pre-order so add some depth as follows:
+ //
+ // <parent>
+ // / |
+ // 2 3
+ // / \
+ // 1 4
+ //
+ // Which should give: 2, 1, 4, 3
+ div2.appendChild(div1);
+ div2.appendChild(div4);
+ animations = document.getAnimations();
+ assert_equals(animations[0].effect.target, div2,
+ 'Order of first animation returned after tree surgery');
+ assert_equals(animations[1].effect.target, div1,
+ 'Order of second animation returned after tree surgery');
+ assert_equals(animations[2].effect.target, div4,
+ 'Order of third animation returned after tree surgery');
+ assert_equals(animations[3].effect.target, div3,
+ 'Order of fourth animation returned after tree surgery');
+
+}, 'Order of CSS Animations - across elements');
+
+test(t => {
+ const div1 = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' });
+ const div2 = addDiv(t, { style: 'animation: animBottom 100s' });
+
+ let expectedResults = [ [ div1, 'animLeft' ],
+ [ div1, 'animTop' ],
+ [ div2, 'animBottom' ] ];
+ let animations = document.getAnimations();
+ assert_equals(animations.length, expectedResults.length,
+ 'getAnimations returns all running CSS Animations');
+ animations.forEach((anim, i) => {
+ assert_equals(anim.effect.target, expectedResults[i][0],
+ 'Target of animation in position ' + i);
+ assert_equals(anim.animationName, expectedResults[i][1],
+ 'Name of animation in position ' + i);
+ });
+
+ // Modify tree structure and animation list
+ div2.appendChild(div1);
+ div1.style.animation = 'animLeft 100s, animRight 100s, animTop 100s';
+
+ expectedResults = [ [ div2, 'animBottom' ],
+ [ div1, 'animLeft' ],
+ [ div1, 'animRight' ],
+ [ div1, 'animTop' ] ];
+ animations = document.getAnimations();
+ assert_equals(animations.length, expectedResults.length,
+ 'getAnimations returns all running CSS Animations after ' +
+ 'making changes');
+ animations.forEach((anim, i) => {
+ assert_equals(anim.effect.target, expectedResults[i][0],
+ 'Target of animation in position ' + i + ' after changes');
+ assert_equals(anim.animationName, expectedResults[i][1],
+ 'Name of animation in position ' + i + ' after changes');
+ });
+}, 'Order of CSS Animations - across and within elements');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' });
+ const animLeft = document.getAnimations()[0];
+ assert_equals(animLeft.animationName, 'animLeft',
+ 'Originally, animLeft animation comes first');
+
+ // Disassociate animLeft from markup and restart
+ div.style.animation = 'animTop 100s';
+ animLeft.play();
+
+ const animations = document.getAnimations();
+ assert_equals(animations.length, 2,
+ 'getAnimations returns markup-bound and free animations');
+ assert_equals(animations[0].animationName, 'animTop',
+ 'Markup-bound animations come first');
+ assert_equals(animations[1], animLeft, 'Free animations come last');
+}, 'Order of CSS Animations - markup-bound vs free animations');
+
+test(t => {
+ // Add an animation first
+ const div = addDiv(t, { style: 'animation: animLeft 100s' });
+ const animLeft = document.getAnimations()[0];
+ // Disassociate animLeft from markup and restart
+ div.style.animation = '';
+ animLeft.play();
+
+ div.style.top = '0px';
+ div.style.transition = 'all 100s';
+ flushComputedStyle(div);
+ // *Then* add a transition
+ div.style.top = '100px';
+ flushComputedStyle(div);
+
+ // Although the transition was added later, it should come first in the list
+ const animations = document.getAnimations();
+ assert_equals(animations.length, 2,
+ 'Both CSS animations and transitions are returned');
+ assert_class_string(animations[0], 'CSSTransition', 'Transition comes first');
+ assert_equals(animations[1], animLeft, 'Free animations come last');
+}, 'Order of CSS Animations - free animation vs CSS Transitions');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' });
+ const animLeft = document.getAnimations()[0];
+ const animTop = document.getAnimations()[1];
+
+ // Disassociate both animations from markup and restart in opposite order
+ div.style.animation = '';
+ animTop.play();
+ animLeft.play();
+
+ const animations = document.getAnimations();
+ assert_equals(animations.length, 2,
+ 'getAnimations returns free animations');
+ assert_equals(animations[0], animTop,
+ 'Free animations are returned in the order they are started');
+ assert_equals(animations[1], animLeft,
+ 'Animations started later are returned later');
+
+ // Restarting an animation should have no effect
+ animTop.cancel();
+ animTop.play();
+ assert_equals(document.getAnimations()[0], animTop,
+ 'After restarting, the ordering of free animations' +
+ ' does not change');
+}, 'Order of CSS Animations - free animations');
+
+test(t => {
+ // Add an animation first
+ const div = addDiv(t, { style: 'animation: animLeft 100s' });
+ div.style.top = '0px';
+ div.style.transition = 'all 100s';
+ flushComputedStyle(div);
+
+ // *Then* add a transition
+ div.style.top = '100px';
+ flushComputedStyle(div);
+
+ // Although the transition was added later, it should come first in the list
+ const animations = document.getAnimations();
+ assert_equals(animations.length, 2,
+ 'Both CSS animations and transitions are returned');
+ assert_class_string(animations[0], 'CSSTransition', 'Transition comes first');
+ assert_class_string(animations[1], 'CSSAnimation', 'Animation comes second');
+}, 'Order of CSS Animations and CSS Transitions');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: animLeft 100s forwards' });
+ div.getAnimations()[0].finish();
+ assert_equals(document.getAnimations().length, 1,
+ 'Forwards-filling CSS animations are returned');
+}, 'Finished but filling CSS Animations are returned');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: animLeft 100s' });
+ div.getAnimations()[0].finish();
+ assert_equals(document.getAnimations().length, 0,
+ 'Non-filling finished CSS animations are not returned');
+}, 'Finished but not filling CSS Animations are not returned');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: animLeft 100s 100s' });
+ assert_equals(document.getAnimations().length, 1,
+ 'Yet-to-start CSS animations are returned');
+}, 'Yet-to-start CSS Animations are returned');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: animLeft 100s' });
+ div.getAnimations()[0].cancel();
+ assert_equals(document.getAnimations().length, 0,
+ 'CSS animations canceled by the API are not returned');
+}, 'CSS Animations canceled via the API are not returned');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: animLeft 100s' });
+ const anim = div.getAnimations()[0];
+ anim.cancel();
+ anim.play();
+ assert_equals(document.getAnimations().length, 1,
+ 'CSS animations canceled and restarted by the API are ' +
+ 'returned');
+}, 'CSS Animations canceled and restarted via the API are returned');
+
+test(t => {
+ addStyle(t, {
+ '#parent::after': "content: ''; animation: anim1 100s ; ",
+ '#parent::before': "content: ''; animation: anim2 100s;",
+ '#child::after': "content: ''; animation: anim3 100s;",
+ '#child::before': "content: ''; animation: anim4 100s;",
+ });
+ const parent = addDiv(t, { id: 'parent' });
+ const child = addDiv(t, { id: 'child'});
+ parent.appendChild(child);
+ var animations = document.getAnimations();
+ var expectedAnimations = [
+ [parent, '::before', 'anim2'],
+ [parent, '::after', 'anim1'],
+ [child, '::before', 'anim4'],
+ [child, '::after', 'anim3'],
+ ];
+ pseudoAnimCompare(animations, expectedAnimations)
+
+ // Swap animation1 and aimation3's effect
+ var anim1 = animations[0];
+ var anim3 = animations[2];
+ const temp = anim1.effect;
+ anim1.effect = anim3.effect;
+ anim3.effect = temp;
+
+ animations = document.getAnimations();
+ expectedAnimations = [
+ [child, '::before', 'anim2'],
+ [parent, '::after', 'anim1'],
+ [parent, '::before', 'anim4'],
+ [child, '::after', 'anim3'],
+ ];
+ pseudoAnimCompare(animations, expectedAnimations)
+}, 'pseudo element with replaced target does not affect animation ordering');
+
+function pseudoAnimCompare(animations, expectedAnimations) {
+ assert_equals(
+ animations.length,
+ expectedAnimations.length,
+ 'CSS animations on both pseudo-elements and elements are returned'
+ );
+ for (const [index, expected] of expectedAnimations.entries()) {
+ const [element, pseudo, name] = expected;
+ const actual = animations[index];
+ if (pseudo) {
+ assert_equals(
+ actual.effect.target,
+ element,
+ `Animation #${index + 1} has the expected target`
+ );
+ assert_equals(
+ actual.effect.pseudoElement,
+ pseudo,
+ `Animation #${index + 1} has the expected pseudo type`
+ );
+ if (name) {
+ assert_equals(
+ actual.animationName,
+ name,
+ `Animation #${index + 1} has the expected name`
+ );
+ }
+ } else {
+ assert_equals(
+ actual.effect.target,
+ element,
+ `Animation #${index + 1} has the expected target`
+ );
+ assert_equals(
+ actual.effect.pseudoElement,
+ null,
+ `Animation #${index + 1} has a null pseudo type`
+ );
+ }
+ }
+}
+
+function pseudoTest(description, testMarkerPseudos) {
+ test(t => {
+ // Create two divs with the following arrangement:
+ //
+ // parent
+ // (::marker,) // Optionally
+ // ::before,
+ // ::after
+ // |
+ // child
+
+ addStyle(t, {
+ '#parent::after': "content: ''; animation: animLeft 100s;",
+ '#parent::before': "content: ''; animation: animRight 100s;",
+ });
+
+ if (testMarkerPseudos) {
+ addStyle(t, {
+ '#parent': 'display: list-item;',
+ '#parent::marker': "content: ''; animation: animLeft 100s;",
+ });
+ }
+
+ const parent = addDiv(t, { id: 'parent' });
+ const child = addDiv(t);
+ parent.appendChild(child);
+ for (const div of [parent, child]) {
+ div.setAttribute('style', 'animation: animBottom 100s');
+ }
+
+ const expectedAnimations = [
+ [parent, undefined],
+ [parent, '::marker'],
+ [parent, '::before'],
+ [parent, '::after'],
+ [child, undefined],
+ ];
+ if (!testMarkerPseudos) {
+ expectedAnimations.splice(1, 1);
+ }
+
+ const animations = document.getAnimations();
+ pseudoAnimCompare(animations, expectedAnimations)
+ }, description);
+}
+
+pseudoTest('CSS Animations targetting (pseudo-)elements should have correct '
+ + 'order after sorting', false);
+pseudoTest('CSS Animations targetting (pseudo-)elements should have correct '
+ + 'order after sorting (::marker)', true);
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html b/testing/web-platform/tests/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html
new file mode 100644
index 0000000000..a5e2288427
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html
@@ -0,0 +1,163 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>
+Element.getAnimations() - Dynamic changes to the list of CSS animations
+</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim1 {
+ to { left: 100px }
+}
+@keyframes anim2 { }
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s';
+ const originalAnimation = div.getAnimations()[0];
+
+ // Wait a moment so we can confirm the startTime doesn't change (and doesn't
+ // simply reflect the current time).
+ await originalAnimation.ready;
+
+ const originalStartTime = originalAnimation.startTime;
+ const originalCurrentTime = originalAnimation.currentTime;
+
+ // Wait a moment so we can confirm the startTime doesn't change (and
+ // doesn't simply reflect the current time).
+ await waitForNextFrame();
+
+ div.style.animationDuration = '200s';
+ const animation = div.getAnimations()[0];
+ assert_equals(animation, originalAnimation,
+ 'The same Animation is returned after updating'
+ + ' animation duration');
+ assert_equals(animation.startTime, originalStartTime,
+ 'Animations returned by getAnimations preserve'
+ + ' their startTime even when they are updated');
+ // Sanity check
+ assert_not_equals(animation.currentTime, originalCurrentTime,
+ 'Animation.currentTime has updated in next'
+ + ' requestAnimationFrame callback');
+}, 'Animations preserve their startTime when changed');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s, anim1 100s';
+
+ // Store original state
+ let animations = div.getAnimations();
+ const animation1 = animations[0];
+ const animation2 = animations[1];
+
+ // Update first in list
+ div.style.animationDuration = '200s, 100s';
+ animations = div.getAnimations();
+ assert_equals(animations[0], animation1,
+ 'First Animation is in same position after update');
+ assert_equals(animations[1], animation2,
+ 'Second Animation is in same position after update');
+}, 'Updated Animations maintain their order in the list');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 200s, anim1 100s';
+
+ // Store original state
+ let animations = div.getAnimations();
+ const animation1 = animations[0];
+ const animation2 = animations[1];
+
+ // Wait before continuing so we can compare start times (otherwise the
+ // new Animation objects and existing Animation objects will all have the same
+ // start time).
+ await waitForAllAnimations(animations);
+ await waitForFrame();
+
+ // Swap duration of first and second in list and prepend animation at the
+ // same time
+ div.style.animation = 'anim1 100s, anim1 100s, anim1 200s';
+ animations = div.getAnimations();
+ assert_true(animations[0] !== animation1 && animations[0] !== animation2,
+ 'New Animation is prepended to start of list');
+ assert_equals(animations[1], animation1,
+ 'First animation is in second position after update');
+ assert_equals(animations[2], animation2,
+ 'Second animation is in third position after update');
+ assert_equals(animations[1].startTime, animations[2].startTime,
+ 'Old animations have the same start time');
+ assert_equals(animations[0].startTime, null,
+ 'New animation has a null start time');
+
+ await animations[0].ready;
+
+ assert_greater_than(animations[0].startTime, animations[1].startTime,
+ 'New animation has later start time');
+}, 'Only the startTimes of existing animations are preserved');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s, anim1 100s';
+ const secondAnimation = div.getAnimations()[1];
+
+ // Wait before continuing so we can compare start times
+ await secondAnimation.ready;
+ await waitForNextFrame();
+
+ // Trim list of animations
+ div.style.animationName = 'anim1';
+ const animations = div.getAnimations();
+ assert_equals(animations.length, 1, 'List of Animations was trimmed');
+ assert_equals(animations[0], secondAnimation,
+ 'Remaining Animation is the second one in the list');
+ assert_equals(typeof(animations[0].startTime), 'number',
+ 'Remaining Animation has resolved startTime');
+ assert_less_than(animations[0].startTime,
+ animations[0].timeline.currentTime,
+ 'Remaining Animation preserves startTime');
+}, 'Animations are removed from the start of the list while preserving'
+ + ' the state of existing Animations');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s';
+ const firstAddedAnimation = div.getAnimations()[0];
+
+ // Wait and add second Animation
+ await firstAddedAnimation.ready;
+ await waitForFrame();
+
+ div.style.animation = 'anim1 100s, anim1 100s';
+ const secondAddedAnimation = div.getAnimations()[0];
+
+ // Wait again and add another Animation
+ await secondAddedAnimation.ready;
+ await waitForFrame();
+
+ div.style.animation = 'anim1 100s, anim2 100s, anim1 100s';
+ const animations = div.getAnimations();
+ assert_not_equals(firstAddedAnimation, secondAddedAnimation,
+ 'New Animations are added to start of the list');
+ assert_equals(animations[0], secondAddedAnimation,
+ 'Second Animation remains in same position after'
+ + ' interleaving');
+ assert_equals(animations[2], firstAddedAnimation,
+ 'First Animation remains in same position after'
+ + ' interleaving');
+ await animations[1].ready;
+
+ assert_greater_than(animations[1].startTime, animations[0].startTime,
+ 'Interleaved animation starts later than existing ' +
+ 'animations');
+ assert_greater_than(animations[0].startTime, animations[2].startTime,
+ 'Original animations retain their start time');
+}, 'Animation state is preserved when interleaving animations in list');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/Element-getAnimations.tentative.html b/testing/web-platform/tests/css/css-animations/Element-getAnimations.tentative.html
new file mode 100644
index 0000000000..651c9d89ed
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/Element-getAnimations.tentative.html
@@ -0,0 +1,453 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Element.getAnimations() for CSS animations</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim1 {
+ to { left: 100px }
+}
+@keyframes anim2 {
+ to { top: 100px }
+}
+@keyframes multiPropAnim {
+ to { background: green, opacity: 0.5, left: 100px, top: 100px }
+}
+::before {
+ content: ''
+}
+::after {
+ content: ''
+}
+@keyframes empty { }
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+ const div = addDiv(t);
+ assert_equals(div.getAnimations().length, 0,
+ 'getAnimations returns an empty sequence for an element'
+ + ' with no animations');
+}, 'getAnimations for non-animated content');
+
+promise_test(async t => {
+ const div = addDiv(t);
+
+ // Add an animation
+ div.style.animation = 'anim1 100s';
+ let animations = div.getAnimations();
+ assert_equals(animations.length, 1,
+ 'getAnimations returns an Animation running CSS Animations');
+ await animations[0].ready;
+
+ // Add a second animation
+ div.style.animation = 'anim1 100s, anim2 100s';
+ animations = div.getAnimations();
+ assert_equals(animations.length, 2,
+ 'getAnimations returns one CSSAnimation for each value of animation-name');
+ // (We don't check the order of the Animations since that is covered by tests
+ // later in this file.)
+}, 'getAnimations for CSS Animations');
+
+test(t => {
+ const div = addDiv(t, { style: 'animation: anim1 100s' });
+ assert_class_string(div.getAnimations()[0], 'CSSAnimation',
+ 'Interface of returned animation is CSSAnimation');
+}, 'getAnimations returns CSSAnimation objects for CSS Animations');
+
+test(t => {
+ const div = addDiv(t);
+
+ // Add an animation that targets multiple properties
+ div.style.animation = 'multiPropAnim 100s';
+ assert_equals(div.getAnimations().length, 1,
+ 'getAnimations returns only one Animation for a CSS Animation'
+ + ' that targets multiple properties');
+}, 'getAnimations for multi-property animations');
+
+promise_test(async t => {
+ const div = addDiv(t);
+
+ // Add an animation
+ div.style.backgroundColor = 'red';
+ div.style.animation = 'anim1 100s';
+ getComputedStyle(div).backgroundColor;
+
+ // Wait until a frame after the animation starts, then add a transition
+ let animations = div.getAnimations();
+ await animations[0].ready;
+ await waitForFrame();
+
+ div.style.transition = 'all 100s';
+ div.style.backgroundColor = 'green';
+
+ animations = div.getAnimations();
+ assert_equals(animations.length, 2,
+ 'getAnimations returns Animations for both animations and'
+ + ' transitions that run simultaneously');
+ assert_class_string(animations[0], 'CSSTransition',
+ 'First-returned animation is the CSS Transition');
+ assert_class_string(animations[1], 'CSSAnimation',
+ 'Second-returned animation is the CSS Animation');
+}, 'getAnimations for both CSS Animations and CSS Transitions at once');
+
+async_test(t => {
+ const div = addDiv(t);
+
+ // Set up event listener
+ div.addEventListener('animationend', t.step_func(() => {
+ assert_equals(div.getAnimations().length, 0,
+ 'getAnimations does not return Animations for finished '
+ + ' (and non-forwards-filling) CSS Animations');
+ t.done();
+ }));
+
+ // Add a very short animation
+ div.style.animation = 'anim1 0.01s';
+}, 'getAnimations for CSS Animations that have finished');
+
+async_test(t => {
+ const div = addDiv(t);
+
+ // Set up event listener
+ div.addEventListener('animationend', t.step_func(() => {
+ assert_equals(div.getAnimations().length, 1,
+ 'getAnimations returns Animations for CSS Animations that have'
+ + ' finished but are filling forwards');
+ t.done();
+ }));
+
+ // Add a very short animation
+ div.style.animation = 'anim1 0.01s forwards';
+}, 'getAnimations for CSS Animations that have finished but are'
+ + ' forwards filling');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'none 100s';
+
+ let animations = div.getAnimations();
+ assert_equals(animations.length, 0,
+ 'getAnimations returns an empty sequence for an element'
+ + ' with animation-name: none');
+
+ div.style.animation = 'none 100s, anim1 100s';
+ animations = div.getAnimations();
+ assert_equals(animations.length, 1,
+ 'getAnimations returns Animations only for those CSS Animations whose'
+ + ' animation-name is not none');
+}, 'getAnimations for CSS Animations with animation-name: none');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'missing 100s';
+ let animations = div.getAnimations();
+ assert_equals(animations.length, 0,
+ 'getAnimations returns an empty sequence for an element'
+ + ' with animation-name: missing');
+
+ div.style.animation = 'anim1 100s, missing 100s';
+ animations = div.getAnimations();
+ assert_equals(animations.length, 1,
+ 'getAnimations returns Animations only for those CSS Animations whose'
+ + ' animation-name is found');
+}, 'getAnimations for CSS Animations with animation-name: missing');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s, notyet 100s';
+ let animations = div.getAnimations();
+ assert_equals(animations.length, 1,
+ 'getAnimations initally only returns Animations for CSS Animations whose'
+ + ' animation-name is found');
+
+ await animations[0].ready;
+ await waitForFrame();
+
+ const keyframes = '@keyframes notyet { to { left: 100px; } }';
+ document.styleSheets[0].insertRule(keyframes, 0);
+ animations = div.getAnimations();
+ assert_equals(animations.length, 2,
+ 'getAnimations includes Animation when @keyframes rule is added'
+ + ' later');
+ await waitForAllAnimations(animations);
+
+ assert_true(animations[0].startTime < animations[1].startTime,
+ 'Newly added animation has a later start time');
+ document.styleSheets[0].deleteRule(0);
+}, 'getAnimations for CSS Animations where the @keyframes rule is added'
+ + ' later');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s, anim1 100s';
+ assert_equals(div.getAnimations().length, 2,
+ 'getAnimations returns one Animation for each CSS animation-name'
+ + ' even if the names are duplicated');
+}, 'getAnimations for CSS Animations with duplicated animation-name');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'empty 100s';
+ assert_equals(div.getAnimations().length, 1,
+ 'getAnimations returns Animations for CSS animations with an'
+ + ' empty keyframes rule');
+}, 'getAnimations for CSS Animations with empty keyframes rule');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s 100s';
+ const animations = div.getAnimations();
+ assert_equals(animations.length, 1,
+ 'getAnimations returns animations for CSS animations whose'
+ + ' delay makes them start later');
+ await animations[0].ready;
+ await waitForFrame();
+
+ assert_true(animations[0].startTime <= document.timeline.currentTime,
+ 'For CSS Animations in delay phase, the start time of the Animation is'
+ + ' not in the future');
+}, 'getAnimations for CSS animations in delay phase');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 0s 100s';
+ assert_equals(div.getAnimations().length, 1,
+ 'getAnimations returns animations for CSS animations whose'
+ + ' duration is zero');
+ div.remove();
+}, 'getAnimations for zero-duration CSS Animations');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s';
+ const originalAnimation = div.getAnimations()[0];
+
+ // Update pause state (an Animation change)
+ div.style.animationPlayState = 'paused';
+ const pendingAnimation = div.getAnimations()[0];
+ assert_equals(pendingAnimation.playState, 'paused',
+ 'animation\'s play state is updated');
+ assert_equals(originalAnimation, pendingAnimation,
+ 'getAnimations returns the same objects even when their'
+ + ' play state changes');
+
+ // Update duration (an Animation change)
+ div.style.animationDuration = '200s';
+ const extendedAnimation = div.getAnimations()[0];
+ assert_equals(
+ extendedAnimation.effect.getTiming().duration,
+ 200 * MS_PER_SEC,
+ 'animation\'s duration has been updated'
+ );
+ assert_equals(originalAnimation, extendedAnimation,
+ 'getAnimations returns the same objects even when their'
+ + ' duration changes');
+}, 'getAnimations returns objects with the same identity');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim1 100s';
+
+ assert_equals(div.getAnimations().length, 1,
+ 'getAnimations returns an animation before canceling');
+
+ const animation = div.getAnimations()[0];
+
+ animation.cancel();
+ assert_equals(div.getAnimations().length, 0,
+ 'getAnimations does not return canceled animations');
+
+ animation.play();
+ assert_equals(div.getAnimations().length, 1,
+ 'getAnimations returns canceled animations that have been re-started');
+
+}, 'getAnimations for CSS Animations that are canceled');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim2 100s';
+
+ await div.getAnimations()[0].ready;
+
+ // Prepend to the list and test that even though anim1 was triggered
+ // *after* anim2, it should come first because it appears first
+ // in the animation-name property.
+ div.style.animation = 'anim1 100s, anim2 100s';
+ let anims = div.getAnimations();
+ assert_equals(anims[0].animationName, 'anim1',
+ 'animation order after prepending to list');
+ assert_equals(anims[1].animationName, 'anim2',
+ 'animation order after prepending to list');
+
+ // Normally calling cancel and play would this push anim1 to the top of
+ // the stack but it shouldn't for CSS animations that map an the
+ // animation-name property.
+ const anim1 = anims[0];
+ anim1.cancel();
+ anim1.play();
+ anims = div.getAnimations();
+ assert_equals(anims[0].animationName, 'anim1',
+ 'animation order after canceling and restarting');
+ assert_equals(anims[1].animationName, 'anim2',
+ 'animation order after canceling and restarting');
+}, 'getAnimations for CSS Animations follows animation-name order');
+
+test(t => {
+ addStyle(t, { '#target::after': 'animation: anim1 10s;',
+ '#target::before': 'animation: anim1 10s;' });
+ const target = addDiv(t, { 'id': 'target' });
+ target.style.animation = 'anim1 100s';
+ const animations = target.getAnimations({ subtree: false });
+
+ assert_equals(animations.length, 1,
+ 'Should find only the element');
+ assert_equals(animations[0].effect.target, target,
+ 'Effect target should be the element');
+}, '{ subtree: false } on a leaf element returns the element\'s animations'
+ + ' and ignore pseudo-elements');
+
+test(t => {
+ addStyle(t, { '#target::after': 'animation: anim1 10s;',
+ '#target::before': 'animation: anim1 10s;' });
+ const target = addDiv(t, { 'id': 'target' });
+ target.style.animation = 'anim1 100s';
+ const animations = target.getAnimations({ subtree: true });
+
+ assert_equals(animations.length, 3,
+ 'getAnimations({ subtree: true }) ' +
+ 'should return animations on pseudo-elements');
+ assert_equals(animations[0].effect.target, target,
+ 'The animation targeting the parent element ' +
+ 'should be returned first');
+ assert_equals(animations[0].effect.pseudoElement, null,
+ 'The animation targeting the parent element ' +
+ 'should be returned first')
+ assert_equals(animations[1].effect.pseudoElement, '::before',
+ 'The animation targeting the ::before pseudo-element ' +
+ 'should be returned second');
+ assert_equals(animations[2].effect.pseudoElement, '::after',
+ 'The animation targeting the ::after pesudo-element ' +
+ 'should be returned last');
+}, '{ subtree: true } on a leaf element returns the element\'s animations'
+ + ' and its pseudo-elements\' animations');
+
+test(t => {
+ addStyle(t, { '#parent::after': 'animation: anim1 10s;',
+ '#parent::before': 'animation: anim1 10s;',
+ '#child::after': 'animation: anim1 10s;',
+ '#child::before': 'animation: anim1 10s;' });
+ const parent = addDiv(t, { 'id': 'parent' });
+ parent.style.animation = 'anim1 100s';
+ const child = addDiv(t, { 'id': 'child' });
+ child.style.animation = 'anim1 100s';
+ parent.appendChild(child);
+
+ const animations = parent.getAnimations({ subtree: false });
+ assert_equals(animations.length, 1,
+ 'Should find only the element even if it has a child');
+ assert_equals(animations[0].effect.target, parent,
+ 'Effect target should be the element');
+}, '{ subtree: false } on an element with a child returns only the element\'s'
+ + ' animations');
+
+test(t => {
+ addStyle(t, { '#parent::after': 'animation: anim1 10s;',
+ '#parent::before': 'animation: anim1 10s;',
+ '#child::after': 'animation: anim1 10s;',
+ '#child::before': 'animation: anim1 10s;' });
+ const parent = addDiv(t, { 'id': 'parent' });
+ const child = addDiv(t, { 'id': 'child' });
+ parent.style.animation = 'anim1 100s';
+ child.style.animation = 'anim1 100s';
+ parent.appendChild(child);
+
+ const animations = parent.getAnimations({ subtree: true });
+ assert_equals(animations.length, 6,
+ 'Should find all elements, pseudo-elements that parent has');
+
+ assert_equals(animations[0].effect.target, parent,
+ 'The animation targeting the parent element ' +
+ 'should be returned first');
+ assert_equals(animations[0].effect.pseudoElement, null,
+ 'The animation targeting the parent element ' +
+ 'should be returned first');
+ assert_equals(animations[1].effect.pseudoElement, '::before',
+ 'The animation targeting the ::before pseudo-element ' +
+ 'should be returned second');
+ assert_equals(animations[1].effect.target, parent,
+ 'This ::before element should be child of parent element');
+ assert_equals(animations[2].effect.pseudoElement, '::after',
+ 'The animation targeting the ::after pesudo-element ' +
+ 'should be returned third');
+ assert_equals(animations[2].effect.target, parent,
+ 'This ::after element should be child of parent element');
+
+ assert_equals(animations[3].effect.target, child,
+ 'The animation targeting the child element ' +
+ 'should be returned fourth');
+ assert_equals(animations[4].effect.pseudoElement, '::before',
+ 'The animation targeting the ::before pseudo-element ' +
+ 'should be returned fifth');
+ assert_equals(animations[4].effect.target, child,
+ 'This ::before element should be child of child element');
+ assert_equals(animations[5].effect.pseudoElement, '::after',
+ 'The animation targeting the ::after pesudo-element ' +
+ 'should be returned last');
+ assert_equals(animations[5].effect.target, child,
+ 'This ::after element should be child of child element');
+}, '{ subtree: true } on an element with a child returns animations from the'
+ + ' element, its pseudo-elements, its child and its child pseudo-elements');
+
+test(t => {
+ const parent = addDiv(t, { 'id': 'parent' });
+ const child1 = addDiv(t, { 'id': 'child1' });
+ const grandchild1 = addDiv(t, { 'id': 'grandchild1' });
+ const grandchild2 = addDiv(t, { 'id': 'grandchild2' });
+ const child2 = addDiv(t, { 'id': 'child2' });
+
+ parent.style.animation = 'anim1 100s';
+ child1.style.animation = 'anim1 100s';
+ grandchild1.style.animation = 'anim1 100s';
+ grandchild2.style.animation = 'anim1 100s';
+ child2.style.animation = 'anim1 100s';
+
+ parent.appendChild(child1);
+ child1.appendChild(grandchild1);
+ child1.appendChild(grandchild2);
+ parent.appendChild(child2);
+
+ const animations = parent.getAnimations({ subtree: true });
+ assert_equals(
+ parent.getAnimations({ subtree: true }).length, 5,
+ 'Should find all descendants of the element');
+
+ assert_equals(animations[0].effect.target, parent,
+ 'The animation targeting the parent element ' +
+ 'should be returned first');
+
+ assert_equals(animations[1].effect.target, child1,
+ 'The animation targeting the child1 element ' +
+ 'should be returned second');
+
+ assert_equals(animations[2].effect.target, grandchild1,
+ 'The animation targeting the grandchild1 element ' +
+ 'should be returned third');
+
+ assert_equals(animations[3].effect.target, grandchild2,
+ 'The animation targeting the grandchild2 element ' +
+ 'should be returned fourth');
+
+ assert_equals(animations[4].effect.target, child2,
+ 'The animation targeting the child2 element ' +
+ 'should be returned last');
+
+}, '{ subtree: true } on an element with many descendants returns animations'
+ + ' from all the descendants');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/KeyframeEffect-getKeyframes.tentative.html b/testing/web-platform/tests/css/css-animations/KeyframeEffect-getKeyframes.tentative.html
new file mode 100644
index 0000000000..6de6e318ad
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/KeyframeEffect-getKeyframes.tentative.html
@@ -0,0 +1,878 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>KeyframeEffect.getKeyframes() for CSS animations</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim-empty { }
+
+@keyframes anim-empty-frames {
+ from { }
+ to { }
+}
+
+@keyframes anim-only-timing {
+ from { animation-timing-function: linear; }
+ to { }
+}
+
+@keyframes anim-only-non-animatable {
+ from { animation-duration: 3s; }
+ to { animation-duration: 5s; }
+}
+
+@keyframes anim-simple {
+ from { color: rgb(0, 0, 0); }
+ to { color: rgb(255, 255, 255); }
+}
+
+@keyframes anim-simple-three {
+ from { color: rgb(0, 0, 0); }
+ 50% { color: rgb(0, 0, 255); }
+ to { color: rgb(255, 255, 255); }
+}
+
+@keyframes anim-simple-timing {
+ from { color: rgb(0, 0, 0); animation-timing-function: linear; }
+ 50% { color: rgb(0, 0, 255); animation-timing-function: ease-in-out; }
+ to { color: rgb(255, 255, 255); animation-timing-function: step-end; }
+}
+
+@keyframes anim-simple-timing-some {
+ from { color: rgb(0, 0, 0); animation-timing-function: linear; }
+ 50% { color: rgb(0, 0, 255); }
+ to { color: rgb(255, 255, 255); }
+}
+
+@keyframes anim-simple-composite {
+ from { color: rgb(0, 0, 0); animation-composition: replace; }
+ 50% { color: rgb(0, 0, 255); animation-composition: add; }
+ to { color: rgb(255, 255, 255); animation-composition: accumulate; }
+}
+
+@keyframes anim-simple-composite-some {
+ from { color: rgb(0, 0, 0); animation-composition: add; }
+ 50% { color: rgb(0, 0, 255); }
+ to { color: rgb(255, 255, 255); }
+}
+
+@keyframes anim-simple-shorthand {
+ from { margin: 8px; }
+ to { margin: 16px; }
+}
+
+@keyframes anim-omit-to {
+ from { color: rgb(0, 0, 255); }
+}
+
+@keyframes anim-omit-from {
+ to { color: rgb(0, 0, 255); }
+}
+
+@keyframes anim-omit-from-to {
+ 50% { color: rgb(0, 0, 255); }
+}
+
+@keyframes anim-partially-omit-to {
+ from { margin-top: 50px;
+ margin-bottom: 100px; }
+ to { margin-top: 150px !important; /* ignored */
+ margin-bottom: 200px; }
+}
+
+@keyframes anim-different-props {
+ from { color: rgb(0, 0, 0); margin-top: 8px; }
+ 25% { color: rgb(0, 0, 255); }
+ 75% { margin-top: 12px; }
+ to { color: rgb(255, 255, 255); margin-top: 16px }
+}
+
+@keyframes anim-different-props-and-easing {
+ from { color: rgb(0, 0, 0); margin-top: 8px; animation-timing-function: linear; }
+ 25% { color: rgb(0, 0, 255); animation-timing-function: step-end; }
+ 75% { margin-top: 12px; animation-timing-function: ease-in; }
+ to { color: rgb(255, 255, 255); margin-top: 16px }
+}
+
+@keyframes anim-merge-offset {
+ from { color: rgb(0, 0, 0); }
+ to { color: rgb(255, 255, 255); }
+ from { margin-top: 8px; }
+ to { margin-top: 16px; }
+}
+
+@keyframes anim-merge-offset-and-easing {
+ from { color: rgb(0, 0, 0); animation-timing-function: step-end; }
+ to { color: rgb(255, 255, 255); }
+ from { margin-top: 8px; animation-timing-function: linear; }
+ to { margin-top: 16px; }
+ from { font-size: 16px; animation-timing-function: step-end; }
+ to { font-size: 32px; }
+ from { padding-left: 2px; animation-timing-function: linear; }
+ to { padding-left: 4px; }
+}
+
+@keyframes anim-no-merge-equiv-easing {
+ from { margin-top: 0px; animation-timing-function: steps(1, end); }
+ from { margin-right: 0px; animation-timing-function: step-end; }
+ from { margin-bottom: 0px; animation-timing-function: steps(1); }
+ 50% { margin-top: 10px; animation-timing-function: step-end; }
+ 50% { margin-right: 10px; animation-timing-function: step-end; }
+ 50% { margin-bottom: 10px; animation-timing-function: step-end; }
+ to { margin-top: 20px; margin-right: 20px; margin-bottom: 20px; }
+}
+
+@keyframes anim-merge-offset-and-composite {
+ from { color: rgb(0, 0, 0); animation-composition: add; }
+ to { color: rgb(255, 255, 255); }
+ from { margin-top: 8px; animation-composition: accumulate; }
+ to { margin-top: 16px; }
+ from { font-size: 16px; animation-composition: add; }
+ to { font-size: 32px; }
+ from { padding-left: 2px; animation-composition: accumulate; }
+ to { padding-left: 4px; }
+}
+
+@keyframes anim-merge-offset-easing-and-composite {
+ from { color: rgb(0, 0, 0); animation-composition: add; }
+ to { color: rgb(255, 255, 255); }
+ from { margin-top: 8px; animation-composition: accumulate; }
+ to { margin-top: 16px; }
+ from { font-size: 16px; animation-composition: add; animation-timing-function: linear; }
+ to { font-size: 32px; }
+ from { padding-left: 2px; animation-composition: accumulate; }
+ to { padding-left: 4px; }
+}
+
+@keyframes anim-overriding {
+ from { padding-top: 50px }
+ 50%, from { padding-top: 30px } /* wins: 0% */
+ 75%, 85%, 50% { padding-top: 20px } /* wins: 75%, 50% */
+ 100%, 85% { padding-top: 70px } /* wins: 100% */
+ 85.1% { padding-top: 60px } /* wins: 85.1% */
+ 85% { padding-top: 30px } /* wins: 85% */
+}
+
+@keyframes anim-filter {
+ to { filter: blur(5px) sepia(60%) saturate(30%); }
+}
+
+@keyframes anim-filter-drop-shadow {
+ from { filter: drop-shadow(10px 10px 10px rgb(0, 255, 0)); }
+ to { filter: drop-shadow(50px 30px 10px rgb(255, 0, 0)); }
+}
+
+@keyframes anim-text-shadow {
+ to { text-shadow: none; }
+}
+
+@keyframes anim-background-size {
+ to { background-size: 50%, 6px, contain }
+}
+
+:root {
+ --var-100px: 100px;
+ --end-color: rgb(255, 0, 0);
+}
+@keyframes anim-variables {
+ to { transform: translate(var(--var-100px), 0) }
+}
+@keyframes anim-variables-shorthand {
+ to { margin: var(--var-100px) }
+}
+@keyframes anim-custom-property-in-keyframe {
+ to { --end-color: rgb(0, 255, 0); color: var(--end-color) }
+}
+@keyframes anim-only-custom-property-in-keyframe {
+ from { transform: translate(100px, 0) }
+ to { --not-used: 200px }
+}
+
+@keyframes anim-only-timing-function-for-from-and-to {
+ from, to { animation-timing-function: linear }
+ 50% { left: 10px }
+}
+
+</style>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+const getKeyframes = elem => elem.getAnimations()[0].effect.getKeyframes();
+
+// animation-timing-function values to test with, where the value
+// is exactly the same as its serialization, sorted by the order
+// getKeyframes() will group frames with the same easing function
+// together (by nsTimingFunction::Compare).
+const kTimingFunctionValues = [
+ "ease",
+ "linear",
+ "ease-in",
+ "ease-out",
+ "ease-in-out",
+ "steps(1, start)",
+ "steps(2, start)",
+ "steps(1)",
+ "steps(2)",
+ "cubic-bezier(0, 0, 1, 1)",
+ "cubic-bezier(0, 0.25, 0.75, 1)"
+];
+
+const kCompositeValues = [
+ "replace",
+ "add",
+ "accumulate"
+];
+
+test(t => {
+ const div = addDiv(t);
+
+ div.style.animation = 'anim-empty 100s';
+ assert_equals(getKeyframes(div).length, 0,
+ "number of frames with empty @keyframes");
+
+ div.style.animation = 'anim-empty-frames 100s';
+ assert_equals(getKeyframes(div).length, 0,
+ "number of frames when @keyframes has empty keyframes");
+
+ div.style.animation = 'anim-only-timing 100s';
+ assert_equals(getKeyframes(div).length, 0,
+ "number of frames when @keyframes only has keyframes with " +
+ "animation-timing-function");
+
+ div.style.animation = 'anim-only-non-animatable 100s';
+ assert_equals(getKeyframes(div).length, 0,
+ "number of frames when @keyframes only has frames with " +
+ "non-animatable properties");
+}, 'KeyframeEffect.getKeyframes() returns no frames for various kinds'
+ + ' of empty animations');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-simple 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease",
+ color: "rgb(0, 0, 0)", composite: "auto" },
+ { offset: 1, computedOffset: 1, easing: "ease",
+ color: "rgb(255, 255, 255)", composite: "auto" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
+ + ' animation');
+
+test(t => {
+ for (const easing of kTimingFunctionValues) {
+ const div = addDiv(t);
+
+ div.style.animation = 'anim-simple-three 100s ' + easing;
+ const frames = getKeyframes(div);
+
+ assert_equals(frames.length, 3, "number of frames");
+
+ for (let i = 0; i < frames.length; i++) {
+ assert_equals(frames[i].easing, easing,
+ "value for 'easing' on ComputedKeyframe #" + i);
+ }
+ }
+}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ + ' values, when the easing comes from animation-timing-function on the'
+ + ' element');
+
+test(t => {
+ const div = addDiv(t);
+
+ div.style.animation = 'anim-simple-timing 100s';
+ const frames = getKeyframes(div);
+
+ assert_equals(frames.length, 3, "number of frames");
+ assert_equals(frames[0].easing, "linear",
+ "value of 'easing' on ComputedKeyframe #0");
+ assert_equals(frames[1].easing, "ease-in-out",
+ "value of 'easing' on ComputedKeyframe #1");
+ assert_equals(frames[2].easing, "steps(1)",
+ "value of 'easing' on ComputedKeyframe #2");
+}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ + ' values, when the easing is specified on each keyframe');
+
+test(t => {
+ const div = addDiv(t);
+
+ div.style.animation = 'anim-simple-timing-some 100s step-start';
+ const frames = getKeyframes(div);
+
+ assert_equals(frames.length, 3, "number of frames");
+ assert_equals(frames[0].easing, "linear",
+ "value of 'easing' on ComputedKeyframe #0");
+ assert_equals(frames[1].easing, "steps(1, start)",
+ "value of 'easing' on ComputedKeyframe #1");
+ assert_equals(frames[2].easing, "steps(1, start)",
+ "value of 'easing' on ComputedKeyframe #2");
+}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ + ' values, when the easing is specified on some keyframes');
+
+test(t => {
+ for (const composite of kCompositeValues) {
+ const div = addDiv(t);
+
+ div.style.animation = 'anim-simple-three 100s';
+ div.style.animationComposition = composite;
+ const frames = getKeyframes(div);
+
+ assert_equals(frames.length, 3, "number of frames");
+
+ for (let i = 0; i < frames.length; i++) {
+ assert_equals(frames[i].composite, "auto",
+ "value for 'composite' on ComputedKeyframe #" + i);
+ }
+ }
+}, 'KeyframeEffect.getKeyframes() returns frames with expected composite'
+ + ' values, when the composite is set on the effect using animation-composition on the'
+ + ' element');
+
+test(t => {
+ const div = addDiv(t);
+
+ div.style.animation = 'anim-simple-composite 100s';
+ const frames = getKeyframes(div);
+
+ assert_equals(frames.length, 3, "number of frames");
+ assert_equals(frames[0].composite, "replace",
+ "value of 'composite' on ComputedKeyframe #0");
+ assert_equals(frames[1].composite, "add",
+ "value of 'composite' on ComputedKeyframe #1");
+ assert_equals(frames[2].composite, "accumulate",
+ "value of 'composite' on ComputedKeyframe #2");
+}, 'KeyframeEffect.getKeyframes() returns frames with expected composite'
+ + ' values, when the composite is specified on each keyframe');
+
+test(t => {
+ const div = addDiv(t);
+
+ div.style.animation = 'anim-simple-composite-some 100s';
+ div.style.animationComposition = 'accumulate';
+ const frames = getKeyframes(div);
+
+ assert_equals(frames.length, 3, "number of frames");
+ assert_equals(frames[0].composite, "add",
+ "value of 'composite' on ComputedKeyframe #0");
+ assert_equals(frames[1].composite, "auto",
+ "value of 'composite' on ComputedKeyframe #1");
+ assert_equals(frames[2].composite, "auto",
+ "value of 'composite' on ComputedKeyframe #2");
+}, 'KeyframeEffect.getKeyframes() returns frames with expected composite'
+ + ' values, when the composite is specified on some keyframes');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-simple-shorthand 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
+ marginBottom: "8px", marginLeft: "8px",
+ marginRight: "8px", marginTop: "8px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ marginBottom: "16px", marginLeft: "16px",
+ marginRight: "16px", marginTop: "16px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
+ + ' animation that specifies a single shorthand property');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-omit-to 100s';
+ div.style.color = 'rgb(255, 255, 255)';
+
+ const frames = getKeyframes(div);
+
+ // Final keyframe should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
+ color: "rgb(0, 0, 255)" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "replace",
+ color: "rgb(255, 255, 255)" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with a 0% keyframe and no 100% keyframe');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-omit-from 100s';
+ div.style.color = 'rgb(255, 255, 255)';
+
+ const frames = getKeyframes(div);
+
+ // Initial keyframe should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+ color: "rgb(255, 255, 255)" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ color: "rgb(0, 0, 255)" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with a 100% keyframe and no 0% keyframe');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-omit-from-to 100s';
+ div.style.color = 'rgb(255, 255, 255)';
+
+ const frames = getKeyframes(div);
+
+ // Initial and final keyframes should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+ color: "rgb(255, 255, 255)" },
+ { offset: 0.5, computedOffset: 0.5, easing: "ease", composite: "auto",
+ color: "rgb(0, 0, 255)" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "replace",
+ color: "rgb(255, 255, 255)" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with no 0% or 100% keyframe but with a 50% keyframe');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-partially-omit-to 100s';
+ div.style.marginTop = '250px';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
+ marginTop: '50px', marginBottom: '100px' },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ marginTop: '250px', marginBottom: '200px' },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with a partially complete 100% keyframe (because the ' +
+ '!important rule is ignored)');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-different-props 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
+ color: "rgb(0, 0, 0)", marginTop: "8px" },
+ { offset: 0.25, computedOffset: 0.25, easing: "ease", composite: "auto",
+ color: "rgb(0, 0, 255)" },
+ { offset: 0.75, computedOffset: 0.75, easing: "ease", composite: "auto",
+ marginTop: "12px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ color: "rgb(255, 255, 255)", marginTop: "16px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with different properties on different keyframes, all ' +
+ 'with the same easing function');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-different-props-and-easing 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "linear", composite: "auto",
+ color: "rgb(0, 0, 0)", marginTop: "8px" },
+ { offset: 0.25, computedOffset: 0.25, easing: "steps(1)", composite: "auto",
+ color: "rgb(0, 0, 255)" },
+ { offset: 0.75, computedOffset: 0.75, easing: "ease-in", composite: "auto",
+ marginTop: "12px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ color: "rgb(255, 255, 255)", marginTop: "16px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with different properties on different keyframes, with ' +
+ 'a different easing function on each');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-merge-offset 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
+ color: "rgb(0, 0, 0)", marginTop: "8px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ color: "rgb(255, 255, 255)", marginTop: "16px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with multiple keyframes for the same time, and all with ' +
+ 'the same easing function');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-merge-offset-and-easing 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "steps(1)", composite: "auto",
+ color: "rgb(0, 0, 0)", fontSize: "16px" },
+ { offset: 0, computedOffset: 0, easing: "linear", composite: "auto",
+ marginTop: "8px", paddingLeft: "2px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
+ paddingLeft: "4px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with multiple keyframes for the same time and with ' +
+ 'different easing functions');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-no-merge-equiv-easing 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "steps(1)", composite: "auto",
+ marginTop: "0px", marginRight: "0px", marginBottom: "0px" },
+ { offset: 0.5, computedOffset: 0.5, easing: "steps(1)", composite: "auto",
+ marginTop: "10px", marginRight: "10px", marginBottom: "10px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ marginTop: "20px", marginRight: "20px", marginBottom: "20px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with multiple keyframes for the same time and with ' +
+ 'different but equivalent easing functions');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-merge-offset-and-composite 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "add",
+ color: "rgb(0, 0, 0)", fontSize: "16px" },
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "accumulate",
+ marginTop: "8px", paddingLeft: "2px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
+ paddingLeft: "4px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with multiple keyframes for the same time and with ' +
+ 'different composite operations');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-merge-offset-easing-and-composite 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "add",
+ color: "rgb(0, 0, 0)" },
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "accumulate",
+ marginTop: "8px", paddingLeft: "2px" },
+ { offset: 0, computedOffset: 0, easing: "linear", composite: "add",
+ fontSize: "16px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
+ paddingLeft: "4px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
+ 'animation with multiple keyframes for the same time and with ' +
+ 'different easing functions and composite operations');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-overriding 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
+ paddingTop: "30px" },
+ { offset: 0.5, computedOffset: 0.5, easing: "ease", composite: "auto",
+ paddingTop: "20px" },
+ { offset: 0.75, computedOffset: 0.75, easing: "ease", composite: "auto",
+ paddingTop: "20px" },
+ { offset: 0.85, computedOffset: 0.85, easing: "ease", composite: "auto",
+ paddingTop: "30px" },
+ { offset: 0.851, computedOffset: 0.851, easing: "ease", composite: "auto",
+ paddingTop: "60px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ paddingTop: "70px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected frames for ' +
+ 'overlapping keyframes');
+
+// Gecko-specific test case: We are specifically concerned here that the
+// computed value for filter, "none", is correctly represented.
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-filter 100s';
+
+ const frames = getKeyframes(div);
+
+ // Initial keyframe should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+ filter: "none" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ filter: "blur(5px) sepia(60%) saturate(30%)" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animations with filter properties and missing keyframes');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-filter-drop-shadow 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
+ filter: "drop-shadow(rgb(0, 255, 0) 10px 10px 10px)" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ filter: "drop-shadow(rgb(255, 0, 0) 50px 30px 10px)" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animation with drop-shadow of filter property');
+
+// Gecko-specific test case: We are specifically concerned here that the
+// computed value for text-shadow and a "none" specified on a keyframe
+// are correctly represented.
+
+test(t => {
+ const div = addDiv(t);
+ div.style.textShadow = '1px 1px 2px rgb(0, 0, 0), ' +
+ '0 0 16px rgb(0, 0, 255), ' +
+ '0 0 3.2px rgb(0, 0, 255)';
+ div.style.animation = 'anim-text-shadow 100s';
+
+ const frames = getKeyframes(div);
+
+ // Initial keyframe should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+ textShadow: "rgb(0, 0, 0) 1px 1px 2px,"
+ + " rgb(0, 0, 255) 0px 0px 16px,"
+ + " rgb(0, 0, 255) 0px 0px 3.2px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ textShadow: "none" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animations with text-shadow properties and missing keyframes');
+
+// Gecko-specific test case: We are specifically concerned here that the
+// initial value for background-size and the specified list are correctly
+// represented.
+
+test(t => {
+ const div = addDiv(t);
+
+ div.style.animation = 'anim-background-size 100s';
+ let frames = getKeyframes(div);
+
+ assert_equals(frames.length, 2, "number of frames");
+
+ // Initial keyframe should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+ backgroundSize: "auto" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ backgroundSize: "50% auto, 6px auto, contain" },
+ ];
+
+ for (let i = 0; i < frames.length; i++) {
+ assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
+ }
+
+ // Test inheriting a background-size value
+
+ expected[0].backgroundSize = div.style.backgroundSize =
+ "30px auto, 40% auto, auto";
+ frames = getKeyframes(div);
+
+ for (let i = 0; i < frames.length; i++) {
+ assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i
+ + " after updating current style");
+ }
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animations with background-size properties and missing keyframes');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-variables 100s';
+
+ const frames = getKeyframes(div);
+
+ // Initial keyframe should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+ transform: "none" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ transform: "translate(100px)" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animations with CSS variables as keyframe values');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-variables-shorthand 100s';
+
+ const frames = getKeyframes(div);
+
+ // Initial keyframe should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+ marginBottom: "0px",
+ marginLeft: "0px",
+ marginRight: "0px",
+ marginTop: "0px" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ marginBottom: "100px",
+ marginLeft: "100px",
+ marginRight: "100px",
+ marginTop: "100px" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animations with CSS variables as keyframe values in a shorthand property');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-custom-property-in-keyframe 100s steps(2, start)';
+
+ const frames = getKeyframes(div);
+
+ // Initial keyframe should be replace as per sections 7 and 8 of
+ // https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "steps(2, start)", composite: "replace",
+ color: "rgb(0, 0, 0)" },
+ { offset: 1, computedOffset: 1, easing: "steps(2, start)", composite: "auto",
+ color: "rgb(0, 255, 0)" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animations with a CSS variable which is overriden by the value in keyframe');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-only-custom-property-in-keyframe 100s';
+
+ const frames = getKeyframes(div);
+
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
+ transform: "translate(100px)" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
+ transform: "none" },
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animations with only custom property in a keyframe');
+
+test(t => {
+ const div = addDiv(t);
+
+ // Add custom @keyframes rule
+ const stylesheet = document.styleSheets[0];
+ const keyframes = '@keyframes anim-custom { to { left: 100px } }';
+ const ruleIndex = stylesheet.insertRule(keyframes, 0);
+ const keyframesRule = stylesheet.cssRules[ruleIndex];
+
+ t.add_cleanup(function() {
+ stylesheet.deleteRule(ruleIndex);
+ });
+
+ div.style.animation = 'anim-custom 100s';
+
+ // Sanity check the initial result
+ let frames = getKeyframes(div);
+ assert_frames_equal(
+ frames[frames.length - 1],
+ {
+ offset: 1,
+ computedOffset: 1,
+ easing: 'ease',
+ composite: 'auto',
+ left: '100px',
+ },
+ 'Keyframes reflect the initial @keyframes rule'
+ );
+
+ // Update the @keyframes rule
+ keyframesRule.deleteRule(0);
+ keyframesRule.appendRule('to { left: 200px }');
+
+ // Check the result from getKeyframes() is updated
+ frames = getKeyframes(div);
+ assert_frames_equal(
+ frames[frames.length - 1],
+ {
+ offset: 1,
+ computedOffset: 1,
+ easing: 'ease',
+ composite: 'auto',
+ left: '200px',
+ },
+ 'Keyframes reflects the updated @keyframes rule'
+ );
+}, 'KeyframeEffect.getKeyframes() reflects changes to @keyframes rules');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-only-timing-function-for-from-and-to 100s';
+
+ const frames = getKeyframes(div);
+
+ // Implicit initial and final keyframes should be replace as per sections
+ // 7 and 8 of https://drafts.csswg.org/css-animations-2/#keyframes
+ const expected = [
+ { offset: 0, computedOffset: 0, easing: "linear", composite: "auto" },
+ { offset: 0, computedOffset: 0, easing: "ease", composite: "replace", left: "auto" },
+ { offset: 0.5, computedOffset: 0.5, easing: "ease", composite: "auto", left: "10px" },
+ { offset: 1, computedOffset: 1, easing: "linear", composite: "auto" },
+ { offset: 1, computedOffset: 1, easing: "ease", composite: "replace", left: "auto" }
+ ];
+ assert_frame_lists_equal(frames, expected);
+}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
+ 'animations with implicit values and a non-default timing' +
+ 'function specified for 0% and 100%');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/css/css-animations/KeyframeEffect-setKeyframes.tentative.html b/testing/web-platform/tests/css/css-animations/KeyframeEffect-setKeyframes.tentative.html
new file mode 100644
index 0000000000..7d8f845413
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/KeyframeEffect-setKeyframes.tentative.html
@@ -0,0 +1,122 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>KeyframeEffect.setKeyframes() for CSS animations</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim-simple {
+ from { left: 0px }
+ to { left: 100px }
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+// Note that the sanity check that getKeyframes() normally DOES return the
+// updated keyframes is contained in KeyframeEffect-getKeyframes.html.
+test(t => {
+ const div = addDiv(t);
+
+ // Add custom @keyframes rule
+ const stylesheet = document.styleSheets[0];
+ const keyframes = '@keyframes anim-custom { to { left: 100px } }';
+ const ruleIndex = stylesheet.insertRule(keyframes, 0);
+ const keyframesRule = stylesheet.cssRules[ruleIndex];
+
+ t.add_cleanup(function() {
+ stylesheet.deleteRule(ruleIndex);
+ });
+
+ div.style.animation = 'anim-custom 100s';
+
+ // Update the keyframes via the API
+ const animation = div.getAnimations()[0];
+ animation.effect.setKeyframes({ left: '200px' });
+
+ // Then update them via style
+ keyframesRule.deleteRule(0);
+ keyframesRule.appendRule('to { left: 300px }');
+
+ // The result should be the keyframes as set by the API, not via style.
+ const frames = animation.effect.getKeyframes();
+ assert_frames_equal(
+ frames[frames.length - 1],
+ {
+ offset: null,
+ computedOffset: 1,
+ easing: 'linear',
+ composite: 'auto',
+ left: '200px',
+ },
+ 'Keyframes reflect the value set via setKeyframes'
+ );
+}, 'KeyframeEffect.setKeyframes() causes subsequent changes to @keyframes'
+ + ' rules to be ignored');
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim-simple 100s';
+
+ const animation = div.getAnimations()[0];
+ assert_equals(animation.effect.getKeyframes()[0].easing, 'ease');
+
+ animation.effect.setKeyframes({ left: ['200px', '300px'] });
+ assert_equals(animation.effect.getKeyframes()[0].easing, 'linear');
+
+ div.style.animationTimingFunction = 'ease-in';
+ getComputedStyle(div).animationTimingFunction;
+
+ assert_equals(
+ animation.effect.getKeyframes()[0].easing,
+ 'linear',
+ 'Easing should be the easing set by the API'
+ );
+}, 'KeyframeEffect.setKeyframes() causes subsequent changes to'
+ + ' animation-timing-function to be ignored');
+
+test(t => {
+ const div = addDiv(t);
+
+ const stylesheet = document.styleSheets[0];
+ const keyframes = '@keyframes anim-custom { to { left: 100px } }';
+ const ruleIndex = stylesheet.insertRule(keyframes, 0);
+ const keyframesRule = stylesheet.cssRules[ruleIndex];
+
+ t.add_cleanup(function() {
+ stylesheet.deleteRule(ruleIndex);
+ });
+
+ div.style.animation = 'anim-custom 100s';
+
+ // Try updating in a way that throws an error
+ const animation = div.getAnimations()[0];
+ assert_throws_js(TypeError, () => {
+ animation.effect.setKeyframes({ left: '200px', offset: 'yer' });
+ });
+
+ keyframesRule.deleteRule(0);
+ keyframesRule.appendRule('to { left: 300px }');
+
+ // The result should be the keyframes as set via style.
+ const frames = animation.effect.getKeyframes();
+ assert_frames_equal(
+ frames[frames.length - 1],
+ {
+ offset: 1,
+ computedOffset: 1,
+ easing: 'ease',
+ composite: 'auto',
+ left: '300px',
+ },
+ 'Keyframes reflect the value set via style'
+ );
+}, 'KeyframeEffect.setKeyframes() should NOT cause subsequent changes to'
+ + ' @keyframes rules to be ignored if it threw');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/css/css-animations/KeyframeEffect-target.tentative.html b/testing/web-platform/tests/css/css-animations/KeyframeEffect-target.tentative.html
new file mode 100644
index 0000000000..4991762099
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/KeyframeEffect-target.tentative.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSSAnimation.effect.target</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim { }
+::before {
+ content: ''
+}
+::after {
+ content: ''
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'anim 100s';
+ const animation = div.getAnimations()[0];
+ assert_equals(animation.effect.target, div,
+ 'Animation.target is the animatable div');
+}, 'Returned CSS animations have the correct effect target');
+
+test(t => {
+ addStyle(t, { '.after::after': 'animation: anim 10s, anim 100s;' });
+ const div = addDiv(t, { class: 'after' });
+ const anims = document.getAnimations();
+ assert_equals(anims.length, 2,
+ 'Got animations running on ::after pseudo element');
+ assert_equals(anims[0].effect.target, anims[1].effect.target,
+ 'Both animations return the same target object');
+}, 'effect.target should return the same CSSPseudoElement object each time');
+
+test(t => {
+ addStyle(t, { '.after::after': 'animation: anim 10s;' });
+ const div = addDiv(t, { class: 'after' });
+ const pseudoTarget = document.getAnimations()[0].effect.target;
+ const effect = new KeyframeEffect(pseudoTarget,
+ { background: ["blue", "red"] },
+ 3 * MS_PER_SEC);
+ const newAnim = new Animation(effect, document.timeline);
+ newAnim.play();
+ const anims = document.getAnimations();
+ assert_equals(anims.length, 2,
+ 'Got animations running on ::after pseudo element');
+ assert_not_equals(anims[0], newAnim,
+ 'The scriped-generated animation appears last');
+ assert_equals(newAnim.effect.target, pseudoTarget,
+ 'The effect.target of the scripted-generated animation is ' +
+ 'the same as the one from the argument of ' +
+ 'KeyframeEffect constructor');
+ assert_equals(anims[0].effect.target, newAnim.effect.target,
+ 'Both animations return the same target object');
+}, 'effect.target from the script-generated animation should return the same ' +
+ 'CSSPseudoElement object as that from the CSS generated animation');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/META.yml b/testing/web-platform/tests/css/css-animations/META.yml
new file mode 100644
index 0000000000..3ef1997000
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/META.yml
@@ -0,0 +1,7 @@
+spec: https://drafts.csswg.org/css-animations/
+suggested_reviewers:
+ - plinss
+ - grorg
+ - dbaron
+ - tabatkins
+ - birtles
diff --git a/testing/web-platform/tests/css/css-animations/animate-with-color-mix.html b/testing/web-platform/tests/css/css-animations/animate-with-color-mix.html
new file mode 100644
index 0000000000..0b6d9bb4ab
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animate-with-color-mix.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <link rel="help" href="https://www.w3.org/TR/css-color-4/#interpolation">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>CSS animaitons with color-mix</title>
+</head>
+<style>
+ @keyframes missing-from-legacy-to {
+ to { background-color: rgb(0, 255, 0); }
+ }
+
+ @keyframes missing-to-legacy-from {
+ from { background-color: rgb(0, 255, 0); }
+ }
+
+ @keyframes missing-from-srgb-to {
+ to { background-color: color(srgb 0 1 0); }
+ }
+
+ @keyframes missing-to-srgb-from {
+ from { background-color: color(srgb 0 1 0); }
+ }
+
+ @keyframes missing-from-rgba-to {
+ to { background-color: rgba(255, 255, 255, 0.75); }
+ }
+
+ @keyframes missing-to-rgba-from {
+ from { background-color: rgba(255, 255, 255, 0.75); }
+ }
+
+ #target {
+ color: black;
+ background-color: color-mix(in srgb, white 50%, currentColor);
+ animation-duration: 1s;
+ animation-timing-function: linear;
+ animation-play-state: paused;
+ animation-fill-mode: forwards;
+ height: 100px;
+ width: 100px;
+ }
+ .missing-from-legacy-to {
+ animation-name: missing-from-legacy-to;
+ }
+
+ .missing-to-legacy-from {
+ animation-name: missing-to-legacy-from;
+ }
+
+ .missing-from-srgb-to {
+ animation-name: missing-from-srgb-to;
+ }
+
+ .missing-to-srgb-from {
+ animation-name: missing-to-srgb-from;
+ }
+
+ .missing-from-rgba-to {
+ background-color: color-mix(in srgb, transparent 50%, currentColor);
+ animation-name: missing-from-rgba-to;
+ }
+
+ .missing-to-rgba-from {
+ background-color: color-mix(in srgb, transparent 50%, currentColor);
+ animation-name: missing-to-rgba-from;
+ }
+
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<script src="/css/support/color-testcommon.js"></script>
+
+<body>
+ <div id="target"></div>
+ <div id="test"></div>
+</body>
+<script>
+ 'use strict';
+
+ async function runAnimationTest(t, name, expected_colors) {
+ const target = document.getElementById('target');
+ target.classList.add(name);
+ t.add_cleanup(() => {
+ target.classList.remove(name);
+ });
+ const anim = document.getAnimations()[0];
+ await anim.ready;
+ expected_colors.forEach(data => {
+ anim.currentTime = 1000 * data.at;
+ const actual = getComputedStyle(target).backgroundColor;
+ const expected = data.value;
+ assert_oklab_color(
+ actual, expected,
+ `Background color at ${100*data.at}% animation progress`);
+ });
+ }
+
+ const gray_to_green = [
+ { at: 0, value: 'oklab(0.5981 0.0000 0.0000)' },
+ { at: 0.25, value: 'oklab(0.6652 -0.0584 0.0449)' },
+ { at: 0.5, value: 'oklab(0.7323 -0.1169 0.0898)' },
+ { at: 0.75, value: 'oklab(0.7994 -0.1754 0.1346)' },
+ { at: 1, value: 'oklab(0.8664 -0.2338 0.1795)' }
+ ];
+
+ const green_to_gray = [
+ { at: 0, value: 'oklab(0.8664 -0.2338 0.1795)' },
+ { at: 0.25, value: 'oklab(0.7994 -0.1754 0.1346)' },
+ { at: 0.5, value: 'oklab(0.7323 -0.1169 0.0898)' },
+ { at: 0.75, value: 'oklab(0.6652 -0.0584 0.0449)' },
+ { at: 1, value: 'oklab(0.5981 0.0000 0.0000)' }
+ ];
+
+ const translucent_black_to_white = [
+ { at: 0, value: 'oklab(0.5981 0 0)' },
+ { at: 0.25, value: 'oklab(0.6784 0 0 / 0.9373)' },
+ { at: 0.5, value: 'oklab(0.7702 0 0 / 0.8745)' },
+ { at: 0.75, value: 'oklab(0.8762 0 0 / 0.8118)' },
+ { at: 1, value: 'oklab(1 0 0 / 0.75)' }
+ ];
+
+ const translucent_white_to_black = [
+ { at: 0, value: 'oklab(1 0 0 / 0.75)' },
+ { at: 0.25, value: 'oklab(0.8762 0 0 / 0.8118)' },
+ { at: 0.5, value: 'oklab(0.7702 0 0 / 0.8745)' },
+ { at: 0.75, value: 'oklab(0.6784 0 0 / 0.9373)' },
+ { at: 1, value: 'oklab(0.5981 0 0)' }
+ ];
+
+ window.onload = async () => {
+ promise_test(t => {
+ return runAnimationTest(t, 'missing-from-legacy-to', gray_to_green);
+ }, 'Animate from neutral keyframe with color-mix to legacy rgb');
+
+ promise_test(t => {
+ return runAnimationTest(t, 'missing-to-legacy-from', green_to_gray);
+ }, 'Animate from legacy rgb to neutral keyframe with color-mix');
+
+ promise_test(t => {
+ return runAnimationTest(t, 'missing-from-srgb-to', gray_to_green);
+ }, 'Animate from neutral keyframe with color-mix to srgb');
+
+ promise_test(t => {
+ return runAnimationTest(t, 'missing-to-srgb-from', green_to_gray);
+ }, 'Animate from srgb to neutral keyframe with color-mix');
+
+ promise_test(t => {
+ return runAnimationTest(t, 'missing-from-rgba-to',
+ translucent_black_to_white);
+ }, 'Animate from color-mix with transparency to legacy rgba');
+
+ promise_test(t => {
+ return runAnimationTest(t, 'missing-to-rgba-from',
+ translucent_white_to_black);
+ }, 'Animate from legacy rgba to color-mix with transparency');
+ };
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/animation-base-response-001.html b/testing/web-platform/tests/css/css-animations/animation-base-response-001.html
new file mode 100644
index 0000000000..c6731e1798
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-base-response-001.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<title>Test that non-animated style is responsive to animated properties</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @keyframes font_size_animation {
+ from { font-size: 10px; }
+ to { font-size: 20px; }
+ }
+ @keyframes var_animation {
+ from { --x: 10px; }
+ to { --x: 20px; }
+ }
+ #targets > div {
+ animation-duration: 1000s;
+ animation-delay: -500s;
+ animation-timing-function: steps(2, end);
+ }
+
+ #target1 {
+ animation-name: font_size_animation;
+ font-size: 1px;
+ width: 1em;
+ }
+ #ref1 {
+ width: 15px;
+ }
+
+ #target2 {
+ animation-name: font_size_animation;
+ font-size: 1px;
+ width: 1ex;
+ }
+ #ref2 {
+ font-size: 15px;
+ width: 1ex;
+ }
+
+ #target3 {
+ animation-name: var_animation;
+ --x: 0px;
+ width: var(--x);
+ }
+ #ref3 {
+ width: 20px;
+ }
+</style>
+<div id="targets">
+ <div id="target1"></div>
+ <div id="target2"></div>
+ <div id="target3"></div>
+</div>
+<div id="refs">
+ <div id="ref1"></div>
+ <div id="ref2"></div>
+ <div id="ref3"></div>
+</div>
+<script>
+
+// Test that the computed value of the given property is equal on
+// 'target' and 'ref'.
+function test_ref(target, ref, property, description) {
+ test(() => {
+ let actual = getComputedStyle(target).getPropertyValue(property);
+ let expected = getComputedStyle(ref).getPropertyValue(property);
+ assert_equals(actual, expected);
+ }, description);
+}
+
+test_ref(target1, ref1, 'width', 'em units respond to font-size animation');
+test_ref(target2, ref2, 'width', 'ex units respond to font-size animation');
+test_ref(target3, ref3, 'width', 'var() references respond to custom property animation');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-base-response-002.html b/testing/web-platform/tests/css/css-animations/animation-base-response-002.html
new file mode 100644
index 0000000000..e9ea964f75
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-base-response-002.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Test that rem units are responsive to animated font-size on root</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @keyframes font_size_animation {
+ from { font-size: 10px; }
+ to { font-size: 20px; }
+ }
+ :root {
+ font-size: 1px;
+ animation: font_size_animation steps(2, end) 1000s -500s;
+ }
+
+ #target1 {
+ width: 1rem;
+ }
+</style>
+<div id="target1"></div>
+<script>
+test(() => {
+ assert_equals(getComputedStyle(target1).getPropertyValue('width'), '15px');
+}, 'Animated font-size on root affects rem units');
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-base-response-003.html b/testing/web-platform/tests/css/css-animations/animation-base-response-003.html
new file mode 100644
index 0000000000..d6e6da7309
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-base-response-003.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Tests that identical elements in the base style responds to font-size animation</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @keyframes font_size_animation {
+ from { font-size: 10px; }
+ to { font-size: 20px; }
+ }
+ div {
+ font-size: 1px;
+ min-width: 1em;
+ animation: font_size_animation steps(2, end) 1000s -500s;
+ }
+</style>
+<div></div>
+<div></div>
+<div></div>
+<script>
+test(() => {
+ let divs = document.querySelectorAll('div');
+ for (let div of divs)
+ assert_equals(getComputedStyle(div).getPropertyValue('min-width'), '15px');
+}, 'Identical elements are all responsive to font-size animation');
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-base-response-004.html b/testing/web-platform/tests/css/css-animations/animation-base-response-004.html
new file mode 100644
index 0000000000..5638a572e9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-base-response-004.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Tests that base responds to font-affecting properties appearing via setKeyframes</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #target1 {
+ font-size: 10px;
+ height: 1em;
+ }
+</style>
+<div id=target1></div>
+<script>
+ test(function() {
+ getComputedStyle(target1).height;
+
+ let animation = target1.animate([
+ { height: '50px' },
+ { height: '100px' },
+ ], {
+ duration: 1000000,
+ delay: -500000,
+ easing: 'steps(2, end)'
+ });
+
+ assert_equals(getComputedStyle(target1).height, '75px');
+
+ animation.effect.setKeyframes([
+ { fontSize: '10px' },
+ { fontSize: '20px' },
+ ]);
+
+ assert_equals(getComputedStyle(target1).height, '15px');
+ }, 'Base is responsive to font-affecting appearing via setKeyframes');
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-before-initial-box-construction-001.html b/testing/web-platform/tests/css/css-animations/animation-before-initial-box-construction-001.html
new file mode 100644
index 0000000000..558c5ea186
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-before-initial-box-construction-001.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="mailto:https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1605610">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#extensions-to-the-element-interface">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+let t = async_test("animations started before initial-containing-block creation properly function");
+let loaded = false;
+let finished = false;
+let span;
+
+function check() {
+ if (!loaded || !finished)
+ return;
+
+ assert_equals(getComputedStyle(span).opacity, "1", "Animation should be properly finished");
+ t.done();
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+ span = document.createElement("span");
+ span.id = "myspan";
+ span.innerHTML = `Some content`;
+ document.body.appendChild(span);
+
+ let anim = span.animate([
+ {
+ opacity: "0",
+ },
+ {
+ opacity: "1",
+ },
+ ], {
+ duration: 300,
+ });
+
+ anim.finished.then(t.step_func(function() {
+ finished = true;
+ check();
+ }));
+});
+
+window.addEventListener("load", t.step_func(function() {
+ loaded = true;
+ check();
+}));
+</script>
+<!-- It is important that there are no more script after this stylesheet -->
+<link rel="stylesheet" href="empty-sheet.css?pipe=trickle(d2)">
+<body>
+</body>
diff --git a/testing/web-platform/tests/css/css-animations/animation-canceled-by-parent-details-element-being-closed.html b/testing/web-platform/tests/css/css-animations/animation-canceled-by-parent-details-element-being-closed.html
new file mode 100644
index 0000000000..3f6521fccf
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-canceled-by-parent-details-element-being-closed.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations: animation should be canceled when a parent details element is closed</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+
+@keyframes anim {
+ to { margin-left: 100px }
+}
+
+</style>
+<div id="log"></div>
+<script>
+
+promise_test(async t => {
+ // create a <div> contained within a <details> element
+ const details = addElement(t, "details", { "open": "open" });
+ const div = addDiv(t);
+ details.appendChild(div);
+
+ // start an animation on the <div>
+ div.style.animation = 'anim 1s';
+ const animation = div.getAnimations()[0];
+ await animation.ready;
+
+ // ensure the animation running on the <div> is canceled as a result of closing the <details>
+ const canceled = new Promise(resolve => animation.addEventListener("cancel", resolve));
+ details.removeAttribute("open");
+ await canceled;
+}, 'A CSS Animation running on an element within a <details> element is canceled after the <details> element is closed.');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-change-underlying-value-changed-in-flight.html b/testing/web-platform/tests/css/css-animations/animation-change-underlying-value-changed-in-flight.html
new file mode 100644
index 0000000000..444ea73785
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-change-underlying-value-changed-in-flight.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Changing the underlying value of an animated property with implicit keyframes</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+
+@keyframes implicit-from {
+ to { margin-left: 100px }
+}
+
+@keyframes implicit-to {
+ from { margin-left: 100px }
+}
+
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+const implicit_keyframe_test = (animationName, offset) => {
+ test(t => {
+ const div = addDiv(t);
+
+ // Set up animation to be paused and be at its mid-way point through easing.
+ div.style.animation = `${animationName} 10s paused steps(2, start)`;
+ const animation = div.getAnimations()[0];
+
+ assert_equals(getComputedStyle(div).marginLeft, "50px", "Computed style before changing the underlying style");
+
+ // Change the underlying value.
+ div.style.marginLeft = "200px";
+ assert_equals(getComputedStyle(div).marginLeft, "150px", "Computed style after changing the underlying style");
+ }, `Changing the underlying value of an animated property with an implicit ${offset}% keyframe`);
+};
+
+implicit_keyframe_test("implicit-from", "0");
+implicit_keyframe_test("implicit-to", "100");
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-common-ref.html b/testing/web-platform/tests/css/css-animations/animation-common-ref.html
new file mode 100644
index 0000000000..ddc7da67dd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-common-ref.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<link rel="author" title="Brian Birtles" href="mailto:bbirtles@mozilla.com">
+<style>
+div {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+}
+</style>
+<div></div>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/animation-composition-keyframes.html b/testing/web-platform/tests/css/css-animations/animation-composition-keyframes.html
new file mode 100644
index 0000000000..7051186f8b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-composition-keyframes.html
@@ -0,0 +1,113 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>animation-composition test in keyframes</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-composition">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="support/testcommon.js"></script>
+<style>
+ @keyframes anim {
+ from {
+ animation-composition: add;
+ filter: blur(10px);
+ width: 100px;
+ }
+ 50% {
+ animation-composition: accumulate;
+ filter: blur(15px);
+ width: 228px;
+ }
+ to {
+ animation-composition: replace;
+ filter: blur(50px);
+ width: 1337px;
+ }
+ }
+
+ .anim-target {
+ animation: anim 1s;
+ animation-fill-mode: forwards;
+ animation-timing-function: linear;
+ filter: blur(5px);
+ width: 50px;
+ }
+
+ .replace {
+ animation-composition: replace;
+ }
+
+ .add {
+ animation-composition: add;
+ }
+
+ .accumulate {
+ animation-composition: accumulate;
+ }
+</style>
+<div id="log"></div>
+<script>
+ function run_test_case(element, property, composite_type, timing_value_map) {
+ element.classList.add(composite_type);
+ const anim = element.getAnimations()[0];
+ for (const [time, value] of Object.entries(timing_value_map)) {
+ anim.currentTime = time;
+ const property_value = getComputedStyle(element).getPropertyValue(property);
+ assert_equals(property_value, value, "at time " + time);
+ }
+ element.classList.remove(composite_type);
+ }
+
+ const test_cases = [
+ ["filter", {
+ "replace": {
+ 0: "blur(5px) blur(10px)",
+ 250: "blur(12.5px) blur(5px)",
+ 500: "blur(20px)",
+ 1000: "blur(50px)"
+ },
+ "add": {
+ 0: "blur(5px) blur(10px)",
+ 250: "blur(12.5px) blur(5px)",
+ 500: "blur(20px)",
+ 1000: "blur(50px)"
+ },
+ "accumulate": {
+ 0: "blur(5px) blur(10px)",
+ 250: "blur(12.5px) blur(5px)",
+ 500: "blur(20px)",
+ 1000: "blur(50px)"
+ }
+ }],
+ ["width", {
+ "replace": {
+ 0: "150px",
+ 250: "214px",
+ 500: "278px",
+ 1000: "1337px"
+ },
+ "add": {
+ 0: "150px",
+ 250: "214px",
+ 500: "278px",
+ 1000: "1337px"
+ },
+ "accumulate": {
+ 0: "150px",
+ 250: "214px",
+ 500: "278px",
+ 1000: "1337px"
+ }
+ }]
+ ]
+
+ for (const test_case of test_cases) {
+ const property = test_case[0];
+ const test_data = test_case[1];
+ for (const [composite_type, expected_values] of Object.entries(test_data)) {
+ test(t => {
+ let elem = addDiv(t, {"class": "anim-target"});
+ run_test_case(elem, property, composite_type, expected_values);
+ }, "animation-composition: " + composite_type + " of property " + property);
+ }
+ }
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-animations/animation-composition.html b/testing/web-platform/tests/css/css-animations/animation-composition.html
new file mode 100644
index 0000000000..c46d55a877
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-composition.html
@@ -0,0 +1,110 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>animation-composition test</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-composition">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="support/testcommon.js"></script>
+<style>
+ @keyframes anim {
+ from {
+ filter: blur(10px);
+ width: 100px;
+ }
+ 50% {
+ filter: blur(15px);
+ width: 228px;
+ }
+ to {
+ filter: blur(20px);
+ width: 1337px;
+ }
+ }
+
+ .anim-target {
+ animation: anim 1s;
+ animation-fill-mode: forwards;
+ animation-timing-function: linear;
+ filter: blur(5px);
+ width: 50px;
+ }
+
+ .replace {
+ animation-composition: replace;
+ }
+
+ .add {
+ animation-composition: add;
+ }
+
+ .accumulate {
+ animation-composition: accumulate;
+ }
+</style>
+<div id="log"></div>
+<script>
+ function run_test_case(element, property, composite_type, timing_value_map) {
+ element.classList.add(composite_type);
+ const anim = element.getAnimations()[0];
+ for (const [time, value] of Object.entries(timing_value_map)) {
+ anim.currentTime = time;
+ const property_value = getComputedStyle(element).getPropertyValue(property);
+ assert_equals(property_value, value, "at time " + time);
+ }
+ element.classList.remove(composite_type);
+ }
+
+ const test_cases = [
+ ["filter", {
+ "replace": {
+ 0: "blur(10px)",
+ 250: "blur(12.5px)",
+ 500: "blur(15px)",
+ 1000: "blur(20px)"
+ },
+ "add": {
+ 0: "blur(5px) blur(10px)",
+ 250: "blur(5px) blur(12.5px)",
+ 500: "blur(5px) blur(15px)",
+ 1000: "blur(5px) blur(20px)"
+ },
+ "accumulate": {
+ 0: "blur(15px)",
+ 250: "blur(17.5px)",
+ 500: "blur(20px)",
+ 1000: "blur(25px)"
+ }
+ }],
+ ["width", {
+ "replace": {
+ 0: "100px",
+ 250: "164px",
+ 500: "228px",
+ 1000: "1337px"
+ },
+ "add": {
+ 0: "150px",
+ 250: "214px",
+ 500: "278px",
+ 1000: "1387px"
+ },
+ "accumulate": {
+ 0: "150px",
+ 250: "214px",
+ 500: "278px",
+ 1000: "1387px"
+ }
+ }]
+ ]
+
+ for (const test_case of test_cases) {
+ const property = test_case[0];
+ const test_data = test_case[1];
+ for (const [composite_type, expected_values] of Object.entries(test_data)) {
+ test(t => {
+ let elem = addDiv(t, {"class": "anim-target"});
+ run_test_case(elem, property, composite_type, expected_values);
+ }, "animation-composition: " + composite_type + " of property " + property);
+ }
+ }
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-animations/animation-css-variable-dependent-property.html b/testing/web-platform/tests/css/css-animations/animation-css-variable-dependent-property.html
new file mode 100644
index 0000000000..26d183f116
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-css-variable-dependent-property.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations: Dependent property updates correctly when animating a declared custom property</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@property --c {
+ syntax: "<color>";
+ inherits: true;
+ initial-value: black;
+}
+@keyframes color-shift {
+ 0% {
+ --c: black;
+ }
+ 100% {
+ --c: white;
+ }
+}
+#target {
+ color: var(--c);
+ animation: color-shift 1s linear 1 forwards paused
+}
+</style>
+<div id=target></div>
+<div id="log"></div>
+<script>
+
+test(t => {
+ const animation = target.getAnimations()[0];
+
+ assert_equals(
+ getComputedStyle(target).color,
+ 'rgb(0, 0, 0)'
+ );
+
+ animation.currentTime = 500;
+
+ assert_equals(
+ getComputedStyle(target).color,
+ 'rgb(128, 128, 128)'
+ );
+
+ animation.currentTime = 1000;
+
+ assert_equals(
+ getComputedStyle(target).color,
+ 'rgb(255, 255, 255)'
+ );
+
+}, 'Dependent property updates correctly');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-css-variable-in-keyframe-adjusted.html b/testing/web-platform/tests/css/css-animations/animation-css-variable-in-keyframe-adjusted.html
new file mode 100644
index 0000000000..f8e760c8ff
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-css-variable-in-keyframe-adjusted.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations: adjust value of CSS variable used in keyframes</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+
+@keyframes anim {
+ from { margin-left: var(--margin-left) }
+ to { margin-left: calc(var(--margin-left) * 2) }
+}
+
+</style>
+<div id="log"></div>
+<script>
+
+test(t => {
+ const div = addDiv(t);
+ div.style.setProperty('--margin-left', '100px');
+
+ div.style.animation = 'anim 1s linear';
+ const animation = div.getAnimations()[0];
+ animation.currentTime = 500;
+
+ assert_equals(
+ getComputedStyle(div).marginLeft,
+ '150px',
+ 'Animated value before updating variable'
+ );
+
+ div.style.setProperty('--margin-left', '200px');
+
+ assert_equals(
+ getComputedStyle(div).marginLeft,
+ '300px',
+ 'Animated value after updating variable'
+ );
+}, 'Animations reflect changes to variables on element');
+
+test(t => {
+ const parentDiv = addDiv(t);
+ const div = addDiv(t);
+ parentDiv.appendChild(div);
+ parentDiv.style.setProperty('--margin-left', '100px');
+
+ div.style.animation = 'anim 1s linear';
+ const animation = div.getAnimations()[0];
+ animation.currentTime = 500;
+
+ assert_equals(
+ getComputedStyle(div).marginLeft,
+ '150px',
+ 'Animated value before updating variable'
+ );
+
+ parentDiv.style.setProperty('--margin-left', '200px');
+
+ assert_equals(
+ getComputedStyle(div).marginLeft,
+ '300px',
+ 'Animated value after updating variable'
+ );
+}, 'Animations reflect changes to variables on parent element');
+
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-delay-001-manual.html
new file mode 100644
index 0000000000..af64b9e722
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-001-manual.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - negative value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-18 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-delay is set to a negative time offset,
+ animation will execute as soon as it is applied
+ but act as if the animation had started the specified
+ time in the past.">
+<style>
+ div {
+ animation-timing-function: linear;
+
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ #test-negative-delay {
+ animation-name: test-negative-delay;
+ animation-duration: 10s;
+ animation-delay: -5s;
+
+ background-color: blue;
+ }
+
+ #ref-no-delay {
+ animation-name: ref-no-delay;
+ animation-duration: 5s;
+
+ background-color: yellow;
+ }
+
+ @keyframes test-negative-delay {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+
+ @keyframes ref-no-delay {
+ from {
+ left: 75px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there are a filled blue square with 'Filler Text'
+ and a filled yellow square with 'Filler Text', and if the two squares
+ start moving together from right to left as soon as the page loads.
+ </p>
+ <div id="test-negative-delay">Filler Text</div>
+ <div id="ref-no-delay">Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-delay-002-manual.html
new file mode 100644
index 0000000000..32b3ae48cf
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-002-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - positive value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-18 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-delay is set to a positive time offset,
+ animation will delay execution by the specified offset value.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-delay: 5s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left after about 5 seconds
+ from the time the page is loaded.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-delay-003-manual.html
new file mode 100644
index 0000000000..7b90c04bbc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-003-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - 0s</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-18 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-delay is set to 0s (zero seconds),
+ animation will execute as soon as it is applied.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-delay: 0s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left as soon as the page loads.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-004-manual.html b/testing/web-platform/tests/css/css-animations/animation-delay-004-manual.html
new file mode 100644
index 0000000000..9eaab1448c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-004-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-18 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-delay applies to the ::after pseudo element.">
+<style>
+ div::after {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-delay: 5s;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left after about 5 seconds
+ from the time the page is loaded.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-005-manual.html b/testing/web-platform/tests/css/css-animations/animation-delay-005-manual.html
new file mode 100644
index 0000000000..7c41bdf677
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-005-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-18 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-delay applies to the ::before pseudo element.">
+<style>
+ div::before {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-delay: 5s;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left after about 5 seconds
+ from the time the page is loaded.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-006-manual.html b/testing/web-platform/tests/css/css-animations/animation-delay-006-manual.html
new file mode 100644
index 0000000000..1be2e380e9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-006-manual.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - initial keyword</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#values">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-delay property accepts 'initial' keyword.">
+<style>
+ #contatiner {
+ animation-delay: 5s;
+ }
+
+ #test {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-delay: initial;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left upon page load and lasts for a span of 10 seconds.
+ </p>
+ <div id="container">
+ <div id="test">Filler Text</div>
+ </div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-007-manual.html b/testing/web-platform/tests/css/css-animations/animation-delay-007-manual.html
new file mode 100644
index 0000000000..a5549d5ff5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-007-manual.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - inherit keyword</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#values">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-delay property accepts 'inherit' keyword.">
+<style>
+ #container {
+ animation-delay: 5s;
+ }
+
+ #test {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-delay: inherit;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left for about 5 seconds, and then moves
+ from right to left and lasts for a span of 5 seconds.
+ </p>
+ <div id="container">
+ <div id="test">Filler Text</div>
+ </div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-008.html b/testing/web-platform/tests/css/css-animations/animation-delay-008.html
new file mode 100644
index 0000000000..52883fa72f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-008.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - liveness</title>
+<link rel="author" title="Brian Birtles" href="mailto:bbirtles@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animations">
+<meta name="assert" content="Check that changes to animation-delay on a running
+animation are reflected in output">
+<meta name="flags" content="dom">
+<link rel="match" href="animation-common-ref.html">
+<style>
+@keyframes two-step {
+ from { background-color: red }
+ 50% { background-color: green }
+ to { background-color: green }
+}
+div {
+ width: 100px;
+ height: 100px;
+ background-color: orange;
+}
+</style>
+<div></div>
+<script>
+// Set up animation with no delay
+var div = document.querySelector('div');
+div.style.animation = 'two-step 200s steps(1)';
+window.getComputedStyle(div).animation;
+
+// Wait until animation has started and change delay
+window.requestAnimationFrame(function() {
+ // Fast-forward to mid-point
+ div.style.animationDelay = '-100s';
+ document.documentElement.removeAttribute('class');
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-009.html b/testing/web-platform/tests/css/css-animations/animation-delay-009.html
new file mode 100644
index 0000000000..6d25b8b1bc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-009.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - liveness with animationend</title>
+<link rel="author" title="Brian Birtles" href="mailto:bbirtles@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animations">
+<meta name="assert" content="Check that shortening the animation-delay triggers
+an animationend event">
+<meta name="flags" content="dom">
+<link rel="match" href="animation-common-ref.html">
+<style>
+@keyframes all-red {
+ from { background-color: red }
+ to { background-color: red }
+}
+div {
+ width: 100px;
+ height: 100px;
+ background-color: orange;
+}
+</style>
+<div></div>
+<script>
+// Set up animation with no delay
+var div = document.querySelector('div');
+div.style.animation = 'all-red 1000s';
+window.getComputedStyle(div).animation;
+
+// Set up an animationend event handler to change the background color
+div.addEventListener('animationend', function() {
+ div.style.animation = 'none';
+ div.style.backgroundColor = 'green';
+});
+
+// Wait until animation has started and change delay
+window.requestAnimationFrame(function() {
+ // Fast-forward to end
+ div.style.animationDelay = '-1000s';
+
+ // Wait a frame to allow the event handler to run
+ window.requestAnimationFrame(function() {
+ document.documentElement.removeAttribute('class');
+ });
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-010.html b/testing/web-platform/tests/css/css-animations/animation-delay-010.html
new file mode 100644
index 0000000000..d18064dd21
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-010.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-delay - liveness with
+ animationstart</title>
+<link rel="author" title="Brian Birtles" href="mailto:bbirtles@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animations">
+<meta name="assert" content="Check that extending the animation-delay triggers
+an animationstart event">
+<meta name="flags" content="dom">
+<link rel="match" href="animation-common-ref.html">
+<style>
+@keyframes all-orange {
+ from { background-color: orange }
+ to { background-color: orange }
+}
+div {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+}
+</style>
+<div></div>
+<script>
+// Set up animation with a negative delay such that it finishes very soon
+var div = document.querySelector('div');
+div.style.animation = 'all-orange 1000s -999.99s';
+
+// Wait for the animation to finish
+div.addEventListener('animationend', function() {
+ // Set up an animationstart event handler to change the background color
+ div.addEventListener('animationstart', function() {
+ div.style.animation = 'none';
+ div.style.backgroundColor = 'green';
+ });
+
+ // Then extend the delay so that the animation restarts
+ div.style.animationDelay = '0s';
+
+ // Wait a frame to allow the event handler to run
+ window.requestAnimationFrame(function() {
+ document.documentElement.removeAttribute('class');
+ });
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/animation-delay-011.html b/testing/web-platform/tests/css/css-animations/animation-delay-011.html
new file mode 100644
index 0000000000..415a574755
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-delay-011.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Animations Test: inherited animation-delay with mismatched animation-name length</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animations">
+<link rel="match" href="animation-common-ref.html">
+<style>
+div:after {
+ content: '';
+ display: block;
+ width: 100px;
+ height: 100px;
+ background: red;
+ animation: doesntmatter 50s linear infinite,
+ bg 100s step-end infinite;
+ animation-play-state: paused;
+ animation-delay: inherit;
+}
+
+@keyframes bg {
+ 50% { background: green; }
+}
+</style>
+<div style="animation-delay: -50s"></div>
diff --git a/testing/web-platform/tests/css/css-animations/animation-direction-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-direction-001-manual.html
new file mode 100644
index 0000000000..5e1ebde7b1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-direction-001-manual.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-direction - alternate</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-24 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-direction">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-direction is set to alternate,
+ animation cycle will iteration that are
+ odd counts are played in the normal direction,
+ and the animation cycle iterations that are
+ even counts are played in a reverse direction.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-direction: alternate;
+ animation-iteration-count: infinite;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left on the page load,
+ and then moves from left to right. This cycle gets repeated.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-direction-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-direction-002-manual.html
new file mode 100644
index 0000000000..8ed910be30
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-direction-002-manual.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-direction - normal</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-24 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-direction">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-direction is set to normal,
+ all iterations of animation are played as specified.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-direction: normal;
+ animation-iteration-count: infinite;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled color square with 'Filler Text',
+ which starts moving from right to left, then back to right and moves from
+ right to left again. This cycle gets repeated.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-direction-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-direction-003-manual.html
new file mode 100644
index 0000000000..12a5bd5fa1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-direction-003-manual.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-direction - alternate-reverse</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-24 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-direction">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-direction is set to alternate-reverse,
+ the animation cycle iterations that are
+ odd counts are played in the normal direction,
+ and the animation cycle iterations that are
+ even counts are played in a reverse direction.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-direction: alternate-reverse;
+ animation-iteration-count: infinite;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 0px;
+ }
+ to {
+ left: 150px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left on the page load,
+ and then moves from left to right. This cycle gets repeated.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-direction-004-manual.html b/testing/web-platform/tests/css/css-animations/animation-direction-004-manual.html
new file mode 100644
index 0000000000..83c6664d78
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-direction-004-manual.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-direction - reverse</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-24 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-direction">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-direction is set to reverse,
+ all iterations of the animation are played in the reverse direction
+ from the way they were specified.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-direction: reverse;
+ animation-iteration-count: infinite;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled color square with 'Filler Text',
+ which starts moving from left to right, then back to left again and moves from
+ left to right. This cycle gets repeated.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-direction-005-manual.html b/testing/web-platform/tests/css/css-animations/animation-direction-005-manual.html
new file mode 100644
index 0000000000..b6ec869fc2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-direction-005-manual.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-direction - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-24 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-direction">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-direction applies to the ::after pseudo element.">
+<style>
+ div::after {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-direction: alternate;
+ animation-iteration-count: infinite;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left on the page load,
+ and then moves from left to right. This cycle gets repeated.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-direction-006-manual.html b/testing/web-platform/tests/css/css-animations/animation-direction-006-manual.html
new file mode 100644
index 0000000000..c1173409a8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-direction-006-manual.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-direction - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-03-24 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-direction">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-direction applies to the ::before pseudo element">
+<style>
+ div::before {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-direction: alternate;
+ animation-iteration-count: infinite;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left on the page load,
+ and then moves from left to right. This cycle gets repeated.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-display-manual.html b/testing/web-platform/tests/css/css-animations/animation-display-manual.html
new file mode 100644
index 0000000000..e62b44dfa1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-display-manual.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation - display</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animations">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that setting 'display' property to 'none' terminates
+ running animation applied to the element, and updating 'display'
+ property to a value other than 'none' will start the animation.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which moves from right to left twice.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ var div = document.getElementsByTagName("div");
+
+ setTimeout(function() {
+ div[0].style.display = "none";
+ }, 5000);
+
+ setTimeout(function() {
+ div[0].style.display = "block";
+ }, 8000);
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-duration-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-duration-001-manual.html
new file mode 100644
index 0000000000..d012b907cb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-duration-001-manual.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-duration - blank value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-05 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-duration value is set blank, there will be no animation seen.">
+<style>
+ div {
+ animation-name: sample;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left for about 2 seconds upon page load,
+ then moves from right to left.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ var div = document.getElementsByTagName("div");
+ div[0].style.animationDuration = "";
+ setTimeout(setAnimationDuration, 2000);
+
+ function setAnimationDuration() {
+ div[0].style.animationDuration = "10s";
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-duration-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-duration-002-manual.html
new file mode 100644
index 0000000000..d0e7de1018
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-duration-002-manual.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-duration - finite value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-05 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-duration is set a finite time offset,
+ animation takes the specifies time to complete one cycle.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left upon page load and lasts for a span of 10 seconds.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-duration-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-duration-003-manual.html
new file mode 100644
index 0000000000..72e1070f1f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-duration-003-manual.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-duration - negative value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-05 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-duration is set to a negative value,
+ it is treated as 0s (zero seconds) and no animation is seen.">
+<style>
+ div {
+ animation-name: sample;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left for about 2 seconds upon page load,
+ then moves from right to left.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ var div = document.getElementsByTagName("div");
+ div[0].style.animationDuration = "-2s";
+ setTimeout(setAnimationDuration, 2000);
+
+ function setAnimationDuration() {
+ div[0].style.animationDuration = "10s";
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-duration-004-manual.html b/testing/web-platform/tests/css/css-animations/animation-duration-004-manual.html
new file mode 100644
index 0000000000..eda2a2c738
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-duration-004-manual.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-duration - 0s</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-05 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-duration is set to 0s (zero seconds),
+ animation occurs instantaneously, there will be no animation seen.">
+<style>
+ div {
+ animation-name: sample;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left for about 2 seconds upon page load,
+ then moves from right to left.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ var div = document.getElementsByTagName("div");
+ div[0].style.animationDuration = "0s";
+ setTimeout(setAnimationDuration, 2000);
+
+ function setAnimationDuration() {
+ div[0].style.animationDuration = "10s";
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-duration-005-manual.html b/testing/web-platform/tests/css/css-animations/animation-duration-005-manual.html
new file mode 100644
index 0000000000..ea6a331c8e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-duration-005-manual.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-duration - 0s, animation-fill-mode - forwards</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-05 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-duration is set to 0s (zero seconds),
+ and 'animation-fill-mode' is set to 'forwards',
+ the last frame of the animation will be displayed.">
+<style>
+ div {
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ }
+
+ #test {
+ animation-name: sample;
+ animation-duration: 0s;
+ animation-fill-mode: forwards;
+
+ background-color: blue;
+ }
+
+ #ref {
+ background-color: red;
+ left: 150px;
+ }
+
+ @keyframes sample {
+ from {
+ left: 0px;
+ }
+ to {
+ left: 150px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue with 'Filler Text',
+ and without animation, and if there is no red.
+ </p>
+ <div id="ref"></div>
+ <div id="test">Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-duration-006-manual.html b/testing/web-platform/tests/css/css-animations/animation-duration-006-manual.html
new file mode 100644
index 0000000000..2ad017b1fd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-duration-006-manual.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-duration - 0s, animation-fill-mode - both</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-05 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-duration is set to 0s (zero seconds),
+ and 'animation-fill-mode' is set to 'both',
+ the last frame of the animation will be displayed.">
+<style>
+ div {
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ }
+
+ #test {
+ animation-name: sample;
+ animation-duration: 0s;
+ animation-fill-mode: both;
+
+ background-color: blue;
+ }
+
+ #ref {
+ background-color: red;
+ left: 150px;
+ }
+
+ @keyframes sample {
+ from {
+ left: 0px;
+ }
+ to {
+ left: 150px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue with 'Filler Text',
+ and without animation, and if there is no red.
+ </p>
+ <div id="ref"></div>
+ <div id="test">Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-duration-007-manual.html b/testing/web-platform/tests/css/css-animations/animation-duration-007-manual.html
new file mode 100644
index 0000000000..94d71796dc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-duration-007-manual.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-duration - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-05 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-duration applies to the ::before pseudo element.">
+<style>
+ div::before {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left upon page load and lasts for a span of 10 seconds.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-duration-008-manual.html b/testing/web-platform/tests/css/css-animations/animation-duration-008-manual.html
new file mode 100644
index 0000000000..b5a8dba02a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-duration-008-manual.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-duration - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-05 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-duration applies to the ::after pseudo element.">
+<style>
+ div::after {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left upon page load and lasts for a span of 10 seconds.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-fill-mode-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-fill-mode-001-manual.html
new file mode 100644
index 0000000000..2176672367
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-fill-mode-001-manual.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-fill-mode - none</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-fill-mode is set to none,
+ animation has no effect when it is applied but not executing.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-fill-mode: none;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+ @keyframes sample {
+ from {
+ background-color: yellow;
+ }
+ to {
+ background-color: green;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled color square with 'Filler Text',
+ whose color gradually changes in the order: YELLOW to GREEN.
+ After the animation is finished, the color goes back to BLUE.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-fill-mode-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-fill-mode-002-manual.html
new file mode 100644
index 0000000000..753d93750a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-fill-mode-002-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-fill-mode - forwards</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-fill-mode is set to forwards,
+ animation will apply the property values for the time the amination ended
+ after the animation ends.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-fill-mode: forwards;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+ @keyframes sample {
+ from {
+ background-color: yellow;
+ }
+ to {
+ background-color: green;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled color square with 'Filler Text',
+ whose color gradually changes in the order:
+ YELLOW to GREEN.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-fill-mode-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-fill-mode-003-manual.html
new file mode 100644
index 0000000000..561237d267
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-fill-mode-003-manual.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-fill-mode - backwards</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-fill-mode is set to backwards,
+ animation-delay is set a positive time offset,
+ and animation-direction is 'normal' or 'alternate-reverse',
+ animation will apply the from or 0% keyframe
+ that will start the first iteration.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-delay: 5s;
+ animation-fill-mode: backwards;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+ @keyframes sample {
+ from {
+ background-color: yellow;
+ }
+ to {
+ background-color: green;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled color square with 'Filler Text',
+ whose color gradually changes in the order: YELLOW to GREEN.
+ After the animation is finished, the color goes back to BLUE.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-fill-mode-004-manual.html b/testing/web-platform/tests/css/css-animations/animation-fill-mode-004-manual.html
new file mode 100644
index 0000000000..77f8f49b70
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-fill-mode-004-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-fill-mode - both</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-delay">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check if animation-fill-mode is set to both,
+ the effects of both 'forwards' and 'backwards' will apply.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-delay: 5s;
+ animation-fill-mode: both;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+ @keyframes sample {
+ from {
+ background-color: yellow;
+ }
+ to {
+ background-color: green;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled color square with 'Filler Text',
+ whose color gradually changes in the order:
+ YELLOW to GREEN.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-fill-mode-005-manual.html b/testing/web-platform/tests/css/css-animations/animation-fill-mode-005-manual.html
new file mode 100644
index 0000000000..c5e2172b31
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-fill-mode-005-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-fill-mode - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-fill-mode applies to the ::after pseudo element.">
+<style>
+ div::after {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-fill-mode: forwards;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+ @keyframes sample {
+ from {
+ background-color: yellow;
+ }
+ to {
+ background-color: green;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled color square with 'Filler Text',
+ whose color gradually changes in the order:
+ YELLOW to GREEN.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-fill-mode-006-manual.html b/testing/web-platform/tests/css/css-animations/animation-fill-mode-006-manual.html
new file mode 100644
index 0000000000..4bf4b0b0ec
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-fill-mode-006-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-fill-mode - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-fill-mode applies to the ::before pseudo element.">
+<style>
+ div::before {
+ animation-name: sample;
+ animation-duration: 5s;
+ animation-fill-mode: forwards;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+ @keyframes sample {
+ from {
+ background-color: yellow;
+ }
+ to {
+ background-color: green;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled color square with 'Filler Text',
+ whose color gradually changes in the order:
+ YELLOW to GREEN.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-important-001.html b/testing/web-platform/tests/css/css-animations/animation-important-001.html
new file mode 100644
index 0000000000..6b1fcfd76b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-important-001.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<title>Test !important declarations vs. animation effects</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#cascade-origin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ CSS.registerProperty({
+ name: "--length-1",
+ syntax: "<length>",
+ initialValue: "0px",
+ inherits: false
+ });
+ CSS.registerProperty({
+ name: "--length-2",
+ syntax: "<length>",
+ initialValue: "0px",
+ inherits: false
+ });
+</script>
+<style>
+ @keyframes bgcolor_animation {
+ from { background-color: rgb(10, 10, 10); }
+ to { background-color: rgb(20, 20, 20); }
+ }
+ @keyframes color_and_bg_animation {
+ from { background-color: rgb(10, 10, 10); color: rgb(10, 10, 10); }
+ to { background-color: rgb(20, 20, 20); color: rgb(20, 20, 20); }
+ }
+ a, div, ::before{
+ animation-duration: 1000s;
+ animation-delay: -500s;
+ animation-timing-function: steps(2, end);
+ }
+ #target1 {
+ animation-name: bgcolor_animation;
+ }
+ #target2 {
+ background-color: rgb(0, 128, 0) !important;
+ animation-name: bgcolor_animation;
+ }
+ #target3 {
+ color: rgb(0, 128, 0) !important;
+ animation-name: color_and_bg_animation;
+ }
+ #target4::before {
+ color: rgb(0, 128, 0) !important;
+ animation-name: color_and_bg_animation;
+ content: " ";
+ }
+ #target5 {
+ animation-name: color_and_bg_animation;
+ }
+ #target5:visited {
+ color: rgb(0, 128, 0) !important;
+ }
+ #target6 {
+ background-color: white;
+ color: white !important;
+ }
+ #target7 {
+ --length-1: 10px;
+ --length-2: 10px !important;
+ }
+</style>
+<div id="target1"></div>
+<div id="target2"></div>
+<div id="target3"></div>
+<div id="target4"></div>
+<a href="" id="target5"></a>
+<span id="target6"></span>
+<span id="target7"></span>
+<script>
+test(() => {
+ assert_equals(getComputedStyle(target1).backgroundColor, 'rgb(15, 15, 15)');
+}, 'Interpolated value is observable');
+
+test(() => {
+ assert_equals(getComputedStyle(target2).backgroundColor, 'rgb(0, 128, 0)');
+}, 'Important rules override animations');
+
+test(() => {
+ assert_equals(getComputedStyle(target3).color, 'rgb(0, 128, 0)');
+ assert_equals(getComputedStyle(target3).backgroundColor, 'rgb(15, 15, 15)');
+}, 'Non-overriden interpolations are observable');
+
+test(() => {
+ assert_equals(getComputedStyle(target4, '::before').color, 'rgb(0, 128, 0)');
+ assert_equals(getComputedStyle(target4, '::before').backgroundColor, 'rgb(15, 15, 15)');
+}, 'Important rules override animations (::before)');
+
+test(() => {
+ assert_equals(getComputedStyle(target5).color, 'rgb(15, 15, 15)');
+ assert_equals(getComputedStyle(target5).backgroundColor, 'rgb(15, 15, 15)');
+}, 'Important rules do not override animations on :visited as seen from JS');
+
+test(() => {
+ getComputedStyle(target6).backgroundColor;
+
+ let animation = target6.animate([
+ { backgroundColor: 'rgb(10, 10, 10)' },
+ { backgroundColor: 'rgb(20, 20, 20)' },
+ ], {
+ duration: 1000000,
+ delay: -500000,
+ easing: 'steps(2, end)'
+ });
+
+ assert_equals(getComputedStyle(target6).backgroundColor, 'rgb(15, 15, 15)');
+ assert_equals(getComputedStyle(target6).color, 'rgb(255, 255, 255)');
+
+ animation.effect.setKeyframes([
+ { color: 'rgb(10, 10, 10)' },
+ { color: 'rgb(20, 20, 20)' },
+ ]);
+
+ assert_equals(getComputedStyle(target6).backgroundColor, 'rgb(255, 255, 255)');
+ assert_equals(getComputedStyle(target6).color, 'rgb(255, 255, 255)');
+}, 'Standard property animations appearing via setKeyframes do not override important declarations');
+
+test(() => {
+ getComputedStyle(target7).getPropertyValue('--length-1');
+
+ let animation = target7.animate([
+ { '--length-1': '100px' },
+ { '--length-1': '200px' },
+ ], {
+ duration: 1000000,
+ delay: -500000,
+ easing: 'steps(2, end)'
+ });
+
+ assert_equals(getComputedStyle(target7).getPropertyValue('--length-1'), '150px');
+ assert_equals(getComputedStyle(target7).getPropertyValue('--length-2'), '10px');
+
+ animation.effect.setKeyframes([
+ { '--length-2': '100px' },
+ { '--length-2': '200px' },
+ ]);
+
+ assert_equals(getComputedStyle(target7).getPropertyValue('--length-1'), '10px');
+ assert_equals(getComputedStyle(target7).getPropertyValue('--length-2'), '10px');
+}, 'Custom property animations appearing via setKeyframes do not override important declarations');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-important-002-ref.html b/testing/web-platform/tests/css/css-animations/animation-important-002-ref.html
new file mode 100644
index 0000000000..52855cb91e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-important-002-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+ a {
+ text-decoration: underline;
+ background-color: rgb(0, 150, 0);
+ }
+</style>
+<a style="color: rgb(150, 0, 0)">Unvisited</a>
+<a style="color: white">Visited</a>
diff --git a/testing/web-platform/tests/css/css-animations/animation-important-002.html b/testing/web-platform/tests/css/css-animations/animation-important-002.html
new file mode 100644
index 0000000000..2a68f8e0ca
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-important-002.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Test that animated properties on :visited are overridden by !important</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#cascade-origin">
+<link rel="match" href="animation-important-002-ref.html">
+<style>
+ @keyframes color_and_bg_animation {
+ from { background-color: rgb(0, 100, 0); color: rgb(100, 0, 0); }
+ to { background-color: rgb(0, 200, 0); color: rgb(200, 0, 0); }
+ }
+ a {
+ animation-name: color_and_bg_animation;
+ animation-duration: 1000s;
+ animation-delay: -500s;
+ animation-timing-function: steps(2, end);
+ }
+ a:visited {
+ color: white !important;
+ }
+</style>
+<a href="#unvisited">Unvisited</a>
+<a href="">Visited</a>
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-001-manual.html
new file mode 100644
index 0000000000..bcbc9eb122
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-001-manual.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-iteration-count - default value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-iteration-count is not set, 1 is taken by default and
+ animation will play from beginning to end once.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left once.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-002-manual.html
new file mode 100644
index 0000000000..1d62e77c31
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-002-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-iteration-count - infinite value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-iteration-count is set to infinite,
+ animation will repeat forever.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-iteration-count: infinite;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled square with 'Filler Text',
+ which repeatedly moves from right to left. Every time a cycle is finished,
+ the square gets re-positioned to right and continues to move left.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-003-manual.html
new file mode 100644
index 0000000000..75b07b93a0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-003-manual.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-iteration-count - negative value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-iteration-count is set to negative count,
+ it is invalid and animation will play from beginning to end once.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-iteration-count: -2;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left once.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-004-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-004-manual.html
new file mode 100644
index 0000000000..e1edfc7a2a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-004-manual.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-iteration-count - non-integer value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-iteration-count is set to non-integer,
+ animation will end partway through its last cycle.">
+<style>
+ #test-iteration-count {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-iteration-count: 2.1;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ #ref-path {
+ background-color: yellow;
+ height: 10px;
+ width: 250px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left along the yellow bar twice,
+ and for the third time it ends partway and then immediately positions
+ itself to the left.
+ </p>
+ <div id="test-iteration-count">Filler Text</div>
+ <div id="ref-path"></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-005-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-005-manual.html
new file mode 100644
index 0000000000..4bd79cfc0d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-005-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-iteration-count - integer value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-iteration-count is set to integer count,
+ animation will repeat the specified number of times.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-iteration-count: 2;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left twice.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-006-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-006-manual.html
new file mode 100644
index 0000000000..0c0c403333
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-006-manual.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-iteration-count - 0 (zero)</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-iteration-count is set to 0 (zero), no animation is seen.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left for about 2 seconds upon page load,
+ then moves from right to left once.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ var div = document.getElementsByTagName("div");
+ div[0].style.animationIterationCount = 0;
+ setTimeout(setAnimationDuration, 2000);
+
+ function setAnimationDuration() {
+ div[0].style.animationIterationCount = 1;
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-007-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-007-manual.html
new file mode 100644
index 0000000000..bb748d84ad
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-007-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-iteration-count - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-iteration-count applies to the ::after pseudo element.">
+<style>
+ div::after {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-iteration-count: 2;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left twice.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-008-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-008-manual.html
new file mode 100644
index 0000000000..645ac557ca
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-008-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-iteration-count - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-iteration-count applies to the ::before pseudo element.">
+<style>
+ div::before {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-iteration-count: 2;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left twice.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-009.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-009.html
new file mode 100644
index 0000000000..da86c81b93
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-009.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Animation Test: fractional animation-iteration-count</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#animation-iteration-count">
+<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes margin-animation {
+ from {
+ margin-left: 0px;
+ }
+ to {
+ margin-left: 100px;
+ }
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin-animation 1s -10s linear 1.5 normal forwards paused';
+ assert_equals(getComputedStyle(div).marginLeft, '50px');
+}, 'Basic floating point iteration count');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin-animation 1s -10s linear 3.25 normal forwards paused';
+ assert_equals(getComputedStyle(div).marginLeft, '25px');
+}, 'Floating point iteration count after multiple iterations');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin-animation 1s -10s linear 0.75 normal forwards paused';
+ assert_equals(getComputedStyle(div).marginLeft, '75px');
+}, 'Floating point iteration count during first iteration');
+
+promise_test(async t => {
+ const div = addDiv(t);
+ div.style.animation = 'margin-animation 1s -10s linear 1.75 alternate forwards paused';
+ assert_equals(getComputedStyle(div).marginLeft, '25px');
+}, 'Floating point iteration count with alternating directions');
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-count-calc.html b/testing/web-platform/tests/css/css-animations/animation-iteration-count-calc.html
new file mode 100644
index 0000000000..44e1e96a58
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-count-calc.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: Animation count accepts calc()</title>
+<link rel="author" name="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-values-3/#number-value">
+<link rel="help" href="https://drafts.csswg.org/css-animations/#animation-iteration-count">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="target" style="animation-iteration-count: calc(1 + 3)">
+<script>
+test(function() {
+ let target = document.getElementById("target");
+ assert_equals(getComputedStyle(target).animationIterationCount, "4")
+}, "calc() should be accepted in animation-iteration-count.");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-iteration-event-manual.html b/testing/web-platform/tests/css/css-animations/animation-iteration-event-manual.html
new file mode 100644
index 0000000000..e7845ea2e2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-iteration-event-manual.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation events - animationiteration</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-iteration-count">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#events">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animationiteration event occurs at the end of
+ each iteration of an animation for which animation-iteration-count
+ is greater than one.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-iteration-count: 3;
+
+ color: yellow;
+ font-weight: bolder;
+ font-size: xx-large;
+ text-align: center;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square, which moves from right to left
+ three times; and if the square contains digit 1 for the first animation,
+ digit 2 for the second, and 3 for the third animation.
+ </p>
+ <div></div>
+ <script>
+ var count = 1;
+ var div = document.getElementsByTagName("div");
+ div[0].innerHTML = count;
+ div[0].addEventListener("animationiteration", animationIteration, true);
+
+ function animationIteration() {
+ count += 1;
+ div[0].innerHTML = count;
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-keyframes-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-keyframes-001-manual.html
new file mode 100644
index 0000000000..eee2d42946
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-keyframes-001-manual.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: @keyframes - from, to, percentage</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#keyframes">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that @keyframes rule accepts 'from' and 'to'
+ keywords, and percentage values.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ }
+
+ @keyframes sample {
+ from {
+ background-color: blue;
+ }
+ 30% {
+ background-color: green;
+ }
+ 65% {
+ background-color: yellow;
+ }
+ to {
+ background-color: blue;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text'
+ initially on page load, and if the color of the square gradully changes
+ in order: BLUE to GREEN to YELLOW and back to BLUE in 10 seconds.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-keyframes-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-keyframes-002-manual.html
new file mode 100644
index 0000000000..544031304b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-keyframes-002-manual.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: @keyframes - negative percentage and higher than 100%</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#keyframes">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that a keyframe selector specifies negative percentage values
+ or values higher than 100%, the keyframe will be ignored.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ }
+
+ @keyframes sample {
+ -100% {
+ background-color: red;
+ }
+ 0% {
+ background-color: blue;
+ }
+ 30% {
+ background-color: green;
+ }
+ 65% {
+ background-color: yellow;
+ }
+ 100% {
+ background-color: blue;
+ }
+ 200% {
+ background-color: red;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text'
+ initially on page load, if the color of the square gradully changes
+ in order: BLUE to GREEN to YELLOW and back to BLUE in 10 seconds,
+ and if there is no red.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-keyframes-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-keyframes-003-manual.html
new file mode 100644
index 0000000000..b0c26ab4a1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-keyframes-003-manual.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: @keyframes - animation-timing-function</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#keyframes">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that @keyframes rule accpets 'animation-timing-function'
+ property used as the animation moves to the next keyframe.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ animation-timing-function: linear;
+ }
+ 50% {
+ left: 75px;
+ animation-timing-function: linear;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left with constant speed.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-multiple-from-to-keyframes-with-only-timing-function.html b/testing/web-platform/tests/css/css-animations/animation-multiple-from-to-keyframes-with-only-timing-function.html
new file mode 100644
index 0000000000..d389b2b962
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-multiple-from-to-keyframes-with-only-timing-function.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Multiple from and to keyframes</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+
+@keyframes multiple-from-to-keyframes {
+ from, to { animation-timing-function: cubic-bezier(0, 0, 1, 1) }
+ from { left: 50px }
+ to { left: 150px }
+}
+
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+ const div = addDiv(t);
+ div.style.animation = 'multiple-from-to-keyframes 100s -50s linear';
+ assert_equals(getComputedStyle(div).left, "100px");
+}, 'Animations should not use an implicit value when the first rule within a @keyframes rule specifies "from" and "to" but only specifies a timing function.');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-name-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-name-001-manual.html
new file mode 100644
index 0000000000..f2cf502aa8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-name-001-manual.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-name - blank value</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-name is set without any value,
+ there will be no animation.">
+<style>
+ div {
+ animation-name: ;
+ animation-duration: 8s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left about 2 seconds, then moves from right to left.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ setTimeout(setAnimationName, 2000);
+
+ function setAnimationName () {
+ var div = document.getElementsByTagName("div");
+ div[0].style.animationName = "sample";
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-name-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-name-002-manual.html
new file mode 100644
index 0000000000..0315bcc136
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-name-002-manual.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-name matchs keyframes at-rule</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-name is set to refer to a keyframe at-rule
+ that provides the property values for the animation,
+ animation will execute.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left on the page load.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-name-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-name-003-manual.html
new file mode 100644
index 0000000000..df89724a93
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-name-003-manual.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-name - none</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-name is set to none, there will be no animation.">
+<style>
+ div {
+ animation-name: none;
+ animation-duration: 8s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left about 2 seconds, then moves from right to left.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ setTimeout(setAnimationName, 2000);
+
+ function setAnimationName () {
+ var div = document.getElementsByTagName("div");
+ div[0].style.animationName = "sample";
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-name-004-manual.html b/testing/web-platform/tests/css/css-animations/animation-name-004-manual.html
new file mode 100644
index 0000000000..aab4974a0b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-name-004-manual.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-name mismatches keyframe at-rule</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-name does not match any keyframe at-rule,
+ there are no properties to be animated and animation will not execute.">
+<style>
+ div {
+ animation-name: sample1;
+ animation-duration: 8s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left about 2 seconds, then moves from right to left.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ setTimeout(setAnimationName, 2000);
+
+ function setAnimationName () {
+ var div = document.getElementsByTagName("div");
+ div[0].style.animationName = "sample";
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-name-005-manual.html b/testing/web-platform/tests/css/css-animations/animation-name-005-manual.html
new file mode 100644
index 0000000000..f72cf10f4c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-name-005-manual.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-name - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-name applies to the ::after pseudo element.">
+<style>
+ div::after {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left on the page load.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-name-006-manual.html b/testing/web-platform/tests/css/css-animations/animation-name-006-manual.html
new file mode 100644
index 0000000000..b1d2258f4d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-name-006-manual.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-name - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-name applies to the ::before pseudo element.">
+<style>
+ div::before {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left on the page load.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-opacity-pause-and-set-time-ref.html b/testing/web-platform/tests/css/css-animations/animation-opacity-pause-and-set-time-ref.html
new file mode 100644
index 0000000000..ab9f614ed8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-opacity-pause-and-set-time-ref.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<style>
+#target {
+ width: 50px;
+ height: 50px;
+ background-color: green;
+ opacity: 0.4;
+}
+</style>
+<body>
+ <div id="target"></div>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-opacity-pause-and-set-time.html b/testing/web-platform/tests/css/css-animations/animation-opacity-pause-and-set-time.html
new file mode 100644
index 0000000000..cbf63ea217
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-opacity-pause-and-set-time.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-2500" />
+<title>CSS Animations Test: pauses a opacity animation and sets the current time</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<link rel="match" href="animation-opacity-pause-and-set-time-ref.html">
+<style>
+#target {
+ width: 50px;
+ height: 50px;
+ background-color: green;
+}
+</style>
+<body>
+ <div id="target"></div>
+</body>
+<script>
+window.onload = () => {
+ requestAnimationFrame(() => {
+ let animation = document.getElementById("target").animate([
+ {opacity: '0.8'},
+ {opacity: '0.0'}
+ ], 1000);
+ requestAnimationFrame(() => {
+ animation.pause();
+ animation.currentTime = 500;
+ document.documentElement.removeAttribute('class');
+ });
+ });
+};
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/animation-play-state-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-play-state-001-manual.html
new file mode 100644
index 0000000000..779f2fe310
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-play-state-001-manual.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-play-state - paused</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-play-state">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-play-state is set to paused,
+ animation is paused where the progress it had made
+ before being paused.">
+<style>
+ #test-animation-paused {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ #ref-path {
+ background-color: yellow;
+ height: 10px;
+ width: 250px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving right to left along the yellow bar,
+ and pauses in the middle after about 2 seconds' animation.
+ </p>
+ <div id="test-animation-paused">Filler Text</div>
+ <div id="ref-path"></div>
+ <script>
+ setTimeout(setAnimationRunning, 2000);
+
+ function setAnimationRunning() {
+ var div = document.getElementsByTagName("div");
+ div[0].style.animationPlayState = "paused";
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-play-state-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-play-state-002-manual.html
new file mode 100644
index 0000000000..05837727c9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-play-state-002-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-play-state - running</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-play-state">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-play-state is set to running,
+ animation will proceed as normal.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-play-state: running;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-play-state-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-play-state-003-manual.html
new file mode 100644
index 0000000000..8a1f719479
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-play-state-003-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-play-state - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-play-state">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-play-state applies to the ::after pseudo element.">
+<style>
+ div::after {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-play-state: running;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-play-state-004-manual.html b/testing/web-platform/tests/css/css-animations/animation-play-state-004-manual.html
new file mode 100644
index 0000000000..27ca389dec
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-play-state-004-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-play-state - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-play-state">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-play-state applies to the ::before pseudo element.">
+<style>
+ div::before {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-play-state: running;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-play-state-005.tentative.html b/testing/web-platform/tests/css/css-animations/animation-play-state-005.tentative.html
new file mode 100644
index 0000000000..bb65ce3a41
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-play-state-005.tentative.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS animations shouldn't restart after resetting its play state</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-play-state">
+<link rel="help" href="https://drafts.csswg.org/web-animations-1/#playing-an-animation-section">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7145">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+ @keyframes anim {
+ from { transform: translateX(100px); }
+ to { transform: translateX(200px); }
+ }
+</style>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+// Check that the auto-rewind flag is set to false when playing an animation in
+// response to a change in animation-play-state.
+promise_test(async t => {
+ const div = addDiv(t, { style: 'animation: anim 100ms' });
+
+ await new Promise(function (resolve) {
+ div.addEventListener('animationend', () => {
+ // After the animation ends, change animation-play-state to paused, flush,
+ // then change it back to running to trigger the re-start.
+ div.style.animationPlayState = 'paused';
+ getComputedStyle(div).animationPlayState;
+ div.style.animationPlayState = 'running';
+ getComputedStyle(div).animationPlayState;
+ resolve();
+ });
+ });
+
+ // Check the animation has not restarted.
+ assert_equals(div.getAnimations().length, 0);
+ assert_equals(getComputedStyle(div).transform, 'none');
+}, 'CSS animation should not restart after resetting its animation-play-state');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/css/css-animations/animation-pseudo-dynamic-001-ref.html b/testing/web-platform/tests/css/css-animations/animation-pseudo-dynamic-001-ref.html
new file mode 100644
index 0000000000..30d70aadc1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-pseudo-dynamic-001-ref.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>CSS test reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+.test {
+ display: flex;
+}
+.test::before {
+ content: "";
+ display: block;
+ width: 100px;
+ height: 100px;
+ background-color: green;
+}
+</style>
+<div class="test"></div>
diff --git a/testing/web-platform/tests/css/css-animations/animation-pseudo-dynamic-001.html b/testing/web-platform/tests/css/css-animations/animation-pseudo-dynamic-001.html
new file mode 100644
index 0000000000..e141b62149
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-pseudo-dynamic-001.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Animation of pseudo-element is stopped properly in presence of dynamic DOM change that reconstructs the layout tree</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1564366">
+<link rel="match" href="animation-pseudo-dynamic-001-ref.html">
+<style>
+@keyframes anim {
+ from { background-color: red }
+ to { background-color: red }
+}
+.test {
+ display: flex;
+}
+.test::before {
+ content: "";
+ display: block;
+ width: 100px;
+ height: 100px;
+ background-color: green;
+}
+.tweak::before {
+ animation: anim 2s linear infinite;
+}
+</style>
+<div class="test tweak">foo</div>
+<script>
+onload = function() {
+ const div = document.querySelector(".test");
+ const pseudoStyle = getComputedStyle(div, "::before");
+ div.getBoundingClientRect(); // update layout
+ div.classList.remove("tweak");
+ div.innerHTML = ""; // This is necessary to trigger the bug.
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-restarted-after-changing-iteration-count-after-completion.html b/testing/web-platform/tests/css/css-animations/animation-restarted-after-changing-iteration-count-after-completion.html
new file mode 100644
index 0000000000..961104a820
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-restarted-after-changing-iteration-count-after-completion.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Setting 'animation-iteration-count: infinite' after a CSS Animation is completed restarts the animation</title>
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#animation-iteration-count">
+<style>
+
+@keyframes anim {
+ to { margin-left: 100px }
+}
+
+</style>
+</head>
+<body>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="target"></div>
+<script>
+
+promise_test(async test => {
+ const target = document.getElementById("target");
+ target.style.animation = "anim 0.1s linear";
+
+ const initialAnimations = target.getAnimations();
+ assert_equals(initialAnimations.length, 1, "An animation runs initially.");
+
+ await initialAnimations[0].finished;
+ assert_equals(target.getAnimations().length, 0, "An animation no longer runs after its completion.");
+
+ await new Promise(setTimeout);
+ target.style.animationIterationCount = "infinite";
+ assert_equals(target.getAnimations().length, 1, "An animation runs again once animation-iteration-count is set.");
+}, "Setting 'animation-iteration-count: infinite' after a CSS Animation is completed restarts the animation.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/animation-shorthand-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-shorthand-001-manual.html
new file mode 100644
index 0000000000..0a947efe84
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-shorthand-001-manual.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation shorthand</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation shorthand property accepts time
+ values for animation-duration and animation-delay in order.">
+<style>
+ div {
+ animation: 10s 2s sample;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left about 2 seconds, and then moves from right to left
+ and lasts for a span of 10 seconds.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-shorthand-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-shorthand-002-manual.html
new file mode 100644
index 0000000000..55824f9fc1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-shorthand-002-manual.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation shorthand - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation shorthand applies to the ::after pseudo element
+ and accepts time values for animation-duration and animation-delay
+ in order.">
+<style>
+ div::after {
+ animation: 10s 2s sample;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left about 2 seconds, and then moves from right to left
+ and lasts for a span of 10 seconds.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-shorthand-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-shorthand-003-manual.html
new file mode 100644
index 0000000000..651e5325bd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-shorthand-003-manual.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation shorthand - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation shorthand applies to the ::before pseudo element
+ and accepts time values for animation-duration and animation-delay
+ in order.">
+<style>
+ div::before {
+ animation: 10s 2s sample;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts staying left about 2 seconds, and then moves from right to left
+ and lasts for a span of 10 seconds.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-style-element-replaced-with-keyframes-rule-of-same-name.html b/testing/web-platform/tests/css/css-animations/animation-style-element-replaced-with-keyframes-rule-of-same-name.html
new file mode 100644
index 0000000000..285442c95b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-style-element-replaced-with-keyframes-rule-of-same-name.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Changes to @keyframes rules</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+ const div = addDiv(t);
+
+ const originalStyleElement = document.createElement("style");
+ originalStyleElement.textContent = '@keyframes anim-custom { from, to { left: 100px } }';
+ document.head.appendChild(originalStyleElement);
+
+ t.add_cleanup(() => originalStyleElement.remove());
+
+ div.style.animation = 'anim-custom 100s';
+
+ const computedStyle = getComputedStyle(div);
+ assert_equals(computedStyle.left, "100px", "The initial @keyframes rule is applied");
+
+ // Remove the original style element and add a new one with an animation with the same name.
+ const newStyleElement = document.createElement("style");
+ newStyleElement.textContent = '@keyframes anim-custom { from, to { left: 200px } }';
+ originalStyleElement.replaceWith(newStyleElement);
+
+ t.add_cleanup(() => newStyleElement.remove());
+
+ assert_equals(computedStyle.left, "200px", "The new @keyframes rule is applied");
+}, 'Replacing a <style> element with a new <style> element while both containing the different @keyframes rule with the same name dynamically updates running animations.');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-001-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-001-manual.html
new file mode 100644
index 0000000000..c9987250dd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-001-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - cubic-bezier with parameters (0,0,1,1)</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-timing-function is set to cubic-bezier
+ with parameters (0,0,1,1), animation will progress over one cycle
+ of its duration with constant speed.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: cubic-bezier(0,0,1,1);
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left with constant speed.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-002-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-002-manual.html
new file mode 100644
index 0000000000..d62bd7186e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-002-manual.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - ease</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-timing-function is set to ease,
+ animation will starts slow, gain acceleration in the middle
+ and again slow down at the end.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: ease;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left; initially the animation starts slow,
+ gains acceleration in the middle and again slows down at the end.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-003-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-003-manual.html
new file mode 100644
index 0000000000..40eec103db
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-003-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - ease-in</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-timing-function is set to ease-in,
+ animation will start slow, gain acceleration as time progresses.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: ease-in;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left; animation starts slow
+ and gains acceleration as time progresses.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-004-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-004-manual.html
new file mode 100644
index 0000000000..833b902252
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-004-manual.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - ease-in-out</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-timing-function is set to ease-in-out,
+ animation will start slow, gain acceleration in the middel
+ and again slow down at the end.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: ease-in-out;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left; initially the animation starts slow,
+ gains acceleration in the middle and again slows down at the end.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-005-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-005-manual.html
new file mode 100644
index 0000000000..4ae04758cb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-005-manual.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - ease-out</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-timing-function is set to ease-out,
+ animation will start with higher (than the normal) speed
+ and relatively slow down as time progresses.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: ease-out;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left; animation starts and relatively
+ slows down as time progresses.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-006-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-006-manual.html
new file mode 100644
index 0000000000..f746e89bdd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-006-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - linear</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-timing-function is set to linear,
+ animation will progress over one cycle of its duration
+ with constant speed.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: linear;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which starts moving from right to left with constant speed.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-007-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-007-manual.html
new file mode 100644
index 0000000000..dee9328741
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-007-manual.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - step-start</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-timing-function is set to step-start,
+ animation will play the end effect at the start of the interval.">
+<style>
+ div {
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ }
+
+ #test-step-start {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-fill-mode: forwards;
+ animation-timing-function: step-start;
+
+ background-color: blue;
+ }
+
+ #ref-no-animation {
+ background-color: red;
+ left: 150px;
+ }
+
+ @keyframes sample {
+ from {
+ left: 0px;
+ }
+ to {
+ left: 150px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which stays right, and if there is NO red.
+ </p>
+ <div id="ref-no-animation"></div>
+ <div id="test-step-start">Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-008-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-008-manual.html
new file mode 100644
index 0000000000..77a4e11be8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-008-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - step-end</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-timing-function is set to step-end,
+ animation will play the end effect at the end time point,
+ and keep the start effect at the start of the interval.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: step-end;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled square with 'Filler Text',
+ which stays right for about 10 seconds, and then jumps to left.
+ </p>
+ <div>Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-009-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-009-manual.html
new file mode 100644
index 0000000000..138e6f61aa
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-009-manual.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - steps with parameters (<number>, start)</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-timing-function is set to steps with paramenters
+ (<number>, start), animation will be defined by the number that divides
+ the domain of operation into equally size intervals, evey time the changes
+ happens at the start of the interval.">
+<style>
+ div {
+ float: left;
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ }
+
+ #test-step-start {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-fill-mode: forwards;
+ animation-timing-function: steps(4, start);
+
+ background-color: blue;
+ }
+
+ #ref-1 {
+ background-color: yellow;
+ left: 200px;
+ }
+
+ #ref-2 {
+ background-color: green;
+ left: 100px;
+ }
+
+ #ref-3 {
+ background-color: red;
+ left: 0px;
+ }
+
+ @keyframes sample {
+ from {
+ left: 400px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there are a filled square with 'Filler Text',
+ which jumps three times from right to left and orderly covers
+ the filled YELLOW, GREEN and RED squares.
+ </p>
+ <div id="ref-1"></div>
+ <div id="ref-2"></div>
+ <div id="ref-3"></div>
+ <div id="test-step-start">Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-010-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-010-manual.html
new file mode 100644
index 0000000000..eed5b76adb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-010-manual.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - steps with parameters (<number>, end)</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="When animation-timing-function is set to steps with paramenters
+ (<number>, end), animation will be defined by the number that divides
+ the domain of operation into equally size intervals, evey time the changes
+ happens at the end of the interval.">
+<style>
+ div {
+ float: left;
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ }
+
+ #test-step-end {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-fill-mode: forwards;
+ animation-timing-function: steps(3, end);
+
+ background-color: blue;
+ }
+
+ #ref-1 {
+ background-color: yellow;
+ left: 200px;
+ }
+
+ #ref-2 {
+ background-color: green;
+ left: 100px;
+ }
+
+ #ref-3 {
+ background-color: red;
+ left: 0px;
+ }
+
+ @keyframes sample {
+ from {
+ left: 300px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there are a filled square with 'Filler Text',
+ which jumps three times from right to left and orderly covers
+ the filled YELLOW, GREEN and RED squares.
+ </p>
+ <div id="ref-1"></div>
+ <div id="ref-2"></div>
+ <div id="ref-3"></div>
+ <div id="test-step-end">Filler Text</div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-011-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-011-manual.html
new file mode 100644
index 0000000000..dfca3921a2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-011-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - ::after</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-timing-function applies to the ::after pseudo element.">
+<style>
+ div::after {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: linear;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which moves from right to left with constant speed.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-timing-function-012-manual.html b/testing/web-platform/tests/css/css-animations/animation-timing-function-012-manual.html
new file mode 100644
index 0000000000..24ffa39e93
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-timing-function-012-manual.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation-timing-function - ::before</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-07 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animation-timing-function applies to the ::before pseudo element.">
+<style>
+ div::before {
+ animation-name: sample;
+ animation-duration: 10s;
+ animation-timing-function: linear;
+
+ background-color: blue;
+ content: "Filler Text";
+ display: block;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square with 'Filler Text',
+ which moves from right to left with constant speed.
+ </p>
+ <div></div>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-transform-pause-and-set-time-ref.html b/testing/web-platform/tests/css/css-animations/animation-transform-pause-and-set-time-ref.html
new file mode 100644
index 0000000000..3cadfd8518
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-transform-pause-and-set-time-ref.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<style>
+#target {
+ width: 50px;
+ height: 50px;
+ background-color: green;
+ transform: translate(500px);
+}
+</style>
+<body>
+ <div id="target"></div>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/css/css-animations/animation-transform-pause-and-set-time.html b/testing/web-platform/tests/css/css-animations/animation-transform-pause-and-set-time.html
new file mode 100644
index 0000000000..e8a9bd9706
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animation-transform-pause-and-set-time.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Animations Test: pauses a transform animation and sets the current time</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-timing-function">
+<link rel="match" href="animation-transform-pause-and-set-time-ref.html">
+<style>
+#target {
+ width: 50px;
+ height: 50px;
+ background-color: green;
+}
+</style>
+<body>
+ <div id="target"></div>
+</body>
+<script>
+window.onload = () => {
+ requestAnimationFrame(() => {
+ let animation = document.getElementById("target").animate([
+ {transform: 'translateX(0px)'},
+ {transform: 'translateX(1000px)'}
+ ], 1000);
+ requestAnimationFrame(() => {
+ animation.pause();
+ animation.currentTime = 500;
+ document.documentElement.removeAttribute('class');
+ });
+ });
+};
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/animationevent-interface.html b/testing/web-platform/tests/css/css-animations/animationevent-interface.html
new file mode 100644
index 0000000000..5b1bc4265f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animationevent-interface.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: AnimationEvent interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#interface-dom">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="animationevent-interface.js"></script>
+<div id="log"></div>
+
diff --git a/testing/web-platform/tests/css/css-animations/animationevent-interface.js b/testing/web-platform/tests/css/css-animations/animationevent-interface.js
new file mode 100644
index 0000000000..fcb7ceba20
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animationevent-interface.js
@@ -0,0 +1,220 @@
+(function() {
+ test(function() {
+ var event = new AnimationEvent("");
+ assert_true(event instanceof window.AnimationEvent);
+ }, "the event is an instance of AnimationEvent");
+
+ test(function() {
+ var event = new AnimationEvent("");
+ assert_true(event instanceof window.Event);
+ }, "the event inherts from Event");
+
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ new AnimationEvent();
+ }, 'First argument is required, so was expecting a TypeError.');
+ }, 'Missing type argument');
+
+ test(function() {
+ var event = new AnimationEvent("test");
+ assert_equals(event.type, "test");
+ }, "type argument is string");
+
+ test(function() {
+ var event = new AnimationEvent(null);
+ assert_equals(event.type, "null");
+ }, "type argument is null");
+
+ test(function() {
+ var event = new AnimationEvent(undefined);
+ assert_equals(event.type, "undefined");
+ }, "event type set to undefined");
+
+ test(function() {
+ var event = new AnimationEvent("test");
+ assert_equals(event.animationName, "");
+ }, "animationName has default value of empty string");
+
+ test(function() {
+ var event = new AnimationEvent("test");
+ assert_equals(event.elapsedTime, 0.0);
+ }, "elapsedTime has default value of 0.0");
+
+ test(function() {
+ var event = new AnimationEvent("test");
+ assert_readonly(event, "animationName", "readonly attribute value");
+ }, "animationName is readonly");
+
+ test(function() {
+ var event = new AnimationEvent("test");
+ assert_readonly(event, "elapsedTime", "readonly attribute value");
+ }, "elapsedTime is readonly");
+
+ test(function() {
+ var event = new AnimationEvent("test", null);
+ assert_equals(event.animationName, "");
+ assert_equals(event.elapsedTime, 0.0);
+ }, "animationEventInit argument is null");
+
+ test(function() {
+ var event = new AnimationEvent("test", undefined);
+ assert_equals(event.animationName, "");
+ assert_equals(event.elapsedTime, 0.0);
+ }, "animationEventInit argument is undefined");
+
+ test(function() {
+ var event = new AnimationEvent("test", {});
+ assert_equals(event.animationName, "");
+ assert_equals(event.elapsedTime, 0.0);
+ }, "animationEventInit argument is empty dictionary");
+
+ test(function() {
+ var event = new AnimationEvent("test", {pseudoElement: "::testPseudo"});
+ assert_equals(event.pseudoElement, "::testPseudo");
+ }, "AnimationEvent.pseudoElement initialized from the dictionary");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: "sample"});
+ assert_equals(event.animationName, "sample");
+ }, "animationName set to 'sample'");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: undefined});
+ assert_equals(event.animationName, "");
+ }, "animationName set to undefined");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: null});
+ assert_equals(event.animationName, "null");
+ }, "animationName set to null");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: false});
+ assert_equals(event.animationName, "false");
+ }, "animationName set to false");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: true});
+ assert_equals(event.animationName, "true");
+ }, "animationName set to true");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: 0.5});
+ assert_equals(event.animationName, "0.5");
+ }, "animationName set to a number");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: []});
+ assert_equals(event.animationName, "");
+ }, "animationName set to []");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: [1, 2, 3]});
+ assert_equals(event.animationName, "1,2,3");
+ }, "animationName set to [1, 2, 3]");
+
+ test(function() {
+ var event = new AnimationEvent("test", {animationName: {sample: 0.5}});
+ assert_equals(event.animationName, "[object Object]");
+ }, "animationName set to an object");
+
+ test(function() {
+ var event = new AnimationEvent("test",
+ {animationName: {valueOf: function () { return 'sample'; }}});
+ assert_equals(event.animationName, "[object Object]");
+ }, "animationName set to an object with a valueOf function");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: 0.5});
+ assert_equals(event.elapsedTime, 0.5);
+ }, "elapsedTime set to 0.5");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: -0.5});
+ assert_equals(event.elapsedTime, -0.5);
+ }, "elapsedTime set to -0.5");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: undefined});
+ assert_equals(event.elapsedTime, 0);
+ }, "elapsedTime set to undefined");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: null});
+ assert_equals(event.elapsedTime, 0);
+ }, "elapsedTime set to null");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: false});
+ assert_equals(event.elapsedTime, 0);
+ }, "elapsedTime set to false");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: true});
+ assert_equals(event.elapsedTime, 1);
+ }, "elapsedTime set to true");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: ""});
+ assert_equals(event.elapsedTime, 0);
+ }, "elapsedTime set to ''");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: []});
+ assert_equals(event.elapsedTime, 0);
+ }, "elapsedTime set to []");
+
+ test(function() {
+ var event = new AnimationEvent("test", {elapsedTime: [0.5]});
+ assert_equals(event.elapsedTime, 0.5);
+ }, "elapsedTime set to [0.5]");
+
+ test(function() {
+ var event = new AnimationEvent(
+ "test", {elapsedTime: { valueOf: function() { return 0.5; }}});
+ assert_equals(event.elapsedTime, 0.5);
+ }, "elapsedTime set to an object with a valueOf function");
+
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ new AnimationEvent("test", {elapsedTime: NaN});
+ }, 'elapsedTime cannot be NaN so was expecting a TypeError');
+ }, "elapsedTime cannot be set to NaN");
+
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ new AnimationEvent("test", {elapsedTime: Infinity});
+ }, 'elapsedTime cannot be Infinity so was expecting a TypeError');
+ }, "elapsedTime cannot be set to Infinity");
+
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ new AnimationEvent("test", {elapsedTime: -Infinity});
+ }, 'elapsedTime cannot be -Infinity so was expecting a TypeError');
+ }, "elapsedTime cannot be set to -Infinity");
+
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ new AnimationEvent("test", {elapsedTime: "sample"});
+ }, 'elapsedTime cannot be a string so was expecting a TypeError');
+ }, "elapsedTime cannot be set to 'sample'");
+
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ new AnimationEvent("test", {elapsedTime: [0.5, 1.0]});
+ }, 'elapsedTime cannot be a multi-element array so was expecting a TypeError');
+ }, "elapsedTime cannot be set to [0.5, 1.0]");
+
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ new AnimationEvent("test", {elapsedTime: { sample: 0.5}});
+ }, 'elapsedTime cannot be an object so was expecting a TypeError');
+ }, "elapsedTime cannot be set to an object");
+
+ test(function() {
+ var eventInit = {animationName: "sample", elapsedTime: 0.5};
+ var event = new AnimationEvent("test", eventInit);
+ assert_equals(event.animationName, "sample");
+ assert_equals(event.elapsedTime, 0.5);
+ }, "AnimationEventInit properties set value");
+})();
diff --git a/testing/web-platform/tests/css/css-animations/animationevent-marker-pseudoelement.html b/testing/web-platform/tests/css/css-animations/animationevent-marker-pseudoelement.html
new file mode 100644
index 0000000000..90c7b86ab9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animationevent-marker-pseudoelement.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: AnimationEvent pseudoElement</title>
+<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-animations/#interface-animationevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #target::marker {
+ content: "";
+ animation: move 1s;
+ }
+
+ @keyframes move {
+ to { transform: translate(100px); }
+ }
+
+ #target {
+ display: list-item;
+ list-style-position: inside;
+ }
+</style>
+<div id='target'></div>
+<script>
+ async_test(function(t) {
+ var target = document.getElementById('target');
+ target.addEventListener("animationstart", t.step_func(function(evt) {
+ assert_true(evt instanceof window.AnimationEvent);
+ assert_equals(evt.pseudoElement, "::marker");
+
+ t.done();
+ }), true);
+ }, "AnimationEvent should have the correct pseudoElement memeber");
+</script>
+
diff --git a/testing/web-platform/tests/css/css-animations/animationevent-pseudoelement.html b/testing/web-platform/tests/css/css-animations/animationevent-pseudoelement.html
new file mode 100644
index 0000000000..8de41cc613
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animationevent-pseudoelement.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: AnimationEvent pseudoElement</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#interface-animationevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #target::before {
+ content: "";
+ animation: move 1s;
+ }
+
+ @keyframes move {
+ to { transform: translate(100px); }
+ }
+</style>
+<div id='target'></div>
+<script>
+ async_test(function(t) {
+ var target = document.getElementById('target');
+ target.addEventListener("animationstart", t.step_func(function(evt) {
+ assert_true(evt instanceof window.AnimationEvent);
+ assert_equals(evt.pseudoElement, "::before");
+
+ t.done();
+ }), true);
+ }, "AnimationEvent should have the correct pseudoElement memeber");
+</script>
+
diff --git a/testing/web-platform/tests/css/css-animations/animationevent-types.html b/testing/web-platform/tests/css/css-animations/animationevent-types.html
new file mode 100644
index 0000000000..77f514a297
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animationevent-types.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>CSS Animations Test: AnimationEvnt types - animationstart, animationend,animationiteration</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#event-animationevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #test {
+ animation-name: sample;
+ animation-duration: 2s;
+ animation-delay: -1s;
+ animation-iteration-count: 2;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<div id="test">Filler Text</div>
+<div id="log"></div>
+<script>
+ var testDiv = document.getElementById("test");
+
+ async_test(function(t) {
+ testDiv.addEventListener("animationstart", t.step_func(function(evt) {
+ assert_true(evt instanceof window.AnimationEvent);
+
+ assert_idl_attribute(evt, "animationName", "animationstart has animationName property");
+ assert_idl_attribute(evt, "elapsedTime", "animationstart has elapsedTime property");
+ assert_idl_attribute(evt, "pseudoElement", "animationstart has pseudoElement property");
+
+ assert_equals(evt.animationName, "sample", "animationstart has animationName value");
+ assert_equals(evt.elapsedTime, 1, "animationstart has elapsedTime value");
+ assert_equals(evt.pseudoElement, "", "animaitonstart has correct pseudoElement value");
+
+ t.done();
+ }), true);
+ }, "animationstart event is instanceof AnimationEvent");
+
+ async_test(function(t) {
+ testDiv.addEventListener("animationend", t.step_func(function(evt) {
+ assert_true(evt instanceof window.AnimationEvent);
+
+ assert_idl_attribute(evt, "animationName", "animationend has animationName property");
+ assert_idl_attribute(evt, "elapsedTime", "animationend has elapsedTime property");
+ assert_idl_attribute(evt, "pseudoElement", "animationstart has pseudoElement property");
+
+ assert_equals(evt.animationName, "sample", "animationend has animationName value");
+ assert_equals(evt.elapsedTime, 4, "animationend has elapsedTime value");
+ assert_equals(evt.pseudoElement, "", "animaitonstart has correct pseudoElement value");
+
+ t.done();
+ }), true);
+ }, "animationend event is instanceof AnimationEvent");
+
+ async_test(function(t) {
+ testDiv.addEventListener("animationiteration", t.step_func(function(evt) {
+ assert_true(evt instanceof window.AnimationEvent);
+
+ assert_idl_attribute(evt, "animationName", "animationiteration has animationName property");
+ assert_idl_attribute(evt, "elapsedTime", "animationiteration has elapsedTime property");
+ assert_idl_attribute(evt, "pseudoElement", "animationstart has pseudoElement property");
+
+ assert_equals(evt.animationName, "sample", "animationiteration has animationName value");
+ assert_equals(evt.elapsedTime, 2, "animationiteration has elapsedTime value");
+ assert_equals(evt.pseudoElement, "", "animaitonstart has correct pseudoElement value");
+
+ t.done();
+ }), true);
+ }, "animationiteration event is instanceof AnimationEvent");
+</script>
+
diff --git a/testing/web-platform/tests/css/css-animations/animationstart-and-animationend-events-manual.html b/testing/web-platform/tests/css/css-animations/animationstart-and-animationend-events-manual.html
new file mode 100644
index 0000000000..4ca5eb736e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/animationstart-and-animationend-events-manual.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: animation events - animationstart and animationend</title>
+<link rel="author" title="Nokia Inc." href="http://www.nokia.com">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="reviewer" title="Zhiqiang Zhang" href="mailto:zhiqiang.zhang@intel.com"> <!-- 2015-05-06 -->
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-name">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-duration">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#events">
+<meta name="flags" content="animated">
+<meta name="assert" content="Check that animationstart event occurs at the start of an animation,
+ animationend event occurs when animation finishes.">
+<style>
+ div {
+ animation-name: sample;
+ animation-duration: 10s;
+
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ position: relative;
+ }
+
+ @keyframes sample {
+ from {
+ left: 150px;
+ }
+ to {
+ left: 0px;
+ }
+ }
+</style>
+<body>
+ <p>
+ Test passes if there is a filled blue square,
+ which moves from right to left with text 'START'
+ and changes to 'END' when the animation is finished.
+ </p>
+ <div>Filler Text</div>
+ <script>
+ var div = document.getElementsByTagName("div");
+ div[0].addEventListener("animationstart", animationStart, true);
+ div[0].addEventListener("animationend", animationEnd, true);
+
+ function animationStart() {
+ div[0].innerHTML = "START";
+ }
+
+ function animationEnd() {
+ div[0].innerHTML = "END";
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-animations/cancel-animation-shadow-slot-invalidation.html b/testing/web-platform/tests/css/css-animations/cancel-animation-shadow-slot-invalidation.html
new file mode 100644
index 0000000000..a5c9c6763f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/cancel-animation-shadow-slot-invalidation.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>CSS Animations Test: Cancel animation for target moving out of flat tree and style invalidation</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#eventdef-animationevent-animationcancel">
+<link rel="help" href="https://crbug.com/1093747">
+<link rel="match" href="../reference/pass_if_pass_below.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+ @keyframes anim {
+ from { color: red; }
+ to { color: green; }
+ }
+ #anim { animation: anim 100s; }
+ .none { display: none; }
+</style>
+<p>Test passes if there is the word "PASS" below.</p>
+<div id="host"><span id="anim">FAIL</span></div>
+<div id="dirty" class="none">PASS</div>
+<script>
+ const root = host.attachShadow({mode:"open"});
+ root.innerHTML = "<slot></slot>";
+ requestAnimationFrame(() => {
+ root.firstChild.name = "skip-slot";
+ dirty.className = "";
+ takeScreenshot();
+ });
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/computed-style-animation-parsing.html b/testing/web-platform/tests/css/css-animations/computed-style-animation-parsing.html
new file mode 100644
index 0000000000..db51106fa8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/computed-style-animation-parsing.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations: parsing computedStyle.animation</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#animation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="test1"></div>
+<div id="test2"></div>
+<script>
+function testComputedStyle(computedStyle1, computedStyle2, name) {
+ assert_equals(computedStyle1.animationName, name, "computedStyle1.animationName");
+ assert_equals(computedStyle2.animationName, name, "computedStyle2.animationName");
+ assert_equals(computedStyle1.animation, computedStyle2.animation,
+ "computedStyle1 and computedStyle2 should have the same animation");
+}
+
+function testAnimation(input, name) {
+ var style1 = test1.style;
+ var style2 = test2.style;
+ var computedStyle1 = getComputedStyle(test1);
+ var computedStyle2 = getComputedStyle(test2);
+
+ style1.animation = input;
+ style2.animation = style1.animation;
+ testComputedStyle(computedStyle1, computedStyle2, name);
+ style2.animation = computedStyle1.animation;
+ testComputedStyle(computedStyle1, computedStyle2, name);
+}
+
+test(() => {
+ // We are duplicating the logic of testAnimation because the animationName of
+ // the getComputedStyle is "none" when there is no animation.
+ var style1 = test1.style;
+ var style2 = test2.style;
+ var computedStyle1 = getComputedStyle(test1);
+ var computedStyle2 = getComputedStyle(test2);
+
+ style1.animation = "";
+ style2.animation = style1.animation;
+ testComputedStyle(computedStyle1, computedStyle2, "none");
+ style2.animation = computedStyle1.animation;
+ assert_equals(computedStyle2.animationName, "none");
+}, "Test animation name being empty.");
+
+test(() => {
+ testAnimation("myShorthandAnim", "myShorthandAnim");
+}, "Test a non-conflicting animation name.");
+
+test(() => {
+ testAnimation("ease", "none");
+ testAnimation("ease-in", "none");
+ testAnimation("ease-in-out", "none");
+ testAnimation("ease-out", "none");
+ testAnimation("linear", "none");
+ testAnimation("step-end", "none");
+ testAnimation("step-start", "none");
+ testAnimation("ease ease", "ease");
+ testAnimation("ease linear", "linear");
+}, "Test an animation name that is the same as a possible animation timing-function.");
+
+test(() => {
+ testAnimation("infinite", "none");
+ testAnimation("infinite infinite", "infinite");
+}, "Test an animation name that is the same as a possible animation iteration-count.");
+
+test(() => {
+ testAnimation("none", "none");
+ testAnimation("forwards", "none");
+ testAnimation("none forwards", "forwards");
+}, "Test an animation name that is the same as a possible animation fill-mode.");
+
+test(() => {
+ testAnimation("normal", "none");
+ testAnimation("reverse", "none");
+ testAnimation("normal normal", "normal");
+ testAnimation("normal reverse", "reverse");
+}, "Test an animation name that is the same as a possible animation direction.");
+
+test(() => {
+ testAnimation("running", "none");
+ testAnimation("paused", "none");
+ testAnimation("running running", "running");
+ testAnimation("running paused", "paused");
+}, "Test an animation name that is the same as a possible running state.");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/crashtests/add-pseudo-while-animating-001.html b/testing/web-platform/tests/css/css-animations/crashtests/add-pseudo-while-animating-001.html
new file mode 100644
index 0000000000..dc815658c5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/crashtests/add-pseudo-while-animating-001.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="test-wait">
+<head>
+<link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1267079">
+<style>
+
+@keyframes a {
+ 0% {
+ border-radius: 1px;
+ }
+}
+
+div {
+ animation-name: a;
+ animation-duration: 3s;
+}
+
+</style>
+<script>
+
+function addSheet(text)
+{
+ var sheet = document.createElement("style");
+ sheet.appendChild(document.createTextNode(text));
+ document.head.appendChild(sheet);
+}
+
+function boom()
+{
+ requestAnimationFrame(() => { requestAnimationFrame(() => {
+ addSheet("div:after { content: 'Z'; }");
+ document.documentElement.classList.remove("test-wait");
+ }); });
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+</head>
+
+<body><div></div></body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/crashtests/replace-keyframes-animating-filter-001.html b/testing/web-platform/tests/css/css-animations/crashtests/replace-keyframes-animating-filter-001.html
new file mode 100644
index 0000000000..e6355376f7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/crashtests/replace-keyframes-animating-filter-001.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="test-wait">
+<title>CSS Test (Animations): Changing an @keyframes that animates filter</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Google" href="http://www.google.com/">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1301937">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#keyframes">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#supported-filter-functions">
+<link rel="help" href="https://drafts.csswg.org/cssom-1/#the-cssstylesheet-interface">
+<meta name="assert" content="This should not crash.">
+
+<style>
+#el1 {
+ height: 100px;
+ width: 100px;
+ animation: kf1 2s linear;
+}
+@keyframes kf1 {
+ from { filter: grayscale(0.9) }
+ to { filter: grayscale(0.8) }
+}
+</style>
+<div id="el1"></div>
+<script>
+
+document.documentElement.addEventListener("TestRendered", step1);
+
+function step1() {
+ requestAnimationFrame(function() { requestAnimationFrame(step2); });
+}
+
+function step2() {
+ let css = document.styleSheets[0];
+ css.insertRule("@keyframes kf1 { from { color: blue } to { color: blue } } ", css.rules.length);
+ requestAnimationFrame(step3);
+}
+
+function step3() {
+ document.documentElement.classList.remove("test-wait");
+}
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/dialog-animation.html b/testing/web-platform/tests/css/css-animations/dialog-animation.html
new file mode 100644
index 0000000000..f6c8f6f0fa
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/dialog-animation.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Animations on a &lt;dialog></title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+
+dialog[open] {
+ animation: dialog-open-animation 1ms;
+}
+
+@keyframes dialog-open-animation {
+ from { opacity: 0 }
+}
+
+</style>
+<div id="log"></div>
+<script>
+
+"use strict";
+
+promise_test(async t => {
+ const dialog = addElement(t, "dialog");
+
+ // Open the dialog a first time, this should trigger a CSS Animation.
+ dialog.open = true;
+ const animations = dialog.getAnimations();
+ assert_equals(animations.length, 1, "As the <dialog> is shown intially an animation is started");
+
+ await animations[0].finished;
+
+ await waitForNextFrame();
+
+ dialog.open = false;
+ assert_equals(dialog.getAnimations().length, 0, "As the <dialog> is closed the animation is removed");
+
+ await waitForNextFrame();
+
+ dialog.open = true;
+ assert_equals(dialog.getAnimations().length, 1, "As the <dialog> is shown again an animation is started again");
+}, "CSS Animations tied to <dialog open> are canceled and restarted as the dialog is hidden and shown");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/dialog-backdrop-animation.html b/testing/web-platform/tests/css/css-animations/dialog-backdrop-animation.html
new file mode 100644
index 0000000000..cc61c2e380
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/dialog-backdrop-animation.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Animations on a &lt;dialog> ::backdrop</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+
+dialog[open]::backdrop {
+ animation: dialog-backdrop-animation 1ms;
+}
+
+@keyframes dialog-backdrop-animation {
+ from { opacity: 0 }
+}
+
+</style>
+<div id="log"></div>
+<script>
+
+"use strict";
+
+promise_test(async t => {
+ const dialog = addElement(t, "dialog");
+
+ // Open the dialog a first time, this should trigger a CSS Animation.
+ dialog.showModal();
+ const animations = dialog.getAnimations({ subtree: true });
+ assert_equals(animations.length, 1, "As the <dialog> is shown intially an animation is started on its ::backdrop");
+
+ await animations[0].finished;
+
+ await waitForNextFrame();
+
+ dialog.close();
+ assert_equals(dialog.getAnimations({ subtree: true }).length, 0, "As the <dialog> is closed the animation is removed from its ::backdrop");
+
+ await waitForNextFrame();
+
+ dialog.showModal();
+ assert_equals(dialog.getAnimations({ subtree: true }).length, 1, "As the <dialog> is shown again an animation is started again on its ::backdrop");
+}, "CSS Animations on a <dialog> ::backdrop are canceled and restarted as the dialog is hidden and shown");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/display-interpolation.html b/testing/web-platform/tests/css/css-animations/display-interpolation.html
new file mode 100644
index 0000000000..fb2503ac8a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/display-interpolation.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/w3c/csswg-drafts/issues/6429">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+const alwaysBlock = [
+ {at: -1, expect: 'block'},
+ {at: 0, expect: 'block'},
+ {at: 0.1, expect: 'block'},
+ {at: 0.9, expect: 'block'},
+ {at: 1, expect: 'block'},
+ {at: 1.5, expect: 'block'},
+];
+const alwaysNone = [
+ {at: -1, expect: 'none'},
+ {at: 0, expect: 'none'},
+ {at: 0.1, expect: 'none'},
+ {at: 0.9, expect: 'none'},
+ {at: 1, expect: 'none'},
+ {at: 1.5, expect: 'none'},
+];
+
+test_interpolation({
+ property: 'display',
+ behavior: 'allow-discrete',
+ from: 'block',
+ to: 'none',
+}, [
+ {at: -1, expect: 'block'},
+ {at: 0, expect: 'block'},
+ {at: 0.1, expect: 'block'},
+ {at: 0.9, expect: 'block'},
+ {at: 1, expect: 'none'},
+ {at: 1.5, expect: 'none'},
+]);
+
+// This transitions test expect 'block' at every point because transitioning
+// from display:none does not provide an initial style. This is expected and
+// can be worked around by using @initial.
+test_interpolation({
+ property: 'display',
+ behavior: 'allow-discrete',
+ from: 'none',
+ to: 'block',
+ 'CSS Transitions with transition: all': alwaysBlock,
+ 'CSS Transitions': alwaysBlock
+}, [
+ {at: -1, expect: 'none'},
+ {at: 0, expect: 'none'},
+ {at: 0.1, expect: 'block'},
+ {at: 0.9, expect: 'block'},
+ {at: 1, expect: 'block'},
+ {at: 1.5, expect: 'block'},
+]);
+
+test_no_interpolation({
+ property: 'display',
+ from: 'inline',
+ to: 'block'
+});
+
+test_interpolation({
+ property: 'display',
+ behavior: 'allow-discrete',
+ from: 'block',
+ to: 'block'
+}, alwaysBlock);
+
+test_interpolation({
+ property: 'display',
+ behavior: 'allow-discrete',
+ from: 'none',
+ to: 'none'
+}, alwaysNone);
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/display-none-dont-cancel.tentative.html b/testing/web-platform/tests/css/css-animations/display-none-dont-cancel.tentative.html
new file mode 100644
index 0000000000..6ae115803b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/display-none-dont-cancel.tentative.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://drafts.csswg.org/css-display-4/#display-animation">
+<link rel=help href="https://github.com/w3c/csswg-drafts/issues/6429">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-animations/support/testcommon.js"></script>
+
+<div id=target1>hello</div>
+<style>
+@keyframes display1 {
+ 0% { display: none; }
+ 100% { display: inline; }
+}
+.animate1 {
+ animation: display1 1s infinite;
+}
+</style>
+<script>
+promise_test(async () => {
+ let numAnimationstartFired = 0;
+ target1.addEventListener('animationstart', () => numAnimationstartFired++);
+
+ await waitForAnimationFrames(1);
+ target1.classList.add('animate1');
+ await waitForAnimationFrames(2);
+
+ assert_equals(getComputedStyle(target1).display, 'inline',
+ 'The display should be inline during the animation.');
+ assert_equals(numAnimationstartFired, 1,
+ 'Only one animation should start.');
+}, 'display:none animating to display:inline should be inline for the whole animation.');
+</script>
+
+<div id=target2>hello</div>
+<style>
+@keyframes display2 {
+ 0% { display: var(--none-value); }
+ 100% { display: inline; }
+}
+.animate2 {
+ animation: display2 1s infinite;
+}
+#target2 {
+ --none-value: none;
+}
+</style>
+<script>
+promise_test(async () => {
+ let numAnimationstartFired = 0;
+ target2.addEventListener('animationstart', () => numAnimationstartFired++);
+
+ await waitForAnimationFrames(1);
+ target2.classList.add('animate2');
+ await waitForAnimationFrames(2);
+
+ assert_equals(getComputedStyle(target2).display, 'inline',
+ 'The display should be inline during the animation.');
+ assert_equals(numAnimationstartFired, 1,
+ 'Only one animation should start.');
+}, 'A CSS variable of display:none animating to display:inline should be inline for the whole animation.');
+</script>
+
+<div id=target3>hello</div>
+<style>
+@keyframes display3 {
+ 0% { display: none; }
+ 100% { display: none; }
+}
+.animate3 {
+ animation: display3 1s infinite;
+}
+</style>
+<script>
+promise_test(async () => {
+ let numAnimationstartFired = 0;
+ target3.addEventListener('animationstart', () => numAnimationstartFired++);
+
+ await waitForAnimationFrames(1);
+ target3.classList.add('animate3');
+ await waitForAnimationFrames(2);
+
+ assert_equals(getComputedStyle(target3).display, 'none',
+ 'The display should be none and the animation should keep running.');
+ assert_equals(numAnimationstartFired, 1,
+ 'Only one animation should start.');
+}, 'Animating from display:none to display:none should not cancel the animation.');
+</script>
+
+<div id=target4>hello</div>
+<style>
+@keyframes display4 {
+ 0% { display: var(--none-value); }
+ 100% { display: var(--none-value); }
+}
+.animate4 {
+ animation: display4 1s infinite;
+}
+#target4 {
+ --none-value: none;
+}
+</style>
+<script>
+promise_test(async () => {
+ let numAnimationstartFired = 0;
+ target4.addEventListener('animationstart', () => numAnimationstartFired++);
+
+ await waitForAnimationFrames(1);
+ target4.classList.add('animate4');
+ await waitForAnimationFrames(2);
+
+ assert_equals(getComputedStyle(target4).display, 'none',
+ 'The display should be none and the animation should keep running.');
+ assert_equals(numAnimationstartFired, 1,
+ 'Only one animation should start.');
+}, 'Animating from display:none to display:none with an intermediate variable should not cancel the animation.');
+</script>
+
+<div id=target5>hello</div>
+<style>
+@keyframes display5 {
+ 0% { --display: none; }
+ 100% { --display: none; }
+}
+.animate5 {
+ animation: display5 1s infinite;
+}
+#target5 {
+ display: var(--display, block);
+}
+</style>
+<script>
+promise_test(async () => {
+ let numAnimationstartFired = 0;
+ target5.addEventListener('animationstart', () => numAnimationstartFired++);
+
+ await waitForAnimationFrames(1);
+ target5.classList.add('animate5');
+ await waitForAnimationFrames(2);
+
+ assert_equals(getComputedStyle(target5).display, 'none',
+ 'The display should be none and the animation should keep running.');
+ assert_equals(numAnimationstartFired, 1,
+ 'Only one animation should start.');
+}, 'Animating a variable of "none" which gets set to display elsewhere should not cancel the animation.');
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/event-dispatch.tentative.html b/testing/web-platform/tests/css/css-animations/event-dispatch.tentative.html
new file mode 100644
index 0000000000..3e577d6ea6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/event-dispatch.tentative.html
@@ -0,0 +1,437 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Tests for CSS animation event dispatch</title>
+<meta name="timeout" content="long">
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim {
+ from { margin-left: 0px; }
+ to { margin-left: 100px; }
+}
+</style>
+<div id="log"></div>
+<script>
+'use strict';
+
+const setupAnimation = (t, animationStyle) => {
+ const div = addDiv(t, { style: 'animation: ' + animationStyle });
+ const animation = div.getAnimations()[0];
+ const timeoutPromise = armTimeoutWhenReady(animation, fastEventsTimeout);
+
+ const watcher = new EventWatcher(t, div, [ 'animationstart',
+ 'animationiteration',
+ 'animationend',
+ 'animationcancel' ],
+ timeoutPromise);
+
+ return { animation, watcher, div };
+};
+
+promise_test(async t => {
+ // Add 1ms delay to ensure that the delay is not included in the elapsedTime.
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 1ms');
+
+ const evt = await watcher.wait_for('animationstart');
+ assert_equals(evt.elapsedTime, 0.0);
+}, 'Idle -> Active');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ // Seek to After phase.
+ animation.finish();
+
+ const events = await watcher.wait_for(['animationstart', 'animationend'], {
+ record: 'all',
+ });
+ assert_equals(events[0].elapsedTime, 0.0);
+ assert_equals(events[1].elapsedTime, 100);
+}, 'Idle -> After');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
+
+ await animation.ready;
+
+ // Seek to Active phase.
+ animation.currentTime = 100 * MS_PER_SEC;
+
+ const evt = await watcher.wait_for('animationstart');
+ assert_equals(evt.elapsedTime, 0.0);
+}, 'Before -> Active');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
+ const allEvents = watcher.wait_for(['animationstart', 'animationend'], {
+ record: 'all',
+ });
+
+ await animation.ready;
+
+ // Seek to After phase.
+ animation.finish();
+
+ const events = await allEvents;
+ assert_equals(events[0].elapsedTime, 0.0);
+ assert_equals(events[1].elapsedTime, 100.0);
+}, 'Before -> After');
+
+promise_test(async t => {
+ const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused');
+
+ await watcher.wait_for('animationstart');
+
+ // Make idle
+ div.style.display = 'none';
+
+ const evt = await watcher.wait_for('animationcancel');
+ assert_equals(evt.elapsedTime, 0.0);
+}, 'Active -> Idle, display: none');
+
+promise_test(async t => {
+ const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused');
+
+ // Seek to After phase.
+ animation.finish();
+ await watcher.wait_for(['animationstart', 'animationend']);
+
+ div.style.display = 'none';
+
+ // Wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'After -> Idle, display: none');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ await watcher.wait_for('animationstart');
+
+ animation.currentTime = 100.0;
+
+ // Make idle
+ animation.timeline = null;
+
+ const evt = await watcher.wait_for('animationcancel');
+ assert_time_equals_literal(evt.elapsedTime, 0.1);
+}, 'Active -> Idle, setting Animation.timeline = null');
+
+promise_test(async t => {
+ // We should NOT pause animation since calling cancel synchronously.
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ await watcher.wait_for('animationstart');
+
+ animation.currentTime = 50.0;
+ animation.cancel();
+
+ const evt = await watcher.wait_for('animationcancel');
+ assert_time_equals_literal(evt.elapsedTime, 0.05);
+}, 'Active -> Idle, calling Animation.cancel()');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
+
+ // Seek to Active phase.
+ animation.currentTime = 100 * MS_PER_SEC;
+ await watcher.wait_for('animationstart');
+
+ // Seek to Before phase.
+ animation.currentTime = 0;
+
+ const evt = await watcher.wait_for('animationend');
+ assert_equals(evt.elapsedTime, 0.0);
+}, 'Active -> Before');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s paused');
+
+ await watcher.wait_for('animationstart');
+
+ // Seek to After phase.
+ animation.finish();
+
+ const evt = await watcher.wait_for('animationend');
+ assert_equals(evt.elapsedTime, 100.0);
+}, 'Active -> After');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
+
+ // Seek to After phase.
+ animation.finish();
+ await watcher.wait_for([ 'animationstart', 'animationend' ]);
+
+ // Seek to Before phase.
+ animation.currentTime = 0;
+
+ const events = await watcher.wait_for(['animationstart', 'animationend'], {
+ record: 'all',
+ });
+ assert_equals(events[0].elapsedTime, 100.0);
+ assert_equals(events[1].elapsedTime, 0.0);
+}, 'After -> Before');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
+
+ // Seek to After phase.
+ animation.finish();
+ await watcher.wait_for([ 'animationstart', 'animationend' ]);
+
+ // Seek to Active phase.
+ animation.currentTime = 100 * MS_PER_SEC;
+
+ const evt = await watcher.wait_for('animationstart');
+ assert_equals(evt.elapsedTime, 100.0);
+}, 'After -> Active');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 3 paused');
+
+ await animation.ready;
+
+ // Seek to iteration 0 (no animationiteration event should be dispatched)
+ animation.currentTime = 100 * MS_PER_SEC;
+ await watcher.wait_for('animationstart');
+
+ // Seek to iteration 2
+ animation.currentTime = 300 * MS_PER_SEC;
+ let evt = await watcher.wait_for('animationiteration');
+ assert_equals(evt.elapsedTime, 200);
+
+ // Seek to After phase (no animationiteration event should be dispatched)
+ animation.currentTime = 400 * MS_PER_SEC;
+ evt = await watcher.wait_for('animationend');
+ assert_equals(evt.elapsedTime, 300);
+}, 'Active -> Active (forwards)');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 3');
+
+ // Seek to After phase.
+ animation.finish();
+ await watcher.wait_for([ 'animationstart', 'animationend' ]);
+
+ // Seek to iteration 2 (no animationiteration event should be dispatched)
+ animation.pause();
+ animation.currentTime = 300 * MS_PER_SEC;
+ await watcher.wait_for('animationstart');
+
+ // Seek to mid of iteration 0 phase.
+ animation.currentTime = 200 * MS_PER_SEC;
+
+ const evt = await watcher.wait_for('animationiteration');
+ assert_equals(evt.elapsedTime, 200.0);
+
+ // Seek to before phase (no animationiteration event should be dispatched)
+ animation.currentTime = 0;
+ await watcher.wait_for('animationend');
+}, 'Active -> Active (backwards)');
+
+promise_test(async t => {
+ const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused');
+
+ await watcher.wait_for('animationstart');
+
+ // Seek to Idle phase.
+ div.style.display = 'none';
+ flushComputedStyle(div);
+
+ await watcher.wait_for('animationcancel');
+
+ // Restart this animation.
+ div.style.display = '';
+ await watcher.wait_for('animationstart');
+}, 'Active -> Idle -> Active: animationstart is fired by restarting animation');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 2 paused');
+
+ // Make After.
+ animation.finish();
+
+ await watcher.wait_for([ 'animationstart', 'animationend' ]);
+ animation.playbackRate = -1;
+
+ let evt = await watcher.wait_for('animationstart');
+ assert_equals(evt.elapsedTime, 200);
+
+ // Seek to 1st iteration
+ animation.currentTime = 200 * MS_PER_SEC - 1;
+
+ evt = await watcher.wait_for('animationiteration');
+ assert_equals(evt.elapsedTime, 100);
+
+ // Seek to before
+ animation.currentTime = 100 * MS_PER_SEC - 1;
+
+ evt = await watcher.wait_for('animationend');
+ assert_equals(evt.elapsedTime, 0);
+ assert_equals(animation.playState, 'running'); // delay
+}, 'Negative playbackRate sanity test(Before -> Active -> Before)');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
+
+ animation.currentTime = 150 * MS_PER_SEC;
+ animation.currentTime = 50 * MS_PER_SEC;
+
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Redundant change, before -> active, then back');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
+
+ animation.currentTime = 250 * MS_PER_SEC;
+ animation.currentTime = 50 * MS_PER_SEC;
+
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Redundant change, before -> after, then back');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
+
+ // Get us into the initial state:
+ animation.currentTime = 150 * MS_PER_SEC;
+
+ await watcher.wait_for('animationstart');
+
+ animation.currentTime = 50 * MS_PER_SEC;
+ animation.currentTime = 150 * MS_PER_SEC;
+
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Redundant change, active -> before, then back');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
+
+ // Get us into the initial state:
+ animation.currentTime = 150 * MS_PER_SEC;
+
+ await watcher.wait_for('animationstart');
+
+ animation.currentTime = 250 * MS_PER_SEC;
+ animation.currentTime = 150 * MS_PER_SEC;
+
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Redundant change, active -> after, then back');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
+
+ // Get us into the initial state:
+ animation.currentTime = 250 * MS_PER_SEC;
+
+ await watcher.wait_for(['animationstart', 'animationend']);
+
+ animation.currentTime = 50 * MS_PER_SEC;
+ animation.currentTime = 250 * MS_PER_SEC;
+
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Redundant change, after -> before, then back');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
+
+ // Get us into the initial state:
+ animation.currentTime = 250 * MS_PER_SEC;
+
+ await watcher.wait_for(['animationstart', 'animationend']);
+
+ animation.currentTime = 150 * MS_PER_SEC;
+ animation.currentTime = 250 * MS_PER_SEC;
+
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Redundant change, after -> active, then back');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ await watcher.wait_for('animationstart');
+
+ // Make idle
+ animation.cancel();
+ await watcher.wait_for('animationcancel');
+
+ animation.cancel();
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Call Animation.cancel after canceling animation.');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ await watcher.wait_for('animationstart');
+
+ // Make idle
+ animation.cancel();
+ animation.play();
+ await watcher.wait_for([ 'animationcancel', 'animationstart' ]);
+}, 'Restart animation after canceling animation immediately.');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ await watcher.wait_for('animationstart');
+
+ // Make idle
+ animation.cancel();
+ animation.play();
+ animation.cancel();
+ await watcher.wait_for('animationcancel');
+
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Call Animation.cancel after restarting animation immediately.');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ await watcher.wait_for('animationstart');
+
+ // Make idle
+ animation.timeline = null;
+ await watcher.wait_for('animationcancel');
+
+ animation.timeline = document.timeline;
+ animation.play();
+ await watcher.wait_for('animationstart');
+}, 'Set timeline and play transition after clearing the timeline.');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ await watcher.wait_for('animationstart');
+
+ // Make idle
+ animation.cancel();
+ await watcher.wait_for('animationcancel');
+
+ animation.effect = null;
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Set null target effect after canceling the animation.');
+
+promise_test(async t => {
+ const { animation, watcher } = setupAnimation(t, 'anim 100s');
+
+ await watcher.wait_for('animationstart');
+
+ animation.effect = null;
+ await watcher.wait_for('animationend');
+
+ animation.cancel();
+ // Then wait a couple of frames and check that no event was dispatched.
+ await waitForAnimationFrames(2);
+}, 'Cancel the animation after clearing the target effect.');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/event-order.tentative.html b/testing/web-platform/tests/css/css-animations/event-order.tentative.html
new file mode 100644
index 0000000000..58f0bb67ca
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/event-order.tentative.html
@@ -0,0 +1,259 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Tests for CSS animation event order</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testcommon.js"></script>
+<style>
+@keyframes anim {
+ from { margin-left: 0px; }
+ to { margin-left: 100px; }
+}
+@keyframes color-anim {
+ from { color: red; }
+ to { color: green; }
+}
+</style>
+<div id="log"></div>
+<script type='text/javascript'>
+'use strict';
+
+/**
+ * Asserts that the set of actual and received events match.
+ * @param actualEvents An array of the received AnimationEvent objects.
+ * @param expectedEvents A series of array objects representing the expected
+ * events, each having the form:
+ * [ event type, target element, [pseudo type], elapsed time ]
+ */
+const checkEvents = (actualEvents, ...expectedEvents) => {
+ const actualTypeSummary = actualEvents.map(event => event.type).join(', ');
+ const expectedTypeSummary = expectedEvents.map(event => event[0]).join(', ');
+
+ assert_equals(
+ actualEvents.length,
+ expectedEvents.length,
+ `Number of events received (${actualEvents.length}) \
+should match expected number (${expectedEvents.length}) \
+(expected: ${expectedTypeSummary}, actual: ${actualTypeSummary})`
+ );
+
+ for (const [index, actualEvent] of actualEvents.entries()) {
+ const expectedEvent = expectedEvents[index];
+ const [type, target] = expectedEvent;
+ const pseudoElement = expectedEvent.length === 4 ? expectedEvent[2] : '';
+ const elapsedTime = expectedEvent[expectedEvent.length - 1];
+
+ assert_equals(
+ actualEvent.type,
+ type,
+ `Event #${index + 1} types should match \
+(expected: ${expectedTypeSummary}, actual: ${actualTypeSummary})`
+ );
+ assert_equals(
+ actualEvent.target,
+ target,
+ `Event #${index + 1} targets should match`
+ );
+ assert_equals(
+ actualEvent.pseudoElement,
+ pseudoElement,
+ `Event #${index + 1} pseudoElements should match`
+ );
+ assert_equals(
+ actualEvent.elapsedTime,
+ elapsedTime,
+ `Event #${index + 1} elapsedTimes should match`
+ );
+ }
+};
+
+const setupAnimation = (t, animationStyle, receiveEvents) => {
+ const div = addDiv(t, { style: 'animation: ' + animationStyle });
+
+ for (const name of ['start', 'iteration', 'end']) {
+ div['onanimation' + name] = evt => {
+ receiveEvents.push({
+ type: evt.type,
+ target: evt.target,
+ pseudoElement: evt.pseudoElement,
+ elapsedTime: evt.elapsedTime,
+ });
+ };
+ }
+
+ const watcher = new EventWatcher(t, div, [
+ 'animationstart',
+ 'animationiteration',
+ 'animationend',
+ ]);
+
+ const animation = div.getAnimations()[0];
+
+ return [animation, watcher, div];
+};
+
+promise_test(async t => {
+ let events = [];
+ const [animation1, watcher1, div1] =
+ setupAnimation(t, 'anim 100s 2 paused', events);
+ const [animation2, watcher2, div2] =
+ setupAnimation(t, 'anim 100s 2 paused', events);
+
+ await Promise.all([ watcher1.wait_for('animationstart'),
+ watcher2.wait_for('animationstart') ]);
+
+ checkEvents(events, ['animationstart', div1, 0],
+ ['animationstart', div2, 0]);
+
+ events.length = 0; // Clear received event array
+
+ animation1.currentTime = 100 * MS_PER_SEC;
+ animation2.currentTime = 100 * MS_PER_SEC;
+
+ await Promise.all([ watcher1.wait_for('animationiteration'),
+ watcher2.wait_for('animationiteration') ]);
+
+ checkEvents(events, ['animationiteration', div1, 100],
+ ['animationiteration', div2, 100]);
+
+ events.length = 0; // Clear received event array
+
+ animation1.finish();
+ animation2.finish();
+
+ await Promise.all([ watcher1.wait_for('animationend'),
+ watcher2.wait_for('animationend') ]);
+
+ checkEvents(events, ['animationend', div1, 200],
+ ['animationend', div2, 200]);
+}, 'Same events are ordered by elements');
+
+function pseudoTest(description, testMarkerPseudos) {
+ promise_test(async t => {
+ // Setup a hierarchy as follows:
+ //
+ // parent
+ // |
+ // (::marker, ::before, ::after) // ::marker optional
+ // |
+ // child
+ const parentDiv = addDiv(t, { style: 'animation: anim 100s' });
+
+ parentDiv.id = 'parent-div';
+ addStyle(t, {
+ '#parent-div::after': "content: ''; animation: anim 100s",
+ '#parent-div::before': "content: ''; animation: anim 100s",
+ });
+
+ if (testMarkerPseudos) {
+ parentDiv.style.display = 'list-item';
+ addStyle(t, {
+ '#parent-div::marker': "content: ''; animation: color-anim 100s",
+ });
+ }
+
+ const childDiv = addDiv(t, { style: 'animation: anim 100s' });
+ parentDiv.append(childDiv);
+
+ // Setup event handlers
+ let events = [];
+ for (const name of ['start', 'iteration', 'end', 'cancel']) {
+ parentDiv['onanimation' + name] = evt => {
+ events.push({
+ type: evt.type,
+ target: evt.target,
+ pseudoElement: evt.pseudoElement,
+ elapsedTime: evt.elapsedTime,
+ });
+ };
+ }
+
+ // Wait a couple of frames for the events to be dispatched
+ await waitForFrame();
+ await waitForFrame();
+
+ const expectedEvents = [
+ ['animationstart', parentDiv, 0],
+ ['animationstart', parentDiv, '::marker', 0],
+ ['animationstart', parentDiv, '::before', 0],
+ ['animationstart', parentDiv, '::after', 0],
+ ['animationstart', childDiv, 0],
+ ];
+ if (!testMarkerPseudos) {
+ expectedEvents.splice(1, 1);
+ }
+
+ checkEvents(events, ...expectedEvents);
+ }, description);
+}
+
+pseudoTest('Same events on pseudo-elements follow the prescribed order', false);
+pseudoTest('Same events on pseudo-elements follow the prescribed order ' +
+ '(::marker)', true);
+
+promise_test(async t => {
+ let events = [];
+ const [animation1, watcher1, div1] =
+ setupAnimation(t, 'anim 200s 400s', events);
+ const [animation2, watcher2, div2] =
+ setupAnimation(t, 'anim 300s 2', events);
+
+ await watcher2.wait_for('animationstart');
+
+ animation1.currentTime = 400 * MS_PER_SEC;
+ animation2.currentTime = 400 * MS_PER_SEC;
+
+ events.length = 0; // Clear received event array
+
+ await Promise.all([ watcher1.wait_for('animationstart'),
+ watcher2.wait_for('animationiteration') ]);
+
+ checkEvents(events, ['animationiteration', div2, 300],
+ ['animationstart', div1, 0]);
+}, 'Start and iteration events are ordered by time');
+
+promise_test(async t => {
+ let events = [];
+ const [animation1, watcher1, div1] =
+ setupAnimation(t, 'anim 150s', events);
+ const [animation2, watcher2, div2] =
+ setupAnimation(t, 'anim 100s 2', events);
+
+ await Promise.all([ watcher1.wait_for('animationstart'),
+ watcher2.wait_for('animationstart') ]);
+
+ animation1.currentTime = 150 * MS_PER_SEC;
+ animation2.currentTime = 150 * MS_PER_SEC;
+
+ events.length = 0; // Clear received event array
+
+ await Promise.all([ watcher1.wait_for('animationend'),
+ watcher2.wait_for('animationiteration') ]);
+
+ checkEvents(events, ['animationiteration', div2, 100],
+ ['animationend', div1, 150]);
+}, 'Iteration and end events are ordered by time');
+
+promise_test(async t => {
+ let events = [];
+ const [animation1, watcher1, div1] =
+ setupAnimation(t, 'anim 100s 100s', events);
+ const [animation2, watcher2, div2] =
+ setupAnimation(t, 'anim 100s 2', events);
+
+ animation1.finish();
+ animation2.finish();
+
+ await Promise.all([ watcher1.wait_for([ 'animationstart',
+ 'animationend' ]),
+ watcher2.wait_for([ 'animationstart',
+ 'animationend' ]) ]);
+
+ checkEvents(events, ['animationstart', div2, 0],
+ ['animationstart', div1, 0],
+ ['animationend', div1, 100],
+ ['animationend', div2, 200]);
+}, 'Start and end events are sorted correctly when fired simultaneously');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/flip-running-animation-via-variable-ref.html b/testing/web-platform/tests/css/css-animations/flip-running-animation-via-variable-ref.html
new file mode 100644
index 0000000000..411d09d433
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/flip-running-animation-via-variable-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Transform animation under large scale</title>
+<link rel="author" title="Kevin Ellis" href="mailto:kevers@chromium.org">
+<link rel="help" href="https://crbug.com/1221622">
+<style>
+#container {
+ display: inline-block;
+ margin-left: 150px;
+ padding: 0;
+ transform: scaleX(-1) rotate(90deg);
+}
+#block-1 {
+ background: blue;
+ height: 200px;
+ width: 100px;
+}
+#block-2 {
+ background: green;
+ height: 100px;
+ width: 100px;
+}
+</style>
+
+<div id="container">
+ <div id="block-1"></div>
+ <div id="block-2"></div>
+</div>
diff --git a/testing/web-platform/tests/css/css-animations/flip-running-animation-via-variable.html b/testing/web-platform/tests/css/css-animations/flip-running-animation-via-variable.html
new file mode 100644
index 0000000000..22d23c1f72
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/flip-running-animation-via-variable.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Transform animation under large scale</title>
+<link rel="author" title="Kevin Ellis" href="mailto:kevers@chromium.org">
+<link rel="help" href="https://crbug.com/1221622">
+<link rel="match" href="flip-running-animation-via-variable-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+@keyframes spin {
+ 0% { transform: scaleX(var(--scale)) rotate(0deg); }
+ 100% { transform: scaleX(var(--scale)) rotate(180deg); }
+}
+
+html {
+ --scale: 1;
+}
+
+html.tweaked {
+ --scale: -1;
+}
+
+#container {
+ display: inline-block;
+ margin-left: 150px;
+ padding: 0;
+ /* Force animation to be effectively frozen at 50% progress. */
+ animation: spin 1000000s cubic-bezier(0, 1, 1, 0) -500000s;
+}
+
+#block-1 {
+ background: blue;
+ height: 200px;
+ width: 100px;
+}
+
+#block-2 {
+ background: green;
+ height: 100px;
+ width: 100px;
+}
+</style>
+
+<body>
+ <div id="container">
+ <div id="block-1"></div>
+ <div id="block-2"></div>
+ </div>
+</body>
+
+<script>
+window.onload = () => {
+ const anim = document.getAnimations()[0];
+ anim.ready.then(() => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ document.querySelector('html').classList.add('tweaked');
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ takeScreenshot();
+ });
+ });
+ });
+ });
+ });
+};
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/historical.html b/testing/web-platform/tests/css/css-animations/historical.html
new file mode 100644
index 0000000000..f9b13fa016
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/historical.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+
+<title>Historical CSS Animation features must be removed</title>
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#animations">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function isInterfaceNuked(name) {
+ test(function() {
+ assert_equals(window[name], undefined)
+ }, "Historical CSS features must be removed: " + name)
+}
+var nukedInterfaces = [
+ "WebKitAnimationEvent", // Replaced by unprefixed AnimationEvent
+];
+nukedInterfaces.forEach(isInterfaceNuked);
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/idlharness.html b/testing/web-platform/tests/css/css-animations/idlharness.html
new file mode 100644
index 0000000000..8ddf2e6a94
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/idlharness.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>css-animations IDL tests</title>
+ <meta name="timeout" content="long">
+ <link rel="help" href="https://drafts.csswg.org/css-animations/">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+
+<body>
+ <div id="log"></div>
+
+ <style>
+ @keyframes test {
+ from { top: 0px; }
+ to { top: 1px; }
+ }
+ </style>
+
+ <script>
+ 'use strict';
+
+ idl_test(
+ ['css-animations'],
+ ['html', 'dom', 'cssom'],
+ idl_array => {
+ try {
+ window.keyframes = document.styleSheets[0].cssRules[0];
+ } catch (e) {
+ // Surfaced in idlharness.js's test_object below.
+ }
+
+ idl_array.add_objects({
+ AnimationEvent: ['new AnimationEvent("animationstart")'],
+ CSSKeyframesRule: ['keyframes'],
+ CSSKeyframeRule: ['keyframes.cssRules[0]'],
+ });
+ }
+ );
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/inheritance-pseudo-element-ref.html b/testing/web-platform/tests/css/css-animations/inheritance-pseudo-element-ref.html
new file mode 100644
index 0000000000..a25f465fdf
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/inheritance-pseudo-element-ref.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<style>
+body {
+ font-size: 30px;
+}
+
+.container {
+ font-size: 5px;
+ height: 40px;
+}
+
+.container::after {
+ content: "";
+ display: block;
+ border: 2px solid blue;
+ width: 1em;
+ height: 1em;
+ font-size: 1em;
+}
+</style>
+<div class="container"></div>
+<div class="container"></div>
+<div class="container"></div>
+<div class="container"></div>
+<div class="container"></div>
diff --git a/testing/web-platform/tests/css/css-animations/inheritance-pseudo-element.html b/testing/web-platform/tests/css/css-animations/inheritance-pseudo-element.html
new file mode 100644
index 0000000000..4ebcbc95de
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/inheritance-pseudo-element.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<title>@keyframes + pseudo-element inherits from the right style.</title>
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1757017">
+<link rel="help" href="https://drafts.csswg.org/css-animations/#property-index">
+<link rel="match" href="inheritance-pseudo-element-ref.html">
+<style>
+body {
+ font-size: 30px;
+}
+
+.container {
+ font-size: 5px;
+ height: 40px;
+}
+
+.container::after {
+ content: "";
+ display: block;
+ border: 2px solid blue;
+ width: 1em;
+ height: 1em;
+}
+
+@keyframes kf-fs5px { from, to { font-size: 5px; } }
+.fs5px::after {
+ animation: kf-fs5px 1s infinite;
+}
+
+@keyframes kf-fs1em { from, to { font-size: 1em; } }
+.fs1em::after {
+ animation: kf-fs1em 1s infinite;
+}
+
+@keyframes kf-fs100p { from, to { font-size: 100%; } }
+.fs100p::after {
+ animation: kf-fs100p 1s infinite;
+}
+
+@keyframes kf-fsinherit { from, to { font-size: inherit; } }
+.fsinherit::after {
+ animation: kf-fsinherit 1s infinite;
+}
+</style>
+<div class="container"></div>
+<div class="container fs5px"></div>
+<div class="container fs1em"></div>
+<div class="container fs100p"></div>
+<div class="container fsinherit"></div>
diff --git a/testing/web-platform/tests/css/css-animations/inheritance.html b/testing/web-platform/tests/css/css-animations/inheritance.html
new file mode 100644
index 0000000000..6e7697b9d8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/inheritance.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Inheritance of CSS Animations properties</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#property-index">
+<meta name="assert" content="Properties inherit or not according to the spec.">
+<meta name="assert" content="Properties have initial values according to the spec.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/inheritance-testcommon.js"></script>
+</head>
+<body>
+<div id="container">
+<div id="target"></div>
+</div>
+<script>
+assert_not_inherited('animation-delay', '0s', '2s');
+assert_not_inherited('animation-direction', 'normal', 'reverse');
+assert_not_inherited('animation-duration', '0s', '3s');
+assert_not_inherited('animation-fill-mode', 'none', 'forwards');
+assert_not_inherited('animation-iteration-count', '1', '4');
+assert_not_inherited('animation-name', 'none', 'spinner');
+assert_not_inherited('animation-play-state', 'running', 'paused');
+assert_not_inherited('animation-timing-function', 'ease', 'linear');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/keyframes-remove-documentElement-crash.html b/testing/web-platform/tests/css/css-animations/keyframes-remove-documentElement-crash.html
new file mode 100644
index 0000000000..9573ce1b0f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/keyframes-remove-documentElement-crash.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html class="test-wait">
+<title>CSS Animations Test: Chrome crash when removing documentElement and @keyframes stylesheet</title>
+<link rel="help" href="https://crbug.com/999522">
+<style>
+ @keyframes anim {
+ from { color: pink }
+ to { color: purple }
+ }
+ div {
+ animation: notfound 1s;
+ }
+</style>
+<div></div>
+<script>
+window.addEventListener('load', () => {
+ document.body.offsetTop;
+ document.querySelector("style").remove();
+ // We need the root later to remove the test-wait class.
+ const root = document.documentElement;
+ document.documentElement.remove();
+
+ // rAF to make sure that style runs.
+ requestAnimationFrame(() => {
+ root.classList.remove('test-wait');
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/keyframes-unrelated-custom-property.html b/testing/web-platform/tests/css/css-animations/keyframes-unrelated-custom-property.html
new file mode 100644
index 0000000000..c9ba7688a9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/keyframes-unrelated-custom-property.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Unrelated custom properties do not conflict with each other</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<link rel="help" href="https://crbug.com/1236043">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @keyframes test {
+ 0% { --x: green; }
+ 100% { --x: green; }
+
+ /* This should not affect the background-color of #div: */
+ 0% { --unused: yellow; }
+ }
+ #div {
+ animation: test 10s linear paused;
+ background-color: var(--x, red);
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<div id=div></div>
+<script>
+
+test(() => {
+ assert_equals(getComputedStyle(div).backgroundColor, 'rgb(0, 128, 0)');
+}, 'Unrelated custom properties do not conflict with each other');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/keyframes-zero-angle-crash.html b/testing/web-platform/tests/css/css-animations/keyframes-zero-angle-crash.html
new file mode 100644
index 0000000000..593e4a6a1c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/keyframes-zero-angle-crash.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+<title>CSS Animations Test: Chrome crash when using zero angles in keyframes</title>
+<link rel="help" href="https://crbug.com/1307230">
+<body>
+<div id="test_div">
+ Output does not matter, only check that we do not crash with zero angles.
+</div>
+<script>
+let anim = new KeyframeEffect(test_div, [{ "transform": "rotate(0) scale3D(1,1,1)" }]);
+new Animation(anim, document.timeline);
+</script>
+</body>
diff --git a/testing/web-platform/tests/css/css-animations/missing-values-first-keyframe.html b/testing/web-platform/tests/css/css-animations/missing-values-first-keyframe.html
new file mode 100644
index 0000000000..311e26259c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/missing-values-first-keyframe.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<title>Missing properties in first keyframe</title>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<link rel="help" href="https://www.w3.org/TR/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect">
+<meta name="assert"
+ content="CSS animation correctly interpolates from neutral keyframe">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<style type="text/css" media="screen">
+ body {
+ margin: 0;
+ }
+
+ .box {
+ position: relative;
+ width: 100px;
+ height: 100px;
+ left: 0;
+ background-color: green;
+ }
+
+ #box1 {
+ left: 200px;
+ animation: move-left 2s paused linear;
+ }
+
+ #box2 {
+ transform: translateX(200px);
+ animation: move-transform 2s paused linear;
+ }
+
+ @keyframes move-left {
+ 0% {
+ opacity: 1;
+ }
+ 25% {
+ opacity: 1;
+ }
+ 50% {
+ left: 0;
+ opacity: 1;
+ }
+ 100% {
+ left: 0;
+ opacity: 0;
+ }
+ }
+
+ @keyframes move-transform {
+ 0% {
+ opacity: 1;
+ }
+ 25% {
+ opacity: 1;
+ }
+ 50% {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ 100% {
+ transform: translateX(0);
+ opacity: 0;
+ }
+ }
+</style>
+<body>
+ <div class="box" id="box1"></div>
+ <div class="box" id="box2"></div>
+</body>
+<script>
+ promise_test(async t => {
+ document.getAnimations().forEach(anim => {
+ anim.currentTime = 500;
+ });
+ assert_equals(getComputedStyle(box1).left, "100px");
+ assert_matrix_equals(
+ getComputedStyle(box2).transform,
+ 'matrix(1, 0, 0, 1, 100, 0)');
+ }, 'Missing property values in the first keyframe are correctly ' +
+ 'interpolated from a neutral keyframe value');
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/missing-values-last-keyframe.html b/testing/web-platform/tests/css/css-animations/missing-values-last-keyframe.html
new file mode 100644
index 0000000000..04c4eba45e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/missing-values-last-keyframe.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<title>Missing properties in last keyframe</title>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<link rel="help" href="https://www.w3.org/TR/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect">
+<meta name="assert"
+ content="CSS animation correctly interpolates from neutral keyframe">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<style type="text/css" media="screen">
+ body {
+ margin: 0;
+ }
+
+ .box {
+ position: relative;
+ width: 100px;
+ height: 100px;
+ left: 0;
+ background-color: green;
+ }
+
+ #box1 {
+ left: 200px;
+ animation: move-left 10s linear;
+ }
+
+ #box2 {
+ transform: translateX(200px);
+ animation: move-transform 10s linear;
+ }
+
+ @keyframes move-left {
+ 0% {
+ left: 0;
+ opacity: 0;
+ }
+ 50% {
+ left: 0;
+ opacity: 1;
+ }
+ 75% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+
+ @keyframes move-transform {
+ 0% {
+ transform: translateX(0);
+ opacity: 0;
+ }
+ 50% {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ 75% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+</style>
+<body>
+ <div class="box" id="box1"></div>
+ <div class="box" id="box2"></div>
+</body>
+<script>
+ promise_test(async t => {
+ document.getAnimations().forEach(anim => {
+ anim.currentTime = 7500;
+ });
+ assert_equals(getComputedStyle(box1).left, "100px");
+ assert_matrix_equals(
+ getComputedStyle(box2).transform,
+ 'matrix(1, 0, 0, 1, 100, 0)');
+ }, 'Missing property values in the last keyframe are correctly ' +
+ 'interpolated from a neutral keyframe value');
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/nested-scale-animations-ref.html b/testing/web-platform/tests/css/css-animations/nested-scale-animations-ref.html
new file mode 100644
index 0000000000..6dd3cde311
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/nested-scale-animations-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<div style="width: 100px; height: 45px; background: blue"></div>
+<div style="height: 10px"></div>
+<div style="width: 100px; height: 45px; background: green"></div>
diff --git a/testing/web-platform/tests/css/css-animations/nested-scale-animations.html b/testing/web-platform/tests/css/css-animations/nested-scale-animations.html
new file mode 100644
index 0000000000..8793e044b7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/nested-scale-animations.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Nested scale animations</title>
+<link rel="author" title="Xianzhu Wang" href="mailto:wangxianzhu@chromium.org">
+<link rel="help" href="https://crbug.com/1165408">
+<link rel="match" href="nested-scale-animations-ref.html">
+<meta name="assert" content="Contents under nested scale animations should not be too blurry">
+<script src="/common/reftest-wait.js"></script>
+<style>
+@keyframes scale {
+ 0% {transform: scale(1);}
+ 1% {transform: scale(10);}
+ 100% {transform: scale(10);}
+}
+.animate {
+ animation: scale 1s forwards;
+ position: relative;
+ top: 40%;
+ left: 40%;
+ width: 20%;
+ height: 20%;
+}
+</style>
+<div style="width: 100px; height: 100px; overflow: hidden; position: relative">
+ <div class="animate" onanimationstart="animationStarted()">
+ <div class="animate" onanimationstart="animationStarted()">
+ <div style="width: 4px; height: 2px; background: blue"></div>
+ <div style="width: 4px; height: 2px; background: green"></div>
+ </div>
+ </div>
+ <!-- To mask off the pixels that may be blurry/antialiased while the rendering
+ quality is acceptable. -->
+ <div style="position: absolute; top: 45px; left: 0; width: 100px; height: 10px; background: white"></div>
+</div>
+<script>
+var startedCount = 0;
+function animationStarted() {
+ startedCount++;
+ if (startedCount == 2)
+ takeScreenshotDelayed(900);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-composition-computed.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-composition-computed.tentative.html
new file mode 100644
index 0000000000..535a40c9ca
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-composition-computed.tentative.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animationComposition</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#propdef-animation-composition">
+<meta name="assert" content="animation-composition computed value is as specified.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+test_computed_value("animation-composition", "replace, add, accumulate");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-composition-invalid.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-composition-invalid.tentative.html
new file mode 100644
index 0000000000..a08b33c84d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-composition-invalid.tentative.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-composition with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#propdef-animation-composition">
+<meta name="assert" content="animation-composition supports only the grammar '<single-animation-composition> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation-composition", "auto");
+test_invalid_value("animation-composition", "add replace");
+
+test_invalid_value("animation-composition", "add, initial");
+test_invalid_value("animation-composition", "initial, add");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-composition-valid.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-composition-valid.tentative.html
new file mode 100644
index 0000000000..8dca8dbabe
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-composition-valid.tentative.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-composition with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#propdef-animation-composition">
+<meta name="assert" content="animation-composition supports the full grammar '<single-animation-composition> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("animation-composition", "replace");
+test_valid_value("animation-composition", "add");
+test_valid_value("animation-composition", "accumulate");
+test_valid_value("animation-composition", "replace, add, accumulate");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-computed.html
new file mode 100644
index 0000000000..2a70585aad
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-computed.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animation</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation">
+<meta name="assert" content="animation computed value is as specified.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+// <single-animation> = <time> || <easing-function> || <time> ||
+// <single-animation-iteration-count> || <single-animation-direction> ||
+// <single-animation-fill-mode> || <single-animation-play-state> ||
+// [ none | <keyframes-name> ]
+
+test(() => {
+ assert_equals(getComputedStyle(document.getElementById('target')).animation, "none");
+}, "Default animation value");
+
+test_computed_value("animation", "1s", "1s");
+test_computed_value("animation", "cubic-bezier(0, -2, 1, 3)", "cubic-bezier(0, -2, 1, 3)");
+test_computed_value("animation", "ease-in-out", "ease-in-out");
+test_computed_value("animation", "1s -3s", "1s -3s");
+test_computed_value("animation", "4", "4");
+test_computed_value("animation", "reverse", "reverse");
+test_computed_value("animation", "both", "both");
+test_computed_value("animation", "paused", "paused");
+test_computed_value("animation", "none", "none");
+test_computed_value("animation", "anim", "anim");
+
+test_computed_value("animation", "anim paused both reverse 4 1s -3s cubic-bezier(0, -2, 1, 3)",
+ "1s cubic-bezier(0, -2, 1, 3) -3s 4 reverse both paused anim");
+
+test_computed_value("animation", "anim paused both reverse, 4 1s -3s cubic-bezier(0, -2, 1, 3)",
+ "reverse both paused anim, 1s cubic-bezier(0, -2, 1, 3) -3s 4");
+
+test_computed_value("animation", "none, none", "none, none");
+
+test(() => {
+ const target = document.getElementById('target');
+ target.style.animation = "initial";
+ target.style.animationDelay = "1s";
+ assert_equals(getComputedStyle(target).animation, "0s 1s");
+}, "Animation with a delay but no duration");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-computed.html
new file mode 100644
index 0000000000..832466ac06
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-computed.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animationDelay</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-delay">
+<meta name="assert" content="animation-delay converts to seconds.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+test_computed_value("animation-delay", "-500ms", "-0.5s");
+test_computed_value("animation-delay", "calc(2 * 3s)", "6s");
+test_computed_value("animation-delay", "20s, 10s");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-computed.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-computed.tentative.html
new file mode 100644
index 0000000000..77f9670638
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-computed.tentative.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-delay-end">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+<div id="target"></div>
+<script>
+test_computed_value("animation-delay-end", "initial", "0s");
+test_computed_value("animation-delay-end", "-500ms", "-0.5s");
+test_computed_value("animation-delay-end", "calc(2 * 3s)", "6s");
+test_computed_value("animation-delay-end", "20s, 10s");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-invalid.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-invalid.tentative.html
new file mode 100644
index 0000000000..7cabd4e8e5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-invalid.tentative.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-delay-end">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_invalid_value("animation-delay-end", "infinite");
+test_invalid_value("animation-delay-end", "0");
+test_invalid_value("animation-delay-end", "1s 2s");
+test_invalid_value("animation-delay-end", "1s / 2s");
+test_invalid_value("animation-delay-end", "100px");
+test_invalid_value("animation-delay-end", "100%");
+
+test_invalid_value("animation-delay-end", "peek 50%");
+test_invalid_value("animation-delay-end", "50% contain");
+test_invalid_value("animation-delay-end", "50% cover");
+test_invalid_value("animation-delay-end", "50% entry");
+test_invalid_value("animation-delay-end", "50% enter");
+test_invalid_value("animation-delay-end", "50% exit");
+test_invalid_value("animation-delay-end", "contain contain");
+test_invalid_value("animation-delay-end", "auto");
+test_invalid_value("animation-delay-end", "none");
+test_invalid_value("animation-delay-end", "cover 50% enter 50%");
+test_invalid_value("animation-delay-end", "cover 100px");
+test_invalid_value("animation-delay-end", "cover");
+test_invalid_value("animation-delay-end", "contain");
+test_invalid_value("animation-delay-end", "enter");
+test_invalid_value("animation-delay-end", "exit");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-valid.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-valid.tentative.html
new file mode 100644
index 0000000000..162c781bb0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-end-valid.tentative.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-delay-end">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_valid_value("animation-delay-end", "-5ms");
+test_valid_value("animation-delay-end", "0s");
+test_valid_value("animation-delay-end", "10s");
+test_valid_value("animation-delay-end", "20s, 10s");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-invalid.html
new file mode 100644
index 0000000000..1569497bb6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-invalid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-delay with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-delay">
+<meta name="assert" content="animation-delay supports only the grammar '<single-animation-play-state> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation-delay", "infinite");
+test_invalid_value("animation-delay", "0");
+test_invalid_value("animation-delay", "1s 2s 3s");
+
+test_invalid_value("animation-delay", "initial, -3s");
+test_invalid_value("animation-delay", "-3s, initial");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-shorthand-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-shorthand-computed.html
new file mode 100644
index 0000000000..0a1eb96041
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-shorthand-computed.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>animation-delay shorthand (computed values)</title>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-delay">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+<div id="target"></div>
+<script>
+test_computed_value("animation-delay", "1s");
+test_computed_value("animation-delay", "-1s");
+test_computed_value("animation-delay", "1s 2s");
+test_computed_value("animation-delay", "1s, 2s");
+test_computed_value("animation-delay", "1s 2s, 3s");
+test_computed_value("animation-delay", "1s, 2s 3s");
+test_computed_value("animation-delay", "1s, 2s, 3s");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-shorthand.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-shorthand.html
new file mode 100644
index 0000000000..5c74a4d8e4
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-shorthand.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>animation-delay shorthand</title>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-delay">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/shorthand-testcommon.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_valid_value("animation-delay", "1s");
+test_valid_value("animation-delay", "-1s");
+test_valid_value("animation-delay", "1s 2s");
+test_valid_value("animation-delay", "1s, 2s");
+test_valid_value("animation-delay", "1s 2s, 3s");
+test_valid_value("animation-delay", "1s, 2s 3s");
+test_valid_value("animation-delay", "1s, 2s, 3s");
+
+test_invalid_value("animation-delay", "1s 2s 3s");
+test_invalid_value("animation-delay", "0s, 1s 2s 3s");
+test_invalid_value("animation-delay", "1s / 2s");
+test_invalid_value("animation-delay", "1s, 2px");
+test_invalid_value("animation-delay", "#ff0000");
+test_invalid_value("animation-delay", "red");
+test_invalid_value("animation-delay", "thing");
+test_invalid_value("animation-delay", "thing 0%");
+test_invalid_value("animation-delay", "thing 42%");
+test_invalid_value("animation-delay", "thing 100%");
+test_invalid_value("animation-delay", "thing 100px");
+test_invalid_value("animation-delay", "100% thing");
+
+test_shorthand_value('animation-delay', '1s 2s', {
+ 'animation-delay-start': '1s',
+ 'animation-delay-end': '2s',
+});
+
+test_shorthand_value('animation-delay', '1s', {
+ 'animation-delay-start': '1s',
+ 'animation-delay-end': '0s',
+});
+
+test_shorthand_value('animation-delay', '1s 2s, 3s 4s', {
+ 'animation-delay-start': '1s, 3s',
+ 'animation-delay-end': '2s, 4s',
+});
+
+test_shorthand_value('animation-delay', '1s 2s, 3s, 4s 5s', {
+ 'animation-delay-start': '1s, 3s, 4s',
+ 'animation-delay-end': '2s, 0s, 5s',
+});
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-computed.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-computed.tentative.html
new file mode 100644
index 0000000000..bfb89d0267
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-computed.tentative.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-delay-start">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+<div id="target"></div>
+<script>
+test_computed_value("animation-delay-start", "initial", "0s");
+test_computed_value("animation-delay-start", "-500ms", "-0.5s");
+test_computed_value("animation-delay-start", "calc(2 * 3s)", "6s");
+test_computed_value("animation-delay-start", "20s, 10s");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-invalid.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-invalid.tentative.html
new file mode 100644
index 0000000000..bff31f3789
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-invalid.tentative.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-delay-start">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_invalid_value("animation-delay-start", "infinite");
+test_invalid_value("animation-delay-start", "0");
+test_invalid_value("animation-delay-start", "1s 2s");
+test_invalid_value("animation-delay-start", "1s / 2s");
+test_invalid_value("animation-delay-start", "100px");
+test_invalid_value("animation-delay-start", "100%");
+
+test_invalid_value("animation-delay-start", "peek 50%");
+test_invalid_value("animation-delay-start", "50% contain");
+test_invalid_value("animation-delay-start", "50% cover");
+test_invalid_value("animation-delay-start", "50% entry");
+test_invalid_value("animation-delay-start", "50% enter");
+test_invalid_value("animation-delay-start", "50% exit");
+test_invalid_value("animation-delay-start", "contain contain");
+test_invalid_value("animation-delay-start", "auto");
+test_invalid_value("animation-delay-start", "none");
+test_invalid_value("animation-delay-start", "cover 50% enter 50%");
+test_invalid_value("animation-delay-start", "cover 100px");
+test_invalid_value("animation-delay-start", "cover");
+test_invalid_value("animation-delay-start", "contain");
+test_invalid_value("animation-delay-start", "enter");
+test_invalid_value("animation-delay-start", "exit");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-valid.tentative.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-valid.tentative.html
new file mode 100644
index 0000000000..f52286444e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-start-valid.tentative.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-delay-start">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_valid_value("animation-delay-start", "-5ms");
+test_valid_value("animation-delay-start", "0s");
+test_valid_value("animation-delay-start", "10s");
+test_valid_value("animation-delay-start", "20s, 10s");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-delay-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-valid.html
new file mode 100644
index 0000000000..5ff0416cc6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-delay-valid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-delay with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-delay">
+<meta name="assert" content="animation-delay supports the full grammar '<single-animation-play-state> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("animation-delay", "-5ms");
+test_valid_value("animation-delay", "0s");
+test_valid_value("animation-delay", "10s");
+test_valid_value("animation-delay", "20s, 10s");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-direction-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-direction-computed.html
new file mode 100644
index 0000000000..d99038b3ba
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-direction-computed.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animationDirection</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-direction">
+<meta name="assert" content="animation-direction computed value is as specified.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+test_computed_value("animation-direction", "normal, reverse, alternate, alternate-reverse");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-direction-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-direction-invalid.html
new file mode 100644
index 0000000000..5c96216d04
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-direction-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-direction with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-direction">
+<meta name="assert" content="animation-direction supports only the grammar '<single-animation-direction> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation-direction", "auto");
+test_invalid_value("animation-direction", "normal reverse");
+
+test_invalid_value("animation-direction", "reverse, initial");
+test_invalid_value("animation-direction", "initial, reverse");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-direction-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-direction-valid.html
new file mode 100644
index 0000000000..bcc9acc342
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-direction-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-direction with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-direction">
+<meta name="assert" content="animation-direction supports the full grammar '<single-animation-direction> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("animation-direction", "normal");
+test_valid_value("animation-direction", "reverse");
+test_valid_value("animation-direction", "alternate");
+test_valid_value("animation-direction", "alternate-reverse");
+test_valid_value("animation-direction", "normal, reverse, alternate, alternate-reverse");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-duration-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-duration-computed.html
new file mode 100644
index 0000000000..a8bc1600f8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-duration-computed.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animationDuration</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-duration">
+<meta name="assert" content="animation-duration converts to seconds.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+test_computed_value("animation-duration", "500ms", "0.5s");
+test_computed_value("animation-duration", "calc(2 * 3s)", "6s");
+test_computed_value("animation-duration", "20s, 10s");
+
+// https://github.com/w3c/csswg-drafts/issues/6530
+test_computed_value("animation-duration", "auto", "0s");
+test_computed_value("animation-duration", "auto, auto", "0s, 0s");
+
+// Test that the resolved value of the specified animation-duration
+// is as expected given some value for animation-timeline.
+function test_auto_duration(duration, timeline, expected) {
+ test((t) => {
+ t.add_cleanup(() => {
+ target.style = "";
+ });
+ target.style.animationDuration = duration;
+ target.style.animationTimeline = timeline;
+ assert_equals(expected, getComputedStyle(target).animationDuration);
+ }, `Resolved value of animation-duration:${duration} with animation-timeline:${timeline}`);
+}
+
+test_auto_duration("auto", "auto", "0s");
+test_auto_duration("auto", "auto, auto", "auto");
+test_auto_duration("auto", "--t", "auto");
+test_auto_duration("auto", "--t, --t2", "auto");
+test_auto_duration("auto", "none", "auto");
+test_auto_duration("auto", "scroll()", "auto");
+test_auto_duration("auto", "view()", "auto");
+test_auto_duration("0s", "auto", "0s");
+test_auto_duration("0s", "auto, auto", "0s");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-duration-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-duration-invalid.html
new file mode 100644
index 0000000000..bd8cf2adfe
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-duration-invalid.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-duration with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-duration">
+<meta name="assert" content="animation-duration supports only the grammar '<time> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation-duration", '-3s');
+test_invalid_value("animation-duration", '0');
+test_invalid_value("animation-duration", 'infinite');
+test_invalid_value("animation-duration", '1s 2s');
+
+test_invalid_value("animation-duration", 'initial, 1s');
+test_invalid_value("animation-duration", '1s, initial');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-duration-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-duration-valid.html
new file mode 100644
index 0000000000..e65a1a7072
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-duration-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-duration with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-duration">
+<meta name="assert" content="animation-duration supports the full grammar '<time> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("animation-duration", '3s');
+test_valid_value("animation-duration", '500ms');
+test_valid_value("animation-duration", '1s, 2s, 3s');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-computed.html
new file mode 100644
index 0000000000..f4083b3833
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-computed.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animationFillMode</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode">
+<meta name="assert" content="animation-fill-mode computed value is as specified.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+test_computed_value("animation-fill-mode", "none, forwards, backwards, both");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-invalid.html
new file mode 100644
index 0000000000..2a82f23546
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-fill-mode with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode">
+<meta name="assert" content="animation-fill-mode supports only the grammar '<single-animation-fill-mode> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation-fill-mode", "auto");
+test_invalid_value("animation-fill-mode", "forwards backwards");
+
+test_invalid_value("animation-fill-mode", "both, initial");
+test_invalid_value("animation-fill-mode", "initial, both");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-valid.html
new file mode 100644
index 0000000000..1f73a821d1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-fill-mode-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-fill-mode with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode">
+<meta name="assert" content="animation-fill-mode supports the full grammar '<single-animation-fill-mode> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("animation-fill-mode", "none");
+test_valid_value("animation-fill-mode", "forwards");
+test_valid_value("animation-fill-mode", "backwards");
+test_valid_value("animation-fill-mode", "both");
+test_valid_value("animation-fill-mode", "none, forwards, backwards, both");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-invalid.html
new file mode 100644
index 0000000000..dce5c29fdc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-invalid.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation">
+<meta name="assert" content="animation supports only the grammar '<single-animation> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation", "1s 2s 3s");
+test_invalid_value("animation", "-1s -2s");
+
+test_invalid_value("animation", "steps(1) steps(2)");
+
+test_invalid_value("animation", "1 2");
+
+test_invalid_value("animation", "reverse alternate alternate-reverse anim");
+
+test_invalid_value("animation", "both backwards forwards anim");
+
+test_invalid_value("animation", "paused running paused anim");
+
+test_invalid_value("animation", "anim1 timeline1 anim2");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-computed.html
new file mode 100644
index 0000000000..0ac53aa651
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-computed.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animationIterationCount</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count">
+<meta name="assert" content="animation-iteration-count computed value is as specified.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+test_computed_value("animation-iteration-count", "0, infinite, 3");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-invalid.html
new file mode 100644
index 0000000000..621340f7c3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-invalid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-iteration-count with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count">
+<meta name="assert" content="animation-iteration-count supports only the grammar '<single-animation-iteration-count> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation-iteration-count", "auto");
+test_invalid_value("animation-iteration-count", "-2");
+test_invalid_value("animation-iteration-count", "3 4");
+
+test_invalid_value("animation-iteration-count", "initial, 4");
+test_invalid_value("animation-iteration-count", "4, initial");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-valid.html
new file mode 100644
index 0000000000..be8a837989
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-iteration-count-valid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-iteration-count with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count">
+<meta name="assert" content="animation-iteration-count supports the full grammar '<single-animation-iteration-count> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("animation-iteration-count", "0");
+test_valid_value("animation-iteration-count", "3");
+test_valid_value("animation-iteration-count", "4.5");
+test_valid_value("animation-iteration-count", "infinite");
+
+test_valid_value("animation-iteration-count", "0, infinite, 3");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-name-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-name-computed.html
new file mode 100644
index 0000000000..f05dd8b013
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-name-computed.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animationName</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-name">
+<meta name="assert" content="animation-name computed value is as specified.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+test_computed_value("animation-name", 'none');
+
+test_computed_value("animation-name", 'foo');
+test_computed_value("animation-name", 'Both');
+test_computed_value("animation-name", 'ease-in');
+test_computed_value("animation-name", 'infinite');
+test_computed_value("animation-name", 'paused');
+test_computed_value("animation-name", 'first, second, third');
+test_computed_value("animation-name", '"something"', ["something", '"something"']);
+
+// TODO: Test more strings, after https://github.com/w3c/csswg-drafts/issues/2435
+// is resolved.
+// Examples that need testing either here or in animation-name-invalid.html :
+// '"Initial"', '"initial"', '"None"', '"Default"', '" x "', "1", '" "', '""',
+// '"multi word string"', '"---\\22---"'
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-name-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-name-invalid.html
new file mode 100644
index 0000000000..739641a51d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-name-invalid.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-name with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-name">
+<meta name="assert" content="animation-name supports only the grammar '[ none | <keyframes-name> ]#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation-name", '12');
+test_invalid_value("animation-name", 'one two');
+
+test_invalid_value("animation-name", 'one, initial');
+test_invalid_value("animation-name", 'one, inherit');
+test_invalid_value("animation-name", 'one, unset');
+test_invalid_value("animation-name", 'default, two');
+test_invalid_value("animation-name", 'revert, three');
+test_invalid_value("animation-name", 'revert-layer, four');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-name-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-name-valid.html
new file mode 100644
index 0000000000..1906f9bdb7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-name-valid.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-name with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-name">
+<meta name="assert" content="animation-name supports the full grammar '[ none | <keyframes-name> ]#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("animation-name", 'NONE', 'none');
+
+test_valid_value("animation-name", 'foo');
+test_valid_value("animation-name", 'Both');
+test_valid_value("animation-name", 'ease-in');
+test_valid_value("animation-name", 'infinite');
+test_valid_value("animation-name", 'paused');
+test_valid_value("animation-name", 'first, second, third');
+
+test_valid_value("animation-name", '"string"', ['"string"', "string"]);
+test_valid_value("animation-name", '"multi word string"', ['"multi word string"', "multi\\ word\\ string"]);
+test_valid_value("animation-name", '"initial"');
+test_valid_value("animation-name", '"---\\22---"', ['\"---\\\"---\"', '---\\\"---']);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-computed.html
new file mode 100644
index 0000000000..c3ff2aa95f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-computed.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: getComputedStyle().animationPlayState</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-play-state">
+<meta name="assert" content="animation-play-state computed value is as specified.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+</head>
+<body>
+<div id="target"></div>
+<script>
+test_computed_value("animation-play-state", "running, paused");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-invalid.html
new file mode 100644
index 0000000000..91a6f01737
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-play-state with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-play-state">
+<meta name="assert" content="animation-play-state supports only the grammar '<single-animation-play-state> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("animation-play-state", "auto");
+test_invalid_value("animation-play-state", "paused running");
+
+test_invalid_value("animation-play-state", "paused, initial");
+test_invalid_value("animation-play-state", "initial, paused");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-valid.html
new file mode 100644
index 0000000000..ce6d053ec2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-play-state-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation-play-state with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation-play-state">
+<meta name="assert" content="animation-play-state supports the full grammar '<single-animation-play-state> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("animation-play-state", "running");
+test_valid_value("animation-play-state", "paused");
+test_valid_value("animation-play-state", "running, paused");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-computed.html
new file mode 100644
index 0000000000..4c96ee81b1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-computed.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-range">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+<div id="target" style="font-size:10px;"></div>
+<script>
+test_computed_value("animation-range-end", "initial", "normal");
+test_computed_value("animation-range-end", "normal");
+test_computed_value("animation-range-end", "cover 0%");
+test_computed_value("animation-range-end", "cover 100%", "cover");
+test_computed_value("animation-range-end", "COVER 0%", "cover 0%");
+test_computed_value("animation-range-end", "COVER 100%", "cover");
+test_computed_value("animation-range-end", "cover 120%");
+test_computed_value("animation-range-end", "0", "0px");
+test_computed_value("animation-range-end", "120%");
+test_computed_value("animation-range-end", "120px");
+test_computed_value("animation-range-end", "cover 42%");
+test_computed_value("animation-range-end", "cover -42%");
+test_computed_value("animation-range-end", "contain 42%");
+test_computed_value("animation-range-end", "exit 42%");
+test_computed_value("animation-range-end", "exit calc(41% + 1%)", "exit 42%");
+test_computed_value("animation-range-end", "exit-crossing 42%");
+test_computed_value("animation-range-end", "exit 1%, cover 2%, contain 0%");
+test_computed_value("animation-range-end", "exit 1%, cover 2%, contain 100%", "exit 1%, cover 2%, contain");
+test_computed_value("animation-range-end", "entry 42px");
+test_computed_value("animation-range-end", "entry-crossing 42px");
+
+test_computed_value("animation-range-end", "contain calc(10% + 10px)");
+test_computed_value("animation-range-end", "entry 1em", "entry 10px");
+test_computed_value("animation-range-end", "exit calc(1em + 10px)", "exit 20px");
+test_computed_value("animation-range-end", "cover");
+test_computed_value("animation-range-end", "contain");
+test_computed_value("animation-range-end", "entry");
+test_computed_value("animation-range-end", "exit");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-invalid.html
new file mode 100644
index 0000000000..459cdfd0cd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-range">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_invalid_value("animation-range-end", "infinite");
+test_invalid_value("animation-range-end", "1s 2s");
+test_invalid_value("animation-range-end", "1s / 2s");
+
+test_invalid_value("animation-range-end", "peek 50%");
+test_invalid_value("animation-range-end", "50% contain");
+test_invalid_value("animation-range-end", "50% cover");
+test_invalid_value("animation-range-end", "50% entry");
+test_invalid_value("animation-range-end", "50% enter");
+test_invalid_value("animation-range-end", "50% exit");
+test_invalid_value("animation-range-end", "contain contain");
+test_invalid_value("animation-range-end", "none");
+test_invalid_value("animation-range-end", "cover 50% enter 50%");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-valid.html
new file mode 100644
index 0000000000..c394c475fc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-range-end-valid.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-range">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+// https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
+test_valid_value("animation-range-end", "normal");
+test_valid_value("animation-range-end", "cover 0%");
+test_valid_value("animation-range-end", "cover 100%", "cover");
+test_valid_value("animation-range-end", "cover 120%");
+test_valid_value("animation-range-end", "cover 42%");
+test_valid_value("animation-range-end", "0", "0px");
+test_valid_value("animation-range-end", "120%");
+test_valid_value("animation-range-end", "120px");
+test_valid_value("animation-range-end", "cover -42%");
+test_valid_value("animation-range-end", "contain 42%");
+test_valid_value("animation-range-end", "exit 42%");
+test_valid_value("animation-range-end", "exit 1%, cover 2%, contain 0%");
+test_valid_value("animation-range-end", "exit 1%, cover 2%, contain 100%", "exit 1%, cover 2%, contain");
+test_valid_value("animation-range-end", "exit-crossing 42%");
+test_valid_value("animation-range-end", "entry 42px");
+test_valid_value("animation-range-end", "entry-crossing 42px");
+test_valid_value("animation-range-end", "contain calc(10px + 10%)", "contain calc(10% + 10px)");
+test_valid_value("animation-range-end", "entry 1em");
+test_valid_value("animation-range-end", "exit calc(1em + 10px)");
+test_valid_value("animation-range-end", "entry 42%");
+test_valid_value("animation-range-end", "cover");
+test_valid_value("animation-range-end", "contain");
+test_valid_value("animation-range-end", "entry");
+test_valid_value("animation-range-end", "exit");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-range-shorthand.html b/testing/web-platform/tests/css/css-animations/parsing/animation-range-shorthand.html
new file mode 100644
index 0000000000..8acf0b1c55
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-range-shorthand.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<title>animation-range shorthand</title>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-range">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/shorthand-testcommon.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+<div id="target" style="font-size:10px"></div>
+<script>
+test_valid_value("animation-range", "normal");
+test_valid_value("animation-range", "normal normal", "normal");
+test_valid_value("animation-range", "cover");
+test_valid_value("animation-range", "contain");
+test_valid_value("animation-range", "entry");
+test_valid_value("animation-range", "entry-crossing");
+test_valid_value("animation-range", "exit");
+test_valid_value("animation-range", "exit-crossing");
+test_valid_value("animation-range", "entry, exit");
+
+test_valid_value("animation-range", "entry 0% entry 100%", "entry");
+test_valid_value("animation-range", "entry-crossing 0% entry-crossing 100%",
+ "entry-crossing");
+test_valid_value("animation-range", "exit 0% exit 100%", "exit");
+test_valid_value("animation-range", "exit-crossing 0% exit-crossing 100%",
+ "exit-crossing");
+test_valid_value("animation-range", "cover 0% cover 100%", "cover");
+test_valid_value("animation-range", "contain 0% contain 100%", "contain");
+test_valid_value("animation-range",
+ "entry calc(10% - 10%) entry calc(50% + 50%)", "entry");
+test_valid_value("animation-range", "cover 50%");
+test_valid_value("animation-range", "contain 50%");
+test_valid_value("animation-range", "entry 50%");
+test_valid_value("animation-range", "entry-crossing 50%");
+test_valid_value("animation-range", "exit 50%");
+test_valid_value("animation-range", "exit-crossing 50%");
+test_valid_value("animation-range", "entry 50px exit 100px");
+test_valid_value("animation-range", "exit calc(10% + 50px)");
+
+test_valid_value("animation-range", "entry 50% exit 50%");
+test_valid_value("animation-range",
+ "cover 50% entry 50%, contain 50% exit 50%");
+test_valid_value("animation-range", "50% exit 50%");
+test_valid_value("animation-range", "normal 100px");
+test_valid_value("animation-range", "100px");
+test_valid_value("animation-range", "100px normal", "100px");
+test_valid_value("animation-range", "10% normal", "10%");
+
+test_computed_value("animation-range", "normal");
+test_computed_value("animation-range", "normal normal", "normal");
+test_computed_value("animation-range", "cover");
+test_computed_value("animation-range", "contain");
+test_computed_value("animation-range", "entry");
+test_computed_value("animation-range", "entry-crossing");
+test_computed_value("animation-range", "exit");
+test_computed_value("animation-range", "exit-crossing");
+test_computed_value("animation-range", "entry, exit");
+
+test_computed_value("animation-range", "entry 0% entry 100%", "entry");
+test_computed_value("animation-range", "entry-crossing 0% entry-crossing 100%",
+ "entry-crossing");
+test_computed_value("animation-range", "exit 0% exit 100%", "exit");
+test_computed_value("animation-range", "exit-crossing 0% exit-crossing 100%",
+ "exit-crossing");
+test_computed_value("animation-range", "cover 0% cover 100%", "cover");
+test_computed_value("animation-range", "contain 0% contain 100%", "contain");
+test_computed_value("animation-range",
+ "entry calc(10% - 10%) entry calc(50% + 50%)", "entry");
+test_computed_value("animation-range", "cover 50%");
+test_computed_value("animation-range", "contain 50%");
+test_computed_value("animation-range", "entry 50%");
+test_computed_value("animation-range", "entry-crossing 50%");
+test_computed_value("animation-range", "exit 50%");
+test_computed_value("animation-range", "exit-crossing 50%");
+test_computed_value("animation-range", "entry 50px exit 100px");
+test_computed_value("animation-range", "exit calc(10% + 50px)");
+
+test_computed_value("animation-range", "entry 50% exit 50%");
+test_computed_value("animation-range",
+ "cover 50% entry 50%, contain 50% exit 50%");
+
+test_computed_value("animation-range", "entry 10em exit 20em", "entry 100px exit 200px");
+test_computed_value("animation-range", "10em exit 20em", "100px exit 200px");
+test_computed_value("animation-range", "normal 100px");
+test_computed_value("animation-range", "100px");
+test_computed_value("animation-range", "100px normal", "100px");
+test_computed_value("animation-range", "10% normal", "10%");
+
+test_invalid_value("animation-range", "entry 50% 0s", "entry 50%");
+test_invalid_value("animation-range", "0s entry 50%");
+test_invalid_value("animation-range", "1s");
+test_invalid_value("animation-range", "-1s");
+test_invalid_value("animation-range", "1s 2s");
+test_invalid_value("animation-range", "1s, 2s");
+test_invalid_value("animation-range", "1s 2s, 3s");
+test_invalid_value("animation-range", "1s, 2s 3s");
+test_invalid_value("animation-range", "1s, 2s, 3s");
+test_invalid_value("animation-range", "1s 2s 3s");
+test_invalid_value("animation-range", "0s, 1s 2s 3s");
+test_invalid_value("animation-range", "1s / 2s");
+test_invalid_value("animation-range", "1s, 2px");
+test_invalid_value("animation-range", "#ff0000");
+test_invalid_value("animation-range", "red");
+test_invalid_value("animation-range", "thing");
+test_invalid_value("animation-range", "thing 0%");
+test_invalid_value("animation-range", "thing 42%");
+test_invalid_value("animation-range", "thing 100%");
+test_invalid_value("animation-range", "thing 100px");
+test_invalid_value("animation-range", "100% thing");
+
+test_shorthand_value('animation-range', 'normal', {
+ 'animation-range-start': 'normal',
+ 'animation-range-end': 'normal',
+});
+
+test_shorthand_value('animation-range', 'normal normal', {
+ 'animation-range-start': 'normal',
+ 'animation-range-end': 'normal',
+});
+
+test_shorthand_value('animation-range', 'normal entry 100%', {
+ 'animation-range-start': 'normal',
+ 'animation-range-end': 'entry',
+});
+
+test_shorthand_value('animation-range', 'normal entry 10%', {
+ 'animation-range-start': 'normal',
+ 'animation-range-end': 'entry 10%',
+});
+
+test_shorthand_value('animation-range', 'cover', {
+ 'animation-range-start': 'cover',
+ 'animation-range-end': 'cover',
+});
+
+test_shorthand_value('animation-range', 'contain', {
+ 'animation-range-start': 'contain',
+ 'animation-range-end': 'contain',
+});
+
+test_shorthand_value('animation-range', 'contain 100% contain 0%', {
+ 'animation-range-start': 'contain 100%',
+ 'animation-range-end': 'contain 0%',
+});
+
+test_shorthand_value('animation-range', 'entry 10% exit 20%', {
+ 'animation-range-start': 'entry 10%',
+ 'animation-range-end': 'exit 20%',
+});
+
+test_shorthand_value('animation-range', 'entry calc(10% + 10px) exit 20px', {
+ 'animation-range-start': 'entry calc(10% + 10px)',
+ 'animation-range-end': 'exit 20px',
+});
+
+test_shorthand_value('animation-range', 'entry, exit', {
+ 'animation-range-start': 'entry, exit',
+ 'animation-range-end': 'entry, exit',
+});
+
+test_shorthand_value('animation-range', 'entry 0%, exit', {
+ 'animation-range-start': 'entry, exit',
+ 'animation-range-end': 'entry, exit',
+});
+
+test_shorthand_value('animation-range', 'exit calc(10% + 50px)', {
+ 'animation-range-start': 'exit calc(10% + 50px)',
+ 'animation-range-end': 'exit',
+});
+test_shorthand_value('animation-range', '100px', {
+ 'animation-range-start': '100px',
+ 'animation-range-end': 'normal',
+});
+test_shorthand_value('animation-range', '10%', {
+ 'animation-range-start': '10%',
+ 'animation-range-end': 'normal',
+});
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-computed.html b/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-computed.html
new file mode 100644
index 0000000000..044aea2ca6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-computed.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-range">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+<div id="target" style="font-size:10px;"></div>
+<script>
+test_computed_value("animation-range-start", "initial", "normal");
+test_computed_value("animation-range-start", "normal");
+test_computed_value("animation-range-start", "cover 0%", "cover");
+test_computed_value("animation-range-start", "cover 100%");
+test_computed_value("animation-range-start", "COVER 0%", "cover");
+test_computed_value("animation-range-start", "COVER 100%", "cover 100%");
+test_computed_value("animation-range-start", "cover 120%");
+test_computed_value("animation-range-start", "cover 42%");
+test_computed_value("animation-range-start", "0", "0px");
+test_computed_value("animation-range-start", "120%");
+test_computed_value("animation-range-start", "120px");
+test_computed_value("animation-range-start", "cover -42%");
+test_computed_value("animation-range-start", "contain 42%");
+test_computed_value("animation-range-start", "exit 42%");
+test_computed_value("animation-range-start", "exit calc(41% + 1%)", "exit 42%");
+test_computed_value("animation-range-start", "exit 1%, cover 2%, contain 0%", "exit 1%, cover 2%, contain");
+test_computed_value("animation-range-start", "exit 1%, cover 2%, contain 100%");
+test_computed_value("animation-range-start", "exit-crossing 42%");
+test_computed_value("animation-range-start", "entry 42px");
+test_computed_value("animation-range-start", "entry-crossing 42px");
+test_computed_value("animation-range-start", "contain calc(10% + 10px)");
+test_computed_value("animation-range-start", "entry 1em", "entry 10px");
+test_computed_value("animation-range-start", "exit calc(1em + 10px)", "exit 20px");
+test_computed_value("animation-range-start", "cover");
+test_computed_value("animation-range-start", "contain");
+test_computed_value("animation-range-start", "entry");
+test_computed_value("animation-range-start", "exit");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-invalid.html
new file mode 100644
index 0000000000..32357d8bd4
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-invalid.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-range">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_invalid_value("animation-range-start", "peek 50%");
+test_invalid_value("animation-range-start", "50% contain");
+test_invalid_value("animation-range-start", "50% cover");
+test_invalid_value("animation-range-start", "50% entry");
+test_invalid_value("animation-range-start", "50% enter");
+test_invalid_value("animation-range-start", "50% exit");
+test_invalid_value("animation-range-start", "contain contain");
+test_invalid_value("animation-range-start", "none");
+test_invalid_value("animation-range-start", "cover 50% enter 50%");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-valid.html
new file mode 100644
index 0000000000..3ca885862e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-range-start-valid.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#propdef-animation-range">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+// https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
+test_valid_value("animation-range-start", "normal");
+test_valid_value("animation-range-start", "cover 0%", "cover");
+test_valid_value("animation-range-start", "cover 100%");
+test_valid_value("animation-range-start", "cover 120%");
+test_valid_value("animation-range-start", "cover 42%");
+test_valid_value("animation-range-start", "0", "0px");
+test_valid_value("animation-range-start", "120%");
+test_valid_value("animation-range-start", "120px");
+test_valid_value("animation-range-start", "cover -42%");
+test_valid_value("animation-range-start", "contain 42%");
+test_valid_value("animation-range-start", "exit 42%");
+test_valid_value("animation-range-start", "exit 1%, cover 2%, contain 0%", "exit 1%, cover 2%, contain");
+test_valid_value("animation-range-start", "exit 1%, cover 2%, contain 100%");
+test_valid_value("animation-range-start", "exit-crossing 42%");
+test_valid_value("animation-range-start", "entry 42px");
+test_valid_value("animation-range-start", "entry-crossing 42px");
+test_valid_value("animation-range-start", "contain calc(10px + 10%)", "contain calc(10% + 10px)");
+test_valid_value("animation-range-start", "entry 1em");
+test_valid_value("animation-range-start", "exit calc(1em + 10px)");
+test_valid_value("animation-range-start", "entry 42%");
+test_valid_value("animation-range-start", "cover");
+test_valid_value("animation-range-start", "contain");
+test_valid_value("animation-range-start", "entry");
+test_valid_value("animation-range-start", "exit");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-shorthand.html b/testing/web-platform/tests/css/css-animations/parsing/animation-shorthand.html
new file mode 100644
index 0000000000..0856ab24a0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-shorthand.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: animation sets longhands</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation">
+<meta name="assert" content="animation supports the full grammar '<single-animation> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/shorthand-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_shorthand_value('animation', 'anim paused both reverse 4 1s -3s cubic-bezier(0, -2, 1, 3)', {
+ 'animation-duration': '1s',
+ 'animation-timing-function': 'cubic-bezier(0, -2, 1, 3)',
+ 'animation-delay': '-3s',
+ 'animation-iteration-count': '4',
+ 'animation-direction': 'reverse',
+ 'animation-fill-mode': 'both',
+ 'animation-play-state': 'paused',
+ 'animation-name': 'anim',
+ 'animation-timeline': 'auto',
+ 'animation-range-start': 'normal',
+ 'animation-range-end': 'normal',
+});
+
+test_shorthand_value('animation', 'anim paused both reverse, 4 1s -3s cubic-bezier(0, -2, 1, 3)', {
+ 'animation-duration': 'auto, 1s',
+ 'animation-timing-function': 'ease, cubic-bezier(0, -2, 1, 3)',
+ 'animation-delay': '0s, -3s',
+ 'animation-iteration-count': '1, 4',
+ 'animation-direction': 'reverse, normal',
+ 'animation-fill-mode': 'both, none',
+ 'animation-play-state': 'paused, running',
+ 'animation-name': 'anim, none',
+ 'animation-timeline': 'auto',
+ 'animation-range-start': 'normal',
+ 'animation-range-end': 'normal',
+});
+
+test_shorthand_value('animation', '4 1s -3s cubic-bezier(0, -2, 1, 3), anim paused both reverse', {
+ 'animation-duration': '1s, auto',
+ 'animation-timing-function': 'cubic-bezier(0, -2, 1, 3), ease',
+ 'animation-delay': '-3s, 0s',
+ 'animation-iteration-count': '4, 1',
+ 'animation-direction': 'normal, reverse',
+ 'animation-fill-mode': 'none, both',
+ 'animation-play-state': 'running, paused',
+ 'animation-name': 'none, anim',
+ 'animation-timeline': 'auto',
+ 'animation-range-start': 'normal',
+ 'animation-range-end': 'normal',
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/animation-valid.html b/testing/web-platform/tests/css/css-animations/parsing/animation-valid.html
new file mode 100644
index 0000000000..0c5d5713e3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/animation-valid.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: parsing animation with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#propdef-animation">
+<meta name="assert" content="animation supports the full grammar '<single-animation> #'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// <single-animation> = <time> || <easing-function> || <time> ||
+// <single-animation-iteration-count> || <single-animation-direction> ||
+// <single-animation-fill-mode> || <single-animation-play-state> ||
+// [ none | <keyframes-name> ]
+test_valid_value("animation", "1s", ["1s", "1s ease 0s 1 normal none running none"]);
+test_valid_value("animation", "cubic-bezier(0, -2, 1, 3)", ["cubic-bezier(0, -2, 1, 3)", "auto cubic-bezier(0, -2, 1, 3) 0s 1 normal none running none"]);
+test_valid_value("animation", "1s -3s", ["1s -3s", "1s ease -3s 1 normal none running none"]);
+test_valid_value("animation", "4", ["4", "auto ease 0s 4 normal none running none"]);
+test_valid_value("animation", "reverse", ["reverse", "auto ease 0s 1 reverse none running none"]);
+test_valid_value("animation", "both", ["both", "auto ease 0s 1 normal both running none"]);
+test_valid_value("animation", "paused", ["paused", "auto ease 0s 1 normal none paused none"]);
+test_valid_value("animation", "none", ["auto", "none", "auto ease 0s 1 normal none running none"]);
+test_valid_value("animation", "anim", ["anim", "auto ease 0s 1 normal none running anim"]);
+
+test_valid_value("animation", "anim paused both reverse 4 1s -3s cubic-bezier(0, -2, 1, 3)",
+ "1s cubic-bezier(0, -2, 1, 3) -3s 4 reverse both paused anim");
+
+test_valid_value("animation", "anim paused both reverse, 4 1s -3s cubic-bezier(0, -2, 1, 3)",
+ ["reverse both paused anim, 1s cubic-bezier(0, -2, 1, 3) -3s 4", "auto ease 0s 1 reverse both paused anim, 1s cubic-bezier(0, -2, 1, 3) -3s 4 normal none running none"]);
+
+// TODO: Add test with a single negative time.
+// TODO: Add test with a single timing-function keyword.
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/keyframes-allowed-properties.html b/testing/web-platform/tests/css/css-animations/parsing/keyframes-allowed-properties.html
new file mode 100644
index 0000000000..f682920b35
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/keyframes-allowed-properties.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Tests which properties are allowed in @keyframes</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#typedef-keyframe-block">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style id=sheet>
+@keyframes foo {
+ from {
+ /* Non-animation properties are allowed */
+ margin-top: 10px;
+ /* animation-timing-function is specially allowed */
+ animation-timing-function: ease;
+ /* All other animation properties are not allowed */
+ animation-name: none;
+ animation-duration: 1s;
+ animation-iteration-count: 1;
+ animation-direction: normal;
+ animation-play-state: running;
+ animation-delay: 0s;
+ animation-fill-mode: none;
+ /* The animation shorthand is also not allowed */
+ animation: bar 1s infinite;
+ }
+}
+</style>
+<script>
+test(() => {
+ const keyframe = sheet.sheet.cssRules[0].cssRules[0];
+ const style = keyframe.style;
+ assert_equals(style.length, 2);
+ assert_equals(style.marginTop, '10px');
+ assert_equals(style.animationTimingFunction, 'ease');
+}, '@keyframes allows all non-animation properties and animation-timing-function');
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/parsing/keyframes-name-invalid.html b/testing/web-platform/tests/css/css-animations/parsing/keyframes-name-invalid.html
new file mode 100644
index 0000000000..f14dff3e2c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/keyframes-name-invalid.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>CSS Animations: parsing @keyframes name with invalid values</title>
+ <link rel="author" title="yisibl(一丝)" href="https://github.com/yisibl"/>
+ <link rel="help" href="https://drafts.csswg.org/css-animations/#typedef-keyframes-name">
+ <meta name="assert" content="@keyframes name supports the full grammar '<custom-ident> | <string>'.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+ <div>
+ <main id="main"></main>
+ </div>
+ <script>
+ test_keyframes_name_invalid('none');
+
+ // The CSS-wide keywords are not valid <custom-ident>s. The default keyword is reserved and is also not a valid <custom-ident>.
+ // Spec: https://drafts.csswg.org/css-values-4/#identifier-value
+ test_keyframes_name_invalid('default');
+ test_keyframes_name_invalid('initial');
+ test_keyframes_name_invalid('inherit');
+ test_keyframes_name_invalid('unset');
+ test_keyframes_name_invalid('revert');
+ test_keyframes_name_invalid('revert-layer');
+
+ test_keyframes_name_invalid('12');
+ test_keyframes_name_invalid('-12');
+ test_keyframes_name_invalid('12foo');
+ test_keyframes_name_invalid('foo.bar');
+ test_keyframes_name_invalid('one two');
+ test_keyframes_name_invalid('one, two');
+
+ test_keyframes_name_invalid('one, initial');
+ test_keyframes_name_invalid('one, inherit');
+ test_keyframes_name_invalid('one, unset');
+ test_keyframes_name_invalid('default, two');
+ test_keyframes_name_invalid('revert, three');
+ test_keyframes_name_invalid('revert-layer, four');
+ // TODO: https://bugs.chromium.org/p/chromium/issues/detail?id=1342609
+ // test_keyframes_name_invalid('--foo');
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-animations/parsing/keyframes-name-valid.html b/testing/web-platform/tests/css/css-animations/parsing/keyframes-name-valid.html
new file mode 100644
index 0000000000..f44540e924
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/parsing/keyframes-name-valid.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>CSS Animations: parsing @keyframes name with valid values</title>
+ <link rel="author" title="yisibl(一丝)" href="https://github.com/yisibl"/>
+ <link rel="help" href="https://drafts.csswg.org/css-animations/#typedef-keyframes-name">
+ <meta name="assert" content="@keyframes name supports the full grammar '<custom-ident> | <string>'.">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+ <div>
+ <main id="main"></main>
+ </div>
+ <script>
+ // Test <custom-ident>
+ test_keyframes_name_valid(' foo ');
+ test_keyframes_name_valid(' foo');
+ test_keyframes_name_valid('-foo');
+ test_keyframes_name_valid('_bar');
+ test_keyframes_name_valid('__bar');
+ test_keyframes_name_valid('__bar__');
+ test_keyframes_name_valid('ease-out');
+ test_keyframes_name_valid('example');
+ test_keyframes_name_valid('EXAMPLE');
+
+ test_keyframes_name_valid('not');
+ test_keyframes_name_valid('and');
+ test_keyframes_name_valid('all');
+ test_keyframes_name_valid('or');
+
+ // <custom-ident> may disable the `auto/normal` keywords in the future
+ // https://github.com/w3c/csswg-drafts/issues/7431
+ test_keyframes_name_valid('auto');
+ test_keyframes_name_valid('normal');
+
+ // Test <string>
+ test_keyframes_name_valid('" foo "');
+ test_keyframes_name_valid('" foo"');
+ test_keyframes_name_valid('"-foo"');
+ test_keyframes_name_valid('"_bar"');
+ test_keyframes_name_valid('"__bar"');
+ test_keyframes_name_valid('"__bar__"');
+ test_keyframes_name_valid('"ease-out"');
+ test_keyframes_name_valid('"example"');
+ test_keyframes_name_valid('"EXAMPLE"');
+
+ test_keyframes_name_valid('"one two"');
+ test_keyframes_name_valid('"one, two"');
+
+ test_keyframes_name_valid('"not"');
+ test_keyframes_name_valid('"and"');
+ test_keyframes_name_valid('"all"');
+ test_keyframes_name_valid('"or"');
+
+ test_keyframes_name_valid('"auto"');
+ test_keyframes_name_valid('"normal"');
+ test_keyframes_name_valid('"none"');
+
+ test_keyframes_name_valid('"default"');
+ test_keyframes_name_valid('"initial"');
+ test_keyframes_name_valid('"inherit"');
+ test_keyframes_name_valid('"unset"');
+ test_keyframes_name_valid('"revert"');
+ test_keyframes_name_valid('"revert-layer"');
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-animations/pending-style-changes-001.html b/testing/web-platform/tests/css/css-animations/pending-style-changes-001.html
new file mode 100644
index 0000000000..fb74d7fa7d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/pending-style-changes-001.html
@@ -0,0 +1,34 @@
+<meta charset=utf-8>
+<title>CSS Animations Test: requirement on pending style changes - getAnimations</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#requirements-on-pending-style-changes">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+@keyframes anim {}
+
+.animate {
+ animation: anim 10s;
+}
+</style>
+
+<div id="target"></div>
+
+<script>
+test (t => {
+ assert_equals(target.getAnimations().length, 0, 'Test precondition.');
+ target.classList.add('animate');
+ assert_equals(target.getAnimations().length, 1,
+ 'target.getAnimations() should include the CSS animation after animate class added.');
+ target.classList.remove('animate');
+}, 'Animatable::getAnimations() should be able to see a style-created CSS animation immediately');
+
+test(t => {
+ assert_equals(document.getAnimations().length, 0, 'Test precondition.');
+ target.classList.add('animate');
+ assert_equals(document.getAnimations().length, 1,
+ 'document.getAnimations() should include the CSS animation after animate class added.');
+ target.classList.remove('animate');
+}, 'Document::getAnimations() should be able to see a style-created CSS animation immediately');
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/responsive/column-rule-color-001.html b/testing/web-platform/tests/css/css-animations/responsive/column-rule-color-001.html
new file mode 100644
index 0000000000..9953db4354
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/responsive/column-rule-color-001.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: column-rule-color animations respond to style changes</title>
+<link rel="help" href="https://drafts.csswg.org/css-multicol-1/#crc">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ .paused {
+ animation-duration: 4s;
+ animation-timing-function: linear;
+ animation-delay: -2s;
+ animation-play-state: paused;
+ }
+ #container {
+ color: rgb(80, 0, 0);
+ }
+ #first {
+ animation-name: first-anim;
+ color: rgb(60, 0, 0);
+ }
+ #second {
+ animation-name: second-anim;
+ }
+ @keyframes first-anim {
+ from { column-rule-color: currentColor; }
+ to { column-rule-color: rgb(0, 60, 0); }
+ }
+ @keyframes second-anim {
+ from { column-rule-color: inherit; }
+ to { column-rule-color: rgb(0, 0, 80); }
+ }
+</style>
+</head>
+<body>
+<div id="container">
+ <div id="first" class="paused"></div>
+ <div id="second" class="paused"></div>
+</div>
+<script>
+'use strict';
+var container = document.getElementById('container');
+
+test(() => {
+ const first = document.getElementById('first');
+ assert_equals(getComputedStyle(first).columnRuleColor, 'rgb(30, 30, 0)');
+ first.style.color = 'rgb(0, 0, 60)';
+ assert_equals(getComputedStyle(first).columnRuleColor, 'rgb(0, 30, 30)');
+}, 'column-rule-color responds to currentColor changes');
+
+test(() => {
+ const container = document.getElementById('container');
+ const second = document.getElementById('second');
+ assert_equals(getComputedStyle(second).columnRuleColor, 'rgb(40, 0, 40)');
+ container.style.columnRuleColor = 'rgb(0, 80, 0)';
+ assert_equals(getComputedStyle(second).columnRuleColor, 'rgb(0, 40, 40)');
+}, 'column-rule-color responds to inherited changes');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/responsive/column-width-001.html b/testing/web-platform/tests/css/css-animations/responsive/column-width-001.html
new file mode 100644
index 0000000000..272b9bc5a2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/responsive/column-width-001.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: column-width animations respond to style changes</title>
+<link rel="help" href="https://drafts.csswg.org/css-multicol-1/#cw">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ .paused {
+ animation-duration: 4s;
+ animation-timing-function: linear;
+ animation-delay: -2s;
+ animation-play-state: paused;
+ }
+ #container {
+ column-width: 40px;
+ font-size: 10px;
+ }
+ #first {
+ animation-name: first-anim;
+ }
+ #second {
+ animation-name: second-anim;
+ }
+ #third {
+ animation-name: third-anim;
+ }
+ @keyframes first-anim {
+ from { column-width: 3em; }
+ to { column-width: 5em; }
+ }
+ @keyframes second-anim {
+ from { column-width: 40px; }
+ to { column-width: calc(40px - 2em); }
+ }
+ @keyframes third-anim {
+ from { column-width: 20px; }
+ to { column-width: inherit; }
+ }
+</style>
+</head>
+<body>
+<div id="container">
+ <div id="first" class="paused"></div>
+ <div id="second" class="paused"></div>
+ <div id="third" class="paused"></div>
+</div>
+<script>
+'use strict';
+var container = document.getElementById('container');
+
+test(() => {
+ const first = document.getElementById('first');
+ assert_equals(getComputedStyle(first).columnWidth, '40px');
+ first.style.fontSize = '20px';
+ assert_equals(getComputedStyle(first).columnWidth, '80px');
+}, 'column-width responds to font-size changes');
+
+test(() => {
+ const second = document.getElementById('second');
+ assert_equals(getComputedStyle(second).columnWidth, '30px');
+ second.style.fontSize = '90px';
+ assert_equals(getComputedStyle(second).columnWidth, '20px');
+}, 'column-width clamps to 0px');
+
+test(() => {
+ const container = document.getElementById('container');
+ const third = document.getElementById('third');
+ assert_equals(getComputedStyle(third).columnWidth, '30px');
+ container.style.columnWidth = 'auto';
+ assert_equals(getComputedStyle(third).columnWidth, 'auto');
+}, 'column-width responds to inherited changes');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/responsive/line-height.html b/testing/web-platform/tests/css/css-animations/responsive/line-height.html
new file mode 100644
index 0000000000..63097ac808
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/responsive/line-height.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Animations: line-height animations respond to style changes</title>
+<link rel="help" href="https://drafts.csswg.org/css-inline/#line-height-property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #target {
+ animation-name: line-height-animation;
+ animation-duration: 4s;
+ animation-timing-function: linear;
+ animation-delay: -2s;
+ animation-play-state: paused;
+ }
+ @keyframes line-height-animation {
+ from { line-height: inherit; }
+ to { line-height: 20px; }
+ }
+</style>
+</head>
+<body>
+<div id="container">
+ <div id="target"></div>
+</div>
+<script>
+'use strict';
+const container = document.getElementById('container');
+const target = document.getElementById('target');
+
+test(() => {
+ container.style.lineHeight = '100px';
+ assert_equals(getComputedStyle(target).lineHeight, '60px');
+
+ container.style.lineHeight = '50px';
+ assert_equals(getComputedStyle(target).lineHeight, '35px');
+
+ container.style.lineHeight = '100px';
+ assert_equals(getComputedStyle(target).lineHeight, '60px');
+}, 'line-height responds to inherited changes');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/sample-on-last-keyframe.html b/testing/web-platform/tests/css/css-animations/sample-on-last-keyframe.html
new file mode 100644
index 0000000000..d9dde2e87b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/sample-on-last-keyframe.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Sample on last keyframe</title>
+ <link rel="help" href="https://www.w3.org/TR/css-animations-1/#animation-direction">
+ <style type="text/css" media="screen">
+ #test {
+ animation: test 1s reverse paused;
+ }
+ @keyframes test {
+ from { opacity: 0; }
+ to { opacity: 0.5; }
+ }
+ </style>
+</head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const div = createDiv(t);
+ div.id = 'test';
+ const anim = div.getAnimations()[0];
+ await anim.ready;
+ const opacity = parseFloat(getComputedStyle(div).opacity);
+ assert_equals(opacity, 0.5);
+}, 'Opacity at start of reverse running animation');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/simultaneous-animations-crash.html b/testing/web-platform/tests/css/css-animations/simultaneous-animations-crash.html
new file mode 100644
index 0000000000..223741157e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/simultaneous-animations-crash.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="test-wait">
+<title>CSS animations test: No crash should occur when an animation ends while other animations continue</title>
+<link rel="author" title="Philip Rogers" href="mailto:pdr@chromium.org">
+<link rel="help" href="https://crbug.com/1213307">
+<style>
+ @keyframes animationKeyframes {
+ from { opacity: 0.1; filter: blur(1px); }
+ to { opacity: 0.9; filter: blur(5px); }
+ }
+</style>
+<div id="longerAnimEl" style="animation: animationKeyframes 64ms;">a</div>
+<div id="shorterAnimEl" style="animation: both animationKeyframes 32ms;">b</div>
+<script>
+ longerAnimEl.addEventListener('animationend', () => {
+ document.documentElement.classList.remove('test-wait');
+ });
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-animations/style-animation-parsing.html b/testing/web-platform/tests/css/css-animations/style-animation-parsing.html
new file mode 100644
index 0000000000..792fda33ed
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/style-animation-parsing.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations: parsing style.animation</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#animation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="test1"></div>
+<div id="test2"></div>
+<script>
+function testStyle(style1, style2, name) {
+ assert_equals(style1.animationName, name, "style1.animationName");
+ assert_equals(style2.animationName, name, "style2.animationName");
+ assert_equals(style1.animation, style2.animation,
+ "style1 and style2 should have the same animation");
+}
+
+function testAnimation(input, name) {
+ var style1 = test1.style;
+ var style2 = test2.style;
+
+ style1.animation = input;
+ style2.animation = style1.animation;
+ testStyle(style1, style2, name);
+}
+
+test(() => {
+ testAnimation("", "");
+}, "Test animation name being empty.");
+
+test(() => {
+ testAnimation("myShorthandAnim", "myShorthandAnim");
+}, "Test a non-conflicting animation name.");
+
+test(() => {
+ testAnimation("none", "none");
+ testAnimation("forwards", "none");
+ testAnimation("none forwards", "forwards");
+}, "Test an animation name that is the same as a possible animation fill-mode.");
+
+test(() => {
+ testAnimation("normal", "none");
+ testAnimation("reverse", "none");
+ testAnimation("normal normal", "normal");
+ testAnimation("normal reverse", "reverse");
+}, "Test an animation name that is the same as a possible animation direction.");
+
+test(() => {
+ testAnimation("running", "none");
+ testAnimation("paused", "none");
+ testAnimation("running running", "running");
+ testAnimation("running paused", "paused");
+}, "Test an animation name that is the same as a possible running state.");
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/support/empty-sheet.css b/testing/web-platform/tests/css/css-animations/support/empty-sheet.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/support/empty-sheet.css
diff --git a/testing/web-platform/tests/css/css-animations/support/testcommon.js b/testing/web-platform/tests/css/css-animations/support/testcommon.js
new file mode 100644
index 0000000000..49c9863147
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/support/testcommon.js
@@ -0,0 +1,249 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Use this variable if you specify duration or some other properties
+ * for script animation.
+ * E.g., div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
+ *
+ * NOTE: Creating animations with short duration may cause intermittent
+ * failures in asynchronous test. For example, the short duration animation
+ * might be finished when animation.ready has been fulfilled because of slow
+ * platforms or busyness of the main thread.
+ * Setting short duration to cancel its animation does not matter but
+ * if you don't want to cancel the animation, consider using longer duration.
+ */
+const MS_PER_SEC = 1000;
+
+/* The recommended minimum precision to use for time values[1].
+ *
+ * [1] https://drafts.csswg.org/web-animations/#precision-of-time-values
+ */
+var TIME_PRECISION = 0.0005; // ms
+
+/*
+ * Allow implementations to substitute an alternative method for comparing
+ * times based on their precision requirements.
+ */
+function assert_times_equal(actual, expected, description) {
+ assert_approx_equals(actual, expected, TIME_PRECISION * 2, description);
+}
+
+/*
+ * Compare a time value based on its precision requirements with a fixed value.
+ */
+function assert_time_equals_literal(actual, expected, description) {
+ assert_approx_equals(actual, expected, TIME_PRECISION, description);
+}
+
+/*
+ * Compare two keyframes
+ */
+function assert_frames_equal(actual, expected, name) {
+ // TODO: Make this skip the 'composite' member when it is not specified in
+ // `expected` or when the implementation does not support it.
+ assert_array_equals(
+ Object.keys(actual).sort(),
+ Object.keys(expected).sort(),
+ `properties on ${name} should match`
+ );
+
+ // Iterates sorted keys to ensure stable failures.
+ for (const prop of Object.keys(actual).sort()) {
+ if (
+ // 'offset' can be null
+ (prop === 'offset' && typeof actual[prop] === 'number') ||
+ prop === 'computedOffset'
+ ) {
+ assert_approx_equals(
+ actual[prop],
+ expected[prop],
+ 0.00001,
+ "value for '" + prop + "' on " + name
+ );
+ } else {
+ assert_equals(
+ actual[prop],
+ expected[prop],
+ `value for '${prop}' on ${name} should match`
+ );
+ }
+ }
+}
+
+/*
+ * Compare two lists of keyframes
+ */
+function assert_frame_lists_equal(actual, expected) {
+ assert_equals(
+ actual.length,
+ expected.length,
+ 'Number of keyframes should match'
+ );
+
+ for (let i = 0; i < actual.length; i++) {
+ assert_frames_equal(actual[i], expected[i], `Keyframe #${i}`);
+ }
+}
+
+/**
+ * Appends an element to the document body.
+ *
+ * @param t The testharness.js Test object. If provided, this will be used
+ * to register a cleanup callback to remove the div when the test
+ * finishes.
+ *
+ * @param name A string specifying the element name.
+ *
+ * @param attrs A dictionary object with attribute names and values to set on
+ * the div.
+ */
+function addElement(t, name, attrs) {
+ var element = document.createElement(name);
+ if (attrs) {
+ for (var attrName in attrs) {
+ element.setAttribute(attrName, attrs[attrName]);
+ }
+ }
+ document.body.appendChild(element);
+ if (t && typeof t.add_cleanup === 'function') {
+ t.add_cleanup(() => element.remove());
+ }
+ return element;
+}
+
+/**
+ * Appends a div to the document body.
+ *
+ * @param t The testharness.js Test object. If provided, this will be used
+ * to register a cleanup callback to remove the div when the test
+ * finishes.
+ *
+ * @param attrs A dictionary object with attribute names and values to set on
+ * the div.
+ */
+function addDiv(t, attrs) {
+ return addElement(t, "div", attrs);
+}
+
+/**
+ * Appends a style div to the document head.
+ *
+ * @param t The testharness.js Test object. If provided, this will be used
+ * to register a cleanup callback to remove the style element
+ * when the test finishes.
+ *
+ * @param rules A dictionary object with selector names and rules to set on
+ * the style sheet.
+ */
+function addStyle(t, rules) {
+ var extraStyle = document.createElement('style');
+ document.head.appendChild(extraStyle);
+ if (rules) {
+ var sheet = extraStyle.sheet;
+ for (var selector in rules) {
+ sheet.insertRule(selector + '{' + rules[selector] + '}',
+ sheet.cssRules.length);
+ }
+ }
+
+ if (t && typeof t.add_cleanup === 'function') {
+ t.add_cleanup(function() {
+ extraStyle.remove();
+ });
+ }
+}
+
+/**
+ * Promise wrapper for requestAnimationFrame.
+ */
+function waitForFrame() {
+ return new Promise(function(resolve, reject) {
+ window.requestAnimationFrame(resolve);
+ });
+}
+
+/**
+ * Waits for a requestAnimationFrame callback in the next refresh driver tick.
+ */
+function waitForNextFrame() {
+ const timeAtStart = document.timeline.currentTime;
+ return new Promise(resolve => {
+ window.requestAnimationFrame(() => {
+ if (timeAtStart === document.timeline.currentTime) {
+ window.requestAnimationFrame(resolve);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * Returns a Promise that is resolved after the given number of consecutive
+ * animation frames have occured (using requestAnimationFrame callbacks).
+ *
+ * @param frameCount The number of animation frames.
+ * @param onFrame An optional function to be processed in each animation frame.
+ */
+function waitForAnimationFrames(frameCount, onFrame) {
+ const timeAtStart = document.timeline.currentTime;
+ return new Promise(function(resolve, reject) {
+ function handleFrame() {
+ if (onFrame && typeof onFrame === 'function') {
+ onFrame();
+ }
+ if (timeAtStart != document.timeline.currentTime &&
+ --frameCount <= 0) {
+ resolve();
+ } else {
+ window.requestAnimationFrame(handleFrame); // wait another frame
+ }
+ }
+ window.requestAnimationFrame(handleFrame);
+ });
+}
+
+/**
+ * Timeout function used for tests with EventWatchers when all animation events
+ * should be received on the next animation frame. If two frames pass before
+ * receiving the expected events, then we can immediate fail the test.
+ */
+function fastEventsTimeout() {
+ return waitForAnimationFrames(2);
+};
+
+/**
+ * Timeout function used for tests with EventWatchers. The client agent has no
+ * strict requirement for how long it takes to resolve the ready promise. Once
+ * the promise is resolved a secondary timeout promise is armed that may have
+ * a tight deadline measured in animation frames.
+ */
+function armTimeoutWhenReady(animation, timeoutPromise) {
+ return () => {
+ if (animation.pending)
+ return animation.ready.then(() => { return timeoutPromise(); });
+ else
+ return timeoutPromise();
+ };
+}
+
+/**
+ * Wrapper that takes a sequence of N animations and returns:
+ *
+ * Promise.all([animations[0].ready, animations[1].ready, ... animations[N-1].ready]);
+ */
+function waitForAllAnimations(animations) {
+ return Promise.all(animations.map(animation => animation.ready));
+}
+
+/**
+ * Flush the computed style for the given element. This is useful, for example,
+ * when we are testing a transition and need the initial value of a property
+ * to be computed so that when we synchronouslyet set it to a different value
+ * we actually get a transition instead of that being the initial value.
+ */
+function flushComputedStyle(elem) {
+ var cs = getComputedStyle(elem);
+ cs.marginLeft;
+}
diff --git a/testing/web-platform/tests/css/css-animations/svg-transform-animation-ref.html b/testing/web-platform/tests/css/css-animations/svg-transform-animation-ref.html
new file mode 100644
index 0000000000..d8666e8a73
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/svg-transform-animation-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<div style="width: 100px; height: 100px; position: relative; top: 100px; left: 100px;
+ background: green"></div>
diff --git a/testing/web-platform/tests/css/css-animations/svg-transform-animation.html b/testing/web-platform/tests/css/css-animations/svg-transform-animation.html
new file mode 100644
index 0000000000..b08629c2eb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/svg-transform-animation.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Transform animation on SVG element with zoom</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/">
+<link rel="match" href="svg-transform-animation-ref.html">
+<style>
+@keyframes transform {
+ from {transform: translate(100px, 100px)}
+ to {transform: translate(100px, 100px)}
+}
+</style>
+<svg width="200" height="200">
+ <rect x="100" y="100" width="100" height="100" fill="red"/>
+ <rect style="animation: transform 2s infinite" x="0" y="0" width="100" height="100" fill="green"/>
+</svg>
diff --git a/testing/web-platform/tests/css/css-animations/transform-animation-under-large-scale-ref.html b/testing/web-platform/tests/css/css-animations/transform-animation-under-large-scale-ref.html
new file mode 100644
index 0000000000..14b4136626
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/transform-animation-under-large-scale-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<div style="width: 200px; height: 100px; background: blue"></div>
+<div style="width: 200px; height: 100px; background: green"></div>
diff --git a/testing/web-platform/tests/css/css-animations/transform-animation-under-large-scale.html b/testing/web-platform/tests/css/css-animations/transform-animation-under-large-scale.html
new file mode 100644
index 0000000000..359380d698
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/transform-animation-under-large-scale.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Transform animation under large scale</title>
+<link rel="author" title="Xianzhu Wang" href="mailto:wangxianzhu@chromium.org">
+<link rel="help" href="https://crbug.com/1153428">
+<link rel="match" href="transform-animation-under-large-scale-ref.html">
+<style>
+@keyframes move {
+ 0% {transform: translateX(-1px);}
+ 100% {transform: translateX(0);}
+}
+</style>
+<div style="width: 2px; height: 2px; transform: scale(100); transform-origin: 0 0; overflow: hidden">
+ <div style="animation: move 1s infinite alternate">
+ <div style="width: 4px; height: 1px; background: blue"></div>
+ <div style="width: 4px; height: 1px; background: green"></div>
+ </div>
+</div>
diff --git a/testing/web-platform/tests/css/css-animations/transition-properties-not-animatable.html b/testing/web-platform/tests/css/css-animations/transition-properties-not-animatable.html
new file mode 100644
index 0000000000..288487b489
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/transition-properties-not-animatable.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-transitions-1/#transition-duration-property">
+<link rel="help" href="https://www.w3.org/TR/css-transitions-1/#transition-delay-property">
+<link rel="help" href="https://www.w3.org/TR/css-transitions-1/#transition-property-property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_not_animatable({
+ property: 'transition-duration',
+ from: '1s',
+ to: '2s',
+ underlying: '0s',
+});
+
+test_not_animatable({
+ property: 'transition-delay',
+ from: '1s',
+ to: '2s',
+ underlying: '0s',
+});
+
+test_not_animatable({
+ property: 'transition-property',
+ from: 'color',
+ to: 'background-color',
+ underlying: 'all',
+});
+</script>
diff --git a/testing/web-platform/tests/css/css-animations/translation-animation-on-important-property-ref.html b/testing/web-platform/tests/css/css-animations/translation-animation-on-important-property-ref.html
new file mode 100644
index 0000000000..97110dd54e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/translation-animation-on-important-property-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Translation animation on important property</title>
+<link rel="help" href="https://crbug.com/1324679">
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transform: none !important;
+}
+</style>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-animations/translation-animation-on-important-property.html b/testing/web-platform/tests/css/css-animations/translation-animation-on-important-property.html
new file mode 100644
index 0000000000..4f02942764
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/translation-animation-on-important-property.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Translation animation on important property</title>
+<link rel="help" href="https://crbug.com/1324679">
+<link rel="match" href="translation-animation-on-important-property-ref.html">
+<style>
+@keyframes move {
+ 0% {transform: translateX(0px);}
+ 100% {transform: translateX(100px);}
+}
+#target {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transform: none !important;
+ animation: move 10000s cubic-bezier(0, 1, 1, 0) -5000s;
+}
+</style>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-animations/translation-animation-subpixel-offset-ref.html b/testing/web-platform/tests/css/css-animations/translation-animation-subpixel-offset-ref.html
new file mode 100644
index 0000000000..5fc04d972b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/translation-animation-subpixel-offset-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+div {
+ position: absolute;
+ top: 11px;
+ left: 1px;
+ width: 100px;
+ height: 100px;
+ background: green;
+}
+</style>
+<div></div>
diff --git a/testing/web-platform/tests/css/css-animations/translation-animation-subpixel-offset.html b/testing/web-platform/tests/css/css-animations/translation-animation-subpixel-offset.html
new file mode 100644
index 0000000000..5692a08232
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/translation-animation-subpixel-offset.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Translation-only transform animation with subpixel offset</title>
+<link rel="author" title="Xianzhu Wang" href="mailto:wangxianzhu@chromium.org">
+<link rel="help" href="https://crbug.com/1155029">
+<link rel="match" href="translation-animation-subpixel-offset-ref.html">
+<style>
+@keyframes move {
+ 0% {transform: translateY(10px);}
+ 100% {transform: translateY(10px);}
+}
+#red {
+ position: absolute;
+ top: 11px;
+ left: 1px;
+ width: 100px;
+ height: 100px;
+ background: red;
+}
+#container {
+ position: absolute;
+ top: 0.4px;
+ left: 0.6px;
+}
+#target {
+ position: relative;
+ top: 0.4px;
+ left: 0.6px;
+ width: 100px;
+ height: 100px;
+ background: green;
+ animation: move 1s infinite alternate;
+}
+</style>
+<div id="red"></div>
+<div id="container">
+ <div id="target"></div>
+</div>
diff --git a/testing/web-platform/tests/css/css-animations/webkit-writing-mode-crash.html b/testing/web-platform/tests/css/css-animations/webkit-writing-mode-crash.html
new file mode 100644
index 0000000000..8c851a7572
--- /dev/null
+++ b/testing/web-platform/tests/css/css-animations/webkit-writing-mode-crash.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="test-wait">
+<link rel="help" href="https://crbug.com/1077501">
+<style>
+ @keyframes test {
+ from { -webkit-writing-mode: var(--x) }
+ to { -webkit-writing-mode: var(--y) }
+ }
+ div {
+ animation: test 1s;
+ }
+</style>
+<div id=div></div>
+<script>
+window.onload = () => {
+ div.offsetTop;
+ document.documentElement.classList.remove('test-wait');
+};
+</script>
+</html>