tor-browser

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

blob-composite-blob-reads.any.js (5756B)


      1 // META: title=IDB-backed composite blobs maintain coherency
      2 // META: script=resources/support-promises.js
      3 // META: timeout=long
      4 'use strict';
      5 
      6 // This test file is intended to help validate browser handling of complex blob
      7 // scenarios where one or more levels of multipart blobs are used and varying
      8 // IPC serialization strategies may be used depending on various complexity
      9 // heuristics.
     10 //
     11 // A variety of approaches of reading the blob's contents are attempted for
     12 // completeness:
     13 // - `fetch-blob-url`: fetch of a URL created via URL.createObjectURL
     14 //   - Note that this is likely to involve multi-process behavior in a way that
     15 //     the next 2 currently will not unless their Blobs are round-tripped
     16 //     through a MessagePort.
     17 // - `file-reader`: FileReader
     18 // - `direct`: Blob.prototype.arrayBuffer()
     19 
     20 function composite_blob_test({ blobCount, blobSize, name }) {
     21  // NOTE: In order to reduce the runtime of this test and due to the similarity
     22  // of the "file-reader" mechanism to the "direct", "file-reader" is commented
     23  // out, but if you are investigating failures detected by this test, you may
     24  // want to uncomment it.
     25  for (const mode of ["fetch-blob-url", /*"file-reader",*/ "direct"]) {
     26    promise_test(async testCase => {
     27      const key = "the-blobs";
     28      let memBlobs = [];
     29      for (let iBlob = 0; iBlob < blobCount; iBlob++) {
     30        memBlobs.push(new Blob([make_arraybuffer_contents(iBlob, blobSize)]));
     31      }
     32 
     33      const db = await createDatabase(testCase, db => {
     34        db.createObjectStore("blobs");
     35      });
     36 
     37      const write_tx = db.transaction("blobs", "readwrite");
     38      let store = write_tx.objectStore("blobs");
     39      store.put(memBlobs, key);
     40      // Make the blobs eligible for GC which is most realistic and most likely
     41      // to cause problems.
     42      memBlobs = null;
     43 
     44      await promiseForTransaction(testCase, write_tx);
     45 
     46      const read_tx = db.transaction("blobs", "readonly");
     47      store = read_tx.objectStore("blobs");
     48      const read_req = store.get(key);
     49 
     50      await promiseForTransaction(testCase, read_tx);
     51 
     52      const diskBlobs = read_req.result;
     53      const compositeBlob = new Blob(diskBlobs);
     54 
     55      if (mode === "fetch-blob-url") {
     56        const blobUrl = URL.createObjectURL(compositeBlob);
     57        let urlResp = await fetch(blobUrl);
     58        let urlFetchArrayBuffer = await urlResp.arrayBuffer();
     59        urlResp = null;
     60 
     61        URL.revokeObjectURL(blobUrl);
     62        validate_arraybuffer_contents("fetched URL", urlFetchArrayBuffer, blobCount, blobSize);
     63        urlFetchArrayBuffer = null;
     64 
     65      } else if (mode === "file-reader") {
     66        let reader = new FileReader();
     67        let readerPromise = new Promise(resolve => {
     68          reader.onload = () => {
     69            resolve(reader.result);
     70          }
     71        })
     72        reader.readAsArrayBuffer(compositeBlob);
     73 
     74        let readArrayBuffer = await readerPromise;
     75        readerPromise = null;
     76        reader = null;
     77 
     78        validate_arraybuffer_contents("FileReader", readArrayBuffer, blobCount, blobSize);
     79        readArrayBuffer = null;
     80      } else if (mode === "direct") {
     81        let directArrayBuffer = await compositeBlob.arrayBuffer();
     82        validate_arraybuffer_contents("arrayBuffer", directArrayBuffer, blobCount, blobSize);
     83      }
     84    }, `Composite Blob Handling: ${name}: ${mode}`);
     85  }
     86 }
     87 
     88 // Create an ArrayBuffer whose even bytes are the index identifier and whose
     89 // odd bytes are a sequence incremented by 3 (wrapping at 256) so that
     90 // discontinuities at power-of-2 boundaries are more detectable.
     91 function make_arraybuffer_contents(index, size) {
     92  const arr = new Uint8Array(size);
     93  for (let i = 0, counter = 0; i < size; i += 2, counter = (counter + 3) % 256) {
     94    arr[i] = index;
     95    arr[i + 1] = counter;
     96  }
     97  return arr.buffer;
     98 }
     99 
    100 function validate_arraybuffer_contents(source, buffer, blobCount, blobSize) {
    101  // Accumulate a list of problems we perceive so we can report what seems to
    102  // have happened all at once.
    103  const problems = [];
    104 
    105  const arr = new Uint8Array(buffer);
    106 
    107  const expectedLength = blobCount * blobSize;
    108  const actualCount = arr.length / blobSize;
    109  if (arr.length !== expectedLength) {
    110    problems.push(`ArrayBuffer only holds ${actualCount} blobs' worth instead of ${blobCount}.`);
    111    problems.push(`Actual ArrayBuffer is ${arr.length} bytes but expected ${expectedLength}`);
    112  }
    113 
    114  const counterBlobStep = (blobSize / 2 * 3) % 256;
    115  let expectedBlob = 0;
    116  let blobSeenSoFar = 0;
    117  let expectedCounter = 0;
    118  let counterDrift = 0;
    119  for (let i = 0; i < arr.length; i += 2) {
    120    if (arr[i] !== expectedBlob || blobSeenSoFar >= blobSize) {
    121      if (blobSeenSoFar !== blobSize) {
    122        problems.push(`Truncated blob ${expectedBlob} after ${blobSeenSoFar} bytes.`);
    123      } else {
    124        expectedBlob++;
    125      }
    126      if (expectedBlob !== arr[i]) {
    127        problems.push(`Expected blob ${expectedBlob} but found ${arr[i]}, compensating.`);
    128        expectedBlob = arr[i];
    129      }
    130      blobSeenSoFar = 0;
    131      expectedCounter = (expectedBlob * counterBlobStep) % 256;
    132      counterDrift = 0;
    133    }
    134 
    135    if (arr[i + 1] !== (expectedCounter + counterDrift) % 256) {
    136      const newDrift = expectedCounter - arr[i + 1];
    137      problems.push(`In blob ${expectedBlob} at ${blobSeenSoFar + 1} bytes in, counter drift now ${newDrift} was ${counterDrift}`);
    138      counterDrift = newDrift;
    139    }
    140 
    141    blobSeenSoFar += 2;
    142    expectedCounter = (expectedCounter + 3) % 256;
    143  }
    144 
    145  if (problems.length) {
    146    assert_true(false, `${source} blob payload problem: ${problems.join("\n")}`);
    147  } else {
    148    assert_true(true, `${source} blob payloads validated.`);
    149  }
    150 }
    151 
    152 composite_blob_test({
    153  blobCount: 16,
    154  blobSize: 256 * 1024,
    155  name: "Many blobs",
    156 });