Bug 1445877 [wpt PR 10047] - [web-animations] Update timing interfaces, a=testonly
authorBrian Birtles <birtles@gmail.com>
Mon, 09 Apr 2018 17:06:01 +0000
changeset 467083 e025a58f144f0a423c5c1a565f40133635930a4c
parent 467082 df3a4f8111c6e6d5da55d4071d88ba2cbafbc3c2
child 467084 cbff506c1e2d5b56e9744c3d9ff4d931f1eaa643
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1445877, 10047
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1445877 [wpt PR 10047] - [web-animations] Update timing interfaces, a=testonly Automatic update from web-platform-tests[web-animations] Update timing interfaces (#10047) This updates the tests to reflect the specification changes made in https://github.com/w3c/csswg-drafts/commit/953041faa37d86d77eca651822eebc7dc7477e01 wpt-commits: b73f249d95a82118603f749c266d540e0b4b3b04 wpt-pr: 10047 wpt-commits: b73f249d95a82118603f749c266d540e0b4b3b04 wpt-pr: 10047
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html
testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html
testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html
testing/web-platform/tests/web-animations/interfaces/Animation/effect.html
testing/web-platform/tests/web-animations/interfaces/Animation/finished.html
testing/web-platform/tests/web-animations/interfaces/Animation/idlharness.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffect/getComputedTiming.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffect/updateTiming.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/idlharness.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html
testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-constructor.html
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/idlharness.html
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
testing/web-platform/tests/web-animations/resources/timing-tests.js
testing/web-platform/tests/web-animations/resources/timing-utils.js
testing/web-platform/tests/web-animations/testcommon.js
testing/web-platform/tests/web-animations/timing-model/animation-effects/phases-and-states.html
testing/web-platform/tests/web-animations/timing-model/animations/finishing-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/updating-the-finished-state.html
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -294704,16 +294704,26 @@
      {}
     ]
    ],
    "web-animations/resources/keyframe-utils.js": [
     [
      {}
     ]
    ],
+   "web-animations/resources/timing-tests.js": [
+    [
+     {}
+    ]
+   ],
+   "web-animations/resources/timing-utils.js": [
+    [
+     {}
+    ]
+   ],
    "web-animations/resources/xhr-doc.py": [
     [
      {}
     ]
    ],
    "web-animations/testcommon.js": [
     [
      {}
@@ -364427,73 +364437,25 @@
     ]
    ],
    "web-animations/interfaces/Animation/startTime.html": [
     [
      "/web-animations/interfaces/Animation/startTime.html",
      {}
     ]
    ],
-   "web-animations/interfaces/AnimationEffectTiming/delay.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/delay.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/direction.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/direction.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/duration.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/duration.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/easing.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/easing.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/endDelay.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/endDelay.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/fill.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/fill.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/idlharness.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/idlharness.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/iterationStart.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/iterationStart.html",
-     {}
-    ]
-   ],
-   "web-animations/interfaces/AnimationEffectTiming/iterations.html": [
-    [
-     "/web-animations/interfaces/AnimationEffectTiming/iterations.html",
+   "web-animations/interfaces/AnimationEffect/getComputedTiming.html": [
+    [
+     "/web-animations/interfaces/AnimationEffect/getComputedTiming.html",
+     {}
+    ]
+   ],
+   "web-animations/interfaces/AnimationEffect/updateTiming.html": [
+    [
+     "/web-animations/interfaces/AnimationEffect/updateTiming.html",
      {}
     ]
    ],
    "web-animations/interfaces/AnimationPlaybackEvent/constructor.html": [
     [
      "/web-animations/interfaces/AnimationPlaybackEvent/constructor.html",
      {}
     ]
@@ -601091,61 +601053,61 @@
    "5d6c3904de02eb3b6c890163ccdc6b8cb6499e56",
    "testharness"
   ],
   "web-animations/animation-model/keyframe-effects/effect-value-context.html": [
    "da405e4bfc35d5d0b4c151706b09eb1a84d2f0da",
    "testharness"
   ],
   "web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html": [
-   "bda186b311457e58c48ca5cf4619f485a41f8e2d",
+   "2c424570af86e1a4541d4f0a67c7693cfcac8abc",
    "testharness"
   ],
   "web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html": [
    "2b10d6fb1cb925290caf57e4cefc3c3f8161777c",
    "testharness"
   ],
   "web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html": [
    "8fd7dbbaf98947f8101c760e41d9cc909c844d2d",
    "testharness"
   ],
   "web-animations/interfaces/Animatable/animate-no-browsing-context.html": [
    "5c30bc76f60eb479cba967f91c6a80af7df8e2e7",
    "testharness"
   ],
   "web-animations/interfaces/Animatable/animate.html": [
-   "8d76a26de47494600da40e756a26de61329ff3aa",
+   "4cd816ad368f1dae077f601022f9b57d9cb931ae",
    "testharness"
   ],
   "web-animations/interfaces/Animatable/getAnimations.html": [
-   "9c03f9a6410dc8463a3e3acb3b6e38c26d79b097",
+   "a11049c78d2ca16a9df9d0f21b3a3d0fd35e29f3",
    "testharness"
   ],
   "web-animations/interfaces/Animation/cancel.html": [
-   "38d509e24fa224fc8b937e4a63dd1c404e72b466",
+   "105313dffbe7953ca6b413e860ba8b47e316e0b7",
    "testharness"
   ],
   "web-animations/interfaces/Animation/constructor.html": [
-   "f4dc4fdca61255557ed346412e134745bce1a3ed",
+   "75e8dd9d9d3c96a87cec1abc2c1373537c6e0f77",
    "testharness"
   ],
   "web-animations/interfaces/Animation/effect.html": [
-   "4445fc8bd2120fb1e212dfc6a1fcf786a531ee6f",
+   "db36e79cf6839cda7d6940f21afe2f56d4d9a115",
    "testharness"
   ],
   "web-animations/interfaces/Animation/finished.html": [
-   "ffcba3379db7094455a7798e4d5972d8e52caec5",
+   "a944518bdd0151534f0172ac58034ac613a70249",
    "testharness"
   ],
   "web-animations/interfaces/Animation/id.html": [
    "4e3dd92351d76c5c7d09ddd1ca025520f4c8875d",
    "testharness"
   ],
   "web-animations/interfaces/Animation/idlharness.html": [
-   "d61aa2d95ea31809a275183408e822c8c1eec87d",
+   "b049999bb0512bfa0c3eb8b60176eb9213d663f7",
    "testharness"
   ],
   "web-animations/interfaces/Animation/oncancel.html": [
    "82abc08a0b416f5198239464fb4fc01d2edd6e1c",
    "testharness"
   ],
   "web-animations/interfaces/Animation/onfinish.html": [
    "db82fabeaf2b646647f134634fef30f05e5ec7f8",
@@ -601166,66 +601128,34 @@
   "web-animations/interfaces/Animation/ready.html": [
    "bd4a18205791b2b0271a6266dba3ebc8482c835b",
    "testharness"
   ],
   "web-animations/interfaces/Animation/startTime.html": [
    "01f669542434f03d37e9f148a4f3135fe3122d46",
    "testharness"
   ],
-  "web-animations/interfaces/AnimationEffectTiming/delay.html": [
-   "4de5b0a692d645961de27df67efa8257adb0a031",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/direction.html": [
-   "642207ce454fb816cc47d14fbe29f65d92ddf6ed",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/duration.html": [
-   "14abe09cb19080585a315115e387b85784c7d862",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/easing.html": [
-   "b3ad4c78c9bce0e17db0ce780cd1260de1ce7cb0",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/endDelay.html": [
-   "a8609f22672b092178c2391d7ba7ef804112bef4",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/fill.html": [
-   "1cef601cde33eea3b591a0826ad52f379bb31d0d",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html": [
-   "020e9faaae05de5a25829a05558ea72672b04f63",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/idlharness.html": [
-   "aa9823e5a06c76921b49aa5f5e61fd1dedfac3af",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/iterationStart.html": [
-   "393b37098ab470e75b1254b53875901e705d8d1b",
-   "testharness"
-  ],
-  "web-animations/interfaces/AnimationEffectTiming/iterations.html": [
-   "1e8bb46b5a1eef496edda32b16c856baa16e9b30",
+  "web-animations/interfaces/AnimationEffect/getComputedTiming.html": [
+   "e0d4ba4c0f1a5da4d126c501eca733e00adbef1a",
+   "testharness"
+  ],
+  "web-animations/interfaces/AnimationEffect/updateTiming.html": [
+   "5daa5580c2d34e992f3560bf5b85419498b6c5ee",
    "testharness"
   ],
   "web-animations/interfaces/AnimationPlaybackEvent/constructor.html": [
    "5aff03b7fa469e5ec0dc02a389eca963ae24b470",
    "testharness"
   ],
   "web-animations/interfaces/AnimationPlaybackEvent/idlharness.html": [
    "bfdfc896fb5fe4451419464e35fe94b5e4938c2c",
    "testharness"
   ],
   "web-animations/interfaces/Document/getAnimations.html": [
-   "12fdbce3e75f5a7d7771d9337089255ef73f9712",
+   "7fbd5eed47955fdaeccd329f82f0884b86654784",
    "testharness"
   ],
   "web-animations/interfaces/Document/timeline.html": [
    "2577a2d5c89fa0eec3cae2fd07b66e576ee6a483",
    "testharness"
   ],
   "web-animations/interfaces/DocumentTimeline/constructor.html": [
    "b11caf0a1766818a168a7f91b01ccd6ae9a7e4f0",
@@ -601235,33 +601165,33 @@
    "72cb7900f86611e9c2a1b0f4acd0f634555310b9",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/composite.html": [
    "12fc2e8e7bcfb1eab6e162b68731ff6fcb767438",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/constructor.html": [
-   "2f6449cbf2b47ae457efb23fb52b8fd1709837ac",
+   "38ff2a4b64e7318fa96eb867aae7f25bed53e67b",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/copy-constructor.html": [
-   "6ef462ddc696269f132d596188ffd5e8da1e1164",
+   "0e3d893d7b8e438b279f087846df596256ccbe84",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/idlharness.html": [
-   "f05c9bd1cdee77ff6be143b0eb4f982c7218908b",
+   "ffe493133d4029820f8b27389a15157706b738e8",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/iterationComposite.html": [
-   "65cd746596a6770d1101b030769712be433bf6f3",
+   "c5ce17faeb355f1e9efae516d6272a88c46daa1f",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html": [
-   "f54c7c0da5728f88f37a067761af7ad815fea005",
+   "ca5efb8556aff617bef957be315ea2fd01e756d8",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-002.html": [
    "e9237e244034845f6f902f8149a0e66e5b6164f2",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/setKeyframes.html": [
    "a346e0e004010a6f51e06ffd30d0b6eddd45421d",
@@ -601282,50 +601212,58 @@
   "web-animations/resources/keyframe-tests.js": [
    "b31029042fdfa77ba8bf0e9370f63a423fbe0da9",
    "support"
   ],
   "web-animations/resources/keyframe-utils.js": [
    "08da0c81847809328bda0d6e0581711f7838916e",
    "support"
   ],
+  "web-animations/resources/timing-tests.js": [
+   "2da06d9e7ace5947141165284697c4ea9be5b9d5",
+   "support"
+  ],
+  "web-animations/resources/timing-utils.js": [
+   "e0918d83187c0fbdadaebb14be72c6f34f8dfc03",
+   "support"
+  ],
   "web-animations/resources/xhr-doc.py": [
    "de68c45fc1d38a49946f9046f34031e9278a1531",
    "support"
   ],
   "web-animations/testcommon.js": [
-   "6f3e85e7ae7786ad9ff3aaec8c6343642d2510c5",
+   "8f593cc9211bcc0f8f99579e8a3475528f837969",
    "support"
   ],
   "web-animations/timing-model/animation-effects/active-time.html": [
    "f05ff3594dde7248c84db42f8a80a6d0136b5f54",
    "testharness"
   ],
   "web-animations/timing-model/animation-effects/current-iteration.html": [
    "617bfd8c533d159c4e56ea823917d580fe262bf6",
    "testharness"
   ],
   "web-animations/timing-model/animation-effects/local-time.html": [
    "cd76b6be13cba0bc7d3b1a0e87b70c6a66222f40",
    "testharness"
   ],
   "web-animations/timing-model/animation-effects/phases-and-states.html": [
-   "3edd2c4bdd8409c2c12f08bc998dd8d532e0fd7d",
+   "b8cc580e3e8d17961ffff4b693857f6c333dd57f",
    "testharness"
   ],
   "web-animations/timing-model/animation-effects/simple-iteration-progress.html": [
    "602fe7e6880e0b18329262699872c696f451d744",
    "testharness"
   ],
   "web-animations/timing-model/animations/canceling-an-animation.html": [
    "e03baa30d438529a0ebe39f0f623563aa9850d74",
    "testharness"
   ],
   "web-animations/timing-model/animations/finishing-an-animation.html": [
-   "4c1cf823a81e72541abcafaa08950cf87424ae55",
+   "afe654435332e798b3771b6ec6ca13bcca99e421",
    "testharness"
   ],
   "web-animations/timing-model/animations/pausing-an-animation.html": [
    "a4cb7b89c778ad5c294eeb55e94461e19ca8eb4b",
    "testharness"
   ],
   "web-animations/timing-model/animations/play-states.html": [
    "0ab2fa3a464001272d1af541ea769fa967490c3b",
@@ -601351,29 +601289,29 @@
    "b2698d9a829a1eadb3ef3b6d8e0050e7a6315305",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html": [
    "cf6040eb52964f12b06a9e3cdf14948ce8141270",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html": [
-   "5575a251b9c265d98471e758b3cf9b218e381cba",
+   "f4e50b805229e0170bff67530b694ee5d6dd1f1a",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html": [
    "e4e134b566327c9d7316aee4f3e7fe4eeb2116ba",
    "testharness"
   ],
   "web-animations/timing-model/animations/the-current-time-of-an-animation.html": [
    "90ba3d81ee9e32b1f13845301c4ad1c8ad47f2f7",
    "testharness"
   ],
   "web-animations/timing-model/animations/updating-the-finished-state.html": [
-   "59e7ed8e4eac5c9edf2526ef748b22e1877b7016",
+   "8eea7caf9f0bf8b9a87b5a6574a28b378054c8db",
    "testharness"
   ],
   "web-animations/timing-model/time-transformations/transformed-progress.html": [
    "2e55f43def584a67eeb313f050154cd146002938",
    "testharness"
   ],
   "web-animations/timing-model/timelines/document-timelines.html": [
    "d0fcb390c19c9ede7288278dc11ea5b3d33671cb",
--- a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
+++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
@@ -15,44 +15,44 @@ test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ alignContent: ['flex-start', 'flex-end'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).alignContent, 'flex-end',
     'Animated align-content style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).alignContent, 'flex-start',
     'Animated align-content style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).alignContent, 'flex-end',
     'Animated align-content style at 50s of the third iteration');
 }, 'iteration composition of discrete type animation (align-content)');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ marginLeft: ['0px', '10px'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '5px',
     'Animated margin-left style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).marginLeft, '20px',
     'Animated margin-left style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '25px',
     'Animated margin-left style at 50s of the third iteration');
 }, 'iteration composition of <length> type animation');
 
 test(t => {
   const parent = createDiv(t);
   parent.style.width = '100px';
   const div = createDiv(t);
@@ -61,132 +61,132 @@ test(t => {
   const anim =
     div.animate({ width: ['0%', '50%'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '25px',
     'Animated width style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).width, '100px',
     'Animated width style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '125px',
     'Animated width style at 50s of the third iteration');
 }, 'iteration composition of <percentage> type animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ color: ['rgb(0, 0, 0)', 'rgb(120, 120, 120)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).color, 'rgb(60, 60, 60)',
     'Animated color style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).color, 'rgb(240, 240, 240)',
     'Animated color style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).color, 'rgb(255, 255, 255)',
     'Animated color style at 50s of the third iteration');
 }, 'iteration composition of <color> type animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ color: ['rgb(0, 120, 0)', 'rgb(60, 60, 60)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).color, 'rgb(30, 90, 30)',
     'Animated color style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).color, 'rgb(120, 240, 120)',
     'Animated color style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   // The green color is (240 + 180) / 2 = 210
   assert_equals(getComputedStyle(div).color, 'rgb(150, 210, 150)',
     'Animated color style at 50s of the third iteration');
 }, 'iteration composition of <color> type animation that green component is ' +
    'decreasing');
 
    test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ flexGrow: [0, 10] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).flexGrow, '5',
     'Animated flex-grow style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).flexGrow, '20',
     'Animated flex-grow style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).flexGrow, '25',
     'Animated flex-grow style at 50s of the third iteration');
 }, 'iteration composition of <number> type animation');
 
 test(t => {
   const div = createDiv(t);
   div.style.position = 'absolute';
   const anim =
     div.animate({ clip: ['rect(0px, 0px, 0px, 0px)',
                          'rect(10px, 10px, 10px, 10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).clip, 'rect(5px, 5px, 5px, 5px)',
     'Animated clip style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).clip, 'rect(20px, 20px, 20px, 20px)',
     'Animated clip style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).clip, 'rect(25px, 25px, 25px, 25px)',
     'Animated clip style at 50s of the third iteration');
 }, 'iteration composition of <shape> type animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ width: ['calc(0vw + 0px)', 'calc(0vw + 10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '5px',
     'Animated calc width style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).width, '20px',
     'Animated calc width style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '25px',
     'Animated calc width style at 50s of the third iteration');
 }, 'iteration composition of <calc()> value animation');
 
 test(t => {
   const parent = createDiv(t);
   parent.style.width = '100px';
   const div = createDiv(t);
@@ -195,221 +195,221 @@ test(t => {
   const anim =
     div.animate({ width: ['calc(0% + 0px)', 'calc(10% + 10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '10px',
     // 100px * 5% + 5px
     'Animated calc width style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).width,
     '40px', // 100px * (10% + 10%) + (10px + 10px)
     'Animated calc width style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width,
     '50px', // (40px + 60px) / 2
     'Animated calc width style at 50s of the third iteration');
 }, 'iteration composition of <calc()> value animation that the values can\'t' +
    'be reduced');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ opacity: [0, 0.4] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).opacity, '0.2',
     'Animated opacity style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).opacity, '0.8',
     'Animated opacity style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).opacity, '1', // (0.8 + 1.2) * 0.5
     'Animated opacity style at 50s of the third iteration');
 }, 'iteration composition of opacity animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ boxShadow: ['rgb(0, 0, 0) 0px 0px 0px 0px',
                               'rgb(120, 120, 120) 10px 10px 10px 0px'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).boxShadow,
     'rgb(60, 60, 60) 5px 5px 5px 0px',
     'Animated box-shadow style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).boxShadow,
     'rgb(240, 240, 240) 20px 20px 20px 0px',
     'Animated box-shadow style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).boxShadow,
     'rgb(255, 255, 255) 25px 25px 25px 0px',
     'Animated box-shadow style at 50s of the third iteration');
 }, 'iteration composition of box-shadow animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ filter: ['blur(0px)', 'blur(10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter, 'blur(5px)',
     'Animated filter blur style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter, 'blur(20px)',
     'Animated filter blur style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter, 'blur(25px)',
     'Animated filter blur style at 50s of the third iteration');
 }, 'iteration composition of filter blur animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ filter: ['brightness(1)',
                            'brightness(180%)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(1.4)',
     'Animated filter brightness style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(2.6)', // brightness(1) + brightness(0.8) + brightness(0.8)
     'Animated filter brightness style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(3)', // (brightness(2.6) + brightness(3.4)) * 0.5
     'Animated filter brightness style at 50s of the third iteration');
 }, 'iteration composition of filter brightness for different unit animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ filter: ['brightness(0)',
                            'brightness(1)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(0.5)',
     'Animated filter brightness style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(0)', // brightness(1) is an identity element, not accumulated.
     'Animated filter brightness style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(0.5)', // brightness(1) is an identity element, not accumulated.
     'Animated filter brightness style at 50s of the third iteration');
 }, 'iteration composition of filter brightness animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ filter: ['drop-shadow(rgb(0, 0, 0) 0px 0px 0px)',
                            'drop-shadow(rgb(120, 120, 120) 10px 10px 10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'drop-shadow(rgb(60, 60, 60) 5px 5px 5px)',
     'Animated filter drop-shadow style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'drop-shadow(rgb(240, 240, 240) 20px 20px 20px)',
     'Animated filter drop-shadow style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'drop-shadow(rgb(255, 255, 255) 25px 25px 25px)',
     'Animated filter drop-shadow style at 50s of the third iteration');
 }, 'iteration composition of filter drop-shadow animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ filter: ['brightness(1) contrast(1)',
                            'brightness(2) contrast(2)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(1.5) contrast(1.5)',
     'Animated filter list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(3) contrast(3)',
     'Animated filter list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(3.5) contrast(3.5)',
     'Animated filter list at 50s of the third iteration');
 }, 'iteration composition of same filter list animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ filter: ['brightness(1) contrast(1)',
                            'contrast(2) brightness(2)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'contrast(2) brightness(2)', // discrete
     'Animated filter list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     // We can't accumulate 'contrast(2) brightness(2)' onto
     // the first list 'brightness(1) contrast(1)' because of
     // mismatch of the order.
     'brightness(1) contrast(1)',
     'Animated filter list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     // We *can* accumulate 'contrast(2) brightness(2)' onto
     // the same list 'contrast(2) brightness(2)' here.
     'contrast(4) brightness(4)', // discrete
     'Animated filter list at 50s of the third iteration');
 }, 'iteration composition of discrete filter list because of mismatch ' +
    'of the order');
 
