https://github.com/web-platform-tests/wpt
Tip revision: a99112edef5480c5a92159a8bf772af8ef00fc85 authored by Lennart Grahl on 24 May 2018, 22:01:25 UTC
fixup! fixup! Add an ugly workaround for createDataChannelPair to make it work in Safari
fixup! fixup! Add an ugly workaround for createDataChannelPair to make it work in Safari
Tip revision: a99112e
large-requests-abort.html
<!doctype html>
<meta charset="utf8">
<meta name="timeout" content="long">
<title>IndexedDB: transactions with large request results are aborted correctly</title>
<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction">
<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support-promises.js"></script>
<script>
'use strict';
// Should be large enough to trigger large value handling in the IndexedDB
// engines that have special code paths for large values.
const wrapThreshold = 128 * 1024;
function populateStore(store) {
store.put({id: 1, key: 'k1', value: largeValue(wrapThreshold, 1) });
store.put({id: 2, key: 'k2', value: ['small-2'] });
store.put({id: 3, key: 'k3', value: largeValue(wrapThreshold, 3) });
store.put({id: 4, key: 'k4', value: ['small-4'] });
}
// Opens index cursors for operations that require open cursors.
//
// onsuccess is called if all cursors are opened successfully. Otherwise,
// onerror will be called at least once.
function openCursors(testCase, index, operations, onerror, onsuccess) {
let pendingCursors = 0;
for (let operation of operations) {
const opcode = operation[0];
const primaryKey = operation[1];
let request;
switch (opcode) {
case 'continue':
request = index.openCursor(
IDBKeyRange.lowerBound(`k${primaryKey - 1}`));
break;
case 'continue-empty':
// k4 is the last key in the data set, so calling continue() will get
// the cursor past the end of the store.
request = index.openCursor(IDBKeyRange.lowerBound('k4'));
break;
default:
continue;
}
operation[2] = request;
++pendingCursors;
request.onsuccess = testCase.step_func(() => {
--pendingCursors;
if (!pendingCursors)
onsuccess();
});
request.onerror = testCase.step_func(onerror);
}
if (!pendingCursors)
onsuccess();
}
function doOperation(testCase, store, index, operation, requestId, results) {
const opcode = operation[0];
const primaryKey = operation[1];
const cursor = operation[2];
return new Promise((resolve, reject) => {
let request;
switch (opcode) {
case 'add': // Tests returning a primary key.
request = store.add(
{ key: `k${primaryKey}`, value: [`small-${primaryKey}`] });
break;
case 'put': // Tests returning a primary key.
request = store.put(
{ key: `k${primaryKey}`, value: [`small-${primaryKey}`] });
break;
case 'put-with-id': // Tests returning success or a primary key.
request = store.put(
{ key: `k${primaryKey}`, value: [`small-${primaryKey}`],
id: primaryKey });
break;
case 'get': // Tests returning a value.
case 'get-empty': // Tests returning undefined.
request = store.get(primaryKey);
break;
case 'getall': // Tests returning an array of values.
request = store.getAll();
break;
case 'error': // Tests returning an error.
request = store.put(
{ key: `k${primaryKey}`, value: [`small-${primaryKey}`] });
break;
case 'continue': // Tests returning a key, primary key, and value.
case 'continue-empty': // Tests returning null.
request = cursor;
cursor.result.continue();
break;
case 'open': // Tests returning a cursor, key, primary key, and value.
case 'open-empty': // Tests returning null.
request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`));
break;
case 'count': // Tests returning a numeric result.
request = index.count();
break;
};
request.onsuccess = testCase.step_func(() => {
reject(new Error(
'requests should not succeed after the transaction is aborted'));
});
request.onerror = testCase.step_func(event => {
event.preventDefault();
results.push([requestId, request.error]);
resolve();
});
});
}
function abortTest(label, operations) {
promise_test(testCase => {
return createDatabase(testCase, (database, transaction) => {
const store = database.createObjectStore(
'test-store', { autoIncrement: true, keyPath: 'id' });
store.createIndex('test-index', 'key', { unique: true });
populateStore(store);
}).then(database => {
const transaction = database.transaction(['test-store'], 'readwrite');
const store = transaction.objectStore('test-store');
const index = store.index('test-index');
return new Promise((resolve, reject) => {
openCursors(testCase, index, operations, reject, () => {
const results = [];
const promises = [];
for (let i = 0; i < operations.length; ++i) {
const promise = doOperation(
testCase, store, index, operations[i], i, results);
promises.push(promise);
};
transaction.abort();
resolve(Promise.all(promises).then(() => results));
});
});
}).then(results => {
assert_equals(
results.length, operations.length,
'Promise.all should resolve after all sub-promises resolve');
for (let i = 0; i < operations.length; ++i) {
assert_equals(
results[i][0], i,
'error event order should match request order');
assert_equals(
results[i][1].name, 'AbortError',
'transaction aborting should result in AbortError on all requests');
}
});
}, label);
}
abortTest('small values', [
['get', 2],
['count', null],
['continue-empty', null],
['get-empty', 5],
['add', 5],
['open', 2],
['continue', 2],
['get', 4],
['get-empty', 6],
['count', null],
['put-with-id', 5],
['put', 6],
['error', 3],
['continue', 4],
['count', null],
['get-empty', 7],
['open', 4],
['open-empty', 7],
['add', 7],
]);
abortTest('large values', [
['open', 1],
['get', 1],
['getall', 4],
['get', 3],
['continue', 3],
['open', 3],
]);
abortTest('large value followed by small values', [
['get', 1],
['getall', null],
['open', 2],
['continue-empty', null],
['get', 2],
['get-empty', 5],
['count', null],
['continue-empty', null],
['open-empty', 5],
['add', 5],
['error', 1],
['continue', 2],
['get-empty', 6],
['put-with-id', 5],
['put', 6],
]);
abortTest('large values mixed with small values', [
['get', 1],
['get', 2],
['get-empty', 5],
['count', null],
['continue-empty', null],
['open', 1],
['continue', 2],
['open-empty', 5],
['getall', 4],
['open', 2],
['continue-empty', null],
['add', 5],
['get', 3],
['count', null],
['get-empty', 6],
['put-with-id', 5],
['getall', null],
['continue', 3],
['open-empty', 6],
['put', 6],
['error', 1],
['continue', 2],
['open', 4],
['get-empty', 7],
['count', null],
['continue', 3],
['add', 7],
['getall', null],
['error', 3],
['count', null],
]);
</script>