tor-browser

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

testcommon.js (11409B)


      1 'use strict';
      2 
      3 const MS_PER_SEC = 1000;
      4 
      5 // The recommended minimum precision to use for time values[1].
      6 //
      7 // [1] https://drafts.csswg.org/web-animations/#precision-of-time-values
      8 const TIME_PRECISION = 0.0005; // ms
      9 
     10 // Allow implementations to substitute an alternative method for comparing
     11 // times based on their precision requirements.
     12 if (!window.assert_times_equal) {
     13  window.assert_times_equal = (actual, expected, description) => {
     14    assert_approx_equals(actual, expected, TIME_PRECISION * 2, description);
     15  };
     16 }
     17 
     18 // Allow implementations to substitute an alternative method for comparing
     19 // times based on their precision requirements.
     20 if (!window.assert_time_greater_than_equal) {
     21 window.assert_time_greater_than_equal = (actual, expected, description) => {
     22    assert_greater_than_equal(actual, expected - 2 * TIME_PRECISION,
     23                              description);
     24  };
     25 }
     26 
     27 // Allow implementations to substitute an alternative method for comparing
     28 // a time value based on its precision requirements with a fixed value.
     29 if (!window.assert_time_equals_literal) {
     30  window.assert_time_equals_literal = (actual, expected, description) => {
     31    if (Math.abs(expected) === Infinity) {
     32      assert_equals(actual, expected, description);
     33    } else {
     34      assert_approx_equals(actual, expected, TIME_PRECISION, description);
     35    }
     36  }
     37 }
     38 
     39 // creates div element, appends it to the document body and
     40 // removes the created element during test cleanup
     41 function createDiv(test, doc) {
     42  return createElement(test, 'div', doc);
     43 }
     44 
     45 // creates element of given tagName, appends it to the document body and
     46 // removes the created element during test cleanup
     47 // if tagName is null or undefined, returns div element
     48 function createElement(test, tagName, doc) {
     49  if (!doc) {
     50    doc = document;
     51  }
     52  const element = doc.createElement(tagName || 'div');
     53  doc.body.appendChild(element);
     54  test.add_cleanup(() => {
     55    element.remove();
     56  });
     57  return element;
     58 }
     59 
     60 // Creates a style element with the specified rules, appends it to the document
     61 // head and removes the created element during test cleanup.
     62 // |rules| is an object. For example:
     63 // { '@keyframes anim': '' ,
     64 //   '.className': 'animation: anim 100s;' };
     65 // or
     66 // { '.className1::before': 'content: ""; width: 0px; transition: all 10s;',
     67 //   '.className2::before': 'width: 100px;' };
     68 // The object property name could be a keyframes name, or a selector.
     69 // The object property value is declarations which are property:value pairs
     70 // split by a space.
     71 function createStyle(test, rules, doc) {
     72  if (!doc) {
     73    doc = document;
     74  }
     75  const extraStyle = doc.createElement('style');
     76  doc.head.appendChild(extraStyle);
     77  if (rules) {
     78    const sheet = extraStyle.sheet;
     79    for (const selector in rules) {
     80      sheet.insertRule(`${selector}{${rules[selector]}}`,
     81                       sheet.cssRules.length);
     82    }
     83  }
     84  test.add_cleanup(() => {
     85    extraStyle.remove();
     86  });
     87 }
     88 
     89 // Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1).
     90 function cubicBezier(x1, y1, x2, y2) {
     91  const xForT = t => {
     92    const omt = 1-t;
     93    return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t;
     94  };
     95 
     96  const yForT = t => {
     97    const omt = 1-t;
     98    return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t;
     99  };
    100 
    101  const tForX = x => {
    102    // Binary subdivision.
    103    let mint = 0, maxt = 1;
    104    for (let i = 0; i < 30; ++i) {
    105      const guesst = (mint + maxt) / 2;
    106      const guessx = xForT(guesst);
    107      if (x < guessx) {
    108        maxt = guesst;
    109      } else {
    110        mint = guesst;
    111      }
    112    }
    113    return (mint + maxt) / 2;
    114  };
    115 
    116  return x => {
    117    if (x == 0) {
    118      return 0;
    119    }
    120    if (x == 1) {
    121      return 1;
    122    }
    123    return yForT(tForX(x));
    124  };
    125 }
    126 
    127 function stepEnd(nsteps) {
    128  return x => Math.floor(x * nsteps) / nsteps;
    129 }
    130 
    131 function stepStart(nsteps) {
    132  return x => {
    133    const result = Math.floor(x * nsteps + 1.0) / nsteps;
    134    return (result > 1.0) ? 1.0 : result;
    135  };
    136 }
    137 
    138 function waitForAnimationFrames(frameCount) {
    139  return new Promise(resolve => {
    140    function handleFrame() {
    141      if (--frameCount <= 0) {
    142        resolve();
    143      } else {
    144        window.requestAnimationFrame(handleFrame); // wait another frame
    145      }
    146    }
    147    window.requestAnimationFrame(handleFrame);
    148  });
    149 }
    150 
    151 // Continually calls requestAnimationFrame until |minDelay| has elapsed
    152 // as recorded using document.timeline.currentTime (i.e. frame time not
    153 // wall-clock time).
    154 function waitForAnimationFramesWithDelay(minDelay) {
    155  const startTime = document.timeline.currentTime;
    156  return new Promise(resolve => {
    157    (function handleFrame() {
    158      if (document.timeline.currentTime - startTime >= minDelay) {
    159        resolve();
    160      } else {
    161        window.requestAnimationFrame(handleFrame);
    162      }
    163    }());
    164  });
    165 }
    166 
    167 function runAndWaitForFrameUpdate(callback) {
    168  return new Promise(resolve => {
    169    window.requestAnimationFrame(() => {
    170      callback();
    171      window.requestAnimationFrame(resolve);
    172    });
    173  });
    174 }
    175 
    176 // Waits for a requestAnimationFrame callback in the next refresh driver tick.
    177 function waitForNextFrame() {
    178  const timeAtStart = document.timeline.currentTime;
    179  return new Promise(resolve => {
    180   (function handleFrame() {
    181    if (timeAtStart === document.timeline.currentTime) {
    182      window.requestAnimationFrame(handleFrame);
    183    } else {
    184      resolve();
    185    }
    186  }());
    187  });
    188 }
    189 
    190 async function insertFrameAndAwaitLoad(test, iframe, doc) {
    191  const eventWatcher = new EventWatcher(test, iframe, ['load']);
    192  const event_promise = eventWatcher.wait_for('load');
    193 
    194  doc.body.appendChild(iframe);
    195  test.add_cleanup(() => { doc.body.removeChild(iframe); });
    196 
    197  await event_promise;
    198 }
    199 
    200 // Returns 'matrix()' or 'matrix3d()' function string generated from an array.
    201 function createMatrixFromArray(array) {
    202  return (array.length == 16 ? 'matrix3d' : 'matrix') + `(${array.join()})`;
    203 }
    204 
    205 // Returns 'matrix3d()' function string equivalent to
    206 // 'rotate3d(x, y, z, radian)'.
    207 function rotate3dToMatrix3d(x, y, z, radian) {
    208  return createMatrixFromArray(rotate3dToMatrix(x, y, z, radian));
    209 }
    210 
    211 // Returns an array of the 4x4 matrix equivalent to 'rotate3d(x, y, z, radian)'.
    212 // https://drafts.csswg.org/css-transforms-2/#Rotate3dDefined
    213 function rotate3dToMatrix(x, y, z, radian) {
    214  const sc = Math.sin(radian / 2) * Math.cos(radian / 2);
    215  const sq = Math.sin(radian / 2) * Math.sin(radian / 2);
    216 
    217  // Normalize the vector.
    218  const length = Math.sqrt(x*x + y*y + z*z);
    219  x /= length;
    220  y /= length;
    221  z /= length;
    222 
    223  return [
    224    1 - 2 * (y*y + z*z) * sq,
    225    2 * (x * y * sq + z * sc),
    226    2 * (x * z * sq - y * sc),
    227    0,
    228    2 * (x * y * sq - z * sc),
    229    1 - 2 * (x*x + z*z) * sq,
    230    2 * (y * z * sq + x * sc),
    231    0,
    232    2 * (x * z * sq + y * sc),
    233    2 * (y * z * sq - x * sc),
    234    1 - 2 * (x*x + y*y) * sq,
    235    0,
    236    0,
    237    0,
    238    0,
    239    1
    240  ];
    241 }
    242 
    243 // Compare matrix string like 'matrix(1, 0, 0, 1, 100, 0)' with tolerances.
    244 function assert_matrix_equals(actual, expected, description) {
    245  const matrixRegExp = /^matrix(?:3d)*\((.+)\)/;
    246  assert_regexp_match(actual, matrixRegExp,
    247    'Actual value is not a matrix')
    248  assert_regexp_match(expected, matrixRegExp,
    249    'Expected value is not a matrix');
    250 
    251  const actualMatrixArray =
    252    actual.match(matrixRegExp)[1].split(',').map(Number);
    253  const expectedMatrixArray =
    254    expected.match(matrixRegExp)[1].split(',').map(Number);
    255 
    256  assert_equals(actualMatrixArray.length, expectedMatrixArray.length,
    257    `dimension of the matrix: ${description}`);
    258  for (let i = 0; i < actualMatrixArray.length; i++) {
    259    assert_approx_equals(actualMatrixArray[i], expectedMatrixArray[i], 0.0001,
    260      `expected ${expected} but got ${actual}: ${description}`);
    261  }
    262 }
    263 
    264 // Compare rotate3d vector like '0 1 0 45deg' with tolerances.
    265 function assert_rotate3d_equals(actual, expected, description) {
    266  const rotationRegExp =/^((([+-]?\d+(\.+\d+)?\s){3})?\d+(\.+\d+)?)deg/;
    267 
    268  assert_regexp_match(actual, rotationRegExp,
    269    'Actual value is not a rotate3d vector')
    270  assert_regexp_match(expected, rotationRegExp,
    271    'Expected value is not a rotate3d vector');
    272 
    273  const actualRotationVector =
    274    actual.match(rotationRegExp)[1].split(' ').map(Number);
    275  const expectedRotationVector =
    276    expected.match(rotationRegExp)[1].split(' ').map(Number);
    277 
    278  assert_equals(actualRotationVector.length, expectedRotationVector.length,
    279                `dimension of the matrix: ${description}`);
    280  for (let i = 0; i < actualRotationVector.length; i++) {
    281    assert_approx_equals(
    282        actualRotationVector[i],
    283        expectedRotationVector[i],
    284        0.0001,
    285        `expected ${expected} but got ${actual}: ${description}`);
    286  }
    287 }
    288 
    289 function assert_phase_at_time(animation, phase, currentTime) {
    290  animation.currentTime = currentTime;
    291  assert_phase(animation, phase);
    292 }
    293 
    294 function assert_phase(animation, phase) {
    295  const fillMode = animation.effect.getTiming().fill;
    296  const currentTime = animation.currentTime;
    297 
    298  if (phase === 'active') {
    299    // If the fill mode is 'none', then progress will only be non-null if we
    300    // are in the active phase, except for progress-based timelines where
    301    // currentTime = 100% is still 'active'.
    302    animation.effect.updateTiming({ fill: 'none' });
    303    if ('ScrollTimeline' in window && animation.timeline instanceof ScrollTimeline) {
    304        const isActive = animation.currentTime?.toString() == "100%" ||
    305                         animation.effect.getComputedTiming().progress != null;
    306        assert_true(isActive,
    307                    'Animation effect is in active phase when current time ' +
    308                    `is ${currentTime}.`);
    309    } else {
    310      assert_not_equals(animation.effect.getComputedTiming().progress, null,
    311                        'Animation effect is in active phase when current time ' +
    312                        `is ${currentTime}.`);
    313    }
    314  } else {
    315    // The easiest way to distinguish between the 'before' phase and the 'after'
    316    // phase is to toggle the fill mode. For example, if the progress is null
    317    // when the fill mode is 'none' but non-null when the fill mode is
    318    // 'backwards' then we are in the before phase.
    319    animation.effect.updateTiming({ fill: 'none' });
    320    assert_equals(animation.effect.getComputedTiming().progress, null,
    321                  `Animation effect is in ${phase} phase when current time ` +
    322                  `is ${currentTime} (progress is null with 'none' fill mode)`);
    323 
    324    animation.effect.updateTiming({
    325      fill: phase === 'before' ? 'backwards' : 'forwards',
    326    });
    327    assert_not_equals(animation.effect.getComputedTiming().progress, null,
    328                      `Animation effect is in ${phase} phase when current ` +
    329                      `time is ${currentTime} (progress is non-null with ` +
    330                      `appropriate fill mode)`);
    331  }
    332 
    333  // Reset fill mode to avoid side-effects.
    334  animation.effect.updateTiming({ fill: fillMode });
    335 }
    336 
    337 
    338 // Use with reftest-wait to wait until compositor commits are no longer deferred
    339 // before taking the screenshot.
    340 // crbug.com/1378671
    341 async function waitForCompositorReady() {
    342  const animation =
    343      document.body.animate({ opacity: [ 0, 1 ] }, {duration: 1 });
    344  return animation.finished;
    345 }
    346 
    347 async function takeScreenshotOnAnimationsReady() {
    348  await Promise.all(document.getAnimations().map(a => a.ready));
    349  requestAnimationFrame(() => requestAnimationFrame(takeScreenshot));
    350 }