@@ -419,174 +419,174 @@ test(t => {
     div.animate({ filter: ['sepia(0)',
                            'sepia(1) contrast(2)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'sepia(0.5) contrast(1.5)',
     'Animated filter list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'sepia(2) contrast(3)',
     'Animated filter list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'sepia(2.5) contrast(3.5)',
     'Animated filter list at 50s of the third iteration');
 }, 'iteration composition of different length filter list animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ transform: ['rotate(0deg)', 'rotate(180deg)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 1, -1, 0, 0, 0)', // rotate(90deg)
     'Animated transform(rotate) style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1, 0, 0, 1, 0, 0)', // rotate(360deg)
     'Animated transform(rotate) style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 1, -1, 0, 0, 0)', // rotate(450deg)
     'Animated transform(rotate) style at 50s of the third iteration');
 }, 'iteration composition of transform(rotate) animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ transform: ['scale(0)', 'scale(1)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5)
     'Animated transform(scale) style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 0, 0, 0, 0, 0)', // scale(0); scale(1) is an identity element,
                                 // not accumulated.
     'Animated transform(scale) style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5); scale(1) an identity
                                     // element, not accumulated.
     'Animated transform(scale) style at 50s of the third iteration');
 }, 'iteration composition of transform: [ scale(0), scale(1) ] animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ transform: ['scale(1)', 'scale(2)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1.5, 0, 0, 1.5, 0, 0)', // scale(1.5)
     'Animated transform(scale) style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(3, 0, 0, 3, 0, 0)', // scale(1 + (2 -1) + (2 -1))
     'Animated transform(scale) style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(3.5, 0, 0, 3.5, 0, 0)', // (scale(3) + scale(4)) * 0.5
     'Animated transform(scale) style at 50s of the third iteration');
 }, 'iteration composition of transform: [ scale(1), scale(2) ] animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ transform: ['scale(0)', 'scale(2)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1, 0, 0, 1, 0, 0)', // scale(1)
     'Animated transform(scale) style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(2, 0, 0, 2, 0, 0)', // (scale(0) + scale(2-1)*2)
     'Animated transform(scale) style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(3, 0, 0, 3, 0, 0)', // (scale(2) + scale(4)) * 0.5
     'Animated transform(scale) style at 50s of the third iteration');
 }, 'iteration composition of transform: scale(2) animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ transform: ['rotate(0deg) translateX(0px)',
                               'rotate(180deg) translateX(10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 1, -1, 0, 0, 5)', // rotate(90deg) translateX(5px)
     'Animated transform list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1, 0, 0, 1, 20, 0)', // rotate(360deg) translateX(20px)
     'Animated transform list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 1, -1, 0, 0, 25)', // rotate(450deg) translateX(25px)
     'Animated transform list at 50s of the third iteration');
 }, 'iteration composition of transform list animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ transform: ['matrix(2, 0, 0, 2, 0, 0)',
                               'matrix(3, 0, 0, 3, 30, 0)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(2.5, 0, 0, 2.5, 15, 0)',
     'Animated transform of matrix function at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // scale(2) + (scale(3-1)*2) + translateX(30px)*2
     'matrix(6, 0, 0, 6, 60, 0)',
     'Animated transform of matrix function at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // from: matrix(6, 0, 0, 6, 60, 0)
     // to:   matrix(7, 0, 0, 7, 90, 0)
     //         = scale(3) + (scale(3-1)*2) + translateX(30px)*3
     'matrix(6.5, 0, 0, 6.5, 75, 0)',
     'Animated transform of matrix function at 50s of the third iteration');
 }, 'iteration composition of transform of matrix function');
 
