summaryrefslogtreecommitdiff
path: root/dom/indexedDB/IndexedDatabaseManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/indexedDB/IndexedDatabaseManager.cpp')
-rw-r--r--dom/indexedDB/IndexedDatabaseManager.cpp1461
1 files changed, 1461 insertions, 0 deletions
diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp
new file mode 100644
index 0000000000..2590b01277
--- /dev/null
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -0,0 +1,1461 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize
+#include "nsIConsoleService.h"
+#include "nsIDiskSpaceWatcher.h"
+#include "nsIDOMWindow.h"
+#include "nsIEventTarget.h"
+#include "nsIFile.h"
+#include "nsIObserverService.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"
+
+#ifdef ENABLE_INTL_API
+#include "nsCharSeparatedTokenizer.h"
+#include "unicode/locid.h"
+#endif
+
+#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"
+
+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<FileManager>
+ 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<RefPtr<FileManager> >&
+ GetArray(PersistenceType aPersistenceType);
+
+ const nsTArray<RefPtr<FileManager> >&
+ GetImmutableArray(PersistenceType aPersistenceType) const
+ {
+ return const_cast<FileManagerInfo*>(this)->GetArray(aPersistenceType);
+ }
+
+ nsTArray<RefPtr<FileManager> > mPersistentStorageFileManagers;
+ nsTArray<RefPtr<FileManager> > mTemporaryStorageFileManagers;
+ nsTArray<RefPtr<FileManager> > 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) || defined(MOZ_ENABLE_PROFILER_SPS)
+const char kPrefLoggingProfiler[] =
+ IDB_PREF_LOGGING_BRANCH_ROOT "profiler-marks";
+#endif
+
+#undef IDB_PREF_LOGGING_BRANCH_ROOT
+#undef IDB_PREF_BRANCH_ROOT
+
+StaticRefPtr<IndexedDatabaseManager> gDBManager;
+
+Atomic<bool> gInitialized(false);
+Atomic<bool> gClosed(false);
+Atomic<bool> gTestingMode(false);
+Atomic<bool> gExperimentalFeaturesEnabled(false);
+Atomic<bool> gFileHandleEnabled(false);
+Atomic<int32_t> gDataThresholdBytes(0);
+Atomic<int32_t> 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<nsIEventTarget> mBackgroundThread;
+
+ RefPtr<FileManager> mFileManager;
+ nsTArray<int64_t> mFileIds;
+
+ RefPtr<DirectoryLock> mDirectoryLock;
+
+ nsCOMPtr<nsIFile> mDirectory;
+ nsCOMPtr<nsIFile> mJournalDirectory;
+
+ State mState;
+
+public:
+ DeleteFilesRunnable(nsIEventTarget* aBackgroundThread,
+ FileManager* aFileManager,
+ nsTArray<int64_t>& 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<Atomic<bool>*>(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::LoggingMode>
+ IndexedDatabaseManager::sLoggingMode(
+ IndexedDatabaseManager::Logging_Disabled);
+
+mozilla::Atomic<bool> 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_IsParentProcess();
+
+ if (sIsMainProcess && Preferences::GetBool("disk_space_watcher.enabled", false)) {
+ // See if we're starting up in low disk space conditions.
+ nsCOMPtr<nsIDiskSpaceWatcher> 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!");
+ }
+ }
+
+ RefPtr<IndexedDatabaseManager> 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) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(obs);
+
+ nsresult rv =
+ obs->AddObserver(this, DISKSPACEWATCHER_OBSERVER_TOPIC, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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);
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ Preferences::RegisterCallback(LoggingModePrefChangedCallback,
+ kPrefLoggingProfiler);
+#endif
+ Preferences::RegisterCallbackAndCall(LoggingModePrefChangedCallback,
+ kPrefLoggingEnabled);
+
+ Preferences::RegisterCallbackAndCall(DataThresholdPrefChangedCallback,
+ kDataThresholdPref);
+
+ Preferences::RegisterCallbackAndCall(MaxSerializedMsgSizePrefChangeCallback,
+ kPrefMaxSerilizedMsgSize);
+
+#ifdef ENABLE_INTL_API
+ 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");
+ }
+#endif
+
+ 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);
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
+ kPrefLoggingProfiler);
+#endif
+ 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> eventTarget = internalEvent->GetTarget();
+ MOZ_ASSERT(eventTarget);
+
+ // Only mess with events that were originally targeted to an IDBRequest.
+ RefPtr<IDBRequest> request;
+ if (NS_FAILED(eventTarget->QueryInterface(kIDBRequestIID,
+ getter_AddRefs(request))) ||
+ !request) {
+ return NS_OK;
+ }
+
+ RefPtr<DOMError> error = request->GetErrorAfterResult();
+
+ nsString errorName;
+ if (error) {
+ error->GetName(errorName);
+ }
+
+ RootedDictionary<ErrorEventInit> 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<nsIDOMWindow> window = do_QueryInterface(eventTarget->GetOwnerGlobal());
+ if (window) {
+ nsCOMPtr<nsIScriptGlobalObject> 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<WorkerGlobalScope> globalScope = workerPrivate->GlobalScope();
+ MOZ_ASSERT(globalScope);
+
+ RefPtr<ErrorEvent> errorEvent =
+ ErrorEvent::Constructor(globalScope,
+ nsDependentString(kErrorEventType),
+ init);
+ MOZ_ASSERT(errorEvent);
+
+ errorEvent->SetTrusted(true);
+
+ auto* target = static_cast<EventTarget*>(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<JSObject*> aGlobal)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
+ "Passed object is not a global object!");
+
+ RefPtr<IDBFactory> factory;
+ if (NS_FAILED(IDBFactory::CreateForMainThreadJS(aCx,
+ aGlobal,
+ getter_AddRefs(factory)))) {
+ return false;
+ }
+
+ MOZ_ASSERT(factory, "This should never fail for chrome!");
+
+ JS::Rooted<JS::Value> 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
+bool
+IndexedDatabaseManager::InLowDiskSpaceMode()
+{
+ NS_ASSERTION(gDBManager,
+ "InLowDiskSpaceMode() called before indexedDB has been "
+ "initialized!");
+ return sLowDiskSpaceMode;
+}
+
+// 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<FileManager>
+IndexedDatabaseManager::GetFileManager(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ const nsAString& aDatabaseName)
+{
+ AssertIsOnIOThread();
+
+ FileManagerInfo* info;
+ if (!mFileManagerInfos.Get(aOrigin, &info)) {
+ return nullptr;
+ }
+
+ RefPtr<FileManager> 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<int64_t>* array;
+ if (!mPendingDeleteInfos.Get(aFileManager, &array)) {
+ array = new nsTArray<int64_t>();
+ 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<BackgroundUtilsChild*>(
+ 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) || defined(MOZ_ENABLE_PROFILER_SPS)
+ Preferences::GetBool(kPrefLoggingProfiler);
+#if !defined(MOZ_ENABLE_PROFILER_SPS)
+ if (useProfiler) {
+ NS_WARNING("IndexedDB cannot create profiler marks because this build does "
+ "not have profiler extensions enabled!");
+ useProfiler = false;
+ }
+#endif
+#else
+ false;
+#endif
+
+ const bool logDetails = Preferences::GetBool(kPrefLoggingDetails);
+
+ if (useProfiler) {
+ sLoggingMode = logDetails ?
+ Logging_DetailedProfilerMarks :
+ Logging_ConciseProfilerMarks;
+ } else {
+ sLoggingMode = logDetails ? Logging_Detailed : Logging_Concise;
+ }
+}
+
+#ifdef ENABLE_INTL_API
+// static
+const nsCString&
+IndexedDatabaseManager::GetLocale()
+{
+ IndexedDatabaseManager* idbManager = Get();
+ MOZ_ASSERT(idbManager, "IDBManager is not ready!");
+
+ return idbManager->mLocale;
+}
+#endif
+
+NS_IMPL_ADDREF(IndexedDatabaseManager)
+NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager, Destroy())
+NS_IMPL_QUERY_INTERFACE(IndexedDatabaseManager, nsIObserver, nsITimerCallback)
+
+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;
+}
+
+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<DeleteFilesRunnable> runnable =
+ new DeleteFilesRunnable(mBackgroundThread, key, *value);
+
+ MOZ_ASSERT(value->IsEmpty());
+
+ runnable->Dispatch();
+ }
+
+ mPendingDeleteInfos.Clear();
+
+ return NS_OK;
+}
+
+already_AddRefed<FileManager>
+FileManagerInfo::GetFileManager(PersistenceType aPersistenceType,
+ const nsAString& aName) const
+{
+ AssertIsOnIOThread();
+
+ const nsTArray<RefPtr<FileManager> >& managers =
+ GetImmutableArray(aPersistenceType);
+
+ for (uint32_t i = 0; i < managers.Length(); i++) {
+ const RefPtr<FileManager>& fileManager = managers[i];
+
+ if (fileManager->DatabaseName() == aName) {
+ RefPtr<FileManager> result = fileManager;
+ return result.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+void
+FileManagerInfo::AddFileManager(FileManager* aFileManager)
+{
+ AssertIsOnIOThread();
+
+ nsTArray<RefPtr<FileManager> >& 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<RefPtr<FileManager > >& 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<RefPtr<FileManager > >& managers = GetArray(aPersistenceType);
+
+ for (uint32_t i = 0; i < managers.Length(); i++) {
+ RefPtr<FileManager>& fileManager = managers[i];
+ if (fileManager->DatabaseName() == aName) {
+ fileManager->Invalidate();
+ managers.RemoveElementAt(i);
+ return;
+ }
+ }
+}
+
+nsTArray<RefPtr<FileManager> >&
+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<int64_t>& 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<nsIFile> 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