[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
diff --git a/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html b/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
index c2e9702..142d49d 100644
--- a/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
+++ b/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
@@ -20,13 +20,13 @@
                   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)');
@@ -41,13 +41,13 @@
                   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');
@@ -66,13 +66,13 @@
                   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');
@@ -87,13 +87,13 @@
                   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');
@@ -108,13 +108,13 @@
                   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');
@@ -131,13 +131,13 @@
                   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');
@@ -154,13 +154,13 @@
                   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');
@@ -175,13 +175,13 @@
                   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');
@@ -200,15 +200,15 @@
                   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');
@@ -225,13 +225,13 @@
                   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');
@@ -247,15 +247,15 @@
                   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');
@@ -271,13 +271,13 @@
                   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');
@@ -293,15 +293,15 @@
                   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');
@@ -318,15 +318,15 @@
                   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');
@@ -343,15 +343,15 @@
                   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');
@@ -368,15 +368,15 @@
                   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');
@@ -393,18 +393,18 @@
                   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.
@@ -424,15 +424,15 @@
                   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');
@@ -448,15 +448,15 @@
                   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');
@@ -472,16 +472,16 @@
                   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.
@@ -498,15 +498,15 @@
                   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');
@@ -522,15 +522,15 @@
                   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');
@@ -547,15 +547,15 @@
                   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');
@@ -572,16 +572,16 @@
                   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)
@@ -601,20 +601,20 @@
                   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)
@@ -636,20 +636,20 @@
                   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) =
@@ -670,17 +670,17 @@
                   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)',
@@ -704,16 +704,16 @@
                   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)
@@ -736,11 +736,11 @@
   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');
@@ -756,13 +756,13 @@
                   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');
@@ -777,15 +777,15 @@
                   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');
@@ -801,17 +801,22 @@
   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');
diff --git a/web-animations/interfaces/Animatable/animate.html b/web-animations/interfaces/Animatable/animate.html
index 36c4ee1..c7b7b25 100644
--- a/web-animations/interfaces/Animatable/animate.html
+++ b/web-animations/interfaces/Animatable/animate.html
@@ -8,6 +8,8 @@
 <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>
@@ -17,8 +19,7 @@
 // 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');
 
@@ -57,26 +58,9 @@
 }, '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)})`);
@@ -84,8 +68,7 @@
 
 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}`);
 }
@@ -99,54 +82,101 @@
   }, `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');
 
@@ -157,7 +187,7 @@
 
   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();
@@ -168,8 +198,7 @@
    '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');
 
diff --git a/web-animations/interfaces/Animatable/getAnimations.html b/web-animations/interfaces/Animatable/getAnimations.html
index d05e2aa..182b5cb 100644
--- a/web-animations/interfaces/Animatable/getAnimations.html
+++ b/web-animations/interfaces/Animatable/getAnimations.html
@@ -75,12 +75,14 @@
   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');
@@ -91,13 +93,13 @@
   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'
@@ -113,17 +115,17 @@
   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');
diff --git a/web-animations/interfaces/Animation/cancel.html b/web-animations/interfaces/Animation/cancel.html
index be1bb5d..711a339 100644
--- a/web-animations/interfaces/Animation/cancel.html
+++ b/web-animations/interfaces/Animation/cancel.html
@@ -29,7 +29,7 @@
   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');
diff --git a/web-animations/interfaces/Animation/constructor.html b/web-animations/interfaces/Animation/constructor.html
index c00c66e..fcbaab1 100644
--- a/web-animations/interfaces/Animation/constructor.html
+++ b/web-animations/interfaces/Animation/constructor.html
@@ -14,7 +14,7 @@
 const gTarget = document.getElementById('target');
 
 function createEffect() {
-  return new KeyframeEffectReadOnly(gTarget, { opacity: [0, 1] });
+  return new KeyframeEffect(gTarget, { opacity: [0, 1] });
 }
 
 function createNull() {
@@ -82,10 +82,9 @@
 }
 
 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);
@@ -100,7 +99,7 @@
 
   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();
diff --git a/web-animations/interfaces/Animation/effect.html b/web-animations/interfaces/Animation/effect.html
index 175fc31..cb8bc09 100644
--- a/web-animations/interfaces/Animation/effect.html
+++ b/web-animations/interfaces/Animation/effect.html
@@ -14,7 +14,7 @@
   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.');
