Bug 692911: Refactor some VERSION_CHANGE transaction locking stuff and disallow transaction creation before databases are completely open. r=bent

This commit is contained in:
Kyle Huey 2011-10-24 16:01:11 -04:00
Родитель 308444aebe
Коммит 5e7a740f36
36 изменённых файлов: 289 добавлений и 58 удалений

Просмотреть файл

@ -560,8 +560,8 @@ TransactionPoolEventTarget::Dispatch(nsIRunnable* aRunnable,
NS_ASSERTION(aRunnable, "Null pointer!");
NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "Unsupported!");
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
TransactionThreadPool* pool = TransactionThreadPool::Get();
NS_ASSERTION(pool, "This should never be null!");
return pool->Dispatch(mTransaction, aRunnable, false, nsnull);
}

Просмотреть файл

@ -125,6 +125,11 @@ public:
static IDBTransaction* GetCurrentTransaction();
bool HasTransaction()
{
return mTransaction;
}
nsISupports* GetSource()
{
return mRequest ? mRequest->Source() : nsnull;

Просмотреть файл

@ -85,7 +85,8 @@ EnumerateObjectStoreNames(const nsAString& aKey,
DatabaseInfo::DatabaseInfo()
: id(0),
nextObjectStoreId(1),
nextIndexId(1)
nextIndexId(1),
runningVersionChange(false)
{
MOZ_COUNT_CTOR(DatabaseInfo);
}

Просмотреть файл

@ -54,7 +54,8 @@ struct DatabaseInfo
~DatabaseInfo();
#else
DatabaseInfo()
: id(0), nextObjectStoreId(1), nextIndexId(1) { }
: id(0), nextObjectStoreId(1), nextIndexId(1), runningVersionChange(false)
{ }
#endif
static bool Get(PRUint32 aId,
@ -73,6 +74,7 @@ struct DatabaseInfo
nsString filePath;
PRInt64 nextObjectStoreId;
PRInt64 nextIndexId;
bool runningVersionChange;
nsAutoRefCnt referenceCount;
};

Просмотреть файл

@ -428,6 +428,34 @@ IDBDatabase::IsClosed()
return mClosed;
}
void
IDBDatabase::EnterSetVersionTransaction()
{
DatabaseInfo* dbInfo;
if (!DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
NS_ERROR("This should never fail!");
}
NS_ASSERTION(!dbInfo->runningVersionChange, "How did that happen?");
dbInfo->runningVersionChange = true;
}
void
IDBDatabase::ExitSetVersionTransaction()
{
DatabaseInfo* dbInfo;
if (!DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
NS_ERROR("This should never fail!");
}
NS_ASSERTION(dbInfo->runningVersionChange, "How did that happen?");
dbInfo->runningVersionChange = false;
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
NS_ASSERTION(manager, "We should always have a manager here");
manager->UnblockSetVersionRunnable(this);
}
void
IDBDatabase::OnUnlink()
{
@ -710,6 +738,10 @@ IDBDatabase::Transaction(nsIVariant* aStoreNames,
NS_ERROR("This should never fail!");
}
if (info->runningVersionChange) {
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
}
nsTArray<nsString> storesToOpen;
switch (type) {

Просмотреть файл

@ -135,6 +135,9 @@ public:
// Whether or not the database has had Close called on it.
bool IsClosed();
void EnterSetVersionTransaction();
void ExitSetVersionTransaction();
private:
IDBDatabase();
~IDBDatabase();

Просмотреть файл

@ -693,19 +693,15 @@ IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
// Now run the helper if there are no more live databases.
if (runnable->mHelper && runnable->mDatabases.IsEmpty()) {
// Don't hold the callback alive longer than necessary.
nsRefPtr<AsyncConnectionHelper> helper;
helper.swap(runnable->mHelper);
// At this point, all databases are closed, so no new transactions can
// be started. There may, however, still be outstanding transactions
// that have not completed. We need to wait for those before we
// dispatch the helper.
if (NS_FAILED(helper->DispatchToTransactionPool())) {
NS_WARNING("Failed to dispatch to thread pool!");
}
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
// Now wait for the transaction to complete. Completing the transaction
// will be our cue to remove the SetVersionRunnable from our list and
// therefore allow other SetVersion requests to begin.
TransactionThreadPool* pool = TransactionThreadPool::Get();
NS_ASSERTION(pool, "This should never be null!");
nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
new WaitForTransactionsToFinishRunnable(runnable);
// All other databases should be closed, so we only need to wait on this
// one.
@ -714,8 +710,8 @@ IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
NS_ERROR("This should never fail!");
}
// Use the SetVersionRunnable as the callback.
if (!pool->WaitForAllDatabasesToComplete(array, runnable)) {
// Use the WaitForTransactionsToFinishRunnable as the callback.
if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
NS_WARNING("Failed to wait for transaction to complete!");
}
}
@ -724,6 +720,27 @@ IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
}
}
void
IndexedDatabaseManager::UnblockSetVersionRunnable(IDBDatabase* aDatabase)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aDatabase, "Null pointer!");
// Check through the list of SetVersionRunnables to find the one we're seeking.
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
NS_ASSERTION(!runnable->mHelper,
"Why are we unblocking a runnable if the helper didn't run?");
NS_DispatchToCurrentThread(runnable);
return;
}
}
NS_NOTREACHED("How did we get here!");
}
// static
bool
IndexedDatabaseManager::SetCurrentDatabase(IDBDatabase* aDatabase)
@ -1283,3 +1300,39 @@ IndexedDatabaseManager::SetVersionRunnable::Run()
return NS_OK;
}
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::WaitForTransactionsToFinishRunnable,
nsIRunnable)
NS_IMETHODIMP
IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// Don't hold the callback alive longer than necessary.
nsRefPtr<AsyncConnectionHelper> helper;
helper.swap(mRunnable->mHelper);
nsRefPtr<SetVersionRunnable> runnable;
runnable.swap(mRunnable);
// If the helper has a transaction, dispatch it to the transaction
// threadpool.
if (helper->HasTransaction()) {
if (NS_FAILED(helper->DispatchToTransactionPool())) {
NS_WARNING("Failed to dispatch to thread pool!");
}
}
// Otherwise, dispatch it to the IO thread.
else {
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
NS_ASSERTION(manager, "We should definitely have a manager here");
helper->Dispatch(manager->IOThread());
}
// The helper is responsible for calling
// IndexedDatabaseManager::UnblockSetVersionRunnable.
return NS_OK;
}

