mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
7cf0b507f9
--HG-- extra : transplant_source : %92%5BBT%93%BA%3DO%D7%03v%B7%88%01%3AjZ%D0%81%AA
929 lines
26 KiB
C++
929 lines
26 KiB
C++
/* -*- 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 <bent.mozilla@gmail.com>
|
|
*
|
|
* 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 "nsDOMClassInfoID.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 "IndexedDatabaseManager.h"
|
|
#include "TransactionThreadPool.h"
|
|
|
|
#define SAVEPOINT_NAME "savepoint"
|
|
|
|
USING_INDEXEDDB_NAMESPACE
|
|
|
|
namespace {
|
|
|
|
PLDHashOperator
|
|
DoomCachedStatements(const nsACString& aQuery,
|
|
nsCOMPtr<mozIStorageStatement>& aStatement,
|
|
void* aUserArg)
|
|
{
|
|
CommitHelper* helper = static_cast<CommitHelper*>(aUserArg);
|
|
helper->AddDoomedObject(aStatement);
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
|
|
// This runnable doesn't actually do anything beyond "prime the pump" and get
|
|
// transactions in the right order on the transaction thread pool.
|
|
class StartTransactionRunnable : public nsIRunnable
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
// NOP
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
// Could really use those NS_REFCOUNTING_HAHA_YEAH_RIGHT macros here.
|
|
NS_IMETHODIMP_(nsrefcnt) StartTransactionRunnable::AddRef()
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
NS_IMETHODIMP_(nsrefcnt) StartTransactionRunnable::Release()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
NS_IMPL_QUERY_INTERFACE1(StartTransactionRunnable, nsIRunnable)
|
|
|
|
StartTransactionRunnable gStartTransactionRunnable;
|
|
|
|
} // anonymous namespace
|
|
|
|
// static
|
|
already_AddRefed<IDBTransaction>
|
|
IDBTransaction::Create(IDBDatabase* aDatabase,
|
|
nsTArray<nsString>& aObjectStoreNames,
|
|
PRUint16 aMode,
|
|
bool aDispatchDelayed)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
nsRefPtr<IDBTransaction> transaction = new IDBTransaction();
|
|
|
|
transaction->mScriptContext = aDatabase->ScriptContext();
|
|
transaction->mOwner = aDatabase->Owner();
|
|
|
|
transaction->mDatabase = aDatabase;
|
|
transaction->mMode = aMode;
|
|
|
|
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) {
|
|
nsCOMPtr<nsIThreadInternal> thread =
|
|
do_QueryInterface(NS_GetCurrentThread());
|
|
NS_ENSURE_TRUE(thread, nsnull);
|
|
|
|
// We need the current recursion depth first.
|
|
PRUint32 depth;
|
|
nsresult rv = thread->GetRecursionDepth(&depth);
|
|
NS_ENSURE_SUCCESS(rv, nsnull);
|
|
|
|
NS_ASSERTION(depth, "This should never be 0!");
|
|
transaction->mCreatedRecursionDepth = depth - 1;
|
|
|
|
rv = thread->AddObserver(transaction);
|
|
NS_ENSURE_SUCCESS(rv, nsnull);
|
|
|
|
transaction->mCreating = true;
|
|
}
|
|
|
|
if (aMode != nsIIDBTransaction::VERSION_CHANGE) {
|
|
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
|
|
pool->Dispatch(transaction, &gStartTransactionRunnable, false, nsnull);
|
|
}
|
|
|
|
return transaction.forget();
|
|
}
|
|
|
|
IDBTransaction::IDBTransaction()
|
|
: mReadyState(nsIIDBTransaction::INITIAL),
|
|
mMode(nsIIDBTransaction::READ_ONLY),
|
|
mPendingRequests(0),
|
|
mCreatedRecursionDepth(0),
|
|
mSavepointCount(0),
|
|
mAborted(false),
|
|
mCreating(false)
|
|
#ifdef DEBUG
|
|
, mFiredCompleteOrAbort(false)
|
|
#endif
|
|
{
|
|
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!");
|
|
NS_ASSERTION(mFiredCompleteOrAbort, "Should have fired event!");
|
|
|
|
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) {
|
|
NS_ASSERTION(mAborted || mReadyState == nsIIDBTransaction::LOADING,
|
|
"Bad state!");
|
|
mReadyState = IDBTransaction::COMMITTING;
|
|
CommitOrRollback();
|
|
}
|
|
}
|
|
|
|
void
|
|
IDBTransaction::SetTransactionListener(IDBTransactionListener* aListener)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(!mListener, "Shouldn't already have a listener!");
|
|
mListener = aListener;
|
|
}
|
|
|
|
nsresult
|
|
IDBTransaction::CommitOrRollback()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
|
|
NS_ENSURE_STATE(pool);
|
|
|
|
nsRefPtr<CommitHelper> helper(new CommitHelper(this, mListener));
|
|
|
|
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<mozIStorageStatement> 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<mozIStorageStatement> 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<mozIStorageStatement> 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<mozIStorageConnection> connection =
|
|
IDBFactory::GetConnection(mDatabase->FilePath());
|
|
NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE);
|
|
|
|
nsCString beginTransaction;
|
|
if (mMode != nsIIDBTransaction::READ_ONLY) {
|
|
beginTransaction.AssignLiteral("BEGIN IMMEDIATE TRANSACTION;");
|
|
}
|
|
else {
|
|
beginTransaction.AssignLiteral("BEGIN TRANSACTION;");
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
nsresult rv = connection->CreateStatement(beginTransaction,
|
|
getter_AddRefs(stmt));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
rv = stmt->Execute();
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
connection.swap(mConnection);
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageConnection> result(mConnection);
|
|
result.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<mozIStorageStatement>
|
|
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<mozIStorageStatement>
|
|
IDBTransaction::IndexDataInsertStatement(bool aAutoIncrement,
|
|
bool aUnique)
|
|
{
|
|
if (aAutoIncrement) {
|
|
if (aUnique) {
|
|
return GetCachedStatement(
|
|
"INSERT INTO ai_unique_index_data "
|
|
"(index_id, ai_object_data_id, value) "
|
|
"VALUES (:index_id, :object_data_id, :value)"
|
|
);
|
|
}
|
|
return GetCachedStatement(
|
|
"INSERT INTO ai_index_data "
|
|
"(index_id, ai_object_data_id, value) "
|
|
"VALUES (:index_id, :object_data_id, :value)"
|
|
);
|
|
}
|
|
if (aUnique) {
|
|
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)"
|
|
);
|
|
}
|
|
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<mozIStorageStatement>
|
|
IDBTransaction::IndexDataDeleteStatement(bool aAutoIncrement,
|
|
bool aUnique)
|
|
{
|
|
if (aAutoIncrement) {
|
|
if (aUnique) {
|
|
return GetCachedStatement(
|
|
"DELETE FROM ai_unique_index_data "
|
|
"WHERE ai_object_data_id = :object_data_id"
|
|
);
|
|
}
|
|
return GetCachedStatement(
|
|
"DELETE FROM ai_index_data "
|
|
"WHERE ai_object_data_id = :object_data_id"
|
|
);
|
|
}
|
|
if (aUnique) {
|
|
return GetCachedStatement(
|
|
"DELETE FROM unique_index_data "
|
|
"WHERE object_data_id = :object_data_id"
|
|
);
|
|
}
|
|
return GetCachedStatement(
|
|
"DELETE FROM index_data "
|
|
"WHERE object_data_id = :object_data_id"
|
|
);
|
|
}
|
|
|
|
already_AddRefed<mozIStorageStatement>
|
|
IDBTransaction::GetCachedStatement(const nsACString& aQuery)
|
|
{
|
|
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(!aQuery.IsEmpty(), "Empty sql statement!");
|
|
NS_ASSERTION(mConnection, "No connection!");
|
|
|
|
nsCOMPtr<mozIStorageStatement> 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::IsOpen() 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<IDBObjectStore>
|
|
IDBTransaction::GetOrCreateObjectStore(const nsAString& aName,
|
|
ObjectStoreInfo* aObjectStoreInfo)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(aObjectStoreInfo, "Null pointer!");
|
|
|
|
nsRefPtr<IDBObjectStore> retval;
|
|
|
|
for (PRUint32 index = 0; index < mCreatedObjectStores.Length(); index++) {
|
|
nsRefPtr<IDBObjectStore>& 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,
|
|
nsIDOMEventTarget)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnCompleteListener)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnAbortListener)
|
|
|
|
for (PRUint32 i = 0; i < tmp->mCreatedObjectStores.Length(); i++) {
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCreatedObjectStores[i]");
|
|
cb.NoteXPCOMChild(static_cast<nsIIDBObjectStore*>(
|
|
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)
|
|
|
|
tmp->mCreatedObjectStores.Clear();
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBTransaction)
|
|
NS_INTERFACE_MAP_ENTRY(nsIIDBTransaction)
|
|
NS_INTERFACE_MAP_ENTRY(nsIThreadObserver)
|
|
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<nsDOMStringList> list(new nsDOMStringList());
|
|
|
|
nsAutoTArray<nsString, 10> stackArray;
|
|
nsTArray<nsString>* arrayOfNames;
|
|
|
|
if (mMode == IDBTransaction::VERSION_CHANGE) {
|
|
DatabaseInfo* info = mDatabase->Info();
|
|
|
|
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 (!IsOpen()) {
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
|
}
|
|
|
|
ObjectStoreInfo* info = nsnull;
|
|
|
|
if (mMode == nsIIDBTransaction::VERSION_CHANGE ||
|
|
mObjectStoreNames.Contains(aName)) {
|
|
mDatabase->Info()->GetObjectStore(aName, &info);
|
|
}
|
|
|
|
if (!info) {
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR;
|
|
}
|
|
|
|
nsRefPtr<IDBObjectStore> 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!");
|
|
|
|
// We can't use IsOpen here since we need it to be possible to call Abort()
|
|
// even from outside of transaction callbacks.
|
|
if (mReadyState != IDBTransaction::INITIAL &&
|
|
mReadyState != IDBTransaction::LOADING) {
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
|
}
|
|
|
|
bool needToCommitOrRollback = mReadyState == nsIIDBTransaction::INITIAL;
|
|
|
|
mAborted = true;
|
|
mReadyState = nsIIDBTransaction::DONE;
|
|
|
|
if (Mode() == nsIIDBTransaction::VERSION_CHANGE) {
|
|
// If a version change transaction is aborted, the db must be closed
|
|
mDatabase->Close();
|
|
}
|
|
|
|
// Fire the abort event if there are no outstanding requests. Otherwise the
|
|
// abort event will be fired when all outstanding requests finish.
|
|
if (needToCommitOrRollback) {
|
|
return CommitOrRollback();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
nsresult
|
|
IDBTransaction::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
|
|
{
|
|
aVisitor.mCanHandle = true;
|
|
aVisitor.mParentTarget = mDatabase;
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX Once nsIThreadObserver gets split this method will disappear.
|
|
NS_IMETHODIMP
|
|
IDBTransaction::OnDispatchedEvent(nsIThreadInternal* aThread)
|
|
{
|
|
NS_NOTREACHED("Don't call me!");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
IDBTransaction::OnProcessNextEvent(nsIThreadInternal* aThread,
|
|
bool aMayWait,
|
|
PRUint32 aRecursionDepth)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(aRecursionDepth > mCreatedRecursionDepth,
|
|
"Should be impossible!");
|
|
NS_ASSERTION(mCreating, "Should be true!");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
IDBTransaction::AfterProcessNextEvent(nsIThreadInternal* aThread,
|
|
PRUint32 aRecursionDepth)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(aThread, "This should never be null!");
|
|
NS_ASSERTION(aRecursionDepth >= mCreatedRecursionDepth,
|
|
"Should be impossible!");
|
|
NS_ASSERTION(mCreating, "Should be true!");
|
|
|
|
if (aRecursionDepth == mCreatedRecursionDepth) {
|
|
// We're back at the event loop, no longer newborn.
|
|
mCreating = false;
|
|
|
|
// Maybe set the readyState to DONE if there were no requests generated.
|
|
if (mReadyState == nsIIDBTransaction::INITIAL) {
|
|
mReadyState = nsIIDBTransaction::DONE;
|
|
|
|
if (NS_FAILED(CommitOrRollback())) {
|
|
NS_WARNING("Failed to commit!");
|
|
}
|
|
}
|
|
|
|
// No longer need to observe thread events.
|
|
if(NS_FAILED(aThread->RemoveObserver(this))) {
|
|
NS_ERROR("Failed to remove observer!");
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
CommitHelper::CommitHelper(IDBTransaction* aTransaction,
|
|
IDBTransactionListener* aListener)
|
|
: mTransaction(aTransaction),
|
|
mListener(aListener),
|
|
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<nsIDOMEvent> event;
|
|
if (mAborted) {
|
|
if (mHaveMetadata) {
|
|
NS_ASSERTION(mTransaction->Mode() == nsIIDBTransaction::VERSION_CHANGE,
|
|
"Bad transaction type!");
|
|
|
|
DatabaseInfo* dbInfo = mTransaction->Database()->Info();
|
|
|
|
if (NS_FAILED(IDBFactory::UpdateDatabaseMetadata(dbInfo, mOldVersion,
|
|
mOldObjectStores))) {
|
|
NS_WARNING("Failed to update database metadata!");
|
|
}
|
|
else {
|
|
NS_ASSERTION(mOldObjectStores.IsEmpty(), "Should have swapped!");
|
|
}
|
|
}
|
|
|
|
event = CreateGenericEvent(NS_LITERAL_STRING(ABORT_EVT_STR));
|
|
}
|
|
else {
|
|
event = CreateGenericEvent(NS_LITERAL_STRING(COMPLETE_EVT_STR));
|
|
}
|
|
NS_ENSURE_TRUE(event, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
|
|
|
bool dummy;
|
|
if (NS_FAILED(mTransaction->DispatchEvent(event, &dummy))) {
|
|
NS_WARNING("Dispatch failed!");
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
mTransaction->mFiredCompleteOrAbort = true;
|
|
#endif
|
|
|
|
// Tell the listener (if we have one) that we're done
|
|
if (mListener) {
|
|
mListener->NotifyTransactionComplete(mTransaction);
|
|
}
|
|
|
|
mTransaction = nsnull;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
IDBDatabase* database = mTransaction->Database();
|
|
if (database->IsInvalidated()) {
|
|
mAborted = true;
|
|
}
|
|
|
|
if (mConnection) {
|
|
IndexedDatabaseManager::SetCurrentDatabase(database);
|
|
|
|
if (!mAborted) {
|
|
NS_NAMED_LITERAL_CSTRING(release, "COMMIT TRANSACTION");
|
|
if (NS_FAILED(mConnection->ExecuteSimpleSQL(release))) {
|
|
mAborted = true;
|
|
}
|
|
}
|
|
|
|
if (mAborted) {
|
|
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();
|
|
|
|
if (mConnection) {
|
|
mConnection->Close();
|
|
mConnection = nsnull;
|
|
|
|
IndexedDatabaseManager::SetCurrentDatabase(nsnull);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|