Bug 765839 - 'Enable IndexedDB OOP test suite'. r=khuey.

--HG--
extra : transplant_source : %3Bb4%A4%99fC%9Cg%86%9B%1F3%C6%0F%01T%1C%3C%AE
This commit is contained in:
Ben Turner 2012-06-19 18:50:39 -07:00
parent 9d93de75c0
commit bfa7cae37b
25 changed files with 605 additions and 364 deletions

View File

@ -11,6 +11,7 @@
#include "nsIDOMFileHandle.h"
#include "nsIFile.h"
#include "nsIFileStorage.h"
#include "nsDOMEventTargetHelper.h"

View File

@ -105,6 +105,16 @@ ObjectStoreInfo::~ObjectStoreInfo()
}
IndexUpdateInfo::IndexUpdateInfo()
: indexId(0),
indexUnique(false)
{
MOZ_COUNT_CTOR(IndexUpdateInfo);
}
IndexUpdateInfo::IndexUpdateInfo(const IndexUpdateInfo& aOther)
: indexId(aOther.indexId),
indexUnique(aOther.indexUnique),
value(aOther.value)
{
MOZ_COUNT_CTOR(IndexUpdateInfo);
}
@ -113,6 +123,7 @@ IndexUpdateInfo::~IndexUpdateInfo()
{
MOZ_COUNT_DTOR(IndexUpdateInfo);
}
#endif /* NS_BUILD_REFCNT_LOGGING */
// static

View File

@ -173,6 +173,7 @@ struct IndexUpdateInfo
{
#ifdef NS_BUILD_REFCNT_LOGGING
IndexUpdateInfo();
IndexUpdateInfo(const IndexUpdateInfo& aOther);
~IndexUpdateInfo();
#endif

View File

@ -4,7 +4,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "IndexedDatabaseManager.h"
#include "FileInfo.h"
USING_INDEXEDDB_NAMESPACE

View File

@ -8,8 +8,10 @@
#define mozilla_dom_indexeddb_fileinfo_h__
#include "IndexedDatabase.h"
#include "nsAtomicRefcnt.h"
#include "nsThreadUtils.h"
#include "FileManager.h"
#include "IndexedDatabaseManager.h"

View File

@ -243,6 +243,10 @@ IDBDatabase::Invalidate()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if (IsInvalidated()) {
return;
}
// Make sure we're closed too.
Close();

View File

@ -8,10 +8,11 @@
#include "nsIStandardFileStream.h"
#include "mozilla/dom/file/File.h"
#include "nsDOMClassInfoID.h"
#include "FileStream.h"
#include "mozilla/dom/file/File.h"
#include "IDBDatabase.h"
USING_INDEXEDDB_NAMESPACE

View File

@ -24,6 +24,7 @@ BEGIN_INDEXEDDB_NAMESPACE
class AsyncConnectionHelper;
class IDBCursor;
class IDBKeyRange;
class IDBRequest;
class IndexedDBObjectStoreChild;
class IndexedDBObjectStoreParent;
class Key;

View File

@ -250,6 +250,7 @@ IDBTransaction::CommitOrRollback()
NS_ASSERTION(mActorChild, "Must have an actor!");
mActorChild->SendAllRequestsFinished();
return NS_OK;
}

View File

