key-conversion-exceptions.any.js (9058B)
1 // META: global=window,worker 2 // META: title=IndexedDB: Exceptions thrown during key conversion 3 // META: script=resources/support.js 4 // META: timeout=long 5 6 'use strict'; 7 8 // Convenience function for tests that only need to run code in onupgradeneeded. 9 function indexeddb_upgrade_only_test(upgrade_callback, description) { 10 indexeddb_test(upgrade_callback, t => t.done(), description); 11 } 12 13 // Key that throws during conversion. 14 function throwing_key(name) { 15 const throws = []; 16 throws.length = 1; 17 const err = new Error('throwing from getter'); 18 err.name = name; 19 Object.defineProperty(throws, '0', { 20 get: function() { 21 throw err; 22 }, 23 enumerable: true, 24 }); 25 return [throws, err]; 26 } 27 28 const valid_key = []; 29 const invalid_key = {}; 30 31 // Calls method on receiver with the specified number of args (default 1) 32 // and asserts that the method fails appropriately (rethrowing if 33 // conversion throws, or DataError if not a valid key), and that 34 // the first argument is fully processed before the second argument 35 // (if appropriate). 36 function check_method(receiver, method, args) { 37 args = args || 1; 38 if (args < 2) { 39 const [key, err] = throwing_key('getter'); 40 assert_throws_exactly(err, () => { 41 receiver[method](key); 42 }, 'key conversion with throwing getter should rethrow'); 43 44 assert_throws_dom('DataError', () => { 45 receiver[method](invalid_key); 46 }, 'key conversion with invalid key should throw DataError'); 47 } else { 48 const [key1, err1] = throwing_key('getter 1'); 49 const [key2, err2] = throwing_key('getter 2'); 50 assert_throws_exactly(err1, () => { 51 receiver[method](key1, key2); 52 }, 'first key conversion with throwing getter should rethrow'); 53 54 assert_throws_dom('DataError', () => { 55 receiver[method](invalid_key, key2); 56 }, 'first key conversion with invalid key should throw DataError'); 57 58 assert_throws_exactly(err2, () => { 59 receiver[method](valid_key, key2); 60 }, 'second key conversion with throwing getter should rethrow'); 61 62 assert_throws_dom('DataError', () => { 63 receiver[method](valid_key, invalid_key); 64 }, 'second key conversion with invalid key should throw DataError'); 65 } 66 } 67 68 // Verifies that invalid keys throw when used with the `IDBGetAllOptions` 69 // dictionary. `getAllRecords()` added `IDBGetAllOptions`, which `getAll()` and 70 // `getAllKeys()` also support. 71 function check_method_with_get_all_options(receiver, method) { 72 assert_throws_dom('DataError', () => { 73 receiver[method]({query: invalid_key}); 74 }, 'options query key conversion with invalid key should throw DataError'); 75 76 const [key, err] = throwing_key('getter'); 77 assert_throws_exactly(err, () => { 78 receiver[method]({query: key}); 79 }, 'options query key conversion with throwing getter should rethrow'); 80 81 // Verify `getAll()` and `getAllKeys()` throw when given an invalid key range 82 // directly without the options dictionary. `getAllRecords()` only supports 83 // the options dictionary. 84 if (method !== 'getAllRecords') { 85 assert_throws_exactly(err, () => { 86 receiver[method](key); 87 }, 'query key conversion with throwing getter should rethrow'); 88 } 89 } 90 91 // Static key comparison utility on IDBFactory. 92 test( 93 t => check_method(indexedDB, 'cmp', 2), 94 'IDBFactory cmp() static with throwing/invalid keys'); 95 96 // Continue methods on IDBCursor. 97 indexeddb_upgrade_only_test((t, db) => { 98 const store = db.createObjectStore('store'); 99 store.put('a', 1).onerror = t.unreached_func('put should succeed'); 100 101 const request = store.openCursor(); 102 request.onerror = t.unreached_func('openCursor should succeed'); 103 request.onsuccess = t.step_func(() => { 104 const cursor = request.result; 105 assert_not_equals(cursor, null, 'cursor should find a value'); 106 check_method(cursor, 'continue'); 107 }); 108 }, 'IDBCursor continue() method with throwing/invalid keys'); 109 110 indexeddb_upgrade_only_test((t, db) => { 111 const store = db.createObjectStore('store'); 112 const index = store.createIndex('index', 'prop'); 113 store.put({prop: 'a'}, 1).onerror = t.unreached_func('put should succeed'); 114 115 const request = index.openCursor(); 116 request.onerror = t.unreached_func('openCursor should succeed'); 117 request.onsuccess = t.step_func(() => { 118 const cursor = request.result; 119 assert_not_equals(cursor, null, 'cursor should find a value'); 120 121 check_method(cursor, 'continuePrimaryKey', 2); 122 }); 123 }, null, 'IDBCursor continuePrimaryKey() method with throwing/invalid keys'); 124 125 // Mutation methods on IDBCursor. 126 indexeddb_upgrade_only_test((t, db) => { 127 const store = db.createObjectStore('store', {keyPath: 'prop'}); 128 store.put({prop: 1}).onerror = t.unreached_func('put should succeed'); 129 130 const request = store.openCursor(); 131 request.onerror = t.unreached_func('openCursor should succeed'); 132 request.onsuccess = t.step_func(() => { 133 const cursor = request.result; 134 assert_not_equals(cursor, null, 'cursor should find a value'); 135 136 const value = {}; 137 let err; 138 [value.prop, err] = throwing_key('getter'); 139 assert_throws_exactly(err, () => { 140 cursor.update(value); 141 }, 'throwing getter should rethrow during clone'); 142 143 // Throwing from the getter during key conversion is 144 // not possible since (1) a clone is used, (2) only own 145 // properties are cloned, and (3) only own properties 146 // are used for key path evaluation. 147 148 value.prop = invalid_key; 149 assert_throws_dom('DataError', () => { 150 cursor.update(value); 151 }, 'key conversion with invalid key should throw DataError'); 152 }); 153 }, 'IDBCursor update() method with throwing/invalid keys'); 154 155 // Static constructors on IDBKeyRange 156 ['only', 'lowerBound', 'upperBound'].forEach((method) => { 157 test( 158 t => check_method(IDBKeyRange, method), 159 'IDBKeyRange ' + method + '() static with throwing/invalid keys'); 160 }); 161 162 test( 163 t => check_method(IDBKeyRange, 'bound', 2), 164 'IDBKeyRange bound() static with throwing/invalid keys'); 165 166 // Insertion methods on IDBObjectStore. 167 ['add', 'put'].forEach((method) => { 168 indexeddb_upgrade_only_test((t, db) => { 169 const out_of_line = db.createObjectStore('out-of-line keys'); 170 const in_line = db.createObjectStore('in-line keys', {keyPath: 'prop'}); 171 let [key, err] = throwing_key('getter'); 172 assert_throws_exactly(err, () => { 173 out_of_line[method]('value', key); 174 }, 'key conversion with throwing getter should rethrow'); 175 176 assert_throws_dom('DataError', () => { 177 out_of_line[method]('value', invalid_key); 178 }, 'key conversion with invalid key should throw DataError'); 179 180 const value = {}; 181 [value.prop, err] = throwing_key('getter'); 182 assert_throws_exactly(err, () => { 183 in_line[method](value); 184 }, 'throwing getter should rethrow during clone'); 185 186 // Throwing from the getter during key conversion is 187 // not possible since (1) a clone is used, (2) only own 188 // properties are cloned, and (3) only own properties 189 // are used for key path evaluation. 190 191 value.prop = invalid_key; 192 assert_throws_dom('DataError', () => { 193 in_line[method](value); 194 }, 'key conversion with invalid key should throw DataError'); 195 }, `IDBObjectStore ${method}() method with throwing/invalid keys`); 196 }); 197 198 // Generic (key-or-key-path) methods on IDBObjectStore. 199 ['delete', 200 'get', 201 'getKey', 202 'count', 203 'openCursor', 204 'openKeyCursor', 205 ].forEach(method => { 206 indexeddb_upgrade_only_test((t, db) => { 207 const store = db.createObjectStore('store'); 208 209 check_method(store, method); 210 }, `IDBObjectStore ${method}() method with throwing/invalid keys`); 211 }); 212 213 // Generic (key-or-key-path) methods on IDBIndex. 214 ['get', 215 'getKey', 216 'count', 217 'openCursor', 218 'openKeyCursor', 219 ].forEach((method) => { 220 indexeddb_upgrade_only_test((t, db) => { 221 const store = db.createObjectStore('store'); 222 const index = store.createIndex('index', 'keyPath'); 223 224 check_method(index, method); 225 }, `IDBIndex ${method}() method with throwing/invalid keys`); 226 }); 227 228 // Verify methods that take `IDBGetAllOptions` on `IDBObjectStore`. 229 ['getAll', 230 'getAllKeys', 231 'getAllRecords', 232 ].forEach(method => { 233 indexeddb_upgrade_only_test((t, db) => { 234 const store = db.createObjectStore('store'); 235 if ('getAllRecords' in store) { 236 check_method_with_get_all_options(store, method); 237 } else if (method !== 'getAllRecords') { 238 // This browser does not support `getAllRecords()` or the 239 // `IDBGetAllOptions` dictionary. 240 check_method(store, method); 241 } 242 }, `IDBObjectStore ${method}() method with throwing/invalid keys`); 243 }); 244 245 // Verify methods that take `IDBGetAllOptions` on `IDBIndex`. 246 ['getAll', 'getAllKeys', 'getAllRecords'].forEach(method => { 247 indexeddb_upgrade_only_test((t, db) => { 248 const store = db.createObjectStore('store'); 249 const index = store.createIndex('index', 'keyPath'); 250 if ('getAllRecords' in index) { 251 check_method_with_get_all_options(index, method); 252 } else if (method !== 'getAllRecords') { 253 check_method(store, method); 254 } 255 }, `IDBIndex ${method}() method with throwing/invalid keys`); 256 });