tor-browser

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

at-property-animation.html (15487B)


      1 <!DOCTYPE html>
      2 <link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <script src="./resources/utils.js"></script>
      6 <div id=outer>
      7  <div id=div></div>
      8 </div>
      9 <script>
     10 
     11 test_with_at_property({
     12  syntax: '"<length>"',
     13  inherits: false,
     14  initialValue: '0px'
     15 }, (name) => {
     16  with_style_node(`
     17    @keyframes test {
     18      from { ${name}: 100px; }
     19      to { ${name}: 200px; }
     20    }
     21    #div { animation: test 100s -50s linear; }
     22  `, () => {
     23    assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
     24  });
     25 }, '@keyframes works with @property');
     26 
     27 test_with_at_property({
     28  syntax: '"<length>"',
     29  inherits: false,
     30  initialValue: '0px'
     31 }, (name) => {
     32  with_style_node(`
     33    @property ${name} {
     34      syntax: "<color>";
     35      inherits: false;
     36      initial-value: black;
     37    }
     38    @keyframes test {
     39      from { ${name}: rgb(100, 100, 100); }
     40      to { ${name}: rgb(200, 200, 200); }
     41    }
     42    #div { animation: test 100s -50s linear; }
     43  `, () => {
     44    assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(150, 150, 150)');
     45  });
     46 }, '@keyframes picks up the latest @property in the document');
     47 
     48 test_with_at_property({
     49  syntax: '"<length>"',
     50  inherits: false,
     51  initialValue: '0px'
     52 }, (name) => {
     53  // These keyframes are initially invalid for the declared custom property.
     54  let animation = div.animate([
     55    { [name]: 'rgb(100, 100, 100)'},
     56    { [name]: 'rgb(200, 200, 200)'},
     57  ], { duration: 10000, delay: -5000, easing: 'linear' });
     58  let cs = getComputedStyle(div);
     59  assert_equals(cs.getPropertyValue(name), '0px');
     60 
     61  // Redeclare the property as a <color>, effectively making the existing
     62  // keyframes valid.
     63  with_at_property({
     64    name: name,
     65    syntax: '"<color>"',
     66    inherits: false,
     67    initialValue: 'black'
     68  }, (name) => {
     69    assert_equals(cs.getPropertyValue(name), 'rgb(150, 150, 150)');
     70  });
     71 
     72  animation.finish();
     73 }, 'Ongoing animation picks up redeclared custom property');
     74 
     75 test_with_at_property({
     76  syntax: '"<length>"',
     77  inherits: false,
     78  initialValue: '0px'
     79 }, (name) => {
     80  // These keyframes are initially invalid for the declared custom property.
     81  let animation = div.animate([
     82    { [name]: 'rgb(100, 100, 100)'},
     83    { [name]: 'rgb(200, 200, 200)'},
     84  ], { duration: 10000, delay: -5000, easing: 'linear' });
     85  let cs = getComputedStyle(div);
     86  assert_equals(cs.getPropertyValue(name), '0px');
     87 
     88  // Setting the keyframes to something that matches <length> makes the
     89  // interpolation valid.
     90  animation.effect.setKeyframes([
     91    {[name]: '100px'},
     92    {[name]: '200px'}
     93  ]);
     94  assert_equals(cs.getPropertyValue(name), '150px');
     95 
     96  animation.finish();
     97 }, 'Ongoing animation matches new keyframes against the current registration');
     98 
     99 test_with_at_property({
    100  syntax: '"<length>"',
    101  inherits: false,
    102  initialValue: '0px'
    103 }, (name) => {
    104  let animation = div.animate([
    105    { [name]: 'initial'},
    106    { [name]: '400px'},
    107  ], { duration: 10000, delay: -5000, easing: 'linear' });
    108  let cs = getComputedStyle(div);
    109  assert_equals(cs.getPropertyValue(name), '200px');
    110 
    111  // Change initial value.
    112  with_at_property({
    113    name: name,
    114    syntax: '"<length>"',
    115    inherits: false,
    116    initialValue: '100px'
    117  }, (name) => {
    118    assert_equals(cs.getPropertyValue(name), '250px');
    119  });
    120 
    121  animation.finish();
    122 }, 'Ongoing animation picks up redeclared intial value');
    123 
    124 test_with_at_property({
    125  syntax: '"<length>"',
    126  inherits: false,
    127  initialValue: '0px'
    128 }, (name) => {
    129  try {
    130    document.body.style = `${name}: 100px`;
    131    // Note that 'inherit' here refers to #outer, which has the initial
    132    // value. (#outer did not inherit from body, since the property is not
    133    // yet declared as inherited).
    134    let animation = div.animate([
    135      { [name]: 'inherit'},
    136      { [name]: '400px'},
    137    ], { duration: 10000, delay: -5000, easing: 'linear' });
    138    let cs = getComputedStyle(div);
    139    assert_equals(cs.getPropertyValue(name), '200px');
    140 
    141    // Change inherits to 'true'. The value should now propagate from body
    142    // to #outer.
    143    with_at_property({
    144      name: name,
    145      syntax: '"<length>"',
    146      inherits: true,
    147      initialValue: '0px'
    148    }, (name) => {
    149      assert_equals(cs.getPropertyValue(name), '250px');
    150    });
    151 
    152    animation.finish();
    153  } finally {
    154    document.body.style = '';
    155  }
    156 }, 'Ongoing animation picks up redeclared inherits flag');
    157 
    158 test_with_at_property({
    159  syntax: '"<length>"',
    160  inherits: false,
    161  initialValue: '0px'
    162 }, (name) => {
    163  try {
    164    outer.style = `${name}: 100px`;
    165    // 'unset' should take the initial value (not the value from #outer), since
    166    // the property is not declared as inherited.
    167    let animation = div.animate([
    168      { [name]: 'unset'},
    169      { [name]: '400px'},
    170    ], { duration: 10000, delay: -5000, easing: 'linear' });
    171    let cs = getComputedStyle(div);
    172    assert_equals(cs.getPropertyValue(name), '200px');
    173 
    174    // Change inherits to 'true'. 'unset' now refers to #outer's value.
    175    with_at_property({
    176      name: name,
    177      syntax: '"<length>"',
    178      inherits: true,
    179      initialValue: '0px'
    180    }, (name) => {
    181      assert_equals(cs.getPropertyValue(name), '250px');
    182    });
    183 
    184    animation.finish();
    185  } finally {
    186    outer.style = '';
    187  }
    188 }, 'Ongoing animation picks up redeclared meaning of \'unset\'');
    189 
    190 test_with_at_property({
    191  syntax: '"<color>"',
    192  inherits: false,
    193  initialValue: 'red'
    194 }, (name) => {
    195  try {
    196    assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(255, 0, 0)');
    197    div.style = `transition: ${name} steps(2, start) 100s; ${name}: blue`;
    198    assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(128, 0, 128)');
    199  } finally {
    200    div.style = '';
    201  }
    202 }, 'Transitioning from initial value');
    203 
    204 test_with_at_property({
    205  syntax: '"<color>"',
    206  inherits: false,
    207  initialValue: 'red'
    208 }, (name) => {
    209  try {
    210    div.style = `${name}: blue;`;
    211    assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 0, 255)');
    212    div.style = `transition: ${name} steps(2, start) 100s; ${name}: green`;
    213    assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 64, 128)');
    214  } finally {
    215    div.style = '';
    216  }
    217 }, 'Transitioning from specified value');
    218 
    219 test_with_at_property({
    220  syntax: '"<length>"',
    221  inherits: false,
    222  initialValue: '100px'
    223 }, (name) => {
    224    with_style_node(`div { transition: ${name} steps(2, start) 100s; }`, () => {
    225      assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
    226      // Re-declaring the property with a different initial value effectively
    227      // means the computed value has changed. This means we should transition
    228      // from the old initial value to the new initial value.
    229      with_at_property({
    230        name: name,
    231        syntax: '"<length>"',
    232        inherits: false,
    233        initialValue: '200px'
    234      }, () => {
    235        assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
    236      });
    237    });
    238 }, 'Transition triggered by initial value change');
    239 
    240 test_with_at_property({
    241  syntax: '"<length>"',
    242  inherits: false,
    243  initialValue: '100px'
    244 }, (name) => {
    245    with_style_node(`div { transition: ${name} steps(2, start) 100s; }`, () => {
    246      assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
    247      with_at_property({
    248        name: name,
    249        syntax: '"<color>"',
    250        inherits: false,
    251        initialValue: 'green'
    252      }, () => {
    253        assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 128, 0)');
    254      });
    255    });
    256 }, 'No transition when changing types');
    257 
    258 test(() => {
    259  let name = generate_name();
    260  with_style_node(`div { ${name}: 100px; transition: ${name} steps(2, start) 100s; }`, () => {
    261    assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
    262 
    263    let style1 = document.createElement('style');
    264    style1.textContent = `
    265      @property ${name} {
    266        syntax: "<length>";
    267        inherits: false;
    268        initial-value: 200px;
    269      }
    270    `;
    271 
    272    let style2 = document.createElement('style');
    273    style2.textContent = `div { ${name}: 400px; }`;
    274 
    275    try {
    276      // Register the property:
    277      document.body.append(style1);
    278      // The token sequence ' 100px' is now interpreted as a length '100px'.
    279      assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
    280 
    281      // Change the computed value:
    282      document.body.append(style2);
    283      // This should cause an interpolation between 100px and 400px:
    284      assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
    285 
    286      // In the middle of the transition above, remove the @property rule
    287      // (making the computed value a token sequence again). We should snap
    288      // to the new token sequence.
    289      style1.remove();
    290      assert_equals(getComputedStyle(div).getPropertyValue(name), '400px');
    291    } finally {
    292      style1.remove();
    293      style2.remove();
    294    }
    295  });
    296 }, 'No transition when removing @property rule');
    297 
    298 test(() => {
    299  let name = generate_name();
    300  with_style_node(`div { transition: ${name} steps(2, start) 100s; }`, () => {
    301    let style1 = document.createElement('style');
    302    style1.textContent = `
    303      @property ${name} {
    304        syntax: "<angle>";
    305        inherits: false;
    306        initial-value: -90deg;
    307      }
    308    `;
    309 
    310    let style2 = document.createElement('style');
    311    style2.textContent = `div { ${name}: 90deg; }`;
    312 
    313    try {
    314      // Register the property:
    315      document.body.append(style1);
    316      // No transition in the beginning.
    317      assert_equals(getComputedStyle(div).getPropertyValue(name), '-90deg');
    318 
    319      // Change the computed value:
    320      document.body.append(style2);
    321      // This should cause an interpolation between -90deg and 90deg (for more
    322      // specifically, it is 50% from -90deg to 90deg, with steps(2, start)).
    323      assert_equals(getComputedStyle(div).getPropertyValue(name), '0deg');
    324 
    325      // In the middle of the transition above, remove style2, which creates
    326      // a transition which reverses the existing one, from 0deg to -90deg.
    327      // Also, it is 50% from 0deg to -90deg, with steps(2, start).
    328      style2.remove();
    329      assert_equals(getComputedStyle(div).getPropertyValue(name), '-45deg');
    330    } finally {
    331      style1.remove();
    332      style2.remove();
    333    }
    334  });
    335 }, 'Ongoing transition reverts to its initial state');
    336 
    337 test_with_at_property({
    338  syntax: '"<length>"',
    339  inherits: false,
    340  initialValue: '0px'
    341 }, (name) => {
    342  with_style_node(`
    343    @keyframes test {
    344      from { ${name}: 100px; }
    345      to { ${name}: 200px; }
    346    }
    347    #div {
    348        animation: test 100s -50s linear;
    349        --unregistered: var(${name});
    350    }
    351  `, () => {
    352    assert_equals(getComputedStyle(div).getPropertyValue('--unregistered'), '150px');
    353  });
    354 }, 'Unregistered properties referencing animated properties update correctly.');
    355 
    356 test_with_at_property({
    357  syntax: '"<length>"',
    358  inherits: false,
    359  initialValue: '0px'
    360 }, (name) => {
    361  with_style_node(`
    362    @keyframes test {
    363      from { ${name}: 100px; }
    364      to { ${name}: 200px; }
    365    }
    366    @property --registered {
    367        syntax: "<length>";
    368        inherits: false;
    369        initialValue: 0px;
    370    }
    371    #div {
    372        animation: test 100s -50s linear;
    373        --registered: var(${name});
    374    }
    375  `, () => {
    376    assert_equals(getComputedStyle(div).getPropertyValue('--registered'), '150px');
    377  });
    378 }, 'Registered properties referencing animated properties update correctly.');
    379 
    380 test_with_at_property({
    381  syntax: '"<length>"',
    382  inherits: false,
    383  initialValue: '0px'
    384 }, (name) => {
    385  with_style_node(`
    386    @keyframes test {
    387      from { ${name}: inherit; }
    388      to { ${name}: 300px; }
    389    }
    390    #outer {
    391      ${name}: 100px;
    392    }
    393    #div {
    394      animation: test 100s -50s linear paused;
    395    }
    396  `, () => {
    397    assert_equals(getComputedStyle(div).getPropertyValue(name), '200px');
    398 
    399    outer.style.setProperty(name, '200px');
    400    assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
    401 
    402    outer.style.setProperty(name, '0px');
    403    assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
    404 
    405    outer.style.removeProperty(name);
    406  });
    407 }, 'CSS animation setting "inherit" for a custom property on a keyframe is responsive to changing that custom property on the parent.');
    408 
    409 test_with_at_property({
    410  syntax: '"<length>"',
    411  inherits: false,
    412  initialValue: '0px'
    413 }, (name) => {
    414  with_style_node(`
    415    #outer {
    416      ${name}: 100px;
    417    }
    418  `, () => {
    419    const animation = div.animate({ [name]: ["inherit", "300px"]}, 1000);
    420    animation.currentTime = 500;
    421    animation.pause();
    422 
    423    assert_equals(getComputedStyle(div).getPropertyValue(name), '200px');
    424 
    425    outer.style.setProperty(name, '200px');
    426    assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
    427 
    428    outer.style.setProperty(name, '0px');
    429    assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
    430 
    431    outer.style.removeProperty(name);
    432  });
    433 }, 'JS-originated animation setting "inherit" for a custom property on a keyframe is responsive to changing that custom property on the parent.');
    434 
    435 test_with_at_property({
    436  syntax: '"<color>"',
    437  inherits: false,
    438  initialValue: 'black'
    439 }, (name) => {
    440  with_style_node(`
    441    @keyframes test {
    442      from { ${name}: currentcolor; }
    443      to { ${name}: rgb(200, 200, 200); }
    444    }
    445    #outer {
    446      color: rgb(100, 100, 100);
    447    }
    448    #div {
    449      animation: test 100s -50s linear paused;
    450    }
    451  `, (style) => {
    452    // See https://github.com/w3c/csswg-drafts/issues/10371 for the two
    453    // possibilities.
    454    assert_in_array(getComputedStyle(div).getPropertyValue(name), [
    455      'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
    456      'rgb(150, 150, 150)',
    457    ]);
    458 
    459    outer.style.color = 'rgb(50, 50, 50)';
    460    assert_in_array(getComputedStyle(div).getPropertyValue(name), [
    461      'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
    462      'rgb(125, 125, 125)',
    463    ]);
    464 
    465    outer.style.color = 'rgb(150, 150, 150)';
    466    assert_in_array(getComputedStyle(div).getPropertyValue(name), [
    467      'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
    468      'rgb(175, 175, 175)',
    469    ]);
    470 
    471    outer.style.removeProperty("color");
    472  });
    473 }, 'CSS animation setting "currentColor" for a custom property on a keyframe is responsive to changing "color" on the parent.');
    474 
    475 test_with_at_property({
    476  syntax: '"<color>"',
    477  inherits: false,
    478  initialValue: 'black'
    479 }, (name) => {
    480  with_style_node(`
    481    #outer {
    482      color: rgb(100, 100, 100);
    483    }
    484  `, () => {
    485    const animation = div.animate({ [name]: ["currentcolor", "rgb(200, 200, 200)"]}, 1000);
    486    animation.currentTime = 500;
    487    animation.pause();
    488 
    489    assert_in_array(getComputedStyle(div).getPropertyValue(name), [
    490      'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
    491      'rgb(150, 150, 150)',
    492    ]);
    493 
    494    outer.style.color = 'rgb(50, 50, 50)';
    495    assert_in_array(getComputedStyle(div).getPropertyValue(name), [
    496      'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
    497      'rgb(125, 125, 125)',
    498    ]);
    499 
    500    outer.style.color = 'rgb(150, 150, 150)';
    501    assert_in_array(getComputedStyle(div).getPropertyValue(name), [
    502      'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
    503      'rgb(175, 175, 175)',
    504    ]);
    505 
    506    outer.style.removeProperty("color");
    507  });
    508 }, 'JS-originated animation setting "currentColor" for a custom property on a keyframe is responsive to changing "color" on the parent.');
    509 
    510 </script>