Bug 1593146 [wpt PR 20040] - update WPT tests to eliminate CSSPseudoElement from animations, a=testonly
authorBlink WPT Bot <blink-w3c-test-autoroller@chromium.org>
Mon, 25 Nov 2019 19:09:50 +0000
changeset 504505 7340a61d2fbac40d4868ec985fe4f9f70713f6dd
parent 504504 5f1a38b40add7528474d54e00a9a2e048e234ecf
child 504506 cafbf49e80c75d56f599ec95312b5fe86e62e928
push id101897
push userwptsync@mozilla.com
push dateFri, 29 Nov 2019 11:10:32 +0000
treeherderautoland@47be1b3fdda6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1593146, 20040, 1894477, 714541
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1593146 [wpt PR 20040] - update WPT tests to eliminate CSSPseudoElement from animations, a=testonly Automatic update from web-platform-tests update WPT tests to eliminate CSSPseudoElement from animations (#20040) Goes with spec CL https://github.com/w3c/csswg-drafts/pull/4437 (merged) Eliminated the use of CSSPseudoElement from WebAnimations to coincide with recent spec changes. JeyframeEffect targets now use an Element reference and pseudo-element selector like getComputedStyle and css-animations events. Change-Id: Ib6957a7b764eac3b4372ad35c1b79b139c356a19 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1894477 Reviewed-by: Stephen McGruer <smcgruer@chromium.org> Reviewed-by: Kevin Ellis <kevers@chromium.org> Commit-Queue: George Steel <gtsteel@chromium.org> Cr-Commit-Position: refs/heads/master@{#714541} -- wpt-commits: 2f837d2e318b64f66ee8da1f3b798b03b697d4d7 wpt-pr: 20040
testing/web-platform/tests/css/css-animations/CSSPseudoElement-getAnimations.tentative.html
testing/web-platform/tests/css/css-animations/Document-getAnimations.tentative.html
testing/web-platform/tests/css/css-animations/Element-getAnimations.tentative.html
testing/web-platform/tests/css/css-transitions/CSSPseudoElement-getAnimations.tentative.html
testing/web-platform/tests/css/css-transitions/Document-getAnimations.tentative.html
testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js
testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html
testing/web-platform/tests/web-animations/testcommon.js
deleted file mode 100644
--- a/testing/web-platform/tests/css/css-animations/CSSPseudoElement-getAnimations.tentative.html
+++ /dev/null
@@ -1,75 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>CSSPseudoElement.getAnimations() for CSS animations</title>
-<!-- TODO: Add a more specific link for this once it is specified. -->
-<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="support/testcommon.js"></script>
-<style>
-@keyframes anim1 { }
-@keyframes anim2 { }
-.before::before {
-  animation: anim1 10s;
-  content: '';
-}
-.after-with-mix-anims-trans::after {
-  content: '';
-  animation: anim1 10s, anim2 10s;
-  width: 0px;
-  height: 0px;
-  transition: all 100s;
-}
-.after-change::after {
-  width: 100px;
-  height: 100px;
-  content: '';
-}
-</style>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const div = addDiv(t, { class: 'before' });
-  const pseudoTarget = document.getAnimations()[0].effect.target;
-  assert_equals(pseudoTarget.getAnimations().length, 1,
-                'Expected number of animations are returned');
-  assert_equals(pseudoTarget.getAnimations()[0].animationName, 'anim1',
-                'CSS animation name matches');
-}, 'getAnimations returns CSSAnimation objects');
-
-test(t => {
-  const div = addDiv(t, { class: 'after-with-mix-anims-trans' });
-  // Trigger transitions
-  flushComputedStyle(div);
-  div.classList.add('after-change');
-
-  // Create additional animation on the pseudo-element from script
-  const pseudoTarget = document.getAnimations()[0].effect.target;
-  const effect = new KeyframeEffect(pseudoTarget,
-                                    { background: ["blue", "red"] },
-                                    3 * MS_PER_SEC);
-  const newAnimation = new Animation(effect, document.timeline);
-  newAnimation.id = 'scripted-anim';
-  newAnimation.play();
-
-  // Check order - the script-generated animation should appear later
-  const anims = pseudoTarget.getAnimations();
-  assert_equals(anims.length, 5,
-                'Got expected number of animations/trnasitions running on ' +
-                '::after pseudo element');
-  assert_equals(anims[0].transitionProperty, 'height',
-                '1st animation is the 1st transition sorted by name');
-  assert_equals(anims[1].transitionProperty, 'width',
-                '2nd animation is the 2nd transition sorted by name ');
-  assert_equals(anims[2].animationName, 'anim1',
-                '3rd animation is the 1st animation in animation-name list');
-  assert_equals(anims[3].animationName, 'anim2',
-                '4rd animation is the 2nd animation in animation-name list');
-  assert_equals(anims[4].id, 'scripted-anim',
-                'Animation added by script appears last');
-}, 'getAnimations returns CSS transitions/animations, and script-generated ' +
-   'animations in the expected order');
-
-</script>
--- a/testing/web-platform/tests/css/css-animations/Document-getAnimations.tentative.html
+++ b/testing/web-platform/tests/css/css-animations/Document-getAnimations.tentative.html
@@ -293,29 +293,34 @@ test(t => {
   );
 
   for (const [index, expected] of expectedAnimations.entries()) {
     const [element, pseudo] = expected;
     const actual = animations[index];
 
     if (pseudo) {
       assert_equals(
-        actual.effect.target.element,
+        actual.effect.target,
         element,
         `Animation #${index + 1} has expected target`
       );
       assert_equals(
-        actual.effect.target.type,
+        actual.effect.pseudoElement,
         pseudo,
         `Animation #${index + 1} has expected pseudo type`
       );
     } else {
       assert_equals(
         actual.effect.target,
         element,
         `Animation #${index + 1} has expected target`
       );
+      assert_equals(
+        actual.effect.pseudoElement,
+        null,
+        `Animation #${index + 1} has null pseudo type`
+      );
     }
   }
 }, 'CSS Animations targetting (pseudo-)elements should have correct order '
    + 'after sorting');
 
 </script>
