Bug 697247: Part 2 - Rework IDB synchronization mechanisms. r=bent

This commit is contained in:
Kyle Huey 2011-11-02 08:53:12 -04:00
parent 9951eda078
commit 3140fc9fd4
13 changed files with 579 additions and 529 deletions

View File

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

View File

@ -206,7 +206,7 @@ CheckPermissionsHelper::Observe(nsISupports* aSubject,
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never be null!");
rv = mgr->WaitForOpenAllowed(mName, mASCIIOrigin, this);
rv = NS_DispatchToCurrentThread(this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;

View File

@ -64,25 +64,21 @@ public:
CheckPermissionsHelper(OpenDatabaseHelper* aHelper,
nsIDOMWindow* aWindow,
const nsAString& aName,
const nsACString& aASCIIOrigin)
: mHelper(aHelper),
mWindow(aWindow),
mName(aName),
mASCIIOrigin(aASCIIOrigin),
mHasPrompted(false),
mPromptResult(0)
{
NS_ASSERTION(aHelper, "Null pointer!");
NS_ASSERTION(aWindow, "Null pointer!");
NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
NS_ASSERTION(!aASCIIOrigin.IsEmpty(), "Empty origin!");
}
private:
nsRefPtr<OpenDatabaseHelper> mHelper;
nsCOMPtr<nsIDOMWindow> mWindow;
nsString mName;
nsCString mASCIIOrigin;
bool mHasPrompted;
PRUint32 mPromptResult;

View File

@ -450,10 +450,6 @@ IDBDatabase::ExitSetVersionTransaction()
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

View File

@ -440,12 +440,12 @@ IDBFactory::Open(const nsAString& aName,
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
nsRefPtr<CheckPermissionsHelper> permissionHelper =
new CheckPermissionsHelper(openHelper, window, aName, origin);
new CheckPermissionsHelper(openHelper, window, origin);
nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::GetOrCreate();
NS_ENSURE_TRUE(mgr, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
rv = mgr->WaitForOpenAllowed(aName, origin, permissionHelper);
rv = mgr->WaitForOpenAllowed(origin, openHelper->Id(), permissionHelper);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
request.forget(_retval);

View File

@ -143,134 +143,6 @@ EnumerateToTArray(const nsACString& aKey,
return PL_DHASH_NEXT;
}
// Responsible for calling IDBDatabase.setVersion after a pending version change
// transaction has completed.
class DelayedSetVersion : public nsRunnable
{
public:
DelayedSetVersion(IDBDatabase* aDatabase,
IDBOpenDBRequest* aRequest,
PRInt64 aOldVersion,
PRInt64 aNewVersion,
AsyncConnectionHelper* aHelper)
: mDatabase(aDatabase),
mRequest(aRequest),
mOldVersion(aOldVersion),
mNewVersion(aNewVersion),
mHelper(aHelper)
{
NS_ASSERTION(aDatabase, "Null database!");
NS_ASSERTION(aRequest, "Null request!");
NS_ASSERTION(aHelper, "Null helper!");
}
NS_IMETHOD Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never be null!");
nsresult rv = mgr->SetDatabaseVersion(mDatabase, mRequest,
mOldVersion, mNewVersion,
mHelper);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
private:
nsRefPtr<IDBDatabase> mDatabase;
nsRefPtr<IDBOpenDBRequest> mRequest;
PRInt64 mOldVersion;
PRInt64 mNewVersion;
nsRefPtr<AsyncConnectionHelper> mHelper;
};
// Responsible for firing "versionchange" events at all live and non-closed
// databases, and for firing a "blocked" event at the requesting database if any
// databases fail to close.
class VersionChangeEventsRunnable : public nsRunnable
{
public:
VersionChangeEventsRunnable(
IDBDatabase* aRequestingDatabase,
IDBOpenDBRequest* aRequest,
nsTArray<nsRefPtr<IDBDatabase> >& aWaitingDatabases,
PRInt64 aOldVersion,
PRInt64 aNewVersion)
: mRequestingDatabase(aRequestingDatabase),
mRequest(aRequest),
mOldVersion(aOldVersion),
mNewVersion(aNewVersion)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRequestingDatabase, "Null pointer!");
NS_ASSERTION(aRequest, "Null pointer!");
if (!mWaitingDatabases.SwapElements(aWaitingDatabases)) {
NS_ERROR("This should never fail!");
}
}
NS_IMETHOD Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// Fire version change events at all of the databases that are not already
// closed. Also kick bfcached documents out of bfcache.
for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
if (database->IsClosed()) {
continue;
}
// First check if the document the IDBDatabase is part of is bfcached
nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
nsIBFCacheEntry* bfCacheEntry;
if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
bfCacheEntry->RemoveFromBFCacheSync();
NS_ASSERTION(database->IsClosed(),
"Kicking doc out of bfcache should have closed database");
continue;
}
// Otherwise fire a versionchange event.
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
bool dummy;
database->DispatchEvent(event, &dummy);
}
// Now check to see if any didn't close. If there are some running still
// then fire the blocked event.
for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
if (!mWaitingDatabases[index]->IsClosed()) {
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::CreateBlocked(mOldVersion, mNewVersion);
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
bool dummy;
mRequest->DispatchEvent(event, &dummy);
break;
}
}
return NS_OK;
}
private:
nsRefPtr<IDBDatabase> mRequestingDatabase;
nsRefPtr<IDBOpenDBRequest> mRequest;
nsTArray<nsRefPtr<IDBDatabase> > mWaitingDatabases;
PRInt64 mOldVersion;
PRInt64 mNewVersion;
};
} // anonymous namespace
IndexedDatabaseManager::IndexedDatabaseManager()
@ -425,20 +297,6 @@ IndexedDatabaseManager::UnregisterDatabase(IDBDatabase* aDatabase)
NS_ERROR("Didn't know anything about this database!");
}
void
IndexedDatabaseManager::OnOriginClearComplete(OriginClearRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRunnable, "Null pointer!");
NS_ASSERTION(!aRunnable->mThread, "Thread should be null!");
NS_ASSERTION(aRunnable->mDelayedRunnables.IsEmpty(),
"Delayed runnables should have been dispatched already!");
if (!mOriginClearRunnables.RemoveElement(aRunnable)) {
NS_ERROR("Don't know anything about this runnable!");
}
}
void
IndexedDatabaseManager::OnUsageCheckComplete(AsyncUsageRunnable* aRunnable)
{
@ -452,64 +310,136 @@ IndexedDatabaseManager::OnUsageCheckComplete(AsyncUsageRunnable* aRunnable)
}
}
void
IndexedDatabaseManager::OnSetVersionRunnableComplete(
SetVersionRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRunnable, "Null pointer!");
NS_ASSERTION(aRunnable->mDelayedRunnables.IsEmpty(),
"Delayed runnables should have been dispatched already!");
// Remove this runnable from the list. This will allow other databases to
// begin to request version changes.
if (!mSetVersionRunnables.RemoveElement(aRunnable)) {
NS_ERROR("Don't know anything about this runnable!");
}
}
nsresult
IndexedDatabaseManager::WaitForOpenAllowed(const nsAString& aName,
const nsACString& aOrigin,
IndexedDatabaseManager::WaitForOpenAllowed(const nsACString& aOrigin,
nsIAtom* aId,
nsIRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
NS_ASSERTION(aRunnable, "Null pointer!");
// See if we're currently clearing database files for this origin. If so then
// queue the runnable for later dispatch after we're done clearing.
PRUint32 count = mOriginClearRunnables.Length();
nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOrigin, aId));
// See if this runnable needs to wait.
bool delayed = false;
for (PRUint32 index = mSynchronizedOps.Length(); index > 0; index--) {
nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
if (op->MustWaitFor(*existingOp)) {
existingOp->DelayRunnable(aRunnable);
delayed = true;
break;
}
}
// Otherwise, dispatch it immediately.
if (!delayed) {
nsresult rv = NS_DispatchToCurrentThread(aRunnable);
NS_ENSURE_SUCCESS(rv, rv);
}
// Adding this to the synchronized ops list will block any additional
// ops from proceeding until this one is done.
mSynchronizedOps.AppendElement(op.forget());
return NS_OK;
}
void
IndexedDatabaseManager::AllowNextSynchronizedOp(const nsACString& aOrigin,
nsIAtom* aId)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
PRUint32 count = mSynchronizedOps.Length();
for (PRUint32 index = 0; index < count; index++) {
nsRefPtr<OriginClearRunnable>& data = mOriginClearRunnables[index];
if (data->mOrigin == aOrigin) {
nsCOMPtr<nsIRunnable>* newPtr =
data->mDelayedRunnables.AppendElement(aRunnable);
NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
if (op->mOrigin.Equals(aOrigin)) {
if (op->mId == aId) {
NS_ASSERTION(op->mDatabases.IsEmpty(), "How did this happen?");
return NS_OK;
op->DispatchDelayedRunnables();
mSynchronizedOps.RemoveElementAt(index);
return;
}
// If one or the other is for an origin clear, we should have matched
// solely on origin.
NS_ASSERTION(op->mId && aId, "Why didn't we match earlier?");
}
}
// Check to see if we're currently doing a SetVersion transaction for this
// database. If so then we delay this runnable for later.
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
if (runnable->mRequestingDatabase->Name() == aName &&
runnable->mRequestingDatabase->Origin() == aOrigin) {
nsCOMPtr<nsIRunnable>* newPtr =
runnable->mDelayedRunnables.AppendElement(aRunnable);
NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
NS_NOTREACHED("Why didn't we find a SynchronizedOp?");
}
return NS_OK;
nsresult
IndexedDatabaseManager::AcquireExclusiveAccess(const nsACString& aOrigin,
IDBDatabase* aDatabase,
AsyncConnectionHelper* aHelper,
WaitingOnDatabasesCallback aCallback,
void* aClosure)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aHelper, "Why are you talking to me?");
// 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;
}
}
}
// We aren't currently clearing databases for this origin and we're not
// running a SetVersion transaction for this database so dispatch the runnable
// immediately.
return NS_DispatchToCurrentThread(aRunnable);
NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
nsTArray<IDBDatabase*>* array;
mLiveDatabases.Get(aOrigin, &array);
// We need to wait for the databases to go away.
// Hold on to all database objects that represent the same database file
// (except the one that is requesting this version change).
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 &&
database != aDatabase &&
database->Id() == aDatabase->Id()))) {
liveDatabases.AppendElement(database);
}
}
}
if (liveDatabases.IsEmpty()) {
IndexedDatabaseManager::DispatchHelper(aHelper);
return NS_OK;
}
NS_ASSERTION(op->mDatabases.IsEmpty(), "How do we already have databases here?");
op->mDatabases.AppendElements(liveDatabases);
op->mHelper = aHelper;
// 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;
}
// static
@ -519,107 +449,6 @@ IndexedDatabaseManager::IsShuttingDown()
return !!gShutdown;
}
nsresult
IndexedDatabaseManager::SetDatabaseVersion(IDBDatabase* aDatabase,
IDBOpenDBRequest* aRequest,
PRInt64 aOldVersion,
PRInt64 aNewVersion,
AsyncConnectionHelper* aHelper)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aDatabase, "Null pointer!");
NS_ASSERTION(aHelper, "Null pointer!");
nsresult rv;
// See if another database has already asked to change the version.
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
if (runnable->mRequestingDatabase == aDatabase) {
// Same database, just queue this call to run after the current
// SetVersion transaction completes.
nsRefPtr<DelayedSetVersion> delayed =
new DelayedSetVersion(aDatabase, aRequest, aOldVersion, aNewVersion,
aHelper);
if (!runnable->mDelayedRunnables.AppendElement(delayed)) {
NS_WARNING("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
// Different database, we can't let this one succeed.
aHelper->SetError(NS_ERROR_DOM_INDEXEDDB_DEADLOCK_ERR);
rv = NS_DispatchToCurrentThread(aHelper);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
}
// Grab all live databases for the same origin.
nsTArray<IDBDatabase*>* array;
if (!mLiveDatabases.Get(aDatabase->Origin(), &array)) {
NS_ERROR("Must have some alive if we've got a live argument!");
}
// Hold on to all database objects that represent the same database file
// (except the one that is requesting this version change).
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
for (PRUint32 index = 0; index < array->Length(); index++) {
IDBDatabase*& database = array->ElementAt(index);
if (database != aDatabase &&
database->Id() == aDatabase->Id() &&
!database->IsClosed() &&
!liveDatabases.AppendElement(database)) {
NS_WARNING("Out of memory?");
return NS_ERROR_OUT_OF_MEMORY;
}
}
// Adding an element to this array here will keep other databases from
// requesting a version change.
nsRefPtr<SetVersionRunnable> runnable =
new SetVersionRunnable(aDatabase, liveDatabases);
if (!mSetVersionRunnables.AppendElement(runnable)) {
NS_WARNING("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
// When all databases are closed we want to dispatch the SetVersion
// transaction to the transaction pool.
runnable->mHelper = aHelper;
if (runnable->mDatabases.IsEmpty()) {
// There are no other databases that need to be closed. Go ahead and run
// the transaction now.
RunSetVersionTransaction(aDatabase);
}
else {
// Otherwise we need to wait for all the other databases to complete.
// Schedule a version change events runnable .
nsTArray<nsRefPtr<IDBDatabase> > waitingDatabases;
if (!waitingDatabases.AppendElements(runnable->mDatabases)) {
NS_WARNING("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
}
nsRefPtr<VersionChangeEventsRunnable> eventsRunnable =
new VersionChangeEventsRunnable(aDatabase, aRequest, waitingDatabases,
aOldVersion, aNewVersion);
rv = NS_DispatchToCurrentThread(eventsRunnable);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
void
IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow)
{
@ -676,71 +505,49 @@ IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aDatabase, "Null pointer!");
// Check through the list of SetVersionRunnables we have amassed to see if
// this database is part of a SetVersion callback.
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
// 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 (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
// This is the SetVersionRunnable for the given database file. Remove the
// database from the list of databases that need to be closed. Since we
// use this hook for SetVersion requests that don't actually need to wait
// for other databases the mDatabases array may be empty.
if (!runnable->mDatabases.IsEmpty() &&
!runnable->mDatabases.RemoveElement(aDatabase)) {
NS_ERROR("Didn't have this database in our list!");
}
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.
// Now run the helper if there are no more live databases.
if (runnable->mHelper && runnable->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.
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
if (!pool) {
NS_ERROR("IndexedDB is totally broken.");
return;
}
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
new WaitForTransactionsToFinishRunnable(op);
nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
new WaitForTransactionsToFinishRunnable(runnable);
nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
array.AppendElement(aDatabase);
// All other databases should be closed, so we only need to wait on this
// one.
nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
if (!array.AppendElement(aDatabase)) {
NS_ERROR("This should never fail!");
}
// Use the WaitForTransactionsToFinishRunnable as the callback.
if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
NS_WARNING("Failed to wait for transaction to complete!");
// Use the WaitForTransactionsToFinishRunnable as the callback.
if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
NS_WARNING("Failed to wait for transaction to complete!");
}
}
break;
}
break;
}
}
}
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)
@ -855,6 +662,45 @@ IndexedDatabaseManager::EnsureQuotaManagementForDirectory(nsIFile* aDirectory)
return rv;
}
// static
nsresult
IndexedDatabaseManager::DispatchHelper(AsyncConnectionHelper* aHelper)
{
nsresult rv = NS_OK;
// If the helper has a transaction, dispatch it to the transaction
// threadpool.
if (aHelper->HasTransaction()) {
rv = aHelper->DispatchToTransactionPool();
}
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());
}
NS_ENSURE_SUCCESS(rv, rv);
return rv;
}
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;
}
}
return false;
}
NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
nsIObserver)
@ -890,12 +736,10 @@ IndexedDatabaseManager::GetUsageForURI(
// See if we're currently clearing the databases for this origin. If so then
// we pretend that we've already deleted everything.
for (PRUint32 index = 0; index < mOriginClearRunnables.Length(); index++) {
if (mOriginClearRunnables[index]->mOrigin == origin) {
rv = NS_DispatchToCurrentThread(runnable);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
if (IsClearOriginPending(origin)) {
rv = NS_DispatchToCurrentThread(runnable);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// Otherwise dispatch to the IO thread to actually compute the usage.
@ -949,15 +793,20 @@ IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
return NS_OK;
}
// If we're already clearing out files for this origin then return
// If there is a pending or running clear operation for this origin, return
// immediately.
PRUint32 clearDataCount = mOriginClearRunnables.Length();
for (PRUint32 index = 0; index < clearDataCount; index++) {
if (mOriginClearRunnables[index]->mOrigin == origin) {
return NS_OK;
}
if (IsClearOriginPending(origin)) {
return NS_OK;
}
// Queue up the origin clear runnable.
nsRefPtr<OriginClearRunnable> runnable =
new OriginClearRunnable(origin, mIOThread);
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
// dying while we invalidate them.
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
@ -965,25 +814,7 @@ IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
// Grab all live databases for this origin.
nsTArray<IDBDatabase*>* array;
if (mLiveDatabases.Get(origin, &array)) {
if (!liveDatabases.AppendElements(*array)) {
NS_WARNING("Out of memory?");
return NS_ERROR_OUT_OF_MEMORY;
}
}
nsRefPtr<OriginClearRunnable> runnable =
new OriginClearRunnable(origin, mIOThread);
// Make a new entry for this origin in mOriginClearRunnables.
nsRefPtr<OriginClearRunnable>* newRunnable =
mOriginClearRunnables.AppendElement(runnable);
NS_ENSURE_TRUE(newRunnable, NS_ERROR_OUT_OF_MEMORY);
if (liveDatabases.IsEmpty()) {
rv = runnable->Run();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
liveDatabases.AppendElements(*array);
}
// Invalidate all the live databases first.
@ -991,16 +822,8 @@ IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
liveDatabases[index]->Invalidate();
}
// Now set up our callback so that we know when they have finished.
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
if (!pool->WaitForAllDatabasesToComplete(liveDatabases, runnable)) {
NS_WARNING("Can't wait on databases!");
return NS_ERROR_FAILURE;
}
NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
// After everything has been invalidated the helper should be dispatched to
// the end of the event queue.
return NS_OK;
}
@ -1077,7 +900,7 @@ NS_IMETHODIMP
IndexedDatabaseManager::OriginClearRunnable::Run()
{
if (NS_IsMainThread()) {
// On the first time on the main thread we simply dispatch to the IO thread.
// On the first time on the main thread we dispatch to the IO thread.
if (mFirstCallback) {
NS_ASSERTION(mThread, "Should have a thread here!");
@ -1097,19 +920,11 @@ IndexedDatabaseManager::OriginClearRunnable::Run()
NS_ASSERTION(!mThread, "Should have been cleared already!");
// Dispatch any queued runnables that we collected while we were waiting.
for (PRUint32 index = 0; index < mDelayedRunnables.Length(); index++) {
if (NS_FAILED(NS_DispatchToCurrentThread(mDelayedRunnables[index]))) {
NS_WARNING("Failed to dispatch delayed runnable!");
}
}
mDelayedRunnables.Clear();
// Tell the IndexedDatabaseManager that we're done.
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
if (mgr) {
mgr->OnOriginClearComplete(this);
}
NS_ASSERTION(mgr, "This should never fail!");
mgr->AllowNextSynchronizedOp(mOrigin, nsnull);
return NS_OK;
}
@ -1257,50 +1072,6 @@ IndexedDatabaseManager::AsyncUsageRunnable::Run()
return NS_OK;
}
IndexedDatabaseManager::SetVersionRunnable::SetVersionRunnable(
IDBDatabase* aDatabase,
nsTArray<nsRefPtr<IDBDatabase> >& aDatabases)
: mRequestingDatabase(aDatabase)
{
NS_ASSERTION(aDatabase, "Null database!");
if (!mDatabases.SwapElements(aDatabases)) {
NS_ERROR("This should never fail!");
}
}
IndexedDatabaseManager::SetVersionRunnable::~SetVersionRunnable()
{
}
NS_IMPL_ISUPPORTS1(IndexedDatabaseManager::SetVersionRunnable, nsIRunnable)
NS_IMETHODIMP
IndexedDatabaseManager::SetVersionRunnable::Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!mHelper, "Should have been cleared already!");
// Dispatch any queued runnables that we picked up while waiting for the
// SetVersion transaction to complete.
for (PRUint32 index = 0; index < mDelayedRunnables.Length(); index++) {
if (NS_FAILED(NS_DispatchToCurrentThread(mDelayedRunnables[index]))) {
NS_WARNING("Failed to dispatch delayed runnable!");
}
}
// No need to hold these alive any longer.
mDelayedRunnables.Clear();
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never be null!");
// Let the IndexedDatabaseManager know that the SetVersion transaction has
// completed.
mgr->OnSetVersionRunnableComplete(this);
return NS_OK;
}
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::WaitForTransactionsToFinishRunnable,
nsIRunnable)
@ -1308,31 +1079,84 @@ NS_IMETHODIMP
IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(mOp && mOp->mHelper, "What?");
// Don't hold the callback alive longer than necessary.
nsRefPtr<AsyncConnectionHelper> helper;
helper.swap(mRunnable->mHelper);
helper.swap(mOp->mHelper);
nsRefPtr<SetVersionRunnable> runnable;
runnable.swap(mRunnable);
mOp = nsnull;
// 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());
}
IndexedDatabaseManager::DispatchHelper(helper);
// The helper is responsible for calling
// IndexedDatabaseManager::UnblockSetVersionRunnable.
// IndexedDatabaseManager::AllowNextSynchronizedOp.
return NS_OK;
}
IndexedDatabaseManager::SynchronizedOp::SynchronizedOp(const nsACString& aOrigin,
nsIAtom* aId)
: mOrigin(aOrigin), mId(aId)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
MOZ_COUNT_CTOR(IndexedDatabaseManager::SynchronizedOp);
}
IndexedDatabaseManager::SynchronizedOp::~SynchronizedOp()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
MOZ_COUNT_DTOR(IndexedDatabaseManager::SynchronizedOp);
}
bool
IndexedDatabaseManager::SynchronizedOp::MustWaitFor(const SynchronizedOp& aRhs)
const
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// If the origins don't match, the second can proceed.
if (!aRhs.mOrigin.Equals(mOrigin)) {
return false;
}
// If the origins and the ids match, the second must wait.
if (aRhs.mId == mId) {
return true;
}
// Waiting is required if either one corresponds to an origin clearing
// (a null Id).
if (!aRhs.mId || !mId) {
return true;
}
// Otherwise, things for the same origin but different databases can proceed
// independently.
return false;
}
void
IndexedDatabaseManager::SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(mDelayedRunnables.IsEmpty() || !mId,
"Only ClearOrigin operations can delay multiple runnables!");
mDelayedRunnables.AppendElement(aRunnable);
}
void
IndexedDatabaseManager::SynchronizedOp::DispatchDelayedRunnables()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!mHelper, "Any helper should be gone by now!");
PRUint32 count = mDelayedRunnables.Length();
for (PRUint32 index = 0; index < count; index++) {
NS_DispatchToCurrentThread(mDelayedRunnables[index]);
}
mDelayedRunnables.Clear();
}

