tor-browser

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

commitStyles.html (17143B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>Animation.commitStyles</title>
      4 <link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-commitstyles">
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="../../testcommon.js"></script>
      8 <style>
      9 .pseudo::before {content: '';}
     10 .pseudo::after {content: '';}
     11 .pseudo::marker {content: '';}
     12 </style>
     13 <body>
     14 <div id="log"></div>
     15 <script>
     16 'use strict';
     17 
     18 function assert_numeric_style_equals(opacity, expected, description) {
     19  return assert_approx_equals(
     20    parseFloat(opacity),
     21    expected,
     22    0.0001,
     23    description
     24  );
     25 }
     26 
     27 // -------------------------
     28 // Tests covering elements not capable of having a style attribute
     29 // --------------------
     30 
     31 test(t => {
     32  const animation = createDiv(t).animate(
     33    { opacity: 0 },
     34    { duration: 1 }
     35  );
     36 
     37  const nonStyleElement = document.createElementNS(
     38    'http://example.org/test',
     39    'test'
     40  );
     41  document.body.appendChild(nonStyleElement);
     42  animation.effect.target = nonStyleElement;
     43 
     44  assert_throws_dom('NoModificationAllowedError', () => {
     45    animation.commitStyles();
     46  });
     47 
     48  nonStyleElement.remove();
     49 }, 'Throws if the target element is not something with a style attribute');
     50 
     51 test(t => {
     52  const div = createDiv(t);
     53  div.classList.add('pseudo');
     54  const animation = div.animate(
     55    { opacity: 0 },
     56    { duration: 1, pseudoElement: '::before' }
     57  );
     58 
     59  assert_throws_dom('NoModificationAllowedError', () => {
     60    animation.commitStyles();
     61  });
     62 }, 'Throws if the target element is a pseudo element');
     63 
     64 test(t => {
     65  const div = createDiv(t);
     66  div.classList.add('pseudo');
     67  const animation = div.animate(
     68    { opacity: 0 },
     69    { duration: 1, pseudoElement: '::before' }
     70  );
     71 
     72  div.remove();
     73 
     74  assert_throws_dom('NoModificationAllowedError', () => {
     75    animation.commitStyles();
     76  });
     77 }, 'Checks the pseudo element condition before the not rendered condition');
     78 
     79 // -------------------------
     80 // Tests covering elements that are not being rendered
     81 // -------------------------
     82 
     83 test(t => {
     84  const div = createDiv(t);
     85  const animation = div.animate(
     86    { opacity: 0 },
     87    { duration: 1 }
     88  );
     89 
     90  div.remove();
     91 
     92  assert_throws_dom('InvalidStateError', () => {
     93    animation.commitStyles();
     94  });
     95 }, 'Throws if the target effect is disconnected');
     96 
     97 test(t => {
     98  const div = createDiv(t);
     99  const animation = div.animate(
    100    { opacity: 0 },
    101    { duration: 1 }
    102  );
    103 
    104  div.style.display = 'none';
    105 
    106  assert_throws_dom('InvalidStateError', () => {
    107    animation.commitStyles();
    108  });
    109 }, 'Throws if the target effect is display:none');
    110 
    111 test(t => {
    112  const container = createDiv(t);
    113  const div = createDiv(t);
    114  container.append(div);
    115 
    116  const animation = div.animate(
    117    { opacity: 0 },
    118    { duration: 1 }
    119  );
    120 
    121  container.style.display = 'none';
    122 
    123  assert_throws_dom('InvalidStateError', () => {
    124    animation.commitStyles();
    125  });
    126 }, "Throws if the target effect's ancestor is display:none");
    127 
    128 test(t => {
    129  const container = createDiv(t);
    130  const div = createDiv(t);
    131  container.append(div);
    132 
    133  const animation = div.animate(
    134    { opacity: 0 },
    135    { duration: 1 }
    136  );
    137 
    138  container.style.display = 'contents';
    139 
    140  // Should NOT throw
    141  animation.commitStyles();
    142 }, 'Treats display:contents as rendered');
    143 
    144 test(t => {
    145  const container = createDiv(t);
    146  const div = createDiv(t);
    147  container.append(div);
    148 
    149  const animation = div.animate(
    150    { opacity: 0 },
    151    { duration: 1 }
    152  );
    153 
    154  div.style.display = 'contents';
    155  container.style.display = 'none';
    156 
    157  assert_throws_dom('InvalidStateError', () => {
    158    animation.commitStyles();
    159  });
    160 }, 'Treats display:contents in a display:none subtree as not rendered');
    161 
    162 // -------------------------
    163 // Tests covering various parts of the active interval
    164 // -------------------------
    165 
    166 test(t => {
    167  const div = createDiv(t);
    168  div.style.opacity = '0.1';
    169 
    170  const animation = div.animate(
    171    { opacity: 0.2 },
    172    { duration: 1 }
    173  );
    174  animation.finish();
    175 
    176  animation.commitStyles();
    177 
    178  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.2);
    179 }, 'Commits styles');
    180 
    181 test(t => {
    182    const div = createDiv(t);
    183    div.style.opacity = '0.1';
    184 
    185    const animation = div.animate(
    186        { opacity: [0.5, 1] },
    187        { duration: 1 }
    188    );
    189    animation.playbackRate = -1;
    190    animation.finish();
    191 
    192    animation.commitStyles();
    193 
    194    assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5);
    195 }, 'Commits styles for backwards animation');
    196 
    197 test(t => {
    198  const div = createDiv(t);
    199  div.style.marginLeft = '10px';
    200 
    201  const animation = div.animate({ opacity: [0.2, 0.7] }, 1000);
    202  animation.currentTime = 500;
    203  animation.commitStyles();
    204  animation.cancel();
    205 
    206  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.45);
    207 }, 'Commits values calculated mid-interval');
    208 
    209 test(t => {
    210  const div = createDiv(t);
    211  div.style.opacity = '0';
    212 
    213  const animation = div.animate(
    214    { opacity: 1 },
    215    { duration: 1000, delay: 1000 }
    216  );
    217  animation.currentTime = 500;
    218  animation.commitStyles();
    219  animation.cancel();
    220 
    221  assert_numeric_style_equals(getComputedStyle(div).opacity, 0);
    222 }, 'Commits values during the start delay');
    223 
    224 test(t => {
    225  const div = createDiv(t);
    226  div.style.opacity = '0';
    227 
    228  const animation = div.animate(
    229    { opacity: 1 },
    230    { duration: 1000, delay: 1000 }
    231  );
    232  animation.currentTime = 2100;
    233  animation.commitStyles();
    234 
    235  assert_numeric_style_equals(getComputedStyle(div).opacity, 0);
    236 }, 'Commits value after the active interval');
    237 
    238 // -------------------------
    239 // Tests covering various parts of the stack
    240 // -------------------------
    241 
    242 promise_test(async t => {
    243  const div = createDiv(t);
    244  div.style.opacity = '0.1';
    245 
    246  const animA = div.animate(
    247    { opacity: '0.2' },
    248    { duration: 1, fill: 'forwards' }
    249  );
    250  const animB = div.animate(
    251    { opacity: '0.2', composite: 'add' },
    252    { duration: 1 }
    253  );
    254  const animC = div.animate(
    255    { opacity: '0.3', composite: 'add' },
    256    { duration: 1 }
    257  );
    258 
    259  animA.persist();
    260  animB.persist();
    261 
    262  await animB.finished;
    263 
    264  // The values above have been chosen such that various error conditions
    265  // produce results that all differ from the desired result:
    266  //
    267  //  Expected result:
    268  //
    269  //    animA + animB = 0.4
    270  //
    271  //  Likely error results:
    272  //
    273  //    <underlying> = 0.1
    274  //    (Commit didn't work at all)
    275  //
    276  //    animB = 0.2
    277  //    (Didn't add at all when resolving)
    278  //
    279  //    <underlying> + animB = 0.3
    280  //    (Added to the underlying value instead of lower-priority animations when
    281  //    resolving)
    282  //
    283  //    <underlying> + animA + animB = 0.5
    284  //    (Didn't respect the composite mode of lower-priority animations)
    285  //
    286  //    animA + animB + animC = 0.7
    287  //    (Resolved the whole stack, not just up to the target effect)
    288  //
    289 
    290  animB.commitStyles();
    291 
    292  animA.cancel();
    293 
    294  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.4);
    295 }, 'Commits the intermediate value of an animation in the middle of stack');
    296 
    297 promise_test(async t => {
    298  const div = createDiv(t);
    299  div.style.opacity = '0.1';
    300 
    301  const animA = div.animate(
    302    { opacity: '0.2' },
    303    { duration: 1 }
    304  );
    305  const animB = div.animate(
    306    { opacity: '0.2', composite: 'add' },
    307    { duration: 1 }
    308  );
    309  const animC = div.animate(
    310    { opacity: '0.3', composite: 'add' },
    311    { duration: 1 }
    312  );
    313 
    314  animA.persist();
    315  animB.persist();
    316 
    317  await animB.finished;
    318 
    319  // The values above have been chosen such that various error conditions
    320  // produce results that all differ from the desired result:
    321  //
    322  //  Expected result:
    323  //
    324  //    animA + animB = 0.4
    325  //
    326  //  Likely error results:
    327  //
    328  //    <underlying> = 0.1
    329  //    (Commit didn't work at all)
    330  //
    331  //    animB = 0.2
    332  //    (Didn't add at all when resolving)
    333  //
    334  //    <underlying> + animB = 0.3
    335  //    (Added to the underlying value instead of lower-priority animations when
    336  //    resolving)
    337  //
    338  //    <underlying> + animA + animB = 0.5
    339  //    (Didn't respect the composite mode of lower-priority animations)
    340  //
    341  //    animA + animB + animC = 0.7
    342  //    (Resolved the whole stack, not just up to the target effect)
    343  //
    344 
    345  animA.commitStyles();
    346  animB.commitStyles();
    347 
    348  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.4);
    349 }, 'Commits the intermediate value of an animation up to the middle of the stack');
    350 
    351 promise_test(async t => {
    352  const div = createDiv(t);
    353  div.style.opacity = '0.1';
    354 
    355  const animA = div.animate(
    356    { opacity: 0.2 },
    357    { duration: 1 }
    358  );
    359  const animB = div.animate(
    360    { opacity: 0.3 },
    361    { duration: 1 }
    362  );
    363 
    364  await animA.finished;
    365 
    366  animB.cancel();
    367 
    368  animA.commitStyles();
    369 
    370  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.2);
    371 }, 'Commits styles for an animation that has been removed');
    372 
    373 promise_test(async t => {
    374  const div = createDiv(t);
    375  div.style.opacity = '0.1';
    376 
    377  const animA = div.animate(
    378    { opacity: '0.2', composite: 'add' },
    379    { duration: 1, fill: 'forwards' }
    380  );
    381  const animB = div.animate(
    382    { opacity: '0.2', composite: 'add' },
    383    { duration: 1 }
    384  );
    385  const animC = div.animate(
    386    { opacity: '0.3', composite: 'add' },
    387    { duration: 1 }
    388  );
    389 
    390  animA.persist();
    391  animB.persist();
    392  await animB.finished;
    393 
    394  // The error cases are similar to the above test with one additional case;
    395  // verifying that the animations composite on top of the correct underlying
    396  // base style.
    397  //
    398  //  Expected result:
    399  //
    400  //  <underlying> + animA + animB = 0.5
    401  //
    402  //  Additional error results:
    403  //
    404  //    <underlying> + animA + animB + animC + animA + animB = 1.0 (saturates)
    405  //    (Added to the computed value instead of underlying value when
    406  //    resolving)
    407  //
    408  //    animA + animB = 0.4
    409  //    Failed to composite on top of underlying value.
    410  //
    411 
    412  animB.commitStyles();
    413 
    414  animA.cancel();
    415 
    416  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5);
    417 }, 'Commit composites on top of the underlying value');
    418 
    419 // -------------------------
    420 // Tests covering handling of logical properties
    421 // -------------------------
    422 
    423 test(t => {
    424  const div = createDiv(t);
    425  div.style.marginLeft = '10px';
    426 
    427  const animation = div.animate(
    428    { marginInlineStart: '20px' },
    429    { duration: 1 }
    430  );
    431  animation.finish();
    432 
    433  animation.commitStyles();
    434 
    435  assert_equals(getComputedStyle(div).marginLeft, '20px');
    436 }, 'Commits logical properties');
    437 
    438 test(t => {
    439  const div = createDiv(t);
    440  div.style.marginLeft = '10px';
    441 
    442  const animation = div.animate(
    443    { marginInlineStart: '20px' },
    444    { duration: 1 }
    445  );
    446  animation.finish();
    447 
    448  animation.commitStyles();
    449 
    450  assert_equals(div.style.marginLeft, '20px');
    451 }, 'Commits logical properties as physical properties');
    452 
    453 test(t => {
    454  const div = createDiv(t);
    455  div.style.fontSize = '10px';
    456 
    457  const animation = div.animate(
    458    { width: '10em' },
    459    { duration: 1 }
    460  );
    461  animation.finish();
    462  animation.commitStyles();
    463 
    464  assert_numeric_style_equals(getComputedStyle(div).width, 100);
    465 
    466  div.style.fontSize = '20px';
    467  assert_numeric_style_equals(
    468    getComputedStyle(div).width,
    469    100,
    470    'Changes to the font-size should have no effect'
    471  );
    472 }, 'Commits em units as pixel values');
    473 
    474 // -------------------------
    475 // Tests covering CSS variables
    476 // -------------------------
    477 
    478 test(t => {
    479  const div = createDiv(t);
    480  div.style.setProperty('--target', '0.5');
    481  div.style.opacity = 'var(--target)';
    482  const animation = div.animate(
    483    { '--target': 0.8 },
    484    { duration: 1 }
    485  );
    486  animation.finish();
    487  animation.commitStyles();
    488 
    489  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.8);
    490 }, 'Commits custom variables');
    491 
    492 test(t => {
    493  const div = createDiv(t);
    494  div.style.setProperty('--target', '0.5');
    495 
    496  const animation = div.animate(
    497    { opacity: 'var(--target)' },
    498    { duration: 1 }
    499  );
    500  animation.finish();
    501  animation.commitStyles();
    502 
    503  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5);
    504 
    505  // Changes to the variable should have no effect
    506  div.style.setProperty('--target', '1');
    507 
    508  assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5);
    509 }, 'Commits variable references as their computed values');
    510 
    511 // -------------------------
    512 // Tests covering the composition of specific properties
    513 // (e.g. line-height, transforms)
    514 // -------------------------
    515 
    516 test(t => {
    517  const div = createDiv(t);
    518  div.style.fontSize = '10px';
    519 
    520  const animation = div.animate(
    521    { lineHeight: '1.5' },
    522    { duration: 1 }
    523  );
    524  animation.finish();
    525  animation.commitStyles();
    526 
    527  assert_numeric_style_equals(getComputedStyle(div).lineHeight, 15);
    528  assert_equals(
    529    div.style.lineHeight,
    530    '1.5',
    531    'line-height is committed as a relative value'
    532  );
    533 
    534  div.style.fontSize = '20px';
    535  assert_numeric_style_equals(
    536    getComputedStyle(div).lineHeight,
    537    30,
    538    'Changes to the font-size should affect the committed line-height'
    539  );
    540 }, 'Commits relative line-height');
    541 
    542 test(t => {
    543  const div = createDiv(t);
    544  const animation = div.animate(
    545    { transform: 'translate(20px, 20px)' },
    546    { duration: 1 }
    547  );
    548  animation.finish();
    549  animation.commitStyles();
    550 
    551  assert_equals(
    552    getComputedStyle(div).transform,
    553    'matrix(1, 0, 0, 1, 20, 20)'
    554  );
    555 }, 'Commits transforms');
    556 
    557 test(t => {
    558  const div = createDiv(t);
    559  div.style.translate = '100px';
    560  div.style.rotate = '45deg';
    561  div.style.scale = '2';
    562 
    563  const animation = div.animate(
    564    { translate: '200px', rotate: '90deg', scale: 3 },
    565    { duration: 1 }
    566  );
    567  animation.finish();
    568 
    569  animation.commitStyles();
    570 
    571  assert_equals(getComputedStyle(div).translate, '200px');
    572  assert_equals(getComputedStyle(div).rotate, '90deg');
    573  assert_numeric_style_equals(getComputedStyle(div).scale, 3);
    574 }, 'Commits styles for individual transform properties');
    575 
    576 test(t => {
    577  const div = createDiv(t);
    578  const animation = div.animate(
    579    { transform: 'translate(20px, 20px)' },
    580    { duration: 1 }
    581  );
    582  animation.finish();
    583  animation.commitStyles();
    584 
    585  assert_equals(div.style.transform, 'translate(20px, 20px)');
    586 }, 'Commits transforms as a transform list');
    587 
    588 test(t => {
    589  const div = createDiv(t);
    590  div.style.width = '200px';
    591  div.style.height = '200px';
    592 
    593  const animation = div.animate(
    594    { transform: ['translate(100%, 0%)', 'scale(3)'] },
    595    1000
    596  );
    597  animation.currentTime = 500;
    598  animation.commitStyles();
    599  animation.cancel();
    600 
    601  // TODO(https://github.com/w3c/csswg-drafts/issues/2854):
    602  // We can't check the committed value directly since it is not specced yet in this case,
    603  // but it should still produce the correct resolved value.
    604  assert_equals(
    605    getComputedStyle(div).transform,
    606    'matrix(2, 0, 0, 2, 100, 0)',
    607    'Resolved transform is correct after commit.'
    608  );
    609 }, 'Commits matrix-interpolated relative transforms');
    610 
    611 test(t => {
    612  const div = createDiv(t);
    613  div.style.width = '200px';
    614  div.style.height = '200px';
    615 
    616  const animation = div.animate({ transform: ['none', 'none'] }, 1000);
    617  animation.currentTime = 500;
    618  animation.commitStyles();
    619  animation.cancel();
    620 
    621  assert_equals(
    622    div.style.transform,
    623    'none',
    624    'Resolved transform is correct after commit.'
    625  );
    626 }, "Commits 'none' transform");
    627 
    628 test(t => {
    629  const div = createDiv(t);
    630  div.style.margin = '10px';
    631 
    632  const animation = div.animate(
    633    { margin: '20px' },
    634    { duration: 1 }
    635  );
    636  animation.finish();
    637 
    638  animation.commitStyles();
    639 
    640  assert_equals(div.style.marginLeft, '20px');
    641 }, 'Commits shorthand styles');
    642 
    643 // -------------------------
    644 // Tests related to setting the style attributes
    645 // (e.g. mutation observer related ones)
    646 // -------------------------
    647 
    648 promise_test(async t => {
    649  const div = createDiv(t);
    650  div.style.opacity = '0.1';
    651 
    652  // Setup animation
    653  const animation = div.animate(
    654    { opacity: 0.2 },
    655    { duration: 1 }
    656  );
    657  animation.finish();
    658 
    659  // Setup observer
    660  const mutationRecords = [];
    661  const observer = new MutationObserver(mutations => {
    662    mutationRecords.push(...mutations);
    663  });
    664  observer.observe(div, { attributes: true, attributeOldValue: true });
    665 
    666  animation.commitStyles();
    667 
    668  // Wait for mutation records to be dispatched
    669  await Promise.resolve();
    670 
    671  assert_equals(mutationRecords.length, 1, 'Should have one mutation record');
    672 
    673  const mutation = mutationRecords[0];
    674  assert_equals(mutation.type, 'attributes');
    675  assert_equals(mutation.oldValue, 'opacity: 0.1;');
    676 
    677  observer.disconnect();
    678 }, 'Triggers mutation observers when updating style');
    679 
    680 promise_test(async t => {
    681  const div = createDiv(t);
    682  div.style.opacity = '0.2';
    683 
    684  // Setup animation
    685  const animation = div.animate(
    686    { opacity: 0.2 },
    687    { duration: 1 }
    688  );
    689  animation.finish();
    690 
    691  // Setup observer
    692  const mutationRecords = [];
    693  const observer = new MutationObserver(mutations => {
    694    mutationRecords.push(...mutations);
    695  });
    696  observer.observe(div, { attributes: true });
    697 
    698  animation.commitStyles();
    699 
    700  // Wait for mutation records to be dispatched
    701  await Promise.resolve();
    702 
    703  assert_equals(mutationRecords.length, 0, 'Should have no mutation records');
    704 
    705  observer.disconnect();
    706 }, 'Does NOT trigger mutation observers when the change to style is redundant');
    707 
    708 </script>
    709 </body>