Просмотреть файл

@ -204,6 +204,8 @@ private:
// Called when AsyncUsageRunnable has finished its Run() method.
inline void OnUsageCheckComplete(AsyncUsageRunnable* aRunnable);
void UnblockSetVersionRunnable(IDBDatabase* aDatabase);
// Responsible for waiting until all databases have been closed before running
// the version change transaction. Created when
// IndexedDatabaseManager::SetDatabaseVersion is called. Runs only once on the
@ -227,6 +229,26 @@ private:
// Called when SetVersionRunnable has finished its Run() method.
inline void OnSetVersionRunnableComplete(SetVersionRunnable* aRunnable);
// A callback runnable used by the TransactionPool when it's safe to proceed
// with a SetVersion/DeleteDatabase/etc.
class WaitForTransactionsToFinishRunnable : public nsIRunnable
{
public:
WaitForTransactionsToFinishRunnable(SetVersionRunnable* aRunnable)
: mRunnable(aRunnable)
{
NS_ASSERTION(mRunnable, "Why don't we have a runnable?");
NS_ASSERTION(mRunnable->mDatabases.IsEmpty(), "We're here too early!");
}
NS_DECL_ISUPPORTS
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<SetVersionRunnable> mRunnable;
};
// Maintains a list of live databases per origin.
nsClassHashtable<nsCStringHashKey, nsTArray<IDBDatabase*> > mLiveDatabases;

Просмотреть файл

