tor-browser

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

local-fs-test-helpers.js (7862B)


      1 // This file defines a directory_test() function that can be used to define
      2 // tests that require a FileSystemDirectoryHandle. The implementation of that
      3 // function in this file will ask the user to select an empty directory and uses
      4 // that directory.
      5 //
      6 // Another implementation of this function exists in
      7 // fs/resources/sandboxed-fs-test-helpers.js, where that version uses the
      8 // sandboxed file system instead.
      9 
     10 function getFileSystemType() {
     11  return 'local';
     12 }
     13 
     14 const directory_promise = (async () => {
     15  await new Promise(resolve => {
     16    window.addEventListener('DOMContentLoaded', resolve);
     17  });
     18 
     19  // Small delay to give chrome's test automation a chance to actually install
     20  // itself.
     21  await new Promise(resolve => step_timeout(resolve, 100));
     22 
     23  await window.test_driver.bless(
     24      'show a file picker.<br />Please select an empty directory');
     25  const entries = await self.showDirectoryPicker();
     26  assert_true(entries instanceof FileSystemHandle);
     27  assert_true(entries instanceof FileSystemDirectoryHandle);
     28  for await (const entry of entries) {
     29    assert_unreached('Selected directory is not empty');
     30  }
     31  return entries;
     32 })();
     33 
     34 async function cleanupDirectory(dir, ignoreRejections) {
     35  // Get a snapshot of the entries.
     36  const entries = await Array.fromAsync(dir.values());
     37 
     38  // Call removeEntry on all of them.
     39  const remove_entry_promises = entries.map(
     40      entry =>
     41          dir.removeEntry(entry.name, {recursive: entry.kind === 'directory'}));
     42 
     43  // Wait for them all to resolve or reject.
     44  if (ignoreRejections) {
     45    await Promise.allSettled(remove_entry_promises);
     46  } else {
     47    await Promise.all(remove_entry_promises);
     48  }
     49 }
     50 
     51 function directory_test(func, description) {
     52  promise_test(async t => {
     53    const directory = await directory_promise;
     54 
     55    // To be extra resilient against bad tests, cleanup before every test.
     56    await cleanupDirectory(directory, /*ignoreRejections=*/ false);
     57 
     58    // Cleanup after every test.
     59    t.add_cleanup(async () => {
     60      // Ignore any rejections since other cleanup code may have deleted them
     61      // before we could.
     62      await cleanupDirectory(directory, /*ignoreRejections=*/ true);
     63    });
     64 
     65    await func(t, directory);
     66  }, description);
     67 }
     68 
     69 directory_test(async (t, dir) => {
     70  assert_equals(await dir.queryPermission({mode: 'read'}), 'granted');
     71 }, 'User succesfully selected an empty directory.');
     72 
     73 directory_test(async (t, dir) => {
     74  const status = await dir.queryPermission({mode: 'readwrite'});
     75  if (status == 'granted')
     76    return;
     77 
     78  await window.test_driver.bless('ask for write permission');
     79  assert_equals(await dir.requestPermission({mode: 'readwrite'}), 'granted');
     80 }, 'User granted write access.');
     81 
     82 const child_frame_js = (origin, frameFn, done) => `
     83  const importScript = ${importScript};
     84  await importScript("/html/cross-origin-embedder-policy/credentialless" +
     85                  "/resources/common.js");
     86  await importScript("/html/anonymous-iframe/resources/common.js");
     87  await importScript("/common/utils.js");
     88  await send("${done}", ${frameFn}("${origin}"));
     89 `;
     90 
     91 /**
     92 * Context identifiers for executor subframes of framed tests. Individual
     93 * contexts (or convenience context lists below) can be used to send JavaScript
     94 * for evaluation in each frame (see framed_test below).
     95 *
     96 * Note that within framed tests:
     97 *  - firstParty represents the top-level document.
     98 *  - thirdParty represents an embedded context (iframe).
     99 *  - ancestorBit contexts include a cross-site ancestor iframe.
    100 *  - anonymousFrame contexts are third-party anonymous iframe contexts.
    101 */
    102 const FRAME_CONTEXT = {
    103  firstParty: 0,
    104  thirdPartySameSite: 1,
    105  thirdPartySameSite_AncestorBit: 2,
    106  thirdPartyCrossSite: 3,
    107  anonymousFrameSameSite: 4,
    108  anonymousFrameSameSite_AncestorBit: 5,
    109  anonymousFrameCrossSite: 6,
    110 };
    111 
    112 // TODO(crbug.com/1322897): Add AncestorBit contexts.
    113 const sameSiteContexts = [
    114  FRAME_CONTEXT.firstParty,
    115  FRAME_CONTEXT.thirdPartySameSite,
    116  FRAME_CONTEXT.anonymousFrameSameSite,
    117 ];
    118 
    119 // TODO(crbug.com/1322897): Add AncestorBit contexts.
    120 const crossSiteContexts = [
    121  FRAME_CONTEXT.thirdPartyCrossSite,
    122  FRAME_CONTEXT.anonymousFrameCrossSite,
    123 ];
    124 
    125 // TODO(crbug.com/1322897): Add AncestorBit contexts.
    126 const childContexts = [
    127  FRAME_CONTEXT.thirdPartySameSite,
    128  FRAME_CONTEXT.thirdPartyCrossSite,
    129  FRAME_CONTEXT.anonymousFrameSameSite,
    130  FRAME_CONTEXT.anonymousFrameCrossSite,
    131 ];
    132 
    133 /**
    134 * Creates a promise test with same- & cross-site executor subframes.
    135 *
    136 * In addition to the standard testing object, the provided func will be called
    137 * with a sendTo function. sendTo expects:
    138 *   - contexts: an Iterable of FRAME_CONTEXT constants representing the
    139 *               frame(s) in which the provided script will be concurrently run.
    140 *   - js_gen: a function which should generate a script string when called
    141 *             with a string token. sendTo will wait until a "done" message
    142 *             is sent to this queue.
    143 */
    144 function framed_test(func, description) {
    145  const same_site_origin = get_host_info().HTTPS_ORIGIN;
    146  const cross_site_origin = get_host_info().HTTPS_NOTSAMESITE_ORIGIN;
    147  const frames = Object.values(FRAME_CONTEXT);
    148 
    149  promise_test(async (t) => {
    150    return new Promise(async (resolve, reject) => {
    151      try {
    152        // Set up handles to all third party frames.
    153        const handles = [
    154          null,                          // firstParty
    155          newIframe(same_site_origin),   // thirdPartySameSite
    156          null,                          // thirdPartySameSite_AncestorBit
    157          newIframe(cross_site_origin),  // thirdPartyCrossSite
    158          newIframeCredentialless(same_site_origin),  // anonymousFrameSameSite
    159          null,  // anonymousFrameSameSite_AncestorBit
    160          newIframeCredentialless(
    161              cross_site_origin),  // anonymousFrameCrossSite
    162        ];
    163        // Set up nested SameSite frames for ancestor bit contexts.
    164        const setUpQueue = token();
    165        send(newIframe(cross_site_origin),
    166          child_frame_js(same_site_origin, "newIframe", setUpQueue));
    167        handles[FRAME_CONTEXT.thirdPartySameSite_AncestorBit] =
    168          await receive(setUpQueue);
    169        send(
    170            newIframeCredentialless(cross_site_origin),
    171            child_frame_js(
    172                same_site_origin, 'newIframeCredentialless', setUpQueue));
    173        handles[FRAME_CONTEXT.anonymousFrameSameSite_AncestorBit] =
    174          await receive(setUpQueue);
    175 
    176        const sendTo = (contexts, js_generator) => {
    177          // Send to all contexts in parallel to minimize timeout concerns.
    178          return Promise.all(contexts.map(async (context) => {
    179            const queue = token();
    180            const js_string = js_generator(queue, context);
    181            switch (context) {
    182              case FRAME_CONTEXT.firstParty:
    183                // Code is executed directly in this frame via eval() rather
    184                // than in a new context to avoid differences in API access.
    185                eval(`(async () => {${js_string}})()`);
    186                break;
    187              case FRAME_CONTEXT.thirdPartySameSite:
    188              case FRAME_CONTEXT.thirdPartyCrossSite:
    189              case FRAME_CONTEXT.anonymousFrameSameSite:
    190              case FRAME_CONTEXT.anonymousFrameCrossSite:
    191              case FRAME_CONTEXT.thirdPartySameSite_AncestorBit:
    192              case FRAME_CONTEXT.anonymousFrameSameSite_AncestorBit:
    193                send(handles[context], js_string);
    194                break;
    195              default:
    196                reject(`Cannot execute in context: ${context}`);
    197            }
    198            if (await receive(queue) != "done") {
    199              reject(`Script failed in frame ${context}: ${js_string}`);
    200            }
    201          }));
    202        };
    203 
    204        await func(t, sendTo);
    205      } catch (e) {
    206        reject(e);
    207      }
    208      resolve();
    209    });
    210  }, description);
    211 }