tor-browser

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

generalParallelTest.js (9937B)


      1 (function(root) {
      2 'use strict';
      3 
      4 var index = 0;
      5 var suite = root.generalParallelTest = {
      6    // prepare individual test
      7    setup: function(data, options) {
      8        suite._setupDom(data, options);
      9        suite._setupEvents(data, options);
     10    },
     11    // clone fixture and prepare data containers
     12    _setupDom: function(data, options) {
     13        // clone fixture into off-viewport test-canvas
     14        data.fixture = document.getElementById('fixture').cloneNode(true);
     15        data.fixture.id = 'test-' + (index++);
     16        (document.getElementById('offscreen') || document.body).appendChild(data.fixture);
     17 
     18        // data container for #fixture > .container > .transition
     19        data.transition = {
     20            node: data.fixture.querySelector('.transition'),
     21            values: [],
     22            events: [],
     23            computedStyle: function(property) {
     24                return computedStyle(data.transition.node, property);
     25            }
     26        };
     27 
     28        // data container for #fixture > .container
     29        data.container = {
     30            node: data.transition.node.parentNode,
     31            values: [],
     32            events: [],
     33            computedStyle: function(property) {
     34                return computedStyle(data.container.node, property);
     35            }
     36        };
     37 
     38        // data container for #fixture > .container > .transition[:before | :after]
     39        if (data.pseudo) {
     40            data.pseudo = {
     41                name: data.pseudo,
     42                values: [],
     43                computedStyle: function(property) {
     44                    return computedStyle(data.transition.node, property, ':' + data.pseudo.name);
     45                }
     46            };
     47        }
     48    },
     49    // bind TransitionEnd event listeners
     50    _setupEvents: function(data, options) {
     51        ['transition', 'container'].forEach(function(elem) {
     52            var handler = function(event) {
     53                event.stopPropagation();
     54                var name = event.propertyName;
     55                var time = Math.round(event.elapsedTime * 1000) / 1000;
     56                var pseudo = event.pseudoElement ? (':' + event.pseudoElement) : '';
     57                data[elem].events.push(name + pseudo + ":" + time + "s");
     58            };
     59            data[elem].node.addEventListener('transitionend', handler, false);
     60            data[elem]._events = {'transitionend': handler};
     61        });
     62    },
     63    // cleanup after individual test
     64    teardown: function(data, options) {
     65        // data.fixture.remove();
     66        if (data.fixture.parentNode) {
     67            data.fixture.parentNode.removeChild(data.fixture);
     68        }
     69    },
     70    // invoked prior to running a slice of tests
     71    sliceStart: function(options, tests) {
     72        // inject styles into document
     73        setStyle(options.styles);
     74    },
     75    transitionsStarted: function(options, tests) {
     76        // kick off value collection loop
     77        generalParallelTest.startValueCollection(options);
     78    },
     79    // invoked after running a slice of tests
     80    sliceDone: function(options, tests) {
     81        // stop value collection loop
     82        generalParallelTest.stopValueCollection(options);
     83        // reset styles cache
     84        options.styles = {};
     85    },
     86    // called once all tests are done
     87    done: function(options) {
     88        // reset document styles
     89        setStyle();
     90        reflow();
     91    },
     92    // add styles of individual test to slice cache
     93    addStyles: function(data, options, styles) {
     94        if (!options.styles) {
     95            options.styles = {};
     96        }
     97 
     98        Object.keys(styles).forEach(function(key) {
     99            var selector = '#' + data.fixture.id
    100                // fixture must become #fixture.fixture rather than a child selector
    101                + (key.substring(0, 8) === '.fixture' ? '' : ' ')
    102                + key;
    103 
    104            options.styles[selector] = styles[key];
    105        });
    106    },
    107    // set style and compute values for container and transition
    108    getStyle: function(data) {
    109        reflow();
    110        // grab current styles: "initial state"
    111        suite._getStyleFor(data, 'from');
    112        // apply target state
    113        suite._addClass(data, 'to', true);
    114        // grab current styles: "target state"
    115        suite._getStyleFor(data, 'to');
    116        // remove target state
    117        suite._removeClass(data, 'to', true);
    118 
    119        // clean up the mess created for value collection
    120        data.container._values = [];
    121        data.transition._values = [];
    122        if (data.pseudo) {
    123            data.pseudo._values = [];
    124        }
    125    },
    126    // grab current styles and store in respective element's data container
    127    _getStyleFor: function(data, key) {
    128        data.container[key] = data.container.computedStyle(data.property);
    129        data.transition[key] = data.transition.computedStyle(data.property);
    130        if (data.pseudo) {
    131            data.pseudo[key] = data.pseudo.computedStyle(data.property);
    132        }
    133    },
    134    // add class to test's elements and possibly reflow
    135    _addClass: function(data, className, forceReflow) {
    136        data.container.node.classList.add(className);
    137        data.transition.node.classList.add(className);
    138        if (forceReflow) {
    139            reflow();
    140        }
    141    },
    142    // remove class from test's elements and possibly reflow
    143    _removeClass: function(data, className, forceReflow) {
    144        data.container.node.classList.remove(className);
    145        data.transition.node.classList.remove(className);
    146        if (forceReflow) {
    147            reflow();
    148        }
    149    },
    150    // add transition and to classes to container and transition
    151    startTransition: function(data) {
    152        // add transition-defining class
    153        suite._addClass(data, 'how', true);
    154        // add target state (without reflowing)
    155        suite._addClass(data, 'to', false);
    156    },
    157    // requestAnimationFrame runLoop to collect computed values
    158    startValueCollection: function(options) {
    159        const promises = [];
    160        const animations = document.getAnimations();
    161        animations.forEach(a => {
    162            promises.push(new Promise(resolve => {
    163                const listener = (event) => {
    164                    let found = false;
    165                    if (event.pseudoElement != '') {
    166                        if (event.pseudoElement == a.effect.pseudoElement) {
    167                            found = true;
    168                        }
    169                    } else if (!a.effect.pseudoElement) {
    170                        found = true;
    171                    }
    172                    if (found) {
    173                        a.effect.target.removeEventListener(
    174                            'transitionend', listener);
    175                        resolve();
    176                    }
    177                };
    178                a.effect.target.addEventListener('transitionend', listener);
    179            }));
    180        });
    181        Promise.all(promises).then(() => {
    182            options.allTransitionsCompleted();
    183        });
    184 
    185        // Sample at these values expressed as relative progress.
    186        // The last sample being the end of the transition ensure that the
    187        // completion events are received in short order.
    188        const sample_at = [0.2, 0.4, 0.6, 0.8, 1.0];
    189        sample_at.forEach(at => {
    190            const time = options.duration * at;
    191            animations.forEach(a => { a.currentTime = time; });
    192            // Collect current style for test's elements.
    193            options.tests.forEach(function(data) {
    194                if (!data.property) {
    195                    return;
    196                }
    197 
    198                ['transition', 'container', 'pseudo'].forEach(function(elem) {
    199                    var pseudo = null;
    200                    if (!data[elem] || (elem === 'pseudo' && !data.pseudo)) {
    201                        return;
    202                    }
    203 
    204                    var current = data[elem].computedStyle(data.property);
    205                    var values = data[elem].values;
    206                    var length = values.length;
    207                    if (!length || values[length - 1] !== current) {
    208                        values.push(current);
    209                    }
    210                });
    211            });
    212        });
    213    },
    214    // stop requestAnimationFrame runLoop collecting computed values
    215    stopValueCollection: function(options) {
    216        options._collectValues = false;
    217    },
    218 
    219    // generate test.step function asserting collected events match expected
    220    assertExpectedEventsFunc: function(data, elem, expected) {
    221        return function() {
    222            var _result = data[elem].events.sort().join(" ");
    223            var _expected = typeof expected === 'string' ? expected : expected.sort().join(" ");
    224            assert_equals(_result, _expected, "Expected TransitionEnd events triggered on ." + elem);
    225        };
    226    },
    227    // generate test.step function asserting collected values are neither initial nor target
    228    assertIntermediateValuesFunc: function(data, elem) {
    229        return function() {
    230            // the first value (index: 0) is always going to be the initial value
    231            // the last value is always going to be the target value
    232            var values = data[elem].values;
    233            if (data.flags.discrete) {
    234                // a discrete value will just switch from one state to another without having passed intermediate states.
    235                assert_equals(values[0], data[elem].from, "must be initial value while transitioning on ." + elem);
    236                assert_equals(values[1], data[elem].to, "must be target value after transitioning on ." + elem);
    237                assert_equals(values.length, 2, "discrete property only has 2 values ." + elem);
    238            } else {
    239                assert_not_equals(values[1], data[elem].from, "may not be initial value while transitioning on ." + elem);
    240                assert_not_equals(values[1], data[elem].to, "may not be target value while transitioning on ." + elem);
    241            }
    242 
    243            // TODO: first value must be initial, last value must be target
    244        };
    245    }
    246 };
    247 
    248 })(window);