@@ -596,30 +596,30 @@ test(t => {
     div.animate({ transform: ['translateX(0px) scale(2)',
                               'scale(3) translateX(10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // Interpolate between matrix(2, 0, 0, 2,  0, 0) = translateX(0px) scale(2)
     //                 and matrix(3, 0, 0, 3, 30, 0) = scale(3) translateX(10px)
     'matrix(2.5, 0, 0, 2.5, 15, 0)',
     'Animated transform list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // 'from' and 'to' value are mismatched, so accumulate
     // matrix(2, 0, 0, 2, 0, 0) onto matrix(3, 0, 0, 3, 30, 0) * 2
     //  = scale(2) + (scale(3-1)*2) + translateX(30px)*2
     'matrix(6, 0, 0, 6, 60, 0)',
     'Animated transform list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // Interpolate between matrix(6, 0, 0, 6, 60, 0)
     //                 and matrix(7, 0, 0, 7, 210, 0) = scale(7) translate(30px)
     'matrix(6.5, 0, 0, 6.5, 135, 0)',
     'Animated transform list at 50s of the third iteration');
 }, 'iteration composition of transform list animation whose order is'
    + ' mismatched');
 
@@ -631,30 +631,30 @@ test(t => {
     div.animate({ transform: ['translateX(0px)',
                               'scale(2) translateX(10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // Interpolate between matrix(1, 0, 0, 1,  0, 0) = translateX(0px)
     //                 and matrix(2, 0, 0, 2, 20, 0) = scale(2) translateX(10px)
     'matrix(1.5, 0, 0, 1.5, 10, 0)',
     'Animated transform list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // 'from' and 'to' value are mismatched, so accumulate
     // matrix(1, 0, 0, 1, 0, 0) onto matrix(2, 0, 0, 2, 20, 0) * 2
     //  = scale(1) + (scale(2-1)*2) + translateX(20px)*2
     'matrix(3, 0, 0, 3, 40, 0)',
     'Animated transform list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // Interpolate between matrix(3, 0, 0, 3, 40, 0)
     //                 and matrix(4, 0, 0, 4, 120, 0) =
     //                       scale(2 + (2-1)*2) translate(10px * 3)
     'matrix(3.5, 0, 0, 3.5, 80, 0)',
     'Animated transform list at 50s of the third iteration');
 }, 'iteration composition of transform list animation whose order is'
    + ' mismatched because of missing functions');
@@ -665,27 +665,27 @@ test(t => {
     div.animate({ transform: ['none',
                               'translateX(10px)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // translateX(none) -> translateX(10px) @ 50%
     'matrix(1, 0, 0, 1, 5, 0)',
     'Animated transform list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // translateX(10px * 2 + none) -> translateX(10px * 2 + 10px) @ 0%
     'matrix(1, 0, 0, 1, 20, 0)',
     'Animated transform list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // translateX(10px * 2 + none) -> translateX(10px * 2 + 10px) @ 50%
     'matrix(1, 0, 0, 1, 25, 0)',
     'Animated transform list at 50s of the third iteration');
 }, 'iteration composition of transform from none to translate');
 
 test(t => {
   const div = createDiv(t);
@@ -699,26 +699,26 @@ test(t => {
                                        '0, 0,  1, 0, ' +
                                        '0, 0, 50, 1)'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 40, 1)',
     'Animated transform of matrix3d function at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // translateZ(30px) + (translateZ(50px)*2)
     'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 130, 1)',
     'Animated transform of matrix3d function at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // from: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 130, 1)
     // to:   matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 150, 1)
     'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 140, 1)',
     'Animated transform of matrix3d function at 50s of the third iteration');
 }, 'iteration composition of transform of matrix3d function');
 
 test(t => {
@@ -731,89 +731,94 @@ test(t => {
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
   anim.currentTime = 0;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1, 0, 0, 1, 0, 0)', // Actually not rotated at all.
     'Animated transform of rotate3d function at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     rotate3dToMatrix3d(1, 1, 0, Math.PI), // 180deg
     'Animated transform of rotate3d function at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     rotate3dToMatrix3d(1, 1, 0, 225 * Math.PI / 180), //((270 + 180) * 0.5)deg
     'Animated transform of rotate3d function at 50s of the third iteration');
 }, 'iteration composition of transform of rotate3d function');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ marginLeft: ['10px', '20px'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '15px',
     'Animated margin-left style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).marginLeft, '50px', // 10px + 20px + 20px
     'Animated margin-left style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '55px', // (50px + 60px) * 0.5
     'Animated margin-left style at 50s of the third iteration');
 }, 'iteration composition starts with non-zero value animation');
 
 test(t => {
   const div = createDiv(t);
   const anim =
     div.animate({ marginLeft: ['10px', '-10px'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
                   iterations: 10,
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft,
     '0px',
     'Animated margin-left style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).marginLeft,
     '-10px', // 10px + -10px + -10px
     'Animated margin-left style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft,
     '-20px', // (-10px + -30px) * 0.5
     'Animated margin-left style at 50s of the third iteration');
 }, 'iteration composition with negative final value animation');
 
 test(t => {
   const div = createDiv(t);
   const anim = div.animate({ marginLeft: ['0px', '10px'] },
                            { duration: 100 * MS_PER_SEC,
                              easing: 'linear',
                              iterations: 10,
                              iterationComposite: 'accumulate' });
   anim.pause();
 
   anim.currentTime =
-    anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2;
+    anim.effect.getComputedTiming().duration * 2 +
+    anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '25px',
     'Animated style at 50s of the third iteration');
 
   // double its duration.
-  anim.effect.timing.duration = anim.effect.timing.duration * 2;
+  anim.effect.updateTiming({
+    duration: anim.effect.getComputedTiming().duration * 2
+  });
   assert_equals(getComputedStyle(div).marginLeft, '12.5px',
     'Animated style at 25s of the first iteration');
 
   // half of original.
-  anim.effect.timing.duration = anim.effect.timing.duration / 4;
+  anim.effect.updateTiming({
+    duration: anim.effect.getComputedTiming().duration / 4
+  });
   assert_equals(getComputedStyle(div).marginLeft, '50px',
       'Animated style at 50s of the fourth iteration');
 }, 'duration changes with an iteration composition operation of accumulate');
 
 </script>
--- a/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
@@ -3,27 +3,28 @@
 <title>Animatable.animate</title>
 <link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-animate">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
 <script src="../../resources/easing-tests.js"></script>
 <script src="../../resources/keyframe-utils.js"></script>
 <script src="../../resources/keyframe-tests.js"></script>
+<script src="../../resources/timing-utils.js"></script>
+<script src="../../resources/timing-tests.js"></script>
 <body>
 <div id="log"></div>
 <iframe width="10" height="10" id="iframe"></iframe>
 <script>
 'use strict';
 
 // Tests on Element
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null);
+  const anim = createDiv(t).animate(null);
   assert_class_string(anim, 'Animation', 'Returned object is an Animation');
 }, 'Element.animate() creates an Animation object');
 
 test(t => {
   const iframe = window.frames[0];
   const div = createDiv(t, iframe.document);
   const anim = Element.prototype.animate.call(div, null);
   assert_equals(Object.getPrototypeOf(anim), iframe.Animation.prototype,
@@ -52,129 +53,157 @@ test(t => {
                 + ' the relevant global for the target element');
   assert_not_equals(Object.getPrototypeOf(anim.effect),
                     KeyframeEffect.prototype,
                     'The prototype of the created KeyframeEffect is NOT that of'
                     + ' the current global');
 }, 'Element.animate() creates an Animation object with a KeyframeEffect'
    + ' that is created in the relevant realm of the target element');
 
-test(t => {
-  const iframe = window.frames[0];
-  const div = createDiv(t, iframe.document);
-  const anim = div.animate(null);
-  assert_equals(Object.getPrototypeOf(anim.effect.timing),
-                iframe.AnimationEffectTiming.prototype,
-                'The prototype of the created AnimationEffectTiming is that'
-                + ' defined on the relevant global for the target element');
-  assert_not_equals(Object.getPrototypeOf(anim.effect.timing),
-                    AnimationEffectTiming.prototype,
-                    'The prototype of the created AnimationEffectTiming is NOT'
-                    + ' that of the current global');
-}, 'Element.animate() creates an Animation object with a KeyframeEffect'
-   + ' whose AnimationEffectTiming object is created in the relevant realm'
-   + ' of the target element');
-
 for (const subtest of gEmptyKeyframeListTests) {
   test(t => {
-    const div = createDiv(t);
-    const anim = div.animate(subtest, 2000);
+    const anim = createDiv(t).animate(subtest, 2000);
     assert_not_equals(anim, null);
   }, 'Element.animate() accepts empty keyframe lists ' +
      `(input: ${JSON.stringify(subtest)})`);
 }
 
 for (const subtest of gKeyframesTests) {
   test(t => {
-    const div = createDiv(t);
-    const anim = div.animate(subtest.input, 2000);
+    const anim = createDiv(t).animate(subtest.input, 2000);
     assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output);
   }, `Element.animate() accepts ${subtest.desc}`);
 }
 
 for (const subtest of gInvalidKeyframesTests) {
   test(t => {
     const div = createDiv(t);
     assert_throws(new TypeError, () => {
       div.animate(subtest.input, 2000);
     });
   }, `Element.animate() does not accept ${subtest.desc}`);
 }
 
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  assert_equals(anim.effect.getTiming().duration, 2000);
+  assert_default_timing_except(anim.effect, ['duration']);
+}, 'Element.animate() accepts a double as an options argument');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { duration: Infinity, fill: 'forwards' });
+  assert_equals(anim.effect.getTiming().duration, Infinity);
+  assert_equals(anim.effect.getTiming().fill, 'forwards');
+  assert_default_timing_except(anim.effect, ['duration', 'fill']);
+}, 'Element.animate() accepts a KeyframeAnimationOptions argument');
+
+test(t => {
+  const anim = createDiv(t).animate(null);
+  assert_default_timing_except(anim.effect, []);
+}, 'Element.animate() accepts an absent options argument');
+
+for (const invalid of gBadDelayValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { delay: invalid });
+    });
+  }, `Element.animate() does not accept invalid delay value: ${invalid}`);
+}
+
+test(t => {
+  const anim = createDiv(t).animate(null, { duration: 'auto' });
+  assert_equals(anim.effect.getTiming().duration, 'auto', 'set duration \'auto\'');
+  assert_equals(anim.effect.getComputedTiming().duration, 0,
+                'getComputedTiming() after set duration \'auto\'');
+}, 'Element.animate() accepts a duration of \'auto\' using a dictionary'
+   + ' object');
+
+for (const invalid of gBadDurationValues) {
+  if (typeof invalid === 'string' && !isNaN(parseFloat(invalid))) {
+    continue;
+  }
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, invalid);
+    });
+  }, 'Element.animate() does not accept invalid duration value: '
+     + (typeof invalid === 'string' ? `"${invalid}"` : invalid));
+}
+
+for (const invalid of gBadDurationValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { duration: invalid });
+    });
+  }, 'Element.animate() does not accept invalid duration value: '
+     + (typeof invalid === 'string' ? `"${invalid}"` : invalid)
+     + ' using a dictionary object');
+}
+
 for (const invalidEasing of gInvalidEasings) {
   test(t => {
-    const div = createDiv(t);
     assert_throws(new TypeError, () => {
-      div.animate({ easing: invalidEasing }, 2000);
+      createDiv(t).animate({ easing: invalidEasing }, 2000);
     });
   }, `Element.animate() does not accept invalid easing: '${invalidEasing}'`);
 }
 
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_equals(anim.effect.timing.duration, 2000);
-  // Also check that unspecified parameters receive their default values
-  assert_equals(anim.effect.timing.fill, 'auto');
-}, 'Element.animate() accepts a double as an options argument');
+for (const invalid of gBadIterationStartValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { iterationStart: invalid });
+    });
+  }, 'Element.animate() does not accept invalid iterationStart value: ' +
+     invalid);
+}
+
+for (const invalid of gBadIterationsValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { iterations: invalid });
+    });
+  }, 'Element.animate() does not accept invalid iterations value: ' +
+     invalid);
+}
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { duration: Infinity, fill: 'forwards' });
-  assert_equals(anim.effect.timing.duration, Infinity);
-  assert_equals(anim.effect.timing.fill, 'forwards');
-  // Also check that unspecified parameters receive their default values
-  assert_equals(anim.effect.timing.direction, 'normal');
-}, 'Element.animate() accepts a KeyframeAnimationOptions argument');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] });
-  assert_equals(anim.effect.timing.duration, 'auto');
-}, 'Element.animate() accepts an absent options argument');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  const anim = createDiv(t).animate(null, 2000);
   assert_equals(anim.id, '');
 }, 'Element.animate() correctly sets the id attribute when no id is specified');
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, { id: 'test' });
+  const anim = createDiv(t).animate(null, { id: 'test' });
   assert_equals(anim.id, 'test');
 }, 'Element.animate() correctly sets the id attribute');
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  const anim = createDiv(t).animate(null, 2000);
   assert_equals(anim.timeline, document.timeline);
 }, 'Element.animate() correctly sets the Animation\'s timeline');
 
 async_test(t => {
   const iframe = document.createElement('iframe');
   iframe.width = 10;
   iframe.height = 10;
 
   iframe.addEventListener('load', t.step_func(() => {
     const div = createDiv(t, iframe.contentDocument);
-    const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+    const anim = div.animate(null, 2000);
     assert_equals(anim.timeline, iframe.contentDocument.timeline);
     iframe.remove();
     t.done();
   }));
 
   document.body.appendChild(iframe);
 }, 'Element.animate() correctly sets the Animation\'s timeline when ' +
    'triggered on an element in a different document');
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  const anim = createDiv(t).animate(null, 2000);
   assert_equals(anim.playState, 'running');
 }, 'Element.animate() calls play on the Animation');
 
 // Tests on CSSPseudoElement
 
 test(t => {
   const pseudoTarget = createPseudo(t, 'before');
   const anim = pseudoTarget.animate(null);
--- a/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html
@@ -70,65 +70,67 @@ test(t => {
 test(t => {
   const div = createDiv(t);
   const animation = div.animate(null, 100 * MS_PER_SEC);
 
   animation.finish();
   assert_array_equals(div.getAnimations(), [],
                       'Animation should not be returned when it is finished');
 
-  animation.effect.timing.duration += 100 * MS_PER_SEC;
+  animation.effect.updateTiming({
+    duration: animation.effect.getTiming().duration + 100 * MS_PER_SEC,
+  });
   assert_array_equals(div.getAnimations(), [animation],
                       'Animation should be returned after extending the'
                       + ' duration');
 
-  animation.effect.timing.duration = 0;
+  animation.effect.updateTiming({ duration: 0 });
   assert_array_equals(div.getAnimations(), [],
                       'Animation should not be returned after setting the'
                       + ' duration to zero');
 }, 'Returns animations based on dynamic changes to individual'
    + ' animations\' duration');
 
 test(t => {
   const div = createDiv(t);
   const animation = div.animate(null, 100 * MS_PER_SEC);
 
-  animation.effect.timing.endDelay = -200 * MS_PER_SEC;
+  animation.effect.updateTiming({ endDelay: -200 * MS_PER_SEC });
   assert_array_equals(div.getAnimations(), [],
                       'Animation should not be returned after setting a'
                       + ' negative end delay such that the end time is less'
                       + ' than the current time');
 
-  animation.effect.timing.endDelay = 100 * MS_PER_SEC;
+  animation.effect.updateTiming({ endDelay: 100 * MS_PER_SEC });
   assert_array_equals(div.getAnimations(), [animation],
                       'Animation should be returned after setting a positive'
                       + ' end delay such that the end time is more than the'
                       + ' current time');
 }, 'Returns animations based on dynamic changes to individual'
    + ' animations\' end delay');
 
 test(t => {
   const div = createDiv(t);
   const animation = div.animate(null, 100 * MS_PER_SEC);
 
   animation.finish();
   assert_array_equals(div.getAnimations(), [],
                       'Animation should not be returned when it is finished');
 
-  animation.effect.timing.iterations = 10;
+  animation.effect.updateTiming({ iterations: 10 });
   assert_array_equals(div.getAnimations(), [animation],
                       'Animation should be returned after inreasing the'
                       + ' number of iterations');
 
-  animation.effect.timing.iterations = 0;
+  animation.effect.updateTiming({ iterations: 0 });
   assert_array_equals(div.getAnimations(), [],
                       'Animations should not be returned after setting the'
                       + ' iteration count to zero');
 
-  animation.effect.timing.iterations = Infinity;
+  animation.effect.updateTiming({ iterations: Infinity });
   assert_array_equals(div.getAnimations(), [animation],
                       'Animation should be returned after inreasing the'
                       + ' number of iterations to infinity');
 }, 'Returns animations based on dynamic changes to individual'
    + ' animations\' iteration count');
 
 test(t => {
   const div = createDiv(t);
--- a/testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html
@@ -24,17 +24,17 @@ promise_test(t => {
                   'transform style is no longer animated after cancelling');
   });
 }, 'Animated style is cleared after calling Animation.cancel()');
 
 test(t => {
   const div = createDiv(t);
   const animation = div.animate({ marginLeft: ['100px', '200px'] },
                                 100 * MS_PER_SEC);
-  animation.effect.timing.easing = 'linear';
+  animation.effect.updateTiming({ easing: 'linear' });
   animation.cancel();
   assert_equals(getComputedStyle(div).marginLeft, '0px',
                 'margin-left style is not animated after cancelling');
 
   animation.currentTime = 50 * MS_PER_SEC;
   assert_equals(getComputedStyle(div).marginLeft, '150px',
                 'margin-left style is updated when cancelled animation is'
                 + ' seeked');
--- a/testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html
@@ -9,17 +9,17 @@
 <div id="log"></div>
 <div id="target"></div>
 <script>
 'use strict';
 
 const gTarget = document.getElementById('target');
 
 function createEffect() {
-  return new KeyframeEffectReadOnly(gTarget, { opacity: [0, 1] });
+  return new KeyframeEffect(gTarget, { opacity: [0, 1] });
 }
 
 function createNull() {
   return null;
 }
 
 const gTestArguments = [
   {
@@ -77,35 +77,34 @@ for (const args of gTestArguments) {
     assert_equals(animation.timeline, args.expectedTimeline,
                   'Animation timeline should be ' + args.expectedTimelineDescription);
     assert_equals(animation.playState, 'idle',
                   'Animation.playState should be initially \'idle\'');
   }, 'Animation can be constructed ' + args.description);
 }
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(null,
-                                            { left: ['10px', '20px'] },
-                                            { duration: 10000,
-                                              fill: 'forwards' });
+  const effect = new KeyframeEffect(null,
+                                    { left: ['10px', '20px'] },
+                                    { duration: 10000, fill: 'forwards' });
   const anim = new Animation(effect, document.timeline);
   anim.pause();
   assert_equals(effect.getComputedTiming().progress, 0.0);
   anim.currentTime += 5000;
   assert_equals(effect.getComputedTiming().progress, 0.5);
   anim.finish();
   assert_equals(effect.getComputedTiming().progress, 1.0);
 }, 'Animation constructed by an effect with null target runs normally');
 
 async_test(t => {
   const iframe = document.createElement('iframe');
 
   iframe.addEventListener('load', t.step_func(() => {
     const div = createDiv(t, iframe.contentDocument);
-    const effect = new KeyframeEffectReadOnly(div, null, 10000);
+    const effect = new KeyframeEffect(div, null, 10000);
     const anim = new Animation(effect);
     assert_equals(anim.timeline, document.timeline);
     iframe.remove();
     t.done();
   }));
 
   document.body.appendChild(iframe);
 }, 'Animation constructed with a keyframe that target element is in iframe');
--- a/testing/web-platform/tests/web-animations/interfaces/Animation/effect.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animation/effect.html
@@ -9,17 +9,17 @@
 <div id="log"></div>
 <script>
 'use strict';
 
 test(t => {
   const anim = new Animation();
   assert_equals(anim.effect, null, 'initial effect is null');
 
-  const newEffect = new KeyframeEffectReadOnly(createDiv(t), null);
+  const newEffect = new KeyframeEffect(createDiv(t), null);
   anim.effect = newEffect;
   assert_equals(anim.effect, newEffect, 'new effect is set');
 }, 'effect is set correctly.');
 
 test(t => {
   const div = createDiv(t);
   const animation = div.animate({ left: ['100px', '100px'] },
                                 { fill: 'forwards' });
--- a/testing/web-platform/tests/web-animations/interfaces/Animation/finished.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animation/finished.html
@@ -184,34 +184,34 @@ promise_test(t => {
   const animation = div.animate({}, 100 * MS_PER_SEC);
   const HALF_DUR = 100 * MS_PER_SEC / 2;
   const QUARTER_DUR = 100 * MS_PER_SEC / 4;
   let gotNextFrame = false;
   let currentTimeBeforeShortening;
   animation.currentTime = HALF_DUR;
   return animation.ready.then(() => {
     currentTimeBeforeShortening = animation.currentTime;
-    animation.effect.timing.duration = QUARTER_DUR;
+    animation.effect.updateTiming({ duration: QUARTER_DUR });
     // Below we use gotNextFrame to check that shortening of the animation
     // duration causes the finished promise to resolve, rather than it just
     // getting resolved on the next animation frame. This relies on the fact
     // that the promises are resolved as a micro-task before the next frame
     // happens.
     waitForAnimationFrames(1).then(() => {
       gotNextFrame = true;
     });
 
     return animation.finished;
   }).then(() => {
     assert_false(gotNextFrame, 'shortening of the animation duration should ' +
                                'resolve the finished promise');
     assert_equals(animation.currentTime, currentTimeBeforeShortening,
                   'currentTime should be unchanged when duration shortened');
     const previousFinishedPromise = animation.finished;
-    animation.effect.timing.duration = 100 * MS_PER_SEC;
+    animation.effect.updateTiming({ duration: 100 * MS_PER_SEC });
     assert_not_equals(animation.finished, previousFinishedPromise,
                       'Finished promise should change after lengthening the ' +
                       'duration causes the animation to become active');
   });
 }, 'Test finished promise changes for animation duration changes');
 
 promise_test(t => {
   const div = createDiv(t);
--- a/testing/web-platform/tests/web-animations/interfaces/Animation/idlharness.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animation/idlharness.html
@@ -5,21 +5,21 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
 <div id="log"></div>
 <script type="text/plain" id="Animation-IDL">
 enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
 
-[Constructor (optional AnimationEffectReadOnly? effect = null,
+[Constructor (optional AnimationEffect? effect = null,
               optional AnimationTimeline? timeline)]
 interface Animation : EventTarget {
              attribute DOMString                id;
-             attribute AnimationEffectReadOnly? effect;
+             attribute AnimationEffect?         effect;
              attribute AnimationTimeline?       timeline;
              attribute double?                  startTime;
              attribute double?                  currentTime;
              attribute double                   playbackRate;
     readonly attribute AnimationPlayState       playState;
     readonly attribute boolean                  pending;
     readonly attribute Promise<Animation>       ready;
     readonly attribute Promise<Animation>       finished;
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffect/getComputedTiming.html
@@ -0,0 +1,199 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>AnimationEffectTiming.getComputedTiming</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffectreadonly-getcomputedtiming">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  const effect = new KeyframeEffect(null, null);
+
+  const ct = effect.getComputedTiming();
+  assert_equals(ct.delay, 0, 'computed delay');
+  assert_equals(ct.fill, 'none', 'computed fill');
+  assert_equals(ct.iterations, 1.0, 'computed iterations');
+  assert_equals(ct.duration, 0, 'computed duration');
+  assert_equals(ct.direction, 'normal', 'computed direction');
+}, 'values of getComputedTiming() when a KeyframeEffect is ' +
+   'constructed without any KeyframeEffectOptions object');
+
+const gGetComputedTimingTests = [
+  { desc:     'an empty KeyframeEffectOptions object',
+    input:    { },
+    expected: { } },
+  { desc:     'a normal KeyframeEffectOptions object',
+    input:    { delay: 1000,
+                fill: 'auto',
+                iterations: 5.5,
+                duration: 'auto',
+                direction: 'alternate' },
+    expected: { delay: 1000,
+                fill: 'none',
+                iterations: 5.5,
+                duration: 0,
+                direction: 'alternate' } },
+  { desc:     'a double value',
+    input:    3000,
+    timing:   { duration: 3000 },
+    expected: { delay: 0,
+                fill: 'none',
+                iterations: 1,
+                duration: 3000,
+                direction: 'normal' } },
+  { desc:     '+Infinity',
+    input:    Infinity,
+    expected: { duration: Infinity } },
+  { desc:     'an Infinity duration',
+    input:    { duration: Infinity },
+    expected: { duration: Infinity } },
+  { desc:     'an auto duration',
+    input:    { duration: 'auto' },
+    expected: { duration: 0 } },
+  { desc:     'an Infinity iterations',
+    input:    { iterations: Infinity },
+    expected: { iterations: Infinity } },
+  { desc:     'an auto fill',
+    input:    { fill: 'auto' },
+    expected: { fill: 'none' } },
+  { desc:     'a forwards fill',
+    input:    { fill: 'forwards' },
+    expected: { fill: 'forwards' } }
+];
+
+for (const stest of gGetComputedTimingTests) {
+  test(t => {
+    const effect = new KeyframeEffect(null, null, stest.input);
+
+    // Helper function to provide default expected values when the test does
+    // not supply them.
+    const expected = (field, defaultValue) => {
+      return field in stest.expected ? stest.expected[field] : defaultValue;
+    };
+
+    const ct = effect.getComputedTiming();
+    assert_equals(ct.delay, expected('delay', 0),
+                  'computed delay');
+    assert_equals(ct.fill, expected('fill', 'none'),
+                  'computed fill');
+    assert_equals(ct.iterations, expected('iterations', 1),
+                  'computed iterations');
+    assert_equals(ct.duration, expected('duration', 0),
+                  'computed duration');
+    assert_equals(ct.direction, expected('direction', 'normal'),
+                  'computed direction');
+
+  }, 'values of getComputedTiming() when a KeyframeEffect is'
+     + ` constructed by ${stest.desc}`);
+}
+
+const gActiveDurationTests = [
+  { desc:     'an empty KeyframeEffectOptions object',
+    input:    { },
+    expected: 0 },
+  { desc:     'a non-zero duration and default iteration count',
+    input:    { duration: 1000 },
+    expected: 1000 },
+  { desc:     'a non-zero duration and integral iteration count',
+    input:    { duration: 1000, iterations: 7 },
+    expected: 7000 },
+  { desc:     'a non-zero duration and fractional iteration count',
+    input:    { duration: 1000, iterations: 2.5 },
+    expected: 2500 },
+  { desc:     'an non-zero duration and infinite iteration count',
+    input:    { duration: 1000, iterations: Infinity },
+    expected: Infinity },
+  { desc:     'an non-zero duration and zero iteration count',
+    input:    { duration: 1000, iterations: 0 },
+    expected: 0 },
+  { desc:     'a zero duration and default iteration count',
+    input:    { duration: 0 },
+    expected: 0 },
+  { desc:     'a zero duration and fractional iteration count',
+    input:    { duration: 0, iterations: 2.5 },
+    expected: 0 },
+  { desc:     'a zero duration and infinite iteration count',
+    input:    { duration: 0, iterations: Infinity },
+    expected: 0 },
+  { desc:     'a zero duration and zero iteration count',
+    input:    { duration: 0, iterations: 0 },
+    expected: 0 },
+  { desc:     'an infinite duration and default iteration count',
+    input:    { duration: Infinity },
+    expected: Infinity },
+  { desc:     'an infinite duration and zero iteration count',
+    input:    { duration: Infinity, iterations: 0 },
+    expected: 0 },
+  { desc:     'an infinite duration and fractional iteration count',
+    input:    { duration: Infinity, iterations: 2.5 },
+    expected: Infinity },
+  { desc:     'an infinite duration and infinite iteration count',
+    input:    { duration: Infinity, iterations: Infinity },
+    expected: Infinity },
+];
+
+for (const stest of gActiveDurationTests) {
+  test(t => {
+    const effect = new KeyframeEffect(null, null, stest.input);
+
+    assert_equals(effect.getComputedTiming().activeDuration,
+                  stest.expected);
+
+  }, `getComputedTiming().activeDuration for ${stest.desc}`);
+}
+
+const gEndTimeTests = [
+  { desc:     'an empty KeyframeEffectOptions object',
+    input:    { },
+    expected: 0 },
+  { desc:     'a non-zero duration and default iteration count',
+    input:    { duration: 1000 },
+    expected: 1000 },
+  { desc:     'a non-zero duration and non-default iteration count',
+    input:    { duration: 1000, iterations: 2.5 },
+    expected: 2500 },
+  { desc:     'a non-zero duration and non-zero delay',
+    input:    { duration: 1000, delay: 1500 },
+    expected: 2500 },
+  { desc:     'a non-zero duration, non-zero delay and non-default iteration',
+    input:    { duration: 1000, delay: 1500, iterations: 2 },
+    expected: 3500 },
+  { desc:     'an infinite iteration count',
+    input:    { duration: 1000, iterations: Infinity },
+    expected: Infinity },
+  { desc:     'an infinite duration',
+    input:    { duration: Infinity, iterations: 10 },
+    expected: Infinity },
+  { desc:     'an infinite duration and delay',
+    input:    { duration: Infinity, iterations: 10, delay: 1000 },
+    expected: Infinity },
+  { desc:     'an infinite duration and negative delay',
+    input:    { duration: Infinity, iterations: 10, delay: -1000 },
+    expected: Infinity },
+  { desc:     'an non-zero duration and negative delay',
+    input:    { duration: 1000, iterations: 2, delay: -1000 },
+    expected: 1000 },
+  { desc:     'an non-zero duration and negative delay greater than active ' +
+              'duration',
+    input:    { duration: 1000, iterations: 2, delay: -3000 },
+    expected: 0 },
+  { desc:     'a zero duration and negative delay',
+    input:    { duration: 0, iterations: 2, delay: -1000 },
+    expected: 0 }
+];
+
+for (const stest of gEndTimeTests) {
+  test(t => {
+    const effect = new KeyframeEffect(null, null, stest.input);
+
+    assert_equals(effect.getComputedTiming().endTime,
+                  stest.expected);
+
+  }, `getComputedTiming().endTime for ${stest.desc}`);
+}
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffect/updateTiming.html
@@ -0,0 +1,475 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>AnimationEffect.updateTiming</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations-1/#dom-animationeffect-updatetiming">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<script src="../../resources/easing-tests.js"></script>
+<script src="../../resources/timing-tests.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+// ------------------------------
+//  delay
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null, 100);
+  anim.effect.updateTiming({ delay: 100 });
+  assert_equals(anim.effect.getTiming().delay, 100, 'set delay 100');
+  assert_equals(anim.effect.getComputedTiming().delay, 100,
+                  'getComputedTiming() after set delay 100');
+}, 'Allows setting the delay to a positive number');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 100);
+  anim.effect.updateTiming({ delay: -100 });
+  assert_equals(anim.effect.getTiming().delay, -100, 'set delay -100');
+  assert_equals(anim.effect.getComputedTiming().delay, -100,
+                'getComputedTiming() after set delay -100');
+}, 'Allows setting the delay to a negative number');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 100);
+  anim.effect.updateTiming({ delay: 100 });
+  assert_equals(anim.effect.getComputedTiming().progress, null);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, null);
+}, 'Allows setting the delay of an animation in progress: positive delay that'
+   + ' causes the animation to be no longer in-effect');
+
+test(t => {
+  const anim = createDiv(t).animate(null, { fill: 'both', duration: 100 });
+  anim.effect.updateTiming({ delay: -50 });
+  assert_equals(anim.effect.getComputedTiming().progress, 0.5);
+}, 'Allows setting the delay of an animation in progress: negative delay that'
+   + ' seeks into the active interval');
+
+test(t => {
+  const anim = createDiv(t).animate(null, { fill: 'both', duration: 100 });
+  anim.effect.updateTiming({ delay: -100 });
+  assert_equals(anim.effect.getComputedTiming().progress, 1);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 0);
+}, 'Allows setting the delay of an animation in progress: large negative delay'
+   + ' that causes the animation to be finished');
+
+for (const invalid of gBadDelayValues) {
+  test(t => {
+    const anim = createDiv(t).animate(null);
+    assert_throws({ name: 'TypeError' }, () => {
+      anim.effect.updateTiming({ delay: invalid });
+    });
+  }, `Throws when setting invalid delay value: ${invalid}`);
+}
+
+
+// ------------------------------
+//  endDelay
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  anim.effect.updateTiming({ endDelay: 123.45 });
+  assert_time_equals_literal(anim.effect.getTiming().endDelay, 123.45,
+                             'set endDelay 123.45');
+  assert_time_equals_literal(anim.effect.getComputedTiming().endDelay, 123.45,
+                             'getComputedTiming() after set endDelay 123.45');
+}, 'Allows setting the endDelay to a positive number');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  anim.effect.updateTiming({ endDelay: -1000 });
+  assert_equals(anim.effect.getTiming().endDelay, -1000, 'set endDelay -1000');
+  assert_equals(anim.effect.getComputedTiming().endDelay, -1000,
+                'getComputedTiming() after set endDelay -1000');
+}, 'Allows setting the endDelay to a negative number');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  assert_throws({ name: 'TypeError' }, () => {
+    anim.effect.updateTiming({ endDelay: Infinity });
+  });
+}, 'Throws when setting the endDelay to infinity');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  assert_throws({ name: 'TypeError' }, () => {
+    anim.effect.updateTiming({ endDelay: -Infinity });
+  });
+}, 'Throws when setting the endDelay to negative infinity');
+
+
+// ------------------------------
+//  fill
+// ------------------------------
+
+for (const fill of ['none', 'forwards', 'backwards', 'both']) {
+  test(t => {
+    const anim = createDiv(t).animate(null, 100);
+    anim.effect.updateTiming({ fill });
+    assert_equals(anim.effect.getTiming().fill, fill, 'set fill ' + fill);
+    assert_equals(anim.effect.getComputedTiming().fill, fill,
+                  'getComputedTiming() after set fill ' + fill);
+  }, `Allows setting the fill to '${fill}'`);
+}
+
+
+// ------------------------------
+//  iterationStart
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterationStart: 0.2,
+                                      iterations: 1,
+                                      fill: 'both',
+                                      duration: 100,
+                                      delay: 1 });
+  anim.effect.updateTiming({ iterationStart: 2.5 });
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
+}, 'Allows setting the iterationStart of an animation in progress:'
+   + ' backwards-filling');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterationStart: 0.2,
+                                      iterations: 1,
+                                      fill: 'both',
+                                      duration: 100,
+                                      delay: 0 });
+  anim.effect.updateTiming({ iterationStart: 2.5 });
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
+}, 'Allows setting the iterationStart of an animation in progress:'
+   + ' active phase');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterationStart: 0.2,
+                                      iterations: 1,
+                                      fill: 'both',
+                                      duration: 100,
+                                      delay: 0 });
+  anim.finish();
+  anim.effect.updateTiming({ iterationStart: 2.5 });
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 3);
+}, 'Allows setting the iterationStart of an animation in progress:'
+   + ' forwards-filling');
+
+for (const invalid of gBadIterationStartValues) {
+  test(t => {
+    const anim = createDiv(t).animate(null);
+    assert_throws({ name: 'TypeError' }, () => {
+      anim.effect.updateTiming({ iterationStart: invalid });
+    }, `setting ${invalid}`);
+  }, `Throws when setting invalid iterationStart value: ${invalid}`);
+}
+
+// ------------------------------
+//  iterations
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  anim.effect.updateTiming({ iterations: 2 });
+  assert_equals(anim.effect.getTiming().iterations, 2, 'set duration 2');
+  assert_equals(anim.effect.getComputedTiming().iterations, 2,
+                       'getComputedTiming() after set iterations 2');
+}, 'Allows setting iterations to a double value');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  anim.effect.updateTiming({ iterations: Infinity });
+  assert_equals(anim.effect.getTiming().iterations, Infinity,
+                'set duration Infinity');
+  assert_equals(anim.effect.getComputedTiming().iterations, Infinity,
+                       'getComputedTiming() after set iterations Infinity');
+}, 'Allows setting iterations to infinity');
+
+for (const invalid of gBadIterationsValues) {
+  test(t => {
+    const anim = createDiv(t).animate(null);
+    assert_throws({ name: 'TypeError' }, () => {
+      anim.effect.updateTiming({ iterations: invalid });
+    });
+  }, `Throws when setting invalid iterations value: ${invalid}`);
+}
+
+test(t => {
+  const anim = createDiv(t).animate(null, { duration: 100000, fill: 'both' });
+
+  anim.finish();
+
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress when animation is finished');
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+                'current iteration when animation is finished');
+
+  anim.effect.updateTiming({ iterations: 2 });
+
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress,
+                             0,
+                             'progress after adding an iteration');
+  assert_time_equals_literal(anim.effect.getComputedTiming().currentIteration,
+                             1,
+                             'current iteration after adding an iteration');
+
+  anim.effect.updateTiming({ iterations: 0 });
+
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'progress after setting iterations to zero');
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+                'current iteration after setting iterations to zero');
+
+  anim.effect.updateTiming({ iterations: Infinity });
+
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'progress after setting iterations to Infinity');
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 1,
+                'current iteration after setting iterations to Infinity');
+}, 'Allows setting the iterations of an animation in progress');
+
+
+// ------------------------------
+//  duration
+// ------------------------------
+
+for (const duration of gGoodDurationValues) {
+  test(t => {
+    const anim = createDiv(t).animate(null, 2000);
+    anim.effect.updateTiming({ duration: duration.specified });
+    if (typeof duration.specified === 'number') {
+      assert_time_equals_literal(anim.effect.getTiming().duration,
+                                 duration.specified,
+                                 'Updates specified duration');
+    } else {
+      assert_equals(anim.effect.getTiming().duration, duration.specified,
+                    'Updates specified duration');
+    }
+    assert_time_equals_literal(anim.effect.getComputedTiming().duration,
+                               duration.computed,
+                               'Updates computed duration');
+  }, `Allows setting the duration to ${duration.specified}`);
+}
+
+for (const invalid of gBadDurationValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { duration: invalid });
+    });
+  }, 'Throws when setting invalid duration: '
+     + (typeof invalid === 'string' ? `"${invalid}"` : invalid));
+}
+
+test(t => {
+  const anim = createDiv(t).animate(null, { duration: 100000, fill: 'both' });
+  anim.finish();
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress when animation is finished');
+  anim.effect.updateTiming({ duration: anim.effect.getTiming().duration * 2 });
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5,
+                             'progress after doubling the duration');
+  anim.effect.updateTiming({ duration: 0 });
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress after setting duration to zero');
+  anim.effect.updateTiming({ duration: 'auto' });
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress after setting duration to \'auto\'');
+}, 'Allows setting the duration of an animation in progress');
+
+promise_test(t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  return anim.ready.then(() => {
+    const originalStartTime   = anim.startTime;
+    const originalCurrentTime = anim.currentTime;
+    assert_time_equals_literal(
+      anim.effect.getComputedTiming().duration,
+      100 * MS_PER_SEC,
+      'Initial duration should be as set on KeyframeEffect'
+    );
+
+    anim.effect.updateTiming({ duration: 200 * MS_PER_SEC });
+    assert_time_equals_literal(
+      anim.effect.getComputedTiming().duration,
+      200 * MS_PER_SEC,
+      'Effect duration should have been updated'
+    );
+    assert_times_equal(anim.startTime, originalStartTime,
+                       'startTime should be unaffected by changing effect ' +
+                       'duration');
+    assert_times_equal(anim.currentTime, originalCurrentTime,
+                       'currentTime should be unaffected by changing effect ' +
+                       'duration');
+  });
+}, 'Allows setting the duration of an animation in progress such that the' +
+   ' the start and current time do not change');
+
+
+// ------------------------------
+//  direction
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+
+  const directions = ['normal', 'reverse', 'alternate', 'alternate-reverse'];
+  for (const direction of directions) {
+    anim.effect.updateTiming({ direction: direction });
+    assert_equals(anim.effect.getTiming().direction, direction,
+                  `set direction to ${direction}`);
+  }
+}, 'Allows setting the direction to each of the possible keywords');
+
+test(t => {
+  const anim = createDiv(t).animate(null, {
+    duration: 10000,
+    direction: 'normal',
+  });
+  anim.currentTime = 7000;
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
+                             'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'reverse' });
+
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
+                             'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'normal\' to'
+   + ' \'reverse\'');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { duration: 10000, direction: 'normal' });
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'reverse' });
+
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'normal\' to'
+   + ' \'reverse\' while at start of active interval');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { fill: 'backwards',
+                                      duration: 10000,
+                                      delay: 10000,
+                                      direction: 'normal' });
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'reverse' });
+
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'normal\' to'
+   + ' \'reverse\' while filling backwards');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterations: 2,
+                                      duration: 10000,
+                                      direction: 'normal' });
+  anim.currentTime = 17000;
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
+                             'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'alternate' });
+
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
+                             'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'normal\' to'
+   + ' \'alternate\'');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterations: 2,
+                                      duration: 10000,
+                                      direction: 'alternate' });
+  anim.currentTime = 17000;
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
+                             'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'alternate-reverse' });
+
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
+                             'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'alternate\''
+   + ' to \'alternate-reverse\'');
+
+
+// ------------------------------
+//  easing
+// ------------------------------
+
+function assert_progress(animation, currentTime, easingFunction) {
+  animation.currentTime = currentTime;
+  const portion = currentTime / animation.effect.getTiming().duration;
+  assert_approx_equals(animation.effect.getComputedTiming().progress,
+                       easingFunction(portion),
+                       0.01,
+                       'The progress of the animation should be approximately'
+                       + ` ${easingFunction(portion)} at ${currentTime}ms`);
+}
+
+for (const options of gEasingTests) {
+  test(t => {
+    const target = createDiv(t);
+    const anim = target.animate(null,
+                                { duration: 1000 * MS_PER_SEC,
+                                  fill: 'forwards' });
+    anim.effect.updateTiming({ easing: options.easing });
+    assert_equals(anim.effect.getTiming().easing,
+                  options.serialization || options.easing);
+
+    const easing = options.easingFunction;
+    assert_progress(anim, 0, easing);
+    assert_progress(anim, 250 * MS_PER_SEC, easing);
+    assert_progress(anim, 500 * MS_PER_SEC, easing);
+    assert_progress(anim, 750 * MS_PER_SEC, easing);
+    assert_progress(anim, 1000 * MS_PER_SEC, easing);
+  }, `Allows setting the easing to a ${options.desc}`);
+}
+
+for (const easing of gRoundtripEasings) {
+  test(t => {
+    const anim = createDiv(t).animate(null);
+    anim.effect.updateTiming({ easing: easing });
+    assert_equals(anim.effect.getTiming().easing, easing);
+  }, `Updates the specified value when setting the easing to '${easing}'`);
+}
+
+test(t => {
+  const delay = 1000 * MS_PER_SEC;
+
+  const target = createDiv(t);
+  const anim = target.animate(null,
+                              { duration: 1000 * MS_PER_SEC,
+                                fill: 'both',
+                                delay: delay,
+                                easing: 'steps(2, start)' });
+
+  anim.effect.updateTiming({ easing: 'steps(2, end)' });
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'easing replace to steps(2, end) at before phase');
+
+  anim.currentTime = delay + 750 * MS_PER_SEC;
+  assert_equals(anim.effect.getComputedTiming().progress, 0.5,
+                'change currentTime to active phase');
+
+  anim.effect.updateTiming({ easing: 'steps(2, start)' });
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'easing replace to steps(2, start) at active phase');
+
+  anim.currentTime = delay + 1500 * MS_PER_SEC;
+  anim.effect.updateTiming({ easing: 'steps(2, end)' });
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'easing replace to steps(2, end) again at after phase');
+}, 'Allows setting the easing of an animation in progress');
+
+</script>
+</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.delay</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffecttiming-delay">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.delay, 0);
-}, 'Has the default value 0');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 100);
-  anim.effect.timing.delay = 100;
-  assert_equals(anim.effect.timing.delay, 100, 'set delay 100');
-  assert_equals(anim.effect.getComputedTiming().delay, 100,
-                  'getComputedTiming() after set delay 100');
-}, 'Can be set to a positive number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 100);
-  anim.effect.timing.delay = -100;
-  assert_equals(anim.effect.timing.delay, -100, 'set delay -100');
-  assert_equals(anim.effect.getComputedTiming().delay, -100,
-                'getComputedTiming() after set delay -100');
-}, 'Can be set to a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 100);
-  anim.effect.timing.delay = 100;
-  assert_equals(anim.effect.getComputedTiming().progress, null);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, null);
-}, 'Can set a positive delay on an animation without a backwards fill to'
-   + ' make it no longer active');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { fill: 'both',
-                             duration: 100 });
-  anim.effect.timing.delay = -50;
-  assert_equals(anim.effect.getComputedTiming().progress, 0.5);
-}, 'Can set a negative delay to seek into the active interval');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { fill: 'both',
-                             duration: 100 });
-  anim.effect.timing.delay = -100;
-  assert_equals(anim.effect.getComputedTiming().progress, 1);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 0);
-}, 'Can set a large negative delay to finishing an animation');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null);
-  for (let invalid of [NaN, Infinity]) {
-    assert_throws({ name: 'TypeError' }, () => {
-      anim.effect.timing.delay = invalid;
-    }, `setting ${invalid}`);
-    assert_throws({ name: 'TypeError' }, () => {
-      div.animate({}, { delay: invalid });
-    }, `animate() with ${invalid}`);
-  }
-}, 'Throws when setting invalid values');
-
-</script>
-</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html
+++ /dev/null
@@ -1,108 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.direction</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffecttiming-direction">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.direction, 'normal');
-}, 'Has the default value \'normal\'');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-
-  const directions = ['normal', 'reverse', 'alternate', 'alternate-reverse'];
-  for (const direction of directions) {
-    anim.effect.timing.direction = direction;
-    assert_equals(anim.effect.timing.direction, direction,
-                  `set direction to ${direction}`);
-  }
-}, 'Can be set to each of the possible keywords');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null, { duration: 10000, direction: 'normal' });
-  anim.currentTime = 7000;
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
-                             'progress before updating direction');
-
-  anim.effect.timing.direction = 'reverse';
-
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
-                             'progress after updating direction');
-}, 'Can be changed from \'normal\' to \'reverse\' while in progress');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { duration: 10000,
-                             direction: 'normal' });
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'progress before updating direction');
-
-  anim.effect.timing.direction = 'reverse';
-
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress after updating direction');
-}, 'Can be changed from \'normal\' to \'reverse\' while at start of active'
-   + ' interval');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { fill: 'backwards',
-                             duration: 10000,
-                             delay: 10000,
-                             direction: 'normal' });
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'progress before updating direction');
-
-  anim.effect.timing.direction = 'reverse';
-
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress after updating direction');
-}, 'Can be changed from \'normal\' to \'reverse\' while filling backwards');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterations: 2,
-                             duration: 10000,
-                             direction: 'normal' });
-  anim.currentTime = 17000;
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
-                             'progress before updating direction');
-
-  anim.effect.timing.direction = 'alternate';
-
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
-                             'progress after updating direction');
-}, 'Can be changed from \'normal\' to \'alternate\' while in progress');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterations: 2,
-                             duration: 10000,
-                             direction: 'alternate' });
-  anim.currentTime = 17000;
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
-                             'progress before updating direction');
-
-  anim.effect.timing.direction = 'alternate-reverse';
-
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
-                             'progress after updating direction');
-}, 'Can be changed from \'alternate\' to \'alternate-reverse\' while in'
-   + ' progress');
-
-</script>
-</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html
+++ /dev/null
@@ -1,190 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.duration</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffecttiming-duration">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.duration, 'auto');
-}, 'Has the default value \'auto\'');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.duration = 123.45;
-  assert_time_equals_literal(anim.effect.timing.duration, 123.45,
-                             'set duration 123.45');
-  assert_time_equals_literal(anim.effect.getComputedTiming().duration, 123.45,
-                             'getComputedTiming() after set duration 123.45');
-}, 'Can be set to a double value');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.duration = 'auto';
-  assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\'');
-  assert_equals(anim.effect.getComputedTiming().duration, 0,
-                'getComputedTiming() after set duration \'auto\'');
-}, 'Can be set to the string \'auto\'');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 'auto' });
-  assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\'');
-  assert_equals(anim.effect.getComputedTiming().duration, 0,
-                'getComputedTiming() after set duration \'auto\'');
-}, 'Can be set to \'auto\' using a dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.duration = Infinity;
-  assert_equals(anim.effect.timing.duration, Infinity, 'set duration Infinity');
-  assert_equals(anim.effect.getComputedTiming().duration, Infinity,
-                'getComputedTiming() after set duration Infinity');
-}, 'Can be set to Infinity');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, -1);
-  });
-}, 'animate() throws when passed a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, -Infinity);
-  });
-}, 'animate() throws when passed negative Infinity');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, NaN);
-  });
-}, 'animate() throws when passed a NaN value');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: -1 });
-  });
-}, 'animate() throws when passed a negative number using a dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: -Infinity });
-  });
-}, 'animate() throws when passed negative Infinity using a dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: NaN });
-  });
-}, 'animate() throws when passed a NaN value using a dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: 'abc' });
-  });
-}, 'animate() throws when passed a string other than \'auto\' using a'
-   + ' dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: '100' });
-  });
-}, 'animate() throws when passed a string containing a number using a'
-   + ' dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = -1;
-  });
-}, 'Throws when setting a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = -Infinity;
-  });
-}, 'Throws when setting negative infinity');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = NaN;
-  });
-}, 'Throws when setting a NaN value');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = 'abc';
-  });
-}, 'Throws when setting a string other than \'auto\'');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = '100';
-  });
-}, 'Throws when setting a string containing a number');
-
-promise_test(t => {
-  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  return anim.ready.then(() => {
-    const originalStartTime   = anim.startTime;
-    const originalCurrentTime = anim.currentTime;
-    assert_equals(anim.effect.getComputedTiming().duration, 100 * MS_PER_SEC,
-                  'Initial duration should be as set on KeyframeEffect');
-
-    anim.effect.timing.duration = 200 * MS_PER_SEC;
-    assert_equals(anim.effect.getComputedTiming().duration, 200 * MS_PER_SEC,
-                  'Effect duration should have been updated');
-    assert_times_equal(anim.startTime, originalStartTime,
-                       'startTime should be unaffected by changing effect ' +
-                       'duration');
-    assert_times_equal(anim.currentTime, originalCurrentTime,
-                       'currentTime should be unaffected by changing effect ' +
-                       'duration');
-  });
-}, 'Extending an effect\'s duration does not change the start or current time');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null, { duration: 100000, fill: 'both' });
-  anim.finish();
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress when animation is finished');
-  anim.effect.timing.duration *= 2;
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5,
-                             'progress after doubling the duration');
-  anim.effect.timing.duration = 0;
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress after setting duration to zero');
-  anim.effect.timing.duration = 'auto';
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress after setting duration to \'auto\'');
-}, 'Can be updated while the animation is in progress');
-
-</script>
-</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html
+++ /dev/null
@@ -1,96 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.easing</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffecttiming-easing">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<script src="../../resources/easing-tests.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.easing, 'linear');
-}, 'Has the default value \'linear\'');
-
-function assert_progress(animation, currentTime, easingFunction) {
-  animation.currentTime = currentTime;
-  const portion = currentTime / animation.effect.timing.duration;
-  assert_approx_equals(animation.effect.getComputedTiming().progress,
-                       easingFunction(portion),
-                       0.01,
-                       'The progress of the animation should be approximately'
-                       + ` ${easingFunction(portion)} at ${currentTime}ms`);
-}
-
-for (const options of gEasingTests) {
-  test(t => {
-    const target = createDiv(t);
-    const anim = target.animate([ { opacity: 0 }, { opacity: 1 } ],
-                                { duration: 1000 * MS_PER_SEC,
-                                  fill: 'forwards' });
-    anim.effect.timing.easing = options.easing;
-    assert_equals(anim.effect.timing.easing,
-                  options.serialization || options.easing);
-
-    const easing = options.easingFunction;
-    assert_progress(anim, 0, easing);
-    assert_progress(anim, 250 * MS_PER_SEC, easing);
-    assert_progress(anim, 500 * MS_PER_SEC, easing);
-    assert_progress(anim, 750 * MS_PER_SEC, easing);
-    assert_progress(anim, 1000 * MS_PER_SEC, easing);
-  }, options.desc);
-}
-
-for (const invalidEasing of gInvalidEasings) {
-  test(t => {
-    const div = createDiv(t);
-    const anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
-    assert_throws({ name: 'TypeError' },
-                  () => {
-                    anim.effect.timing.easing = invalidEasing;
-                  });
-  }, `Throws on invalid easing: '${invalidEasing}'`);
-}
-
-for (const easing of gRoundtripEasings) {
-  test(t => {
-    const anim = createDiv(t).animate(null);
-    anim.effect.timing.easing = easing;
-    assert_equals(anim.effect.timing.easing, easing);
-  }, `Canonical easing '${easing}' is returned as set`);
-}
-
-test(t => {
-  const delay = 1000 * MS_PER_SEC;
-
-  const target = createDiv(t);
-  const anim = target.animate([ { opacity: 0 }, { opacity: 1 } ],
-                              { duration: 1000 * MS_PER_SEC,
-                                fill: 'both',
-                                delay: delay,
-                                easing: 'steps(2, start)' });
-
-  anim.effect.timing.easing = 'steps(2, end)';
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'easing replace to steps(2, end) at before phase');
-
-  anim.currentTime = delay + 750 * MS_PER_SEC;
-  assert_equals(anim.effect.getComputedTiming().progress, 0.5,
-                'change currentTime to active phase');
-
-  anim.effect.timing.easing = 'steps(2, start)';
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'easing replace to steps(2, start) at active phase');
-
-  anim.currentTime = delay + 1500 * MS_PER_SEC;
-  anim.effect.timing.easing = 'steps(2, end)';
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'easing replace to steps(2, end) again at after phase');
-}, 'Allows the easing to be changed while the animation is in progress');
-
-</script>
-</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html
+++ /dev/null
@@ -1,89 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.endDelay</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffecttiming-enddelay">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.endDelay, 0);
-}, 'Has the default value 0');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.endDelay = 123.45;
-  assert_time_equals_literal(anim.effect.timing.endDelay, 123.45,
-                             'set endDelay 123.45');
-  assert_time_equals_literal(anim.effect.getComputedTiming().endDelay, 123.45,
-                             'getComputedTiming() after set endDelay 123.45');
-}, 'Can be set to a positive number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.endDelay = -1000;
-  assert_equals(anim.effect.timing.endDelay, -1000, 'set endDelay -1000');
-  assert_equals(anim.effect.getComputedTiming().endDelay, -1000,
-                'getComputedTiming() after set endDelay -1000');
-}, 'Can be set to a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.endDelay = Infinity;
-  }, 'we can not assign Infinity to timing.endDelay');
-}, 'Throws when setting infinity');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.endDelay = -Infinity;
-  }, 'we can not assign negative Infinity to timing.endDelay');
-}, 'Throws when setting negative infinity');
-
-async_test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { duration: 100000, endDelay: 50000 });
-  anim.onfinish = t.step_func(event => {
-    assert_unreached('finish event should not be fired');
-  });
-
-  anim.ready.then(() => {
-    anim.currentTime = 100000;
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(() => {
-    t.done();
-  }));
-}, 'finish event is not fired at the end of the active interval when the'
-   + ' endDelay has not expired');
-
-async_test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { duration: 100000, endDelay: 30000 });
-  anim.ready.then(() => {
-    anim.currentTime = 110000; // during endDelay
-    anim.onfinish = t.step_func(event => {
-      assert_unreached('onfinish event should not be fired during endDelay');
-    });
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(() => {
-    anim.onfinish = t.step_func(event => {
-      t.done();
-    });
-    anim.currentTime = 130000; // after endTime
-  }));
-}, 'finish event is fired after the endDelay has expired');
-
-</script>
-</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.fill</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffecttiming-fill">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.fill, 'auto');
-}, 'Has the default value \'auto\'');
-
-for (const fill of ['none', 'forwards', 'backwards', 'both']) {
-  test(t => {
-    const div = createDiv(t);
-    const anim = div.animate({ opacity: [ 0, 1 ] }, 100);
-    anim.effect.timing.fill = fill;
-    assert_equals(anim.effect.timing.fill, fill, 'set fill ' + fill);
-    assert_equals(anim.effect.getComputedTiming().fill, fill,
-                  'getComputedTiming() after set fill ' + fill);
-  }, `Can set fill to ${fill}`);
-}
-
-</script>
-</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html
+++ /dev/null
@@ -1,201 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.getComputedTiming</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffectreadonly-getcomputedtiming">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const effect = new KeyframeEffect(null, null);
-
-  const ct = effect.getComputedTiming();
-  assert_equals(ct.delay, 0, 'computed delay');
-  assert_equals(ct.fill, 'none', 'computed fill');
-  assert_equals(ct.iterations, 1.0, 'computed iterations');
-  assert_equals(ct.duration, 0, 'computed duration');
-  assert_equals(ct.direction, 'normal', 'computed direction');
-}, 'values of getComputedTiming() when a KeyframeEffect is ' +
-   'constructed without any KeyframeEffectOptions object');
-
-const gGetComputedTimingTests = [
-  { desc:     'an empty KeyframeEffectOptions object',
-    input:    { },
-    expected: { } },
-  { desc:     'a normal KeyframeEffectOptions object',
-    input:    { delay: 1000,
-                fill: 'auto',
-                iterations: 5.5,
-                duration: 'auto',
-                direction: 'alternate' },
-    expected: { delay: 1000,
-                fill: 'none',
-                iterations: 5.5,
-                duration: 0,
-                direction: 'alternate' } },
-  { desc:     'a double value',
-    input:    3000,
-    timing:   { duration: 3000 },
-    expected: { delay: 0,
-                fill: 'none',
-                iterations: 1,
-                duration: 3000,
-                direction: 'normal' } },
-  { desc:     '+Infinity',
-    input:    Infinity,
-    expected: { duration: Infinity } },
-  { desc:     'an Infinity duration',
-    input:    { duration: Infinity },
-    expected: { duration: Infinity } },
-  { desc:     'an auto duration',
-    input:    { duration: 'auto' },
-    expected: { duration: 0 } },
-  { desc:     'an Infinity iterations',
-    input:    { iterations: Infinity },
-    expected: { iterations: Infinity } },
-  { desc:     'an auto fill',
-    input:    { fill: 'auto' },
-    expected: { fill: 'none' } },
-  { desc:     'a forwards fill',
-    input:    { fill: 'forwards' },
-    expected: { fill: 'forwards' } }
-];
-
-for (const stest of gGetComputedTimingTests) {
-  test(t => {
-    const effect = new KeyframeEffect(null, null, stest.input);
-
-    // Helper function to provide default expected values when the test does
-    // not supply them.
-    const expected = (field, defaultValue) => {
-      return field in stest.expected ? stest.expected[field] : defaultValue;
-    };
-
-    const ct = effect.getComputedTiming();
-    assert_equals(ct.delay, expected('delay', 0),
-                  'computed delay');
-    assert_equals(ct.fill, expected('fill', 'none'),
-                  'computed fill');
-    assert_equals(ct.iterations, expected('iterations', 1),
-                  'computed iterations');
-    assert_equals(ct.duration, expected('duration', 0),
-                  'computed duration');
-    assert_equals(ct.direction, expected('direction', 'normal'),
-                  'computed direction');
-
-  }, 'values of getComputedTiming() when a KeyframeEffect is'
-     + ` constructed by ${stest.desc}`);
-}
-
-const gActiveDurationTests = [
-  { desc:     'an empty KeyframeEffectOptions object',
-    input:    { },
-    expected: 0 },
-  { desc:     'a non-zero duration and default iteration count',
-    input:    { duration: 1000 },
-    expected: 1000 },
-  { desc:     'a non-zero duration and integral iteration count',
-    input:    { duration: 1000, iterations: 7 },
-    expected: 7000 },
-  { desc:     'a non-zero duration and fractional iteration count',
-    input:    { duration: 1000, iterations: 2.5 },
-    expected: 2500 },
-  { desc:     'an non-zero duration and infinite iteration count',
-    input:    { duration: 1000, iterations: Infinity },
-    expected: Infinity },
-  { desc:     'an non-zero duration and zero iteration count',
-    input:    { duration: 1000, iterations: 0 },
-    expected: 0 },
-  { desc:     'a zero duration and default iteration count',
-    input:    { duration: 0 },
-    expected: 0 },
-  { desc:     'a zero duration and fractional iteration count',
-    input:    { duration: 0, iterations: 2.5 },
-    expected: 0 },
-  { desc:     'a zero duration and infinite iteration count',
-    input:    { duration: 0, iterations: Infinity },
-    expected: 0 },
-  { desc:     'a zero duration and zero iteration count',
-    input:    { duration: 0, iterations: 0 },
-    expected: 0 },
-  { desc:     'an infinite duration and default iteration count',
-    input:    { duration: Infinity },
-    expected: Infinity },
-  { desc:     'an infinite duration and zero iteration count',
-    input:    { duration: Infinity, iterations: 0 },
-    expected: 0 },
-  { desc:     'an infinite duration and fractional iteration count',
-    input:    { duration: Infinity, iterations: 2.5 },
-    expected: Infinity },
-  { desc:     'an infinite duration and infinite iteration count',
-    input:    { duration: Infinity, iterations: Infinity },
-    expected: Infinity },
-];
-
-for (const stest of gActiveDurationTests) {
-  test(t => {
-    const effect = new KeyframeEffect(null, null, stest.input);
-
-    assert_equals(effect.getComputedTiming().activeDuration,
-                  stest.expected);
-
-  }, `getComputedTiming().activeDuration for ${stest.desc}`);
-}
-
-const gEndTimeTests = [
-  { desc:     'an empty KeyframeEffectOptions object',
-    input:    { },
-    expected: 0 },
-  { desc:     'a non-zero duration and default iteration count',
-    input:    { duration: 1000 },
-    expected: 1000 },
-  { desc:     'a non-zero duration and non-default iteration count',
-    input:    { duration: 1000, iterations: 2.5 },
-    expected: 2500 },
-  { desc:     'a non-zero duration and non-zero delay',
-    input:    { duration: 1000, delay: 1500 },
-    expected: 2500 },
-  { desc:     'a non-zero duration, non-zero delay and non-default iteration',
-    input:    { duration: 1000, delay: 1500, iterations: 2 },
-    expected: 3500 },
-  { desc:     'an infinite iteration count',
-    input:    { duration: 1000, iterations: Infinity },
-    expected: Infinity },
-  { desc:     'an infinite duration',
-    input:    { duration: Infinity, iterations: 10 },
-    expected: Infinity },
-  { desc:     'an infinite duration and delay',
-    input:    { duration: Infinity, iterations: 10, delay: 1000 },
-    expected: Infinity },
-  { desc:     'an infinite duration and negative delay',
-    input:    { duration: Infinity, iterations: 10, delay: -1000 },
-    expected: Infinity },
-  { desc:     'an non-zero duration and negative delay',
-    input:    { duration: 1000, iterations: 2, delay: -1000 },
-    expected: 1000 },
-  { desc:     'an non-zero duration and negative delay greater than active ' +
-              'duration',
-    input:    { duration: 1000, iterations: 2, delay: -3000 },
-    expected: 0 },
-  { desc:     'a zero duration and negative delay',
-    input:    { duration: 0, iterations: 2, delay: -1000 },
-    expected: 0 }
-];
-
-for (const stest of gEndTimeTests) {
-  test(t => {
-    const effect = new KeyframeEffect(null, null, stest.input);
-
-    assert_equals(effect.getComputedTiming().endTime,
-                  stest.expected);
-
-  }, `getComputedTiming().endTime for ${stest.desc}`);
-}
-
-done();
-</script>
-</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/idlharness.html
+++ /dev/null
@@ -1,80 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming and AnimationEffectTimingReadOnly IDL</title>
-<link rel="help"
-      href="https://drafts.csswg.org/web-animations/#animationeffecttiming">
-<link rel="help"
-      href="https://drafts.csswg.org/web-animations/#animationeffecttimingreadonly">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/resources/idlharness.js"></script>
-<div id="log"></div>
-<script type="text/plain" id="AnimationEffectTimingReadOnly-IDL">
-enum FillMode { "none", "forwards", "backwards", "both", "auto" };
-enum PlaybackDirection {
-  "normal",
-  "reverse",
-  "alternate",
-  "alternate-reverse"
-};
-
-dictionary AnimationEffectTimingProperties {
-  double                             delay = 0.0;
-  double                             endDelay = 0.0;
-  FillMode                           fill = "auto";
-  double                             iterationStart = 0.0;
-  unrestricted double                iterations = 1.0;
-  (unrestricted double or DOMString) duration = "auto";
-  PlaybackDirection                  direction = "normal";
-  DOMString                          easing = "linear";
-};
-
-[Exposed=Window]
-interface AnimationEffectTimingReadOnly {
-  readonly attribute double                             delay;
-  readonly attribute double                             endDelay;
-  readonly attribute FillMode                           fill;
-  readonly attribute double                             iterationStart;
-  readonly attribute unrestricted double                iterations;
-  readonly attribute (unrestricted double or DOMString) duration;
-  readonly attribute PlaybackDirection                  direction;
-  readonly attribute DOMString                          easing;
-};
-</script>
-<script type="text/plain" id="AnimationEffectTiming-IDL">
-[Exposed=Window]
-interface AnimationEffectTiming : AnimationEffectTimingReadOnly {
-  inherit attribute double                             delay;
-  inherit attribute double                             endDelay;
-  inherit attribute FillMode                           fill;
-  inherit attribute double                             iterationStart;
-  inherit attribute unrestricted double                iterations;
-  inherit attribute (unrestricted double or DOMString) duration;
-  inherit attribute PlaybackDirection                  direction;
-  inherit attribute DOMString                          easing;
-};
-</script>
-<script>
-'use strict';
-
-const idlArray = new IdlArray();
-
-idlArray.add_idls(
-  document.getElementById('AnimationEffectTimingReadOnly-IDL').textContent
-);
-idlArray.add_idls(
-  document.getElementById('AnimationEffectTiming-IDL').textContent
-);
-
-idlArray.add_objects({
-  AnimationEffectTiming: [
-    '(new KeyframeEffect(null, null)).timing'
-  ],
-  AnimationEffectTimingReadOnly: [
-    '(new KeyframeEffectReadOnly(null, null)).timing'
-  ],
-});
-idlArray.test();
-
-</script>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html
+++ /dev/null
@@ -1,72 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.iterationStart</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffecttiming-iterationstart">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.iterationStart, 0);
-}, 'Has the default value 0');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterationStart: 0.2,
-                             iterations: 1,
-                             fill: 'both',
-                             duration: 100,
-                             delay: 1 });
-  anim.effect.timing.iterationStart = 2.5;
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
-}, 'Changing the value updates computed timing when backwards-filling');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterationStart: 0.2,
-                             iterations: 1,
-                             fill: 'both',
-                             duration: 100,
-                             delay: 0 });
-  anim.effect.timing.iterationStart = 2.5;
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
-}, 'Changing the value updates computed timing during the active phase');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterationStart: 0.2,
-                             iterations: 1,
-                             fill: 'both',
-                             duration: 100,
-                             delay: 0 });
-  anim.finish();
-  anim.effect.timing.iterationStart = 2.5;
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 3);
-}, 'Changing the value updates computed timing when forwards-filling');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null);
-  for (let invalid of [-1, NaN, Infinity]) {
-    assert_throws({ name: 'TypeError' }, () => {
-      anim.effect.timing.iterationStart = invalid;
-    }, `setting ${invalid}`);
-    assert_throws({ name: 'TypeError' }, () => {
-      div.animate({}, { iterationStart: invalid });
-    }, `animate() with ${invalid}`);
-  }
-}, 'Throws when setting invalid values');
-
-</script>
-</body>
deleted file mode 100644
--- a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html
+++ /dev/null
@@ -1,96 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.iterations</title>
-<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffecttiming-iterations">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.iterations, 1);
-}, 'Has the default value 1');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.iterations = 2;
-  assert_equals(anim.effect.timing.iterations, 2, 'set duration 2');
-  assert_equals(anim.effect.getComputedTiming().iterations, 2,
-                       'getComputedTiming() after set iterations 2');
-}, 'Can be set to a double value');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.iterations = Infinity;
-  assert_equals(anim.effect.timing.iterations, Infinity, 'set duration Infinity');
-  assert_equals(anim.effect.getComputedTiming().iterations, Infinity,
-                       'getComputedTiming() after set iterations Infinity');
-}, 'Can be set to infinity');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.iterations = -1;
-  });
-}, 'Throws when setting a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.iterations = -Infinity;
-  });
-}, 'Throws when setting negative infinity');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.iterations = NaN;
-  });
-}, 'Throws when setting a NaN value');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null, { duration: 100000, fill: 'both' });
-
-  anim.finish();
-
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress when animation is finished');
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
-                'current iteration when animation is finished');
-
-  anim.effect.timing.iterations = 2;
-
-  assert_time_equals_literal(anim.effect.getComputedTiming().progress,
-                             0,
-                             'progress after adding an iteration');
-  assert_time_equals_literal(anim.effect.getComputedTiming().currentIteration,
-                             1,
-                             'current iteration after adding an iteration');
-
-  anim.effect.timing.iterations = 0;
-
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'progress after setting iterations to zero');
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
-                'current iteration after setting iterations to zero');
-
-  anim.effect.timing.iterations = Infinity;
-
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'progress after setting iterations to Infinity');
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 1,
-                'current iteration after setting iterations to Infinity');
-}, 'Can be updated while the animation is in progress');
-
-</script>
-</body>
--- a/testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html
@@ -37,17 +37,17 @@ test(t => {
   const anim1 = div.animate(gKeyFrames, 100 * MS_PER_SEC);
   const anim2 = div.animate(gKeyFrames, 100 * MS_PER_SEC);
   assert_array_equals(document.getAnimations(),
                       [ anim1, anim2 ],
                       'getAnimations() returns running animations');
 }, 'Test the order of document.getAnimations with script generated animations')
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC);
+  const effect = new KeyframeEffect(null, gKeyFrames, 100 * MS_PER_SEC);
   const anim = new Animation(effect, document.timeline);
   anim.play();
 
   assert_equals(document.getAnimations().length, 0,
                 'document.getAnimations() only returns animations targeting ' +
                 'elements in this document');
 }, 'Test document.getAnimations with null target');
 
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
-<title>KeyframeEffect and KeyframeEffectReadOnly constructor</title>
+<title>KeyframeEffect constructor</title>
 <link rel="help"
       href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-keyframeeffect">
 <link rel="help"
       href="https://drafts.csswg.org/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
 <script src="../../resources/easing-tests.js"></script>