@ -7,6 +7,7 @@
#include "IndexedDatabaseManager.h"
#include "DatabaseInfo.h"
#include "nsIAtom.h"
#include "nsIDOMScriptObjectFactory.h"
#include "nsIFile.h"
#include "nsIFileStorage.h"
@ -437,33 +438,25 @@ IndexedDatabaseManager::AllowNextSynchronizedOp(const nsACString& aOrigin,
}
nsresult
IndexedDatabaseManager::AcquireExclusiveAccess(const nsACString& aOrigin,
IDBDatabase* aDatabase,
AsyncConnectionHelper* aHelper,
WaitingOnDatabasesCallback aCallback,
void* aClosure)
IndexedDatabaseManager::AcquireExclusiveAccess(
const nsACString& aOrigin,
IDBDatabase* aDatabase,
AsyncConnectionHelper* aHelper,
nsIRunnable* aRunnable,
WaitingOnDatabasesCallback aCallback,
void* aClosure)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aHelper, "Why are you talking to me?");
NS_ASSERTION(!aDatabase || aHelper, "Need a helper with a database!");
NS_ASSERTION(aDatabase || aRunnable, "Need a runnable without a database!");
// Find the right SynchronizedOp.
SynchronizedOp* op = nsnull;
PRUint32 count = mSynchronizedOps.Length();
for (PRUint32 index = 0; index < count; index++) {
SynchronizedOp* currentop = mSynchronizedOps[index].get();
if (currentop->mOrigin.Equals(aOrigin)) {
if (!currentop->mId ||
(aDatabase && currentop->mId == aDatabase->Id())) {
// We've found the right one.
NS_ASSERTION(!currentop->mHelper,
"SynchronizedOp already has a helper?!?");
op = currentop;
break;
}
}
}
SynchronizedOp* op =
FindSynchronizedOp(aOrigin, aDatabase ? aDatabase->Id() : nsnull);
NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
NS_ASSERTION(!op->mHelper, "SynchronizedOp already has a helper?!?");
NS_ASSERTION(!op->mRunnable, "SynchronizedOp already has a runnable?!?");
nsTArray<IDBDatabase*>* array;
mLiveDatabases.Get(aOrigin, &array);
@ -474,33 +467,50 @@ IndexedDatabaseManager::AcquireExclusiveAccess(const nsACString& aOrigin,
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
if (array) {
PRUint32 count = array->Length();
for (PRUint32 index = 0; index < count; index++) {
IDBDatabase*& database = array->ElementAt(index);
if (!database->IsClosed() &&
(!aDatabase ||
(aDatabase &&
if (aDatabase) {
// Grab all databases that are not yet closed but whose database id match
// the one we're looking for.
for (PRUint32 index = 0; index < array->Length(); index++) {
IDBDatabase*& database = array->ElementAt(index);
if (!database->IsClosed() &&
database != aDatabase &&
database->Id() == aDatabase->Id()))) {
liveDatabases.AppendElement(database);
database->Id() == aDatabase->Id()) {
liveDatabases.AppendElement(database);
}
}
}
else {
// We want *all* databases, even those that are closed, if we're going to
// clear the origin.
liveDatabases.AppendElements(*array);
}
}
if (liveDatabases.IsEmpty()) {
IndexedDatabaseManager::DispatchHelper(aHelper);
return NS_OK;
op->mHelper = aHelper;
op->mRunnable = aRunnable;
if (!liveDatabases.IsEmpty()) {
NS_ASSERTION(op->mDatabases.IsEmpty(),
"How do we already have databases here?");
op->mDatabases.AppendElements(liveDatabases);
// Give our callback the databases so it can decide what to do with them.
aCallback(liveDatabases, aClosure);
NS_ASSERTION(liveDatabases.IsEmpty(),
"Should have done something with the array!");
if (aDatabase) {
// Wait for those databases to close.
return NS_OK;
}
}
NS_ASSERTION(op->mDatabases.IsEmpty(), "How do we already have databases here?");
op->mDatabases.AppendElements(liveDatabases);
op->mHelper = aHelper;
// If we're trying to open a database and nothing blocks it, or if we're
// clearing an origin, then go ahead and schedule the op.
nsresult rv = RunSynchronizedOp(aDatabase, op);
NS_ENSURE_SUCCESS(rv, rv);
// Give our callback the databases so it can decide what to do with them.
aCallback(liveDatabases, aClosure);
NS_ASSERTION(liveDatabases.IsEmpty(),
"Should have done something with the array!");
return NS_OK;
}
@ -585,59 +595,23 @@ IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
// Check through the list of SynchronizedOps to see if any are waiting for
// this database to close before proceeding.
PRUint32 count = mSynchronizedOps.Length();
for (PRUint32 index = 0; index < count; index++) {
nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
if (op->mOrigin == aDatabase->Origin() &&
(op->mId == aDatabase->Id() || !op->mId)) {
// This database is in the scope of this SynchronizedOp. Remove it
// from the list if necessary.
if (op->mDatabases.RemoveElement(aDatabase)) {
// Now set up the helper if there are no more live databases.
NS_ASSERTION(op->mHelper, "How did we get rid of the helper before "
"removing the last database?");
if (op->mDatabases.IsEmpty()) {
// 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.
FileService* service = FileService::Get();
TransactionThreadPool* pool = TransactionThreadPool::Get();
PRUint32 count = !!service + !!pool;
nsRefPtr<WaitForTransactionsToFinishRunnable> runnable =
new WaitForTransactionsToFinishRunnable(op,
NS_MAX<PRUint32>(count, 1));
if (!count) {
runnable->Run();
}
else {
// Use the WaitForTransactionsToxFinishRunnable as the callback.
if (service) {
nsTArray<nsCOMPtr<nsIFileStorage> > array;
array.AppendElement(aDatabase);
if (!service->WaitForAllStoragesToComplete(array, runnable)) {
NS_WARNING("Failed to wait for storages to complete!");
}
}
if (pool) {
nsTArray<nsRefPtr<IDBDatabase> > array;
array.AppendElement(aDatabase);
if (!pool->WaitForAllDatabasesToComplete(array, runnable)) {
NS_WARNING("Failed to wait for databases to complete!");
}
}
}
SynchronizedOp* op = FindSynchronizedOp(aDatabase->Origin(), aDatabase->Id());
if (op) {
// This database is in the scope of this SynchronizedOp. Remove it
// from the list if necessary.
if (op->mDatabases.RemoveElement(aDatabase)) {
// Now set up the helper if there are no more live databases.
NS_ASSERTION(op->mHelper || op->mRunnable,
"How did we get rid of the helper/runnable before "
"removing the last database?");
if (op->mDatabases.IsEmpty()) {
// 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(RunSynchronizedOp(aDatabase, op))) {
NS_WARNING("Failed to run synchronized op!");
}
break;
}
}
}
@ -1101,41 +1075,62 @@ IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager,
// static
nsresult
IndexedDatabaseManager::DispatchHelper(AsyncConnectionHelper* aHelper)
IndexedDatabaseManager::RunSynchronizedOp(IDBDatabase* aDatabase,
SynchronizedOp* aOp)
{
nsresult rv = NS_OK;
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aOp, "Null pointer!");
NS_ASSERTION(!aDatabase || aOp->mHelper, "No helper on this op!");
NS_ASSERTION(aDatabase || aOp->mRunnable, "No runnable on this op!");
NS_ASSERTION(!aDatabase || aOp->mDatabases.IsEmpty(),
"This op isn't ready to run!");
// If the helper has a transaction, dispatch it to the transaction
// threadpool.
if (aHelper->HasTransaction()) {
rv = aHelper->DispatchToTransactionPool();
FileService* service = FileService::Get();
TransactionThreadPool* pool = TransactionThreadPool::Get();
nsTArray<nsRefPtr<IDBDatabase> > databases;
if (aDatabase) {
if (service || pool) {
databases.AppendElement(aDatabase);
}
}
else {
// Otherwise, dispatch it to the IO thread.
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
NS_ASSERTION(manager, "We should definitely have a manager here");
rv = aHelper->Dispatch(manager->IOThread());
aOp->mDatabases.SwapElements(databases);
}
NS_ENSURE_SUCCESS(rv, rv);
return rv;
}
PRUint32 waitCount = service && pool && !databases.IsEmpty() ? 2 : 1;
bool
IndexedDatabaseManager::IsClearOriginPending(const nsACString& origin)
{
// Iterate through our SynchronizedOps to see if we have an entry that matches
// this origin and has no id.
PRUint32 count = mSynchronizedOps.Length();
for (PRUint32 index = 0; index < count; index++) {
nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
if (op->mOrigin.Equals(origin) && !op->mId) {
return true;
nsRefPtr<WaitForTransactionsToFinishRunnable> runnable =
new WaitForTransactionsToFinishRunnable(aOp, waitCount);
// There's no point in delaying if we don't yet have a transaction thread pool
// or a file service. Also, if we're not waiting on any databases then we can
// also run immediately.
if (!(service || pool) || databases.IsEmpty()) {
nsresult rv = runnable->Run();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// Ask each service to call us back when they're done with this database.
if (service) {
// Have to copy here in case the pool needs a list too.
nsTArray<nsCOMPtr<nsIFileStorage> > array;
array.AppendElements(databases);
if (!service->WaitForAllStoragesToComplete(array, runnable)) {
NS_WARNING("Failed to wait for storages to complete!");
return NS_ERROR_FAILURE;
}
}
return false;
if (pool && !pool->WaitForAllDatabasesToComplete(databases, runnable)) {
NS_WARNING("Failed to wait for databases to complete!");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
@ -1237,33 +1232,31 @@ IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
}
// Queue up the origin clear runnable.
nsRefPtr<OriginClearRunnable> runnable =
new OriginClearRunnable(origin, mIOThread);
nsRefPtr<OriginClearRunnable> runnable = new OriginClearRunnable(origin);
rv = WaitForOpenAllowed(origin, nsnull, runnable);
NS_ENSURE_SUCCESS(rv, rv);
// Give the runnable some help by invalidating any databases in the way.
// We need to grab references to any live databases here to prevent them from
runnable->AdvanceState();
// Give the runnable some help by invalidating any databases in the way. We
// need to grab references to any live databases here to prevent them from
// dying while we invalidate them.
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
// Grab all live databases for this origin.
nsTArray<IDBDatabase*>* array;
if (mLiveDatabases.Get(origin, &array)) {
liveDatabases.AppendElements(*array);
}
// Invalidate all the live databases first.
for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
liveDatabases[index]->Invalidate();
}
DatabaseInfo::RemoveAllForOrigin(origin);
// After everything has been invalidated the helper should be dispatched to
// the end of the event queue.
return NS_OK;
}
@ -1374,60 +1367,105 @@ IndexedDatabaseManager::Observe(nsISupports* aSubject,
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::OriginClearRunnable,
nsIRunnable)
// static
void
IndexedDatabaseManager::
OriginClearRunnable::InvalidateOpenedDatabases(
nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
void* aClosure)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
OriginClearRunnable* self = static_cast<OriginClearRunnable*>(aClosure);
nsTArray<nsRefPtr<IDBDatabase> > databases;
databases.SwapElements(aDatabases);
for (PRUint32 index = 0; index < databases.Length(); index++) {
databases[index]->Invalidate();
}
DatabaseInfo::RemoveAllForOrigin(self->mOrigin);
}
NS_IMETHODIMP
IndexedDatabaseManager::OriginClearRunnable::Run()
{
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never fail!");
if (NS_IsMainThread()) {
// On the first time on the main thread we dispatch to the IO thread.
if (mFirstCallback) {
NS_ASSERTION(mThread, "Should have a thread here!");
switch (mCallbackState) {
case Pending: {
NS_NOTREACHED("Should never get here without being dispatched!");
return NS_ERROR_UNEXPECTED;
}
mFirstCallback = false;
case OpenAllowed: {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
nsCOMPtr<nsIThread> thread;
mThread.swap(thread);
AdvanceState();
// Dispatch to the IO thread.
if (NS_FAILED(thread->Dispatch(this, NS_DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch to IO thread!");
// Now we have to wait until the thread pool is done with all of the
// databases we care about.
nsresult rv =
mgr->AcquireExclusiveAccess(mOrigin, this, InvalidateOpenedDatabases,
this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
case IO: {
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
AdvanceState();
// Remove the directory that contains all our databases.
nsCOMPtr<nsIFile> directory;
nsresult rv =
mgr->GetDirectoryForOrigin(mOrigin, getter_AddRefs(directory));
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to get directory to remove!");
if (NS_SUCCEEDED(rv)) {
bool exists;
rv = directory->Exists(&exists);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
"Failed to check that the directory exists!");
if (NS_SUCCEEDED(rv) && exists && NS_FAILED(directory->Remove(true))) {
// This should never fail if we've closed all database connections
// correctly...
NS_ERROR("Failed to remove directory!");
}
}
// Now dispatch back to the main thread.
if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch to main thread!");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_ASSERTION(!mThread, "Should have been cleared already!");
case Complete: {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mgr->InvalidateFileManagersForOrigin(mOrigin);
mgr->InvalidateFileManagersForOrigin(mOrigin);
// Tell the IndexedDatabaseManager that we're done.
mgr->AllowNextSynchronizedOp(mOrigin, nsnull);
// Tell the IndexedDatabaseManager that we're done.
mgr->AllowNextSynchronizedOp(mOrigin, nsnull);
return NS_OK;
}
NS_ASSERTION(!mThread, "Should have been cleared already!");
// Remove the directory that contains all our databases.
nsCOMPtr<nsIFile> directory;
nsresult rv = mgr->GetDirectoryForOrigin(mOrigin, getter_AddRefs(directory));
if (NS_SUCCEEDED(rv)) {
bool exists;
rv = directory->Exists(&exists);
if (NS_SUCCEEDED(rv) && exists) {
rv = directory->Remove(true);
return NS_OK;
}
default:
NS_ERROR("Unknown state value!");
return NS_ERROR_UNEXPECTED;
}
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to remove directory!");
// Switch back to the main thread to complete the sequence.
rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
NS_NOTREACHED("Should never get here!");
return NS_ERROR_UNEXPECTED;
}
IndexedDatabaseManager::AsyncUsageRunnable::AsyncUsageRunnable(
@ -1591,14 +1629,16 @@ IndexedDatabaseManager::AsyncUsageRunnable::Run()
return NS_OK;
}
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::WaitForTransactionsToFinishRunnable,
nsIRunnable)
NS_IMPL_THREADSAFE_ISUPPORTS1(
IndexedDatabaseManager::WaitForTransactionsToFinishRunnable,
nsIRunnable)
NS_IMETHODIMP
IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(mOp && mOp->mHelper, "What?");
NS_ASSERTION(mOp, "Null op!");
NS_ASSERTION(mOp->mHelper || mOp->mRunnable, "Nothing to run!");
NS_ASSERTION(mCountdown, "Wrong countdown!");
if (--mCountdown) {
@ -1609,18 +1649,40 @@ IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run()
nsRefPtr<AsyncConnectionHelper> helper;
helper.swap(mOp->mHelper);
nsCOMPtr<nsIRunnable> runnable;
runnable.swap(mOp->mRunnable);
mOp = nsnull;
IndexedDatabaseManager::DispatchHelper(helper);
nsresult rv;
// The helper is responsible for calling
if (helper && helper->HasTransaction()) {
// If the helper has a transaction, dispatch it to the transaction
// threadpool.
rv = helper->DispatchToTransactionPool();
NS_ENSURE_SUCCESS(rv, rv);
}
else {
// Otherwise, dispatch it to the IO thread.
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
NS_ASSERTION(manager, "We should definitely have a manager here");
nsIEventTarget* target = manager->IOThread();
rv = helper ?
helper->Dispatch(target) :
target->Dispatch(runnable, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
}
// The helper or runnable is responsible for calling
// IndexedDatabaseManager::AllowNextSynchronizedOp.
return NS_OK;
}
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::WaitForLockedFilesToFinishRunnable,
nsIRunnable)
NS_IMPL_THREADSAFE_ISUPPORTS1(
IndexedDatabaseManager::WaitForLockedFilesToFinishRunnable,
nsIRunnable)
NS_IMETHODIMP
IndexedDatabaseManager::WaitForLockedFilesToFinishRunnable::Run()
@ -1632,8 +1694,9 @@ IndexedDatabaseManager::WaitForLockedFilesToFinishRunnable::Run()
return NS_OK;
}
IndexedDatabaseManager::SynchronizedOp::SynchronizedOp(const nsACString& aOrigin,
nsIAtom* aId)
IndexedDatabaseManager::
SynchronizedOp::SynchronizedOp(const nsACString& aOrigin,
nsIAtom* aId)
: mOrigin(aOrigin), mId(aId)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");

View File

@ -7,10 +7,7 @@
#ifndef mozilla_dom_indexeddb_indexeddatabasemanager_h__
#define mozilla_dom_indexeddb_indexeddatabasemanager_h__
#include "mozilla/dom/indexedDB/FileManager.h"
#include "mozilla/dom/indexedDB/IndexedDatabase.h"
#include "mozilla/dom/indexedDB/IDBDatabase.h"
#include "mozilla/dom/indexedDB/IDBRequest.h"
#include "mozilla/Mutex.h"
@ -27,14 +24,17 @@
#define INDEXEDDB_MANAGER_CONTRACTID "@mozilla.org/dom/indexeddb/manager;1"
class mozIStorageQuotaCallback;
class nsIAtom;
class nsIFile;
class nsITimer;
class nsPIDOMWindow;
BEGIN_INDEXEDDB_NAMESPACE
class AsyncConnectionHelper;
class CheckQuotaHelper;
class FileManager;
class IDBDatabase;
class IndexedDatabaseManager MOZ_FINAL : public nsIIndexedDatabaseManager,
public nsIObserver
@ -74,26 +74,29 @@ public:
static bool IsClosed();
typedef void (*WaitingOnDatabasesCallback)(nsTArray<nsRefPtr<IDBDatabase> >&, void*);
typedef void
(*WaitingOnDatabasesCallback)(nsTArray<nsRefPtr<IDBDatabase> >&, void*);
// Acquire exclusive access to the database given (waits for all others to
// close). If databases need to close first, the callback will be invoked
// with an array of said databases.
nsresult AcquireExclusiveAccess(IDBDatabase* aDatabase,
const nsACString& aOrigin,
AsyncConnectionHelper* aHelper,
WaitingOnDatabasesCallback aCallback,
void* aClosure)
{
NS_ASSERTION(aDatabase, "Need a DB here!");
return AcquireExclusiveAccess(aDatabase->Origin(), aDatabase, aHelper,
return AcquireExclusiveAccess(aOrigin, aDatabase, aHelper, nsnull,
aCallback, aClosure);
}
nsresult AcquireExclusiveAccess(const nsACString& aOrigin,
AsyncConnectionHelper* aHelper,
nsresult AcquireExclusiveAccess(const nsACString& aOrigin,
nsIRunnable* aRunnable,
WaitingOnDatabasesCallback aCallback,
void* aClosure)
{
return AcquireExclusiveAccess(aOrigin, nsnull, aHelper, aCallback,
return AcquireExclusiveAccess(aOrigin, nsnull, nsnull, aRunnable, aCallback,
aClosure);
}
@ -197,9 +200,10 @@ private:
IndexedDatabaseManager();
~IndexedDatabaseManager();
nsresult AcquireExclusiveAccess(const nsACString& aOrigin,
nsresult AcquireExclusiveAccess(const nsACString& aOrigin,
IDBDatabase* aDatabase,
AsyncConnectionHelper* aHelper,
nsIRunnable* aRunnable,
WaitingOnDatabasesCallback aCallback,
void* aClosure);
@ -226,23 +230,54 @@ private:
// IndexedDatabaseManager that the job has been completed.
class OriginClearRunnable MOZ_FINAL : public nsIRunnable
{
enum CallbackState {
// Not yet run.
Pending = 0,
// Running on the main thread in the callback for OpenAllowed.
OpenAllowed,
// Running on the IO thread.
IO,
// Running on the main thread after all work is done.
Complete
};
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIRUNNABLE
OriginClearRunnable(const nsACString& aOrigin,
nsIThread* aThread)
OriginClearRunnable(const nsACString& aOrigin)
: mOrigin(aOrigin),
mThread(aThread),
mFirstCallback(true)
mCallbackState(Pending)
{ }
nsCString mOrigin;
nsCOMPtr<nsIThread> mThread;
bool mFirstCallback;
};
void AdvanceState()
{
switch (mCallbackState) {
case Pending:
mCallbackState = OpenAllowed;
return;
case OpenAllowed:
mCallbackState = IO;
return;
case IO:
mCallbackState = Complete;
return;
default:
NS_NOTREACHED("Can't advance past Complete!");
}
}
bool IsClearOriginPending(const nsACString& origin);
static void InvalidateOpenedDatabases(
nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
void* aClosure);
private:
nsCString mOrigin;
CallbackState mCallbackState;
};
// Responsible for calculating the amount of space taken up by databases of a
// certain origin. Created when nsIIDBIndexedDatabaseManager::GetUsageForURI
@ -301,6 +336,7 @@ private:
const nsCString mOrigin;
nsCOMPtr<nsIAtom> mId;
nsRefPtr<AsyncConnectionHelper> mHelper;
nsCOMPtr<nsIRunnable> mRunnable;
nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
nsTArray<nsRefPtr<IDBDatabase> > mDatabases;
};
@ -316,7 +352,8 @@ private:
{
NS_ASSERTION(mOp, "Why don't we have a runnable?");
NS_ASSERTION(mOp->mDatabases.IsEmpty(), "We're here too early!");
NS_ASSERTION(mOp->mHelper, "What are we supposed to do when we're done?");
NS_ASSERTION(mOp->mHelper || mOp->mRunnable,
"What are we supposed to do when we're done?");
NS_ASSERTION(mCountdown, "Wrong countdown!");
}
@ -361,7 +398,26 @@ private:
nsString mFilePath;
};
static nsresult DispatchHelper(AsyncConnectionHelper* aHelper);
static nsresult RunSynchronizedOp(IDBDatabase* aDatabase,
SynchronizedOp* aOp);
SynchronizedOp* FindSynchronizedOp(const nsACString& aOrigin,
nsIAtom* aId)
{
for (PRUint32 index = 0; index < mSynchronizedOps.Length(); index++) {
const nsAutoPtr<SynchronizedOp>& currentOp = mSynchronizedOps[index];
if (currentOp->mOrigin == aOrigin &&
(!currentOp->mId || currentOp->mId == aId)) {
return currentOp;
}
}
return nsnull;
}
bool IsClearOriginPending(const nsACString& aOrigin)
{
return !!FindSynchronizedOp(aOrigin, nsnull);
}
// Maintains a list of live databases per origin.
nsClassHashtable<nsCStringHashKey, nsTArray<IDBDatabase*> > mLiveDatabases;

View File

@ -1880,15 +1880,14 @@ OpenDatabaseHelper::StartSetVersion()
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never be null!");
rv = mgr->AcquireExclusiveAccess(mDatabase, helper,
&VersionChangeEventsRunnable::QueueVersionChange<SetVersionHelper>,
rv = mgr->AcquireExclusiveAccess(mDatabase, mDatabase->Origin(), helper,
&VersionChangeEventsRunnable::QueueVersionChange<SetVersionHelper>,
helper);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
// The SetVersionHelper is responsible for dispatching us back to the
// main thread again and changing the state to eSetVersionCompleted.
mState = eSetVersionPending;
return NS_OK;
}
@ -1910,8 +1909,8 @@ OpenDatabaseHelper::StartDelete()
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never be null!");
rv = mgr->AcquireExclusiveAccess(mDatabase, helper,
&VersionChangeEventsRunnable::QueueVersionChange<DeleteDatabaseHelper>,
rv = mgr->AcquireExclusiveAccess(mDatabase, mDatabase->Origin(), helper,
&VersionChangeEventsRunnable::QueueVersionChange<DeleteDatabaseHelper>,
helper);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@ -2136,7 +2135,7 @@ OpenDatabaseHelper::NotifySetVersionFinished()
NS_ASSERTION(mState = eSetVersionPending, "How did we get here?");
mState = eSetVersionCompleted;
// Dispatch ourself back to the main thread
return NS_DispatchToCurrentThread(this);
}

View File

@ -104,6 +104,38 @@ public:
DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE;
};
class VersionChangeRunnable : public nsRunnable
{
nsRefPtr<IDBDatabase> mDatabase;
uint64_t mOldVersion;
uint64_t mNewVersion;
public:
VersionChangeRunnable(IDBDatabase* aDatabase, const uint64_t& aOldVersion,
const uint64_t& aNewVersion)
: mDatabase(aDatabase), mOldVersion(aOldVersion), mNewVersion(aNewVersion)
{
MOZ_ASSERT(aDatabase);
}
NS_IMETHOD Run() MOZ_OVERRIDE
{
if (mDatabase->IsClosed()) {
return NS_OK;
}
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
MOZ_ASSERT(event);
bool dummy;
nsresult rv = mDatabase->DispatchEvent(event, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
};
} // anonymous namespace
/*******************************************************************************
@ -362,12 +394,13 @@ IndexedDBDatabaseChild::RecvBlocked(const uint64_t& aOldVersion)
MOZ_ASSERT(mRequest);
MOZ_ASSERT(!mDatabase);
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::CreateBlocked(aOldVersion, mVersion);
nsCOMPtr<nsIRunnable> runnable =
IDBVersionChangeEvent::CreateBlockedRunnable(aOldVersion, mVersion,
mRequest);
bool dummy;
if (NS_FAILED(mRequest->DispatchEvent(event, &dummy))) {
NS_WARNING("Failed to dispatch blocked event!");
MainThreadEventTarget target;
if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
NS_WARNING("Dispatch of blocked event failed!");
}
return true;
@ -379,16 +412,12 @@ IndexedDBDatabaseChild::RecvVersionChange(const uint64_t& aOldVersion,
{
MOZ_ASSERT(mDatabase);
if (mDatabase->IsClosed()) {
return true;
}
nsCOMPtr<nsIRunnable> runnable =
new VersionChangeRunnable(mDatabase, aOldVersion, aNewVersion);
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::Create(aOldVersion, aNewVersion);
bool dummy;
if (NS_FAILED(mDatabase->DispatchEvent(event, &dummy))) {
NS_WARNING("Failed to dispatch blocked event!");
MainThreadEventTarget target;
if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
NS_WARNING("Dispatch of versionchange event failed!");
}
return true;
@ -503,18 +532,7 @@ IndexedDBTransactionChild::SetTransaction(IDBTransaction* aTransaction)
}
void
IndexedDBTransactionChild::ActorDestroy(ActorDestroyReason aWhy)
{
if (mTransaction) {
mTransaction->SetActor(static_cast<IndexedDBTransactionChild*>(NULL));
#ifdef DEBUG
mTransaction = NULL;
#endif
}
}
bool
IndexedDBTransactionChild::RecvComplete(const nsresult& aRv)
IndexedDBTransactionChild::FireCompleteEvent(nsresult aRv)
{
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(mStrongTransaction);
@ -530,10 +548,36 @@ IndexedDBTransactionChild::RecvComplete(const nsresult& aRv)
// }
nsRefPtr<CommitHelper> helper = new CommitHelper(transaction, aRv);
if (NS_FAILED(helper->Run())) {
NS_WARNING("CommitHelper failed!");
MainThreadEventTarget target;
if (NS_FAILED(target.Dispatch(helper, NS_DISPATCH_NORMAL))) {
NS_WARNING("Dispatch of CommitHelper failed!");
}
}
void
IndexedDBTransactionChild::ActorDestroy(ActorDestroyReason aWhy)
{
if (mStrongTransaction) {
// We're being torn down before we received a complete event from the parent
// so fake one here.
FireCompleteEvent(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
MOZ_ASSERT(!mStrongTransaction);
}
if (mTransaction) {
mTransaction->SetActor(static_cast<IndexedDBTransactionChild*>(NULL));
#ifdef DEBUG
mTransaction = NULL;
#endif
}
}
bool
IndexedDBTransactionChild::RecvComplete(const nsresult& aRv)
{
FireCompleteEvent(aRv);
return true;
}
@ -1059,12 +1103,13 @@ IndexedDBDeleteDatabaseRequestChild::RecvBlocked(
{
MOZ_ASSERT(mOpenRequest);
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::CreateBlocked(aCurrentVersion, 0);
nsCOMPtr<nsIRunnable> runnable =
IDBVersionChangeEvent::CreateBlockedRunnable(aCurrentVersion, 0,
mOpenRequest);
bool dummy;
if (NS_FAILED(mOpenRequest->DispatchEvent(event, &dummy))) {
NS_WARNING("Failed to dispatch blocked event!");
MainThreadEventTarget target;
if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
NS_WARNING("Dispatch of blocked event failed!");
}
return true;

View File

@ -155,6 +155,9 @@ public:
}
protected:
void
FireCompleteEvent(nsresult aRv);
virtual void
ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;

View File

@ -31,18 +31,14 @@ LOCAL_INCLUDES += \
DEFINES += -D_IMPL_NS_LAYOUT
# Test is disabled for the moment to investigate OS X 10.7 crash.
#
#TEST_FILES = \
# test_ipc.html \
# $(NULL)
TEST_FILES = \
test_ipc.html \
$(NULL)
include $(topsrcdir)/config/config.mk
include $(topsrcdir)/ipc/chromium/chromium-config.mk
include $(topsrcdir)/config/rules.mk
# Test is disabled for the moment to investigate OS X 10.7 crash.
#
#libs:: $(TEST_FILES)
# $(INSTALL) $(foreach f,$^,"$f") \
# $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
libs:: $(TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") \
$(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)

View File

@ -12,7 +12,17 @@
SimpleTest.waitForExplicitFinish();
// This isn't a single test, really... It runs the entirety of the IndexedDB
// tests. Each of those has a normal timeout handler, so there's no point in
// having a timeout here. I'm setting this really high just to avoid getting
// killed.
SimpleTest.requestLongerTimeout(100);
function iframeScriptFirst() {
// Disable this functionality, it breaks later tests.
SpecialPowers.prototype.registerProcessCrashObservers = function() { };
SpecialPowers.prototype.unregisterProcessCrashObservers = function() { };
content.wrappedJSObject.RunSet.reloadAndRunAll({
preventDefault: function() { }
});
@ -41,7 +51,11 @@
}
}
let regex = /^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL|TEST-DEBUG-INFO) \| ([^\|]+) \|(.*)/;
let regexString =
"^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL" +
"|TEST-DEBUG-INFO) \\| ([^\\|]+) \\|(.*)";
let regex = new RegExp(regexString);
function onTestMessage(data) {
let message = data.json.msg;
@ -69,19 +83,8 @@
}
function onTestComplete() {
let comp = SpecialPowers.wrap(Components);
let idbManager =
comp.classes["@mozilla.org/dom/indexeddb/manager;1"]
.getService(comp.interfaces.nsIIndexedDatabaseManager);
let uri = SpecialPowers.getDocumentURIObject(document);
idbManager.clearDatabasesForURI(uri);
idbManager.getUsageForURI(uri, function(uri, usage, fileUsage) {
is(usage, 0, "Cleared ipc databases properly");
SimpleTest.executeSoon(function () { SimpleTest.finish(); });
});
ok(true, "Got test complete message");
SimpleTest.executeSoon(function () { SimpleTest.finish(); });
}
function runTests() {

View File

@ -15,7 +15,6 @@ XPCSHELL_TESTS = unit
include $(topsrcdir)/config/rules.mk
TEST_FILES = \
bfcache_iframe1.html \
bfcache_iframe2.html \

View File

@ -10,9 +10,39 @@ function executeSoon(aFun)
SimpleTest.executeSoon(aFun);
}
function clearAllDatabases(callback) {
function runCallback() {
SimpleTest.executeSoon(function () { callback(); });
}
if (!SpecialPowers.isMainProcess()) {
runCallback();
return;
}
let comp = SpecialPowers.wrap(Components);
let idbManager =
comp.classes["@mozilla.org/dom/indexeddb/manager;1"]
.getService(comp.interfaces.nsIIndexedDatabaseManager);
let uri = SpecialPowers.getDocumentURIObject(document);
idbManager.clearDatabasesForURI(uri);
idbManager.getUsageForURI(uri, function(uri, usage, fileUsage) {
if (usage) {
throw new Error("getUsageForURI returned non-zero usage after " +
"clearing all databases!");
}
runCallback();
});
}
if (!window.runTest) {
window.runTest = function(limitedQuota)
{
SimpleTest.waitForExplicitFinish();
allowIndexedDB();
if (limitedQuota) {
denyUnlimitedQuota();
@ -21,8 +51,7 @@ if (!window.runTest) {
allowUnlimitedQuota();
}
SimpleTest.waitForExplicitFinish();
testGenerator.next();
clearAllDatabases(function () { testGenerator.next(); });
}
}
@ -33,7 +62,7 @@ function finishTest()
SimpleTest.executeSoon(function() {
testGenerator.close();
SimpleTest.finish();
clearAllDatabases(function() { SimpleTest.finish(); });
});
}

View File

@ -9,7 +9,98 @@
<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" src="unit/test_writer_starvation.js"></script>
<script type="text/javascript;version=1.7">
function testSteps()
{
const name = window.location.pathname;
// Needs to be enough to saturate the thread pool.
const SYNC_REQUEST_COUNT = 25;
let request = mozIndexedDB.open(name, 1);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
db.onerror = errorHandler;
is(event.target.transaction.mode, "versionchange", "Correct mode");
let objectStore = db.createObjectStore("foo", { autoIncrement: true });
request = objectStore.add({});
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
let key = event.target.result;
ok(key, "Got a key");
yield;
let continueReading = true;
let readerCount = 0;
let writerCount = 0;
let callbackCount = 0;
// Generate a bunch of reads right away without returning to the event
// loop.
info("Generating " + SYNC_REQUEST_COUNT + " readonly requests");
for (let i = 0; i < SYNC_REQUEST_COUNT; i++) {
readerCount++;
let request = db.transaction("foo").objectStore("foo").get(key);
request.onsuccess = function(event) {
is(event.target.transaction.mode, "readonly", "Correct mode");
callbackCount++;
};
}
while (continueReading) {
readerCount++;
info("Generating additional readonly request (" + readerCount + ")");
let request = db.transaction("foo").objectStore("foo").get(key);
request.onsuccess = function(event) {
callbackCount++;
info("Received readonly request callback (" + callbackCount + ")");
is(event.target.transaction.mode, "readonly", "Correct mode");
if (callbackCount == SYNC_REQUEST_COUNT) {
writerCount++;
info("Generating 1 readwrite request with " + readerCount +
" previous readonly requests");
let request = db.transaction("foo", "readwrite")
.objectStore("foo")
.add({}, readerCount);
request.onsuccess = function(event) {
callbackCount++;
info("Received readwrite request callback (" + callbackCount + ")");
is(event.target.transaction.mode, "readwrite", "Correct mode");
is(event.target.result, callbackCount,
"write callback came before later reads");
}
}
else if (callbackCount == SYNC_REQUEST_COUNT + 5) {
continueReading = false;
}
};
setTimeout(function() { testGenerator.next(); }, writerCount ? 1000 : 100);
yield;
}
while (callbackCount < (readerCount + writerCount)) {
executeSoon(function() { testGenerator.next(); });
yield;
}
is(callbackCount, readerCount + writerCount, "All requests accounted for");
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>

View File

@ -62,10 +62,8 @@ TEST_FILES = \
test_transaction_lifetimes.js \
test_transaction_lifetimes_nested.js \
test_transaction_ordering.js \
test_writer_starvation.js \
$(NULL)
libs:: $(TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)

View File

@ -57,8 +57,8 @@ function testSteps()
ok(!(event.newVersion === 0), "newVersion should be null");
db.close();
db2.close();
db.onversionchange = errorHandler;
db2.onversionchange = errorHandler;
db.onversionchange = unexpectedSuccessHandler;
db2.onversionchange = unexpectedSuccessHandler;
};
// The IDB spec doesn't guarantee the order that onversionchange will fire

View File

@ -11,7 +11,7 @@ function testSteps()
const description = "My Test Database";
let request = mozIndexedDB.open(name, 1, description);
request.onerror = grabEventAndContinueHandler;
request.onerror = errorHandler;
request.onsuccess = unexpectedSuccessHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
let event = yield;
@ -38,7 +38,11 @@ function testSteps()
is(db.version, 1, "Correct version");
is(db.objectStoreNames.length, 1, "Correct objectStoreNames length");
request.onerror = grabEventAndContinueHandler;
request.onupgradeneeded = unexpectedSuccessHandler;
event = yield;
is(event.type, "error", "Got request error event");
is(event.target, request, "Right target");
is(event.target.transaction, null, "No transaction");
@ -46,19 +50,27 @@ function testSteps()
event.preventDefault();
request = mozIndexedDB.open(name, 1, description);
request.onerror = grabEventAndContinueHandler;
request.onerror = errorHandler;
request.onsuccess = unexpectedSuccessHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
let event = yield;
event = yield;
is(event.type, "upgradeneeded", "Got upgradeneeded event");
let db2 = event.target.result;
isnot(db, db2, "Should give a different db instance");
is(db2.version, 1, "Correct version");
is(db2.objectStoreNames.length, 0, "Correct objectStoreNames length");
request.onsuccess = grabEventAndContinueHandler;
yield;
request.onupgradeneeded = unexpectedSuccessHandler;
event = yield;
is(event.target.result, db2, "Correct target");
is(event.type, "success", "Got success event");
is(db2.version, 1, "Correct version");
is(db2.objectStoreNames.length, 0, "Correct objectStoreNames length");
finishTest();
yield;

View File

@ -17,6 +17,7 @@ function testSteps()
let request2 = mozIndexedDB.open(name, 2);
request2.onerror = errorHandler;
request2.onupgradeneeded = unexpectedSuccessHandler;
request2.onsuccess = unexpectedSuccessHandler;
let event = yield;
is(event.type, "upgradeneeded", "Expect an upgradeneeded event");
@ -41,9 +42,11 @@ function testSteps()
is(e.code, DOMException.INVALID_STATE_ERR, "Expect an INVALID_STATE_ERR");
}
request.onupgradeneeded = unexpectedSuccessHandler;
request.transaction.oncomplete = grabEventAndContinueHandler;
yield;
event = yield;
is(event.type, "complete", "Got complete event");
// The database is still not fully open here.
try {
@ -57,7 +60,9 @@ function testSteps()
request.onsuccess = grabEventAndContinueHandler;
yield;
event = yield;
is(event.type, "success", "Expect a success event");
is(event.target.result, db, "Same database");
db.onversionchange = function() {
ok(true, "next setVersion was unblocked appropriately");
@ -71,10 +76,22 @@ function testSteps()
ok(false, "Transactions should be allowed now!");
}
request2.onupgradeneeded = null;
request.onsuccess = unexpectedSuccessHandler;
request2.onupgradeneeded = grabEventAndContinueHandler;
event = yield;
is(event.type, "upgradeneeded", "Expect an upgradeneeded event");
db = event.target.result;
is(db.version, 2, "Database has correct version");
request2.onupgradeneeded = unexpectedSuccessHandler;
request2.onsuccess = grabEventAndContinueHandler;
yield;
event = yield;
is(event.type, "success", "Expect a success event");
is(event.target.result, db, "Same database");
is(db.version, 2, "Database has correct version");
finishTest();
yield;

View File

@ -1,90 +0,0 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var testGenerator = testSteps();
function testSteps()
{
const name = this.window ? window.location.pathname : "Splendid Test";
const description = "My Test Database";
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
is(event.target.transaction.mode, "versionchange", "Correct mode");
let objectStore = db.createObjectStore("foo", { autoIncrement: true });
request = objectStore.add({});
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
let key = event.target.result;
ok(key, "Got a key");
yield;
let continueReading = true;
let readerCount = 0;
let callbackCount = 0;
let finalCallbackCount = 0;
// Generate a bunch of reads right away without returning to the event
// loop.
for (let i = 0; i < 20; i++) {
readerCount++;
request = db.transaction("foo").objectStore("foo").get(key);
request.onerror = errorHandler;
request.onsuccess = function(event) {
callbackCount++;
};
}
while (continueReading) {
readerCount++;
request = db.transaction("foo").objectStore("foo").get(key);
request.onerror = errorHandler;
request.onsuccess = function(event) {
is(event.target.transaction.mode, "readonly", "Correct mode");
callbackCount++;
if (callbackCount == 100) {
request = db.transaction("foo", "readwrite")
.objectStore("foo")
.add({}, readerCount);
request.onerror = errorHandler;
request.onsuccess = function(event) {
continueReading = false;
finalCallbackCount = callbackCount;
is(event.target.result, callbackCount,
"write callback came before later reads");
}
}
};
executeSoon(function() { testGenerator.next(); });
yield;
}
while (callbackCount < readerCount) {
executeSoon(function() { testGenerator.next(); });
yield;
}
is(callbackCount, readerCount, "All requests accounted for");
ok(callbackCount > finalCallbackCount, "More readers after writer");
finishTest();
yield;
}
if (this.window)
SimpleTest.requestLongerTimeout(5); // see bug 580875

View File

@ -51,5 +51,4 @@ tail =
[test_transaction_abort.js]
[test_transaction_lifetimes.js]
[test_transaction_lifetimes_nested.js]
[test_transaction_ordering.js]
[test_writer_starvation.js]
[test_transaction_ordering.js]