tor-browser

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

idb-filelist-serialization.https.html (6068B)


      1 <!--
      2  Any copyright is dedicated to the Public Domain.
      3  http://creativecommons.org/publicdomain/zero/1.0/
      4 -->
      5 <html>
      6 <head>
      7  <meta charset="utf-8">
      8  <meta name="timeout" content="long">
      9  <script src="/resources/testharness.js"></script>
     10  <script src="/resources/testharnessreport.js"></script>
     11 </head>
     12 <body>
     13 <script>
     14 
     15 var fileCounter = 0;
     16 var dbCounter = 0;
     17 
     18 const fileListSize = 3;
     19 const elementSize = 8196;
     20 
     21 /**
     22 * Acknowledgement:
     23 * This test takes inspiration from IndexedDB/structured-clone.any.js
     24 * but the focus is on the variations of the filelist serialization.
     25 */
     26 function addCloneTest(testName, orig, verifyFunc) {
     27  promise_test(async t => {
     28    const requestToFinish = req => {
     29      return new Promise((resolve, reject) => {
     30        req.onerror = () => {
     31          reject(req.error);
     32        };
     33        req.onblocked = () => {
     34          reject("Unexpected block");
     35        };
     36        req.onupgradeneeded = () => {
     37          reject("Unexpected upgrade");
     38        };
     39        req.onsuccess = ev => {
     40          resolve(ev.target.result);
     41        };
     42      });
     43    };
     44 
     45    const txEvents = [
     46      "abort",
     47      "complete",
     48      "error",
     49    ];
     50 
     51    const dbName = "db_" + dbCounter;
     52    ++dbCounter;
     53 
     54    const performRequest = async (query) => {
     55      const db = await new Promise((resolve, reject) => {
     56        const openReq = indexedDB.open(dbName, 1);
     57        openReq.onerror = () => {
     58          reject(openReq.error);
     59        };
     60        openReq.onupgradeneeded = ev => {
     61          const dbObj = ev.target.result;
     62          const store = dbObj.createObjectStore("store");
     63          // This index is not used, but evaluating key path on each put()
     64          // call will exercise (de)serialization.
     65          store.createIndex("index", "dummyKeyPath");
     66        };
     67        openReq.onsuccess = () => {
     68          resolve(openReq.result);
     69        };
     70      });
     71 
     72      t.add_cleanup(() => {
     73        if (db) {
     74          db.close();
     75          indexedDB.deleteDatabase(db.name);
     76        }
     77      });
     78 
     79      let result = undefined;
     80      try {
     81        const tx = db.transaction("store", "readwrite");
     82        const store = tx.objectStore("store");
     83        result = await requestToFinish(query(store));
     84        await new EventWatcher(t, tx, txEvents).wait_for("complete");
     85      } finally {
     86        db.close();
     87      }
     88 
     89      return result;
     90    };
     91 
     92    await performRequest(store => store.put(orig, "key"));
     93    const clone = await performRequest(store => store.get("key"));
     94 
     95    assert_not_equals(orig, clone);
     96    await verifyFunc(orig, clone);
     97  }, testName);
     98 }
     99 
    100 function makeFileList(dataGenerators) {
    101  const fileOpts = { type: "text/plain" };
    102  const dataTransfer = new DataTransfer();
    103  dataGenerators.forEach((generator, i) => {
    104    const file = new File(generator(i), "test_" + fileCounter, fileOpts);
    105    dataTransfer.items.add(file);
    106    ++fileCounter;
    107  });
    108 
    109  return dataTransfer.files;
    110 }
    111 
    112 const compareCloneToOrig = async (orig, clone) => {
    113  assert_equals(orig.length, clone.length);
    114  assert_equals(orig.length, fileListSize);
    115  for (let i = 0; i < orig.length; ++i) {
    116    const origFile = orig.item(i);
    117    const cloneFile = clone.item(i);
    118    assert_equals(origFile.name, cloneFile.name);
    119    assert_equals(await origFile.text(), await cloneFile.text());
    120  }
    121 };
    122 
    123 const compareObjects = async (orig, clone) => {
    124  assert_true("value" in orig);
    125  assert_true("value" in clone);
    126 
    127  return await compareCloneToOrig(orig.value, clone.value);
    128 };
    129 
    130 const compareArrays = async (orig, clone) => {
    131  assert_equals(orig.length, 1);
    132  assert_equals(clone.length, 1);
    133 
    134  return await compareCloneToOrig(orig[0], clone[0]);
    135 };
    136 
    137 const randomLetters = n => {
    138  const chars = "abcd";
    139  const someLetter = () => chars[Math.floor(Math.random() * chars.length)];
    140  return Array(n).fill().map(someLetter).join("");
    141 };
    142 
    143 // FileList - exposed in Workers, but not constructable.
    144 if ("document" in self) {
    145  const addTestCases = (dataName, dataGenerator) => {
    146    const fileListStatic = makeFileList(
    147      Array(fileListSize).fill(dataGenerator)
    148    );
    149 
    150    addCloneTest(
    151      "Serialize filelist containing " + dataName,
    152      fileListStatic,
    153      compareCloneToOrig
    154    );
    155 
    156    addCloneTest(
    157      "Serialize object with filelist containing " + dataName,
    158      { value: fileListStatic },
    159      compareObjects
    160    );
    161 
    162    addCloneTest(
    163      "Serialize array with filelist containing " + dataName,
    164      [fileListStatic],
    165      compareArrays
    166    );
    167 
    168    const baseData = dataGenerator();
    169 
    170    // Currently it's legal for the same File to appear in a FileList
    171    // multiple times. This was the subject of some brief discussion
    172    // at TPAC 2024 and it's possible that as FileList moves entirely
    173    // into the HTML spec this may change.
    174    // In the meantime we want to make sure we support this case and
    175    // that IndexedDB's optimizations related to File-interning
    176    // don't break things, although that logic is tested more thoroughly
    177    // in test_file_filelist.html
    178    const fileListRepeated = makeFileList(
    179      Array(fileListSize).fill(() => {
    180        return baseData;
    181      })
    182    );
    183 
    184    addCloneTest(
    185      "Serialize filelist containing repeated " + dataName,
    186      fileListRepeated,
    187      compareCloneToOrig
    188    );
    189 
    190    addCloneTest(
    191      "Serialize object with filelist containing repeated " + dataName,
    192      { value: fileListRepeated },
    193      compareObjects
    194    );
    195 
    196    addCloneTest(
    197      "Serialize array with filelist containing repeated " + dataName,
    198      [fileListRepeated],
    199      compareArrays
    200    );
    201  };
    202 
    203  const genString = () => {
    204    return [randomLetters(elementSize)];
    205  };
    206 
    207  addTestCases("random string", genString);
    208 
    209  const genArray = () => {
    210    const array = new Uint32Array(elementSize);
    211    crypto.getRandomValues(array);
    212    return array;
    213  };
    214 
    215  addTestCases("random typed array", genArray);
    216 
    217  const genBlob = () => {
    218    const array = new Uint32Array(elementSize);
    219    crypto.getRandomValues(array);
    220    return [new Blob(array)];
    221  };
    222 
    223  addTestCases("random blob", genBlob);
    224 }
    225 
    226 </script>
    227 </body>
    228 </html>