tor-browser

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

processing-a-keyframes-argument-001.html (17191B)


      1 <!DOCTYPE html>
      2 <meta charset=utf-8>
      3 <title>Processing a keyframes argument (property access)</title>
      4 <link rel="help" href="https://drafts.csswg.org/web-animations/#processing-a-keyframes-argument">
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="../../testcommon.js"></script>
      8 <script src="../../resources/keyframe-utils.js"></script>
      9 <body>
     10 <div id="log"></div>
     11 <div id="target"></div>
     12 <script>
     13 'use strict';
     14 
     15 // This file only tests the KeyframeEffect constructor since it is
     16 // assumed that the implementation of the KeyframeEffect constructor,
     17 // Animatable.animate() method, and KeyframeEffect.setKeyframes() method will
     18 // all share common machinery and it is not necessary to test each method.
     19 
     20 // Test that only animatable properties are accessed
     21 
     22 const gNonAnimatableProps = [
     23  'animation', // Shorthands where all the longhand sub-properties are not
     24               // animatable, are also not animatable.
     25  'animationDelay',
     26  'animationDirection',
     27  'animationDuration',
     28  'animationFillMode',
     29  'animationIterationCount',
     30  'animationName',
     31  'animationPlayState',
     32  'animationTimingFunction',
     33  'transition',
     34  'transitionDelay',
     35  'transitionDuration',
     36  'transitionProperty',
     37  'transitionTimingFunction',
     38  'contain',
     39  'direction',
     40  'textCombineUpright',
     41  'textOrientation',
     42  'unicodeBidi',
     43  'willChange',
     44  'writingMode',
     45 
     46  'unsupportedProperty',
     47 
     48  'float', // We use the string "cssFloat" to represent "float" property, and
     49           // so reject "float" in the keyframe-like object.
     50  'font-size', // Supported property that uses dashes
     51 ];
     52 
     53 function TestKeyframe(testProp) {
     54  let _propAccessCount = 0;
     55 
     56  Object.defineProperty(this, testProp, {
     57    get: () => { _propAccessCount++; },
     58    enumerable: true,
     59  });
     60 
     61  Object.defineProperty(this, 'propAccessCount', {
     62    get: () => _propAccessCount
     63  });
     64 }
     65 
     66 function GetTestKeyframeSequence(testProp) {
     67  return [ new TestKeyframe(testProp) ]
     68 }
     69 
     70 for (const prop of gNonAnimatableProps) {
     71  test(() => {
     72    const testKeyframe = new TestKeyframe(prop);
     73 
     74    new KeyframeEffect(null, testKeyframe);
     75 
     76    assert_equals(testKeyframe.propAccessCount, 0, 'Accessor not called');
     77  }, `non-animatable property '${prop}' is not accessed when using`
     78     + ' a property-indexed keyframe object');
     79 }
     80 
     81 for (const prop of gNonAnimatableProps) {
     82  test(() => {
     83    const testKeyframes = GetTestKeyframeSequence(prop);
     84 
     85    new KeyframeEffect(null, testKeyframes);
     86 
     87    assert_equals(testKeyframes[0].propAccessCount, 0, 'Accessor not called');
     88  }, `non-animatable property '${prop}' is not accessed when using`
     89     + ' a keyframe sequence');
     90 }
     91 
     92 // Test equivalent forms of property-indexed and sequenced keyframe syntax
     93 
     94 function assertEquivalentKeyframeSyntax(keyframesA, keyframesB) {
     95  const processedKeyframesA =
     96    new KeyframeEffect(null, keyframesA).getKeyframes();
     97  const processedKeyframesB =
     98    new KeyframeEffect(null, keyframesB).getKeyframes();
     99  assert_frame_lists_equal(processedKeyframesA, processedKeyframesB);
    100 }
    101 
    102 const gEquivalentSyntaxTests = [
    103  {
    104    description: 'two properties with one value',
    105    indexedKeyframes: {
    106      left: '100px',
    107      opacity: ['1'],
    108    },
    109    sequencedKeyframes: [
    110      { left: '100px', opacity: '1' },
    111    ],
    112  },
    113  {
    114    description: 'two properties with three values',
    115    indexedKeyframes: {
    116      left: ['10px', '100px', '150px'],
    117      opacity: ['1', '0', '1'],
    118    },
    119    sequencedKeyframes: [
    120      { left: '10px', opacity: '1' },
    121      { left: '100px', opacity: '0' },
    122      { left: '150px', opacity: '1' },
    123    ],
    124  },
    125  {
    126    description: 'two properties with different numbers of values',
    127    indexedKeyframes: {
    128      left: ['0px', '100px', '200px'],
    129      opacity: ['0', '1']
    130    },
    131    sequencedKeyframes: [
    132      { left: '0px', opacity: '0' },
    133      { left: '100px' },
    134      { left: '200px', opacity: '1' },
    135    ],
    136  },
    137  {
    138    description: 'same easing applied to all keyframes',
    139    indexedKeyframes: {
    140      left: ['10px', '100px', '150px'],
    141      opacity: ['1', '0', '1'],
    142      easing: 'ease',
    143    },
    144    sequencedKeyframes: [
    145      { left: '10px', opacity: '1', easing: 'ease' },
    146      { left: '100px', opacity: '0', easing: 'ease' },
    147      { left: '150px', opacity: '1', easing: 'ease' },
    148    ],
    149  },
    150  {
    151    description: 'same composite applied to all keyframes',
    152    indexedKeyframes: {
    153      left: ['0px', '100px'],
    154      composite: 'add',
    155    },
    156    sequencedKeyframes: [
    157      { left: '0px', composite: 'add' },
    158      { left: '100px', composite: 'add' },
    159    ],
    160  },
    161 ];
    162 
    163 for (const {description, indexedKeyframes, sequencedKeyframes} of
    164     gEquivalentSyntaxTests) {
    165  test(() => {
    166    assertEquivalentKeyframeSyntax(indexedKeyframes, sequencedKeyframes);
    167  }, `Equivalent property-indexed and sequenced keyframes: ${description}`);
    168 }
    169 
    170 // Test handling of custom iterable objects.
    171 
    172 function createIterable(iterations) {
    173  return {
    174    [Symbol.iterator]() {
    175      let i = 0;
    176      return {
    177        next() {
    178          return iterations[i++];
    179        },
    180      };
    181    },
    182  };
    183 }
    184 
    185 test(() => {
    186  const effect = new KeyframeEffect(null, createIterable([
    187    { done: false, value: { left: '100px' } },
    188    { done: false, value: { left: '300px' } },
    189    { done: false, value: { left: '200px' } },
    190    { done: true },
    191  ]));
    192  assert_frame_lists_equal(effect.getKeyframes(), [
    193    {
    194      offset: null,
    195      computedOffset: 0,
    196      easing: 'linear',
    197      left: '100px',
    198      composite: 'auto',
    199    },
    200    {
    201      offset: null,
    202      computedOffset: 0.5,
    203      easing: 'linear',
    204      left: '300px',
    205      composite: 'auto',
    206    },
    207    {
    208      offset: null,
    209      computedOffset: 1,
    210      easing: 'linear',
    211      left: '200px',
    212      composite: 'auto',
    213    },
    214  ]);
    215 }, 'Keyframes are read from a custom iterator');
    216 
    217 test(() => {
    218  const keyframes = createIterable([
    219    { done: false, value: { left: '100px' } },
    220    { done: false, value: { left: '300px' } },
    221    { done: false, value: { left: '200px' } },
    222    { done: true },
    223  ]);
    224  keyframes.easing = 'ease-in-out';
    225  keyframes.offset = '0.1';
    226  const effect = new KeyframeEffect(null, keyframes);
    227  assert_frame_lists_equal(effect.getKeyframes(), [
    228    {
    229      offset: null,
    230      computedOffset: 0,
    231      easing: 'linear',
    232      left: '100px',
    233      composite: 'auto',
    234    },
    235    {
    236      offset: null,
    237      computedOffset: 0.5,
    238      easing: 'linear',
    239      left: '300px',
    240      composite: 'auto',
    241    },
    242    {
    243      offset: null,
    244      computedOffset: 1,
    245      easing: 'linear',
    246      left: '200px',
    247      composite: 'auto',
    248    },
    249  ]);
    250 }, '\'easing\' and \'offset\' are ignored on iterable objects');
    251 
    252 test(() => {
    253  const effect = new KeyframeEffect(null, createIterable([
    254    { done: false, value: { left: '100px', top: '200px' } },
    255    { done: false, value: { left: '300px' } },
    256    { done: false, value: { left: '200px', top: '100px' } },
    257    { done: true },
    258  ]));
    259  assert_frame_lists_equal(effect.getKeyframes(), [
    260    {
    261      offset: null,
    262      computedOffset: 0,
    263      easing: 'linear',
    264      left: '100px',
    265      top: '200px',
    266      composite: 'auto',
    267    },
    268    {
    269      offset: null,
    270      computedOffset: 0.5,
    271      easing: 'linear',
    272      left: '300px',
    273      composite: 'auto',
    274    },
    275    {
    276      offset: null,
    277      computedOffset: 1,
    278      easing: 'linear',
    279      left: '200px',
    280      top: '100px',
    281      composite: 'auto',
    282    },
    283  ]);
    284 }, 'Keyframes are read from a custom iterator with multiple properties'
    285   + ' specified');
    286 
    287 test(() => {
    288  const effect = new KeyframeEffect(null, createIterable([
    289    { done: false, value: { left: '100px' } },
    290    { done: false, value: { left: '250px', offset: 0.75 } },
    291    { done: false, value: { left: '200px' } },
    292    { done: true },
    293  ]));
    294  assert_frame_lists_equal(effect.getKeyframes(), [
    295    {
    296      offset: null,
    297      computedOffset: 0,
    298      easing: 'linear',
    299      left: '100px',
    300      composite: 'auto',
    301    },
    302    {
    303      offset: 0.75,
    304      computedOffset: 0.75,
    305      easing: 'linear',
    306      left: '250px',
    307      composite: 'auto',
    308    },
    309    {
    310      offset: null,
    311      computedOffset: 1,
    312      easing: 'linear',
    313      left: '200px',
    314      composite: 'auto',
    315    },
    316  ]);
    317 }, 'Keyframes are read from a custom iterator with where an offset is'
    318   + ' specified');
    319 
    320 test(() => {
    321  const test_error = { name: 'test' };
    322  const bad_keyframe = { get left() { throw test_error; } };
    323  assert_throws_exactly(test_error, () => {
    324    new KeyframeEffect(null, createIterable([
    325      { done: false, value: { left: '100px' } },
    326      { done: false, value: bad_keyframe },
    327      { done: false, value: { left: '200px' } },
    328      { done: true },
    329    ]));
    330  });
    331 }, 'If a keyframe throws for an animatable property, that exception should be'
    332    + ' propagated');
    333 
    334 test(() => {
    335  assert_throws_js(TypeError, () => {
    336    new KeyframeEffect(null, createIterable([
    337      { done: false, value: { left: '100px' } },
    338      { done: false, value: 1234 },
    339      { done: false, value: { left: '200px' } },
    340      { done: true },
    341    ]));
    342  });
    343 }, 'Reading from a custom iterator that returns a non-object keyframe'
    344   + ' should throw');
    345 
    346 test(() => {
    347  assert_throws_js(TypeError, () => {
    348    new KeyframeEffect(null, createIterable([
    349      { done: false, value: { left: '100px', easing: '' } },
    350      { done: false, value: 1234 },
    351      { done: false, value: { left: '200px' } },
    352      { done: true },
    353    ]));
    354  });
    355 }, 'Reading from a custom iterator that returns a non-object keyframe'
    356   + ' and an invalid easing should throw');
    357 
    358 test(() => {
    359  assert_throws_js(TypeError, () => {
    360    new KeyframeEffect(null, createIterable([
    361      { done: false, value: { left: '100px' } },
    362      { done: false, value: { left: '150px', offset: 'o' } },
    363      { done: false, value: { left: '200px' } },
    364      { done: true },
    365    ]));
    366  });
    367 }, 'Reading from a custom iterator that returns a keyframe with a non finite'
    368   + ' floating-point offset value should throw');
    369 
    370 test(() => {
    371  assert_throws_js(TypeError, () => {
    372    new KeyframeEffect(null, createIterable([
    373      { done: false, value: { left: '100px', easing: '' } },
    374      { done: false, value: { left: '150px', offset: 'o' } },
    375      { done: false, value: { left: '200px' } },
    376      { done: true },
    377    ]));
    378  });
    379 }, 'Reading from a custom iterator that returns a keyframe with a non finite'
    380   + ' floating-point offset value and an invalid easing should throw');
    381 
    382 test(() => {
    383  const effect = new KeyframeEffect(null, createIterable([
    384    { done: false, value: { left: '100px' } },
    385    { done: false },  // No value member; keyframe is undefined.
    386    { done: false, value: { left: '200px' } },
    387    { done: true },
    388  ]));
    389  assert_frame_lists_equal(effect.getKeyframes(), [
    390    { left: '100px', offset: null, computedOffset: 0, easing: 'linear', composite: 'auto' },
    391    { offset: null, computedOffset: 0.5, easing: 'linear', composite: 'auto' },
    392    { left: '200px', offset: null, computedOffset: 1, easing: 'linear', composite: 'auto' },
    393  ]);
    394 }, 'An undefined keyframe returned from a custom iterator should be treated as a'
    395    + ' default keyframe');
    396 
    397 test(() => {
    398  const effect = new KeyframeEffect(null, createIterable([
    399    { done: false, value: { left: '100px' } },
    400    { done: false, value: null },
    401    { done: false, value: { left: '200px' } },
    402    { done: true },
    403  ]));
    404  assert_frame_lists_equal(effect.getKeyframes(), [
    405    { left: '100px', offset: null, computedOffset: 0, easing: 'linear', composite: 'auto' },
    406    { offset: null, computedOffset: 0.5, easing: 'linear', composite: 'auto' },
    407    { left: '200px', offset: null, computedOffset: 1, easing: 'linear', composite: 'auto' },
    408  ]);
    409 }, 'A null keyframe returned from a custom iterator should be treated as a'
    410    + ' default keyframe');
    411 
    412 test(() => {
    413  const effect = new KeyframeEffect(null, createIterable([
    414    { done: false, value: { left: ['100px', '200px'] } },
    415    { done: true },
    416  ]));
    417  assert_frame_lists_equal(effect.getKeyframes(), [
    418    { offset: null, computedOffset: 1, easing: 'linear', composite: 'auto' }
    419  ]);
    420 }, 'A list of values returned from a custom iterator should be ignored');
    421 
    422 test(() => {
    423  const test_error = { name: 'test' };
    424  const keyframe_obj = {
    425    [Symbol.iterator]() {
    426      return { next() { throw test_error; } };
    427    },
    428  };
    429  assert_throws_exactly(test_error, () => {
    430    new KeyframeEffect(null, keyframe_obj);
    431  });
    432 }, 'If a custom iterator throws from next(), the exception should be rethrown');
    433 
    434 // Test handling of invalid Symbol.iterator
    435 
    436 test(() => {
    437  const test_error = { name: 'test' };
    438  const keyframe_obj = {
    439    [Symbol.iterator]() {
    440      throw test_error;
    441    },
    442  };
    443  assert_throws_exactly(test_error, () => {
    444    new KeyframeEffect(null, keyframe_obj);
    445  });
    446 }, 'Accessing a Symbol.iterator property that throws should rethrow');
    447 
    448 test(() => {
    449  const keyframe_obj = {
    450    [Symbol.iterator]() {
    451      return 42;  // Not an object.
    452    },
    453  };
    454  assert_throws_js(TypeError, () => {
    455    new KeyframeEffect(null, keyframe_obj);
    456  });
    457 }, 'A non-object returned from the Symbol.iterator property should cause a'
    458    + ' TypeError to be thrown');
    459 
    460 test(() => {
    461  const keyframe = {};
    462  Object.defineProperty(keyframe, 'width', { value: '200px' });
    463  Object.defineProperty(keyframe, 'height', {
    464    value: '100px',
    465    enumerable: true,
    466  });
    467  assert_equals(keyframe.width, '200px', 'width of keyframe is readable');
    468  assert_equals(keyframe.height, '100px', 'height of keyframe is readable');
    469 
    470  const effect = new KeyframeEffect(null, [keyframe, { height: '200px' }]);
    471 
    472  assert_frame_lists_equal(effect.getKeyframes(), [
    473    {
    474      offset: null,
    475      computedOffset: 0,
    476      easing: 'linear',
    477      height: '100px',
    478      composite: 'auto',
    479    },
    480    {
    481      offset: null,
    482      computedOffset: 1,
    483      easing: 'linear',
    484      height: '200px',
    485      composite: 'auto',
    486    },
    487  ]);
    488 }, 'Only enumerable properties on keyframes are read');
    489 
    490 test(() => {
    491  const KeyframeParent = function() { this.width = '100px'; };
    492  KeyframeParent.prototype = { height: '100px' };
    493  const Keyframe = function() { this.top = '100px'; };
    494  Keyframe.prototype = Object.create(KeyframeParent.prototype);
    495  Object.defineProperty(Keyframe.prototype, 'left', {
    496    value: '100px',
    497    enumerable: true,
    498  });
    499  const keyframe = new Keyframe();
    500 
    501  const effect = new KeyframeEffect(null, [keyframe, { top: '200px' }]);
    502 
    503  assert_frame_lists_equal(effect.getKeyframes(), [
    504    {
    505      offset: null,
    506      computedOffset: 0,
    507      easing: 'linear',
    508      top: '100px',
    509      composite: 'auto',
    510    },
    511    {
    512      offset: null,
    513      computedOffset: 1,
    514      easing: 'linear',
    515      top: '200px',
    516      composite: 'auto',
    517    },
    518  ]);
    519 }, 'Only properties defined directly on keyframes are read');
    520 
    521 test(() => {
    522  const keyframes = {};
    523  Object.defineProperty(keyframes, 'width', ['100px', '200px']);
    524  Object.defineProperty(keyframes, 'height', {
    525    value: ['100px', '200px'],
    526    enumerable: true,
    527  });
    528 
    529  const effect = new KeyframeEffect(null, keyframes);
    530 
    531  assert_frame_lists_equal(effect.getKeyframes(), [
    532    {
    533      offset: null,
    534      computedOffset: 0,
    535      easing: 'linear',
    536      height: '100px',
    537      composite: 'auto',
    538    },
    539    {
    540      offset: null,
    541      computedOffset: 1,
    542      easing: 'linear',
    543      height: '200px',
    544      composite: 'auto',
    545    },
    546  ]);
    547 }, 'Only enumerable properties on property-indexed keyframes are read');
    548 
    549 test(() => {
    550  const KeyframesParent = function() { this.width = '100px'; };
    551  KeyframesParent.prototype = { height: '100px' };
    552  const Keyframes = function() { this.top = ['100px', '200px']; };
    553  Keyframes.prototype = Object.create(KeyframesParent.prototype);
    554  Object.defineProperty(Keyframes.prototype, 'left', {
    555    value: ['100px', '200px'],
    556    enumerable: true,
    557  });
    558  const keyframes = new Keyframes();
    559 
    560  const effect = new KeyframeEffect(null, keyframes);
    561 
    562  assert_frame_lists_equal(effect.getKeyframes(), [
    563    {
    564      offset: null,
    565      computedOffset: 0,
    566      easing: 'linear',
    567      top: '100px',
    568      composite: 'auto',
    569    },
    570    {
    571      offset: null,
    572      computedOffset: 1,
    573      easing: 'linear',
    574      top: '200px',
    575      composite: 'auto',
    576    },
    577  ]);
    578 }, 'Only properties defined directly on property-indexed keyframes are read');
    579 
    580 test(() => {
    581  const expectedOrder = ['composite', 'easing', 'offset', 'left', 'marginLeft'];
    582  const actualOrder = [];
    583  const kf1 = {};
    584  for (const {prop, value} of [{ prop: 'marginLeft', value: '10px' },
    585                               { prop: 'left',       value: '20px' },
    586                               { prop: 'offset',     value: '0' },
    587                               { prop: 'easing',     value: 'linear' },
    588                               { prop: 'composite',  value: 'replace' }]) {
    589    Object.defineProperty(kf1, prop, {
    590      enumerable: true,
    591      get: () => { actualOrder.push(prop); return value; }
    592    });
    593  }
    594  const kf2 = { marginLeft: '10px', left: '20px', offset: 1 };
    595 
    596  new KeyframeEffect(target, [kf1, kf2]);
    597 
    598  assert_array_equals(actualOrder, expectedOrder, 'property access order');
    599 }, 'Properties are read in ascending order by Unicode codepoint');
    600 
    601 </script>