tor-browser

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

utils.js (5422B)


      1 const windowLoaded = new Promise(resolve => window.addEventListener('load', resolve));
      2 if ("setup" in globalThis) {
      3  setup(() =>
      4    assert_implements(window.PerformanceLongAnimationFrameTiming,
      5      'Long animation frames are not supported.'));
      6 }
      7 
      8 const very_long_frame_duration = 360;
      9 const no_long_frame_timeout = very_long_frame_duration * 2;
     10 const waiting_for_long_frame_timeout = very_long_frame_duration * 10;
     11 
     12 function loaf_promise(t) {
     13  return new Promise(resolve => {
     14      const observer = new PerformanceObserver(entries => {
     15          const entry = entries.getEntries()[0];
     16          // TODO: understand why we need this 5ms epsilon.
     17          if (entry.duration > very_long_frame_duration - 5) {
     18            observer.disconnect();
     19            resolve(entry);
     20          }
     21      });
     22 
     23      t.add_cleanup(() => observer.disconnect());
     24 
     25      observer.observe({entryTypes: ['long-animation-frame']});
     26  });
     27 }
     28 
     29 function busy_wait(ms_delay = very_long_frame_duration) {
     30  const deadline = performance.now() + ms_delay;
     31  while (performance.now() < deadline) {}
     32 }
     33 
     34 function generate_long_animation_frame(duration = very_long_frame_duration) {
     35  busy_wait(duration / 2);
     36  const reference_time = performance.now();
     37  busy_wait(duration / 2);
     38  return new Promise(resolve => new PerformanceObserver((entries, observer) => {
     39    const entry = entries.getEntries().find(e =>
     40        ((e.startTime < reference_time) &&
     41        (reference_time < (e.startTime + e.duration))));
     42    if (entry) {
     43      observer.disconnect();
     44      resolve(entry);
     45    }
     46  }).observe({type: "long-animation-frame"}));
     47 }
     48 
     49 async function expect_long_frame(cb, t) {
     50  await windowLoaded;
     51  const timeout = new Promise((resolve, reject) =>
     52    t.step_timeout(() => resolve("timeout"), waiting_for_long_frame_timeout));
     53  let resolve_loaf;
     54  const received_loaf = new Promise(resolve => { resolve_loaf = resolve; });
     55  const generate_loaf = (duration = very_long_frame_duration) =>
     56    generate_long_animation_frame(duration).then(resolve_loaf);
     57  window.generate_loaf_now = generate_loaf;
     58  await cb(t, generate_loaf);
     59  const entry = await Promise.race([
     60    received_loaf,
     61    timeout
     62  ]);
     63  delete window.generate_loaf_now;
     64  return entry;
     65 }
     66 
     67 function generate_long_animation_frame(duration = 120) {
     68  busy_wait(duration / 2);
     69  const reference_time = performance.now();
     70  busy_wait(duration / 2);
     71  return new Promise(resolve => new PerformanceObserver((entries, observer) => {
     72    const entry = entries.getEntries().find(e =>
     73        (e.startTime < reference_time) &&
     74        (reference_time < (e.startTime + e.duration)));
     75    if (entry) {
     76      observer.disconnect();
     77      resolve(entry);
     78    }
     79  }).observe({type: "long-animation-frame"}));
     80 }
     81 
     82 async function expect_long_frame_with_script(cb, predicate, t) {
     83  const entry = await expect_long_frame(cb, t);
     84  for (const script of entry.scripts ?? []) {
     85    if (predicate(script, entry))
     86      return [entry, script];
     87  }
     88 
     89  return [];
     90 }
     91 
     92 async function expect_no_long_frame(cb, t) {
     93  await windowLoaded;
     94  for (let i = 0; i < 5; ++i) {
     95    const receivedLongFrame = loaf_promise(t);
     96    await cb();
     97    const result = await Promise.race([receivedLongFrame,
     98        new Promise(resolve => t.step_timeout(() => resolve("timeout"),
     99        no_long_frame_timeout))]);
    100    if (result === "timeout")
    101      return false;
    102  }
    103 
    104  throw new Error("Consistently creates long frame");
    105 }
    106 
    107 async function prepare_exec_iframe(t, origin) {
    108  const iframe = document.createElement("iframe");
    109  t.add_cleanup(() => iframe.remove());
    110  const url = new URL("/common/dispatcher/remote-executor.html", origin);
    111  const uuid = token();
    112  url.searchParams.set("uuid", uuid);
    113  iframe.src = url.href;
    114  document.body.appendChild(iframe);
    115  await new Promise(resolve => iframe.addEventListener("load", resolve));
    116  return [new RemoteContext(uuid), iframe];
    117 }
    118 
    119 
    120 async function prepare_exec_popup(t, origin) {
    121  const url = new URL("/common/dispatcher/remote-executor.html", origin);
    122  const uuid = token();
    123  url.searchParams.set("uuid", uuid);
    124  const popup = window.open(url);
    125  t.add_cleanup(() => popup.close());
    126  return [new RemoteContext(uuid), popup];
    127 }
    128 
    129 function test_loaf_script(cb, invoker, invokerType, label) {
    130  promise_test(async t => {
    131    let [entry, script] = [];
    132    [entry, script] = await expect_long_frame_with_script(cb,
    133      script => (
    134        script.invokerType === invokerType &&
    135        script.invoker.startsWith(invoker)), t);
    136 
    137    assert_true(!!entry, "Entry detected");
    138    assert_greater_than_equal(entry.duration, script.duration);
    139    assert_greater_than_equal(script.executionStart, script.startTime);
    140    assert_greater_than_equal(script.startTime, entry.startTime)
    141    assert_equals(script.window, window);
    142    assert_equals(script.forcedStyleAndLayoutDuration, 0);
    143    assert_equals(script.windowAttribution, "self");
    144 }, `LoAF script: ${invoker} ${invokerType},${label ? ` ${label}` : ''}`);
    145 
    146 }
    147 
    148 function test_self_user_callback(cb, invoker, label) {
    149    test_loaf_script(cb, invoker, "user-callback", label);
    150 }
    151 
    152 function test_self_event_listener(cb, invoker, label) {
    153  test_loaf_script(cb, invoker, "event-listener", label);
    154 }
    155 
    156 function test_promise_script(cb, resolve_or_reject, invoker, label) {
    157  test_loaf_script(cb, invoker, `${resolve_or_reject}-promise`, label);
    158 }
    159 
    160 function test_self_script_block(cb, invoker, type) {
    161  test_loaf_script(cb, invoker, type);
    162 }