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