tor-browser

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

test-render-blocking.js (4186B)


      1 // Observes the `load` event of an EventTarget, or the finishing of a resource
      2 // given its url. Requires `/preload/resources/preload_helper.js` for the latter
      3 // usage.
      4 class LoadObserver {
      5  constructor(target) {
      6    this.finishTime = null;
      7    this.load = new Promise((resolve, reject) => {
      8      if (target.addEventListener) {
      9        target.addEventListener('load', ev => {
     10          this.finishTime = ev.timeStamp;
     11          resolve(ev);
     12        });
     13        target.addEventListener('error', reject);
     14      } else if (typeof target === 'string') {
     15        const observer = new PerformanceObserver(() => {
     16          if (numberOfResourceTimingEntries(target)) {
     17            this.finishTime = performance.now();
     18            resolve();
     19          }
     20        });
     21        observer.observe({type: 'resource', buffered: true});
     22      } else {
     23        reject('Unsupported target for LoadObserver');
     24      }
     25    });
     26  }
     27 
     28  get finished() {
     29    return this.finishTime !== null;
     30  }
     31 }
     32 
     33 // Observes the insertion of a script/parser-blocking element into DOM via
     34 // MutationObserver, so that we can access the element before it's loaded.
     35 function nodeInserted(parentNode, predicate) {
     36  return new Promise(resolve => {
     37    function callback(mutationList) {
     38      for (let mutation of mutationList) {
     39        for (let node of mutation.addedNodes) {
     40          if (predicate(node))
     41            resolve(node);
     42        }
     43      }
     44    }
     45    new MutationObserver(callback).observe(parentNode, {childList: true});
     46  });
     47 }
     48 
     49 function createAutofocusTarget() {
     50  const autofocusTarget = document.createElement('textarea');
     51  autofocusTarget.setAttribute('autofocus', '');
     52  // We may not have a body element at this point if we are testing a
     53  // script-blocking stylesheet. Hence, the new element is added to
     54  // documentElement.
     55  document.documentElement.appendChild(autofocusTarget);
     56  return autofocusTarget;
     57 }
     58 
     59 function createScrollTarget() {
     60  const scrollTarget = document.createElement('div');
     61  scrollTarget.style.overflow = 'scroll';
     62  scrollTarget.style.height = '100px';
     63  const scrollContent = document.createElement('div');
     64  scrollContent.style.height = '200px';
     65  scrollTarget.appendChild(scrollContent);
     66  document.documentElement.appendChild(scrollTarget);
     67  return scrollTarget;
     68 }
     69 
     70 function createAnimationTarget() {
     71  const style = document.createElement('style');
     72  style.textContent = `
     73      @keyframes anim {
     74        from { height: 100px; }
     75        to { height: 200px; }
     76      }
     77  `;
     78  const animationTarget = document.createElement('div');
     79  animationTarget.style.backgroundColor = 'green';
     80  animationTarget.style.height = '50px';
     81  animationTarget.style.animation = 'anim 100ms';
     82  document.documentElement.appendChild(style);
     83  document.documentElement.appendChild(animationTarget);
     84  return animationTarget;
     85 }
     86 
     87 // Error margin for comparing timestamps of paint and load events, in case they
     88 // are reported by different threads.
     89 const epsilon = 50;
     90 
     91 function test_render_blocking(optionalElementOrUrl, finalTest, finalTestTitle) {
     92  // Ideally, we should observe the 'load' event on the specific render-blocking
     93  // elements. However, this is not possible for script-blocking stylesheets, so
     94  // we have to observe the 'load' event on 'window' instead.
     95  if (!(optionalElementOrUrl instanceof HTMLElement) &&
     96      typeof optionalElementOrUrl !== 'string') {
     97    finalTestTitle = finalTest;
     98    finalTest = optionalElementOrUrl;
     99    optionalElementOrUrl = undefined;
    100  }
    101  const loadObserver = new LoadObserver(optionalElementOrUrl || window);
    102 
    103  promise_test(async test => {
    104    assert_implements(window.PerformancePaintTiming);
    105 
    106    await test.step_wait(() => performance.getEntriesByType('paint').length);
    107 
    108    assert_true(loadObserver.finished);
    109    for (let entry of performance.getEntriesByType('paint')) {
    110      assert_greater_than(entry.startTime, loadObserver.finishTime - epsilon,
    111                          `${entry.name} should occur after loading render-blocking resources`);
    112    }
    113  }, 'Rendering is blocked before render-blocking resources are loaded');
    114 
    115  promise_test(test => {
    116    return loadObserver.load.then(() => finalTest(test));
    117  }, finalTestTitle);
    118 }