@ -495,10 +495,13 @@ public:
NS_DECL_ISUPPORTS_INHERITED
nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
nsresult GetSuccessResult(JSContext* aCx,
jsval* aVal);
protected:
nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
nsresult Init();
// SetVersionHelper never fires an error event at the request. It hands that
// responsibility back to the OpenDatabaseHelper
void OnError() { }
@ -684,6 +687,7 @@ OpenDatabaseHelper::StartSetVersion()
// The SetVersionHelper is responsible for dispatching us back to the
// main thread again and changing the state to eSetVersionCompleted.
mState = eSetVersionPending;
return NS_OK;
}
@ -711,6 +715,12 @@ OpenDatabaseHelper::Run()
mState == eSetVersionCompleted, "Why are we here?");
if (mState == eSetVersionCompleted) {
// Allow transaction creation/other version change transactions to proceed
// before we fire events. Other version changes will be postd to the end
// of the event loop, and will be behind whatever the page does in
// its error/success event handlers.
mDatabase->ExitSetVersionTransaction();
mState = eFiringEvents;
} else {
// Notify the request that we're done, but only if we didn't just finish
@ -865,6 +875,15 @@ OpenDatabaseHelper::NotifySetVersionFinished()
return NS_DispatchToCurrentThread(this);
}
void
OpenDatabaseHelper::BlockDatabase()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(mDatabase, "This is going bad fast.");
mDatabase->EnterSetVersionTransaction();
}
void
OpenDatabaseHelper::DispatchSuccessEvent()
{
@ -916,6 +935,15 @@ OpenDatabaseHelper::ReleaseMainThreadObjects()
NS_IMPL_ISUPPORTS_INHERITED0(SetVersionHelper, AsyncConnectionHelper);
nsresult
SetVersionHelper::Init()
{
// Block transaction creation until we are done.
mOpenHelper->BlockDatabase();
return NS_OK;
}
nsresult
SetVersionHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
{

Просмотреть файл

@ -82,6 +82,7 @@ public:
}
nsresult NotifySetVersionFinished();
void BlockDatabase();
protected:
// Methods only called on the main thread

Просмотреть файл

@ -238,7 +238,6 @@ TransactionThreadPool::FinishTransaction(IDBTransaction* aTransaction)
#ifdef DEBUG
if (aTransaction->mMode == IDBTransaction::VERSION_CHANGE) {
NS_ASSERTION(dbTransactionInfo->locked, "Should be locked!");
NS_ASSERTION(transactionCount == 1,
"More transactions running than should be!");
}
@ -344,10 +343,6 @@ TransactionThreadPool::TransactionCanRun(IDBTransaction* aTransaction,
PRUint32 transactionCount = transactionsInProgress.Length();
NS_ASSERTION(transactionCount, "Should never be 0!");
if (mode == IDBTransaction::VERSION_CHANGE) {
dbTransactionInfo->lockPending = true;
}
for (PRUint32 index = 0; index < transactionCount; index++) {
// See if this transaction is in out list of current transactions.
const TransactionInfo& info = transactionsInProgress[index];
@ -358,11 +353,7 @@ TransactionThreadPool::TransactionCanRun(IDBTransaction* aTransaction,
}
}
if (dbTransactionInfo->locked || dbTransactionInfo->lockPending) {
*aCanRun = false;
*aExistingQueue = nsnull;
return NS_OK;
}
NS_ASSERTION(mode != IDBTransaction::VERSION_CHANGE, "How did we get here?");
bool writeOverlap;
nsresult rv =
@ -448,11 +439,6 @@ TransactionThreadPool::Dispatch(IDBTransaction* aTransaction,
dbTransactionInfo = autoDBTransactionInfo;
}
if (aTransaction->mMode == IDBTransaction::VERSION_CHANGE) {
NS_ASSERTION(!dbTransactionInfo->locked, "Already locked?!");
dbTransactionInfo->locked = true;
}
const nsTArray<nsString>& objectStoreNames = aTransaction->mObjectStoreNames;
nsTArray<nsString>& storesInUse =

Просмотреть файл

@ -123,12 +123,6 @@ protected:
struct DatabaseTransactionInfo
{
DatabaseTransactionInfo()
: locked(false), lockPending(false)
{ }
bool locked;
bool lockPending;
nsTArray<TransactionInfo> transactions;
nsTArray<nsString> storesReading;
nsTArray<nsString> storesWriting;

Просмотреть файл

@ -98,6 +98,7 @@ TEST_FILES = \
test_setVersion.html \
test_setVersion_abort.html \
test_setVersion_events.html \
test_setVersion_exclusion.html \
test_writer_starvation.html \
third_party_iframe1.html \
third_party_iframe2.html \

Просмотреть файл

@ -104,7 +104,7 @@
db.onerror = errorEventCounter;
db.addEventListener("error", errorEventCounter, true);
event.target.transaction.oncomplete = grabEventAndContinueHandler;
event.target.onsuccess = grabEventAndContinueHandler;
db.createObjectStore("foo", { autoIncrement: true });
yield;

Просмотреть файл

@ -12,7 +12,7 @@ function startDBWork() {
}
var store = db.createObjectStore("mystore");
store.add({ hello: "world" }, 42);
trans.oncomplete = madeMod;
e.target.onsuccess = madeMod;
};
}

Просмотреть файл

@ -33,6 +33,9 @@
let key = event.target.result;
ok(key, "Added entry");
request.onsuccess = grabEventAndContinueHandler;
event = yield;
let objectStore = db.transaction("foo").objectStore("foo");
let first = objectStore.index("first");

Просмотреть файл

@ -25,7 +25,7 @@
let db = request.result;
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
let objectStore = db.createObjectStore("foo", { autoIncrement: true });

Просмотреть файл

@ -23,7 +23,7 @@
let db = event.target.result;
db.onerror = errorHandler;
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
// Make object store, add data.
let objectStore = db.createObjectStore("foo", { keyPath: "id" });
@ -39,7 +39,7 @@
let db2 = event.target.result;
db2.onerror = errorHandler;
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
// Create index.
event.target.transaction.objectStore("foo").createIndex("foo", "num");

Просмотреть файл

@ -35,7 +35,7 @@
let event = yield;
let db = event.target.result;
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
let objectStore = db.createObjectStore("foo", { keyPath: "ss" });
objectStore.createIndex("name", "name", { unique: true });

Просмотреть файл

@ -26,6 +26,7 @@
let objectStore = db.createObjectStore("foo", { autoIncrement: true });
request.onsuccess = grabEventAndContinueHandler;
request = objectStore.getAll();
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
@ -46,6 +47,7 @@
}
}
yield;
yield;
request = db.transaction("foo").objectStore("foo").getAll();
request.onerror = errorHandler;

