tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Element-getAnimations.tentative.html (17378B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>Element.getAnimations() for CSS animations</title>
      4 <!-- TODO: Add a more specific link for this once it is specified. -->
      5 <link rel="help" href="https://drafts.csswg.org/css-animations-2/">
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 <script src="support/testcommon.js"></script>
      9 <style>
     10 @keyframes anim1 {
     11  to { left: 100px }
     12 }
     13 @keyframes anim2 {
     14  to { top: 100px }
     15 }
     16 @keyframes multiPropAnim {
     17  to { background: green, opacity: 0.5, left: 100px, top: 100px }
     18 }
     19 ::before {
     20  content: ''
     21 }
     22 ::after {
     23  content: ''
     24 }
     25 @keyframes empty { }
     26 </style>
     27 <div id="log"></div>
     28 <script>
     29 'use strict';
     30 
     31 test(t => {
     32  const div = addDiv(t);
     33  assert_equals(div.getAnimations().length, 0,
     34    'getAnimations returns an empty sequence for an element'
     35    + ' with no animations');
     36 }, 'getAnimations for non-animated content');
     37 
     38 promise_test(async t => {
     39  const div = addDiv(t);
     40 
     41  // Add an animation
     42  div.style.animation = 'anim1 100s';
     43  let animations = div.getAnimations();
     44  assert_equals(animations.length, 1,
     45    'getAnimations returns an Animation running CSS Animations');
     46  await animations[0].ready;
     47 
     48  // Add a second animation
     49  div.style.animation = 'anim1 100s, anim2 100s';
     50  animations = div.getAnimations();
     51  assert_equals(animations.length, 2,
     52    'getAnimations returns one CSSAnimation for each value of animation-name');
     53  // (We don't check the order of the Animations since that is covered by tests
     54  //  later in this file.)
     55 }, 'getAnimations for CSS Animations');
     56 
     57 test(t => {
     58  const div = addDiv(t, { style: 'animation: anim1 100s' });
     59  assert_class_string(div.getAnimations()[0], 'CSSAnimation',
     60                      'Interface of returned animation is CSSAnimation');
     61 }, 'getAnimations returns CSSAnimation objects for CSS Animations');
     62 
     63 test(t => {
     64  const div = addDiv(t);
     65 
     66  // Add an animation that targets multiple properties
     67  div.style.animation = 'multiPropAnim 100s';
     68  assert_equals(div.getAnimations().length, 1,
     69    'getAnimations returns only one Animation for a CSS Animation'
     70    + ' that targets multiple properties');
     71 }, 'getAnimations for multi-property animations');
     72 
     73 promise_test(async t => {
     74  const div = addDiv(t);
     75 
     76  // Add an animation
     77  div.style.backgroundColor = 'red';
     78  div.style.animation = 'anim1 100s';
     79  getComputedStyle(div).backgroundColor;
     80 
     81  // Wait until a frame after the animation starts, then add a transition
     82  let animations = div.getAnimations();
     83  await animations[0].ready;
     84  await waitForFrame();
     85 
     86  div.style.transition = 'all 100s';
     87  div.style.backgroundColor = 'green';
     88 
     89  animations = div.getAnimations();
     90  assert_equals(animations.length, 2,
     91    'getAnimations returns Animations for both animations and'
     92    + ' transitions that run simultaneously');
     93  assert_class_string(animations[0], 'CSSTransition',
     94                      'First-returned animation is the CSS Transition');
     95  assert_class_string(animations[1], 'CSSAnimation',
     96                      'Second-returned animation is the CSS Animation');
     97 }, 'getAnimations for both CSS Animations and CSS Transitions at once');
     98 
     99 async_test(t => {
    100  const div = addDiv(t);
    101 
    102  // Set up event listener
    103  div.addEventListener('animationend', t.step_func(() => {
    104    assert_equals(div.getAnimations().length, 0,
    105      'getAnimations does not return Animations for finished '
    106      + ' (and non-forwards-filling) CSS Animations');
    107    t.done();
    108  }));
    109 
    110  // Add a very short animation
    111  div.style.animation = 'anim1 0.01s';
    112 }, 'getAnimations for CSS Animations that have finished');
    113 
    114 async_test(t => {
    115  const div = addDiv(t);
    116 
    117  // Set up event listener
    118  div.addEventListener('animationend', t.step_func(() => {
    119    assert_equals(div.getAnimations().length, 1,
    120      'getAnimations returns Animations for CSS Animations that have'
    121      + ' finished but are filling forwards');
    122    t.done();
    123  }));
    124 
    125  // Add a very short animation
    126  div.style.animation = 'anim1 0.01s forwards';
    127 }, 'getAnimations for CSS Animations that have finished but are'
    128   + ' forwards filling');
    129 
    130 test(t => {
    131  const div = addDiv(t);
    132  div.style.animation = 'none 100s';
    133 
    134  let animations = div.getAnimations();
    135  assert_equals(animations.length, 0,
    136    'getAnimations returns an empty sequence for an element'
    137    + ' with animation-name: none');
    138 
    139  div.style.animation = 'none 100s, anim1 100s';
    140  animations = div.getAnimations();
    141  assert_equals(animations.length, 1,
    142    'getAnimations returns Animations only for those CSS Animations whose'
    143    + ' animation-name is not none');
    144 }, 'getAnimations for CSS Animations with animation-name: none');
    145 
    146 test(t => {
    147  const div = addDiv(t);
    148  div.style.animation = 'missing 100s';
    149  let animations = div.getAnimations();
    150  assert_equals(animations.length, 0,
    151    'getAnimations returns an empty sequence for an element'
    152    + ' with animation-name: missing');
    153 
    154  div.style.animation = 'anim1 100s, missing 100s';
    155  animations = div.getAnimations();
    156  assert_equals(animations.length, 1,
    157    'getAnimations returns Animations only for those CSS Animations whose'
    158    + ' animation-name is found');
    159 }, 'getAnimations for CSS Animations with animation-name: missing');
    160 
    161 promise_test(async t => {
    162  const div = addDiv(t);
    163  div.style.animation = 'anim1 100s, notyet 100s';
    164  let animations = div.getAnimations();
    165  assert_equals(animations.length, 1,
    166    'getAnimations initally only returns Animations for CSS Animations whose'
    167    + ' animation-name is found');
    168 
    169  await animations[0].ready;
    170  await waitForFrame();
    171 
    172  const keyframes = '@keyframes notyet { to { left: 100px; } }';
    173  document.styleSheets[0].insertRule(keyframes, 0);
    174  animations = div.getAnimations();
    175  assert_equals(animations.length, 2,
    176    'getAnimations includes Animation when @keyframes rule is added'
    177    + ' later');
    178  await waitForAllAnimations(animations);
    179 
    180  assert_true(animations[0].startTime < animations[1].startTime,
    181    'Newly added animation has a later start time');
    182  document.styleSheets[0].deleteRule(0);
    183 }, 'getAnimations for CSS Animations where the @keyframes rule is added'
    184   + ' later');
    185 
    186 test(t => {
    187  const div = addDiv(t);
    188  div.style.animation = 'anim1 100s, anim1 100s';
    189  assert_equals(div.getAnimations().length, 2,
    190    'getAnimations returns one Animation for each CSS animation-name'
    191    + ' even if the names are duplicated');
    192 }, 'getAnimations for CSS Animations with duplicated animation-name');
    193 
    194 test(t => {
    195  const div = addDiv(t);
    196  div.style.animation = 'empty 100s';
    197  assert_equals(div.getAnimations().length, 1,
    198    'getAnimations returns Animations for CSS animations with an'
    199    + ' empty keyframes rule');
    200 }, 'getAnimations for CSS Animations with empty keyframes rule');
    201 
    202 promise_test(async t => {
    203  const div = addDiv(t);
    204  div.style.animation = 'anim1 100s 100s';
    205  const animations = div.getAnimations();
    206  assert_equals(animations.length, 1,
    207    'getAnimations returns animations for CSS animations whose'
    208    + ' delay makes them start later');
    209  await animations[0].ready;
    210  await waitForFrame();
    211 
    212  assert_true(animations[0].startTime <= document.timeline.currentTime,
    213    'For CSS Animations in delay phase, the start time of the Animation is'
    214    + ' not in the future');
    215 }, 'getAnimations for CSS animations in delay phase');
    216 
    217 test(t => {
    218  const div = addDiv(t);
    219  div.style.animation = 'anim1 0s 100s';
    220  assert_equals(div.getAnimations().length, 1,
    221    'getAnimations returns animations for CSS animations whose'
    222    + ' duration is zero');
    223  div.remove();
    224 }, 'getAnimations for zero-duration CSS Animations');
    225 
    226 test(t => {
    227  const div = addDiv(t);
    228  div.style.animation = 'anim1 100s';
    229  const originalAnimation = div.getAnimations()[0];
    230 
    231  // Update pause state (an Animation change)
    232  div.style.animationPlayState = 'paused';
    233  const pendingAnimation = div.getAnimations()[0];
    234  assert_equals(pendingAnimation.playState, 'paused',
    235                'animation\'s play state is updated');
    236  assert_equals(originalAnimation, pendingAnimation,
    237                'getAnimations returns the same objects even when their'
    238                + ' play state changes');
    239 
    240  // Update duration (an Animation change)
    241  div.style.animationDuration = '200s';
    242  const extendedAnimation = div.getAnimations()[0];
    243  assert_equals(
    244    extendedAnimation.effect.getTiming().duration,
    245    200 * MS_PER_SEC,
    246    'animation\'s duration has been updated'
    247  );
    248  assert_equals(originalAnimation, extendedAnimation,
    249                'getAnimations returns the same objects even when their'
    250                + ' duration changes');
    251 }, 'getAnimations returns objects with the same identity');
    252 
    253 test(t => {
    254  const div = addDiv(t);
    255  div.style.animation = 'anim1 100s';
    256 
    257  assert_equals(div.getAnimations().length, 1,
    258    'getAnimations returns an animation before canceling');
    259 
    260  const animation = div.getAnimations()[0];
    261 
    262  animation.cancel();
    263  assert_equals(div.getAnimations().length, 0,
    264    'getAnimations does not return canceled animations');
    265 
    266  animation.play();
    267  assert_equals(div.getAnimations().length, 1,
    268    'getAnimations returns canceled animations that have been re-started');
    269 
    270 }, 'getAnimations for CSS Animations that are canceled');
    271 
    272 promise_test(async t => {
    273  const div = addDiv(t);
    274  div.style.animation = 'anim2 100s';
    275 
    276  await div.getAnimations()[0].ready;
    277 
    278  // Prepend to the list and test that even though anim1 was triggered
    279  // *after* anim2, it should come first because it appears first
    280  // in the animation-name property.
    281  div.style.animation = 'anim1 100s, anim2 100s';
    282  let anims = div.getAnimations();
    283  assert_equals(anims[0].animationName, 'anim1',
    284                'animation order after prepending to list');
    285  assert_equals(anims[1].animationName, 'anim2',
    286                'animation order after prepending to list');
    287 
    288  // Normally calling cancel and play would this push anim1 to the top of
    289  // the stack but it shouldn't for CSS animations that map an the
    290  // animation-name property.
    291  const anim1 = anims[0];
    292  anim1.cancel();
    293  anim1.play();
    294  anims = div.getAnimations();
    295  assert_equals(anims[0].animationName, 'anim1',
    296                'animation order after canceling and restarting');
    297  assert_equals(anims[1].animationName, 'anim2',
    298                'animation order after canceling and restarting');
    299 }, 'getAnimations for CSS Animations follows animation-name order');
    300 
    301 test(t => {
    302  addStyle(t, { '#target::after': 'animation: anim1 10s;',
    303                '#target::before': 'animation: anim1 10s;' });
    304  const target = addDiv(t, { 'id': 'target' });
    305  target.style.animation = 'anim1 100s';
    306  const animations = target.getAnimations({ subtree: false });
    307 
    308  assert_equals(animations.length, 1,
    309                'Should find only the element');
    310  assert_equals(animations[0].effect.target, target,
    311                'Effect target should be the element');
    312 }, '{ subtree: false } on a leaf element returns the element\'s animations'
    313   + ' and ignore pseudo-elements');
    314 
    315 test(t => {
    316  addStyle(t, { '#target::after': 'animation: anim1 10s;',
    317                '#target::before': 'animation: anim1 10s;' });
    318  const target = addDiv(t, { 'id': 'target' });
    319  target.style.animation = 'anim1 100s';
    320  const animations = target.getAnimations({ subtree: true });
    321 
    322  assert_equals(animations.length, 3,
    323                'getAnimations({ subtree: true }) ' +
    324                'should return animations on pseudo-elements');
    325  assert_equals(animations[0].effect.target, target,
    326                'The animation targeting the parent element ' +
    327                'should be returned first');
    328  assert_equals(animations[0].effect.pseudoElement, null,
    329                'The animation targeting the parent element ' +
    330                'should be returned first')
    331  assert_equals(animations[1].effect.pseudoElement, '::before',
    332                'The animation targeting the ::before pseudo-element ' +
    333                'should be returned second');
    334  assert_equals(animations[2].effect.pseudoElement, '::after',
    335                'The animation targeting the ::after pesudo-element ' +
    336                'should be returned last');
    337 }, '{ subtree: true } on a leaf element returns the element\'s animations'
    338   + ' and its pseudo-elements\' animations');
    339 
    340 test(t => {
    341  addStyle(t, { '#parent::after': 'animation: anim1 10s;',
    342                '#parent::before': 'animation: anim1 10s;',
    343                '#child::after': 'animation: anim1 10s;',
    344                '#child::before': 'animation: anim1 10s;' });
    345  const parent = addDiv(t, { 'id': 'parent' });
    346  parent.style.animation = 'anim1 100s';
    347  const child = addDiv(t, { 'id': 'child' });
    348  child.style.animation = 'anim1 100s';
    349  parent.appendChild(child);
    350 
    351  const animations = parent.getAnimations({ subtree: false });
    352  assert_equals(animations.length, 1,
    353                'Should find only the element even if it has a child');
    354  assert_equals(animations[0].effect.target, parent,
    355                'Effect target should be the element');
    356 }, '{ subtree: false } on an element with a child returns only the element\'s'
    357   + ' animations');
    358 
    359 test(t => {
    360  addStyle(t, { '#parent::after': 'animation: anim1 10s;',
    361                '#parent::before': 'animation: anim1 10s;',
    362                '#child::after': 'animation: anim1 10s;',
    363                '#child::before': 'animation: anim1 10s;' });
    364  const parent = addDiv(t, { 'id': 'parent' });
    365  const child = addDiv(t, { 'id': 'child' });
    366  parent.style.animation = 'anim1 100s';
    367  child.style.animation = 'anim1 100s';
    368  parent.appendChild(child);
    369 
    370  const animations = parent.getAnimations({ subtree: true });
    371  assert_equals(animations.length, 6,
    372                'Should find all elements, pseudo-elements that parent has');
    373 
    374  assert_equals(animations[0].effect.target, parent,
    375                'The animation targeting the parent element ' +
    376                'should be returned first');
    377  assert_equals(animations[0].effect.pseudoElement, null,
    378                'The animation targeting the parent element ' +
    379                'should be returned first');
    380  assert_equals(animations[1].effect.pseudoElement, '::before',
    381                'The animation targeting the ::before pseudo-element ' +
    382                'should be returned second');
    383  assert_equals(animations[1].effect.target, parent,
    384                'This ::before element should be child of parent element');
    385  assert_equals(animations[2].effect.pseudoElement, '::after',
    386                'The animation targeting the ::after pesudo-element ' +
    387                'should be returned third');
    388  assert_equals(animations[2].effect.target, parent,
    389                'This ::after element should be child of parent element');
    390 
    391  assert_equals(animations[3].effect.target, child,
    392                'The animation targeting the child element ' +
    393                'should be returned fourth');
    394  assert_equals(animations[4].effect.pseudoElement, '::before',
    395                'The animation targeting the ::before pseudo-element ' +
    396                'should be returned fifth');
    397  assert_equals(animations[4].effect.target, child,
    398                'This ::before element should be child of child element');
    399  assert_equals(animations[5].effect.pseudoElement, '::after',
    400                'The animation targeting the ::after pesudo-element ' +
    401                'should be returned last');
    402  assert_equals(animations[5].effect.target, child,
    403                'This ::after element should be child of child element');
    404 }, '{ subtree: true } on an element with a child returns animations from the'
    405   + ' element, its pseudo-elements, its child and its child pseudo-elements');
    406 
    407 test(t => {
    408  const parent = addDiv(t, { 'id': 'parent' });
    409  const child1 = addDiv(t, { 'id': 'child1' });
    410  const grandchild1 = addDiv(t, { 'id': 'grandchild1' });
    411  const grandchild2 = addDiv(t, { 'id': 'grandchild2' });
    412  const child2 = addDiv(t, { 'id': 'child2' });
    413 
    414  parent.style.animation = 'anim1 100s';
    415  child1.style.animation = 'anim1 100s';
    416  grandchild1.style.animation = 'anim1 100s';
    417  grandchild2.style.animation = 'anim1 100s';
    418  child2.style.animation = 'anim1 100s';
    419 
    420  parent.appendChild(child1);
    421  child1.appendChild(grandchild1);
    422  child1.appendChild(grandchild2);
    423  parent.appendChild(child2);
    424 
    425  const animations = parent.getAnimations({ subtree: true });
    426  assert_equals(
    427    parent.getAnimations({ subtree: true }).length, 5,
    428                         'Should find all descendants of the element');
    429 
    430  assert_equals(animations[0].effect.target, parent,
    431                'The animation targeting the parent element ' +
    432                'should be returned first');
    433 
    434  assert_equals(animations[1].effect.target, child1,
    435                'The animation targeting the child1 element ' +
    436                'should be returned second');
    437 
    438  assert_equals(animations[2].effect.target, grandchild1,
    439                'The animation targeting the grandchild1 element ' +
    440                'should be returned third');
    441 
    442  assert_equals(animations[3].effect.target, grandchild2,
    443                'The animation targeting the grandchild2 element ' +
    444                'should be returned fourth');
    445 
    446  assert_equals(animations[4].effect.target, child2,
    447                'The animation targeting the child2 element ' +
    448                'should be returned last');
    449 
    450 }, '{ subtree: true } on an element with many descendants returns animations'
    451   + ' from all the descendants');
    452 
    453 </script>