View File

@ -81,11 +81,14 @@ public:
NS_DECL_NSIOBSERVER
// Waits for databases to be cleared and for version change transactions to
// complete before dispatching the give runnable.
nsresult WaitForOpenAllowed(const nsAString& aName,
const nsACString& aOrigin,
// complete before dispatching the given runnable.
nsresult WaitForOpenAllowed(const nsACString& aOrigin,
nsIAtom* aId,
nsIRunnable* aRunnable);
void AllowNextSynchronizedOp(const nsACString& aOrigin,
nsIAtom* aId);
nsIThread* IOThread()
{
NS_ASSERTION(mIOThread, "This should never be null!");
@ -95,12 +98,28 @@ public:
// Returns true if we've begun the shutdown process.
static bool IsShuttingDown();
// Begins the process of setting a database version.
nsresult SetDatabaseVersion(IDBDatabase* aDatabase,
IDBOpenDBRequest* aRequest,
PRInt64 aOldVersion,
PRInt64 aNewVersion,
AsyncConnectionHelper* aHelper);
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,
AsyncConnectionHelper* aHelper,
WaitingOnDatabasesCallback aCallback,
void* aClosure)
{
NS_ASSERTION(aDatabase, "Need a DB here!");
return AcquireExclusiveAccess(aDatabase->Origin(), aDatabase, aHelper,
aCallback, aClosure);
}
nsresult AcquireExclusiveAccess(const nsACString& aOrigin,
AsyncConnectionHelper* aHelper,
WaitingOnDatabasesCallback aCallback,
void* aClosure)
{
return AcquireExclusiveAccess(aOrigin, nsnull, aHelper, aCallback,
aClosure);
}
// Called when a window is being purged from the bfcache or the user leaves
// a page which isn't going into the bfcache. Forces any live database
@ -122,6 +141,12 @@ private:
IndexedDatabaseManager();
~IndexedDatabaseManager();
nsresult AcquireExclusiveAccess(const nsACString& aOrigin,
IDBDatabase* aDatabase,
AsyncConnectionHelper* aHelper,
WaitingOnDatabasesCallback aCallback,
void* aClosure);
// Called when a database is created.
bool RegisterDatabase(IDBDatabase* aDatabase);
@ -131,21 +156,14 @@ private:
// Called when a database has been closed.
void OnDatabaseClosed(IDBDatabase* aDatabase);
// Called when a version change transaction can run immediately.
void RunSetVersionTransaction(IDBDatabase* aDatabase)
{
OnDatabaseClosed(aDatabase);
}
// Responsible for clearing the database files for a particular origin on the
// IO thread. Created when nsIIDBIndexedDatabaseManager::ClearDatabasesForURI
// is called. Runs three times, first on the main thread, next on the IO
// thread, and then finally again on the main thread. While on the IO thread
// the runnable will actually remove the origin's database files and the
// directory that contains them before dispatching itself back to the main
// thread. When on the main thread the runnable will dispatch any queued
// runnables and then notify the IndexedDatabaseManager that the job has been
// completed.
// thread. When back on the main thread the runnable will notify the
// IndexedDatabaseManager that the job has been completed.
class OriginClearRunnable : public nsIRunnable
{
public:
@ -161,12 +179,10 @@ private:
nsCString mOrigin;
nsCOMPtr<nsIThread> mThread;
nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
bool mFirstCallback;
};
// Called when OriginClearRunnable has finished its Run() method.
inline void OnOriginClearComplete(OriginClearRunnable* aRunnable);
bool IsClearOriginPending(const nsACString& origin);
// Responsible for calculating the amount of space taken up by databases of a
// certain origin. Created when nsIIDBIndexedDatabaseManager::GetUsageForURI
@ -204,63 +220,59 @@ 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
// main thread when the version change transaction has completed.
class SetVersionRunnable : public nsIRunnable
// A struct that contains the information corresponding to a pending or
// running operation that requires synchronization (e.g. opening a db,
// clearing dbs for an origin, etc).
struct SynchronizedOp
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIRUNNABLE
SynchronizedOp(const nsACString& aOrigin, nsIAtom* aId);
~SynchronizedOp();
SetVersionRunnable(IDBDatabase* aDatabase,
nsTArray<nsRefPtr<IDBDatabase> >& aDatabases);
~SetVersionRunnable();
// Test whether the second SynchronizedOp needs to get behind this one.
bool MustWaitFor(const SynchronizedOp& aRhs) const;
nsRefPtr<IDBDatabase> mRequestingDatabase;
nsTArray<nsRefPtr<IDBDatabase> > mDatabases;
void DelayRunnable(nsIRunnable* aRunnable);
void DispatchDelayedRunnables();
const nsCString mOrigin;
nsCOMPtr<nsIAtom> mId;
nsRefPtr<AsyncConnectionHelper> mHelper;
nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
nsTArray<nsRefPtr<IDBDatabase> > mDatabases;
};
// 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)
WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp)
: mOp(aOp)
{
NS_ASSERTION(mRunnable, "Why don't we have a runnable?");
NS_ASSERTION(mRunnable->mDatabases.IsEmpty(), "We're here too early!");
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_DECL_ISUPPORTS
NS_DECL_NSIRUNNABLE
private:
nsRefPtr<SetVersionRunnable> mRunnable;
// The IndexedDatabaseManager holds this alive.
SynchronizedOp* mOp;
};
static nsresult DispatchHelper(AsyncConnectionHelper* aHelper);
// Maintains a list of live databases per origin.
nsClassHashtable<nsCStringHashKey, nsTArray<IDBDatabase*> > mLiveDatabases;
// Maintains a list of origins that are currently being cleared.
nsAutoTArray<nsRefPtr<OriginClearRunnable>, 1> mOriginClearRunnables;
// Maintains a list of origins that we're currently enumerating to gather
// usage statistics.
nsAutoTArray<nsRefPtr<AsyncUsageRunnable>, 1> mUsageRunnables;
// Maintains a list of SetVersion calls that are in progress.
nsAutoTArray<nsRefPtr<SetVersionRunnable>, 1> mSetVersionRunnables;
// Maintains a list of synchronized operatons that are in progress or queued.
nsAutoTArray<nsAutoPtr<SynchronizedOp>, 5> mSynchronizedOps;
// Thread on which IO is performed.
nsCOMPtr<nsIThread> mIOThread;