Просмотреть файл

@ -60,6 +60,7 @@
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
@ -78,7 +79,7 @@
}
}
}
event = yield;
yield;
ok(true, "1");
// Now create the indexes.
@ -88,7 +89,6 @@
}
is(objectStore.indexNames.length, indexData.length, "Good index count");
continueToNextStep();
yield;
ok(true, "2");

Просмотреть файл

@ -60,6 +60,7 @@
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
@ -87,7 +88,6 @@
}
is(objectStore.indexNames.length, indexData.length, "Good index count");
continueToNextStep();
yield;
objectStore = db.transaction(objectStoreName)

Просмотреть файл

@ -37,7 +37,7 @@
let db = event.target.result;
db.onerror = errorHandler;
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
for (let objectStoreIndex in objectStoreData) {
const objectStoreInfo = objectStoreData[objectStoreIndex];

Просмотреть файл

@ -71,6 +71,7 @@
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
@ -97,7 +98,6 @@
indexData[i].options);
}
is(objectStore.indexNames.length, indexData.length, "Good index count");
continueToNextStep();
yield;
objectStore = db.transaction(objectStoreName)

Просмотреть файл

@ -51,6 +51,7 @@
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
@ -87,6 +88,7 @@
}
}
yield;
yield;
objectStore = db.transaction(objectStoreName)
.objectStore(objectStoreName);

Просмотреть файл

@ -28,7 +28,7 @@
let index2 = objectStore2.index("bar");
ok(index1 === index2, "Got same indexes");
transaction.oncomplete = continueToNextStep;
request.onsuccess = continueToNextStep;
yield;
transaction = db.transaction("foo");

Просмотреть файл

@ -29,7 +29,7 @@
autoIncrement: true });
let index = objectStore.createIndex("foo", "index");
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
yield;
objectStore = db.transaction("foo", IDBTransaction.READ_WRITE)

