tor-browser

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

helper.js (10235B)


      1 //
      2 // Simple Helper Functions For Testing CSS
      3 //
      4 
      5 (function(root) {
      6 'use strict';
      7 
      8 // serialize styles object and dump to dom
      9 // appends <style id="dynamic-style"> to <head>
     10 // setStyle("#some-selector", {"some-style" : "value"})
     11 // setStyle({"#some-selector": {"some-style" : "value"}})
     12 root.setStyle = function(selector, styles) {
     13    var target = document.getElementById('dynamic-style');
     14    if (!target) {
     15        target = document.createElement('style');
     16        target.id = 'dynamic-style';
     17        target.type = "text/css";
     18        document.getElementsByTagName('head')[0].appendChild(target);
     19    }
     20 
     21    var data = [];
     22    // single selector/styles
     23    if (typeof selector === 'string' && styles !== undefined) {
     24        data = [selector, '{', serializeStyles(styles), '}'];
     25        target.textContent = data.join("\n");
     26        return;
     27    }
     28    // map of selector/styles
     29    for (var key in selector) {
     30        if (Object.prototype.hasOwnProperty.call(selector, key)) {
     31            var _data = [key, '{', serializeStyles(selector[key]), '}'];
     32            data.push(_data.join('\n'));
     33        }
     34    }
     35 
     36    target.textContent = data.join("\n");
     37 };
     38 
     39 function serializeStyles(styles) {
     40    var data = [];
     41    for (var property in styles) {
     42        if (Object.prototype.hasOwnProperty.call(styles, property)) {
     43            var prefixedProperty = addVendorPrefix(property);
     44            data.push(prefixedProperty + ":" + styles[property] + ";");
     45        }
     46    }
     47 
     48    return data.join('\n');
     49 }
     50 
     51 
     52 // shorthand for computed style
     53 root.computedStyle = function(element, property, pseudo) {
     54    var prefixedProperty = addVendorPrefix(property);
     55    return window
     56        .getComputedStyle(element, pseudo || null)
     57        .getPropertyValue(prefixedProperty);
     58 };
     59 
     60 // flush rendering buffer
     61 root.reflow = function() {
     62    document.body.offsetWidth;
     63 };
     64 
     65 // merge objects
     66 root.extend = function(target /*, ..rest */) {
     67    Array.prototype.slice.call(arguments, 1).forEach(function(obj) {
     68        Object.keys(obj).forEach(function(key) {
     69            target[key] = obj[key];
     70        });
     71    });
     72 
     73    return target;
     74 };
     75 
     76 // dom fixture helper ("resetting dom test elements")
     77 var _domFixture;
     78 var _domFixtureSelector;
     79 root.domFixture = function(selector) {
     80    var fixture = document.querySelector(selector || _domFixtureSelector);
     81    if (!fixture) {
     82        throw new Error('fixture ' + (selector || _domFixtureSelector) + ' not found!');
     83    }
     84    if (!_domFixture && selector) {
     85        // save a copy
     86        _domFixture = fixture.cloneNode(true);
     87        _domFixtureSelector = selector;
     88    } else if (_domFixture) {
     89        // restore the copy
     90        var tmp = _domFixture.cloneNode(true);
     91        fixture.parentNode.replaceChild(tmp, fixture);
     92    } else {
     93        throw new Error('domFixture must be initialized first!');
     94    }
     95 };
     96 
     97 root.MS_PER_SEC = 1000;
     98 
     99 /*
    100 * The recommended minimum precision to use for time values.
    101 *
    102 * Based on Web Animations:
    103 * https://w3c.github.io/web-animations/#precision-of-time-values
    104 */
    105 const TIME_PRECISION = 0.0005; // ms
    106 
    107 /*
    108 * Allow implementations to substitute an alternative method for comparing
    109 * times based on their precision requirements.
    110 */
    111 root.assert_times_equal = function(actual, expected, description) {
    112  assert_approx_equals(actual, expected, TIME_PRECISION, description);
    113 };
    114 
    115 /*
    116 * Compare a time value based on its precision requirements with a fixed value.
    117 */
    118 root.assert_time_equals_literal = (actual, expected, description) => {
    119  assert_approx_equals(actual, expected, TIME_PRECISION, description);
    120 };
    121 
    122 /**
    123 * Assert that CSSTransition event, |evt|, has the expected property values
    124 * defined by |propertyName|, |elapsedTime|, and |pseudoElement|.
    125 */
    126 root.assert_end_events_equal = function(evt, propertyName, elapsedTime,
    127                                        pseudoElement = '') {
    128  assert_equals(evt.propertyName, propertyName);
    129  assert_times_equal(evt.elapsedTime, elapsedTime);
    130  assert_equals(evt.pseudoElement, pseudoElement);
    131 };
    132 
    133 /**
    134 * Assert that array of simultaneous CSSTransition events, |evts|, have the
    135 * corresponding property names listed in |propertyNames|, and the expected
    136 * |elapsedTimes| and |pseudoElement| members.
    137 *
    138 * |elapsedTimes| may be a single value if all events are expected to have the
    139 * same elapsedTime, or an array parallel to |propertyNames|.
    140 */
    141 root.assert_end_event_batch_equal = function(evts, propertyNames, elapsedTimes,
    142                                             pseudoElement = '') {
    143  assert_equals(
    144    evts.length,
    145    propertyNames.length,
    146    'Test harness error: should have waited for the correct number of events'
    147  );
    148  assert_true(
    149    typeof elapsedTimes === 'number' ||
    150      (Array.isArray(elapsedTimes) &&
    151        elapsedTimes.length === propertyNames.length),
    152    'Test harness error: elapsedTimes must either be a number or an array of' +
    153      ' numbers with the same length as propertyNames'
    154  );
    155 
    156  if (typeof elapsedTimes === 'number') {
    157    elapsedTimes = Array(propertyNames.length).fill(elapsedTimes);
    158  }
    159  const testPairs = propertyNames.map((propertyName, index) => ({
    160    propertyName,
    161    elapsedTime: elapsedTimes[index]
    162  }));
    163 
    164  const sortByPropertyName = (a, b) =>
    165    a.propertyName.localeCompare(b.propertyName);
    166  evts.sort(sortByPropertyName);
    167  testPairs.sort(sortByPropertyName);
    168 
    169  for (let evt of evts) {
    170    const expected = testPairs.shift();
    171    assert_end_events_equal(
    172      evt,
    173      expected.propertyName,
    174      expected.elapsedTime,
    175      pseudoElement
    176    );
    177  }
    178 }
    179 
    180 /**
    181 * Appends a div to the document body.
    182 *
    183 * @param t  The testharness.js Test object. If provided, this will be used
    184 *           to register a cleanup callback to remove the div when the test
    185 *           finishes.
    186 *
    187 * @param attrs  A dictionary object with attribute names and values to set on
    188 *               the div.
    189 */
    190 root.addDiv = function(t, attrs) {
    191  var div = document.createElement('div');
    192  if (attrs) {
    193    for (var attrName in attrs) {
    194      div.setAttribute(attrName, attrs[attrName]);
    195    }
    196  }
    197  document.body.appendChild(div);
    198  if (t && typeof t.add_cleanup === 'function') {
    199    t.add_cleanup(function() {
    200      if (div.parentNode) {
    201        div.remove();
    202      }
    203    });
    204  }
    205  return div;
    206 };
    207 
    208 /**
    209 * Appends a style div to the document head.
    210 *
    211 * @param t  The testharness.js Test object. If provided, this will be used
    212 *           to register a cleanup callback to remove the style element
    213 *           when the test finishes.
    214 *
    215 * @param rules  A dictionary object with selector names and rules to set on
    216 *               the style sheet.
    217 */
    218 root.addStyle = (t, rules) => {
    219  const extraStyle = document.createElement('style');
    220  document.head.appendChild(extraStyle);
    221  if (rules) {
    222    const sheet = extraStyle.sheet;
    223    for (const selector in rules) {
    224      sheet.insertRule(selector + '{' + rules[selector] + '}',
    225                       sheet.cssRules.length);
    226    }
    227  }
    228 
    229  if (t && typeof t.add_cleanup === 'function') {
    230    t.add_cleanup(() => {
    231      extraStyle.remove();
    232    });
    233  }
    234  return extraStyle;
    235 };
    236 
    237 /**
    238 * Promise wrapper for requestAnimationFrame.
    239 */
    240 root.waitForFrame = () => {
    241  return new Promise(resolve => {
    242    window.requestAnimationFrame(resolve);
    243  });
    244 };
    245 
    246 /**
    247 * Returns a Promise that is resolved after the given number of consecutive
    248 * animation frames have occured (using requestAnimationFrame callbacks).
    249 *
    250 * @param frameCount  The number of animation frames.
    251 * @param onFrame  An optional function to be processed in each animation frame.
    252 */
    253 root.waitForAnimationFrames = (frameCount, onFrame) => {
    254  const timeAtStart = document.timeline.currentTime;
    255  return new Promise(resolve => {
    256    function handleFrame() {
    257      if (onFrame && typeof onFrame === 'function') {
    258        onFrame();
    259      }
    260      if (timeAtStart != document.timeline.currentTime &&
    261          --frameCount <= 0) {
    262        resolve();
    263      } else {
    264        window.requestAnimationFrame(handleFrame); // wait another frame
    265      }
    266    }
    267    window.requestAnimationFrame(handleFrame);
    268  });
    269 };
    270 
    271 /**
    272 * Wrapper that takes a sequence of N animations and returns:
    273 *
    274 *   Promise.all([animations[0].ready, animations[1].ready, ... animations[N-1].ready]);
    275 */
    276 root.waitForAllAnimations = animations =>
    277  Promise.all(animations.map(animation => animation.ready));
    278 
    279 /**
    280 * Utility that takes a Promise and a maximum number of frames to wait and
    281 * returns a new Promise that behaves as follows:
    282 *
    283 * - If the provided Promise resolves _before_ the specified number of frames
    284 *   have passed, resolves with the result of the provided Promise.
    285 * - If the provided Promise rejects _before_ the specified number of frames
    286 *   have passed, rejects with the error result of the provided Promise.
    287 * - Otherwise, rejects with a 'Timed out' error message. If |message| is
    288 *   provided, it will be appended to the error message.
    289 */
    290 root.frameTimeout = (promiseToWaitOn, framesToWait, message) => {
    291  let framesRemaining = framesToWait;
    292  let aborted = false;
    293 
    294  const timeoutPromise = new Promise(function waitAFrame(resolve, reject) {
    295    if (aborted) {
    296      resolve();
    297      return;
    298    }
    299    if (framesRemaining-- > 0) {
    300      requestAnimationFrame(() => {
    301        waitAFrame(resolve, reject);
    302      });
    303      return;
    304    }
    305    let errorMessage = 'Timed out waiting for Promise to resolve';
    306    if (message) {
    307      errorMessage += `: ${message}`;
    308    }
    309    reject(new Error(errorMessage));
    310  });
    311 
    312  const wrappedPromiseToWaitOn = promiseToWaitOn.then(result => {
    313    aborted = true;
    314    return result;
    315  });
    316 
    317  return Promise.race([timeoutPromise, wrappedPromiseToWaitOn]);
    318 };
    319 
    320 root.supportsStartingStyle = () => {
    321  let sheet = new CSSStyleSheet();
    322  sheet.replaceSync("@starting-style{}");
    323  return sheet.cssRules.length == 1;
    324 };
    325 
    326 /**
    327 * Waits for a 'transitionend' event to fire on the given element.
    328 *
    329 * @param element  The DOM element to listen for the transitionend event on.
    330 * @returns {Promise<void>} A promise that resolves when the transitionend event is fired.
    331 */
    332 root.waitForTransitionEnd = function(element) {
    333  return new Promise(resolve => {
    334    element.addEventListener('transitionend', resolve, { once: true });
    335  });
    336 };
    337 
    338 
    339 })(window);