mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-27 02:05:07 +02:00
LibWeb/IndexedDB: Prevent copying and moving of RequestList
RequestList cannot be copied or moved, because m_pending_request_queue contains lambdas that store pointers to the original RequestList and completion steps that we don't have a reference to. Fixes a bunch of WPT regressions and imports the ones that work.
This commit is contained in:
committed by
Jelle Raaijmakers
parent
4ede2cdf18
commit
85e8d2ba38
Notes:
github-actions[bot]
2025-10-23 12:44:02 +00:00
Author: https://github.com/Lubrsi Commit: https://github.com/LadybirdBrowser/ladybird/commit/85e8d2ba38b Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6429 Reviewed-by: https://github.com/gmta ✅
@@ -0,0 +1,256 @@
|
||||
// META: global=window,worker
|
||||
// META: title=IndexedDB: Exceptions thrown during key conversion
|
||||
// META: script=resources/support.js
|
||||
// META: timeout=long
|
||||
|
||||
'use strict';
|
||||
|
||||
// Convenience function for tests that only need to run code in onupgradeneeded.
|
||||
function indexeddb_upgrade_only_test(upgrade_callback, description) {
|
||||
indexeddb_test(upgrade_callback, t => t.done(), description);
|
||||
}
|
||||
|
||||
// Key that throws during conversion.
|
||||
function throwing_key(name) {
|
||||
const throws = [];
|
||||
throws.length = 1;
|
||||
const err = new Error('throwing from getter');
|
||||
err.name = name;
|
||||
Object.defineProperty(throws, '0', {
|
||||
get: function() {
|
||||
throw err;
|
||||
},
|
||||
enumerable: true,
|
||||
});
|
||||
return [throws, err];
|
||||
}
|
||||
|
||||
const valid_key = [];
|
||||
const invalid_key = {};
|
||||
|
||||
// Calls method on receiver with the specified number of args (default 1)
|
||||
// and asserts that the method fails appropriately (rethrowing if
|
||||
// conversion throws, or DataError if not a valid key), and that
|
||||
// the first argument is fully processed before the second argument
|
||||
// (if appropriate).
|
||||
function check_method(receiver, method, args) {
|
||||
args = args || 1;
|
||||
if (args < 2) {
|
||||
const [key, err] = throwing_key('getter');
|
||||
assert_throws_exactly(err, () => {
|
||||
receiver[method](key);
|
||||
}, 'key conversion with throwing getter should rethrow');
|
||||
|
||||
assert_throws_dom('DataError', () => {
|
||||
receiver[method](invalid_key);
|
||||
}, 'key conversion with invalid key should throw DataError');
|
||||
} else {
|
||||
const [key1, err1] = throwing_key('getter 1');
|
||||
const [key2, err2] = throwing_key('getter 2');
|
||||
assert_throws_exactly(err1, () => {
|
||||
receiver[method](key1, key2);
|
||||
}, 'first key conversion with throwing getter should rethrow');
|
||||
|
||||
assert_throws_dom('DataError', () => {
|
||||
receiver[method](invalid_key, key2);
|
||||
}, 'first key conversion with invalid key should throw DataError');
|
||||
|
||||
assert_throws_exactly(err2, () => {
|
||||
receiver[method](valid_key, key2);
|
||||
}, 'second key conversion with throwing getter should rethrow');
|
||||
|
||||
assert_throws_dom('DataError', () => {
|
||||
receiver[method](valid_key, invalid_key);
|
||||
}, 'second key conversion with invalid key should throw DataError');
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that invalid keys throw when used with the `IDBGetAllOptions`
|
||||
// dictionary. `getAllRecords()` added `IDBGetAllOptions`, which `getAll()` and
|
||||
// `getAllKeys()` also support.
|
||||
function check_method_with_get_all_options(receiver, method) {
|
||||
assert_throws_dom('DataError', () => {
|
||||
receiver[method]({query: invalid_key});
|
||||
}, 'options query key conversion with invalid key should throw DataError');
|
||||
|
||||
const [key, err] = throwing_key('getter');
|
||||
assert_throws_exactly(err, () => {
|
||||
receiver[method]({query: key});
|
||||
}, 'options query key conversion with throwing getter should rethrow');
|
||||
|
||||
// Verify `getAll()` and `getAllKeys()` throw when given an invalid key range
|
||||
// directly without the options dictionary. `getAllRecords()` only supports
|
||||
// the options dictionary.
|
||||
if (method !== 'getAllRecords') {
|
||||
assert_throws_exactly(err, () => {
|
||||
receiver[method](key);
|
||||
}, 'query key conversion with throwing getter should rethrow');
|
||||
}
|
||||
}
|
||||
|
||||
// Static key comparison utility on IDBFactory.
|
||||
test(
|
||||
t => check_method(indexedDB, 'cmp', 2),
|
||||
'IDBFactory cmp() static with throwing/invalid keys');
|
||||
|
||||
// Continue methods on IDBCursor.
|
||||
indexeddb_upgrade_only_test((t, db) => {
|
||||
const store = db.createObjectStore('store');
|
||||
store.put('a', 1).onerror = t.unreached_func('put should succeed');
|
||||
|
||||
const request = store.openCursor();
|
||||
request.onerror = t.unreached_func('openCursor should succeed');
|
||||
request.onsuccess = t.step_func(() => {
|
||||
const cursor = request.result;
|
||||
assert_not_equals(cursor, null, 'cursor should find a value');
|
||||
check_method(cursor, 'continue');
|
||||
});
|
||||
}, 'IDBCursor continue() method with throwing/invalid keys');
|
||||
|
||||
indexeddb_upgrade_only_test((t, db) => {
|
||||
const store = db.createObjectStore('store');
|
||||
const index = store.createIndex('index', 'prop');
|
||||
store.put({prop: 'a'}, 1).onerror = t.unreached_func('put should succeed');
|
||||
|
||||
const request = index.openCursor();
|
||||
request.onerror = t.unreached_func('openCursor should succeed');
|
||||
request.onsuccess = t.step_func(() => {
|
||||
const cursor = request.result;
|
||||
assert_not_equals(cursor, null, 'cursor should find a value');
|
||||
|
||||
check_method(cursor, 'continuePrimaryKey', 2);
|
||||
});
|
||||
}, null, 'IDBCursor continuePrimaryKey() method with throwing/invalid keys');
|
||||
|
||||
// Mutation methods on IDBCursor.
|
||||
indexeddb_upgrade_only_test((t, db) => {
|
||||
const store = db.createObjectStore('store', {keyPath: 'prop'});
|
||||
store.put({prop: 1}).onerror = t.unreached_func('put should succeed');
|
||||
|
||||
const request = store.openCursor();
|
||||
request.onerror = t.unreached_func('openCursor should succeed');
|
||||
request.onsuccess = t.step_func(() => {
|
||||
const cursor = request.result;
|
||||
assert_not_equals(cursor, null, 'cursor should find a value');
|
||||
|
||||
const value = {};
|
||||
let err;
|
||||
[value.prop, err] = throwing_key('getter');
|
||||
assert_throws_exactly(err, () => {
|
||||
cursor.update(value);
|
||||
}, 'throwing getter should rethrow during clone');
|
||||
|
||||
// Throwing from the getter during key conversion is
|
||||
// not possible since (1) a clone is used, (2) only own
|
||||
// properties are cloned, and (3) only own properties
|
||||
// are used for key path evaluation.
|
||||
|
||||
value.prop = invalid_key;
|
||||
assert_throws_dom('DataError', () => {
|
||||
cursor.update(value);
|
||||
}, 'key conversion with invalid key should throw DataError');
|
||||
});
|
||||
}, 'IDBCursor update() method with throwing/invalid keys');
|
||||
|
||||
// Static constructors on IDBKeyRange
|
||||
['only', 'lowerBound', 'upperBound'].forEach((method) => {
|
||||
test(
|
||||
t => check_method(IDBKeyRange, method),
|
||||
'IDBKeyRange ' + method + '() static with throwing/invalid keys');
|
||||
});
|
||||
|
||||
test(
|
||||
t => check_method(IDBKeyRange, 'bound', 2),
|
||||
'IDBKeyRange bound() static with throwing/invalid keys');
|
||||
|
||||
// Insertion methods on IDBObjectStore.
|
||||
['add', 'put'].forEach((method) => {
|
||||
indexeddb_upgrade_only_test((t, db) => {
|
||||
const out_of_line = db.createObjectStore('out-of-line keys');
|
||||
const in_line = db.createObjectStore('in-line keys', {keyPath: 'prop'});
|
||||
let [key, err] = throwing_key('getter');
|
||||
assert_throws_exactly(err, () => {
|
||||
out_of_line[method]('value', key);
|
||||
}, 'key conversion with throwing getter should rethrow');
|
||||
|
||||
assert_throws_dom('DataError', () => {
|
||||
out_of_line[method]('value', invalid_key);
|
||||
}, 'key conversion with invalid key should throw DataError');
|
||||
|
||||
const value = {};
|
||||
[value.prop, err] = throwing_key('getter');
|
||||
assert_throws_exactly(err, () => {
|
||||
in_line[method](value);
|
||||
}, 'throwing getter should rethrow during clone');
|
||||
|
||||
// Throwing from the getter during key conversion is
|
||||
// not possible since (1) a clone is used, (2) only own
|
||||
// properties are cloned, and (3) only own properties
|
||||
// are used for key path evaluation.
|
||||
|
||||
value.prop = invalid_key;
|
||||
assert_throws_dom('DataError', () => {
|
||||
in_line[method](value);
|
||||
}, 'key conversion with invalid key should throw DataError');
|
||||
}, `IDBObjectStore ${method}() method with throwing/invalid keys`);
|
||||
});
|
||||
|
||||
// Generic (key-or-key-path) methods on IDBObjectStore.
|
||||
['delete',
|
||||
'get',
|
||||
'getKey',
|
||||
'count',
|
||||
'openCursor',
|
||||
'openKeyCursor',
|
||||
].forEach(method => {
|
||||
indexeddb_upgrade_only_test((t, db) => {
|
||||
const store = db.createObjectStore('store');
|
||||
|
||||
check_method(store, method);
|
||||
}, `IDBObjectStore ${method}() method with throwing/invalid keys`);
|
||||
});
|
||||
|
||||
// Generic (key-or-key-path) methods on IDBIndex.
|
||||
['get',
|
||||
'getKey',
|
||||
'count',
|
||||
'openCursor',
|
||||
'openKeyCursor',
|
||||
].forEach((method) => {
|
||||
indexeddb_upgrade_only_test((t, db) => {
|
||||
const store = db.createObjectStore('store');
|
||||
const index = store.createIndex('index', 'keyPath');
|
||||
|
||||
check_method(index, method);
|
||||
}, `IDBIndex ${method}() method with throwing/invalid keys`);
|
||||
});
|
||||
|
||||
// Verify methods that take `IDBGetAllOptions` on `IDBObjectStore`.
|
||||
['getAll',
|
||||
'getAllKeys',
|
||||
'getAllRecords',
|
||||
].forEach(method => {
|
||||
indexeddb_upgrade_only_test((t, db) => {
|
||||
const store = db.createObjectStore('store');
|
||||
if ('getAllRecords' in store) {
|
||||
check_method_with_get_all_options(store, method);
|
||||
} else if (method !== 'getAllRecords') {
|
||||
// This browser does not support `getAllRecords()` or the
|
||||
// `IDBGetAllOptions` dictionary.
|
||||
check_method(store, method);
|
||||
}
|
||||
}, `IDBObjectStore ${method}() method with throwing/invalid keys`);
|
||||
});
|
||||
|
||||
// Verify methods that take `IDBGetAllOptions` on `IDBIndex`.
|
||||
['getAll', 'getAllKeys', 'getAllRecords'].forEach(method => {
|
||||
indexeddb_upgrade_only_test((t, db) => {
|
||||
const store = db.createObjectStore('store');
|
||||
const index = store.createIndex('index', 'keyPath');
|
||||
if ('getAllRecords' in index) {
|
||||
check_method_with_get_all_options(index, method);
|
||||
} else if (method !== 'getAllRecords') {
|
||||
check_method(store, method);
|
||||
}
|
||||
}, `IDBIndex ${method}() method with throwing/invalid keys`);
|
||||
});
|
||||
Reference in New Issue
Block a user