Просмотреть файл

@ -20,6 +20,7 @@
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
@ -31,7 +32,6 @@
is(db.objectStoreNames.length, 1, "Bad objectStores list");
is(db.objectStoreNames.item(0), objectStoreName, "Bad name");
continueToNextStep();
yield;
objectStore = db.transaction(objectStoreName).objectStore(objectStoreName);

Просмотреть файл

@ -26,7 +26,7 @@
let db = event.target.result;
is(db.objectStoreNames.length, 0, "Correct objectStoreNames list");
event.target.transaction.oncomplete = grabEventAndContinueHandler;
event.target.onsuccess = grabEventAndContinueHandler;
for (let i in objectStores) {
db.createObjectStore(objectStores[i], { autoIncrement: true });
}

Просмотреть файл

@ -22,6 +22,7 @@
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
@ -29,6 +30,8 @@
db.createObjectStore(osName, { autoIncrement: "true" });
yield;
let key1, key2;
request = db.transaction([osName], READ_WRITE)

Просмотреть файл

@ -0,0 +1,90 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Indexed Database Property Test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript;version=1.7">
function testSteps()
{
const name = window.location.pathname;
let request = mozIndexedDB.open(name, 1);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = unexpectedSuccessHandler;
let request2 = mozIndexedDB.open(name, 2);
request2.onerror = errorHandler;
request2.onupgradeneeded = unexpectedSuccessHandler;
let event = yield;
is(event.type, "upgradeneeded", "Expect an upgradeneeded event");
is(event.target, request, "Event should be fired on the request");
ok(event.target.result instanceof IDBDatabase, "Expect a database here");
let db = event.target.result;
is(db.version, 1, "Database has correct version");
db.onupgradeneeded = function() {
ok(false, "our ongoing VERSION_CHANGE transaction should exclude any others!");
}
db.createObjectStore("foo");
try {
db.transaction("foo");
ok(false, "Transactions should be disallowed now!");
} catch (e) {
ok(e instanceof IDBDatabaseException, "Expect an IDBException");
is(e.code, IDBDatabaseException.NOT_ALLOWED_ERR, "Expect a NOT_ALLOWED_ERR");
}
request.transaction.oncomplete = grabEventAndContinueHandler;
yield;
// The database is still not fully open here.
try {
db.transaction("foo");
ok(false, "Transactions should be disallowed now!");
} catch (e) {
ok(e instanceof IDBDatabaseException, "Expect an IDBException");
is(e.code, IDBDatabaseException.NOT_ALLOWED_ERR, "Expect a NOT_ALLOWED_ERR");
}
request.onsuccess = grabEventAndContinueHandler;
yield;
db.onversionchange = function() {
ok(true, "next setVersion was unblocked appropriately");
db.close();
}
try {
db.transaction("foo");
ok(true, "Transactions should be allowed now!");
} catch (e) {
ok(false, "Transactions should be allowed now!");
}
request2.onupgradeneeded = null;
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>

Просмотреть файл

@ -19,7 +19,7 @@
let db = event.target.result;
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
let objectStore = db.createObjectStore("foo");
objectStore.add({}, 1).onerror = errorHandler;

Просмотреть файл

@ -29,6 +29,7 @@
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
@ -157,6 +158,8 @@
ok(true, "RemoveIndex threw");
}
yield;
request = db.transaction("foo", READ_WRITE).objectStore("foo").add({});
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;

Просмотреть файл

@ -20,7 +20,7 @@
let db = event.target.result;
db.onerror = errorHandler;
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
db.createObjectStore("foo", { autoIncrement: true });
yield;

Просмотреть файл

@ -20,7 +20,7 @@
let db = event.target.result;
db.onerror = errorHandler;
event.target.transaction.oncomplete = continueToNextStep;
event.target.onsuccess = continueToNextStep;
db.createObjectStore("foo");
yield;

Просмотреть файл

@ -23,6 +23,7 @@
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
@ -39,7 +40,6 @@
let key = event.target.result;
ok(key, "Got a key");
SimpleTest.executeSoon(function() { testGenerator.next(); });
yield;
let continueReading = true;