@@ -16,185 +16,177 @@
 <div id="target"></div>
 <script>
 'use strict';
 
 const target = document.getElementById('target');
 
 test(t => {
   for (const frames of gEmptyKeyframeListTests) {
-    assert_equals(new KeyframeEffectReadOnly(target, frames)
-                        .getKeyframes().length,
+    assert_equals(new KeyframeEffect(target, frames).getKeyframes().length,
                   0, `number of frames for ${JSON.stringify(frames)}`);
   }
-}, 'A KeyframeEffectReadOnly can be constructed with no frames');
+}, 'A KeyframeEffect can be constructed with no frames');
 
 test(t => {
   for (const subtest of gEasingParsingTests) {
     const easing = subtest[0];
     const expected = subtest[1];
-    const effect = new KeyframeEffectReadOnly(target, {
+    const effect = new KeyframeEffect(target, {
       left: ['10px', '20px']
     }, { easing: easing });
-    assert_equals(effect.timing.easing, expected,
+    assert_equals(effect.getTiming().easing, expected,
                   `resulting easing for '${easing}'`);
   }
 }, 'easing values are parsed correctly when passed to the ' +
-   'KeyframeEffectReadOnly constructor in KeyframeEffectOptions');
+   'KeyframeEffect constructor in KeyframeEffectOptions');
 
 test(t => {
   for (const invalidEasing of gInvalidEasings) {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, null, { easing: invalidEasing });
+      new KeyframeEffect(target, null, { easing: invalidEasing });
     }, `TypeError is thrown for easing '${invalidEasing}'`);
   }
 }, 'Invalid easing values are correctly rejected when passed to the ' +
