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