--- a/testing/web-platform/tests/css/css-animations/Element-getAnimations.tentative.html
+++ b/testing/web-platform/tests/css/css-animations/Element-getAnimations.tentative.html
@@ -320,20 +320,23 @@ test(t => {
 
   const animations = target.getAnimations({ subtree: true });
   assert_equals(animations.length, 3,
                 'getAnimations({ subtree: true }) ' +
                 'should return animations on pseudo-elements');
   assert_equals(animations[0].effect.target, target,
                 'The animation targeting the parent element ' +
                 'should be returned first');
-  assert_equals(animations[1].effect.target.type, '::before',
+  assert_equals(animations[0].effect.pseudoElement, null,
+                'The animation targeting the parent element ' +
+                'should be returned first')
+  assert_equals(animations[1].effect.pseudoElement, '::before',
                 'The animation targeting the ::before pseudo-element ' +
                 'should be returned second');
-  assert_equals(animations[2].effect.target.type, '::after',
+  assert_equals(animations[2].effect.pseudoElement, '::after',
                 'The animation targeting the ::after pesudo-element ' +
                 'should be returned last');
 }, '{ subtree: true } on a leaf element returns the element\'s animations'
    + ' and its pseudo-elements\' animations');
 
 test(t => {
   addStyle(t, { '#parent::after': 'animation: anim1 10s;',
                 '#parent::before': 'animation: anim1 10s;',
@@ -361,44 +364,47 @@ test(t => {
   const parent = addDiv(t, { 'id': 'parent' });
   const child = addDiv(t, { 'id': 'child' });
   parent.style.animation = 'anim1 100s';
   child.style.animation = 'anim1 100s';
   parent.appendChild(child);
 
   const animations = parent.getAnimations({ subtree: true });
   assert_equals(animations.length, 6,
-                'Should find all elements, pesudo-elements that parent has');
+                'Should find all elements, pseudo-elements that parent has');
 
   assert_equals(animations[0].effect.target, parent,
                 'The animation targeting the parent element ' +
                 'should be returned first');
-  assert_equals(animations[1].effect.target.type, '::before',
+  assert_equals(animations[0].effect.pseudoElement, null,
+                'The animation targeting the parent element ' +
+                'should be returned first');
+  assert_equals(animations[1].effect.pseudoElement, '::before',
                 'The animation targeting the ::before pseudo-element ' +
                 'should be returned second');
-  assert_equals(animations[1].effect.target.element, parent,
+  assert_equals(animations[1].effect.target, parent,
                 'This ::before element should be child of parent element');
-  assert_equals(animations[2].effect.target.type, '::after',
+  assert_equals(animations[2].effect.pseudoElement, '::after',
                 'The animation targeting the ::after pesudo-element ' +
                 'should be returned third');
-  assert_equals(animations[2].effect.target.element, parent,
+  assert_equals(animations[2].effect.target, parent,
                 'This ::after element should be child of parent element');
 
   assert_equals(animations[3].effect.target, child,
                 'The animation targeting the child element ' +
                 'should be returned fourth');
-  assert_equals(animations[4].effect.target.type, '::before',
+  assert_equals(animations[4].effect.pseudoElement, '::before',
                 'The animation targeting the ::before pseudo-element ' +
                 'should be returned fifth');
-  assert_equals(animations[4].effect.target.element, child,
+  assert_equals(animations[4].effect.target, child,
                 'This ::before element should be child of child element');
-  assert_equals(animations[5].effect.target.type, '::after',
+  assert_equals(animations[5].effect.pseudoElement, '::after',
                 'The animation targeting the ::after pesudo-element ' +
                 'should be returned last');
-  assert_equals(animations[5].effect.target.element, child,
+  assert_equals(animations[5].effect.target, child,
                 'This ::after element should be child of child element');
 }, '{ subtree: true } on an element with a child returns animations from the'
    + ' element, its pseudo-elements, its child and its child pseudo-elements');
 
 test(t => {
   const parent = addDiv(t, { 'id': 'parent' });
   const child1 = addDiv(t, { 'id': 'child1' });
   const grandchild1 = addDiv(t, { 'id': 'grandchild1' });
deleted file mode 100644
--- a/testing/web-platform/tests/css/css-transitions/CSSPseudoElement-getAnimations.tentative.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>CSSPseudoElement.getAnimations() for CSS transitions</title>
-<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#animation-composite-order">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="support/helper.js"></script>
-<style>
-.init::before {
-  content: '';
-  height: 0px;
-  width: 0px;
-  opacity: 0;
-  transition: all 100s;
-}
-.change::before {
-  height: 100px;
-  width: 100px;
-  opacity: 1;
-}
-</style>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const div = addDiv(t, { class: 'init' });
-  getComputedStyle(div).width;
-  div.classList.add('change');
-
-  // Sanity checks
-  assert_equals(document.getAnimations().length, 3,
-                'Got expected number of animations on document');
-  const pseudoTarget = document.getAnimations()[0].effect.target;
-  assert_class_string(pseudoTarget, 'CSSPseudoElement',
-                      'Got pseudo-element target');
-
-  // Check animations returned from the pseudo element are in correct order
-  const anims = pseudoTarget.getAnimations();
-  assert_equals(anims.length, 3,
-                'Got expected number of animations on pseudo-element');
-  assert_equals(anims[0].transitionProperty, 'height');
-  assert_equals(anims[1].transitionProperty, 'opacity');
-  assert_equals(anims[2].transitionProperty, 'width');
-}, 'getAnimations sorts simultaneous transitions by name');
-
-</script>
--- a/testing/web-platform/tests/css/css-transitions/Document-getAnimations.tentative.html
+++ b/testing/web-platform/tests/css/css-transitions/Document-getAnimations.tentative.html
@@ -94,31 +94,36 @@ test(t => {
   );
 
   for (const [index, expected] of expectedTransitions.entries()) {
     const [element, pseudo] = expected;
     const actual = transitions[index];
 
     if (pseudo) {
       assert_equals(
-        actual.effect.target.element,
+        actual.effect.target,
         element,
         `Transition #${index + 1} has expected target`
       );
       assert_equals(
-        actual.effect.target.type,
+        actual.effect.pseudoElement,
         pseudo,
         `Transition #${index + 1} has expected pseudo type`
       );
     } else {
       assert_equals(
         actual.effect.target,
         element,
         `Transition #${index + 1} has expected target`
       );
+      assert_equals(
+        actual.effect.pseudoElement,
+        null,
+        `Transition #${index + 1} has null pseudo type`
+      );
     }
   }
 }, 'CSS Transitions targetting (pseudo-)elements should have correct order '
    + 'after sorting');
 
 promise_test(async t => {
   const div = addDiv(t, { style: 'left: 0px; transition: all 50ms' });
   getComputedStyle(div).left;
--- a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js
+++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js
@@ -405,25 +405,16 @@ const gCSSProperties = {
     }
   },
   'column-width': {
     // https://drafts.csswg.org/css-multicol/#propdef-column-width
     types: [ 'length',
       { type: 'discrete', options: [ [ 'auto', '1px' ] ] }
     ]
   },
-  'content': {
-    // https://drafts.csswg.org/css-content-3/#propdef-content
-    types: [
-      { type: 'discrete', options: [ [ '"a"', '"b"' ] ] }
-    ],
-    setup: t => {
-      return getPseudoElement(t, 'before');
-    }
-  },
   'counter-increment': {
     // https://drafts.csswg.org/css-lists-3/#propdef-counter-increment
     types: [
       { type: 'discrete', options: [ [ 'ident-1 1', 'ident-2 2' ] ] }
     ]
   },
   'counter-reset': {
     // https://drafts.csswg.org/css-lists-3/#propdef-counter-reset
@@ -1427,41 +1418,37 @@ const gCSSProperties = {
   'z-index': {
     // https://drafts.csswg.org/css-position/#propdef-z-index
     types: [
     ]
   },
 };
 
 function testAnimationSamples(animation, idlName, testSamples) {
-  const type = animation.effect.target.type;
-  const target = animation.effect.target.constructor.name === 'CSSPseudoElement'
-                 ? animation.effect.target.element
-                 : animation.effect.target;
+  const pseudoType = animation.effect.pseudoElement;
+  const target = animation.effect.target;
   for (const testSample of testSamples) {
     animation.currentTime = testSample.time;
-    assert_equals(getComputedStyle(target, type)[idlName],
+    assert_equals(getComputedStyle(target, pseudoType)[idlName],
                   testSample.expected,
                   `The value should be ${testSample.expected}` +
                   ` at ${testSample.time}ms`);
   }
 }
 
 function toOrderedArray(string) {
   return string.split(/\s*,\s/).sort();
 }
 
 // This test is for some list-based CSS properties such as font-variant-settings
 // don't specify an order for serializing computed values.
 // This test is for such the property.
 function testAnimationSamplesWithAnyOrder(animation, idlName, testSamples) {
-  const type = animation.effect.target.type;
-  const target = animation.effect.target.constructor.name === 'CSSPseudoElement'
-                 ? animation.effect.target.element
-                 : animation.effect.target;
+  const type = animation.effect.pseudoElement;
+  const target = animation.effect.target;
   for (const testSample of testSamples) {
     animation.currentTime = testSample.time;
 
     // Convert to array and sort the expected and actual value lists first
     // before comparing them.
     const computedValues =
       toOrderedArray(getComputedStyle(target, type)[idlName]);
     const expectedValues = toOrderedArray(testSample.expected);
--- a/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html
@@ -5,16 +5,21 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
 <script src="../../resources/easing-tests.js"></script>
 <script src="../../resources/keyframe-utils.js"></script>
 <script src="../../resources/keyframe-tests.js"></script>
 <script src="../../resources/timing-utils.js"></script>
 <script src="../../resources/timing-tests.js"></script>
+<style>
+.pseudo::before {content: '';}
+.pseudo::after {content: '';}
+.pseudo::marker {content: '';}
+</style>
 <body>
 <div id="log"></div>
 <iframe width="10" height="10" id="iframe"></iframe>
 <script>
 'use strict';
 
 // Tests on Element
 
@@ -230,39 +235,64 @@ promise_test(async t => {
   // Wait for the animation to start and then for one more animation
   // frame to give the transitionrun event a chance to be dispatched.
   await anim.ready;
   await waitForAnimationFrames(1);
 
   assert_false(gotTransition, 'A transition should NOT have been triggered');
 }, 'Element.animate() does NOT trigger a style change event');
 
-// Tests on CSSPseudoElement
+// Tests on pseudo-elements
+
+test(t => {
+  const div = createDiv(t);
+  div.classList.add('pseudo');
+  const anim = div.animate(null, {pseudoElement: '::before'});
+  assert_class_string(anim, 'Animation', 'The returned object is an Animation');
+}, 'animate() with pseudoElement parameter creates an Animation object');
 
 test(t => {
-  const pseudoTarget = getPseudoElement(t, 'before');
-  const anim = pseudoTarget.animate(null);
-  assert_class_string(anim, 'Animation', 'The returned object is an Animation');
-}, 'CSSPseudoElement.animate() creates an Animation object');
+  const div = createDiv(t);
+  div.classList.add('pseudo');
+  div.style.display = 'list-item';
+  const anim = div.animate(null, {pseudoElement: '::marker'});
+  assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::marker');
+}, 'animate() with pseudoElement parameter  creates an Animation object for ::marker');
 
 test(t => {
-  const pseudoTarget = getPseudoElement(t, 'marker');
-  const anim = pseudoTarget.animate(null);
-  assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::marker');
-}, 'CSSPseudoElement.animate() creates an Animation object for ::marker');
+  const div = createDiv(t);
+  div.classList.add('pseudo');
+  div.textContent = 'foo';
+  const anim = div.animate(null, {pseudoElement: '::first-line'});
+  assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::first-line');
+}, 'animate() with pseudoElement parameter  creates an Animation object for ::first-line');
 
 test(t => {
-  const pseudoTarget = getPseudoElement(t, 'before');
-  const anim = pseudoTarget.animate(null);
-  assert_equals(anim.effect.target, pseudoTarget,
-                'The returned Animation targets to the correct object');
-}, 'CSSPseudoElement.animate() creates an Animation object targeting ' +
-   'to the correct CSSPseudoElement object');
+  const div = createDiv(t);
+  div.classList.add('pseudo');
+  const anim = div.animate(null, {pseudoElement: '::before'});
+  assert_equals(anim.effect.pseudoElement, '::before',
+                'The returned Animation targets to the correct selector');
+}, 'animate() with pseudoElement an Animation object targeting ' +
+   'to the correct pseudo-element');
 
 test(t => {
-  const pseudoTarget = getPseudoElement(t, 'marker');
-  const anim = pseudoTarget.animate(null);
-  assert_equals(anim.effect.target, pseudoTarget,
-                'The returned Animation targets to the correct object for ::marker');
-}, 'CSSPseudoElement.animate() creates an Animation object targeting ' +
-   'to the correct CSSPseudoElement object for ::marker');
+  const div = createDiv(t);
+  div.classList.add('pseudo');
+  div.style.display = 'list-item';
+  const anim = div.animate(null, {pseudoElement: '::marker'});
+  assert_equals(anim.effect.pseudoElement, '::marker',
+                'The returned Animation targets to the correct selector');
+}, 'animate() with pseudoElement an Animation object targeting ' +
+   'to the correct pseudo-element for ::marker');
+
+test(t => {
+  const div = createDiv(t);
+  div.classList.add('pseudo');
+  div.textContent = 'foo';
+  const anim = div.animate(null, {pseudoElement: '::first-line'});
+  assert_equals(anim.effect.pseudoElement, '::first-line',
+                'The returned Animation targets to the correct selector');
+}, 'animate() with pseudoElement an Animation object targeting ' +
+   'to the correct pseudo-element for ::first-line');
+
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html
@@ -1,15 +1,20 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Animation.commitStyles</title>
 <link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-commitstyles">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
+<style>
+.pseudo::before {content: '';}
+.pseudo::after {content: '';}
+.pseudo::marker {content: '';}
+</style>
 <body>
 <div id="log"></div>
 <script>
 'use strict';
 
 function assert_numeric_style_equals(opacity, expected, description) {
   return assert_approx_equals(
     parseFloat(opacity),
@@ -258,20 +263,22 @@ promise_test(async t => {
   await Promise.resolve();
 
   assert_equals(mutationRecords.length, 0, 'Should have no mutation records');
 
   observer.disconnect();
 }, 'Does NOT trigger mutation observers when the change to style is redundant');
 
 test(t => {
-  const pseudo = getPseudoElement(t, 'before');
-  const animation = pseudo.animate(
+
+  const div = createDiv(t);
+  div.classList.add('pseudo');
+  const animation = div.animate(
     { opacity: 0 },
-    { duration: 1, fill: 'forwards' }
+    { duration: 1, fill: 'forwards', pseudoElement: '::before' }
   );
 
   assert_throws('NoModificationAllowedError', () => {
     animation.commitStyles();
   });
 }, 'Throws if the target element is a pseudo element');
 
 test(t => {
@@ -367,23 +374,24 @@ test(t => {
   div.remove();
 
   assert_throws('InvalidStateError', () => {
     animation.commitStyles();
   });
 }, 'Throws if the target effect is disconnected');
 
 test(t => {
-  const pseudo = getPseudoElement(t, 'before');
-  const animation = pseudo.animate(
+  const div = createDiv(t);
+  div.classList.add('pseudo');
+  const animation = div.animate(
     { opacity: 0 },
-    { duration: 1, fill: 'forwards' }
+    { duration: 1, fill: 'forwards', pseudoElement: '::before' }
   );
 
-  pseudo.element.remove();
+  div.remove();
 
   assert_throws('NoModificationAllowedError', () => {
     animation.commitStyles();
   });
 }, 'Checks the pseudo element condition before the not rendered condition');
 
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/testcommon.js
+++ b/testing/web-platform/tests/web-animations/testcommon.js
@@ -82,33 +82,16 @@ function createStyle(test, rules, doc) {
                        sheet.cssRules.length);
     }
   }
   test.add_cleanup(() => {
     extraStyle.remove();
   });
 }
 
-// Create a pseudo element
-function getPseudoElement(test, type) {
-  createStyle(test, { '@keyframes anim': '',
-                      [`.pseudo::${type}`]: 'animation: anim 10s; ' +
-                                            'content: \'\';'  });
-  const div = createDiv(test);
-  if (type == 'marker') {
-    div.style.display = 'list-item';
-  }
-  div.classList.add('pseudo');
-  const anims = document.getAnimations();
-  assert_true(anims.length >= 1);
-  const anim = anims[anims.length - 1];
-  anim.cancel();
-  return anim.effect.target;
-}
-
 // Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1).
 function cubicBezier(x1, y1, x2, y2) {
   const xForT = t => {
     const omt = 1-t;
     return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t;
   };
 
   const yForT = t => {