-   'KeyframeEffectReadOnly constructor in KeyframeEffectOptions');
+   'KeyframeEffect constructor in KeyframeEffectOptions');
 
 test(t => {
   const getKeyframe =
     composite => ({ left: [ '10px', '20px' ], composite: composite });
   for (const composite of gGoodKeyframeCompositeValueTests) {
-    const effect = new KeyframeEffectReadOnly(target, getKeyframe(composite));
+    const effect = new KeyframeEffect(target, getKeyframe(composite));
     assert_equals(effect.getKeyframes()[0].composite, composite,
                   `resulting composite for '${composite}'`);
   }
   for (const composite of gBadKeyframeCompositeValueTests) {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, getKeyframe(composite));
+      new KeyframeEffect(target, getKeyframe(composite));
     });
   }
 }, 'composite values are parsed correctly when passed to the ' +
-   'KeyframeEffectReadOnly constructor in property-indexed keyframes');
+   'KeyframeEffect constructor in property-indexed keyframes');
 
 test(t => {
   const getKeyframes = composite =>
     [
       { offset: 0, left: '10px', composite: composite },
       { offset: 1, left: '20px' }
     ];
   for (const composite of gGoodKeyframeCompositeValueTests) {
-    const effect = new KeyframeEffectReadOnly(target, getKeyframes(composite));
+    const effect = new KeyframeEffect(target, getKeyframes(composite));
     assert_equals(effect.getKeyframes()[0].composite, composite,
                   `resulting composite for '${composite}'`);
   }
   for (const composite of gBadKeyframeCompositeValueTests) {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, getKeyframes(composite));
+      new KeyframeEffect(target, getKeyframes(composite));
     });
   }
 }, 'composite values are parsed correctly when passed to the ' +
