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>