tor-browser

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

largest-contentful-paint-helpers.js (6358B)


      1 const image_delay = 2000;
      2 const delay_pipe_value = image_delay / 1000;
      3 
      4 const await_with_timeout = async (delay, message, promise, cleanup = ()=>{}) => {
      5  let timeout_id;
      6  const timeout = new Promise((_, reject) => {
      7    timeout_id = step_timeout(() =>
      8      reject(new DOMException(message, "TimeoutError")), delay)
      9  });
     10  let result = null;
     11  try {
     12    result = await Promise.race([promise, timeout]);
     13    clearTimeout(timeout_id);
     14  } finally {
     15    cleanup();
     16  }
     17  return result;
     18 };
     19 
     20 // Receives an image LargestContentfulPaint |entry| and checks |entry|'s attribute values.
     21 // The |timeLowerBound| parameter is a lower bound on the loadTime value of the entry.
     22 // The |options| parameter may contain some string values specifying the following:
     23 // * 'renderTimeIs0': the renderTime should be 0 (image does not pass Timing-Allow-Origin checks).
     24 //     When not present, the renderTime should not be 0 (image passes the checks).
     25 // * 'sizeLowerBound': the |expectedSize| is only a lower bound on the size attribute value.
     26 //     When not present, |expectedSize| must be exactly equal to the size attribute value.
     27 // * 'approximateSize': the |expectedSize| is only approximate to the size attribute value.
     28 //     This option is mutually exclusive to 'sizeLowerBound'.
     29 function checkImage(entry, expectedUrl, expectedID, expectedSize, timeLowerBound, options = []) {
     30  assert_equals(entry.name, '', "Entry name should be the empty string");
     31  assert_equals(entry.entryType, 'largest-contentful-paint',
     32    "Entry type should be largest-contentful-paint");
     33  assert_equals(entry.duration, 0, "Entry duration should be 0");
     34  // The entry's url can be truncated.
     35  assert_equals(expectedUrl.substr(0, 100), entry.url.substr(0, 100),
     36    `Expected URL ${expectedUrl} should at least start with the entry's URL ${entry.url}`);
     37  assert_equals(entry.id, expectedID, "Entry ID matches expected one");
     38  assert_equals(entry.element, document.getElementById(expectedID),
     39    "Entry element is expected one");
     40  if (options.includes('skip')) {
     41    return;
     42  }
     43  assert_greater_than_equal(performance.now(), entry.renderTime,
     44    'renderTime should occur before the entry is dispatched to the observer.');
     45  assert_approx_equals(entry.startTime, entry.renderTime, 0.001,
     46    'startTime should be equal to renderTime to the precision of 1 millisecond.');
     47  if (options.includes('sizeLowerBound')) {
     48    assert_greater_than(entry.size, expectedSize);
     49  } else if (options.includes('approximateSize')) {
     50    assert_approx_equals(entry.size, expectedSize, 1);
     51  } else{
     52    assert_equals(entry.size, expectedSize);
     53  }
     54 
     55  assert_greater_than_equal(entry.paintTime, timeLowerBound, 'paintTime should represent the time when the UA started painting');
     56 
     57  // PaintTimingMixin
     58  if ("presentationTime" in entry && entry.presentationTime !== null) {
     59    assert_greater_than(entry.presentationTime, entry.paintTime);
     60    assert_equals(entry.presentationTime, entry.renderTime);
     61  } else {
     62    assert_equals(entry.renderTime, entry.paintTime);
     63  }
     64 
     65  if (options.includes('animated')) {
     66    assert_less_than(entry.renderTime, image_delay,
     67      'renderTime should be smaller than the delay applied to the second frame');
     68    assert_greater_than(entry.renderTime, 0,
     69      'renderTime should be larger than 0');
     70  }
     71  else {
     72    assert_between_inclusive(entry.loadTime, timeLowerBound, entry.renderTime,
     73      'loadTime should occur between the lower bound and the renderTime');
     74  }
     75 }
     76 
     77 const load_and_observe = url => {
     78  return new Promise(resolve => {
     79    (new PerformanceObserver(entryList => {
     80      for (let entry of entryList.getEntries()) {
     81        if (entry.url == url) {
     82          resolve(entryList.getEntries()[0]);
     83        }
     84      }
     85    })).observe({ type: 'largest-contentful-paint', buffered: true });
     86    const img = new Image();
     87    img.id = 'image_id';
     88    img.src = url;
     89    document.body.appendChild(img);
     90  });
     91 };
     92 
     93 const load_video_and_observe = url => {
     94  return new Promise(resolve => {
     95    (new PerformanceObserver(entryList => {
     96      for (let entry of entryList.getEntries()) {
     97        if (entry.url == url) {
     98          resolve(entryList.getEntries()[0]);
     99        }
    100      }
    101    })).observe({ type: 'largest-contentful-paint', buffered: true });
    102    const video = document.createElement("video");
    103    video.id = 'video_id';
    104    video.src = url;
    105    video.autoplay = true;
    106    video.muted = true;
    107    video.loop = true;
    108    document.body.appendChild(video);
    109  });
    110 };
    111 
    112 const getLCPStartTime = (identifier) => {
    113  return new Promise(resolve => {
    114    new PerformanceObserver((entryList, observer) => {
    115      entryList.getEntries().forEach(e => {
    116        if (e.url.includes(identifier)) {
    117          resolve(e);
    118          observer.disconnect();
    119        }
    120      });
    121    }).observe({ type: 'largest-contentful-paint', buffered: true });
    122  });
    123 }
    124 
    125 const getFCPStartTime = () => {
    126  return performance.getEntriesByName('first-contentful-paint')[0];
    127 }
    128 
    129 const add_text = (text) => {
    130  const paragraph = document.createElement('p');
    131  paragraph.innerHTML = text;
    132  document.body.appendChild(paragraph);
    133 }
    134 
    135 const loadImage = (url, shouldBeIgnoredForLCP = false) => {
    136  return new Promise(function (resolve, reject) {
    137    let image = document.createElement('img');
    138    image.addEventListener('load', () => { resolve(image); });
    139    image.addEventListener('error', reject);
    140    image.src = url;
    141    if (shouldBeIgnoredForLCP)
    142      image.style.opacity = 0;
    143    document.body.appendChild(image);
    144  });
    145 }
    146 
    147 const checkLCPEntryForNonTaoImages = (times = {}) => {
    148  const lcp = times['lcp'];
    149  const fcp = times['fcp'];
    150  const lcp_url_components = lcp.url.split('/');
    151 
    152  if (lcp.loadTime <= fcp.startTime) {
    153    assert_approx_equals(lcp.startTime, fcp.startTime, 0.001,
    154      'LCP start time should be the same as FCP for ' +
    155      lcp_url_components[lcp_url_components.length - 1]) +
    156      ' when LCP load time is less than FCP.';
    157  } else {
    158    assert_approx_equals(lcp.startTime, lcp.loadTime, 0.001,
    159      'LCP start time should be the same as LCP load time for ' +
    160      lcp_url_components[lcp_url_components.length - 1]) +
    161      ' when LCP load time is no less than FCP.';
    162  }
    163 
    164  assert_equals(lcp.renderTime, 0,
    165    'The LCP render time of Non-Tao image should always be 0.');
    166 }
    167 
    168 const raf = () => {
    169  return new Promise(resolve => requestAnimationFrame(resolve));
    170 }