-   'KeyframeEffectReadOnly constructor in regular keyframes');
+   'KeyframeEffect constructor in regular keyframes');
 
 test(t => {
   for (const composite of gGoodOptionsCompositeValueTests) {
-    const effect = new KeyframeEffectReadOnly(target, {
+    const effect = new KeyframeEffect(target, {
       left: ['10px', '20px']
-    }, { composite: composite });
+    }, { composite });
     assert_equals(effect.getKeyframes()[0].composite, null,
                   `resulting composite for '${composite}'`);
   }
   for (const composite of gBadOptionsCompositeValueTests) {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, {
+      new KeyframeEffect(target, {
         left: ['10px', '20px']
       }, { composite: composite });
     });
   }
 }, 'composite value is null if the composite operation specified on the ' +
    'keyframe effect is being used');
 
 for (const subtest of gKeyframesTests) {
   test(t => {
-    const effect = new KeyframeEffectReadOnly(target, subtest.input);
+    const effect = new KeyframeEffect(target, subtest.input);
     assert_frame_lists_equal(effect.getKeyframes(), subtest.output);
-  }, `A KeyframeEffectReadOnly can be constructed with ${subtest.desc}`);
+  }, `A KeyframeEffect can be constructed with ${subtest.desc}`);
 
   test(t => {
-    const effect = new KeyframeEffectReadOnly(target, subtest.input);
-    const secondEffect =
-      new KeyframeEffectReadOnly(target, effect.getKeyframes());
+    const effect = new KeyframeEffect(target, subtest.input);
+    const secondEffect = new KeyframeEffect(target, effect.getKeyframes());
     assert_frame_lists_equal(secondEffect.getKeyframes(),
                              effect.getKeyframes());
-  }, `A KeyframeEffectReadOnly constructed with ${subtest.desc} roundtrips`);
+  }, `A KeyframeEffect constructed with ${subtest.desc} roundtrips`);
 }
 
 for (const subtest of gInvalidKeyframesTests) {
   test(t => {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, subtest.input);
+      new KeyframeEffect(target, subtest.input);
     });
