/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize #include "nsIConsoleService.h" #include "nsIDOMWindow.h" #include "nsIEventTarget.h" #include "nsIFile.h" #include "nsIScriptError.h" #include "nsIScriptGlobalObject.h" #include "jsapi.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/CondVar.h" #include "mozilla/ContentEvents.h" #include "mozilla/EventDispatcher.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/dom/DOMError.h" #include "mozilla/dom/ErrorEvent.h" #include "mozilla/dom/ErrorEventBinding.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/PBackgroundChild.h" #include "nsContentUtils.h" #include "nsGlobalWindow.h" #include "nsThreadUtils.h" #include "mozilla/Logging.h" #include "FileInfo.h" #include "FileManager.h" #include "IDBEvents.h" #include "IDBFactory.h" #include "IDBKeyRange.h" #include "IDBRequest.h" #include "ProfilerHelpers.h" #include "ScriptErrorHelper.h" #include "WorkerScope.h" #include "WorkerPrivate.h" // Bindings for ResolveConstructors #include "mozilla/dom/IDBCursorBinding.h" #include "mozilla/dom/IDBDatabaseBinding.h" #include "mozilla/dom/IDBFactoryBinding.h" #include "mozilla/dom/IDBIndexBinding.h" #include "mozilla/dom/IDBKeyRangeBinding.h" #include "mozilla/dom/IDBMutableFileBinding.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" #include "nsCharSeparatedTokenizer.h" #include "unicode/locid.h" #define IDB_STR "indexedDB" namespace mozilla { namespace dom { namespace indexedDB { using namespace mozilla::dom::quota; using namespace mozilla::dom::workers; using namespace mozilla::ipc; 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() || !mDefaultStorageFileManagers.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; nsTArray > mDefaultStorageFileManagers; }; } // namespace indexedDB using namespace mozilla::dom::indexedDB; namespace { NS_DEFINE_IID(kIDBRequestIID, PRIVATE_IDBREQUEST_IID); const uint32_t kDeleteTimeoutMs = 1000; // The threshold we use for structured clone data storing. // Anything smaller than the threshold is compressed and stored in the database. // Anything larger is compressed and stored outside the database. const int32_t kDefaultDataThresholdBytes = 1024 * 1024; // 1MB // The maximal size of a serialized object to be transfered through IPC. const int32_t kDefaultMaxSerializedMsgSize = IPC::Channel::kMaximumMessageSize; #define IDB_PREF_BRANCH_ROOT "dom.indexedDB." const char kTestingPref[] = IDB_PREF_BRANCH_ROOT "testing"; const char kPrefExperimental[] = IDB_PREF_BRANCH_ROOT "experimental"; const char kPrefFileHandle[] = "dom.fileHandle.enabled"; const char kDataThresholdPref[] = IDB_PREF_BRANCH_ROOT "dataThreshold"; const char kPrefMaxSerilizedMsgSize[] = IDB_PREF_BRANCH_ROOT "maxSerializedMsgSize"; #define IDB_PREF_LOGGING_BRANCH_ROOT IDB_PREF_BRANCH_ROOT "logging." const char kPrefLoggingEnabled[] = IDB_PREF_LOGGING_BRANCH_ROOT "enabled"; const char kPrefLoggingDetails[] = IDB_PREF_LOGGING_BRANCH_ROOT "details"; #if defined(DEBUG) const char kPrefLoggingProfiler[] = IDB_PREF_LOGGING_BRANCH_ROOT "profiler-marks"; #endif #undef IDB_PREF_LOGGING_BRANCH_ROOT #undef IDB_PREF_BRANCH_ROOT StaticRefPtr gDBManager; Atomic gInitialized(false); Atomic gClosed(false); Atomic gTestingMode(false); Atomic gExperimentalFeaturesEnabled(false); Atomic gFileHandleEnabled(false); Atomic gDataThresholdBytes(0); Atomic gMaxSerializedMsgSize(0); class DeleteFilesRunnable final : public nsIRunnable , public OpenDirectoryListener { typedef mozilla::dom::quota::DirectoryLock DirectoryLock; enum State { // Just created on the main thread. Next step is State_DirectoryOpenPending. State_Initial, // Waiting for directory open allowed on the main thread. The next step is // State_DatabaseWorkOpen. State_DirectoryOpenPending, // Waiting to do/doing work on the QuotaManager IO thread. The next step is // State_UnblockingOpen. State_DatabaseWorkOpen, // Notifying the QuotaManager that it can proceed to the next operation on // the main thread. Next step is State_Completed. State_UnblockingOpen, // All done. State_Completed }; nsCOMPtr mBackgroundThread; RefPtr mFileManager; nsTArray mFileIds; RefPtr mDirectoryLock; nsCOMPtr mDirectory; nsCOMPtr mJournalDirectory; State mState; public: DeleteFilesRunnable(nsIEventTarget* aBackgroundThread, FileManager* aFileManager, nsTArray& aFileIds); void Dispatch(); NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIRUNNABLE virtual void DirectoryLockAcquired(DirectoryLock* aLock) override; virtual void DirectoryLockFailed() override; private: ~DeleteFilesRunnable() {} nsresult Open(); nsresult DeleteFile(int64_t aFileId); nsresult DoDatabaseWork(); void Finish(); void UnblockOpen(); }; void AtomicBoolPrefChangedCallback(const char* aPrefName, void* aClosure) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aClosure); *static_cast*>(aClosure) = Preferences::GetBool(aPrefName); } void DataThresholdPrefChangedCallback(const char* aPrefName, void* aClosure) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!strcmp(aPrefName, kDataThresholdPref)); MOZ_ASSERT(!aClosure); int32_t dataThresholdBytes = Preferences::GetInt(aPrefName, kDefaultDataThresholdBytes); // The magic -1 is for use only by tests that depend on stable blob file id's. if (dataThresholdBytes == -1) { dataThresholdBytes = INT32_MAX; } gDataThresholdBytes = dataThresholdBytes; } void MaxSerializedMsgSizePrefChangeCallback(const char* aPrefName, void* aClosure) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!strcmp(aPrefName, kPrefMaxSerilizedMsgSize)); MOZ_ASSERT(!aClosure); gMaxSerializedMsgSize = Preferences::GetInt(aPrefName, kDefaultMaxSerializedMsgSize); MOZ_ASSERT(gMaxSerializedMsgSize > 0); } } // namespace IndexedDatabaseManager::IndexedDatabaseManager() : mFileMutex("IndexedDatabaseManager.mFileMutex") , mBackgroundActor(nullptr) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); } IndexedDatabaseManager::~IndexedDatabaseManager() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (mBackgroundActor) { mBackgroundActor->SendDeleteMeInternal(); MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!"); } } bool IndexedDatabaseManager::sIsMainProcess = false; bool IndexedDatabaseManager::sFullSynchronousMode = false; mozilla::LazyLogModule IndexedDatabaseManager::sLoggingModule("IndexedDB"); Atomic IndexedDatabaseManager::sLoggingMode( IndexedDatabaseManager::Logging_Disabled); // static IndexedDatabaseManager* IndexedDatabaseManager::GetOrCreate() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); if (IsClosed()) { NS_ERROR("Calling GetOrCreate() after shutdown!"); return nullptr; } if (!gDBManager) { sIsMainProcess = XRE_IsParentProcess(); RefPtr 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; } nsresult IndexedDatabaseManager::Init() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); // During Init() we can't yet call IsMainProcess(), just check sIsMainProcess // directly. if (sIsMainProcess) { mDeleteTimer = do_CreateInstance(NS_TIMER_CONTRACTID); NS_ENSURE_STATE(mDeleteTimer); if (QuotaManager* quotaManager = QuotaManager::Get()) { NoteLiveQuotaManager(quotaManager); } } Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback, kTestingPref, &gTestingMode); Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback, kPrefExperimental, &gExperimentalFeaturesEnabled); Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback, kPrefFileHandle, &gFileHandleEnabled); // By default IndexedDB uses SQLite with PRAGMA synchronous = NORMAL. This // guarantees (unlike synchronous = OFF) atomicity and consistency, but not // necessarily durability in situations such as power loss. This preference // allows enabling PRAGMA synchronous = FULL on SQLite, which does guarantee // durability, but with an extra fsync() and the corresponding performance // hit. sFullSynchronousMode = Preferences::GetBool("dom.indexedDB.fullSynchronous"); Preferences::RegisterCallback(LoggingModePrefChangedCallback, kPrefLoggingDetails); Preferences::RegisterCallbackAndCall(LoggingModePrefChangedCallback, kPrefLoggingEnabled); Preferences::RegisterCallbackAndCall(DataThresholdPrefChangedCallback, kDataThresholdPref); Preferences::RegisterCallbackAndCall(MaxSerializedMsgSizePrefChangeCallback, kPrefMaxSerilizedMsgSize); const nsAdoptingCString& acceptLang = Preferences::GetLocalizedCString("intl.accept_languages"); // Split values on commas. nsCCharSeparatedTokenizer langTokenizer(acceptLang, ','); while (langTokenizer.hasMoreTokens()) { nsAutoCString lang(langTokenizer.nextToken()); icu::Locale locale = icu::Locale::createCanonical(lang.get()); if (!locale.isBogus()) { // icu::Locale::getBaseName is always ASCII as per BCP 47 mLocale.AssignASCII(locale.getBaseName()); break; } } if (mLocale.IsEmpty()) { mLocale.AssignLiteral("en_US"); } 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?!"); } if (sIsMainProcess && mDeleteTimer) { if (NS_FAILED(mDeleteTimer->Cancel())) { NS_WARNING("Failed to cancel timer!"); } mDeleteTimer = nullptr; } Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback, kTestingPref, &gTestingMode); Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback, kPrefExperimental, &gExperimentalFeaturesEnabled); Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback, kPrefFileHandle, &gFileHandleEnabled); Preferences::UnregisterCallback(LoggingModePrefChangedCallback, kPrefLoggingDetails); Preferences::UnregisterCallback(LoggingModePrefChangedCallback, kPrefLoggingEnabled); Preferences::UnregisterCallback(DataThresholdPrefChangedCallback, kDataThresholdPref); Preferences::UnregisterCallback(MaxSerializedMsgSizePrefChangeCallback, kPrefMaxSerilizedMsgSize); delete this; } // static nsresult IndexedDatabaseManager::CommonPostHandleEvent(EventChainPostVisitor& aVisitor, IDBFactory* aFactory) { MOZ_ASSERT(aVisitor.mDOMEvent); MOZ_ASSERT(aFactory); if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) { return NS_OK; } Event* internalEvent = aVisitor.mDOMEvent->InternalDOMEvent(); MOZ_ASSERT(internalEvent); if (!internalEvent->IsTrusted()) { return NS_OK; } nsString type; MOZ_ALWAYS_SUCCEEDS(internalEvent->GetType(type)); MOZ_ASSERT(nsDependentString(kErrorEventType).EqualsLiteral("error")); if (!type.EqualsLiteral("error")) { return NS_OK; } nsCOMPtr eventTarget = internalEvent->GetTarget(); MOZ_ASSERT(eventTarget); // Only mess with events that were originally targeted to an IDBRequest. RefPtr request; if (NS_FAILED(eventTarget->QueryInterface(kIDBRequestIID, getter_AddRefs(request))) || !request) { return NS_OK; } RefPtr error = request->GetErrorAfterResult(); nsString errorName; if (error) { error->GetName(errorName); } RootedDictionary init(RootingCx()); request->GetCallerLocation(init.mFilename, &init.mLineno, &init.mColno); init.mMessage = errorName; init.mCancelable = true; init.mBubbles = true; nsEventStatus status = nsEventStatus_eIgnore; if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(eventTarget->GetOwnerGlobal()); if (window) { nsCOMPtr sgo = do_QueryInterface(window); MOZ_ASSERT(sgo); if (NS_WARN_IF(NS_FAILED(sgo->HandleScriptError(init, &status)))) { status = nsEventStatus_eIgnore; } } else { // We don't fire error events at any global for non-window JS on the main // thread. } } else { // Not on the main thread, must be in a worker. WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); RefPtr globalScope = workerPrivate->GlobalScope(); MOZ_ASSERT(globalScope); RefPtr errorEvent = ErrorEvent::Constructor(globalScope, nsDependentString(kErrorEventType), init); MOZ_ASSERT(errorEvent); errorEvent->SetTrusted(true); auto* target = static_cast(globalScope.get()); if (NS_WARN_IF(NS_FAILED( EventDispatcher::DispatchDOMEvent(target, /* aWidgetEvent */ nullptr, errorEvent, /* aPresContext */ nullptr, &status)))) { status = nsEventStatus_eIgnore; } } if (status == nsEventStatus_eConsumeNoDefault) { return NS_OK; } // Log the error to the error console. ScriptErrorHelper::Dump(errorName, init.mFilename, init.mLineno, init.mColno, nsIScriptError::errorFlag, aFactory->IsChrome(), aFactory->InnerWindowID()); return NS_OK; } // static bool IndexedDatabaseManager::ResolveSandboxBinding(JSContext* aCx) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(js::GetObjectClass(JS::CurrentGlobalOrNull(aCx))->flags & JSCLASS_DOM_GLOBAL, "Passed object is not a global object!"); // We need to ensure that the manager has been created already here so that we // load preferences that may control which properties are exposed. if (NS_WARN_IF(!GetOrCreate())) { return false; } if (!IDBCursorBinding::GetConstructorObject(aCx) || !IDBCursorWithValueBinding::GetConstructorObject(aCx) || !IDBDatabaseBinding::GetConstructorObject(aCx) || !IDBFactoryBinding::GetConstructorObject(aCx) || !IDBIndexBinding::GetConstructorObject(aCx) || !IDBKeyRangeBinding::GetConstructorObject(aCx) || !IDBLocaleAwareKeyRangeBinding::GetConstructorObject(aCx) || !IDBMutableFileBinding::GetConstructorObject(aCx) || !IDBObjectStoreBinding::GetConstructorObject(aCx) || !IDBOpenDBRequestBinding::GetConstructorObject(aCx) || !IDBRequestBinding::GetConstructorObject(aCx) || !IDBTransactionBinding::GetConstructorObject(aCx) || !IDBVersionChangeEventBinding::GetConstructorObject(aCx)) { return false; } return true; } // static bool IndexedDatabaseManager::DefineIndexedDB(JSContext* aCx, JS::Handle aGlobal) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL, "Passed object is not a global object!"); RefPtr factory; if (NS_FAILED(IDBFactory::CreateForMainThreadJS(aCx, aGlobal, getter_AddRefs(factory)))) { return false; } MOZ_ASSERT(factory, "This should never fail for chrome!"); JS::Rooted indexedDB(aCx); js::AssertSameCompartment(aCx, aGlobal); if (!GetOrCreateDOMReflector(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_IsParentProcess()) == sIsMainProcess, "XRE_GetProcessType changed its tune!"); return sIsMainProcess; } // static IndexedDatabaseManager::LoggingMode IndexedDatabaseManager::GetLoggingMode() { MOZ_ASSERT(gDBManager, "GetLoggingMode called before IndexedDatabaseManager has been " "initialized!"); return sLoggingMode; } // static mozilla::LogModule* IndexedDatabaseManager::GetLoggingModule() { MOZ_ASSERT(gDBManager, "GetLoggingModule called before IndexedDatabaseManager has been " "initialized!"); return sLoggingModule; } #endif // DEBUG // static bool IndexedDatabaseManager::InTestingMode() { MOZ_ASSERT(gDBManager, "InTestingMode() called before indexedDB has been initialized!"); return gTestingMode; } // static bool IndexedDatabaseManager::FullSynchronous() { MOZ_ASSERT(gDBManager, "FullSynchronous() called before indexedDB has been initialized!"); return sFullSynchronousMode; } // static bool IndexedDatabaseManager::ExperimentalFeaturesEnabled() { if (NS_IsMainThread()) { if (NS_WARN_IF(!GetOrCreate())) { return false; } } else { MOZ_ASSERT(Get(), "ExperimentalFeaturesEnabled() called off the main thread " "before indexedDB has been initialized!"); } return gExperimentalFeaturesEnabled; } // static bool IndexedDatabaseManager::ExperimentalFeaturesEnabled(JSContext* aCx, JSObject* aGlobal) { // If, in the child process, properties of the global object are enumerated // before the chrome registry (and thus the value of |intl.accept_languages|) // is ready, calling IndexedDatabaseManager::Init will permanently break // that preference. We can retrieve gExperimentalFeaturesEnabled without // actually going through IndexedDatabaseManager. // See Bug 1198093 comment 14 for detailed explanation. if (IsNonExposedGlobal(aCx, js::GetGlobalForObjectCrossCompartment(aGlobal), GlobalNames::BackstagePass)) { MOZ_ASSERT(NS_IsMainThread()); static bool featureRetrieved = false; if (!featureRetrieved) { gExperimentalFeaturesEnabled = Preferences::GetBool(kPrefExperimental); featureRetrieved = true; } return gExperimentalFeaturesEnabled; } return ExperimentalFeaturesEnabled(); } // static bool IndexedDatabaseManager::IsFileHandleEnabled() { MOZ_ASSERT(gDBManager, "IsFileHandleEnabled() called before indexedDB has been " "initialized!"); return gFileHandleEnabled; } // static uint32_t IndexedDatabaseManager::DataThreshold() { MOZ_ASSERT(gDBManager, "DataThreshold() called before indexedDB has been initialized!"); return gDataThresholdBytes; } // static uint32_t IndexedDatabaseManager::MaxSerializedMsgSize() { MOZ_ASSERT(gDBManager, "MaxSerializedMsgSize() called before indexedDB has been initialized!"); MOZ_ASSERT(gMaxSerializedMsgSize > 0); return gMaxSerializedMsgSize; } void IndexedDatabaseManager::ClearBackgroundActor() { MOZ_ASSERT(NS_IsMainThread()); mBackgroundActor = nullptr; } void IndexedDatabaseManager::NoteLiveQuotaManager(QuotaManager* aQuotaManager) { MOZ_ASSERT(IsMainProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aQuotaManager); mBackgroundThread = aQuotaManager->OwningThread(); } void IndexedDatabaseManager::NoteShuttingDownQuotaManager() { MOZ_ASSERT(IsMainProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel()); mBackgroundThread = nullptr; } already_AddRefed IndexedDatabaseManager::GetFileManager(PersistenceType aPersistenceType, const nsACString& aOrigin, const nsAString& aDatabaseName) { AssertIsOnIOThread(); FileManagerInfo* info; if (!mFileManagerInfos.Get(aOrigin, &info)) { return nullptr; } RefPtr 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); } void IndexedDatabaseManager::InvalidateAllFileManagers() { AssertIsOnIOThread(); for (auto iter = mFileManagerInfos.ConstIter(); !iter.Done(); iter.Next()) { auto value = iter.Data(); MOZ_ASSERT(value); value->InvalidateAllFileManagers(); } mFileManagerInfos.Clear(); } void IndexedDatabaseManager::InvalidateFileManagers(PersistenceType aPersistenceType, const nsACString& aOrigin) { AssertIsOnIOThread(); MOZ_ASSERT(!aOrigin.IsEmpty()); FileManagerInfo* info; if (!mFileManagerInfos.Get(aOrigin, &info)) { return; } info->InvalidateAndRemoveFileManagers(aPersistenceType); if (!info->HasFileManagers()) { mFileManagerInfos.Remove(aOrigin); } } 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) { MOZ_ASSERT(IsMainProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aFileManager); MOZ_ASSERT(aFileId > 0); MOZ_ASSERT(mDeleteTimer); if (!mBackgroundThread) { return NS_OK; } nsresult rv = mDeleteTimer->Cancel(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = mDeleteTimer->InitWithCallback(this, kDeleteTimeoutMs, nsITimer::TYPE_ONE_SHOT); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsTArray* array; if (!mPendingDeleteInfos.Get(aFileManager, &array)) { array = new nsTArray(); mPendingDeleteInfos.Put(aFileManager, array); } array->AppendElement(aFileId); 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) { MOZ_ASSERT(NS_IsMainThread()); if (NS_WARN_IF(!InTestingMode())) { return NS_ERROR_UNEXPECTED; } if (!mBackgroundActor) { PBackgroundChild* bgActor = BackgroundChild::GetForCurrentThread(); if (NS_WARN_IF(!bgActor)) { return NS_ERROR_FAILURE; } BackgroundUtilsChild* actor = new BackgroundUtilsChild(this); mBackgroundActor = static_cast( bgActor->SendPBackgroundIndexedDBUtilsConstructor(actor)); } if (NS_WARN_IF(!mBackgroundActor)) { return NS_ERROR_FAILURE; } if (!mBackgroundActor->SendGetFileReferences(aPersistenceType, nsCString(aOrigin), nsString(aDatabaseName), aFileId, aRefCnt, aDBRefCnt, aSliceRefCnt, aResult)) { return NS_ERROR_FAILURE; } return NS_OK; } nsresult IndexedDatabaseManager::FlushPendingFileDeletions() { MOZ_ASSERT(NS_IsMainThread()); if (NS_WARN_IF(!InTestingMode())) { return NS_ERROR_UNEXPECTED; } if (IsMainProcess()) { nsresult rv = mDeleteTimer->Cancel(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = Notify(mDeleteTimer); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { PBackgroundChild* bgActor = BackgroundChild::GetForCurrentThread(); if (NS_WARN_IF(!bgActor)) { return NS_ERROR_FAILURE; } if (!bgActor->SendFlushPendingFileDeletions()) { return NS_ERROR_FAILURE; } } return NS_OK; } // static void IndexedDatabaseManager::LoggingModePrefChangedCallback( const char* /* aPrefName */, void* /* aClosure */) { MOZ_ASSERT(NS_IsMainThread()); if (!Preferences::GetBool(kPrefLoggingEnabled)) { sLoggingMode = Logging_Disabled; return; } bool useProfiler = #if defined(DEBUG) Preferences::GetBool(kPrefLoggingProfiler); if (useProfiler) { NS_WARNING("IndexedDB cannot create profiler marks because this build does " "not have profiler extensions enabled!"); useProfiler = false; } #else false; #endif const bool logDetails = Preferences::GetBool(kPrefLoggingDetails); if (useProfiler) { sLoggingMode = logDetails ? Logging_DetailedProfilerMarks : Logging_ConciseProfilerMarks; } else { sLoggingMode = logDetails ? Logging_Detailed : Logging_Concise; } } // static const nsCString& IndexedDatabaseManager::GetLocale() { IndexedDatabaseManager* idbManager = Get(); MOZ_ASSERT(idbManager, "IDBManager is not ready!"); return idbManager->mLocale; } NS_IMPL_ADDREF(IndexedDatabaseManager) NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager, Destroy()) NS_IMPL_QUERY_INTERFACE(IndexedDatabaseManager, nsITimerCallback) NS_IMETHODIMP IndexedDatabaseManager::Notify(nsITimer* aTimer) { MOZ_ASSERT(IsMainProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mBackgroundThread); for (auto iter = mPendingDeleteInfos.ConstIter(); !iter.Done(); iter.Next()) { auto key = iter.Key(); auto value = iter.Data(); MOZ_ASSERT(!value->IsEmpty()); RefPtr runnable = new DeleteFilesRunnable(mBackgroundThread, key, *value); MOZ_ASSERT(value->IsEmpty()); runnable->Dispatch(); } mPendingDeleteInfos.Clear(); return NS_OK; } 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 RefPtr& fileManager = managers[i]; if (fileManager->DatabaseName() == aName) { RefPtr 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(); } for (i = 0; i < mDefaultStorageFileManagers.Length(); i++) { mDefaultStorageFileManagers[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++) { RefPtr& 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_DEFAULT: return mDefaultStorageFileManagers; case PERSISTENCE_TYPE_INVALID: default: MOZ_CRASH("Bad storage type value!"); } } DeleteFilesRunnable::DeleteFilesRunnable(nsIEventTarget* aBackgroundThread, FileManager* aFileManager, nsTArray& aFileIds) : mBackgroundThread(aBackgroundThread) , mFileManager(aFileManager) , mState(State_Initial) { mFileIds.SwapElements(aFileIds); } void DeleteFilesRunnable::Dispatch() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mState == State_Initial); MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL)); } NS_IMPL_ISUPPORTS(DeleteFilesRunnable, nsIRunnable) NS_IMETHODIMP DeleteFilesRunnable::Run() { nsresult rv; switch (mState) { case State_Initial: rv = Open(); break; case State_DatabaseWorkOpen: rv = DoDatabaseWork(); break; case State_UnblockingOpen: UnblockOpen(); return NS_OK; case State_DirectoryOpenPending: default: MOZ_CRASH("Should never get here!"); } if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) { Finish(); } return NS_OK; } void DeleteFilesRunnable::DirectoryLockAcquired(DirectoryLock* aLock) { AssertIsOnBackgroundThread(); MOZ_ASSERT(mState == State_DirectoryOpenPending); MOZ_ASSERT(!mDirectoryLock); mDirectoryLock = aLock; QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); // Must set this before dispatching otherwise we will race with the IO thread mState = State_DatabaseWorkOpen; nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { Finish(); return; } } void DeleteFilesRunnable::DirectoryLockFailed() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mState == State_DirectoryOpenPending); MOZ_ASSERT(!mDirectoryLock); Finish(); } nsresult DeleteFilesRunnable::Open() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mState == State_Initial); QuotaManager* quotaManager = QuotaManager::Get(); if (NS_WARN_IF(!quotaManager)) { return NS_ERROR_FAILURE; } mState = State_DirectoryOpenPending; quotaManager->OpenDirectory(mFileManager->Type(), mFileManager->Group(), mFileManager->Origin(), mFileManager->IsApp(), Client::IDB, /* aExclusive */ false, this); return NS_OK; } nsresult DeleteFilesRunnable::DeleteFile(int64_t aFileId) { MOZ_ASSERT(mDirectory); MOZ_ASSERT(mJournalDirectory); nsCOMPtr file = mFileManager->GetFileForId(mDirectory, aFileId); NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); nsresult rv; int64_t fileSize; if (mFileManager->EnforcingQuota()) { rv = file->GetFileSize(&fileSize); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); } rv = file->Remove(false); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); if (mFileManager->EnforcingQuota()) { QuotaManager* quotaManager = QuotaManager::Get(); NS_ASSERTION(quotaManager, "Shouldn't be null!"); quotaManager->DecreaseUsageForOrigin(mFileManager->Type(), mFileManager->Group(), mFileManager->Origin(), fileSize); } file = mFileManager->GetFileForId(mJournalDirectory, aFileId); NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); rv = file->Remove(false); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult DeleteFilesRunnable::DoDatabaseWork() { AssertIsOnIOThread(); MOZ_ASSERT(mState == State_DatabaseWorkOpen); if (!mFileManager->Invalidated()) { mDirectory = mFileManager->GetDirectory(); if (NS_WARN_IF(!mDirectory)) { return NS_ERROR_FAILURE; } mJournalDirectory = mFileManager->GetJournalDirectory(); if (NS_WARN_IF(!mJournalDirectory)) { return NS_ERROR_FAILURE; } for (int64_t fileId : mFileIds) { if (NS_FAILED(DeleteFile(fileId))) { NS_WARNING("Failed to delete file!"); } } } Finish(); return NS_OK; } void DeleteFilesRunnable::Finish() { // Must set mState before dispatching otherwise we will race with the main // thread. mState = State_UnblockingOpen; MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL)); } void DeleteFilesRunnable::UnblockOpen() { AssertIsOnBackgroundThread(); MOZ_ASSERT(mState == State_UnblockingOpen); mDirectoryLock = nullptr; mState = State_Completed; } } // namespace dom } // namespace mozilla