diff --git a/web-animations/interfaces/Animation/finished.html b/web-animations/interfaces/Animation/finished.html
index 79700f3..563d4ba 100644
--- a/web-animations/interfaces/Animation/finished.html
+++ b/web-animations/interfaces/Animation/finished.html
@@ -189,7 +189,7 @@
   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
@@ -206,7 +206,7 @@
     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');
diff --git a/web-animations/interfaces/Animation/idlharness.html b/web-animations/interfaces/Animation/idlharness.html
index bf7c9f3..20fca13 100644
--- a/web-animations/interfaces/Animation/idlharness.html
+++ b/web-animations/interfaces/Animation/idlharness.html
@@ -10,11 +10,11 @@
 <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;
diff --git a/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html b/web-animations/interfaces/AnimationEffect/getComputedTiming.html
similarity index 99%
rename from web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html
rename to web-animations/interfaces/AnimationEffect/getComputedTiming.html
index 9a92880..e0e4fc2 100644
--- a/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html
+++ b/web-animations/interfaces/AnimationEffect/getComputedTiming.html
@@ -195,7 +195,5 @@
 
   }, `getComputedTiming().endTime for ${stest.desc}`);
 }
-
-done();
 </script>
 </body>
diff --git a/web-animations/interfaces/AnimationEffect/updateTiming.html b/web-animations/interfaces/AnimationEffect/updateTiming.html
new file mode 100644
index 0000000..746f0d7
--- /dev/null
+++ b/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/delay.html b/web-animations/interfaces/AnimationEffectTiming/delay.html
deleted file mode 100644
index 41f099b..0000000
--- a/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/direction.html b/web-animations/interfaces/AnimationEffectTiming/direction.html
deleted file mode 100644
index 3238f5d..0000000
--- a/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/duration.html b/web-animations/interfaces/AnimationEffectTiming/duration.html
deleted file mode 100644
index 42913f5..0000000
--- a/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/easing.html b/web-animations/interfaces/AnimationEffectTiming/easing.html
deleted file mode 100644
index ec2b239..0000000
--- a/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/endDelay.html b/web-animations/interfaces/AnimationEffectTiming/endDelay.html
deleted file mode 100644
index 3062c8c..0000000
--- a/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/fill.html b/web-animations/interfaces/AnimationEffectTiming/fill.html
deleted file mode 100644
index 0173947..0000000
--- a/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/idlharness.html b/web-animations/interfaces/AnimationEffectTiming/idlharness.html
deleted file mode 100644
index 7e537d1..0000000
--- a/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/iterationStart.html b/web-animations/interfaces/AnimationEffectTiming/iterationStart.html
deleted file mode 100644
index 73bc481..0000000
--- a/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>
diff --git a/web-animations/interfaces/AnimationEffectTiming/iterations.html b/web-animations/interfaces/AnimationEffectTiming/iterations.html
deleted file mode 100644
index 3bfaf8e..0000000
--- a/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>
diff --git a/web-animations/interfaces/Document/getAnimations.html b/web-animations/interfaces/Document/getAnimations.html
index 165626d..3a1d7be 100644
--- a/web-animations/interfaces/Document/getAnimations.html
+++ b/web-animations/interfaces/Document/getAnimations.html
@@ -42,7 +42,7 @@
 }, '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();
 
diff --git a/web-animations/interfaces/KeyframeEffect/constructor.html b/web-animations/interfaces/KeyframeEffect/constructor.html
index dfdebde..250a8c7 100644
--- a/web-animations/interfaces/KeyframeEffect/constructor.html
+++ b/web-animations/interfaces/KeyframeEffect/constructor.html
@@ -1,6 +1,6 @@
 <!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"
@@ -21,49 +21,48 @@
 
 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 =>
@@ -72,29 +71,29 @@
       { 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 });
     });
@@ -104,32 +103,30 @@
 
 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');
@@ -142,14 +139,12 @@
   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.
@@ -157,7 +152,7 @@
       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'),
@@ -169,27 +164,24 @@
     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' };
diff --git a/web-animations/interfaces/KeyframeEffect/copy-constructor.html b/web-animations/interfaces/KeyframeEffect/copy-constructor.html
index c4e8a8e..e3bc0db 100644
--- a/web-animations/interfaces/KeyframeEffect/copy-constructor.html
+++ b/web-animations/interfaces/KeyframeEffect/copy-constructor.html
@@ -1,6 +1,6 @@
 <!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"