-  }, `KeyframeEffectReadOnly constructor throws with ${subtest.desc}`);
+  }, `KeyframeEffect constructor throws with ${subtest.desc}`);
 }
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(target,
-                                            { left: ['10px', '20px'] });
+  const effect = new KeyframeEffect(target, { left: ['10px', '20px'] });
 
-  const timing = effect.timing;
+  const timing = effect.getTiming();
   assert_equals(timing.delay, 0, 'default delay');
   assert_equals(timing.endDelay, 0, 'default endDelay');
   assert_equals(timing.fill, 'auto', 'default fill');
   assert_equals(timing.iterations, 1.0, 'default iterations');
   assert_equals(timing.iterationStart, 0.0, 'default iterationStart');
   assert_equals(timing.duration, 'auto', 'default duration');
   assert_equals(timing.direction, 'normal', 'default direction');
   assert_equals(timing.easing, 'linear', 'default easing');
 
   assert_equals(effect.composite, 'replace', 'default composite');
   assert_equals(effect.iterationComposite, 'replace',
                 'default iterationComposite');
-}, 'A KeyframeEffectReadOnly constructed without any ' +
-   'KeyframeEffectOptions object');
+}, 'A KeyframeEffect constructed without any KeyframeEffectOptions object');
 
 for (const subtest of gKeyframeEffectOptionTests) {
   test(t => {
-    const effect = new KeyframeEffectReadOnly(target,
-                                              { left: ['10px', '20px'] },
-                                              subtest.input);
+    const effect = new KeyframeEffect(target, { left: ['10px', '20px'] },
+                                      subtest.input);
 
     // Helper function to provide default expected values when the test does
     // not supply them.
     const expected = (field, defaultValue) => {
       return field in subtest.expected ? subtest.expected[field] : defaultValue;
     };
 
-    const timing = effect.timing;
+    const timing = effect.getTiming();
     assert_equals(timing.delay, expected('delay', 0),
                   'timing delay');
     assert_equals(timing.fill, expected('fill', 'auto'),
                   'timing fill');
     assert_equals(timing.iterations, expected('iterations', 1),
                   'timing iterations');
     assert_equals(timing.duration, expected('duration', 'auto'),
                   'timing duration');
     assert_equals(timing.direction, expected('direction', 'normal'),
                   'timing direction');
 
-  }, `A KeyframeEffectReadOnly constructed by ${subtest.desc}`);
+  }, `A KeyframeEffect constructed by ${subtest.desc}`);
 }
 
 for (const subtest of gInvalidKeyframeEffectOptionTests) {
   test(t => {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target,
-                                 { left: ['10px', '20px'] },
-                                 subtest.input);
+      new KeyframeEffect(target, { left: ['10px', '20px'] }, subtest.input);
     });
-  }, `Invalid KeyframeEffectReadOnly option by ${subtest.desc}`);
+  }, `Invalid KeyframeEffect option by ${subtest.desc}`);
 }
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(null,
-                                            { left: ['10px', '20px'] },
-                                            { duration: 100 * MS_PER_SEC,
-                                              fill: 'forwards' });
+  const effect = new KeyframeEffect(null, { left: ['10px', '20px'] },
+                                    { duration: 100 * MS_PER_SEC,
+                                      fill: 'forwards' });
   assert_equals(effect.target, null,
                 'Effect created with null target has correct target');
-}, 'A KeyframeEffectReadOnly constructed with null target');
+}, 'A KeyframeEffect constructed with null target');
 
 test(t => {
   const test_error = { name: 'test' };
 
   assert_throws(test_error, () => {
     new KeyframeEffect(target, { get left() { throw test_error }})
   });
 }, 'KeyframeEffect constructor propagates exceptions generated by accessing'
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-constructor.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-constructor.html
@@ -1,39 +1,39 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
-<title>KeyframeEffect and KeyframeEffectReadOnly copy constructor</title>
+<title>KeyframeEffect copy constructor</title>
 <link rel="help"
       href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-keyframeeffect-source">
 <link rel="help"
       href="https://drafts.csswg.org/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly-source">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
 <body>
 <div id="log"></div>
 <script>
 'use strict';
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(createDiv(t), null);
-  const copiedEffect = new KeyframeEffectReadOnly(effect);
+  const effect = new KeyframeEffect(createDiv(t), null);
+  const copiedEffect = new KeyframeEffect(effect);
   assert_equals(copiedEffect.target, effect.target, 'same target');
-}, 'Copied KeyframeEffectReadOnly has the same target');
+}, 'Copied KeyframeEffect has the same target');
 
 test(t => {
   const effect =
-    new KeyframeEffectReadOnly(null,
-                               [ { marginLeft: '0px' },
-                                 { marginLeft: '-20px', easing: 'ease-in',
-                                   offset: 0.1 },
-                                 { marginLeft: '100px', easing: 'ease-out' },
-                                 { marginLeft: '50px' } ]);
+    new KeyframeEffect(null,
+                       [ { marginLeft: '0px' },
+                         { marginLeft: '-20px', easing: 'ease-in',
+                           offset: 0.1 },
+                         { marginLeft: '100px', easing: 'ease-out' },
+                         { marginLeft: '50px' } ]);
 
-  const copiedEffect = new KeyframeEffectReadOnly(effect);
+  const copiedEffect = new KeyframeEffect(effect);
   const keyframesA = effect.getKeyframes();
   const keyframesB = copiedEffect.getKeyframes();
   assert_equals(keyframesA.length, keyframesB.length, 'same keyframes length');
 
   for (let i = 0; i < keyframesA.length; ++i) {
     assert_equals(keyframesA[i].offset, keyframesB[i].offset,
                   `Keyframe ${i} has the same offset`);
     assert_equals(keyframesA[i].computedOffset, keyframesB[i].computedOffset,
@@ -45,62 +45,49 @@ test(t => {
 
     assert_true(!!keyframesA[i].marginLeft,
                 `Original keyframe ${i} has a valid property value`);
     assert_true(!!keyframesB[i].marginLeft,
                 `New keyframe ${i} has a valid property value`);
     assert_equals(keyframesA[i].marginLeft, keyframesB[i].marginLeft,
                   `Keyframe ${i} has the same property value pair`);
   }
-}, 'Copied KeyframeEffectReadOnly has the same keyframes');
+}, 'Copied KeyframeEffect has the same keyframes');
 
 test(t => {
   const effect =
-    new KeyframeEffectReadOnly(null, null,
-                               { iterationComposite: 'accumulate' });
+    new KeyframeEffect(null, null, { iterationComposite: 'accumulate' });
 
-  const copiedEffect = new KeyframeEffectReadOnly(effect);
+  const copiedEffect = new KeyframeEffect(effect);
   assert_equals(copiedEffect.iterationComposite, effect.iterationComposite,
                 'same iterationCompositeOperation');
   assert_equals(copiedEffect.composite, effect.composite,
                 'same compositeOperation');
-}, 'Copied KeyframeEffectReadOnly has the same KeyframeEffectOptions');
+}, 'Copied KeyframeEffect has the same KeyframeEffectOptions');
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(null, null,
-                                            { duration: 100 * MS_PER_SEC,
-                                              delay: -1 * MS_PER_SEC,
-                                              endDelay: 2 * MS_PER_SEC,
-                                              fill: 'forwards',
-                                              iterationStart: 2,
-                                              iterations: 20,
-                                              easing: 'ease-out',
-                                              direction: 'alternate' } );
+  const effect = new KeyframeEffect(null, null,
+                                    { duration: 100 * MS_PER_SEC,
+                                      delay: -1 * MS_PER_SEC,
+                                      endDelay: 2 * MS_PER_SEC,
+                                      fill: 'forwards',
+                                      iterationStart: 2,
+                                      iterations: 20,
+                                      easing: 'ease-out',
+                                      direction: 'alternate' } );
 
-  const copiedEffect = new KeyframeEffectReadOnly(effect);
-  const timingA = effect.timing;
-  const timingB = copiedEffect.timing;
+  const copiedEffect = new KeyframeEffect(effect);
+  const timingA = effect.getTiming();
+  const timingB = copiedEffect.getTiming();
   assert_not_equals(timingA, timingB, 'different timing objects');
   assert_equals(timingA.delay, timingB.delay, 'same delay');
   assert_equals(timingA.endDelay, timingB.endDelay, 'same endDelay');
   assert_equals(timingA.fill, timingB.fill, 'same fill');
   assert_equals(timingA.iterationStart, timingB.iterationStart,
                 'same iterationStart');
   assert_equals(timingA.iterations, timingB.iterations, 'same iterations');
   assert_equals(timingA.duration, timingB.duration, 'same duration');
   assert_equals(timingA.direction, timingB.direction, 'same direction');
   assert_equals(timingA.easing, timingB.easing, 'same easing');
-}, 'Copied KeyframeEffectReadOnly has the same timing content');
-
-test(t => {
-  const effect = new KeyframeEffectReadOnly(createDiv(t), null);
-  assert_equals(effect.constructor.name, 'KeyframeEffectReadOnly');
-  assert_equals(effect.timing.constructor.name,
-                'AnimationEffectTimingReadOnly');
-
-  // Make a mutable copy
-  const copiedEffect = new KeyframeEffect(effect);
-  assert_equals(copiedEffect.constructor.name, 'KeyframeEffect');
-  assert_equals(copiedEffect.timing.constructor.name, 'AnimationEffectTiming');
-}, 'KeyframeEffect constructed from a KeyframeEffectReadOnly is mutable');
+}, 'Copied KeyframeEffect has the same timing content');
 
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/idlharness.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/idlharness.html
@@ -4,116 +4,97 @@
 <link rel="help" href="https://drafts.csswg.org/web-animations/#keyframeeffect">
 <link rel="help"
       href="https://drafts.csswg.org/web-animations/#keyframeeffectreadonly">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
 <div id="log"></div>
-<script type="text/plain" id="AnimationEffectTimingReadOnly-IDL">
+<script type="text/plain" id="AnimationEffect-IDL">
 enum FillMode { "none", "forwards", "backwards", "both", "auto" };
 enum PlaybackDirection {
   "normal",
   "reverse",
   "alternate",
   "alternate-reverse"
 };
 
