Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /dom/indexedDB/idb-filelist-serialization.https.html - WPT Dashboard Interop Dashboard
<!--
Any copyright is dedicated to the Public Domain.
-->
<html>
<head>
<meta charset="utf-8">
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
var fileCounter = 0;
var dbCounter = 0;
const fileListSize = 3;
const elementSize = 8196;
/**
* Acknowledgement:
* This test takes inspiration from IndexedDB/structured-clone.any.js
* but the focus is on the variations of the filelist serialization.
*/
function addCloneTest(testName, orig, verifyFunc) {
promise_test(async t => {
const requestToFinish = req => {
return new Promise((resolve, reject) => {
req.onerror = () => {
reject(req.error);
};
req.onblocked = () => {
reject("Unexpected block");
};
req.onupgradeneeded = () => {
reject("Unexpected upgrade");
};
req.onsuccess = ev => {
resolve(ev.target.result);
};
});
};
const txEvents = [
"abort",
"complete",
"error",
];
const dbName = "db_" + dbCounter;
++dbCounter;
const performRequest = async (query) => {
const db = await new Promise((resolve, reject) => {
const openReq = indexedDB.open(dbName, 1);
openReq.onerror = () => {
reject(openReq.error);
};
openReq.onupgradeneeded = ev => {
const dbObj = ev.target.result;
const store = dbObj.createObjectStore("store");
// This index is not used, but evaluating key path on each put()
// call will exercise (de)serialization.
store.createIndex("index", "dummyKeyPath");
};
openReq.onsuccess = () => {
resolve(openReq.result);
};
});
t.add_cleanup(() => {
if (db) {
db.close();
indexedDB.deleteDatabase(db.name);
}
});
let result = undefined;
try {
const tx = db.transaction("store", "readwrite");
const store = tx.objectStore("store");
result = await requestToFinish(query(store));
await new EventWatcher(t, tx, txEvents).wait_for("complete");
} finally {
db.close();
}
return result;
};
await performRequest(store => store.put(orig, "key"));
const clone = await performRequest(store => store.get("key"));
assert_not_equals(orig, clone);
await verifyFunc(orig, clone);
}, testName);
}
function makeFileList(dataGenerators) {
const fileOpts = { type: "text/plain" };
const dataTransfer = new DataTransfer();
dataGenerators.forEach((generator, i) => {
const file = new File(generator(i), "test_" + fileCounter, fileOpts);
dataTransfer.items.add(file);
++fileCounter;
});
return dataTransfer.files;
}
const compareCloneToOrig = async (orig, clone) => {
assert_equals(orig.length, clone.length);
assert_equals(orig.length, fileListSize);
for (let i = 0; i < orig.length; ++i) {
const origFile = orig.item(i);
const cloneFile = clone.item(i);
assert_equals(origFile.name, cloneFile.name);
assert_equals(await origFile.text(), await cloneFile.text());
}
};
const compareObjects = async (orig, clone) => {
assert_true("value" in orig);
assert_true("value" in clone);
return await compareCloneToOrig(orig.value, clone.value);
};
const compareArrays = async (orig, clone) => {
assert_equals(orig.length, 1);
assert_equals(clone.length, 1);
return await compareCloneToOrig(orig[0], clone[0]);
};
const randomLetters = n => {
const chars = "abcd";
const someLetter = () => chars[Math.floor(Math.random() * chars.length)];
return Array(n).fill().map(someLetter).join("");
};
// FileList - exposed in Workers, but not constructable.
if ("document" in self) {
const addTestCases = (dataName, dataGenerator) => {
const fileListStatic = makeFileList(
Array(fileListSize).fill(dataGenerator)
);
addCloneTest(
"Serialize filelist containing " + dataName,
fileListStatic,
compareCloneToOrig
);
addCloneTest(
"Serialize object with filelist containing " + dataName,
{ value: fileListStatic },
compareObjects
);
addCloneTest(
"Serialize array with filelist containing " + dataName,
[fileListStatic],
compareArrays
);
const baseData = dataGenerator();
// Currently it's legal for the same File to appear in a FileList
// multiple times. This was the subject of some brief discussion
// at TPAC 2024 and it's possible that as FileList moves entirely
// into the HTML spec this may change.
// In the meantime we want to make sure we support this case and
// that IndexedDB's optimizations related to File-interning
// don't break things, although that logic is tested more thoroughly
// in test_file_filelist.html
const fileListRepeated = makeFileList(
Array(fileListSize).fill(() => {
return baseData;
})
);
addCloneTest(
"Serialize filelist containing repeated " + dataName,
fileListRepeated,
compareCloneToOrig
);
addCloneTest(
"Serialize object with filelist containing repeated " + dataName,
{ value: fileListRepeated },
compareObjects
);
addCloneTest(
"Serialize array with filelist containing repeated " + dataName,
[fileListRepeated],
compareArrays
);
};
const genString = () => {
return [randomLetters(elementSize)];
};
addTestCases("random string", genString);
const genArray = () => {
const array = new Uint32Array(elementSize);
crypto.getRandomValues(array);
return array;
};
addTestCases("random typed array", genArray);
const genBlob = () => {
const array = new Uint32Array(elementSize);
crypto.getRandomValues(array);
return [new Blob(array)];
};
addTestCases("random blob", genBlob);
}
</script>
</body>
</html>