tor-browser

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

test_fullscreen-api-rapid-cycle.html (5418B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4  <title>Test for rapid cycling of Fullscreen API requests</title>
      5  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      6  <script src="/tests/SimpleTest/EventUtils.js"></script>
      7  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
      8 </head>
      9 <body>
     10 <script>
     11 
     12 // There are two ways that web content should be able to reliably
     13 // request and respond to fullscreen:
     14 //
     15 // 1) Wait on the requestFullscreen() and exitFullscreen() promises.
     16 // 2) Respond to the "fullscreenchange" and "fullscreenerror" events
     17 //    after calling requestFullscreen() or exitFullscreen().
     18 //
     19 // This test exercises both methods rapidly, while checking to see
     20 // if any expected signal is taking too long. If awaiting a promise
     21 // or waiting for an event takes longer than some number of seconds,
     22 // the test will fail instead of timing out. This is to help detect
     23 // vulnerabilities in the implementation which would slow down the
     24 // test harness waiting for the timeout.
     25 
     26 // How many enter-exit cycles we run for each method of detecting a
     27 // fullscreen transition.
     28 const CYCLE_COUNT = 3;
     29 
     30 // How long do we wait for one transition before considering it as
     31 // an error.
     32 const TOO_LONG_SECONDS = 3;
     33 
     34 SimpleTest.requestFlakyTimeout("We race against Promises to turn possible timeouts into errors.");
     35 
     36 function rejectAfterTooLong() {
     37  return new Promise((resolve, reject) => {
     38    const fail = () => {
     39      reject(`timeout after ${TOO_LONG_SECONDS} seconds`);
     40    }
     41    setTimeout(fail, TOO_LONG_SECONDS * 1000);
     42  });
     43 }
     44 
     45 add_setup(async () => {
     46  await SpecialPowers.pushPrefEnv({
     47    "set": [
     48      // Keep the test structure simple.
     49      ["full-screen-api.allow-trusted-requests-only", false],
     50 
     51      // Make macOS fullscreen transitions asynchronous.
     52      ["full-screen-api.macos-native-full-screen", true],
     53 
     54      // Clarify that even no-duration async transitions are vulnerable.
     55      ["full-screen-api.transition-duration.enter", "0 0"],
     56      ["full-screen-api.transition-duration.leave", "0 0"],
     57    ]
     58  });
     59 });
     60 
     61 add_task(ensureOutOfFullscreen);
     62 
     63 // It is an implementation detail that promises resolve first, and
     64 // then events are fired on a later event loop. For this reason,
     65 // it's very important that we do the rapidCycleAwaitEvents task
     66 // first, because we don't want to have any "stray" fullscreenchange
     67 // events in the pipeline when we start that task. Conversely,
     68 // there's really no way for the rapidCycleAwaitEvents to poison
     69 // the environment for the next task, which waits on promises.
     70 add_task(rapidCycleAwaitEvents);
     71 
     72 add_task(ensureOutOfFullscreen);
     73 
     74 add_task(rapidCycleAwaitPromises);
     75 
     76 add_task(() => { ok(true, "Completed test with one expected result."); });
     77 
     78 // This is a helper function to repeatedly invoke a Promise generator
     79 // until the Promise resolves, delaying by one event loop on each
     80 // attempt.
     81 async function repeatUntilSuccessful(f) {
     82  let successful = false;
     83  do {
     84    try {
     85      // Delay one event loop.
     86      await new Promise(r => SimpleTest.executeSoon(r));
     87      await f();
     88      successful = true;
     89    } catch (error) {
     90      info(`repeatUntilSuccessful: error ${error}.`);
     91    }
     92  } while(!successful);
     93 }
     94 
     95 async function ensureOutOfFullscreen() {
     96  // Repeatedly call exitFullscreen until we get out.
     97  await repeatUntilSuccessful(async () => {
     98    if (document.fullscreenElement) {
     99      await document.exitFullscreen();
    100    }
    101    if (document.fullscreenElement) {
    102      throw new Error("still in fullscreen");
    103    }
    104  });
    105 }
    106 
    107 async function rapidCycleAwaitEvents() {
    108  const receiveOneFullscreenchange = () => {
    109    return new Promise(resolve => {
    110      document.addEventListener("fullscreenchange", resolve, { once: true });
    111    });
    112  };
    113 
    114  let gotError = false;
    115  for (let cycle = 0; cycle < CYCLE_COUNT; cycle++) {
    116    info(`Event cycle ${cycle} request fullscreen.`);
    117    const enterPromise = receiveOneFullscreenchange();
    118    document.documentElement.requestFullscreen();
    119    await Promise.race([enterPromise, rejectAfterTooLong()]).catch(error => {
    120      ok(false, `Event cycle ${cycle} requestFullscreen errored with ${error}.`);
    121      gotError = true;
    122    });
    123    if (gotError) {
    124      break;
    125    }
    126 
    127    info(`Event cycle ${cycle} exit fullscreen.`);
    128    const exitPromise = receiveOneFullscreenchange();
    129    document.exitFullscreen();
    130    await Promise.race([exitPromise, rejectAfterTooLong()]).catch(error => {
    131      ok(false, `Event cycle ${cycle} exitFullscreen errored with ${error}.`);
    132      gotError = true;
    133    });
    134    if (gotError) {
    135      break;
    136    }
    137  }
    138 }
    139 
    140 async function rapidCycleAwaitPromises() {
    141  let gotError = false;
    142  for (let cycle = 0; cycle < CYCLE_COUNT; cycle++) {
    143    info(`Promise cycle ${cycle} request fullscreen.`);
    144    const enterPromise = document.documentElement.requestFullscreen();
    145    await Promise.race([enterPromise, rejectAfterTooLong()]).catch(error => {
    146      ok(false, `Promise cycle ${cycle} requestFullscreen errored with ${error}.`);
    147      gotError = true;
    148    });
    149    if (gotError) {
    150      break;
    151    }
    152 
    153    info(`Promise cycle ${cycle} exit fullscreen.`);
    154    const exitPromise = document.exitFullscreen();
    155    await Promise.race([exitPromise, rejectAfterTooLong()]).catch(error => {
    156      ok(false, `Promise cycle ${cycle} exitFullscreen errored with ${error}.`);
    157      gotError = true;
    158    });
    159    if (gotError) {
    160      break;
    161    }
    162  }
    163 }
    164 
    165 </script>
    166 </body>
    167 </html>