-dictionary AnimationEffectTimingProperties {
+dictionary EffectTiming {
   double                             delay = 0.0;
   double                             endDelay = 0.0;
   FillMode                           fill = "auto";
   double                             iterationStart = 0.0;
   unrestricted double                iterations = 1.0;
   (unrestricted double or DOMString) duration = "auto";
   PlaybackDirection                  direction = "normal";
   DOMString                          easing = "linear";
 };
 
-[Exposed=Window]
-interface AnimationEffectTimingReadOnly {
-  readonly attribute double                             delay;
-  readonly attribute double                             endDelay;
-  readonly attribute FillMode                           fill;
-  readonly attribute double                             iterationStart;
-  readonly attribute unrestricted double                iterations;
-  readonly attribute (unrestricted double or DOMString) duration;
-  readonly attribute PlaybackDirection                  direction;
-  readonly attribute DOMString                          easing;
+dictionary OptionalEffectTiming {
+  double                              delay;
+  double                              endDelay;
+  FillMode                            fill;
+  double                              iterationStart;
+  unrestricted double                 iterations;
+  (unrestricted double or DOMString)  duration;
+  PlaybackDirection                   direction;
+  DOMString                           easing;
 };
-</script>
-<script type="text/plain" id="AnimationEffectReadOnly-IDL">
-dictionary ComputedTimingProperties : AnimationEffectTimingProperties {
-  unrestricted double  endTime;
-  unrestricted double  activeDuration;
-  double?              localTime;
-  double?              progress;
-  unrestricted double? currentIteration;
+
+dictionary ComputedEffectTiming : EffectTiming {
+  unrestricted double   endTime = 0.0;
+  unrestricted double   activeDuration = 0.0;
+  double?               localTime = null;
+  double?               progress = null;
+  unrestricted double?  currentIteration = null;
 };
 
 [Exposed=Window]
-interface AnimationEffectReadOnly {
-  readonly attribute AnimationEffectTimingReadOnly timing;
-  ComputedTimingProperties getComputedTiming();
+interface AnimationEffect {
+  EffectTiming getTiming();
+  ComputedEffectTiming getComputedTiming();
+  void updateTiming(optional OptionalEffectTiming timing);
 };
 </script>
-<script type="text/plain" id="KeyframeEffectReadOnly-IDL">
+<script type="text/plain" id="KeyframeEffect-IDL">
 enum IterationCompositeOperation { "replace", "accumulate" };
 enum CompositeOperation { "replace", "add", "accumulate" };
 
-dictionary KeyframeEffectOptions : AnimationEffectTimingProperties {
+dictionary KeyframeEffectOptions : EffectTiming {
   IterationCompositeOperation iterationComposite = "replace";
   CompositeOperation          composite = "replace";
 };
 
 [Exposed=Window,
  Constructor ((Element or CSSPseudoElement)? target,
               object? keyframes,
               optional (unrestricted double or KeyframeEffectOptions) options),
- Constructor (KeyframeEffectReadOnly source)]
-interface KeyframeEffectReadOnly : AnimationEffectReadOnly {
-  readonly attribute (Element or CSSPseudoElement)? target;
-  readonly attribute IterationCompositeOperation    iterationComposite;
-  readonly attribute CompositeOperation             composite;
+ Constructor (KeyframeEffect source)]
+interface KeyframeEffect : AnimationEffect {
+  attribute (Element or CSSPseudoElement)? target;
+  attribute IterationCompositeOperation    iterationComposite;
+  attribute CompositeOperation             composite;
+
   sequence<object> getKeyframes ();
-};
-</script>
-<script type="text/plain" id="KeyframeEffect-IDL">
-[Exposed=Window,
- Constructor ((Element or CSSPseudoElement)? target,
-              object? keyframes,
-              optional (unrestricted double or KeyframeEffectOptions) options),
- Constructor (KeyframeEffectReadOnly source)]
-interface KeyframeEffect : KeyframeEffectReadOnly {
-  inherit attribute (Element or CSSPseudoElement)? target;
-  inherit attribute IterationCompositeOperation    iterationComposite;
-  inherit attribute CompositeOperation             composite;
   void setKeyframes (object? keyframes);
 };
 </script>
 <script>
 'use strict';
 
 const idlArray = new IdlArray();
 
 idlArray.add_untested_idls('interface CSSPseudoElement {};');
 idlArray.add_untested_idls('interface Element {};');
 idlArray.add_untested_idls(
-  document.getElementById('AnimationEffectTimingReadOnly-IDL').textContent
-);
-idlArray.add_idls(
-  document.getElementById('AnimationEffectReadOnly-IDL').textContent
-);
-idlArray.add_idls(
-  document.getElementById('KeyframeEffectReadOnly-IDL').textContent
+  document.getElementById('AnimationEffect-IDL').textContent
 );
 idlArray.add_idls(
   document.getElementById('KeyframeEffect-IDL').textContent
 );
 idlArray.add_objects({
   KeyframeEffect: ['new KeyframeEffect(null, null)'],
-  KeyframeEffectReadOnly: ['new KeyframeEffectReadOnly(null, null)'],
 });
 
 idlArray.test();
 
 </script>
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html
@@ -14,17 +14,18 @@ test(t => {
   const anim = div.animate({ marginLeft: ['0px', '10px'] },
                            { duration: 100 * MS_PER_SEC,
                              easing: 'linear',
                              iterations: 10,
                              iterationComposite: 'accumulate' });
   anim.pause();
 
   anim.currentTime =
-    anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2;
+    anim.effect.getComputedTiming().duration * 2 +
+    anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '25px',
     'Animated style at 50s of the third iteration');
 
   anim.effect.iterationComposite = 'replace';
   assert_equals(getComputedStyle(div).marginLeft, '5px',
     'Animated style at 50s of the third iteration');
 
   anim.effect.iterationComposite = 'accumulate';
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
@@ -8,17 +8,17 @@
 <script src="../../resources/keyframe-utils.js"></script>
 <body>
 <div id="log"></div>
 <div id="target"></div>
 <script>
 'use strict';
 
 // This file only tests the KeyframeEffect constructor since it is
-// assumed that the implementation of the KeyframeEffectReadOnly constructor,
+// assumed that the implementation of the KeyframeEffect constructor,
 // Animatable.animate() method, and KeyframeEffect.setKeyframes() method will
 // all share common machinery and it is not necessary to test each method.
 
 // Test that only animatable properties are accessed
 
 const gNonAnimatableProps = [
   'animation', // Shorthands where all the longhand sub-properties are not
                // animatable, are also not animatable.
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/resources/timing-tests.js
@@ -0,0 +1,46 @@
+'use strict';
+
+// =================================
+//
+// Common timing parameter test data
+//
+// =================================
+
+
+// ------------------------------
+//  Delay values
+// ------------------------------
+
+const gBadDelayValues = [
+  NaN, Infinity, -Infinity
+];
+
+// ------------------------------
+//  Duration values
+// ------------------------------
+
+const gGoodDurationValues = [
+  { specified: 123.45, computed: 123.45 },
+  { specified: 'auto', computed: 0 },
+  { specified: Infinity, computed: Infinity },
+];
+
+const gBadDurationValues = [
+  -1, NaN, -Infinity, 'abc', '100'
+];
+
+// ------------------------------
+//  iterationStart values
+// ------------------------------
+
+const gBadIterationStartValues = [
+  -1, NaN, Infinity, -Infinity
+];
+
+// ------------------------------
+//  iterations values
+// ------------------------------
+
+const gBadIterationsValues = [
+  -1, -Infinity, NaN
+];
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/resources/timing-utils.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// =======================================
+//
+// Utility functions for testing timing
+//
+// =======================================
+
+
+// ------------------------------
+//  Helper functions
+// ------------------------------
+
+// Utility function to check that a subset of timing properties have their
+// default values.
+function assert_default_timing_except(effect, propertiesToSkip) {
+  const defaults = {
+    delay: 0,
+    endDelay: 0,
+    fill: 'auto',
+    iterationStart: 0,
+    iterations: 1,
+    duration: 'auto',
+    direction: 'normal',
+    easing: 'linear',
+  };
+
+  for (const prop of Object.keys(defaults)) {
+    if (propertiesToSkip.includes(prop)) {
+      continue;
+    }
+
+    assert_equals(
+      effect.getTiming()[prop],
+      defaults[prop],
+      `${prop} parameter has default value:`
+    );
+  }
+}
--- a/testing/web-platform/tests/web-animations/testcommon.js
+++ b/testing/web-platform/tests/web-animations/testcommon.js
@@ -24,17 +24,21 @@ if (!window.assert_times_equal) {
     assert_approx_equals(actual, expected, TIME_PRECISION * 2, description);
   };
 }
 
 // Allow implementations to substitute an alternative method for comparing
 // a time value based on its precision requirements with a fixed value.
 if (!window.assert_time_equals_literal) {
   window.assert_time_equals_literal = (actual, expected, description) => {
-    assert_approx_equals(actual, expected, TIME_PRECISION, description);
+    if (Math.abs(expected) === Infinity) {
+      assert_equals(actual, expected, description);
+    } else {
+      assert_approx_equals(actual, expected, TIME_PRECISION, description);
+    }
   }
 }
 
 // creates div element, appends it to the document body and
 // removes the created element during test cleanup
 function createDiv(test, doc) {
   return createElement(test, 'div', doc);
 }
@@ -278,9 +282,9 @@ function assert_rotate3d_equals(actual, 
     expected.match(rotationRegExp)[1].split(' ').map(Number);
 
   assert_equals(actualRotationVector.length, expectedRotationVector.length,
     `dimension of the matrix: ${description}`);
   for (let i = 0; i < actualRotationVector.length; i++) {
     assert_approx_equals(actualRotationVector[i], expectedRotationVector[i], 0.0001,
       `expected ${expected} but got ${actual}: ${description}`);
   }
-}
\ No newline at end of file
+}
--- a/testing/web-platform/tests/web-animations/timing-model/animation-effects/phases-and-states.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animation-effects/phases-and-states.html
@@ -17,34 +17,34 @@
 // --------------------------------------------------------------------
 
 function assert_phase_at_time(animation, phase, currentTime) {
   animation.currentTime = currentTime;
 
   if (phase === 'active') {
     // If the fill mode is 'none', then progress will only be non-null if we
     // are in the active phase.
-    animation.effect.timing.fill = 'none';
+    animation.effect.updateTiming({ fill: 'none' });
     assert_not_equals(animation.effect.getComputedTiming().progress, null,
                       'Animation effect is in active phase when current time'
                       + ` is ${currentTime}ms`);
   } else {
     // The easiest way to distinguish between the 'before' phase and the 'after'
     // phase is to toggle the fill mode. For example, if the progress is null
     // will the fill node is 'none' but non-null when the fill mode is
     // 'backwards' then we are in the before phase.
-    animation.effect.timing.fill = 'none';
+    animation.effect.updateTiming({ fill: 'none' });
     assert_equals(animation.effect.getComputedTiming().progress, null,
                   `Animation effect is in ${phase} phase when current time`
                   + ` is ${currentTime}ms`
                   + ' (progress is null with \'none\' fill mode)');
 
-    animation.effect.timing.fill = phase === 'before'
-                                   ? 'backwards'
-                                   : 'forwards';
+    animation.effect.updateTiming({
+      fill: phase === 'before' ? 'backwards' : 'forwards',
+    });
     assert_not_equals(animation.effect.getComputedTiming().progress, null,
                       `Animation effect is in ${phase} phase when current time`
                       + ` is ${currentTime}ms`
                       + ' (progress is non-null with appropriate fill mode)');
   }
 }
 
 test(t => {
--- a/testing/web-platform/tests/web-animations/timing-model/animations/finishing-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/finishing-an-animation.html
@@ -191,17 +191,17 @@ promise_test(async t => {
 
   animation.finish();
   await Promise.resolve();
 
   assert_true(resolvedFinished, 'finished promise should be resolved');
 }, 'Finishing an animation resolves the finished promise synchronously');
 
 promise_test(async t => {
-  const effect = new KeyframeEffectReadOnly(null, null, 100 * MS_PER_SEC);
+  const effect = new KeyframeEffect(null, null, 100 * MS_PER_SEC);
   const animation = new Animation(effect, document.timeline);
   let resolvedFinished = false;
   animation.finished.then(() => {
     resolvedFinished = true;
   });
 
   await animation.ready;
 
--- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
@@ -31,35 +31,35 @@ promise_test(t => {
 }, 'If new effect is null and old effect is not null, we reset the pending ' +
    'tasks and ready promise is rejected');
 
 promise_test(async t => {
   const anim = new Animation();
   anim.pause();
   assert_true(anim.pending);
 
-  anim.effect = new KeyframeEffectReadOnly(createDiv(t),
-                                           { marginLeft: [ '0px', '100px' ] },
-                                           100 * MS_PER_SEC);
+  anim.effect = new KeyframeEffect(createDiv(t),
+                                   { marginLeft: [ '0px', '100px' ] },
+                                   100 * MS_PER_SEC);
   assert_true(anim.pending);
   await anim.ready;
 
   assert_false(anim.pending);
   assert_equals(anim.playState, 'paused');
 }, 'If animation has a pending pause task, reschedule that task to run ' +
    'as soon as animation is ready.');
 
 promise_test(async t => {
   const anim = new Animation();
   anim.play();
   assert_true(anim.pending);
 
-  anim.effect = new KeyframeEffectReadOnly(createDiv(t),
-                                           { marginLeft: [ '0px', '100px' ] },
-                                           100 * MS_PER_SEC);
+  anim.effect = new KeyframeEffect(createDiv(t),
+                                   { marginLeft: [ '0px', '100px' ] },
+                                   100 * MS_PER_SEC);
   assert_true(anim.pending);
   await anim.ready;
 
   assert_false(anim.pending);
   assert_equals(anim.playState, 'running');
 }, 'If animation has a pending play task, reschedule that task to run ' +
    'as soon as animation is ready to play new effect.');
 
--- a/testing/web-platform/tests/web-animations/timing-model/animations/updating-the-finished-state.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/updating-the-finished-state.html
@@ -136,17 +136,19 @@ promise_test(async t => {
   // put the animation in a state where the hold time is set.
   anim.finish();
   await anim.ready;
 
   assert_equals(anim.currentTime, 100 * MS_PER_SEC,
                 'Hold time is initially set');
   // Then extend the duration so that the hold time is cleared and on
   // the next tick the current time will increase.
-  anim.effect.timing.duration *= 2;
+  anim.effect.updateTiming({
+    duration: anim.effect.getComputedTiming().duration * 2,
+  });
   await waitForNextFrame();
 
   assert_greater_than(anim.currentTime, 100 * MS_PER_SEC,
                       'Hold time is not set so current time should increase');
 }, 'Updating the finished state when playing before end');
 
 // Did seek = true; playback rate > 0
 promise_test(async t => {
@@ -242,17 +244,17 @@ promise_test(async t => {
 
 // CASE 5: current time unresolved
 
 promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.cancel();
   // Trigger a change that will cause the "update the finished state"
   // procedure to run.
-  anim.effect.timing.duration = 200 * MS_PER_SEC;
+  anim.effect.updateTiming({ duration: 200 * MS_PER_SEC });
   assert_equals(anim.currentTime, null,
                 'The animation hold time / start time should not be updated');
   // The "update the finished state" procedure is supposed to run after any
   // change to timing, but just in case an implementation defers that, let's
   // wait a frame and check that the hold time / start time has still not been
   // updated.
   await waitForAnimationFrames(1);
 
@@ -268,33 +270,33 @@ test(t => {
   anim.currentTime = 75 * MS_PER_SEC;
   anim.play();
   // We now have a pending task and a resolved current time.
   //
   // In the next step we will adjust the timing so that the current time
   // is greater than the target end. At this point the "update the finished
   // state" procedure should run and if we fail to check for a pending task
   // we will set the hold time to the target end, i.e. 50ms.
-  anim.effect.timing.duration = 50 * MS_PER_SEC;
+  anim.effect.updateTiming({ duration: 50 * MS_PER_SEC });
   assert_equals(anim.currentTime, 75 * MS_PER_SEC,
                 'Hold time should not be updated');
 }, 'Updating the finished state when there is a pending task');
 
 // CASE 7: start time unresolved
 
 // Did seek = false
 promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.cancel();
   // Make it so that only the start time is unresolved (to avoid overlapping
   // with the test case where current time is unresolved)
   anim.currentTime = 150 * MS_PER_SEC;
   // Trigger a change that will cause the "update the finished state"
   // procedure to run (did seek = false).
-  anim.effect.timing.duration = 200 * MS_PER_SEC;
+  anim.effect.updateTiming({ duration: 200 * MS_PER_SEC });
   await waitForAnimationFrames(1);
 
   assert_equals(anim.currentTime, 150 * MS_PER_SEC,
                 'The animation hold time should not be updated');
   assert_equals(anim.startTime, null,
                 'The animation start time should not be updated');
 }, 'Updating the finished state when start time is unresolved and'
    + ' did seek = false');
@@ -343,17 +345,17 @@ promise_test(t => {
 promise_test(async t => {
   const animation = createDiv(t).animate(null, 1);
   await animation.ready;
 
   return waitForFinishEventAndPromise(animation);
 }, 'Finish notification steps run when the animation completes normally');
 
 promise_test(async t => {
-  const effect = new KeyframeEffectReadOnly(null, null, 1);
+  const effect = new KeyframeEffect(null, null, 1);
   const animation = new Animation(effect, document.timeline);
   animation.play();
   await animation.ready;
 
   return waitForFinishEventAndPromise(animation);
 }, 'Finish notification steps run when an animation without a target'
    + ' effect completes normally');
 
@@ -412,10 +414,43 @@ async_test(t => {
   animation.onfinish = event => {
     animation.play();
     animation.onfinish = event => {
       t.done();
     };
   };
 }, 'Animation finish event is fired again after replaying from start');
 
+async_test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { duration: 100000, endDelay: 50000 });
+  anim.onfinish = t.step_func(event => {
+    assert_unreached('finish event should not be fired');
+  });
+
+  anim.ready.then(() => {
+    anim.currentTime = 100000;
+    return waitForAnimationFrames(2);
+  }).then(t.step_func(() => {
+    t.done();
+  }));
+}, 'finish event is not fired at the end of the active interval when the'
+   + ' endDelay has not expired');
+
+async_test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { duration: 100000, endDelay: 30000 });
+  anim.ready.then(() => {
+    anim.currentTime = 110000; // during endDelay
+    anim.onfinish = t.step_func(event => {
+      assert_unreached('onfinish event should not be fired during endDelay');
+    });
+    return waitForAnimationFrames(2);
+  }).then(t.step_func(() => {
+    anim.onfinish = t.step_func(event => {
+      t.done();
+    });
+    anim.currentTime = 130000; // after endTime
+  }));
+}, 'finish event is fired after the endDelay has expired');
+
 </script>
 </body>