@@ -14,21 +14,21 @@
 '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');
@@ -50,34 +50,33 @@
     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');
@@ -88,19 +87,7 @@
   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>
diff --git a/web-animations/interfaces/KeyframeEffect/idlharness.html b/web-animations/interfaces/KeyframeEffect/idlharness.html
index 5056a87..0340a19 100644
--- a/web-animations/interfaces/KeyframeEffect/idlharness.html
+++ b/web-animations/interfaces/KeyframeEffect/idlharness.html
@@ -9,7 +9,7 @@
 <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",
@@ -18,7 +18,7 @@
   "alternate-reverse"
 };
 
-dictionary AnimationEffectTimingProperties {
+dictionary EffectTiming {
   double                             delay = 0.0;
   double                             endDelay = 0.0;
   FillMode                           fill = "auto";
@@ -29,38 +29,37 @@
   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";
 };
@@ -69,24 +68,13 @@
  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>
@@ -98,20 +86,13 @@
 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();
diff --git a/web-animations/interfaces/KeyframeEffect/iterationComposite.html b/web-animations/interfaces/KeyframeEffect/iterationComposite.html
index de2afa7..bbb8ee2 100644
--- a/web-animations/interfaces/KeyframeEffect/iterationComposite.html
+++ b/web-animations/interfaces/KeyframeEffect/iterationComposite.html
@@ -19,7 +19,8 @@
   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');
 
diff --git a/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html b/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
index 523019d..133fd57 100644
--- a/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
+++ b/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
@@ -13,7 +13,7 @@
 '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.
 
diff --git a/web-animations/resources/timing-tests.js b/web-animations/resources/timing-tests.js
new file mode 100644
index 0000000..4b0f021
--- /dev/null
+++ b/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
+];
diff --git a/web-animations/resources/timing-utils.js b/web-animations/resources/timing-utils.js
new file mode 100644
index 0000000..d7267f9
--- /dev/null
+++ b/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:`
+    );
+  }
+}
diff --git a/web-animations/testcommon.js b/web-animations/testcommon.js
index bb5acc2..46f2356 100644
--- a/web-animations/testcommon.js
+++ b/web-animations/testcommon.js
@@ -29,7 +29,11 @@
 // 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);
+    }
   }
 }
 
@@ -283,4 +287,4 @@
     assert_approx_equals(actualRotationVector[i], expectedRotationVector[i], 0.0001,
       `expected ${expected} but got ${actual}: ${description}`);
   }
-}
\ No newline at end of file
+}
diff --git a/web-animations/timing-model/animation-effects/phases-and-states.html b/web-animations/timing-model/animation-effects/phases-and-states.html
index ac3732f..b62726c 100644
--- a/web-animations/timing-model/animation-effects/phases-and-states.html
+++ b/web-animations/timing-model/animation-effects/phases-and-states.html
@@ -22,7 +22,7 @@
   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`);
@@ -31,15 +31,15 @@
     // 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`
diff --git a/web-animations/timing-model/animations/finishing-an-animation.html b/web-animations/timing-model/animations/finishing-an-animation.html
index b479db9..833f074 100644
--- a/web-animations/timing-model/animations/finishing-an-animation.html
+++ b/web-animations/timing-model/animations/finishing-an-animation.html
@@ -196,7 +196,7 @@
 }, '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(() => {
diff --git a/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html b/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
index 184804d..daa73f5 100644
--- a/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
+++ b/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
@@ -36,9 +36,9 @@
   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;
 
@@ -52,9 +52,9 @@
   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;
 
diff --git a/web-animations/timing-model/animations/updating-the-finished-state.html b/web-animations/timing-model/animations/updating-the-finished-state.html
index 7059423..8e02fed 100644
--- a/web-animations/timing-model/animations/updating-the-finished-state.html
+++ b/web-animations/timing-model/animations/updating-the-finished-state.html
@@ -141,7 +141,9 @@
                 '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,
@@ -247,7 +249,7 @@
   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
@@ -273,7 +275,7 @@
   // 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');
@@ -289,7 +291,7 @@
   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,
@@ -348,7 +350,7 @@
 }, '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;
@@ -417,5 +419,38 @@
   };
 }, '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>