/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Indexed Database. * * The Initial Developer of the Original Code is * The Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ben Turner * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "IDBTransaction.h" #include "nsIScriptContext.h" #include "mozilla/storage.h" #include "nsDOMClassInfo.h" #include "nsEventDispatcher.h" #include "nsPIDOMWindow.h" #include "nsProxyRelease.h" #include "nsThreadUtils.h" #include "AsyncConnectionHelper.h" #include "DatabaseInfo.h" #include "IDBCursor.h" #include "IDBEvents.h" #include "IDBFactory.h" #include "IDBObjectStore.h" #include "TransactionThreadPool.h" #define SAVEPOINT_NAME "savepoint" USING_INDEXEDDB_NAMESPACE namespace { IDBTransaction::ThreadObserver* gThreadObserver = nsnull; PLDHashOperator DoomCachedStatements(const nsACString& aQuery, nsCOMPtr& aStatement, void* aUserArg) { CommitHelper* helper = static_cast(aUserArg); helper->AddDoomedObject(aStatement); return PL_DHASH_REMOVE; } } // anonymous namespace // static already_AddRefed IDBTransaction::Create(IDBDatabase* aDatabase, nsTArray& aObjectStoreNames, PRUint16 aMode, PRUint32 aTimeout, bool aDispatchDelayed) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsRefPtr transaction = new IDBTransaction(); transaction->mScriptContext = aDatabase->ScriptContext(); transaction->mOwner = aDatabase->Owner(); transaction->mDatabase = aDatabase; transaction->mMode = aMode; transaction->mTimeout = aTimeout; if (!transaction->mObjectStoreNames.AppendElements(aObjectStoreNames)) { NS_ERROR("Out of memory!"); return nsnull; } if (!transaction->mCachedStatements.Init()) { NS_ERROR("Failed to initialize hash!"); return nsnull; } if (!aDispatchDelayed) { if (!ThreadObserver::BeginObserving(transaction)) { return nsnull; } transaction->mCreating = true; } return transaction.forget(); } IDBTransaction::IDBTransaction() : mReadyState(nsIIDBTransaction::INITIAL), mMode(nsIIDBTransaction::READ_ONLY), mTimeout(0), mPendingRequests(0), mSavepointCount(0), mAborted(false), mCreating(false) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); } IDBTransaction::~IDBTransaction() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!mPendingRequests, "Should have no pending requests here!"); NS_ASSERTION(!mSavepointCount, "Should have released them all!"); NS_ASSERTION(!mConnection, "Should have called CommitOrRollback!"); NS_ASSERTION(!mCreating, "Should have been cleared already!"); if (mListenerManager) { mListenerManager->Disconnect(); } } void IDBTransaction::OnNewRequest() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mPendingRequests) { NS_ASSERTION(mReadyState == nsIIDBTransaction::INITIAL, "Reusing a transaction!"); mReadyState = nsIIDBTransaction::LOADING; } ++mPendingRequests; } void IDBTransaction::OnRequestFinished() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(mPendingRequests, "Mismatched calls!"); --mPendingRequests; if (!mPendingRequests) { if (!mAborted) { NS_ASSERTION(mReadyState == nsIIDBTransaction::LOADING, "Bad state!"); } CommitOrRollback(); } } nsresult IDBTransaction::CommitOrRollback() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!mAborted) { NS_ASSERTION(mReadyState == nsIIDBTransaction::LOADING, "Bad state!"); } TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate(); NS_ENSURE_STATE(pool); nsRefPtr helper(new CommitHelper(this)); mCachedStatements.Enumerate(DoomCachedStatements, helper); NS_ASSERTION(!mCachedStatements.Count(), "Statements left!"); nsresult rv = pool->Dispatch(this, helper, true, helper); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } bool IDBTransaction::StartSavepoint() { NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!"); NS_PRECONDITION(mConnection, "No connection!"); nsCOMPtr stmt = GetCachedStatement(NS_LITERAL_CSTRING( "SAVEPOINT " SAVEPOINT_NAME )); NS_ENSURE_TRUE(stmt, false); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, false); ++mSavepointCount; return true; } nsresult IDBTransaction::ReleaseSavepoint() { NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!"); NS_PRECONDITION(mConnection, "No connection!"); NS_ASSERTION(mSavepointCount, "Mismatch!"); nsCOMPtr stmt = GetCachedStatement(NS_LITERAL_CSTRING( "RELEASE SAVEPOINT " SAVEPOINT_NAME )); NS_ENSURE_TRUE(stmt, false); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, false); --mSavepointCount; return NS_OK; } void IDBTransaction::RollbackSavepoint() { NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!"); NS_PRECONDITION(mConnection, "No connection!"); NS_ASSERTION(mSavepointCount == 1, "Mismatch!"); mSavepointCount = 0; nsCOMPtr stmt = GetCachedStatement(NS_LITERAL_CSTRING( "ROLLBACK TO SAVEPOINT " SAVEPOINT_NAME )); NS_ENSURE_TRUE(stmt,); mozStorageStatementScoper scoper(stmt); nsresult rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv,); } nsresult IDBTransaction::GetOrCreateConnection(mozIStorageConnection** aResult) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); if (mDatabase->IsInvalidated()) { return NS_ERROR_NOT_AVAILABLE; } if (!mConnection) { nsCOMPtr connection = IDBFactory::GetConnection(mDatabase->FilePath()); NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE); NS_NAMED_LITERAL_CSTRING(beginTransaction, "BEGIN TRANSACTION;"); nsresult rv = connection->ExecuteSimpleSQL(beginTransaction); NS_ENSURE_SUCCESS(rv, false); connection.swap(mConnection); } nsCOMPtr result(mConnection); result.forget(aResult); return NS_OK; } already_AddRefed IDBTransaction::AddStatement(bool aCreate, bool aOverwrite, bool aAutoIncrement) { #ifdef DEBUG if (!aCreate) { NS_ASSERTION(aOverwrite, "Bad param combo!"); } #endif if (aAutoIncrement) { if (aCreate) { if (aOverwrite) { return GetCachedStatement( "INSERT OR FAIL INTO ai_object_data (object_store_id, id, data) " "VALUES (:osid, :key_value, :data)" ); } return GetCachedStatement( "INSERT INTO ai_object_data (object_store_id, data) " "VALUES (:osid, :data)" ); } return GetCachedStatement( "UPDATE ai_object_data " "SET data = :data " "WHERE object_store_id = :osid " "AND id = :key_value" ); } if (aCreate) { if (aOverwrite) { return GetCachedStatement( "INSERT OR FAIL INTO object_data (object_store_id, key_value, data) " "VALUES (:osid, :key_value, :data)" ); } return GetCachedStatement( "INSERT INTO object_data (object_store_id, key_value, data) " "VALUES (:osid, :key_value, :data)" ); } return GetCachedStatement( "UPDATE object_data " "SET data = :data " "WHERE object_store_id = :osid " "AND key_value = :key_value" ); } already_AddRefed IDBTransaction::DeleteStatement(bool aAutoIncrement) { if (aAutoIncrement) { return GetCachedStatement( "DELETE FROM ai_object_data " "WHERE id = :key_value " "AND object_store_id = :osid" ); } return GetCachedStatement( "DELETE FROM object_data " "WHERE key_value = :key_value " "AND object_store_id = :osid" ); } already_AddRefed IDBTransaction::GetStatement(bool aAutoIncrement) { if (aAutoIncrement) { return GetCachedStatement( "SELECT data " "FROM ai_object_data " "WHERE id = :id " "AND object_store_id = :osid" ); } return GetCachedStatement( "SELECT data " "FROM object_data " "WHERE key_value = :id " "AND object_store_id = :osid" ); } already_AddRefed IDBTransaction::IndexGetStatement(bool aUnique, bool aAutoIncrement) { if (aAutoIncrement) { if (aUnique) { return GetCachedStatement( "SELECT ai_object_data_id " "FROM ai_unique_index_data " "WHERE index_id = :index_id " "AND value = :value" ); } return GetCachedStatement( "SELECT ai_object_data_id " "FROM ai_index_data " "WHERE index_id = :index_id " "AND value = :value" ); } if (aUnique) { return GetCachedStatement( "SELECT object_data_key " "FROM unique_index_data " "WHERE index_id = :index_id " "AND value = :value" ); } return GetCachedStatement( "SELECT object_data_key " "FROM index_data " "WHERE index_id = :index_id " "AND value = :value" ); } already_AddRefed IDBTransaction::IndexGetObjectStatement(bool aUnique, bool aAutoIncrement) { if (aAutoIncrement) { if (aUnique) { return GetCachedStatement( "SELECT data " "FROM ai_object_data " "INNER JOIN ai_unique_index_data " "ON ai_object_data.id = ai_unique_index_data.ai_object_data_id " "WHERE index_id = :index_id " "AND value = :value" ); } return GetCachedStatement( "SELECT data " "FROM ai_object_data " "INNER JOIN ai_index_data " "ON ai_object_data.id = ai_index_data.ai_object_data_id " "WHERE index_id = :index_id " "AND value = :value" ); } if (aUnique) { return GetCachedStatement( "SELECT data " "FROM object_data " "INNER JOIN unique_index_data " "ON object_data.id = unique_index_data.object_data_id " "WHERE index_id = :index_id " "AND value = :value" ); } return GetCachedStatement( "SELECT data " "FROM object_data " "INNER JOIN index_data " "ON object_data.id = index_data.object_data_id " "WHERE index_id = :index_id " "AND value = :value" ); } already_AddRefed IDBTransaction::IndexUpdateStatement(bool aAutoIncrement, bool aUnique, bool aOverwrite) { if (aAutoIncrement) { if (aUnique) { if (aOverwrite) { return GetCachedStatement( "INSERT OR REPLACE INTO ai_unique_index_data " "(index_id, ai_object_data_id, id, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)" ); } return GetCachedStatement( "INSERT INTO ai_unique_index_data " "(index_id, aI_object_data_id, id, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)" ); } if (aOverwrite) { return GetCachedStatement( "INSERT OR REPLACE INTO ai_index_data " "(index_id, ai_object_data_id, id, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)" ); } return GetCachedStatement( "INSERT INTO ai_index_data " "(index_id, ai_object_data_id, id, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)" ); } if (aUnique) { if (aOverwrite) { return GetCachedStatement( "INSERT OR REPLACE INTO unique_index_data " "(index_id, object_data_id, object_data_key, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)" ); } return GetCachedStatement( "INSERT INTO unique_index_data " "(index_id, object_data_id, object_data_key, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)" ); } if (aOverwrite) { return GetCachedStatement( "INSERT INTO index_data (" "index_id, object_data_id, object_data_key, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)" ); } return GetCachedStatement( "INSERT INTO index_data (" "index_id, object_data_id, object_data_key, value) " "VALUES (:index_id, :object_data_id, :object_data_key, :value)" ); } already_AddRefed IDBTransaction::GetCachedStatement(const nsACString& aQuery) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!aQuery.IsEmpty(), "Empty sql statement!"); NS_ASSERTION(mConnection, "No connection!"); nsCOMPtr stmt; if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) { nsresult rv = mConnection->CreateStatement(aQuery, getter_AddRefs(stmt)); #ifdef DEBUG if (NS_FAILED(rv)) { nsCString error; error.AppendLiteral("The statement `"); error.Append(aQuery); error.AppendLiteral("` failed to compile with the error message `"); nsCString msg; (void)mConnection->GetLastErrorString(msg); error.Append(msg); error.AppendLiteral("`."); NS_ERROR(error.get()); } #endif NS_ENSURE_SUCCESS(rv, nsnull); if (!mCachedStatements.Put(aQuery, stmt)) { NS_ERROR("Out of memory?!"); } } return stmt.forget(); } bool IDBTransaction::TransactionIsOpen() const { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); // If we haven't started anything then we're open. if (mReadyState == nsIIDBTransaction::INITIAL) { NS_ASSERTION(AsyncConnectionHelper::GetCurrentTransaction() != this, "This should be some other transaction (or null)!"); return true; } // If we've already started then we need to check to see if we still have the // mCreating flag set. If we do (i.e. we haven't returned to the event loop // from the time we were created) then we are open. Otherwise check the // currently running transaction to see if it's the same. We only allow other // requests to be made if this transaction is currently running. if (mReadyState == nsIIDBTransaction::LOADING) { if (mCreating) { return true; } if (AsyncConnectionHelper::GetCurrentTransaction() == this) { return true; } } return false; } already_AddRefed IDBTransaction::GetOrCreateObjectStore(const nsAString& aName, ObjectStoreInfo* aObjectStoreInfo) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!aName.IsEmpty(), "Empty name!"); NS_ASSERTION(aObjectStoreInfo, "Null pointer!"); nsRefPtr retval; for (PRUint32 index = 0; index < mCreatedObjectStores.Length(); index++) { nsRefPtr& objectStore = mCreatedObjectStores[index]; if (objectStore->Name() == aName) { retval = objectStore; return retval.forget(); } } retval = IDBObjectStore::Create(this, aObjectStoreInfo); NS_ENSURE_TRUE(retval, nsnull); if (!mCreatedObjectStores.AppendElement(retval)) { NS_WARNING("Out of memory!"); return nsnull; } return retval.forget(); } NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction, nsDOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mDatabase, nsPIDOMEventTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnCompleteListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnAbortListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnTimeoutListener) for (PRUint32 i = 0; i < tmp->mCreatedObjectStores.Length(); i++) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCreatedObjectStores[i]"); cb.NoteXPCOMChild(static_cast( tmp->mCreatedObjectStores[i].get())); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction, nsDOMEventTargetHelper) // Don't unlink mDatabase! NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnErrorListener) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnCompleteListener) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnAbortListener) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnTimeoutListener) tmp->mCreatedObjectStores.Clear(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBTransaction) NS_INTERFACE_MAP_ENTRY(nsIIDBTransaction) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(IDBTransaction) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(IDBTransaction, nsDOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(IDBTransaction, nsDOMEventTargetHelper) DOMCI_DATA(IDBTransaction, IDBTransaction) NS_IMETHODIMP IDBTransaction::GetDb(nsIIDBDatabase** aDB) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ADDREF(*aDB = mDatabase); return NS_OK; } NS_IMETHODIMP IDBTransaction::GetReadyState(PRUint16* aReadyState) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); *aReadyState = mReadyState; return NS_OK; } NS_IMETHODIMP IDBTransaction::GetMode(PRUint16* aMode) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); *aMode = mMode; return NS_OK; } NS_IMETHODIMP IDBTransaction::GetObjectStoreNames(nsIDOMDOMStringList** aObjectStores) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsRefPtr list(new nsDOMStringList()); nsAutoTArray stackArray; nsTArray* arrayOfNames; if (mMode == IDBTransaction::VERSION_CHANGE) { DatabaseInfo* info; if (!DatabaseInfo::Get(mDatabase->Id(), &info)) { NS_ERROR("This should never fail!"); } if (!info->GetObjectStoreNames(stackArray)) { NS_ERROR("Out of memory!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } arrayOfNames = &stackArray; } else { arrayOfNames = &mObjectStoreNames; } PRUint32 count = arrayOfNames->Length(); for (PRUint32 index = 0; index < count; index++) { NS_ENSURE_TRUE(list->Add(arrayOfNames->ElementAt(index)), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } list.forget(aObjectStores); return NS_OK; } NS_IMETHODIMP IDBTransaction::ObjectStore(const nsAString& aName, nsIIDBObjectStore** _retval) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!TransactionIsOpen()) { return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; } ObjectStoreInfo* info = nsnull; if (mMode == nsIIDBTransaction::VERSION_CHANGE || mObjectStoreNames.Contains(aName)) { ObjectStoreInfo::Get(mDatabase->Id(), aName, &info); } if (!info) { return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR; } nsRefPtr objectStore = GetOrCreateObjectStore(aName, info); NS_ENSURE_TRUE(objectStore, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); objectStore.forget(_retval); return NS_OK; } NS_IMETHODIMP IDBTransaction::Abort() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (!TransactionIsOpen()) { return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; } mAborted = true; mReadyState = nsIIDBTransaction::DONE; return NS_OK; } NS_IMETHODIMP IDBTransaction::SetOnerror(nsIDOMEventListener* aErrorListener) { return RemoveAddEventListener(NS_LITERAL_STRING(ERROR_EVT_STR), mOnErrorListener, aErrorListener); } NS_IMETHODIMP IDBTransaction::GetOnerror(nsIDOMEventListener** aErrorListener) { return GetInnerEventListener(mOnErrorListener, aErrorListener); } NS_IMETHODIMP IDBTransaction::GetOncomplete(nsIDOMEventListener** aOncomplete) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return GetInnerEventListener(mOnCompleteListener, aOncomplete); } NS_IMETHODIMP IDBTransaction::SetOncomplete(nsIDOMEventListener* aOncomplete) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return RemoveAddEventListener(NS_LITERAL_STRING(COMPLETE_EVT_STR), mOnCompleteListener, aOncomplete); } NS_IMETHODIMP IDBTransaction::GetOnabort(nsIDOMEventListener** aOnabort) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return GetInnerEventListener(mOnAbortListener, aOnabort); } NS_IMETHODIMP IDBTransaction::SetOnabort(nsIDOMEventListener* aOnabort) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return RemoveAddEventListener(NS_LITERAL_STRING(ABORT_EVT_STR), mOnAbortListener, aOnabort); } NS_IMETHODIMP IDBTransaction::GetOntimeout(nsIDOMEventListener** aOntimeout) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return GetInnerEventListener(mOnTimeoutListener, aOntimeout); } NS_IMETHODIMP IDBTransaction::SetOntimeout(nsIDOMEventListener* aOntimeout) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return RemoveAddEventListener(NS_LITERAL_STRING(TIMEOUT_EVT_STR), mOnTimeoutListener, aOntimeout); } nsresult IDBTransaction::PreHandleEvent(nsEventChainPreVisitor& aVisitor) { aVisitor.mCanHandle = PR_TRUE; aVisitor.mParentTarget = mDatabase; return NS_OK; } IDBTransaction:: ThreadObserver::ThreadObserver() : mBaseRecursionDepth(0), mDone(false) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!gThreadObserver, "Multiple observers?!"); } IDBTransaction:: ThreadObserver::~ThreadObserver() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(gThreadObserver == this, "Multiple observers?!"); #ifdef DEBUG for (PRUint32 i = 0; i < mTransactions.Length(); i++) { NS_ASSERTION(mTransactions[i].transactions.IsEmpty(), "Unprocessed transactions!"); } #endif // Clear the global. gThreadObserver = nsnull; } void IDBTransaction:: ThreadObserver::UpdateNewlyCreatedTransactions(PRUint32 aRecursionDepth) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); for (PRUint32 i = 0; i < mTransactions.Length(); i++) { TransactionInfo& info = mTransactions[i]; if (info.recursionDepth == aRecursionDepth) { for (PRUint32 j = 0; j < info.transactions.Length(); j++) { nsRefPtr& transaction = info.transactions[j]; // Clear the mCreating flag now. transaction->mCreating = false; // And maybe set the readyState to DONE if there were no requests // generated. if (transaction->mReadyState == nsIIDBTransaction::INITIAL) { transaction->mReadyState = nsIIDBTransaction::DONE; } } // Don't hang on to transactions any longer than we have to. info.transactions.Clear(); break; } } } // static bool IDBTransaction:: ThreadObserver::BeginObserving(IDBTransaction* aTransaction) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aTransaction, "Null pointer!"); nsCOMPtr thread(do_QueryInterface(NS_GetCurrentThread())); NS_ENSURE_TRUE(thread, false); // We need the current recursion depth first. PRUint32 depth; nsresult rv = thread->GetRecursionDepth(&depth); NS_ENSURE_SUCCESS(rv, false); NS_ASSERTION(depth, "This should never be 0!"); depth--; // If we've already got an observer created then simply append this // transaction to its list. if (gThreadObserver) { for (PRUint32 i = 0; i < gThreadObserver->mTransactions.Length(); i++) { TransactionInfo& info = gThreadObserver->mTransactions[i]; if (info.recursionDepth == depth) { if (!info.transactions.AppendElement(aTransaction)) { NS_WARNING("Out of memory!"); return false; } return true; } } // No transactions at this depth yet, make a new entry TransactionInfo* newInfo = gThreadObserver->mTransactions.AppendElement(); if (!newInfo || !newInfo->transactions.AppendElement(aTransaction)) { NS_WARNING("Out of memory!"); return false; } newInfo->recursionDepth = depth; return true; } // Make a new thread observer and install it. nsRefPtr observer(new ThreadObserver()); TransactionInfo* info = observer->mTransactions.AppendElement(); NS_ASSERTION(info, "This should never fail!"); info->recursionDepth = observer->mBaseRecursionDepth = depth; if (!info->transactions.AppendElement(aTransaction)) { NS_WARNING("Out of memory!"); return false; } // We need to keep the thread observer chain intact so grab the previous // observer. rv = thread->GetObserver(getter_AddRefs(observer->mPreviousObserver)); NS_ENSURE_SUCCESS(rv, false); // Now set our new observer. rv = thread->SetObserver(observer); NS_ENSURE_SUCCESS(rv, false); // And set the global so that we don't recreate it later. gThreadObserver = observer; return true; } NS_IMPL_THREADSAFE_ISUPPORTS1(IDBTransaction::ThreadObserver, nsIThreadObserver) NS_IMETHODIMP IDBTransaction:: ThreadObserver::OnDispatchedEvent(nsIThreadInternal* aThread) { // This may be called on any thread! // Nothing special is needed here, just call the previous observer. if (mPreviousObserver) { return mPreviousObserver->OnDispatchedEvent(aThread); } return NS_OK; } NS_IMETHODIMP IDBTransaction:: ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread, PRBool aMayWait, PRUint32 aRecursionDepth) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aThread, "This should never be null!"); NS_ASSERTION(!mKungFuDeathGrip, "Shouldn't have a self-ref here!"); // If we're at the base recursion depth here then we're ready to unset // ourselves as the thread observer. if (aRecursionDepth == mBaseRecursionDepth || mDone) { // From here on we'll continue to try to unset ourselves. mDone = true; nsCOMPtr currentObserver; if (NS_FAILED(aThread->GetObserver(getter_AddRefs(currentObserver)))) { NS_WARNING("Can't get current observer?!"); } // We can only set the previous observer if this is the current observer. // Otherwise someone else has installed themselves into the chain and we // have to hang around until they unset themselves. if (currentObserver == this) { // Setting a different thread observer could delete us. Maintain a // reference until AfterProcessNextEvent is called. mKungFuDeathGrip = this; // Set our previous observer back on the thread. if (NS_FAILED(aThread->SetObserver(mPreviousObserver))) { NS_ERROR("This should never fail!"); } } } // Take care of any transactions that were created at this recursion depth. UpdateNewlyCreatedTransactions(aRecursionDepth); // And call the previous observer. if (mPreviousObserver) { return mPreviousObserver->OnProcessNextEvent(aThread, aMayWait, aRecursionDepth); } return NS_OK; } NS_IMETHODIMP IDBTransaction:: ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* aThread, PRUint32 aRecursionDepth) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aThread, "This should never be null!"); nsRefPtr kungFuDeathGrip; nsCOMPtr observer; if (mKungFuDeathGrip) { NS_ASSERTION(mDone, "Huh?!"); // We can drop the reference to this observer after this call. kungFuDeathGrip.swap(mKungFuDeathGrip); // And we don't need the previous observer after this call either. observer.swap(mPreviousObserver); } else { // Still call the previous observer. observer = mPreviousObserver; } // We may have collected more transactions while the event was processed. // Update them now. UpdateNewlyCreatedTransactions(aRecursionDepth); if (observer) { return observer->AfterProcessNextEvent(aThread, aRecursionDepth); } return NS_OK; } CommitHelper::CommitHelper(IDBTransaction* aTransaction) : mTransaction(aTransaction), mAborted(!!aTransaction->mAborted), mHaveMetadata(false) { mConnection.swap(aTransaction->mConnection); } CommitHelper::~CommitHelper() { } NS_IMPL_THREADSAFE_ISUPPORTS1(CommitHelper, nsIRunnable) NS_IMETHODIMP CommitHelper::Run() { if (NS_IsMainThread()) { NS_ASSERTION(mDoomedObjects.IsEmpty(), "Didn't release doomed objects!"); mTransaction->mReadyState = nsIIDBTransaction::DONE; nsCOMPtr event; if (mAborted) { if (mHaveMetadata) { NS_ASSERTION(mTransaction->Mode() == nsIIDBTransaction::VERSION_CHANGE, "Bad transaction type!"); DatabaseInfo* dbInfo; if (!DatabaseInfo::Get(mTransaction->Database()->Id(), &dbInfo)) { NS_ERROR("This should never fail!"); } if (NS_FAILED(IDBFactory::UpdateDatabaseMetadata(dbInfo, mOldVersion, mOldObjectStores))) { NS_WARNING("Failed to update database metadata!"); } else { NS_ASSERTION(mOldObjectStores.IsEmpty(), "Should have swapped!"); } } event = IDBEvent::CreateGenericEvent(NS_LITERAL_STRING(ABORT_EVT_STR)); } else { event = IDBEvent::CreateGenericEvent(NS_LITERAL_STRING(COMPLETE_EVT_STR)); } NS_ENSURE_TRUE(event, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); PRBool dummy; if (NS_FAILED(mTransaction->DispatchEvent(event, &dummy))) { NS_WARNING("Dispatch failed!"); } mTransaction = nsnull; return NS_OK; } NS_ASSERTION(mConnection, "This had better not be null!"); IDBDatabase* database = mTransaction->Database(); if (database->IsInvalidated()) { mAborted = true; } IDBFactory::SetCurrentDatabase(database); if (!mAborted) { NS_NAMED_LITERAL_CSTRING(release, "END TRANSACTION"); if (NS_FAILED(mConnection->ExecuteSimpleSQL(release))) { mAborted = PR_TRUE; } } if (mAborted) { NS_ASSERTION(mConnection, "This had better not be null!"); NS_NAMED_LITERAL_CSTRING(rollback, "ROLLBACK TRANSACTION"); if (NS_FAILED(mConnection->ExecuteSimpleSQL(rollback))) { NS_WARNING("Failed to rollback transaction!"); } if (mTransaction->Mode() == nsIIDBTransaction::VERSION_CHANGE) { nsresult rv = IDBFactory::LoadDatabaseInformation(mConnection, mTransaction->Database()->Id(), mOldVersion, mOldObjectStores); if (NS_SUCCEEDED(rv)) { mHaveMetadata = true; } else { NS_WARNING("Failed to get database information!"); } } } mDoomedObjects.Clear(); mConnection->Close(); mConnection = nsnull; IDBFactory::SetCurrentDatabase(nsnull); return NS_OK; }