tor-browser

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

resizeTestHelper.js (5698B)


      1 'use strict';
      2 
      3 /**
      4  ResizeTestHelper is a framework to test ResizeObserver
      5  notifications. Use it to make assertions about ResizeObserverEntries.
      6  This framework is needed because ResizeObservations are
      7  delivered asynchronously inside the event loop.
      8 
      9  Features:
     10  - can queue multiple notification steps in a test
     11  - handles timeouts
     12  - returns Promise that is fulfilled when test completes.
     13    Use to chain tests (since parallel async ResizeObserver tests
     14    would conflict if reusing same DOM elements).
     15 
     16  Usage:
     17 
     18  create ResizeTestHelper for every test.
     19  Make assertions inside notify, timeout callbacks.
     20  Start tests with helper.start()
     21  Chain tests with Promises.
     22  Counts animation frames, see startCountingRaf
     23 */
     24 
     25 /*
     26  @param name: test name
     27  @param steps:
     28  {
     29    setup: function(ResizeObserver) {
     30      // called at the beginning of the test step
     31      // your observe/resize code goes here
     32    },
     33    notify: function(entries, observer) {
     34      // ResizeObserver callback.
     35      // Make assertions here.
     36      // Return true if next step should start on the next event loop.
     37    },
     38    timeout: function() {
     39      // Define this if your test expects to time out, and the expected timeout
     40      // value will be 100ms.
     41      // If undefined, timeout is assert_unreached after 1000ms.
     42    }
     43  }
     44 */
     45 function ResizeTestHelper(name, steps)
     46 {
     47    this._name = name;
     48    this._steps = steps || [];
     49    this._stepIdx = -1;
     50    this._harnessTest = null;
     51    this._observer = new ResizeObserver(this._handleNotification.bind(this));
     52    this._timeoutBind = this._handleTimeout.bind(this);
     53    this._nextStepBind = this._nextStep.bind(this);
     54 }
     55 
     56 // The default timeout value in ms.
     57 // We expect TIMEOUT to be longer than we would ever have to wait for notify()
     58 // to be fired. This is used for tests which are not expected to time out, so
     59 // it can be large without slowing down the test.
     60 ResizeTestHelper.TIMEOUT = 1000;
     61 // A reasonable short timeout value in ms.
     62 // We expect SHORT_TIMEOUT to be long enough that notify() would usually get a
     63 // chance to fire before SHORT_TIMEOUT expires. This is used for tests which
     64 // *are* expected to time out (with no callbacks fired), so we'd like to keep
     65 // it relatively short to avoid slowing down the test.
     66 ResizeTestHelper.SHORT_TIMEOUT = 100;
     67 
     68 ResizeTestHelper.prototype = {
     69  get _currentStep() {
     70    return this._steps[this._stepIdx];
     71  },
     72 
     73  _nextStep: function() {
     74    if (++this._stepIdx == this._steps.length)
     75      return this._done();
     76    // Use SHORT_TIMEOUT if this step expects timeout.
     77    let timeoutValue = this._steps[this._stepIdx].timeout ?
     78        ResizeTestHelper.SHORT_TIMEOUT :
     79        ResizeTestHelper.TIMEOUT;
     80    this._timeoutId = this._harnessTest.step_timeout(
     81      this._timeoutBind, timeoutValue);
     82    try {
     83      this._steps[this._stepIdx].setup(this._observer);
     84    }
     85    catch(err) {
     86      this._harnessTest.step(() => {
     87        assert_unreached("Caught a throw, possible syntax error");
     88      });
     89    }
     90  },
     91 
     92  _handleNotification: function(entries) {
     93    if (this._timeoutId) {
     94      window.clearTimeout(this._timeoutId);
     95      delete this._timeoutId;
     96    }
     97    this._harnessTest.step(() => {
     98      try {
     99        let rafDelay = this._currentStep.notify(entries, this._observer);
    100        if (rafDelay)
    101          window.requestAnimationFrame(this._nextStepBind);
    102        else
    103          this._nextStep();
    104      }
    105      catch(err) {
    106        this._harnessTest.step(() => {
    107          throw err;
    108        });
    109        // Force to _done() the current test.
    110        this._done();
    111      }
    112    });
    113  },
    114 
    115  _handleTimeout: function() {
    116    delete this._timeoutId;
    117    this._harnessTest.step(() => {
    118      if (this._currentStep.timeout) {
    119        this._currentStep.timeout();
    120      }
    121      else {
    122        this._harnessTest.step(() => {
    123          assert_unreached("Timed out waiting for notification. (" + ResizeTestHelper.TIMEOUT + "ms)");
    124        });
    125      }
    126      this._nextStep();
    127    });
    128  },
    129 
    130  _done: function() {
    131    this._observer.disconnect();
    132    delete this._observer;
    133    this._harnessTest.done();
    134    if (this._rafCountRequest) {
    135      window.cancelAnimationFrame(this._rafCountRequest);
    136      delete this._rafCountRequest;
    137    }
    138    window.requestAnimationFrame(() => { this._resolvePromise(); });
    139  },
    140 
    141  start: function(cleanup) {
    142    this._harnessTest = async_test(this._name);
    143 
    144    if (cleanup) {
    145      this._harnessTest.add_cleanup(cleanup);
    146    }
    147 
    148    this._harnessTest.step(() => {
    149      assert_equals(this._stepIdx, -1, "start can only be called once");
    150      this._nextStep();
    151    });
    152    return new Promise( (resolve, reject) => {
    153      this._resolvePromise = resolve;
    154      this._rejectPromise = reject;
    155    });
    156  },
    157 
    158  get rafCount() {
    159    if (!this._rafCountRequest)
    160      throw "rAF count is not active";
    161    return this._rafCount;
    162  },
    163 
    164  get test() {
    165    if (!this._harnessTest) {
    166      throw "_harnessTest is not initialized";
    167    }
    168    return this._harnessTest;
    169  },
    170 
    171  _incrementRaf: function() {
    172    if (this._rafCountRequest) {
    173      this._rafCount++;
    174      this._rafCountRequest = window.requestAnimationFrame(this._incrementRafBind);
    175    }
    176  },
    177 
    178  startCountingRaf: function() {
    179    if (this._rafCountRequest)
    180      window.cancelAnimationFrame(this._rafCountRequest);
    181    if (!this._incrementRafBind)
    182      this._incrementRafBind = this._incrementRaf.bind(this);
    183    this._rafCount = 0;
    184    this._rafCountRequest = window.requestAnimationFrame(this._incrementRafBind);
    185  }
    186 }
    187 
    188 function createAndAppendElement(tagName, parent) {
    189  if (!parent) {
    190    parent = document.body;
    191  }
    192  const element = document.createElement(tagName);
    193  parent.appendChild(element);
    194  return element;
    195 }