summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/IndexedDB/large-requests-abort.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/IndexedDB/large-requests-abort.html')
-rw-r--r--testing/web-platform/tests/IndexedDB/large-requests-abort.html244
1 files changed, 244 insertions, 0 deletions
diff --git a/testing/web-platform/tests/IndexedDB/large-requests-abort.html b/testing/web-platform/tests/IndexedDB/large-requests-abort.html
new file mode 100644
index 0000000000..38c73ff406
--- /dev/null
+++ b/testing/web-platform/tests/IndexedDB/large-requests-abort.html
@@ -0,0 +1,244 @@
+<!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="resources/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>