michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "base/basictypes.h" michael@0: michael@0: #include "IDBDatabase.h" michael@0: michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/storage.h" michael@0: #include "mozilla/dom/ContentParent.h" michael@0: #include "mozilla/dom/DOMStringList.h" michael@0: #include "mozilla/dom/DOMStringListBinding.h" michael@0: #include "mozilla/dom/quota/Client.h" michael@0: #include "mozilla/dom/quota/QuotaManager.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #include "AsyncConnectionHelper.h" michael@0: #include "DatabaseInfo.h" michael@0: #include "IDBEvents.h" michael@0: #include "IDBFactory.h" michael@0: #include "IDBFileHandle.h" michael@0: #include "IDBIndex.h" michael@0: #include "IDBObjectStore.h" michael@0: #include "IDBTransaction.h" michael@0: #include "IDBFactory.h" michael@0: #include "ProfilerHelpers.h" michael@0: #include "ReportInternalError.h" michael@0: #include "TransactionThreadPool.h" michael@0: michael@0: #include "ipc/IndexedDBChild.h" michael@0: #include "ipc/IndexedDBParent.h" michael@0: michael@0: #include "mozilla/dom/IDBDatabaseBinding.h" michael@0: michael@0: USING_INDEXEDDB_NAMESPACE michael@0: using mozilla::dom::ContentParent; michael@0: using mozilla::dom::quota::AssertIsOnIOThread; michael@0: using mozilla::dom::quota::Client; michael@0: using mozilla::dom::quota::QuotaManager; michael@0: using mozilla::ErrorResult; michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: namespace { michael@0: michael@0: class NoRequestDatabaseHelper : public AsyncConnectionHelper michael@0: { michael@0: public: michael@0: NoRequestDatabaseHelper(IDBTransaction* aTransaction) michael@0: : AsyncConnectionHelper(aTransaction, nullptr) michael@0: { michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: NS_ASSERTION(aTransaction, "Null transaction!"); michael@0: } michael@0: michael@0: virtual ChildProcessSendResult michael@0: SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; michael@0: michael@0: virtual nsresult michael@0: UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) michael@0: MOZ_OVERRIDE; michael@0: michael@0: virtual nsresult OnSuccess() MOZ_OVERRIDE; michael@0: michael@0: virtual void OnError() MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: class CreateObjectStoreHelper : public NoRequestDatabaseHelper michael@0: { michael@0: public: michael@0: CreateObjectStoreHelper(IDBTransaction* aTransaction, michael@0: IDBObjectStore* aObjectStore) michael@0: : NoRequestDatabaseHelper(aTransaction), mObjectStore(aObjectStore) michael@0: { } michael@0: michael@0: virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) michael@0: MOZ_OVERRIDE; michael@0: michael@0: virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: nsRefPtr mObjectStore; michael@0: }; michael@0: michael@0: class DeleteObjectStoreHelper : public NoRequestDatabaseHelper michael@0: { michael@0: public: michael@0: DeleteObjectStoreHelper(IDBTransaction* aTransaction, michael@0: int64_t aObjectStoreId) michael@0: : NoRequestDatabaseHelper(aTransaction), mObjectStoreId(aObjectStoreId) michael@0: { } michael@0: michael@0: virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) michael@0: MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: // In-params. michael@0: int64_t mObjectStoreId; michael@0: }; michael@0: michael@0: class CreateFileHelper : public AsyncConnectionHelper michael@0: { michael@0: public: michael@0: CreateFileHelper(IDBDatabase* aDatabase, michael@0: IDBRequest* aRequest, michael@0: const nsAString& aName, michael@0: const nsAString& aType) michael@0: : AsyncConnectionHelper(aDatabase, aRequest), michael@0: mName(aName), mType(aType) michael@0: { } michael@0: michael@0: ~CreateFileHelper() michael@0: { } michael@0: michael@0: nsresult DoDatabaseWork(mozIStorageConnection* aConnection); michael@0: nsresult GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal); michael@0: void ReleaseMainThreadObjects() michael@0: { michael@0: mFileInfo = nullptr; michael@0: AsyncConnectionHelper::ReleaseMainThreadObjects(); michael@0: } michael@0: michael@0: virtual ChildProcessSendResult SendResponseToChildProcess( michael@0: nsresult aResultCode) michael@0: MOZ_OVERRIDE michael@0: { michael@0: return Success_NotSent; michael@0: } michael@0: michael@0: virtual nsresult UnpackResponseFromParentProcess( michael@0: const ResponseValue& aResponseValue) michael@0: MOZ_OVERRIDE michael@0: { michael@0: MOZ_CRASH("Should never get here!"); michael@0: } michael@0: michael@0: private: michael@0: // In-params. michael@0: nsString mName; michael@0: nsString mType; michael@0: michael@0: // Out-params. michael@0: nsRefPtr mFileInfo; michael@0: }; michael@0: michael@0: class MOZ_STACK_CLASS AutoRemoveObjectStore michael@0: { michael@0: public: michael@0: AutoRemoveObjectStore(DatabaseInfo* aInfo, const nsAString& aName) michael@0: : mInfo(aInfo), mName(aName) michael@0: { } michael@0: michael@0: ~AutoRemoveObjectStore() michael@0: { michael@0: if (mInfo) { michael@0: mInfo->RemoveObjectStore(mName); michael@0: } michael@0: } michael@0: michael@0: void forget() michael@0: { michael@0: mInfo = nullptr; michael@0: } michael@0: michael@0: private: michael@0: DatabaseInfo* mInfo; michael@0: nsString mName; michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: // static michael@0: already_AddRefed michael@0: IDBDatabase::Create(IDBWrapperCache* aOwnerCache, michael@0: IDBFactory* aFactory, michael@0: already_AddRefed aDatabaseInfo, michael@0: const nsACString& aASCIIOrigin, michael@0: FileManager* aFileManager, michael@0: mozilla::dom::ContentParent* aContentParent) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aFactory, "Null pointer!"); michael@0: NS_ASSERTION(!aASCIIOrigin.IsEmpty(), "Empty origin!"); michael@0: michael@0: nsRefPtr databaseInfo(aDatabaseInfo); michael@0: NS_ASSERTION(databaseInfo, "Null pointer!"); michael@0: michael@0: nsRefPtr db(new IDBDatabase(aOwnerCache)); michael@0: michael@0: db->SetScriptOwner(aOwnerCache->GetScriptOwner()); michael@0: db->mFactory = aFactory; michael@0: db->mDatabaseId = databaseInfo->id; michael@0: db->mName = databaseInfo->name; michael@0: db->mFilePath = databaseInfo->filePath; michael@0: db->mPersistenceType = databaseInfo->persistenceType; michael@0: db->mGroup = databaseInfo->group; michael@0: databaseInfo.swap(db->mDatabaseInfo); michael@0: db->mASCIIOrigin = aASCIIOrigin; michael@0: db->mFileManager = aFileManager; michael@0: db->mContentParent = aContentParent; michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never be null!"); michael@0: michael@0: db->mQuotaClient = quotaManager->GetClient(Client::IDB); michael@0: NS_ASSERTION(db->mQuotaClient, "This shouldn't fail!"); michael@0: michael@0: if (!quotaManager->RegisterStorage(db)) { michael@0: // Either out of memory or shutting down. michael@0: return nullptr; michael@0: } michael@0: michael@0: db->mRegistered = true; michael@0: michael@0: return db.forget(); michael@0: } michael@0: michael@0: // static michael@0: IDBDatabase* michael@0: IDBDatabase::FromStorage(nsIOfflineStorage* aStorage) michael@0: { michael@0: return aStorage->GetClient()->GetType() == Client::IDB ? michael@0: static_cast(aStorage) : nullptr; michael@0: } michael@0: michael@0: IDBDatabase::IDBDatabase(IDBWrapperCache* aOwnerCache) michael@0: : IDBWrapperCache(aOwnerCache), michael@0: mActorChild(nullptr), michael@0: mActorParent(nullptr), michael@0: mContentParent(nullptr), michael@0: mInvalidated(false), michael@0: mRegistered(false), michael@0: mClosed(false), michael@0: mRunningVersionChange(false) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: } michael@0: michael@0: IDBDatabase::~IDBDatabase() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::LastRelease() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: NS_ASSERTION(!mActorParent, "Actor parent owns us, how can we be dying?!"); michael@0: if (mActorChild) { michael@0: NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: mActorChild->Send__delete__(mActorChild); michael@0: NS_ASSERTION(!mActorChild, "Should have cleared in Send__delete__!"); michael@0: } michael@0: michael@0: if (mRegistered) { michael@0: CloseInternal(true); michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: if (quotaManager) { michael@0: quotaManager->UnregisterStorage(this); michael@0: } michael@0: mRegistered = false; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: IDBDatabase::Invalidate() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: InvalidateInternal(/* aIsDead */ false); michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::InvalidateInternal(bool aIsDead) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (IsInvalidated()) { michael@0: return; michael@0: } michael@0: michael@0: mInvalidated = true; michael@0: michael@0: // Make sure we're closed too. michael@0: Close(); michael@0: michael@0: // When the IndexedDatabaseManager needs to invalidate databases, all it has michael@0: // is an origin, so we call into the quota manager here to cancel any prompts michael@0: // for our owner. michael@0: nsPIDOMWindow* owner = GetOwner(); michael@0: if (owner) { michael@0: QuotaManager::CancelPromptsForWindow(owner); michael@0: } michael@0: michael@0: // We want to forcefully remove in the child when the parent has invalidated michael@0: // us in IPC mode because the database might no longer exist. michael@0: // We don't want to forcefully remove in the parent when a child dies since michael@0: // other child processes may be using the referenced DatabaseInfo. michael@0: if (!aIsDead) { michael@0: DatabaseInfo::Remove(mDatabaseId); michael@0: } michael@0: michael@0: // And let the child process know as well. michael@0: if (mActorParent) { michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: mActorParent->Invalidate(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::DisconnectFromActorParent() michael@0: { michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // Make sure we're closed too. michael@0: Close(); michael@0: michael@0: // Kill any outstanding prompts. michael@0: nsPIDOMWindow* owner = GetOwner(); michael@0: if (owner) { michael@0: QuotaManager::CancelPromptsForWindow(owner); michael@0: } michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::CloseInternal(bool aIsDead) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (!mClosed) { michael@0: mClosed = true; michael@0: michael@0: // If we're getting called from Unlink, avoid cloning the DatabaseInfo. michael@0: { michael@0: nsRefPtr previousInfo; michael@0: mDatabaseInfo.swap(previousInfo); michael@0: michael@0: if (!aIsDead) { michael@0: mDatabaseInfo = previousInfo->Clone(); michael@0: } michael@0: } michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: if (quotaManager) { michael@0: quotaManager->OnStorageClosed(this); michael@0: } michael@0: michael@0: // And let the parent process know as well. michael@0: if (mActorChild && !IsInvalidated()) { michael@0: NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: mActorChild->SendClose(aIsDead); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: IDBDatabase::IsClosed() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: return mClosed; michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::EnterSetVersionTransaction() michael@0: { michael@0: NS_ASSERTION(!mRunningVersionChange, "How did that happen?"); michael@0: michael@0: mPreviousDatabaseInfo = mDatabaseInfo->Clone(); michael@0: michael@0: mRunningVersionChange = true; michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::ExitSetVersionTransaction() michael@0: { michael@0: NS_ASSERTION(mRunningVersionChange, "How did that happen?"); michael@0: michael@0: mPreviousDatabaseInfo = nullptr; michael@0: michael@0: mRunningVersionChange = false; michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::RevertToPreviousState() michael@0: { michael@0: mDatabaseInfo = mPreviousDatabaseInfo; michael@0: mPreviousDatabaseInfo = nullptr; michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::OnUnlink() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // We've been unlinked, at the very least we should be able to prevent further michael@0: // transactions from starting and unblock any other SetVersion callers. michael@0: CloseInternal(true); michael@0: michael@0: // No reason for the QuotaManager to track us any longer. michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: if (mRegistered && quotaManager) { michael@0: quotaManager->UnregisterStorage(this); michael@0: michael@0: // Don't try to unregister again in the destructor. michael@0: mRegistered = false; michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBDatabase::CreateObjectStoreInternal(IDBTransaction* aTransaction, michael@0: const ObjectStoreInfoGuts& aInfo, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aTransaction, "Null transaction!"); michael@0: michael@0: DatabaseInfo* databaseInfo = aTransaction->DBInfo(); michael@0: michael@0: nsRefPtr newInfo = new ObjectStoreInfo(); michael@0: *static_cast(newInfo.get()) = aInfo; michael@0: michael@0: newInfo->nextAutoIncrementId = aInfo.autoIncrement ? 1 : 0; michael@0: newInfo->comittedAutoIncrementId = newInfo->nextAutoIncrementId; michael@0: michael@0: if (!databaseInfo->PutObjectStore(newInfo)) { michael@0: IDB_WARNING("Put failed!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Don't leave this in the hash if we fail below! michael@0: AutoRemoveObjectStore autoRemove(databaseInfo, newInfo->name); michael@0: michael@0: nsRefPtr objectStore = michael@0: aTransaction->GetOrCreateObjectStore(newInfo->name, newInfo, true); michael@0: if (!objectStore) { michael@0: IDB_WARNING("Failed to get objectStore!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (IndexedDatabaseManager::IsMainProcess()) { michael@0: nsRefPtr helper = michael@0: new CreateObjectStoreHelper(aTransaction, objectStore); michael@0: michael@0: nsresult rv = helper->DispatchToTransactionPool(); michael@0: if (NS_FAILED(rv)) { michael@0: IDB_WARNING("Failed to dispatch!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: autoRemove.forget(); michael@0: michael@0: IDB_PROFILER_MARK("IndexedDB Pseudo-request: " michael@0: "database(%s).transaction(%s).createObjectStore(%s)", michael@0: "MT IDBDatabase.createObjectStore()", michael@0: IDB_PROFILER_STRING(this), michael@0: IDB_PROFILER_STRING(aTransaction), michael@0: IDB_PROFILER_STRING(objectStore)); michael@0: michael@0: return objectStore.forget(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(IDBDatabase) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBDatabase, IDBWrapperCache) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFactory) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBDatabase, IDBWrapperCache) michael@0: // Don't unlink mFactory! michael@0: michael@0: // Do some cleanup. michael@0: tmp->OnUnlink(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBDatabase) michael@0: NS_INTERFACE_MAP_ENTRY(nsIFileStorage) michael@0: NS_INTERFACE_MAP_ENTRY(nsIOfflineStorage) michael@0: NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(IDBDatabase, IDBWrapperCache) michael@0: NS_IMPL_RELEASE_INHERITED(IDBDatabase, IDBWrapperCache) michael@0: michael@0: JSObject* michael@0: IDBDatabase::WrapObject(JSContext* aCx) michael@0: { michael@0: return IDBDatabaseBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: uint64_t michael@0: IDBDatabase::Version() const michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: DatabaseInfo* info = Info(); michael@0: return info->version; michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBDatabase::GetObjectStoreNames(ErrorResult& aRv) const michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: DatabaseInfo* info = Info(); michael@0: michael@0: nsRefPtr list(new DOMStringList()); michael@0: if (!info->GetObjectStoreNames(list->StringArray())) { michael@0: IDB_WARNING("Couldn't get names!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: return list.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBDatabase::CreateObjectStore( michael@0: JSContext* aCx, const nsAString& aName, michael@0: const IDBObjectStoreParameters& aOptionalParameters, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction(); michael@0: michael@0: if (!transaction || michael@0: transaction->GetMode() != IDBTransaction::VERSION_CHANGE) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: DatabaseInfo* databaseInfo = transaction->DBInfo(); michael@0: michael@0: KeyPath keyPath(0); michael@0: if (NS_FAILED(KeyPath::Parse(aCx, aOptionalParameters.mKeyPath, &keyPath))) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (databaseInfo->ContainsStoreName(aName)) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!keyPath.IsAllowedForObjectStore(aOptionalParameters.mAutoIncrement)) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: ObjectStoreInfoGuts guts; michael@0: michael@0: guts.name = aName; michael@0: guts.id = databaseInfo->nextObjectStoreId++; michael@0: guts.keyPath = keyPath; michael@0: guts.autoIncrement = aOptionalParameters.mAutoIncrement; michael@0: michael@0: return CreateObjectStoreInternal(transaction, guts, aRv); michael@0: } michael@0: michael@0: void michael@0: IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction(); michael@0: michael@0: if (!transaction || michael@0: transaction->GetMode() != IDBTransaction::VERSION_CHANGE) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); michael@0: return; michael@0: } michael@0: michael@0: DatabaseInfo* info = transaction->DBInfo(); michael@0: ObjectStoreInfo* objectStoreInfo = info->GetObjectStore(aName); michael@0: if (!objectStoreInfo) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (IndexedDatabaseManager::IsMainProcess()) { michael@0: nsRefPtr helper = michael@0: new DeleteObjectStoreHelper(transaction, objectStoreInfo->id); michael@0: michael@0: nsresult rv = helper->DispatchToTransactionPool(); michael@0: if (NS_FAILED(rv)) { michael@0: IDB_WARNING("Failed to dispatch!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: IndexedDBTransactionChild* actor = transaction->GetActorChild(); michael@0: NS_ASSERTION(actor, "Must have an actor here!"); michael@0: michael@0: actor->SendDeleteObjectStore(nsString(aName)); michael@0: } michael@0: michael@0: transaction->RemoveObjectStore(aName); michael@0: michael@0: IDB_PROFILER_MARK("IndexedDB Pseudo-request: " michael@0: "database(%s).transaction(%s).deleteObjectStore(\"%s\")", michael@0: "MT IDBDatabase.deleteObjectStore()", michael@0: IDB_PROFILER_STRING(this), michael@0: IDB_PROFILER_STRING(transaction), michael@0: NS_ConvertUTF16toUTF8(aName).get()); michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBDatabase::Transaction(const Sequence& aStoreNames, michael@0: IDBTransactionMode aMode, ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (QuotaManager::IsShuttingDown()) { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (mClosed) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (mRunningVersionChange) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aStoreNames.IsEmpty()) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: IDBTransaction::Mode transactionMode = IDBTransaction::READ_ONLY; michael@0: switch (aMode) { michael@0: case IDBTransactionMode::Readonly: michael@0: transactionMode = IDBTransaction::READ_ONLY; michael@0: break; michael@0: case IDBTransactionMode::Readwrite: michael@0: transactionMode = IDBTransaction::READ_WRITE; michael@0: break; michael@0: case IDBTransactionMode::Versionchange: michael@0: transactionMode = IDBTransaction::VERSION_CHANGE; michael@0: break; michael@0: default: michael@0: MOZ_CRASH("Unknown mode!"); michael@0: } michael@0: michael@0: // Now check to make sure the object store names we collected actually exist. michael@0: DatabaseInfo* info = Info(); michael@0: for (uint32_t index = 0; index < aStoreNames.Length(); index++) { michael@0: if (!info->ContainsStoreName(aStoreNames[index])) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: nsRefPtr transaction = michael@0: IDBTransaction::Create(this, aStoreNames, transactionMode, false); michael@0: if (!transaction) { michael@0: IDB_WARNING("Failed to create the transaction!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: IDB_PROFILER_MARK("IndexedDB Transaction %llu: database(%s).transaction(%s)", michael@0: "IDBTransaction[%llu] MT Started", michael@0: transaction->GetSerialNumber(), IDB_PROFILER_STRING(this), michael@0: IDB_PROFILER_STRING(transaction)); michael@0: michael@0: return transaction.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: IDBDatabase::MozCreateFileHandle(const nsAString& aName, michael@0: const Optional& aType, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (!IndexedDatabaseManager::IsMainProcess()) { michael@0: IDB_WARNING("Not supported yet!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (QuotaManager::IsShuttingDown()) { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (mClosed) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr request = IDBRequest::Create(this, nullptr); michael@0: michael@0: nsRefPtr helper = michael@0: new CreateFileHelper(this, request, aName, michael@0: aType.WasPassed() ? aType.Value() : EmptyString()); michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "We should definitely have a manager here"); michael@0: michael@0: nsresult rv = helper->Dispatch(quotaManager->IOThread()); michael@0: if (NS_FAILED(rv)) { michael@0: IDB_WARNING("Failed to dispatch!"); michael@0: aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: return request.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: IDBDatabase::Close() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: CloseInternal(false); michael@0: michael@0: NS_ASSERTION(mClosed, "Should have set the closed flag!"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(const nsACString&) michael@0: IDBDatabase::Id() michael@0: { michael@0: return mDatabaseId; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: IDBDatabase::IsInvalidated() michael@0: { michael@0: return mInvalidated; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: IDBDatabase::IsShuttingDown() michael@0: { michael@0: return QuotaManager::IsShuttingDown(); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: IDBDatabase::SetThreadLocals() michael@0: { michael@0: NS_ASSERTION(GetOwner(), "Should have owner!"); michael@0: QuotaManager::SetCurrentWindow(GetOwner()); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: IDBDatabase::UnsetThreadLocals() michael@0: { michael@0: QuotaManager::SetCurrentWindow(nullptr); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(mozilla::dom::quota::Client*) michael@0: IDBDatabase::GetClient() michael@0: { michael@0: return mQuotaClient; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: IDBDatabase::IsOwned(nsPIDOMWindow* aOwner) michael@0: { michael@0: return GetOwner() == aOwner; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(const nsACString&) michael@0: IDBDatabase::Origin() michael@0: { michael@0: return mASCIIOrigin; michael@0: } michael@0: michael@0: nsresult michael@0: IDBDatabase::PostHandleEvent(EventChainPostVisitor& aVisitor) michael@0: { michael@0: return IndexedDatabaseManager::FireWindowOnError(GetOwner(), aVisitor); michael@0: } michael@0: michael@0: AsyncConnectionHelper::ChildProcessSendResult michael@0: NoRequestDatabaseHelper::SendResponseToChildProcess(nsresult aResultCode) michael@0: { michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: return Success_NotSent; michael@0: } michael@0: michael@0: nsresult michael@0: NoRequestDatabaseHelper::UnpackResponseFromParentProcess( michael@0: const ResponseValue& aResponseValue) michael@0: { michael@0: MOZ_CRASH("Should never get here!"); michael@0: } michael@0: michael@0: nsresult michael@0: NoRequestDatabaseHelper::OnSuccess() michael@0: { michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: NoRequestDatabaseHelper::OnError() michael@0: { michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: mTransaction->Abort(GetResultCode()); michael@0: } michael@0: michael@0: nsresult michael@0: CreateObjectStoreHelper::DoDatabaseWork(mozIStorageConnection* aConnection) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "CreateObjectStoreHelper::DoDatabaseWork"); michael@0: michael@0: if (IndexedDatabaseManager::InLowDiskSpaceMode()) { michael@0: NS_WARNING("Refusing to create additional objectStore because disk space " michael@0: "is low!"); michael@0: return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; michael@0: } michael@0: michael@0: nsCOMPtr stmt = michael@0: mTransaction->GetCachedStatement(NS_LITERAL_CSTRING( michael@0: "INSERT INTO object_store (id, auto_increment, name, key_path) " michael@0: "VALUES (:id, :auto_increment, :name, :key_path)" michael@0: )); michael@0: IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), michael@0: mObjectStore->Id()); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("auto_increment"), michael@0: mObjectStore->IsAutoIncrement() ? 1 : 0); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mObjectStore->Name()); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: const KeyPath& keyPath = mObjectStore->GetKeyPath(); michael@0: if (keyPath.IsValid()) { michael@0: nsAutoString keyPathSerialization; michael@0: keyPath.SerializeToString(keyPathSerialization); michael@0: rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"), michael@0: keyPathSerialization); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: } michael@0: else { michael@0: rv = stmt->BindNullByName(NS_LITERAL_CSTRING("key_path")); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: } michael@0: michael@0: rv = stmt->Execute(); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CreateObjectStoreHelper::ReleaseMainThreadObjects() michael@0: { michael@0: mObjectStore = nullptr; michael@0: NoRequestDatabaseHelper::ReleaseMainThreadObjects(); michael@0: } michael@0: michael@0: nsresult michael@0: DeleteObjectStoreHelper::DoDatabaseWork(mozIStorageConnection* aConnection) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "DeleteObjectStoreHelper::DoDatabaseWork"); michael@0: michael@0: nsCOMPtr stmt = michael@0: mTransaction->GetCachedStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM object_store " michael@0: "WHERE id = :id " michael@0: )); michael@0: IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mObjectStoreId); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = stmt->Execute(); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CreateFileHelper::DoDatabaseWork(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "CreateFileHelper::DoDatabaseWork"); michael@0: michael@0: if (IndexedDatabaseManager::InLowDiskSpaceMode()) { michael@0: NS_WARNING("Refusing to create file because disk space is low!"); michael@0: return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; michael@0: } michael@0: michael@0: FileManager* fileManager = mDatabase->Manager(); michael@0: michael@0: mFileInfo = fileManager->GetNewFileInfo(); michael@0: IDB_ENSURE_TRUE(mFileInfo, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: const int64_t& fileId = mFileInfo->Id(); michael@0: michael@0: nsCOMPtr directory = fileManager->EnsureJournalDirectory(); michael@0: NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr file = fileManager->GetFileForId(directory, fileId); michael@0: NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: directory = fileManager->GetDirectory(); michael@0: IDB_ENSURE_TRUE(directory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: file = fileManager->GetFileForId(directory, fileId); michael@0: IDB_ENSURE_TRUE(file, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CreateFileHelper::GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal) michael@0: { michael@0: nsRefPtr fileHandle = michael@0: IDBFileHandle::Create(mDatabase, mName, mType, mFileInfo.forget()); michael@0: IDB_ENSURE_TRUE(fileHandle, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: return WrapNative(aCx, NS_ISUPPORTS_CAST(EventTarget*, fileHandle), aVal); michael@0: }