View File

@ -498,6 +498,9 @@ public:
nsresult GetSuccessResult(JSContext* aCx,
jsval* aVal);
static
void QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
void* aClosure);
protected:
nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
nsresult Init();
@ -519,6 +522,91 @@ private:
PRUint64 mCurrentVersion;
};
// Responsible for firing "versionchange" events at all live and non-closed
// databases, and for firing a "blocked" event at the requesting database if any
// databases fail to close.
class VersionChangeEventsRunnable : public nsRunnable
{
public:
VersionChangeEventsRunnable(
IDBDatabase* aRequestingDatabase,
IDBOpenDBRequest* aRequest,
nsTArray<nsRefPtr<IDBDatabase> >& aWaitingDatabases,
PRInt64 aOldVersion,
PRInt64 aNewVersion)
: mRequestingDatabase(aRequestingDatabase),
mRequest(aRequest),
mOldVersion(aOldVersion),
mNewVersion(aNewVersion)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRequestingDatabase, "Null pointer!");
NS_ASSERTION(aRequest, "Null pointer!");
if (!mWaitingDatabases.SwapElements(aWaitingDatabases)) {
NS_ERROR("This should never fail!");
}
}
NS_IMETHOD Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// Fire version change events at all of the databases that are not already
// closed. Also kick bfcached documents out of bfcache.
PRUint32 count = mWaitingDatabases.Length();
for (PRUint32 index = 0; index < count; index++) {
nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
if (database->IsClosed()) {
continue;
}
// First check if the document the IDBDatabase is part of is bfcached.
nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
nsIBFCacheEntry* bfCacheEntry;
if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
bfCacheEntry->RemoveFromBFCacheSync();
NS_ASSERTION(database->IsClosed(),
"Kicking doc out of bfcache should have closed database");
continue;
}
// Otherwise fire a versionchange event.
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
bool dummy;
database->DispatchEvent(event, &dummy);
}
// Now check to see if any didn't close. If there are some running still
// then fire the blocked event.
for (PRUint32 index = 0; index < count; index++) {
if (!mWaitingDatabases[index]->IsClosed()) {
nsRefPtr<nsDOMEvent> event =
IDBVersionChangeEvent::CreateBlocked(mOldVersion, mNewVersion);
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
bool dummy;
mRequest->DispatchEvent(event, &dummy);
break;
}
}
return NS_OK;
}
private:
nsRefPtr<IDBDatabase> mRequestingDatabase;
nsRefPtr<IDBOpenDBRequest> mRequest;
nsTArray<nsRefPtr<IDBDatabase> > mWaitingDatabases;
PRInt64 mOldVersion;
PRInt64 mNewVersion;
};
} // anonymous namespace
NS_IMPL_THREADSAFE_ISUPPORTS1(OpenDatabaseHelper, nsIRunnable);
@ -694,8 +782,9 @@ OpenDatabaseHelper::StartSetVersion()
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
NS_ASSERTION(mgr, "This should never be null!");
rv = mgr->SetDatabaseVersion(mDatabase, mOpenDBRequest, mCurrentVersion,
mRequestedVersion, helper);
rv = mgr->AcquireExclusiveAccess(mDatabase, helper,
&SetVersionHelper::QueueVersionChange,
helper);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
// The SetVersionHelper is responsible for dispatching us back to the
@ -756,6 +845,11 @@ OpenDatabaseHelper::Run()
DispatchSuccessEvent();
}
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
NS_ASSERTION(manager, "This should never be null!");
manager->AllowNextSynchronizedOp(mASCIIOrigin, mDatabaseId);
ReleaseMainThreadObjects();
return NS_OK;
@ -1000,6 +1094,27 @@ SetVersionHelper::GetSuccessResult(JSContext* aCx,
aVal);
}
// static
void
SetVersionHelper::QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
void* aClosure)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!aDatabases.IsEmpty(), "Why are we here?");
SetVersionHelper* helper = static_cast<SetVersionHelper*>(aClosure);
NS_ASSERTION(helper, "Why don't we have a helper?");
nsRefPtr<VersionChangeEventsRunnable> eventsRunnable =
new VersionChangeEventsRunnable(helper->mOpenHelper->Database(),
helper->mOpenRequest,
aDatabases,
helper->mCurrentVersion,
helper->mRequestedVersion);
NS_DispatchToCurrentThread(eventsRunnable);
}
already_AddRefed<nsDOMEvent>
SetVersionHelper::CreateSuccessEvent()
{

View File

@ -86,6 +86,17 @@ public:
nsresult NotifySetVersionFinished();
void BlockDatabase();
nsIAtom* Id() const
{
return mDatabaseId.get();
}
IDBDatabase* Database() const
{
NS_ASSERTION(mDatabase, "Calling at the wrong time!");
return mDatabase;
}
protected:
// Methods only called on the main thread
nsresult EnsureSuccessResult();

View File

@ -39,7 +39,7 @@
#include "nsISupports.idl"
[scriptable, uuid(2f182bf1-1542-47fe-b2f7-4b1741b5283c)]
[scriptable, uuid(7aad2542-a5cb-4a57-b20c-c7d16b8582ab)]
interface nsIIDBDatabaseException : nsISupports
{
// const unsigned short NO_ERR = 0;
@ -52,10 +52,9 @@ interface nsIIDBDatabaseException : nsISupports
const unsigned short TRANSACTION_INACTIVE_ERR = 7;
const unsigned short ABORT_ERR = 8;
const unsigned short READ_ONLY_ERR = 9;
const unsigned short RECOVERABLE_ERR = 10;
const unsigned short TRANSIENT_ERR = 11;
const unsigned short TIMEOUT_ERR = 12;
const unsigned short DEADLOCK_ERR = 13;
const unsigned short TIMEOUT_ERR = 10;
const unsigned short QUOTA_ERR = 11;
const unsigned short VERSION_ERR = 12;
readonly attribute unsigned short code;
};

View File

@ -92,6 +92,7 @@ TEST_FILES = \
test_request_readyState.html \
test_success_events_after_abort.html \
test_third_party.html \
test_traffic_jam.html \
test_transaction_abort.html \
test_transaction_lifetimes.html \
test_transaction_lifetimes_nested.html \

View File

@ -12,10 +12,6 @@
<script type="text/javascript;version=1.7">
function testSteps()
{
todo(false, "Reenable me!");
finishTest();
yield;
const name = window.location.pathname;
let request = mozIndexedDB.open(name, 1);
@ -79,6 +75,9 @@
}
request2.onupgradeneeded = null;
request2.onsuccess = grabEventAndContinueHandler;
yield;
finishTest();
yield;

View File

@ -0,0 +1,99 @@
<!--
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 requests = [];
function doOpen(version, errorCallback, upgradeNeededCallback, successCallback) {
let request = mozIndexedDB.open(name, version);
request.onerror = errorCallback;
request.onupgradeneeded = upgradeNeededCallback;
request.onsuccess = successCallback;
requests.push(request);
}
doOpen(1, errorHandler, grabEventAndContinueHandler, grabEventAndContinueHandler);
doOpen(2, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
let event = yield;
is(event.type, "upgradeneeded", "expect an upgradeneeded event");
is(event.target, requests[0], "fired at the right request");
let db = event.target.result;
db.createObjectStore("foo");
doOpen(3, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
doOpen(2, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
doOpen(3, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
event.target.transaction.oncomplete = grabEventAndContinueHandler;
event = yield;
is(event.type, "complete", "expect a complete event");
is(event.target, requests[0].transaction, "expect it to be fired at the transaction");
event = yield;
is(event.type, "success", "expect a success event");
is(event.target, requests[0], "fired at the right request");
event.target.result.close();
requests[1].onupgradeneeded = grabEventAndContinueHandler;
event = yield;
is(event.type, "upgradeneeded", "expect an upgradeneeded event");
is(event.target, requests[1], "fired at the right request");
requests[1].onsuccess = grabEventAndContinueHandler;
event = yield;
is(event.type, "success", "expect a success event");
is(event.target, requests[1], "fired at the right request");
event.target.result.close();
requests[2].onupgradeneeded = grabEventAndContinueHandler;
event = yield;
is(event.type, "upgradeneeded", "expect an upgradeneeded event");
is(event.target, requests[2], "fired at the right request");
requests[2].onsuccess = grabEventAndContinueHandler;
event = yield;
is(event.type, "success", "expect a success event");
is(event.target, requests[2], "fired at the right request");
event.target.result.close();
requests[3].onerror = new ExpectError(IDBDatabaseException.VERSION_ERR);
event = yield;
requests[4].onsuccess = grabEventAndContinueHandler;
event = yield;
is(event.type, "success", "expect a success event");
is(event.target, requests[4], "fired at the right request");
event.target.result.close();
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>