tor-browser

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

transform-matrix-composition.html (7899B)


      1 <!DOCTYPE html>
      2 <meta charset="UTF-8">
      3 <title>transform-matrix composition</title>
      4 <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
      5 <meta name="assert" content="transform-matrix supports animation as a transform list">
      6 
      7 <script src="/resources/testharness.js"></script>
      8 <script src="/resources/testharnessreport.js"></script>
      9 <script src="/css/support/interpolation-testcommon.js"></script>
     10 
     11 <body>
     12 <script>
     13 // For matrix and matrix3d, addition is defined as concatenation whilst
     14 // accumulation works by decomposing the matrix and then accumulating the
     15 // decomposed functions. We can therefore test the difference between the
     16 // two by mixing functions such that a naive multiplication would look
     17 // different than the accumulation behavior.
     18 //
     19 // Note that due to the complexities of decomposition the test space here is
     20 // huge; we cover some basic cases and hope that the tests for the individual
     21 // functions provide a lot of the remaining coverage.
     22 
     23 // Creates a matrix3d function, encoding the passed rotation and translation.
     24 // Note that the translate will not be affected by the rotation.
     25 function create3dMatrix(x, y, z, radians, translateX) {
     26  // Normalize the rotation axes.
     27  const length = Math.sqrt(x*x + y*y + z*z);
     28  x /= length;
     29  y /= length;
     30  z /= length;
     31 
     32  const sc = Math.sin(radians / 2) * Math.cos(radians / 2);
     33  const sq = Math.sin(radians / 2) * Math.sin(radians / 2);
     34 
     35  // https://drafts.csswg.org/css-transforms-2/#Rotate3dDefined
     36  // https://drafts.csswg.org/css-transforms-2/#Translate3dDefined
     37  return 'matrix3d(' + [
     38      1 - 2 * (y*y + z*z) * sq,
     39      2 * (x * y * sq + z * sc),
     40      2 * (x * z * sq - y * sc),
     41      0,
     42      2 * (x * y * sq - z * sc),
     43      1 - 2 * (x*x + z*z) * sq,
     44      2 * (y * z * sq + x * sc),
     45      0,
     46      2 * (x * z * sq + y * sc),
     47      2 * (y * z * sq - x * sc),
     48      1 - 2 * (x*x + y*y) * sq,
     49      0,
     50      translateX, 0, 0, 1].join() + ')';
     51 }
     52 
     53 // ------------ Addition tests --------------
     54 
     55 test_composition({
     56  property: 'transform',
     57  // translateX(100px) rotate(90deg)
     58  underlying: 'matrix(0, 1, -1, 0, 100, 0)',
     59  // translateX(100px)
     60  addFrom: 'matrix(1, 0, 0, 1, 100, 0)',
     61  // translateX(200px)
     62  addTo: 'matrix(1, 0, 0, 1, 200, 0)',
     63 }, [
     64  {at: -0.5, expect: 'matrix(0, 1, -1, 0, 100, 50)'},
     65  {at: 0, expect: 'matrix(0, 1, -1, 0, 100, 100)'},
     66  {at: 0.25, expect: 'matrix(0, 1, -1, 0, 100, 125)'},
     67  {at: 0.5, expect: 'matrix(0, 1, -1, 0, 100, 150)'},
     68  {at: 0.75, expect: 'matrix(0, 1, -1, 0, 100, 175)'},
     69  {at: 1, expect: 'matrix(0, 1, -1, 0, 100, 200)'},
     70  {at: 1.5, expect: 'matrix(0, 1, -1, 0, 100, 250)'},
     71 ]);
     72 
     73 test_composition({
     74  property: 'transform',
     75  // translateX(100px) rotate3d(1, 1, 0, 45deg)
     76  underlying: create3dMatrix(1, 1, 0, Math.PI / 4, 100),
     77  // translateX(100px)
     78  addFrom: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 0, 0, 1)',
     79  // translateX(200px)
     80  addTo: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 0, 0, 1)',
     81 }, [
     82  // matrix3ds are hard to read; these are the decomposed forms for clarity
     83  {at: -0.5, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(50px)'},
     84  {at: 0, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(100px)'},
     85  {at: 0.25, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(125px)'},
     86  {at: 0.5, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(150px)'},
     87  {at: 0.75, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(175px)'},
     88  {at: 1, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(200px)'},
     89  {at: 1.5, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(250px)'},
     90 ]);
     91 
     92 // Addition of non-invertible matrices is still defined as concatenation so
     93 // includes the underlying value.
     94 
     95 test_composition({
     96  property: 'transform',
     97  // Non-invertible.
     98  underlying: 'matrix(1, 1, 0, 0, 0, 100)',
     99  // translateX(100px)
    100  addFrom: 'matrix(1, 0, 0, 1, 100, 0)',
    101  // translateX(200px)
    102  addTo: 'matrix(1, 0, 0, 1, 200, 0)',
    103 }, [
    104  {at: -0.5, expect: 'matrix(1, 1, 0, 0, 100, 200)'},
    105  {at: 0, expect: 'matrix(1, 1, 0, 0, 100, 200)'},
    106  {at: 0.25, expect: 'matrix(1, 1, 0, 0, 100, 200)'},
    107  {at: 0.5, expect: 'matrix(1, 1, 0, 0, 200, 300)'},
    108  {at: 0.75, expect: 'matrix(1, 1, 0, 0, 200, 300)'},
    109  {at: 1, expect: 'matrix(1, 1, 0, 0, 200, 300)'},
    110  {at: 1.5, expect: 'matrix(1, 1, 0, 0, 200, 300)'},
    111 ]);
    112 
    113 test_composition({
    114  property: 'transform',
    115  // translateX(100px)
    116  underlying: 'matrix(1, 0, 0, 1, 100, 0)',
    117  // Non-invertible
    118  addFrom: 'matrix(1, 1, 0, 0, 0, 100)',
    119  // translateX(200px)
    120  addTo: 'matrix(1, 0, 0, 1, 200, 0)',
    121 }, [
    122  {at: -0.5, expect: 'matrix(1, 1, 0, 0, 100, 100)'},
    123  {at: 0, expect: 'matrix(1, 1, 0, 0, 100, 100)'},
    124  {at: 0.25, expect: 'matrix(1, 1, 0, 0, 100, 100)'},
    125  {at: 0.5, expect: 'matrix(1, 0, 0, 1, 300, 0)'},
    126  {at: 0.75, expect: 'matrix(1, 0, 0, 1, 300, 0)'},
    127  {at: 1, expect: 'matrix(1, 0, 0, 1, 300, 0)'},
    128  {at: 1.5, expect: 'matrix(1, 0, 0, 1, 300, 0)'},
    129 ]);
    130 
    131 // ------------ Accumulation tests --------------
    132 
    133 test_composition({
    134  property: 'transform',
    135  // translateX(100px) rotate(90deg)
    136  underlying: 'matrix(0, 1, -1, 0, 100, 0)',
    137  // translateX(100px)
    138  accumulateFrom: 'matrix(1, 0, 0, 1, 100, 0)',
    139  // translateX(200px)
    140  accumulateTo: 'matrix(1, 0, 0, 1, 200, 0)',
    141 }, [
    142  {at: -0.5, expect: 'matrix(0, 1, -1, 0, 150, 0)'},
    143  {at: 0, expect: 'matrix(0, 1, -1, 0, 200, 0)'},
    144  {at: 0.25, expect: 'matrix(0, 1, -1, 0, 225, 0)'},
    145  {at: 0.5, expect: 'matrix(0, 1, -1, 0, 250, 0)'},
    146  {at: 0.75, expect: 'matrix(0, 1, -1, 0, 275, 0)'},
    147  {at: 1, expect: 'matrix(0, 1, -1, 0, 300, 0)'},
    148  {at: 1.5, expect: 'matrix(0, 1, -1, 0, 350, 0)'},
    149 ]);
    150 
    151 test_composition({
    152  property: 'transform',
    153  // translateX(100px) rotate3d(1, 1, 0, 45deg)
    154  underlying: create3dMatrix(1, 1, 0, Math.PI / 4, 100),
    155  // translateX(100px)
    156  accumulateFrom: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 0, 0, 1)',
    157  // translateX(200px)
    158  accumulateTo: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 0, 0, 1)',
    159 }, [
    160  // matrix3ds are hard to read; these are the decomposed forms for clarity
    161  {at: -0.5, expect: 'translateX(150px) rotate3d(1, 1, 0, 45deg)'},
    162  {at: 0, expect: 'translateX(200px) rotate3d(1, 1, 0, 45deg)'},
    163  {at: 0.25, expect: 'translateX(225px) rotate3d(1, 1, 0, 45deg)'},
    164  {at: 0.5, expect: 'translateX(250px) rotate3d(1, 1, 0, 45deg)'},
    165  {at: 0.75, expect: 'translateX(275px) rotate3d(1, 1, 0, 45deg)'},
    166  {at: 1, expect: 'translateX(300px) rotate3d(1, 1, 0, 45deg)'},
    167  {at: 1.5, expect: 'translateX(350px) rotate3d(1, 1, 0, 45deg)'},
    168 ]);
    169 
    170 // Accumulation of non-invertible matrices falls back to replace behavior.
    171 
    172 test_composition({
    173  property: 'transform',
    174  // Non-invertible.
    175  underlying: 'matrix(1, 1, 0, 0, 0, 100)',
    176  // translateX(100px)
    177  accumulateFrom: 'matrix(1, 0, 0, 1, 100, 0)',
    178  // translateX(200px)
    179  accumulateTo: 'matrix(1, 0, 0, 1, 200, 0)',
    180 }, [
    181  {at: -0.5, expect: 'matrix(1, 0, 0, 1, 50, 0)'},
    182  {at: 0, expect: 'matrix(1, 0, 0, 1, 100, 0)'},
    183  {at: 0.25, expect: 'matrix(1, 0, 0, 1, 125, 0)'},
    184  {at: 0.5, expect: 'matrix(1, 0, 0, 1, 150, 0)'},
    185  {at: 0.75, expect: 'matrix(1, 0, 0, 1, 175, 0)'},
    186  {at: 1, expect: 'matrix(1, 0, 0, 1, 200, 0)'},
    187  {at: 1.5, expect: 'matrix(1, 0, 0, 1, 250, 0)'},
    188 ]);
    189 
    190 test_composition({
    191  property: 'transform',
    192  // translateX(100px)
    193  underlying: 'matrix(1, 0, 0, 1, 100, 0)',
    194  // Non-invertible
    195  accumulateFrom: 'matrix(1, 1, 0, 0, 0, 100)',
    196  // translateX(200px)
    197  accumulateTo: 'matrix(1, 0, 0, 1, 200, 0)',
    198 }, [
    199  {at: -0.5, expect: 'matrix(1, 1, 0, 0, 0, 100)'},
    200  {at: 0, expect: 'matrix(1, 1, 0, 0, 0, 100)'},
    201  {at: 0.25, expect: 'matrix(1, 1, 0, 0, 0, 100)'},
    202  {at: 0.5, expect: 'matrix(1, 0, 0, 1, 300, 0)'},
    203  {at: 0.75, expect: 'matrix(1, 0, 0, 1, 300, 0)'},
    204  {at: 1, expect: 'matrix(1, 0, 0, 1, 300, 0)'},
    205  {at: 1.5, expect: 'matrix(1, 0, 0, 1, 300, 0)'},
    206 ]);
    207 </script>
    208 </body>