diff -r 000000000000 -r 6474c204b198 dom/indexedDB/IndexedDatabaseManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/indexedDB/IndexedDatabaseManager.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,925 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "IndexedDatabaseManager.h" + +#include "nsIConsoleService.h" +#include "nsIDiskSpaceWatcher.h" +#include "nsIFile.h" +#include "nsIFileStorage.h" +#include "nsIObserverService.h" +#include "nsIScriptError.h" + +#include "jsapi.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CondVar.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/dom/ErrorEventBinding.h" +#include "mozilla/dom/quota/OriginOrPatternString.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/Utilities.h" +#include "mozilla/dom/TabContext.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "mozilla/storage.h" +#include "nsContentUtils.h" +#include "nsThreadUtils.h" + +#include "IDBEvents.h" +#include "IDBFactory.h" +#include "IDBKeyRange.h" +#include "IDBRequest.h" + +// Bindings for ResolveConstructors +#include "mozilla/dom/IDBCursorBinding.h" +#include "mozilla/dom/IDBDatabaseBinding.h" +#include "mozilla/dom/IDBFactoryBinding.h" +#include "mozilla/dom/IDBFileHandleBinding.h" +#include "mozilla/dom/IDBKeyRangeBinding.h" +#include "mozilla/dom/IDBIndexBinding.h" +#include "mozilla/dom/IDBObjectStoreBinding.h" +#include "mozilla/dom/IDBOpenDBRequestBinding.h" +#include "mozilla/dom/IDBRequestBinding.h" +#include "mozilla/dom/IDBTransactionBinding.h" +#include "mozilla/dom/IDBVersionChangeEventBinding.h" + +#define IDB_STR "indexedDB" + +// The two possible values for the data argument when receiving the disk space +// observer notification. +#define LOW_DISK_SPACE_DATA_FULL "full" +#define LOW_DISK_SPACE_DATA_FREE "free" + +USING_INDEXEDDB_NAMESPACE +using namespace mozilla; +using namespace mozilla::dom; +USING_QUOTA_NAMESPACE + +BEGIN_INDEXEDDB_NAMESPACE + +class FileManagerInfo +{ +public: + already_AddRefed + GetFileManager(PersistenceType aPersistenceType, + const nsAString& aName) const; + + void + AddFileManager(FileManager* aFileManager); + + bool + HasFileManagers() const + { + AssertIsOnIOThread(); + + return !mPersistentStorageFileManagers.IsEmpty() || + !mTemporaryStorageFileManagers.IsEmpty(); + } + + void + InvalidateAllFileManagers() const; + + void + InvalidateAndRemoveFileManagers(PersistenceType aPersistenceType); + + void + InvalidateAndRemoveFileManager(PersistenceType aPersistenceType, + const nsAString& aName); + +private: + nsTArray >& + GetArray(PersistenceType aPersistenceType); + + const nsTArray >& + GetImmutableArray(PersistenceType aPersistenceType) const + { + return const_cast(this)->GetArray(aPersistenceType); + } + + nsTArray > mPersistentStorageFileManagers; + nsTArray > mTemporaryStorageFileManagers; +}; + +END_INDEXEDDB_NAMESPACE + +namespace { + +mozilla::StaticRefPtr gDBManager; + +mozilla::Atomic gInitialized(false); +mozilla::Atomic gClosed(false); + +class AsyncDeleteFileRunnable MOZ_FINAL : public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + AsyncDeleteFileRunnable(FileManager* aFileManager, int64_t aFileId); + +private: + nsRefPtr mFileManager; + int64_t mFileId; +}; + +class GetFileReferencesHelper MOZ_FINAL : public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + GetFileReferencesHelper(PersistenceType aPersistenceType, + const nsACString& aOrigin, + const nsAString& aDatabaseName, + int64_t aFileId) + : mPersistenceType(aPersistenceType), + mOrigin(aOrigin), + mDatabaseName(aDatabaseName), + mFileId(aFileId), + mMutex(IndexedDatabaseManager::FileMutex()), + mCondVar(mMutex, "GetFileReferencesHelper::mCondVar"), + mMemRefCnt(-1), + mDBRefCnt(-1), + mSliceRefCnt(-1), + mResult(false), + mWaiting(true) + { } + + nsresult + DispatchAndReturnFileReferences(int32_t* aMemRefCnt, + int32_t* aDBRefCnt, + int32_t* aSliceRefCnt, + bool* aResult); + +private: + PersistenceType mPersistenceType; + nsCString mOrigin; + nsString mDatabaseName; + int64_t mFileId; + + mozilla::Mutex& mMutex; + mozilla::CondVar mCondVar; + int32_t mMemRefCnt; + int32_t mDBRefCnt; + int32_t mSliceRefCnt; + bool mResult; + bool mWaiting; +}; + +struct MOZ_STACK_CLASS InvalidateInfo +{ + InvalidateInfo(PersistenceType aPersistenceType, const nsACString& aPattern) + : persistenceType(aPersistenceType), pattern(aPattern) + { } + + PersistenceType persistenceType; + const nsACString& pattern; +}; + +} // anonymous namespace + +IndexedDatabaseManager::IndexedDatabaseManager() +: mFileMutex("IndexedDatabaseManager.mFileMutex") +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); +} + +IndexedDatabaseManager::~IndexedDatabaseManager() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); +} + +bool IndexedDatabaseManager::sIsMainProcess = false; +mozilla::Atomic IndexedDatabaseManager::sLowDiskSpaceMode(false); + +// static +IndexedDatabaseManager* +IndexedDatabaseManager::GetOrCreate() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + if (IsClosed()) { + NS_ERROR("Calling GetOrCreate() after shutdown!"); + return nullptr; + } + + if (!gDBManager) { + sIsMainProcess = XRE_GetProcessType() == GeckoProcessType_Default; + + if (sIsMainProcess && Preferences::GetBool("disk_space_watcher.enabled", false)) { + // See if we're starting up in low disk space conditions. + nsCOMPtr watcher = + do_GetService(DISKSPACEWATCHER_CONTRACTID); + if (watcher) { + bool isDiskFull; + if (NS_SUCCEEDED(watcher->GetIsDiskFull(&isDiskFull))) { + sLowDiskSpaceMode = isDiskFull; + } + else { + NS_WARNING("GetIsDiskFull failed!"); + } + } + else { + NS_WARNING("No disk space watcher component available!"); + } + } + + nsRefPtr instance(new IndexedDatabaseManager()); + + nsresult rv = instance->Init(); + NS_ENSURE_SUCCESS(rv, nullptr); + + if (gInitialized.exchange(true)) { + NS_ERROR("Initialized more than once?!"); + } + + gDBManager = instance; + + ClearOnShutdown(&gDBManager); + } + + return gDBManager; +} + +// static +IndexedDatabaseManager* +IndexedDatabaseManager::Get() +{ + // Does not return an owning reference. + return gDBManager; +} + +// static +IndexedDatabaseManager* +IndexedDatabaseManager::FactoryCreate() +{ + // Returns a raw pointer that carries an owning reference! Lame, but the + // singleton factory macros force this. + IndexedDatabaseManager* mgr = GetOrCreate(); + NS_IF_ADDREF(mgr); + return mgr; +} + +nsresult +IndexedDatabaseManager::Init() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // Make sure that the quota manager is up. + QuotaManager* qm = QuotaManager::GetOrCreate(); + NS_ENSURE_STATE(qm); + + // During Init() we can't yet call IsMainProcess(), just check sIsMainProcess + // directly. + if (sIsMainProcess) { + // Must initialize the storage service on the main thread. + nsCOMPtr ss = + do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); + NS_ENSURE_STATE(ss); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + NS_ENSURE_STATE(obs); + + nsresult rv = + obs->AddObserver(this, DISKSPACEWATCHER_OBSERVER_TOPIC, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +void +IndexedDatabaseManager::Destroy() +{ + // Setting the closed flag prevents the service from being recreated. + // Don't set it though if there's no real instance created. + if (gInitialized && gClosed.exchange(true)) { + NS_ERROR("Shutdown more than once?!"); + } + + delete this; +} + +// static +nsresult +IndexedDatabaseManager::FireWindowOnError(nsPIDOMWindow* aOwner, + EventChainPostVisitor& aVisitor) +{ + NS_ENSURE_TRUE(aVisitor.mDOMEvent, NS_ERROR_UNEXPECTED); + if (!aOwner) { + return NS_OK; + } + + if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) { + return NS_OK; + } + + nsString type; + nsresult rv = aVisitor.mDOMEvent->GetType(type); + NS_ENSURE_SUCCESS(rv, rv); + + if (!type.EqualsLiteral(ERROR_EVT_STR)) { + return NS_OK; + } + + nsCOMPtr eventTarget = + aVisitor.mDOMEvent->InternalDOMEvent()->GetTarget(); + + IDBRequest* request = static_cast(eventTarget.get()); + NS_ENSURE_TRUE(request, NS_ERROR_UNEXPECTED); + + ErrorResult ret; + nsRefPtr error = request->GetError(ret); + if (ret.Failed()) { + return ret.ErrorCode(); + } + + nsString errorName; + if (error) { + error->GetName(errorName); + } + + ThreadsafeAutoJSContext cx; + RootedDictionary init(cx); + request->FillScriptErrorEvent(init); + + init.mMessage = errorName; + init.mCancelable = true; + init.mBubbles = true; + + nsCOMPtr sgo(do_QueryInterface(aOwner)); + NS_ASSERTION(sgo, "How can this happen?!"); + + nsEventStatus status = nsEventStatus_eIgnore; + if (NS_FAILED(sgo->HandleScriptError(init, &status))) { + NS_WARNING("Failed to dispatch script error event"); + status = nsEventStatus_eIgnore; + } + + bool preventDefaultCalled = status == nsEventStatus_eConsumeNoDefault; + if (preventDefaultCalled) { + return NS_OK; + } + + // Log an error to the error console. + nsCOMPtr scriptError = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (NS_FAILED(scriptError->InitWithWindowID(errorName, + init.mFilename, + EmptyString(), init.mLineno, + 0, 0, + "IndexedDB", + aOwner->WindowID()))) { + NS_WARNING("Failed to init script error!"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return consoleService->LogMessage(scriptError); +} + +// static +bool +IndexedDatabaseManager::TabContextMayAccessOrigin(const TabContext& aContext, + const nsACString& aOrigin) +{ + NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!"); + + // If aContext is for a browser element, it's allowed only to access other + // browser elements. But if aContext is not for a browser element, it may + // access both browser and non-browser elements. + nsAutoCString pattern; + QuotaManager::GetOriginPatternStringMaybeIgnoreBrowser( + aContext.OwnOrContainingAppId(), + aContext.IsBrowserElement(), + pattern); + + return PatternMatchesOrigin(pattern, aOrigin); +} + +// static +bool +IndexedDatabaseManager::DefineIndexedDB(JSContext* aCx, + JS::Handle aGlobal) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(nsContentUtils::IsCallerChrome(), "Only for chrome!"); + MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL, + "Passed object is not a global object!"); + + if (!IDBCursorBinding::GetConstructorObject(aCx, aGlobal) || + !IDBCursorWithValueBinding::GetConstructorObject(aCx, aGlobal) || + !IDBDatabaseBinding::GetConstructorObject(aCx, aGlobal) || + !IDBFactoryBinding::GetConstructorObject(aCx, aGlobal) || + !IDBFileHandleBinding::GetConstructorObject(aCx, aGlobal) || + !IDBIndexBinding::GetConstructorObject(aCx, aGlobal) || + !IDBKeyRangeBinding::GetConstructorObject(aCx, aGlobal) || + !IDBObjectStoreBinding::GetConstructorObject(aCx, aGlobal) || + !IDBOpenDBRequestBinding::GetConstructorObject(aCx, aGlobal) || + !IDBRequestBinding::GetConstructorObject(aCx, aGlobal) || + !IDBTransactionBinding::GetConstructorObject(aCx, aGlobal) || + !IDBVersionChangeEventBinding::GetConstructorObject(aCx, aGlobal)) + { + return false; + } + + nsRefPtr factory; + if (NS_FAILED(IDBFactory::Create(aCx, aGlobal, nullptr, + getter_AddRefs(factory)))) { + return false; + } + + MOZ_ASSERT(factory, "This should never fail for chrome!"); + + JS::Rooted indexedDB(aCx); + js::AssertSameCompartment(aCx, aGlobal); + if (!WrapNewBindingObject(aCx, factory, &indexedDB)) { + return false; + } + + return JS_DefineProperty(aCx, aGlobal, IDB_STR, indexedDB, JSPROP_ENUMERATE); +} + +// static +bool +IndexedDatabaseManager::IsClosed() +{ + return gClosed; +} + +#ifdef DEBUG +// static +bool +IndexedDatabaseManager::IsMainProcess() +{ + NS_ASSERTION(gDBManager, + "IsMainProcess() called before indexedDB has been initialized!"); + NS_ASSERTION((XRE_GetProcessType() == GeckoProcessType_Default) == + sIsMainProcess, "XRE_GetProcessType changed its tune!"); + return sIsMainProcess; +} + +//static +bool +IndexedDatabaseManager::InLowDiskSpaceMode() +{ + NS_ASSERTION(gDBManager, + "InLowDiskSpaceMode() called before indexedDB has been " + "initialized!"); + return sLowDiskSpaceMode; +} +#endif + +already_AddRefed +IndexedDatabaseManager::GetFileManager(PersistenceType aPersistenceType, + const nsACString& aOrigin, + const nsAString& aDatabaseName) +{ + AssertIsOnIOThread(); + + FileManagerInfo* info; + if (!mFileManagerInfos.Get(aOrigin, &info)) { + return nullptr; + } + + nsRefPtr fileManager = + info->GetFileManager(aPersistenceType, aDatabaseName); + + return fileManager.forget(); +} + +void +IndexedDatabaseManager::AddFileManager(FileManager* aFileManager) +{ + AssertIsOnIOThread(); + NS_ASSERTION(aFileManager, "Null file manager!"); + + FileManagerInfo* info; + if (!mFileManagerInfos.Get(aFileManager->Origin(), &info)) { + info = new FileManagerInfo(); + mFileManagerInfos.Put(aFileManager->Origin(), info); + } + + info->AddFileManager(aFileManager); +} + +// static +PLDHashOperator +IndexedDatabaseManager::InvalidateAndRemoveFileManagers( + const nsACString& aKey, + nsAutoPtr& aValue, + void* aUserArg) +{ + AssertIsOnIOThread(); + NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); + NS_ASSERTION(aValue, "Null pointer!"); + + if (!aUserArg) { + aValue->InvalidateAllFileManagers(); + return PL_DHASH_REMOVE; + } + + InvalidateInfo* info = static_cast(aUserArg); + + if (PatternMatchesOrigin(info->pattern, aKey)) { + aValue->InvalidateAndRemoveFileManagers(info->persistenceType); + + if (!aValue->HasFileManagers()) { + return PL_DHASH_REMOVE; + } + } + + return PL_DHASH_NEXT; +} + +void +IndexedDatabaseManager::InvalidateAllFileManagers() +{ + AssertIsOnIOThread(); + + mFileManagerInfos.Enumerate(InvalidateAndRemoveFileManagers, nullptr); +} + +void +IndexedDatabaseManager::InvalidateFileManagers( + PersistenceType aPersistenceType, + const OriginOrPatternString& aOriginOrPattern) +{ + AssertIsOnIOThread(); + NS_ASSERTION(!aOriginOrPattern.IsEmpty(), "Empty pattern!"); + + if (aOriginOrPattern.IsOrigin()) { + FileManagerInfo* info; + if (!mFileManagerInfos.Get(aOriginOrPattern, &info)) { + return; + } + + info->InvalidateAndRemoveFileManagers(aPersistenceType); + + if (!info->HasFileManagers()) { + mFileManagerInfos.Remove(aOriginOrPattern); + } + } + else { + InvalidateInfo info(aPersistenceType, aOriginOrPattern); + mFileManagerInfos.Enumerate(InvalidateAndRemoveFileManagers, &info); + } +} + +void +IndexedDatabaseManager::InvalidateFileManager(PersistenceType aPersistenceType, + const nsACString& aOrigin, + const nsAString& aDatabaseName) +{ + AssertIsOnIOThread(); + + FileManagerInfo* info; + if (!mFileManagerInfos.Get(aOrigin, &info)) { + return; + } + + info->InvalidateAndRemoveFileManager(aPersistenceType, aDatabaseName); + + if (!info->HasFileManagers()) { + mFileManagerInfos.Remove(aOrigin); + } +} + +nsresult +IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager, + int64_t aFileId) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_ARG_POINTER(aFileManager); + + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + // See if we're currently clearing the storages for this origin. If so then + // we pretend that we've already deleted everything. + if (quotaManager->IsClearOriginPending( + aFileManager->Origin(), + Nullable(aFileManager->Type()))) { + return NS_OK; + } + + nsRefPtr runnable = + new AsyncDeleteFileRunnable(aFileManager, aFileId); + + nsresult rv = + quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +IndexedDatabaseManager::BlockAndGetFileReferences( + PersistenceType aPersistenceType, + const nsACString& aOrigin, + const nsAString& aDatabaseName, + int64_t aFileId, + int32_t* aRefCnt, + int32_t* aDBRefCnt, + int32_t* aSliceRefCnt, + bool* aResult) +{ + nsRefPtr helper = + new GetFileReferencesHelper(aPersistenceType, aOrigin, aDatabaseName, + aFileId); + + nsresult rv = helper->DispatchAndReturnFileReferences(aRefCnt, aDBRefCnt, + aSliceRefCnt, aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMPL_ADDREF(IndexedDatabaseManager) +NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager, Destroy()) +NS_IMPL_QUERY_INTERFACE(IndexedDatabaseManager, nsIIndexedDatabaseManager, + nsIObserver) + +NS_IMETHODIMP +IndexedDatabaseManager::InitWindowless(JS::Handle aGlobal, JSContext* aCx) +{ + NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE); + + JS::Rooted global(aCx, JSVAL_TO_OBJECT(aGlobal)); + if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) { + NS_WARNING("Passed object is not a global object!"); + return NS_ERROR_FAILURE; + } + + bool hasIndexedDB; + if (!JS_HasProperty(aCx, global, IDB_STR, &hasIndexedDB)) { + return NS_ERROR_FAILURE; + } + + if (hasIndexedDB) { + NS_WARNING("Passed object already has an 'indexedDB' property!"); + return NS_ERROR_FAILURE; + } + + if (!DefineIndexedDB(aCx, global)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +IndexedDatabaseManager::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + NS_ASSERTION(IsMainProcess(), "Wrong process!"); + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + if (!strcmp(aTopic, DISKSPACEWATCHER_OBSERVER_TOPIC)) { + NS_ASSERTION(aData, "No data?!"); + + const nsDependentString data(aData); + + if (data.EqualsLiteral(LOW_DISK_SPACE_DATA_FULL)) { + sLowDiskSpaceMode = true; + } + else if (data.EqualsLiteral(LOW_DISK_SPACE_DATA_FREE)) { + sLowDiskSpaceMode = false; + } + else { + NS_NOTREACHED("Unknown data value!"); + } + + return NS_OK; + } + + NS_NOTREACHED("Unknown topic!"); + return NS_ERROR_UNEXPECTED; + } + +already_AddRefed +FileManagerInfo::GetFileManager(PersistenceType aPersistenceType, + const nsAString& aName) const +{ + AssertIsOnIOThread(); + + const nsTArray >& managers = + GetImmutableArray(aPersistenceType); + + for (uint32_t i = 0; i < managers.Length(); i++) { + const nsRefPtr& fileManager = managers[i]; + + if (fileManager->DatabaseName() == aName) { + nsRefPtr result = fileManager; + return result.forget(); + } + } + + return nullptr; +} + +void +FileManagerInfo::AddFileManager(FileManager* aFileManager) +{ + AssertIsOnIOThread(); + + nsTArray >& managers = GetArray(aFileManager->Type()); + + NS_ASSERTION(!managers.Contains(aFileManager), "Adding more than once?!"); + + managers.AppendElement(aFileManager); +} + +void +FileManagerInfo::InvalidateAllFileManagers() const +{ + AssertIsOnIOThread(); + + uint32_t i; + + for (i = 0; i < mPersistentStorageFileManagers.Length(); i++) { + mPersistentStorageFileManagers[i]->Invalidate(); + } + + for (i = 0; i < mTemporaryStorageFileManagers.Length(); i++) { + mTemporaryStorageFileManagers[i]->Invalidate(); + } +} + +void +FileManagerInfo::InvalidateAndRemoveFileManagers( + PersistenceType aPersistenceType) +{ + AssertIsOnIOThread(); + + nsTArray >& managers = GetArray(aPersistenceType); + + for (uint32_t i = 0; i < managers.Length(); i++) { + managers[i]->Invalidate(); + } + + managers.Clear(); +} + +void +FileManagerInfo::InvalidateAndRemoveFileManager( + PersistenceType aPersistenceType, + const nsAString& aName) +{ + AssertIsOnIOThread(); + + nsTArray >& managers = GetArray(aPersistenceType); + + for (uint32_t i = 0; i < managers.Length(); i++) { + nsRefPtr& fileManager = managers[i]; + if (fileManager->DatabaseName() == aName) { + fileManager->Invalidate(); + managers.RemoveElementAt(i); + return; + } + } +} + +nsTArray >& +FileManagerInfo::GetArray(PersistenceType aPersistenceType) +{ + switch (aPersistenceType) { + case PERSISTENCE_TYPE_PERSISTENT: + return mPersistentStorageFileManagers; + case PERSISTENCE_TYPE_TEMPORARY: + return mTemporaryStorageFileManagers; + + case PERSISTENCE_TYPE_INVALID: + default: + MOZ_CRASH("Bad storage type value!"); + return mPersistentStorageFileManagers; + } +} + +AsyncDeleteFileRunnable::AsyncDeleteFileRunnable(FileManager* aFileManager, + int64_t aFileId) +: mFileManager(aFileManager), mFileId(aFileId) +{ +} + +NS_IMPL_ISUPPORTS(AsyncDeleteFileRunnable, + nsIRunnable) + +NS_IMETHODIMP +AsyncDeleteFileRunnable::Run() +{ + AssertIsOnIOThread(); + + nsCOMPtr directory = mFileManager->GetDirectory(); + NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE); + + nsCOMPtr file = mFileManager->GetFileForId(directory, mFileId); + NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); + + nsresult rv; + int64_t fileSize; + + if (mFileManager->Privilege() != Chrome) { + rv = file->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + + rv = file->Remove(false); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + if (mFileManager->Privilege() != Chrome) { + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->DecreaseUsageForOrigin(mFileManager->Type(), + mFileManager->Group(), + mFileManager->Origin(), fileSize); + } + + directory = mFileManager->GetJournalDirectory(); + NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE); + + file = mFileManager->GetFileForId(directory, mFileId); + NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); + + rv = file->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +GetFileReferencesHelper::DispatchAndReturnFileReferences(int32_t* aMemRefCnt, + int32_t* aDBRefCnt, + int32_t* aSliceRefCnt, + bool* aResult) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + nsresult rv = + quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + mozilla::MutexAutoLock autolock(mMutex); + while (mWaiting) { + mCondVar.Wait(); + } + + *aMemRefCnt = mMemRefCnt; + *aDBRefCnt = mDBRefCnt; + *aSliceRefCnt = mSliceRefCnt; + *aResult = mResult; + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(GetFileReferencesHelper, + nsIRunnable) + +NS_IMETHODIMP +GetFileReferencesHelper::Run() +{ + AssertIsOnIOThread(); + + IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); + NS_ASSERTION(mgr, "This should never fail!"); + + nsRefPtr fileManager = + mgr->GetFileManager(mPersistenceType, mOrigin, mDatabaseName); + + if (fileManager) { + nsRefPtr fileInfo = fileManager->GetFileInfo(mFileId); + + if (fileInfo) { + fileInfo->GetReferences(&mMemRefCnt, &mDBRefCnt, &mSliceRefCnt); + + if (mMemRefCnt != -1) { + // We added an extra temp ref, so account for that accordingly. + mMemRefCnt--; + } + + mResult = true; + } + } + + mozilla::MutexAutoLock lock(mMutex); + NS_ASSERTION(mWaiting, "Huh?!"); + + mWaiting = false; + mCondVar.Notify(); + + return NS_OK; +}