Skip to content

Commit

Permalink
[IndexedDB]: Implement explicit commit() in renderer
Browse files Browse the repository at this point in the history
Adding the framework for IndexedDB transaction's explicit commit() API

Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=898257
Change-Id: I5e8bdc697052139d014757408d9a7f2b6367655b
  • Loading branch information
andreas-butler authored and chromium-wpt-export-bot committed Nov 16, 2018
1 parent 64ee30d commit 75e470d
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 10 deletions.
45 changes: 45 additions & 0 deletions IndexedDB/idb-explicit-commit-throw.any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// META: script=support-promises.js

/**
* This file contains a test that was separated out from the rest of the idb
* explict commit tests because it requires the flag 'allow_uncaught_exception',
* which prevents unintentionally thrown errors from failing tests.
*
* @author [email protected]
*/

setup({allow_uncaught_exception:true});

promise_test(async testCase => {
// Register an event listener that will prevent the intentionally thrown
// error from bubbling up to the window and failing the testharness. This
// is necessary because currently allow_uncaught_exception does not behave
// as expected for promise_test.
//
// Git issue: https://github.com/web-platform-tests/wpt/issues/14041
self.addEventListener('error', (event) => { event.preventDefault(); });

const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});

const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const putRequest = objectStore.put({isbn:'one', title:'t1'});
txn.commit();
putRequest.onsuccess = () => {
throw new Error('This error thrown after an explicit commit should not ' +
'prevent the transaction from committing.');
}
await promiseForTransaction(testCase, txn);

// Ensure that despite the uncaught error after the put request, the explicit
// commit still causes the request to be committed.
const txn2 = db.transaction(['books'], 'readwrite');
const objectStore2 = txn2.objectStore('books');
const getRequest = objectStore2.get('one');
await promiseForTransaction(testCase, txn2);

assert_equals(getRequest.result.title, 't1');
}, 'Any errors in callbacks that run after an explicit commit will not stop '
+ 'the commit from being processed.');
181 changes: 181 additions & 0 deletions IndexedDB/idb-explicit-commit.any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// META: script=support-promises.js

/**
* This file contains the webplatform tests for the explicit commit() method
* of the IndexedDB transaction API.
*
* @author [email protected]
*/

promise_test(async testCase => {
const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
objectStore.put({isbn: 'one', title: 'title1'});
objectStore.put({isbn: 'two', title: 'title2'});
objectStore.put({isbn: 'three', title: 'title3'});
txn.commit();
await promiseForTransaction(testCase, txn);

const txn2 = db.transaction(['books'], 'readonly');
const objectStore2 = txn2.objectStore('books');
const getRequestitle1 = objectStore2.get('one');
const getRequestitle2 = objectStore2.get('two');
const getRequestitle3 = objectStore2.get('three');
txn2.commit();
await promiseForTransaction(testCase, txn2);
assert_array_equals(
[getRequestitle1.result.title,
getRequestitle2.result.title,
getRequestitle3.result.title],
['title1', 'title2', 'title3'],
'All three retrieved titles should match those that were put.');
db.close();
}, 'Explicitly committed data can be read back out.');


promise_test(async testCase => {
let db = await createDatabase(testCase, () => {});
assert_equals(1, db.version, 'A database should be created as version 1');
db.close();

// Upgrade the versionDB database and explicitly commit its versionchange
// transaction.
db = await migrateDatabase(testCase, 2, async (db, txn) => {
txn.commit();
});
assert_equals(2, db.version,
'The database version should have been incremented regardless of '
+ 'whether the versionchange transaction was explicitly or implicitly '
+ 'committed.');
db.close();
}, 'commit() on a version change transaction does not cause errors.');


promise_test(async testCase => {
const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
txn.commit();
assert_throws('TransactionInactiveError',
() => { objectStore.put({isbn: 'one', title: 'title1'}); },
'After commit is called, the transaction should be inactive.');
db.close();
}, 'A committed transaction becomes inactive immediately.');


promise_test(async testCase => {
const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const putRequest = objectStore.put({isbn: 'one', title: 'title1'});
putRequest.onsuccess = testCase.step_func(() => {
assert_throws('TransactionInactiveError',
() => { objectStore.put({isbn:'two', title:'title2'}); },
'The transaction should not be active in the callback of a request after '
+ 'commit() is called.');
});
txn.commit();
await promiseForTransaction(testCase, txn);
db.close();
}, 'A committed transaction is inactive in future request callbacks.');


promise_test(async testCase => {
const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
txn.commit();

assert_throws('TransactionInactiveError',
() => { objectStore.put({isbn:'one', title:'title1'}); },
'After commit is called, the transaction should be inactive.');

const txn2 = db.transaction(['books'], 'readonly');
const objectStore2 = txn2.objectStore('books');
const getRequest = objectStore2.get('one');
await promiseForTransaction(testCase, txn2);
assert_equals(getRequest.result, undefined);

db.close();
}, 'Puts issued after commit are not fulfilled.');


promise_test(async testCase => {
const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
txn.abort();
assert_throws('InvalidStateError',
() => { txn.commit(); },
'The transaction should have been aborted.');
db.close();
}, 'Calling commit on an aborted transaction throws.');


promise_test(async testCase => {
const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
txn.commit();
assert_throws('InvalidStateError',
() => { txn.commit(); },
'The transaction should have already committed.');
db.close();
}, 'Calling commit on a committed transaction throws.');


promise_test(async testCase => {
const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const putRequest = objectStore.put({isbn:'one', title:'title1'});
txn.commit();
assert_throws('InvalidStateError',
() => { txn.abort(); },
'The transaction should already have committed.');
const txn2 = db.transaction(['books'], 'readwrite');
const objectStore2 = txn2.objectStore('books');
const getRequest = objectStore2.get('one');
await promiseForTransaction(testCase, txn2);
assert_equals(
getRequest.result.title,
'title1',
'Explicitly committed data should be gettable.');
db.close();
}, 'Calling abort on a committed transaction throws and does not prevent '
+ 'persisting the data.');


promise_test(async testCase => {
const db = await createDatabase(testCase, async db => {
await createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const releaseTxnFunction = keepAlive(txn, 'books');

// Break up the scope of execution to force the transaction into an inactive
// state.
await timeoutPromise(0);

assert_throws('InvalidStateError',
() => { txn.commit(); },
'The transaction should be inactive so calling commit should throw.');
releaseTxnFunction();
db.close();
}, 'Calling txn.commit() when txn is inactive should throw.');
29 changes: 29 additions & 0 deletions IndexedDB/support-promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,32 @@ async function deleteAllDatabases(testCase) {
await eventWatcher.wait_for('success');
}
}

// Keeps the passed transaction alive indefinitely (by making requests
// against the named store). Returns a function that asserts that the
// transaction has not already completed and then ends the request loop so that
// the transaction may autocommit and complete.
function keepAlive(transaction, storeName) {
let completed = false;
transaction.addEventListener('complete', () => { completed = true; });

let keepSpinning = true;

function spin() {
if (!keepSpinning)
return;
transaction.objectStore(storeName).get(0).onsuccess = spin;
}
spin();

return () => {
assert_false(completed, 'Transaction completed while kept alive');
keepSpinning = false;
};
}

// Return a promise that resolves after a setTimeout finishes to break up the
// scope of a function's execution.
function timeoutPromise(ms) {
return new Promise(resolve => { setTimeout(resolve, ms); });
}
17 changes: 7 additions & 10 deletions IndexedDB/support.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
var databaseName = "database";
var databaseVersion = 1;

/* Delete created databases
*
* Go through each finished test, see if it has an associated database. Close
Expand Down Expand Up @@ -170,25 +167,25 @@ function is_transaction_active(tx, store_name) {
}
}

// Keep the passed transaction alive indefinitely (by making requests
// against the named store). Returns a function to to let the
// transaction finish, and asserts that the transaction is not yet
// finished.
// Keeps the passed transaction alive indefinitely (by making requests
// against the named store). Returns a function that asserts that the
// transaction has not already completed and then ends the request loop so that
// the transaction may autocommit and complete.
function keep_alive(tx, store_name) {
let completed = false;
tx.addEventListener('complete', () => { completed = true; });

let pin = true;
let keepSpinning = true;

function spin() {
if (!pin)
if (!keepSpinning)
return;
tx.objectStore(store_name).get(0).onsuccess = spin;
}
spin();

return () => {
assert_false(completed, 'Transaction completed while kept alive');
pin = false;
keepSpinning = false;
};
}

0 comments on commit 75e470d

Please sign in to comment.