summaryrefslogtreecommitdiffstats
path: root/dom/indexedDB/ActorsParent.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/indexedDB/ActorsParent.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/indexedDB/ActorsParent.cpp')
-rw-r--r--dom/indexedDB/ActorsParent.cpp29795
1 files changed, 29795 insertions, 0 deletions
diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp
new file mode 100644
index 000000000..702d5c985
--- /dev/null
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -0,0 +1,29795 @@
+/* -*- Mode: C++; tab-width: 2; 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 "ActorsParent.h"
+
+#include <algorithm>
+#include "FileInfo.h"
+#include "FileManager.h"
+#include "IDBObjectStore.h"
+#include "IDBTransaction.h"
+#include "IndexedDatabase.h"
+#include "IndexedDatabaseInlines.h"
+#include "IndexedDatabaseManager.h"
+#include "js/StructuredClone.h"
+#include "js/Value.h"
+#include "jsapi.h"
+#include "KeyPath.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AppProcessChecker.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Casting.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/SnappyCompressOutputStream.h"
+#include "mozilla/SnappyUncompressInputStream.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/storage.h"
+#include "mozilla/Unused.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/dom/filehandle/ActorsParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
+#include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestParent.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/dom/quota/Client.h"
+#include "mozilla/dom/quota/FileStreams.h"
+#include "mozilla/dom/quota/OriginScope.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/quota/UsageInfo.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/InputStreamParams.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/PBackground.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/storage/Variant.h"
+#include "nsAutoPtr.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsClassHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsDataHashtable.h"
+#include "nsEscape.h"
+#include "nsHashKeys.h"
+#include "nsNetUtil.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIAppsService.h"
+#include "nsIEventTarget.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIInputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIOutputStream.h"
+#include "nsIPipe.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISupports.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsPriority.h"
+#include "nsIThread.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsQueryObject.h"
+#include "nsRefPtrHashtable.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "nsThreadPool.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCID.h"
+#include "PermissionRequestBase.h"
+#include "ProfilerHelpers.h"
+#include "prsystem.h"
+#include "prtime.h"
+#include "ReportInternalError.h"
+#include "snappy/snappy.h"
+
+#define DISABLE_ASSERTS_FOR_FUZZING 0
+
+#if DISABLE_ASSERTS_FOR_FUZZING
+#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
+#else
+#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
+#endif
+
+#define IDB_DEBUG_LOG(_args) \
+ MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), \
+ LogLevel::Debug, \
+ _args )
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+#define IDB_MOBILE
+#endif
+
+#define BLOB_IMPL_STORED_FILE_IID \
+ {0x6b505c84, 0x2c60, 0x4ffb, {0x8b, 0x91, 0xfe, 0x22, 0xb1, 0xec, 0x75, 0xe2}}
+
+namespace mozilla {
+
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc,
+ PRFileDesc,
+ PR_Close);
+
+namespace dom {
+namespace indexedDB {
+
+using namespace mozilla::dom::quota;
+using namespace mozilla::ipc;
+
+namespace {
+
+class ConnectionPool;
+class Cursor;
+class Database;
+struct DatabaseActorInfo;
+class DatabaseFile;
+class DatabaseLoggingInfo;
+class DatabaseMaintenance;
+class Factory;
+class Maintenance;
+class MutableFile;
+class OpenDatabaseOp;
+class TransactionBase;
+class TransactionDatabaseOperationBase;
+class VersionChangeTransaction;
+
+/*******************************************************************************
+ * Constants
+ ******************************************************************************/
+
+// If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
+// schema version.
+static_assert(JS_STRUCTURED_CLONE_VERSION == 8,
+ "Need to update the major schema version.");
+
+// Major schema version. Bump for almost everything.
+const uint32_t kMajorSchemaVersion = 25;
+
+// Minor schema version. Should almost always be 0 (maybe bump on release
+// branches if we have to).
+const uint32_t kMinorSchemaVersion = 0;
+
+// The schema version we store in the SQLite database is a (signed) 32-bit
+// integer. The major version is left-shifted 4 bits so the max value is
+// 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
+static_assert(kMajorSchemaVersion <= 0xFFFFFFF,
+ "Major version needs to fit in 28 bits.");
+static_assert(kMinorSchemaVersion <= 0xF,
+ "Minor version needs to fit in 4 bits.");
+
+const int32_t kSQLiteSchemaVersion =
+ int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);
+
+const int32_t kStorageProgressGranularity = 1000;
+
+// Changing the value here will override the page size of new databases only.
+// A journal mode change and VACUUM are needed to change existing databases, so
+// the best way to do that is to use the schema version upgrade mechanism.
+const uint32_t kSQLitePageSizeOverride =
+#ifdef IDB_MOBILE
+ 2048;
+#else
+ 4096;
+#endif
+
+static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
+ (kSQLitePageSizeOverride % 2 == 0 &&
+ kSQLitePageSizeOverride >= 512 &&
+ kSQLitePageSizeOverride <= 65536),
+ "Must be 0 (disabled) or a power of 2 between 512 and 65536!");
+
+// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
+// enforce a custom limit.
+const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
+
+// Set to some multiple of the page size to grow the database in larger chunks.
+const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
+
+static_assert(kSQLiteGrowthIncrement >= 0 &&
+ kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
+ kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
+ "Must be 0 (disabled) or a positive multiple of the page size!");
+
+// The maximum number of threads that can be used for database activity at a
+// single time.
+const uint32_t kMaxConnectionThreadCount = 20;
+
+static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");
+
+// The maximum number of threads to keep when idle. Threads that become idle in
+// excess of this number will be shut down immediately.
+const uint32_t kMaxIdleConnectionThreadCount = 2;
+
+static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
+ "Idle thread limit must be less than total thread limit!");
+
+// The length of time that database connections will be held open after all
+// transactions have completed before doing idle maintenance.
+const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds
+
+// The length of time that database connections will be held open after all
+// transactions and maintenance have completed.
+const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds
+
+// The length of time that idle threads will stay alive before being shut down.
+const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds
+
+#define SAVEPOINT_CLAUSE "SAVEPOINT sp;"
+
+const uint32_t kFileCopyBufferSize = 32768;
+
+#define JOURNAL_DIRECTORY_NAME "journals"
+
+const char kFileManagerDirectoryNameSuffix[] = ".files";
+const char kSQLiteJournalSuffix[] = ".sqlite-journal";
+const char kSQLiteSHMSuffix[] = ".sqlite-shm";
+const char kSQLiteWALSuffix[] = ".sqlite-wal";
+
+const char kPrefIndexedDBEnabled[] = "dom.indexedDB.enabled";
+
+const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
+
+#define IDB_PREFIX "indexedDB"
+
+#define PERMISSION_STRING_CHROME_BASE IDB_PREFIX "-chrome-"
+#define PERMISSION_STRING_CHROME_READ_SUFFIX "-read"
+#define PERMISSION_STRING_CHROME_WRITE_SUFFIX "-write"
+
+#ifdef DEBUG
+
+const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
+const uint32_t kDEBUGThreadSleepMS = 0;
+
+const int32_t kDEBUGTransactionThreadPriority =
+ nsISupportsPriority::PRIORITY_NORMAL;
+const uint32_t kDEBUGTransactionThreadSleepMS = 0;
+
+#endif
+
+template <size_t N>
+constexpr size_t
+LiteralStringLength(const char (&aArr)[N])
+{
+ static_assert(N, "Zero-length string literal?!");
+
+ // Don't include the null terminator.
+ return N - 1;
+}
+
+/*******************************************************************************
+ * Metadata classes
+ ******************************************************************************/
+
+struct FullIndexMetadata
+{
+ IndexMetadata mCommonMetadata;
+
+ bool mDeleted;
+
+public:
+ FullIndexMetadata()
+ : mCommonMetadata(0, nsString(), KeyPath(0), nsCString(), false, false, false)
+ , mDeleted(false)
+ {
+ // This can happen either on the QuotaManager IO thread or on a
+ // versionchange transaction thread. These threads can never race so this is
+ // totally safe.
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
+
+private:
+ ~FullIndexMetadata()
+ { }
+};
+
+typedef nsRefPtrHashtable<nsUint64HashKey, FullIndexMetadata> IndexTable;
+
+struct FullObjectStoreMetadata
+{
+ ObjectStoreMetadata mCommonMetadata;
+ IndexTable mIndexes;
+
+ // These two members are only ever touched on a transaction thread!
+ int64_t mNextAutoIncrementId;
+ int64_t mCommittedAutoIncrementId;
+
+ bool mDeleted;
+
+public:
+ FullObjectStoreMetadata()
+ : mCommonMetadata(0, nsString(), KeyPath(0), false)
+ , mNextAutoIncrementId(0)
+ , mCommittedAutoIncrementId(0)
+ , mDeleted(false)
+ {
+ // This can happen either on the QuotaManager IO thread or on a
+ // versionchange transaction thread. These threads can never race so this is
+ // totally safe.
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
+
+ bool
+ HasLiveIndexes() const;
+
+private:
+ ~FullObjectStoreMetadata()
+ { }
+};
+
+typedef nsRefPtrHashtable<nsUint64HashKey, FullObjectStoreMetadata>
+ ObjectStoreTable;
+
+struct FullDatabaseMetadata
+{
+ DatabaseMetadata mCommonMetadata;
+ nsCString mDatabaseId;
+ nsString mFilePath;
+ ObjectStoreTable mObjectStores;
+
+ int64_t mNextObjectStoreId;
+ int64_t mNextIndexId;
+
+public:
+ explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
+ : mCommonMetadata(aCommonMetadata)
+ , mNextObjectStoreId(0)
+ , mNextIndexId(0)
+ {
+ AssertIsOnBackgroundThread();
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullDatabaseMetadata)
+
+ already_AddRefed<FullDatabaseMetadata>
+ Duplicate() const;
+
+private:
+ ~FullDatabaseMetadata()
+ { }
+};
+
+template <class MetadataType>
+class MOZ_STACK_CLASS MetadataNameOrIdMatcher final
+{
+ typedef MetadataNameOrIdMatcher<MetadataType> SelfType;
+
+ const int64_t mId;
+ const nsString mName;
+ RefPtr<MetadataType> mMetadata;
+ bool mCheckName;
+
+public:
+ template <class Enumerable>
+ static MetadataType*
+ Match(const Enumerable& aEnumerable, uint64_t aId, const nsAString& aName)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aId);
+
+ SelfType closure(aId, aName);
+ MatchHelper(aEnumerable, &closure);
+
+ return closure.mMetadata;
+ }
+
+ template <class Enumerable>
+ static MetadataType*
+ Match(const Enumerable& aEnumerable, uint64_t aId)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aId);
+
+ SelfType closure(aId);
+ MatchHelper(aEnumerable, &closure);
+
+ return closure.mMetadata;
+ }
+
+private:
+ MetadataNameOrIdMatcher(const int64_t& aId, const nsAString& aName)
+ : mId(aId)
+ , mName(PromiseFlatString(aName))
+ , mMetadata(nullptr)
+ , mCheckName(true)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aId);
+ }
+
+ explicit MetadataNameOrIdMatcher(const int64_t& aId)
+ : mId(aId)
+ , mMetadata(nullptr)
+ , mCheckName(false)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aId);
+ }
+
+ template <class Enumerable>
+ static void
+ MatchHelper(const Enumerable& aEnumerable, SelfType* aClosure)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aClosure);
+
+ for (auto iter = aEnumerable.ConstIter(); !iter.Done(); iter.Next()) {
+#ifdef DEBUG
+ const uint64_t key = iter.Key();
+#endif
+ MetadataType* value = iter.UserData();
+ MOZ_ASSERT(key != 0);
+ MOZ_ASSERT(value);
+
+ if (!value->mDeleted &&
+ (aClosure->mId == value->mCommonMetadata.id() ||
+ (aClosure->mCheckName &&
+ aClosure->mName == value->mCommonMetadata.name()))) {
+ aClosure->mMetadata = value;
+ break;
+ }
+ }
+ }
+};
+
+struct IndexDataValue final
+{
+ int64_t mIndexId;
+ Key mKey;
+ Key mSortKey;
+ bool mUnique;
+
+ IndexDataValue()
+ : mIndexId(0)
+ , mUnique(false)
+ {
+ MOZ_COUNT_CTOR(IndexDataValue);
+ }
+
+ explicit
+ IndexDataValue(const IndexDataValue& aOther)
+ : mIndexId(aOther.mIndexId)
+ , mKey(aOther.mKey)
+ , mSortKey(aOther.mSortKey)
+ , mUnique(aOther.mUnique)
+ {
+ MOZ_ASSERT(!aOther.mKey.IsUnset());
+
+ MOZ_COUNT_CTOR(IndexDataValue);
+ }
+
+ IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey)
+ : mIndexId(aIndexId)
+ , mKey(aKey)
+ , mUnique(aUnique)
+ {
+ MOZ_ASSERT(!aKey.IsUnset());
+
+ MOZ_COUNT_CTOR(IndexDataValue);
+ }
+
+ IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey,
+ const Key& aSortKey)
+ : mIndexId(aIndexId)
+ , mKey(aKey)
+ , mSortKey(aSortKey)
+ , mUnique(aUnique)
+ {
+ MOZ_ASSERT(!aKey.IsUnset());
+
+ MOZ_COUNT_CTOR(IndexDataValue);
+ }
+
+ ~IndexDataValue()
+ {
+ MOZ_COUNT_DTOR(IndexDataValue);
+ }
+
+ bool
+ operator==(const IndexDataValue& aOther) const
+ {
+ if (mIndexId != aOther.mIndexId) {
+ return false;
+ }
+ if (mSortKey.IsUnset()) {
+ return mKey == aOther.mKey;
+ }
+ return mSortKey == aOther.mSortKey;
+ }
+
+ bool
+ operator<(const IndexDataValue& aOther) const
+ {
+ if (mIndexId == aOther.mIndexId) {
+ if (mSortKey.IsUnset()) {
+ return mKey < aOther.mKey;
+ }
+ return mSortKey < aOther.mSortKey;
+ }
+
+ return mIndexId < aOther.mIndexId;
+ }
+};
+
+/*******************************************************************************
+ * SQLite functions
+ ******************************************************************************/
+
+int32_t
+MakeSchemaVersion(uint32_t aMajorSchemaVersion,
+ uint32_t aMinorSchemaVersion)
+{
+ return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
+}
+
+uint32_t
+HashName(const nsAString& aName)
+{
+ struct Helper
+ {
+ static uint32_t
+ RotateBitsLeft32(uint32_t aValue, uint8_t aBits)
+ {
+ MOZ_ASSERT(aBits < 32);
+ return (aValue << aBits) | (aValue >> (32 - aBits));
+ }
+ };
+
+ static const uint32_t kGoldenRatioU32 = 0x9e3779b9u;
+
+ const char16_t* str = aName.BeginReading();
+ size_t length = aName.Length();
+
+ uint32_t hash = 0;
+ for (size_t i = 0; i < length; i++) {
+ hash = kGoldenRatioU32 * (Helper::RotateBitsLeft32(hash, 5) ^ str[i]);
+ }
+
+ return hash;
+}
+
+nsresult
+ClampResultCode(nsresult aResultCode)
+{
+ if (NS_SUCCEEDED(aResultCode) ||
+ NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) {
+ return aResultCode;
+ }
+
+ switch (aResultCode) {
+ case NS_ERROR_FILE_NO_DEVICE_SPACE:
+ return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ case NS_ERROR_STORAGE_CONSTRAINT:
+ return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
+ default:
+#ifdef DEBUG
+ nsPrintfCString message("Converting non-IndexedDB error code (0x%X) to "
+ "NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
+ aResultCode);
+ NS_WARNING(message.get());
+#else
+ ;
+#endif
+ }
+
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+}
+
+void
+GetDatabaseFilename(const nsAString& aName,
+ nsAutoString& aDatabaseFilename)
+{
+ MOZ_ASSERT(aDatabaseFilename.IsEmpty());
+
+ aDatabaseFilename.AppendInt(HashName(aName));
+
+ nsCString escapedName;
+ if (!NS_Escape(NS_ConvertUTF16toUTF8(aName), escapedName, url_XPAlphas)) {
+ MOZ_CRASH("Can't escape database name!");
+ }
+
+ const char* forwardIter = escapedName.BeginReading();
+ const char* backwardIter = escapedName.EndReading() - 1;
+
+ nsAutoCString substring;
+ while (forwardIter <= backwardIter && substring.Length() < 21) {
+ if (substring.Length() % 2) {
+ substring.Append(*backwardIter--);
+ } else {
+ substring.Append(*forwardIter++);
+ }
+ }
+
+ aDatabaseFilename.AppendASCII(substring.get(), substring.Length());
+}
+
+uint32_t
+CompressedByteCountForNumber(uint64_t aNumber)
+{
+ // All bytes have 7 bits available.
+ uint32_t count = 1;
+ while ((aNumber >>= 7)) {
+ count++;
+ }
+
+ return count;
+}
+
+uint32_t
+CompressedByteCountForIndexId(int64_t aIndexId)
+{
+ MOZ_ASSERT(aIndexId);
+ MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
+ "Overflow!");
+
+ return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
+}
+
+void
+WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator)
+{
+ MOZ_ASSERT(aIterator);
+ MOZ_ASSERT(*aIterator);
+
+ uint8_t*& buffer = *aIterator;
+
+#ifdef DEBUG
+ const uint8_t* bufferStart = buffer;
+ const uint64_t originalNumber = aNumber;
+#endif
+
+ while (true) {
+ uint64_t shiftedNumber = aNumber >> 7;
+ if (shiftedNumber) {
+ *buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
+ aNumber = shiftedNumber;
+ } else {
+ *buffer++ = uint8_t(aNumber);
+ break;
+ }
+ }
+
+ MOZ_ASSERT(buffer > bufferStart);
+ MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
+ CompressedByteCountForNumber(originalNumber));
+}
+
+uint64_t
+ReadCompressedNumber(const uint8_t** aIterator, const uint8_t* aEnd)
+{
+ MOZ_ASSERT(aIterator);
+ MOZ_ASSERT(*aIterator);
+ MOZ_ASSERT(aEnd);
+ MOZ_ASSERT(*aIterator < aEnd);
+
+ const uint8_t*& buffer = *aIterator;
+
+ uint8_t shiftCounter = 0;
+ uint64_t result = 0;
+
+ while (true) {
+ MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
+
+ result += (uint64_t(*buffer & 0x7f) << shiftCounter);
+ shiftCounter += 7;
+
+ if (!(*buffer++ & 0x80)) {
+ break;
+ }
+
+ if (NS_WARN_IF(buffer == aEnd)) {
+ MOZ_ASSERT(false);
+ break;
+ }
+ }
+
+ return result;
+}
+
+void
+WriteCompressedIndexId(int64_t aIndexId, bool aUnique, uint8_t** aIterator)
+{
+ MOZ_ASSERT(aIndexId);
+ MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
+ "Overflow!");
+ MOZ_ASSERT(aIterator);
+ MOZ_ASSERT(*aIterator);
+
+ const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
+ WriteCompressedNumber(indexId, aIterator);
+}
+
+void
+ReadCompressedIndexId(const uint8_t** aIterator,
+ const uint8_t* aEnd,
+ int64_t* aIndexId,
+ bool* aUnique)
+{
+ MOZ_ASSERT(aIterator);
+ MOZ_ASSERT(*aIterator);
+ MOZ_ASSERT(aIndexId);
+ MOZ_ASSERT(aUnique);
+
+ uint64_t indexId = ReadCompressedNumber(aIterator, aEnd);
+
+ if (indexId % 2) {
+ *aUnique = true;
+ indexId--;
+ } else {
+ *aUnique = false;
+ }
+
+ MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
+
+ *aIndexId = int64_t(indexId / 2);
+}
+
+// static
+nsresult
+MakeCompressedIndexDataValues(
+ const FallibleTArray<IndexDataValue>& aIndexValues,
+ UniqueFreePtr<uint8_t>& aCompressedIndexDataValues,
+ uint32_t* aCompressedIndexDataValuesLength)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(!aCompressedIndexDataValues);
+ MOZ_ASSERT(aCompressedIndexDataValuesLength);
+
+ PROFILER_LABEL("IndexedDB",
+ "MakeCompressedIndexDataValues",
+ js::ProfileEntry::Category::STORAGE);
+
+ const uint32_t arrayLength = aIndexValues.Length();
+ if (!arrayLength) {
+ *aCompressedIndexDataValuesLength = 0;
+ return NS_OK;
+ }
+
+ // First calculate the size of the final buffer.
+ uint32_t blobDataLength = 0;
+
+ for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
+ const IndexDataValue& info = aIndexValues[arrayIndex];
+ const nsCString& keyBuffer = info.mKey.GetBuffer();
+ const nsCString& sortKeyBuffer = info.mSortKey.GetBuffer();
+ const uint32_t keyBufferLength = keyBuffer.Length();
+ const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
+
+ MOZ_ASSERT(!keyBuffer.IsEmpty());
+
+ // Don't let |infoLength| overflow.
+ if (NS_WARN_IF(UINT32_MAX - keyBuffer.Length() <
+ CompressedByteCountForIndexId(info.mIndexId) +
+ CompressedByteCountForNumber(keyBufferLength) +
+ CompressedByteCountForNumber(sortKeyBufferLength))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ const uint32_t infoLength =
+ CompressedByteCountForIndexId(info.mIndexId) +
+ CompressedByteCountForNumber(keyBufferLength) +
+ CompressedByteCountForNumber(sortKeyBufferLength) +
+ keyBufferLength +
+ sortKeyBufferLength;
+
+ // Don't let |blobDataLength| overflow.
+ if (NS_WARN_IF(UINT32_MAX - infoLength < blobDataLength)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ blobDataLength += infoLength;
+ }
+
+ UniqueFreePtr<uint8_t> blobData(
+ static_cast<uint8_t*>(malloc(blobDataLength)));
+ if (NS_WARN_IF(!blobData)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint8_t* blobDataIter = blobData.get();
+
+ for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
+ const IndexDataValue& info = aIndexValues[arrayIndex];
+ const nsCString& keyBuffer = info.mKey.GetBuffer();
+ const nsCString& sortKeyBuffer = info.mSortKey.GetBuffer();
+ const uint32_t keyBufferLength = keyBuffer.Length();
+ const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
+
+ WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
+ WriteCompressedNumber(keyBufferLength, &blobDataIter);
+
+ memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
+ blobDataIter += keyBufferLength;
+
+ WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);
+
+ memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
+ blobDataIter += sortKeyBufferLength;
+ }
+
+ MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength);
+
+ aCompressedIndexDataValues.swap(blobData);
+ *aCompressedIndexDataValuesLength = uint32_t(blobDataLength);
+
+ return NS_OK;
+}
+
+nsresult
+ReadCompressedIndexDataValuesFromBlob(const uint8_t* aBlobData,
+ uint32_t aBlobDataLength,
+ nsTArray<IndexDataValue>& aIndexValues)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aBlobData);
+ MOZ_ASSERT(aBlobDataLength);
+ MOZ_ASSERT(aIndexValues.IsEmpty());
+
+ PROFILER_LABEL("IndexedDB",
+ "ReadCompressedIndexDataValuesFromBlob",
+ js::ProfileEntry::Category::STORAGE);
+
+ const uint8_t* blobDataIter = aBlobData;
+ const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;
+
+ while (blobDataIter < blobDataEnd) {
+ int64_t indexId;
+ bool unique;
+ ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);
+
+ if (NS_WARN_IF(blobDataIter == blobDataEnd)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Read key buffer length.
+ const uint64_t keyBufferLength =
+ ReadCompressedNumber(&blobDataIter, blobDataEnd);
+
+ if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
+ NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
+ NS_WARN_IF(blobDataIter + keyBufferLength > blobDataEnd)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ nsCString keyBuffer(reinterpret_cast<const char*>(blobDataIter),
+ uint32_t(keyBufferLength));
+ blobDataIter += keyBufferLength;
+
+ IndexDataValue idv(indexId, unique, Key(keyBuffer));
+
+ // Read sort key buffer length.
+ const uint64_t sortKeyBufferLength =
+ ReadCompressedNumber(&blobDataIter, blobDataEnd);
+
+ if (sortKeyBufferLength > 0) {
+ if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
+ NS_WARN_IF(sortKeyBufferLength > uint64_t(UINT32_MAX)) ||
+ NS_WARN_IF(blobDataIter + sortKeyBufferLength > blobDataEnd)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ nsCString sortKeyBuffer(reinterpret_cast<const char*>(blobDataIter),
+ uint32_t(sortKeyBufferLength));
+ blobDataIter += sortKeyBufferLength;
+
+ idv.mSortKey = Key(sortKeyBuffer);
+ }
+
+ if (NS_WARN_IF(!aIndexValues.InsertElementSorted(idv, fallible))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ MOZ_ASSERT(blobDataIter == blobDataEnd);
+
+ return NS_OK;
+}
+
+// static
+template <typename T>
+nsresult
+ReadCompressedIndexDataValuesFromSource(T* aSource,
+ uint32_t aColumnIndex,
+ nsTArray<IndexDataValue>& aIndexValues)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aSource);
+ MOZ_ASSERT(aIndexValues.IsEmpty());
+
+ int32_t columnType;
+ nsresult rv = aSource->GetTypeOfIndex(aColumnIndex, &columnType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB);
+
+ const uint8_t* blobData;
+ uint32_t blobDataLength;
+ rv = aSource->GetSharedBlob(aColumnIndex, &blobDataLength, &blobData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!blobDataLength)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ rv = ReadCompressedIndexDataValuesFromBlob(blobData,
+ blobDataLength,
+ aIndexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ReadCompressedIndexDataValues(mozIStorageStatement* aStatement,
+ uint32_t aColumnIndex,
+ nsTArray<IndexDataValue>& aIndexValues)
+{
+ return ReadCompressedIndexDataValuesFromSource(aStatement,
+ aColumnIndex,
+ aIndexValues);
+}
+
+nsresult
+ReadCompressedIndexDataValues(mozIStorageValueArray* aValues,
+ uint32_t aColumnIndex,
+ nsTArray<IndexDataValue>& aIndexValues)
+{
+ return ReadCompressedIndexDataValuesFromSource(aValues,
+ aColumnIndex,
+ aIndexValues);
+}
+
+nsresult
+CreateFileTables(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "CreateFileTables",
+ js::ProfileEntry::Category::STORAGE);
+
+ // Table `file`
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE file ("
+ "id INTEGER PRIMARY KEY, "
+ "refcount INTEGER NOT NULL"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_insert_trigger "
+ "AFTER INSERT ON object_data "
+ "FOR EACH ROW "
+ "WHEN NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(NULL, NEW.file_ids); "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_update_trigger "
+ "AFTER UPDATE OF file_ids ON object_data "
+ "FOR EACH ROW "
+ "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_delete_trigger "
+ "AFTER DELETE ON object_data "
+ "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NULL); "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER file_update_trigger "
+ "AFTER UPDATE ON file "
+ "FOR EACH ROW WHEN NEW.refcount = 0 "
+ "BEGIN "
+ "DELETE FROM file WHERE id = OLD.id; "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CreateTables(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "CreateTables",
+ js::ProfileEntry::Category::STORAGE);
+
+ // Table `database`
+
+ // There are two reasons for having the origin column.
+ // First, we can ensure that we don't have collisions in the origin hash we
+ // use for the path because when we open the db we can make sure that the
+ // origins exactly match. Second, chrome code crawling through the idb
+ // directory can figure out the origin of every db without having to
+ // reverse-engineer our hash scheme.
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE database"
+ "( name TEXT PRIMARY KEY"
+ ", origin TEXT NOT NULL"
+ ", version INTEGER NOT NULL DEFAULT 0"
+ ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
+ ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
+ ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Table `object_store`
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE object_store"
+ "( id INTEGER PRIMARY KEY"
+ ", auto_increment INTEGER NOT NULL DEFAULT 0"
+ ", name TEXT NOT NULL"
+ ", key_path TEXT"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Table `object_store_index`
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE object_store_index"
+ "( id INTEGER PRIMARY KEY"
+ ", object_store_id INTEGER NOT NULL"
+ ", name TEXT NOT NULL"
+ ", key_path TEXT NOT NULL"
+ ", unique_index INTEGER NOT NULL"
+ ", multientry INTEGER NOT NULL"
+ ", locale TEXT"
+ ", is_auto_locale BOOLEAN NOT NULL"
+ ", FOREIGN KEY (object_store_id) "
+ "REFERENCES object_store(id) "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Table `object_data`
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE object_data"
+ "( object_store_id INTEGER NOT NULL"
+ ", key BLOB NOT NULL"
+ ", index_data_values BLOB DEFAULT NULL"
+ ", file_ids TEXT"
+ ", data BLOB NOT NULL"
+ ", PRIMARY KEY (object_store_id, key)"
+ ", FOREIGN KEY (object_store_id) "
+ "REFERENCES object_store(id) "
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Table `index_data`
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE index_data"
+ "( index_id INTEGER NOT NULL"
+ ", value BLOB NOT NULL"
+ ", object_data_key BLOB NOT NULL"
+ ", object_store_id INTEGER NOT NULL"
+ ", value_locale BLOB"
+ ", PRIMARY KEY (index_id, value, object_data_key)"
+ ", FOREIGN KEY (index_id) "
+ "REFERENCES object_store_index(id) "
+ ", FOREIGN KEY (object_store_id, object_data_key) "
+ "REFERENCES object_data(object_store_id, key) "
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX index_data_value_locale_index "
+ "ON index_data (index_id, value_locale, object_data_key, value) "
+ "WHERE value_locale IS NOT NULL;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Table `unique_index_data`
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE unique_index_data"
+ "( index_id INTEGER NOT NULL"
+ ", value BLOB NOT NULL"
+ ", object_store_id INTEGER NOT NULL"
+ ", object_data_key BLOB NOT NULL"
+ ", value_locale BLOB"
+ ", PRIMARY KEY (index_id, value)"
+ ", FOREIGN KEY (index_id) "
+ "REFERENCES object_store_index(id) "
+ ", FOREIGN KEY (object_store_id, object_data_key) "
+ "REFERENCES object_data(object_store_id, key) "
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX unique_index_data_value_locale_index "
+ "ON unique_index_data (index_id, value_locale, object_data_key, value) "
+ "WHERE value_locale IS NOT NULL;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CreateFileTables(aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(kSQLiteSchemaVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom4To5(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom4To5",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv;
+
+ // All we changed is the type of the version column, so lets try to
+ // convert that to an integer, and if we fail, set it to 0.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT name, version, dataVersion "
+ "FROM database"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString name;
+ int32_t intVersion;
+ int64_t dataVersion;
+
+ {
+ mozStorageStatementScoper scoper(stmt);
+
+ bool hasResults;
+ rv = stmt->ExecuteStep(&hasResults);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (NS_WARN_IF(!hasResults)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString version;
+ rv = stmt->GetString(1, version);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ intVersion = version.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ intVersion = 0;
+ }
+
+ rv = stmt->GetString(0, name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->GetInt64(2, &dataVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE database"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE database ("
+ "name TEXT NOT NULL, "
+ "version INTEGER NOT NULL DEFAULT 0, "
+ "dataVersion INTEGER NOT NULL"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO database (name, version, dataVersion) "
+ "VALUES (:name, :version, :dataVersion)"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ {
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindStringParameter(0, name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt32Parameter(1, intVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64Parameter(2, dataVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = aConnection->SetSchemaVersion(5);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom5To6(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom5To6",
+ js::ProfileEntry::Category::STORAGE);
+
+ // First, drop all the indexes we're no longer going to use.
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX key_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX ai_key_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX value_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX ai_value_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now, reorder the columns of object_data to put the blob data last. We do
+ // this by copying into a temporary table, dropping the original, then copying
+ // back into a newly created table.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id, "
+ "key_value, "
+ "data "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, key_value, data "
+ "FROM object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE object_data ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id INTEGER NOT NULL, "
+ "key_value DEFAULT NULL, "
+ "data BLOB NOT NULL, "
+ "UNIQUE (object_store_id, key_value), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO object_data "
+ "SELECT id, object_store_id, key_value, data "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We need to add a unique constraint to our ai_object_data table. Copy all
+ // the data out of it using a temporary table as before.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id, "
+ "data "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, data "
+ "FROM ai_object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE ai_object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE ai_object_data ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "object_store_id INTEGER NOT NULL, "
+ "data BLOB NOT NULL, "
+ "UNIQUE (object_store_id, id), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO ai_object_data "
+ "SELECT id, object_store_id, data "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Fix up the index_data table. We're reordering the columns as well as
+ // changing the primary key from being a simple id to being a composite.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "object_data_key, "
+ "object_data_id "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value NOT NULL, "
+ "object_data_key NOT NULL, "
+ "object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, object_data_key), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE, "
+ "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO index_data "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX index_data_object_data_id_index "
+ "ON index_data (object_data_id);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Fix up the unique_index_data table. We're reordering the columns as well as
+ // changing the primary key from being a simple id to being a composite.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "object_data_key, "
+ "object_data_id "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE unique_index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value NOT NULL, "
+ "object_data_key NOT NULL, "
+ "object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, object_data_key), "
+ "UNIQUE (index_id, value), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE "
+ "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO unique_index_data "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX unique_index_data_object_data_id_index "
+ "ON unique_index_data (object_data_id);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Fix up the ai_index_data table. We're reordering the columns as well as
+ // changing the primary key from being a simple id to being a composite.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "ai_object_data_id "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, value, ai_object_data_id "
+ "FROM ai_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE ai_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE ai_index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value NOT NULL, "
+ "ai_object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, ai_object_data_id), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE, "
+ "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO ai_index_data "
+ "SELECT index_id, value, ai_object_data_id "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX ai_index_data_ai_object_data_id_index "
+ "ON ai_index_data (ai_object_data_id);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Fix up the ai_unique_index_data table. We're reordering the columns as well
+ // as changing the primary key from being a simple id to being a composite.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "ai_object_data_id "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, value, ai_object_data_id "
+ "FROM ai_unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE ai_unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE ai_unique_index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value NOT NULL, "
+ "ai_object_data_id INTEGER NOT NULL, "
+ "UNIQUE (index_id, value), "
+ "PRIMARY KEY (index_id, value, ai_object_data_id), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE, "
+ "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO ai_unique_index_data "
+ "SELECT index_id, value, ai_object_data_id "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX ai_unique_index_data_ai_object_data_id_index "
+ "ON ai_unique_index_data (ai_object_data_id);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(6);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom6To7(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom6To7",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id, "
+ "name, "
+ "key_path, "
+ "auto_increment"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, name, key_path, auto_increment "
+ "FROM object_store;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE object_store;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE object_store ("
+ "id INTEGER PRIMARY KEY, "
+ "auto_increment INTEGER NOT NULL DEFAULT 0, "
+ "name TEXT NOT NULL, "
+ "key_path TEXT, "
+ "UNIQUE (name)"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO object_store "
+ "SELECT id, auto_increment, name, nullif(key_path, '') "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(7);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom7To8(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom7To8",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id, "
+ "object_store_id, "
+ "name, "
+ "key_path, "
+ "unique_index, "
+ "object_store_autoincrement"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, name, key_path, "
+ "unique_index, object_store_autoincrement "
+ "FROM object_store_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE object_store_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE object_store_index ("
+ "id INTEGER, "
+ "object_store_id INTEGER NOT NULL, "
+ "name TEXT NOT NULL, "
+ "key_path TEXT NOT NULL, "
+ "unique_index INTEGER NOT NULL, "
+ "multientry INTEGER NOT NULL, "
+ "object_store_autoincrement INTERGER NOT NULL, "
+ "PRIMARY KEY (id), "
+ "UNIQUE (object_store_id, name), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO object_store_index "
+ "SELECT id, object_store_id, name, key_path, "
+ "unique_index, 0, object_store_autoincrement "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(8);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class CompressDataBlobsFunction final
+ : public mozIStorageFunction
+{
+public:
+ NS_DECL_ISUPPORTS
+
+private:
+ ~CompressDataBlobsFunction()
+ { }
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override
+ {
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+
+ PROFILER_LABEL("IndexedDB",
+ "CompressDataBlobsFunction::OnFunctionCall",
+ js::ProfileEntry::Category::STORAGE);
+
+ uint32_t argc;
+ nsresult rv = aArguments->GetNumEntries(&argc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (argc != 1) {
+ NS_WARNING("Don't call me with the wrong number of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t type;
+ rv = aArguments->GetTypeOfIndex(0, &type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (type != mozIStorageStatement::VALUE_TYPE_BLOB) {
+ NS_WARNING("Don't call me with the wrong type of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const uint8_t* uncompressed;
+ uint32_t uncompressedLength;
+ rv = aArguments->GetSharedBlob(0, &uncompressedLength, &uncompressed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
+ UniqueFreePtr<uint8_t> compressed(
+ static_cast<uint8_t*>(malloc(compressedLength)));
+ if (NS_WARN_IF(!compressed)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ snappy::RawCompress(reinterpret_cast<const char*>(uncompressed),
+ uncompressedLength,
+ reinterpret_cast<char*>(compressed.get()),
+ &compressedLength);
+
+ std::pair<uint8_t *, int> data(compressed.release(),
+ int(compressedLength));
+
+ nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
+
+ result.forget(aResult);
+ return NS_OK;
+ }
+};
+
+nsresult
+UpgradeSchemaFrom8To9_0(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom8To9_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ // We no longer use the dataVersion column.
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE database SET dataVersion = 0;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageFunction> compressor = new CompressDataBlobsFunction();
+
+ NS_NAMED_LITERAL_CSTRING(compressorName, "compress");
+
+ rv = aConnection->CreateFunction(compressorName, 1, compressor);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Turn off foreign key constraints before we do anything here.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE object_data SET data = compress(data);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE ai_object_data SET data = compress(data);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->RemoveFunction(compressorName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(9, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom9_0To10_0(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom9_0To10_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE object_data ADD COLUMN file_ids TEXT;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CreateFileTables(aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(10, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom10_0To11_0(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom10_0To11_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id, "
+ "object_store_id, "
+ "name, "
+ "key_path, "
+ "unique_index, "
+ "multientry"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, name, key_path, "
+ "unique_index, multientry "
+ "FROM object_store_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE object_store_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE object_store_index ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id INTEGER NOT NULL, "
+ "name TEXT NOT NULL, "
+ "key_path TEXT NOT NULL, "
+ "unique_index INTEGER NOT NULL, "
+ "multientry INTEGER NOT NULL, "
+ "UNIQUE (object_store_id, name), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO object_store_index "
+ "SELECT id, object_store_id, name, key_path, "
+ "unique_index, multientry "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TRIGGER object_data_insert_trigger;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO object_data (object_store_id, key_value, data, file_ids) "
+ "SELECT object_store_id, id, data, file_ids "
+ "FROM ai_object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_insert_trigger "
+ "AFTER INSERT ON object_data "
+ "FOR EACH ROW "
+ "WHEN NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(NULL, NEW.file_ids); "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO index_data (index_id, value, object_data_key, object_data_id) "
+ "SELECT ai_index_data.index_id, ai_index_data.value, ai_index_data.ai_object_data_id, object_data.id "
+ "FROM ai_index_data "
+ "INNER JOIN object_store_index ON "
+ "object_store_index.id = ai_index_data.index_id "
+ "INNER JOIN object_data ON "
+ "object_data.object_store_id = object_store_index.object_store_id AND "
+ "object_data.key_value = ai_index_data.ai_object_data_id;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO unique_index_data (index_id, value, object_data_key, object_data_id) "
+ "SELECT ai_unique_index_data.index_id, ai_unique_index_data.value, ai_unique_index_data.ai_object_data_id, object_data.id "
+ "FROM ai_unique_index_data "
+ "INNER JOIN object_store_index ON "
+ "object_store_index.id = ai_unique_index_data.index_id "
+ "INNER JOIN object_data ON "
+ "object_data.object_store_id = object_store_index.object_store_id AND "
+ "object_data.key_value = ai_unique_index_data.ai_object_data_id;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE object_store "
+ "SET auto_increment = (SELECT max(id) FROM ai_object_data) + 1 "
+ "WHERE auto_increment;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE ai_unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE ai_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE ai_object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(11, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class EncodeKeysFunction final
+ : public mozIStorageFunction
+{
+public:
+ NS_DECL_ISUPPORTS
+
+private:
+ ~EncodeKeysFunction()
+ { }
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override
+ {
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+
+ PROFILER_LABEL("IndexedDB",
+ "EncodeKeysFunction::OnFunctionCall",
+ js::ProfileEntry::Category::STORAGE);
+
+ uint32_t argc;
+ nsresult rv = aArguments->GetNumEntries(&argc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (argc != 1) {
+ NS_WARNING("Don't call me with the wrong number of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t type;
+ rv = aArguments->GetTypeOfIndex(0, &type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ Key key;
+ if (type == mozIStorageStatement::VALUE_TYPE_INTEGER) {
+ int64_t intKey;
+ aArguments->GetInt64(0, &intKey);
+ key.SetFromInteger(intKey);
+ } else if (type == mozIStorageStatement::VALUE_TYPE_TEXT) {
+ nsString stringKey;
+ aArguments->GetString(0, stringKey);
+ key.SetFromString(stringKey);
+ } else {
+ NS_WARNING("Don't call me with the wrong type of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const nsCString& buffer = key.GetBuffer();
+
+ std::pair<const void *, int> data(static_cast<const void*>(buffer.get()),
+ int(buffer.Length()));
+
+ nsCOMPtr<nsIVariant> result = new mozilla::storage::BlobVariant(data);
+
+ result.forget(aResult);
+ return NS_OK;
+ }
+};
+
+nsresult
+UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom11_0To12_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ NS_NAMED_LITERAL_CSTRING(encoderName, "encode");
+
+ nsCOMPtr<mozIStorageFunction> encoder = new EncodeKeysFunction();
+
+ nsresult rv = aConnection->CreateFunction(encoderName, 1, encoder);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id, "
+ "key_value, "
+ "data, "
+ "file_ids "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, encode(key_value), data, file_ids "
+ "FROM object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE object_data ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id INTEGER NOT NULL, "
+ "key_value BLOB DEFAULT NULL, "
+ "file_ids TEXT, "
+ "data BLOB NOT NULL, "
+ "UNIQUE (object_store_id, key_value), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO object_data "
+ "SELECT id, object_store_id, key_value, file_ids, data "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_insert_trigger "
+ "AFTER INSERT ON object_data "
+ "FOR EACH ROW "
+ "WHEN NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(NULL, NEW.file_ids); "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_update_trigger "
+ "AFTER UPDATE OF file_ids ON object_data "
+ "FOR EACH ROW "
+ "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_delete_trigger "
+ "AFTER DELETE ON object_data "
+ "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NULL); "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "object_data_key, "
+ "object_data_id "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, encode(value), encode(object_data_key), object_data_id "
+ "FROM index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value BLOB NOT NULL, "
+ "object_data_key BLOB NOT NULL, "
+ "object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, object_data_key), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE, "
+ "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO index_data "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX index_data_object_data_id_index "
+ "ON index_data (object_data_id);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "object_data_key, "
+ "object_data_id "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, encode(value), encode(object_data_key), object_data_id "
+ "FROM unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE unique_index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value BLOB NOT NULL, "
+ "object_data_key BLOB NOT NULL, "
+ "object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, object_data_key), "
+ "UNIQUE (index_id, value), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE "
+ "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+ "CASCADE"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO unique_index_data "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE temp_upgrade;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX unique_index_data_object_data_id_index "
+ "ON unique_index_data (object_data_id);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->RemoveFunction(encoderName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(12, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom12_0To13_0(mozIStorageConnection* aConnection,
+ bool* aVacuumNeeded)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom12_0To13_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv;
+
+#ifdef IDB_MOBILE
+ int32_t defaultPageSize;
+ rv = aConnection->GetDefaultPageSize(&defaultPageSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Enable auto_vacuum mode and update the page size to the platform default.
+ nsAutoCString upgradeQuery("PRAGMA auto_vacuum = FULL; PRAGMA page_size = ");
+ upgradeQuery.AppendInt(defaultPageSize);
+
+ rv = aConnection->ExecuteSimpleSQL(upgradeQuery);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aVacuumNeeded = true;
+#endif
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(13, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom13_0To14_0(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ // The only change between 13 and 14 was a different structured
+ // clone format, but it's backwards-compatible.
+ nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(14, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom14_0To15_0(mozIStorageConnection* aConnection)
+{
+ // The only change between 14 and 15 was a different structured
+ // clone format, but it's backwards-compatible.
+ nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(15, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom15_0To16_0(mozIStorageConnection* aConnection)
+{
+ // The only change between 15 and 16 was a different structured
+ // clone format, but it's backwards-compatible.
+ nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(16, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom16_0To17_0(mozIStorageConnection* aConnection)
+{
+ // The only change between 16 and 17 was a different structured
+ // clone format, but it's backwards-compatible.
+ nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(17, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class UpgradeSchemaFrom17_0To18_0Helper final
+{
+ class InsertIndexDataValuesFunction;
+ class UpgradeKeyFunction;
+
+public:
+ static nsresult
+ DoUpgrade(mozIStorageConnection* aConnection, const nsACString& aOrigin);
+
+private:
+ static nsresult
+ DoUpgradeInternal(mozIStorageConnection* aConnection,
+ const nsACString& aOrigin);
+
+ UpgradeSchemaFrom17_0To18_0Helper()
+ {
+ MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!");
+ }
+
+ ~UpgradeSchemaFrom17_0To18_0Helper()
+ {
+ MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!");
+ }
+};
+
+class UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction final
+ : public mozIStorageFunction
+{
+public:
+ InsertIndexDataValuesFunction()
+ { }
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~InsertIndexDataValuesFunction()
+ { }
+
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::
+ InsertIndexDataValuesFunction,
+ mozIStorageFunction);
+
+NS_IMETHODIMP
+UpgradeSchemaFrom17_0To18_0Helper::
+InsertIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
+ nsIVariant** _retval)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aValues);
+ MOZ_ASSERT(_retval);
+
+#ifdef DEBUG
+ {
+ uint32_t argCount;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
+ MOZ_ASSERT(argCount == 4);
+
+ int32_t valueType;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
+ valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+ }
+#endif
+
+ // Read out the previous value. It may be NULL, in which case we'll just end
+ // up with an empty array.
+ AutoTArray<IndexDataValue, 32> indexValues;
+ nsresult rv = ReadCompressedIndexDataValues(aValues, 0, indexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t indexId;
+ rv = aValues->GetInt64(1, &indexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t unique;
+ rv = aValues->GetInt32(2, &unique);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ Key value;
+ rv = value.SetFromValueArray(aValues, 3);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the array with the new addition.
+ if (NS_WARN_IF(!indexValues.SetCapacity(indexValues.Length() + 1,
+ fallible))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ MOZ_ALWAYS_TRUE(
+ indexValues.InsertElementSorted(IndexDataValue(indexId, !!unique, value),
+ fallible));
+
+ // Compress the array.
+ UniqueFreePtr<uint8_t> indexValuesBlob;
+ uint32_t indexValuesBlobLength;
+ rv = MakeCompressedIndexDataValues(indexValues,
+ indexValuesBlob,
+ &indexValuesBlobLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The compressed blob is the result of this function.
+ std::pair<uint8_t *, int> indexValuesBlobPair(indexValuesBlob.release(),
+ indexValuesBlobLength);
+
+ nsCOMPtr<nsIVariant> result =
+ new storage::AdoptedBlobVariant(indexValuesBlobPair);
+
+ result.forget(_retval);
+ return NS_OK;
+}
+
+class UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction final
+ : public mozIStorageFunction
+{
+public:
+ UpgradeKeyFunction()
+ { }
+
+ static nsresult
+ CopyAndUpgradeKeyBuffer(const uint8_t* aSource,
+ const uint8_t* aSourceEnd,
+ uint8_t* aDestination)
+ {
+ return CopyAndUpgradeKeyBufferInternal(aSource,
+ aSourceEnd,
+ aDestination,
+ 0 /* aTagOffset */,
+ 0 /* aRecursionDepth */);
+ }
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~UpgradeKeyFunction()
+ { }
+
+ static nsresult
+ CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
+ const uint8_t* aSourceEnd,
+ uint8_t*& aDestination,
+ uint8_t aTagOffset,
+ uint8_t aRecursionDepth);
+
+ static uint32_t
+ AdjustedSize(uint32_t aMaxSize,
+ const uint8_t* aSource,
+ const uint8_t* aSourceEnd)
+ {
+ MOZ_ASSERT(aMaxSize);
+ MOZ_ASSERT(aSource);
+ MOZ_ASSERT(aSourceEnd);
+ MOZ_ASSERT(aSource <= aSourceEnd);
+
+ return std::min(aMaxSize, uint32_t(aSourceEnd - aSource));
+ }
+
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+// static
+nsresult
+UpgradeSchemaFrom17_0To18_0Helper::
+UpgradeKeyFunction::CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
+ const uint8_t* aSourceEnd,
+ uint8_t*& aDestination,
+ uint8_t aTagOffset,
+ uint8_t aRecursionDepth)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aSource);
+ MOZ_ASSERT(*aSource);
+ MOZ_ASSERT(aSourceEnd);
+ MOZ_ASSERT(aSource < aSourceEnd);
+ MOZ_ASSERT(aDestination);
+ MOZ_ASSERT(aTagOffset <= Key::kMaxArrayCollapse);
+
+ static constexpr uint8_t kOldNumberTag = 0x1;
+ static constexpr uint8_t kOldDateTag = 0x2;
+ static constexpr uint8_t kOldStringTag = 0x3;
+ static constexpr uint8_t kOldArrayTag = 0x4;
+ static constexpr uint8_t kOldMaxType = kOldArrayTag;
+
+ if (NS_WARN_IF(aRecursionDepth > Key::kMaxRecursionDepth)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ const uint8_t sourceTag = *aSource - (aTagOffset * kOldMaxType);
+ MOZ_ASSERT(sourceTag);
+
+ if (NS_WARN_IF(sourceTag > kOldMaxType * Key::kMaxArrayCollapse)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (sourceTag == kOldNumberTag || sourceTag == kOldDateTag) {
+ // Write the new tag.
+ *aDestination++ =
+ (sourceTag == kOldNumberTag ? Key::eFloat : Key::eDate) +
+ (aTagOffset * Key::eMaxType);
+ aSource++;
+
+ // Numbers and Dates are encoded as 64-bit integers, but trailing 0
+ // bytes have been removed.
+ const uint32_t byteCount =
+ AdjustedSize(sizeof(uint64_t), aSource, aSourceEnd);
+
+ for (uint32_t count = 0; count < byteCount; count++) {
+ *aDestination++ = *aSource++;
+ }
+
+ return NS_OK;
+ }
+
+ if (sourceTag == kOldStringTag) {
+ // Write the new tag.
+ *aDestination++ = Key::eString + (aTagOffset * Key::eMaxType);
+ aSource++;
+
+ while (aSource < aSourceEnd) {
+ const uint8_t byte = *aSource++;
+ *aDestination++ = byte;
+
+ if (!byte) {
+ // Just copied the terminator.
+ break;
+ }
+
+ // Maybe copy one or two extra bytes if the byte is tagged and we have
+ // enough source space.
+ if (byte & 0x80) {
+ const uint32_t byteCount =
+ AdjustedSize((byte & 0x40) ? 2 : 1, aSource, aSourceEnd);
+
+ for (uint32_t count = 0; count < byteCount; count++) {
+ *aDestination++ = *aSource++;
+ }
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(sourceTag < kOldArrayTag)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ aTagOffset++;
+
+ if (aTagOffset == Key::kMaxArrayCollapse) {
+ MOZ_ASSERT(sourceTag == kOldArrayTag);
+
+ *aDestination++ = (aTagOffset * Key::eMaxType);
+ aSource++;
+
+ aTagOffset = 0;
+ }
+
+ while (aSource < aSourceEnd &&
+ (*aSource - (aTagOffset * kOldMaxType)) != Key::eTerminator) {
+ nsresult rv = CopyAndUpgradeKeyBufferInternal(aSource,
+ aSourceEnd,
+ aDestination,
+ aTagOffset,
+ aRecursionDepth + 1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aTagOffset = 0;
+ }
+
+ if (aSource < aSourceEnd) {
+ MOZ_ASSERT((*aSource - (aTagOffset * kOldMaxType)) == Key::eTerminator);
+ *aDestination++ = Key::eTerminator + (aTagOffset * Key::eMaxType);
+ aSource++;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction,
+ mozIStorageFunction);
+
+NS_IMETHODIMP
+UpgradeSchemaFrom17_0To18_0Helper::
+UpgradeKeyFunction::OnFunctionCall(mozIStorageValueArray* aValues,
+ nsIVariant** _retval)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aValues);
+ MOZ_ASSERT(_retval);
+
+#ifdef DEBUG
+ {
+ uint32_t argCount;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
+ MOZ_ASSERT(argCount == 1);
+
+ int32_t valueType;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+ }
+#endif
+
+ // Dig the old key out of the values.
+ const uint8_t* blobData;
+ uint32_t blobDataLength;
+ nsresult rv = aValues->GetSharedBlob(0, &blobDataLength, &blobData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Upgrading the key doesn't change the amount of space needed to hold it.
+ UniqueFreePtr<uint8_t> upgradedBlobData(
+ static_cast<uint8_t*>(malloc(blobDataLength)));
+ if (NS_WARN_IF(!upgradedBlobData)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = CopyAndUpgradeKeyBuffer(blobData,
+ blobData + blobDataLength,
+ upgradedBlobData.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The upgraded key is the result of this function.
+ std::pair<uint8_t*, int> data(upgradedBlobData.release(),
+ int(blobDataLength));
+
+ nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
+
+ result.forget(_retval);
+ return NS_OK;
+}
+
+// static
+nsresult
+UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(mozIStorageConnection* aConnection,
+ const nsACString& aOrigin)
+{
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ // Register the |upgrade_key| function.
+ RefPtr<UpgradeKeyFunction> updateFunction = new UpgradeKeyFunction();
+
+ NS_NAMED_LITERAL_CSTRING(upgradeKeyFunctionName, "upgrade_key");
+
+ nsresult rv =
+ aConnection->CreateFunction(upgradeKeyFunctionName, 1, updateFunction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Register the |insert_idv| function.
+ RefPtr<InsertIndexDataValuesFunction> insertIDVFunction =
+ new InsertIndexDataValuesFunction();
+
+ NS_NAMED_LITERAL_CSTRING(insertIDVFunctionName, "insert_idv");
+
+ rv = aConnection->CreateFunction(insertIDVFunctionName, 4, insertIDVFunction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_ALWAYS_SUCCEEDS(aConnection->RemoveFunction(upgradeKeyFunctionName));
+ return rv;
+ }
+
+ rv = DoUpgradeInternal(aConnection, aOrigin);
+
+ MOZ_ALWAYS_SUCCEEDS(aConnection->RemoveFunction(upgradeKeyFunctionName));
+ MOZ_ALWAYS_SUCCEEDS(aConnection->RemoveFunction(insertIDVFunctionName));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+UpgradeSchemaFrom17_0To18_0Helper::DoUpgradeInternal(
+ mozIStorageConnection* aConnection,
+ const nsACString& aOrigin)
+{
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Drop these triggers to avoid unnecessary work during the upgrade process.
+ "DROP TRIGGER object_data_insert_trigger;"
+ "DROP TRIGGER object_data_update_trigger;"
+ "DROP TRIGGER object_data_delete_trigger;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Drop these indexes before we do anything else to free disk space.
+ "DROP INDEX index_data_object_data_id_index;"
+ "DROP INDEX unique_index_data_object_data_id_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Create the new tables and triggers first.
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // This will eventually become the |database| table.
+ "CREATE TABLE database_upgrade "
+ "( name TEXT PRIMARY KEY"
+ ", origin TEXT NOT NULL"
+ ", version INTEGER NOT NULL DEFAULT 0"
+ ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
+ ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
+ ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // This will eventually become the |object_store| table.
+ "CREATE TABLE object_store_upgrade"
+ "( id INTEGER PRIMARY KEY"
+ ", auto_increment INTEGER NOT NULL DEFAULT 0"
+ ", name TEXT NOT NULL"
+ ", key_path TEXT"
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // This will eventually become the |object_store_index| table.
+ "CREATE TABLE object_store_index_upgrade"
+ "( id INTEGER PRIMARY KEY"
+ ", object_store_id INTEGER NOT NULL"
+ ", name TEXT NOT NULL"
+ ", key_path TEXT NOT NULL"
+ ", unique_index INTEGER NOT NULL"
+ ", multientry INTEGER NOT NULL"
+ ", FOREIGN KEY (object_store_id) "
+ "REFERENCES object_store(id) "
+ ");"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // This will eventually become the |object_data| table.
+ "CREATE TABLE object_data_upgrade"
+ "( object_store_id INTEGER NOT NULL"
+ ", key BLOB NOT NULL"
+ ", index_data_values BLOB DEFAULT NULL"
+ ", file_ids TEXT"
+ ", data BLOB NOT NULL"
+ ", PRIMARY KEY (object_store_id, key)"
+ ", FOREIGN KEY (object_store_id) "
+ "REFERENCES object_store(id) "
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // This will eventually become the |index_data| table.
+ "CREATE TABLE index_data_upgrade"
+ "( index_id INTEGER NOT NULL"
+ ", value BLOB NOT NULL"
+ ", object_data_key BLOB NOT NULL"
+ ", object_store_id INTEGER NOT NULL"
+ ", PRIMARY KEY (index_id, value, object_data_key)"
+ ", FOREIGN KEY (index_id) "
+ "REFERENCES object_store_index(id) "
+ ", FOREIGN KEY (object_store_id, object_data_key) "
+ "REFERENCES object_data(object_store_id, key) "
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // This will eventually become the |unique_index_data| table.
+ "CREATE TABLE unique_index_data_upgrade"
+ "( index_id INTEGER NOT NULL"
+ ", value BLOB NOT NULL"
+ ", object_store_id INTEGER NOT NULL"
+ ", object_data_key BLOB NOT NULL"
+ ", PRIMARY KEY (index_id, value)"
+ ", FOREIGN KEY (index_id) "
+ "REFERENCES object_store_index(id) "
+ ", FOREIGN KEY (object_store_id, object_data_key) "
+ "REFERENCES object_data(object_store_id, key) "
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Temporarily store |index_data_values| that we build during the upgrade of
+ // the index tables. We will later move this to the |object_data| table.
+ "CREATE TEMPORARY TABLE temp_index_data_values "
+ "( object_store_id INTEGER NOT NULL"
+ ", key BLOB NOT NULL"
+ ", index_data_values BLOB DEFAULT NULL"
+ ", PRIMARY KEY (object_store_id, key)"
+ ") WITHOUT ROWID;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // These two triggers help build the |index_data_values| blobs. The nested
+ // SELECT statements help us achieve an "INSERT OR UPDATE"-like behavior.
+ "CREATE TEMPORARY TRIGGER unique_index_data_upgrade_insert_trigger "
+ "AFTER INSERT ON unique_index_data_upgrade "
+ "BEGIN "
+ "INSERT OR REPLACE INTO temp_index_data_values "
+ "VALUES "
+ "( NEW.object_store_id"
+ ", NEW.object_data_key"
+ ", insert_idv("
+ "( SELECT index_data_values "
+ "FROM temp_index_data_values "
+ "WHERE object_store_id = NEW.object_store_id "
+ "AND key = NEW.object_data_key "
+ "), NEW.index_id"
+ ", 1" /* unique */
+ ", NEW.value"
+ ")"
+ ");"
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TRIGGER index_data_upgrade_insert_trigger "
+ "AFTER INSERT ON index_data_upgrade "
+ "BEGIN "
+ "INSERT OR REPLACE INTO temp_index_data_values "
+ "VALUES "
+ "( NEW.object_store_id"
+ ", NEW.object_data_key"
+ ", insert_idv("
+ "("
+ "SELECT index_data_values "
+ "FROM temp_index_data_values "
+ "WHERE object_store_id = NEW.object_store_id "
+ "AND key = NEW.object_data_key "
+ "), NEW.index_id"
+ ", 0" /* not unique */
+ ", NEW.value"
+ ")"
+ ");"
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |unique_index_data| table to change the column order, remove the
+ // ON DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Insert all the data.
+ "INSERT INTO unique_index_data_upgrade "
+ "SELECT "
+ "unique_index_data.index_id, "
+ "upgrade_key(unique_index_data.value), "
+ "object_data.object_store_id, "
+ "upgrade_key(unique_index_data.object_data_key) "
+ "FROM unique_index_data "
+ "JOIN object_data "
+ "ON unique_index_data.object_data_id = object_data.id;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // The trigger is no longer needed.
+ "DROP TRIGGER unique_index_data_upgrade_insert_trigger;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // The old table is no longer needed.
+ "DROP TABLE unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Rename the table.
+ "ALTER TABLE unique_index_data_upgrade "
+ "RENAME TO unique_index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |index_data| table to change the column order, remove the ON
+ // DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Insert all the data.
+ "INSERT INTO index_data_upgrade "
+ "SELECT "
+ "index_data.index_id, "
+ "upgrade_key(index_data.value), "
+ "upgrade_key(index_data.object_data_key), "
+ "object_data.object_store_id "
+ "FROM index_data "
+ "JOIN object_data "
+ "ON index_data.object_data_id = object_data.id;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // The trigger is no longer needed.
+ "DROP TRIGGER index_data_upgrade_insert_trigger;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // The old table is no longer needed.
+ "DROP TABLE index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Rename the table.
+ "ALTER TABLE index_data_upgrade "
+ "RENAME TO index_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |object_data| table to add the |index_data_values| column,
+ // remove the ON DELETE CASCADE clause, and apply the WITHOUT ROWID
+ // optimization.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Insert all the data.
+ "INSERT INTO object_data_upgrade "
+ "SELECT "
+ "object_data.object_store_id, "
+ "upgrade_key(object_data.key_value), "
+ "temp_index_data_values.index_data_values, "
+ "object_data.file_ids, "
+ "object_data.data "
+ "FROM object_data "
+ "LEFT JOIN temp_index_data_values "
+ "ON object_data.object_store_id = "
+ "temp_index_data_values.object_store_id "
+ "AND upgrade_key(object_data.key_value) = "
+ "temp_index_data_values.key;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // The temporary table is no longer needed.
+ "DROP TABLE temp_index_data_values;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // The old table is no longer needed.
+ "DROP TABLE object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ // Rename the table.
+ "ALTER TABLE object_data_upgrade "
+ "RENAME TO object_data;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |object_store_index| table to remove the UNIQUE constraint and
+ // the ON DELETE CASCADE clause.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO object_store_index_upgrade "
+ "SELECT * "
+ "FROM object_store_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE object_store_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE object_store_index_upgrade "
+ "RENAME TO object_store_index;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |object_store| table to remove the UNIQUE constraint.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO object_store_upgrade "
+ "SELECT * "
+ "FROM object_store;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE object_store;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE object_store_upgrade "
+ "RENAME TO object_store;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |database| table to include the origin, vacuum information, and
+ // apply the WITHOUT ROWID optimization.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO database_upgrade "
+ "SELECT name, :origin, version, 0, 0, 0 "
+ "FROM database;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE database;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE database_upgrade "
+ "RENAME TO database;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ {
+ // Make sure there's only one entry in the |database| table.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ MOZ_ASSERT(NS_SUCCEEDED(
+ aConnection->CreateStatement(
+ NS_LITERAL_CSTRING("SELECT COUNT(*) "
+ "FROM database;"),
+ getter_AddRefs(stmt))));
+
+ bool hasResult;
+ MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
+
+ int64_t count;
+ MOZ_ASSERT(NS_SUCCEEDED(stmt->GetInt64(0, &count)));
+
+ MOZ_ASSERT(count == 1);
+ }
+#endif
+
+ // Recreate file table triggers.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_insert_trigger "
+ "AFTER INSERT ON object_data "
+ "WHEN NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(NULL, NEW.file_ids);"
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_update_trigger "
+ "AFTER UPDATE OF file_ids ON object_data "
+ "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NEW.file_ids);"
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_delete_trigger "
+ "AFTER DELETE ON object_data "
+ "WHEN OLD.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NULL);"
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Finally, turn on auto_vacuum mode. We use full auto_vacuum mode to reclaim
+ // disk space on mobile devices (at the cost of some COMMIT speed), and
+ // incremental auto_vacuum mode on desktop builds.
+ rv = aConnection->ExecuteSimpleSQL(
+#ifdef IDB_MOBILE
+ NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
+#else
+ NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
+#endif
+ );
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(18, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom17_0To18_0(mozIStorageConnection* aConnection,
+ const nsACString& aOrigin)
+{
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom17_0To18_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ return UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(aConnection, aOrigin);
+}
+
+nsresult
+UpgradeSchemaFrom18_0To19_0(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ nsresult rv;
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom18_0To19_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE object_store_index "
+ "ADD COLUMN locale TEXT;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE object_store_index "
+ "ADD COLUMN is_auto_locale BOOLEAN;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE index_data "
+ "ADD COLUMN value_locale BLOB;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE unique_index_data "
+ "ADD COLUMN value_locale BLOB;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX index_data_value_locale_index "
+ "ON index_data (index_id, value_locale, object_data_key, value) "
+ "WHERE value_locale IS NOT NULL;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE INDEX unique_index_data_value_locale_index "
+ "ON unique_index_data (index_id, value_locale, object_data_key, value) "
+ "WHERE value_locale IS NOT NULL;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(19, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+#if !defined(MOZ_B2G)
+
+class NormalJSContext;
+
+class UpgradeFileIdsFunction final
+ : public mozIStorageFunction
+{
+ RefPtr<FileManager> mFileManager;
+ nsAutoPtr<NormalJSContext> mContext;
+
+public:
+ UpgradeFileIdsFunction()
+ {
+ AssertIsOnIOThread();
+ }
+
+ nsresult
+ Init(nsIFile* aFMDirectory,
+ mozIStorageConnection* aConnection);
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~UpgradeFileIdsFunction()
+ {
+ AssertIsOnIOThread();
+
+ if (mFileManager) {
+ mFileManager->Invalidate();
+ }
+ }
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override;
+};
+
+#endif // MOZ_B2G
+
+nsresult
+UpgradeSchemaFrom19_0To20_0(nsIFile* aFMDirectory,
+ mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom19_0To20_0",
+ js::ProfileEntry::Category::STORAGE);
+
+#if defined(MOZ_B2G)
+
+ // We don't have to do the upgrade of file ids on B2G. The old format was
+ // only used by the previous single process implementation and B2G was
+ // always multi process. This is a nice optimization since the upgrade needs
+ // to deserialize all structured clones which reference a stored file or
+ // a mutable file.
+ nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#else // MOZ_B2G
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT count(*) "
+ "FROM object_data "
+ "WHERE file_ids IS NOT NULL"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t count;
+
+ {
+ mozStorageStatementScoper scoper(stmt);
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!hasResult)) {
+ MOZ_ASSERT(false, "This should never be possible!");
+ return NS_ERROR_FAILURE;
+ }
+
+ count = stmt->AsInt64(0);
+ if (NS_WARN_IF(count < 0)) {
+ MOZ_ASSERT(false, "This should never be possible!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (count == 0) {
+ // Nothing to upgrade.
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ RefPtr<UpgradeFileIdsFunction> function = new UpgradeFileIdsFunction();
+
+ rv = function->Init(aFMDirectory, aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(functionName, "upgrade");
+
+ rv = aConnection->CreateFunction(functionName, 2, function);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Disable update trigger.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TRIGGER object_data_update_trigger;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE object_data "
+ "SET file_ids = upgrade(file_ids, data) "
+ "WHERE file_ids IS NOT NULL;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Enable update trigger.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TRIGGER object_data_update_trigger "
+ "AFTER UPDATE OF file_ids ON object_data "
+ "FOR EACH ROW "
+ "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
+ "END;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->RemoveFunction(functionName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#endif // MOZ_B2G
+
+ return NS_OK;
+}
+
+class UpgradeIndexDataValuesFunction final
+ : public mozIStorageFunction
+{
+public:
+ UpgradeIndexDataValuesFunction()
+ {
+ AssertIsOnIOThread();
+ }
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~UpgradeIndexDataValuesFunction()
+ {
+ AssertIsOnIOThread();
+ }
+
+ nsresult
+ ReadOldCompressedIDVFromBlob(const uint8_t* aBlobData,
+ uint32_t aBlobDataLength,
+ nsTArray<IndexDataValue>& aIndexValues);
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override;
+};
+
+NS_IMPL_ISUPPORTS(UpgradeIndexDataValuesFunction, mozIStorageFunction)
+
+nsresult
+UpgradeIndexDataValuesFunction::ReadOldCompressedIDVFromBlob(
+ const uint8_t* aBlobData,
+ uint32_t aBlobDataLength,
+ nsTArray<IndexDataValue>& aIndexValues)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aBlobData);
+ MOZ_ASSERT(aBlobDataLength);
+ MOZ_ASSERT(aIndexValues.IsEmpty());
+
+ const uint8_t* blobDataIter = aBlobData;
+ const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;
+
+ int64_t indexId;
+ bool unique;
+ bool nextIndexIdAlreadyRead = false;
+
+ while (blobDataIter < blobDataEnd) {
+ if (!nextIndexIdAlreadyRead) {
+ ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);
+ }
+ nextIndexIdAlreadyRead = false;
+
+ if (NS_WARN_IF(blobDataIter == blobDataEnd)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Read key buffer length.
+ const uint64_t keyBufferLength =
+ ReadCompressedNumber(&blobDataIter, blobDataEnd);
+
+ if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
+ NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
+ NS_WARN_IF(blobDataIter + keyBufferLength > blobDataEnd)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ nsCString keyBuffer(reinterpret_cast<const char*>(blobDataIter),
+ uint32_t(keyBufferLength));
+ blobDataIter += keyBufferLength;
+
+ IndexDataValue idv(indexId, unique, Key(keyBuffer));
+
+ if (blobDataIter < blobDataEnd) {
+ // Read either a sort key buffer length or an index id.
+ uint64_t maybeIndexId = ReadCompressedNumber(&blobDataIter, blobDataEnd);
+
+ // Locale-aware indexes haven't been around long enough to have any users,
+ // we can safely assume all sort key buffer lengths will be zero.
+ if (maybeIndexId != 0) {
+ if (maybeIndexId % 2) {
+ unique = true;
+ maybeIndexId--;
+ } else {
+ unique = false;
+ }
+ indexId = maybeIndexId/2;
+ nextIndexIdAlreadyRead = true;
+ }
+ }
+
+ if (NS_WARN_IF(!aIndexValues.InsertElementSorted(idv, fallible))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ MOZ_ASSERT(blobDataIter == blobDataEnd);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UpgradeIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult)
+{
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeIndexDataValuesFunction::OnFunctionCall",
+ js::ProfileEntry::Category::STORAGE);
+
+ uint32_t argc;
+ nsresult rv = aArguments->GetNumEntries(&argc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (argc != 1) {
+ NS_WARNING("Don't call me with the wrong number of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t type;
+ rv = aArguments->GetTypeOfIndex(0, &type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (type != mozIStorageStatement::VALUE_TYPE_BLOB) {
+ NS_WARNING("Don't call me with the wrong type of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const uint8_t* oldBlob;
+ uint32_t oldBlobLength;
+ rv = aArguments->GetSharedBlob(0, &oldBlobLength, &oldBlob);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ AutoTArray<IndexDataValue, 32> oldIdv;
+ rv = ReadOldCompressedIDVFromBlob(oldBlob, oldBlobLength, oldIdv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ UniqueFreePtr<uint8_t> newIdv;
+ uint32_t newIdvLength;
+ rv = MakeCompressedIndexDataValues(oldIdv, newIdv, &newIdvLength);
+
+ std::pair<uint8_t*, int> data(newIdv.release(), newIdvLength);
+
+ nsCOMPtr<nsIVariant> result = new storage::AdoptedBlobVariant(data);
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom20_0To21_0(mozIStorageConnection* aConnection)
+{
+ // This should have been part of the 18 to 19 upgrade, where we changed the
+ // layout of the index_data_values blobs but didn't upgrade the existing data.
+ // See bug 1202788.
+
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom20_0To21_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ RefPtr<UpgradeIndexDataValuesFunction> function =
+ new UpgradeIndexDataValuesFunction();
+
+ NS_NAMED_LITERAL_CSTRING(functionName, "upgrade_idv");
+
+ nsresult rv = aConnection->CreateFunction(functionName, 1, function);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE object_data "
+ "SET index_data_values = upgrade_idv(index_data_values) "
+ "WHERE index_data_values IS NOT NULL;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->RemoveFunction(functionName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(21, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom21_0To22_0(mozIStorageConnection* aConnection)
+{
+ // The only change between 21 and 22 was a different structured clone format,
+ // but it's backwards-compatible.
+ nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(22, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom22_0To23_0(mozIStorageConnection* aConnection,
+ const nsACString& aOrigin)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeSchemaFrom22_0To23_0",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE database "
+ "SET origin = :origin;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->SetSchemaVersion(MakeSchemaVersion(23, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom23_0To24_0(mozIStorageConnection* aConnection)
+{
+ // The only change between 23 and 24 was a different structured clone format,
+ // but it's backwards-compatible.
+ nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(24, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeSchemaFrom24_0To25_0(mozIStorageConnection* aConnection)
+{
+ // The changes between 24 and 25 were an upgraded snappy library, a different
+ // structured clone format and a different file_ds format. But everything is
+ // backwards-compatible.
+ nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(25, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GetDatabaseFileURL(nsIFile* aDatabaseFile,
+ PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ uint32_t aTelemetryId,
+ nsIFileURL** aResult)
+{
+ MOZ_ASSERT(aDatabaseFile);
+ MOZ_ASSERT(aResult);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIProtocolHandler> protocolHandler(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file", &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler(
+ do_QueryInterface(protocolHandler, &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = fileHandler->NewFileURI(aDatabaseFile, getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
+ MOZ_ASSERT(fileUrl);
+
+ nsAutoCString type;
+ PersistenceTypeToText(aPersistenceType, type);
+
+ nsAutoCString telemetryFilenameClause;
+ if (aTelemetryId) {
+ telemetryFilenameClause.AssignLiteral("&telemetryFilename=indexedDB-");
+ telemetryFilenameClause.AppendInt(aTelemetryId);
+ telemetryFilenameClause.AppendLiteral(".sqlite");
+ }
+
+ rv = fileUrl->SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type +
+ NS_LITERAL_CSTRING("&group=") + aGroup +
+ NS_LITERAL_CSTRING("&origin=") + aOrigin +
+ NS_LITERAL_CSTRING("&cache=private") +
+ telemetryFilenameClause);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ fileUrl.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+SetDefaultPragmas(mozIStorageConnection* aConnection)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aConnection);
+
+ static const char kBuiltInPragmas[] =
+ // We use foreign keys in DEBUG builds only because there is a performance
+ // cost to using them.
+ "PRAGMA foreign_keys = "
+#ifdef DEBUG
+ "ON"
+#else
+ "OFF"
+#endif
+ ";"
+
+ // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
+ // instead it fires only the insert trigger. This confuses the update
+ // refcount function. This behavior changes with enabled recursive triggers,
+ // so the statement fires the delete trigger first and then the insert
+ // trigger.
+ "PRAGMA recursive_triggers = ON;"
+
+ // We aggressively truncate the database file when idle so don't bother
+ // overwriting the WAL with 0 during active periods.
+ "PRAGMA secure_delete = OFF;"
+ ;
+
+ nsresult rv =
+ aConnection->ExecuteSimpleSQL(
+ nsDependentCString(kBuiltInPragmas,
+ LiteralStringLength(kBuiltInPragmas)));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString pragmaStmt;
+ pragmaStmt.AssignLiteral("PRAGMA synchronous = ");
+
+ if (IndexedDatabaseManager::FullSynchronous()) {
+ pragmaStmt.AppendLiteral("FULL");
+ } else {
+ pragmaStmt.AppendLiteral("NORMAL");
+ }
+ pragmaStmt.Append(';');
+
+ rv = aConnection->ExecuteSimpleSQL(pragmaStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifndef IDB_MOBILE
+ if (kSQLiteGrowthIncrement) {
+ // This is just an optimization so ignore the failure if the disk is
+ // currently too full.
+ rv = aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement,
+ EmptyCString());
+ if (rv != NS_ERROR_FILE_TOO_BIG && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+#endif // IDB_MOBILE
+
+ return NS_OK;
+}
+
+nsresult
+SetJournalMode(mozIStorageConnection* aConnection)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aConnection);
+
+ // Try enabling WAL mode. This can fail in various circumstances so we have to
+ // check the results here.
+ NS_NAMED_LITERAL_CSTRING(journalModeQueryStart, "PRAGMA journal_mode = ");
+ NS_NAMED_LITERAL_CSTRING(journalModeWAL, "wal");
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv =
+ aConnection->CreateStatement(journalModeQueryStart + journalModeWAL,
+ getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ nsCString journalMode;
+ rv = stmt->GetUTF8String(0, journalMode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (journalMode.Equals(journalModeWAL)) {
+ // WAL mode successfully enabled. Maybe set limits on its size here.
+ if (kMaxWALPages >= 0) {
+ nsAutoCString pageCount;
+ pageCount.AppendInt(kMaxWALPages);
+
+ rv = aConnection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = ") + pageCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ } else {
+ NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
+#ifdef IDB_MOBILE
+ rv = aConnection->ExecuteSimpleSQL(journalModeQueryStart +
+ NS_LITERAL_CSTRING("truncate"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+ }
+
+ return NS_OK;
+}
+
+template <class FileOrURLType>
+struct StorageOpenTraits;
+
+template <>
+struct StorageOpenTraits<nsIFileURL*>
+{
+ static nsresult
+ Open(mozIStorageService* aStorageService,
+ nsIFileURL* aFileURL,
+ mozIStorageConnection** aConnection)
+ {
+ return aStorageService->OpenDatabaseWithFileURL(aFileURL, aConnection);
+ }
+
+#ifdef DEBUG
+ static void
+ GetPath(nsIFileURL* aFileURL, nsCString& aPath)
+ {
+ MOZ_ALWAYS_SUCCEEDS(aFileURL->GetFileName(aPath));
+ }
+#endif
+};
+
+template <>
+struct StorageOpenTraits<nsIFile*>
+{
+ static nsresult
+ Open(mozIStorageService* aStorageService,
+ nsIFile* aFile,
+ mozIStorageConnection** aConnection)
+ {
+ return aStorageService->OpenUnsharedDatabase(aFile, aConnection);
+ }
+
+#ifdef DEBUG
+ static void
+ GetPath(nsIFile* aFile, nsCString& aPath)
+ {
+ nsString path;
+ MOZ_ALWAYS_SUCCEEDS(aFile->GetPath(path));
+
+ aPath.AssignWithConversion(path);
+ }
+#endif
+};
+
+template <template <class> class SmartPtr, class FileOrURLType>
+struct StorageOpenTraits<SmartPtr<FileOrURLType>>
+ : public StorageOpenTraits<FileOrURLType*>
+{ };
+
+template <class FileOrURLType>
+nsresult
+OpenDatabaseAndHandleBusy(mozIStorageService* aStorageService,
+ FileOrURLType aFileOrURL,
+ mozIStorageConnection** aConnection)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aStorageService);
+ MOZ_ASSERT(aFileOrURL);
+ MOZ_ASSERT(aConnection);
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ nsresult rv =
+ StorageOpenTraits<FileOrURLType>::Open(aStorageService,
+ aFileOrURL,
+ getter_AddRefs(connection));
+
+ if (rv == NS_ERROR_STORAGE_BUSY) {
+#ifdef DEBUG
+ {
+ nsCString path;
+ StorageOpenTraits<FileOrURLType>::GetPath(aFileOrURL, path);
+
+ nsPrintfCString message("Received NS_ERROR_STORAGE_BUSY when attempting "
+ "to open database '%s', retrying for up to 10 "
+ "seconds",
+ path.get());
+ NS_WARNING(message.get());
+ }
+#endif
+
+ // Another thread must be checkpointing the WAL. Wait up to 10 seconds for
+ // that to complete.
+ TimeStamp start = TimeStamp::NowLoRes();
+
+ while (true) {
+ PR_Sleep(PR_MillisecondsToInterval(100));
+
+ rv = StorageOpenTraits<FileOrURLType>::Open(aStorageService,
+ aFileOrURL,
+ getter_AddRefs(connection));
+ if (rv != NS_ERROR_STORAGE_BUSY ||
+ TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
+ break;
+ }
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ connection.forget(aConnection);
+ return NS_OK;
+}
+
+nsresult
+CreateStorageConnection(nsIFile* aDBFile,
+ nsIFile* aFMDirectory,
+ const nsAString& aName,
+ PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ uint32_t aTelemetryId,
+ mozIStorageConnection** aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDBFile);
+ MOZ_ASSERT(aFMDirectory);
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "CreateStorageConnection",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv;
+ bool exists;
+
+ if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
+ rv = aDBFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ NS_WARNING("Refusing to create database because disk space is low!");
+ return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+ }
+
+ nsCOMPtr<nsIFileURL> dbFileUrl;
+ rv = GetDatabaseFileURL(aDBFile,
+ aPersistenceType,
+ aGroup,
+ aOrigin,
+ aTelemetryId,
+ getter_AddRefs(dbFileUrl));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageService> ss =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ // If we're just opening the database during origin initialization, then
+ // we don't want to erase any files. The failure here will fail origin
+ // initialization too.
+ if (aName.IsVoid()) {
+ return rv;
+ }
+
+ // Nuke the database file.
+ rv = aDBFile->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aFMDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ bool isDirectory;
+ rv = aFMDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (NS_WARN_IF(!isDirectory)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ rv = aFMDirectory->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = SetDefaultPragmas(connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = connection->EnableModule(NS_LITERAL_CSTRING("filesystem"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Check to make sure that the database schema is correct.
+ int32_t schemaVersion;
+ rv = connection->GetSchemaVersion(&schemaVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Unknown schema will fail origin initialization too.
+ if (!schemaVersion && aName.IsVoid()) {
+ IDB_WARNING("Unable to open IndexedDB database, schema is not set!");
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ if (schemaVersion > kSQLiteSchemaVersion) {
+ IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ bool journalModeSet = false;
+
+ if (schemaVersion != kSQLiteSchemaVersion) {
+ const bool newDatabase = !schemaVersion;
+
+ if (newDatabase) {
+ // Set the page size first.
+ if (kSQLitePageSizeOverride) {
+ rv = connection->ExecuteSimpleSQL(
+ nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
+ );
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // We have to set the auto_vacuum mode before opening a transaction.
+ rv = connection->ExecuteSimpleSQL(
+#ifdef IDB_MOBILE
+ // Turn on full auto_vacuum mode to reclaim disk space on mobile
+ // devices (at the cost of some COMMIT speed).
+ NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
+#else
+ // Turn on incremental auto_vacuum mode on desktop builds.
+ NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
+#endif
+ );
+ if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+ // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
+ // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
+ rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = SetJournalMode(connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ journalModeSet = true;
+ } else {
+#ifdef DEBUG
+ // Disable foreign key support while upgrading. This has to be done before
+ // starting a transaction.
+ MOZ_ALWAYS_SUCCEEDS(
+ connection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;")));
+#endif
+ }
+
+ bool vacuumNeeded = false;
+
+ mozStorageTransaction transaction(connection, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ if (newDatabase) {
+ rv = CreateTables(connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)));
+ MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO database (name, origin) "
+ "VALUES (:name, :origin)"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ // This logic needs to change next time we change the schema!
+ static_assert(kSQLiteSchemaVersion == int32_t((25 << 4) + 0),
+ "Upgrade function needed due to schema version increase.");
+
+ while (schemaVersion != kSQLiteSchemaVersion) {
+ if (schemaVersion == 4) {
+ rv = UpgradeSchemaFrom4To5(connection);
+ } else if (schemaVersion == 5) {
+ rv = UpgradeSchemaFrom5To6(connection);
+ } else if (schemaVersion == 6) {
+ rv = UpgradeSchemaFrom6To7(connection);
+ } else if (schemaVersion == 7) {
+ rv = UpgradeSchemaFrom7To8(connection);
+ } else if (schemaVersion == 8) {
+ rv = UpgradeSchemaFrom8To9_0(connection);
+ vacuumNeeded = true;
+ } else if (schemaVersion == MakeSchemaVersion(9, 0)) {
+ rv = UpgradeSchemaFrom9_0To10_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(10, 0)) {
+ rv = UpgradeSchemaFrom10_0To11_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(11, 0)) {
+ rv = UpgradeSchemaFrom11_0To12_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(12, 0)) {
+ rv = UpgradeSchemaFrom12_0To13_0(connection, &vacuumNeeded);
+ } else if (schemaVersion == MakeSchemaVersion(13, 0)) {
+ rv = UpgradeSchemaFrom13_0To14_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(14, 0)) {
+ rv = UpgradeSchemaFrom14_0To15_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(15, 0)) {
+ rv = UpgradeSchemaFrom15_0To16_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(16, 0)) {
+ rv = UpgradeSchemaFrom16_0To17_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(17, 0)) {
+ rv = UpgradeSchemaFrom17_0To18_0(connection, aOrigin);
+ vacuumNeeded = true;
+ } else if (schemaVersion == MakeSchemaVersion(18, 0)) {
+ rv = UpgradeSchemaFrom18_0To19_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(19, 0)) {
+ rv = UpgradeSchemaFrom19_0To20_0(aFMDirectory, connection);
+ } else if (schemaVersion == MakeSchemaVersion(20, 0)) {
+ rv = UpgradeSchemaFrom20_0To21_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(21, 0)) {
+ rv = UpgradeSchemaFrom21_0To22_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(22, 0)) {
+ rv = UpgradeSchemaFrom22_0To23_0(connection, aOrigin);
+ } else if (schemaVersion == MakeSchemaVersion(23, 0)) {
+ rv = UpgradeSchemaFrom23_0To24_0(connection);
+ } else if (schemaVersion == MakeSchemaVersion(24, 0)) {
+ rv = UpgradeSchemaFrom24_0To25_0(connection);
+ } else {
+ IDB_WARNING("Unable to open IndexedDB database, no upgrade path is "
+ "available!");
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = connection->GetSchemaVersion(&schemaVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
+ }
+
+ rv = transaction.Commit();
+ if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+ // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
+ // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
+ rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ if (!newDatabase) {
+ // Re-enable foreign key support after doing a foreign key check.
+ nsCOMPtr<mozIStorageStatement> checkStmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ connection->CreateStatement(
+ NS_LITERAL_CSTRING("PRAGMA foreign_key_check;"),
+ getter_AddRefs(checkStmt)));
+
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(checkStmt->ExecuteStep(&hasResult));
+ MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");
+
+ MOZ_ALWAYS_SUCCEEDS(
+ connection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;")));
+ }
+#endif
+
+ if (kSQLitePageSizeOverride && !newDatabase) {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+ "PRAGMA page_size;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ int32_t pageSize;
+ rv = stmt->GetInt32(0, &pageSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
+
+ if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
+ // We must not be in WAL journal mode to change the page size.
+ rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA journal_mode = DELETE;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+ "PRAGMA journal_mode;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ nsCString journalMode;
+ rv = stmt->GetUTF8String(0, journalMode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (journalMode.EqualsLiteral("delete")) {
+ // Successfully set to rollback journal mode so changing the page size
+ // is possible with a VACUUM.
+ rv = connection->ExecuteSimpleSQL(
+ nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
+ );
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We will need to VACUUM in order to change the page size.
+ vacuumNeeded = true;
+ } else {
+ NS_WARNING("Failed to set journal_mode for database, unable to "
+ "change the page size!");
+ }
+ }
+ }
+
+ if (vacuumNeeded) {
+ rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (newDatabase || vacuumNeeded) {
+ if (journalModeSet) {
+ // Make sure we checkpoint to get an accurate file size.
+ rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA wal_checkpoint(FULL);"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ int64_t fileSize;
+ rv = aDBFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(fileSize > 0);
+
+ PRTime vacuumTime = PR_Now();
+ MOZ_ASSERT(vacuumTime);
+
+ nsCOMPtr<mozIStorageStatement> vacuumTimeStmt;
+ rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE database "
+ "SET last_vacuum_time = :time"
+ ", last_vacuum_size = :size;"
+ ), getter_AddRefs(vacuumTimeStmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("time"),
+ vacuumTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("size"),
+ fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = vacuumTimeStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ if (!journalModeSet) {
+ rv = SetJournalMode(connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ connection.forget(aConnection);
+ return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+GetFileForPath(const nsAString& aPath)
+{
+ MOZ_ASSERT(!aPath.IsEmpty());
+
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ if (NS_WARN_IF(!file)) {
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(file->InitWithPath(aPath)))) {
+ return nullptr;
+ }
+
+ return file.forget();
+}
+
+nsresult
+GetStorageConnection(nsIFile* aDatabaseFile,
+ PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ uint32_t aTelemetryId,
+ mozIStorageConnection** aConnection)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aDatabaseFile);
+ MOZ_ASSERT(aConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "GetStorageConnection",
+ js::ProfileEntry::Category::STORAGE);
+
+ bool exists;
+ nsresult rv = aDatabaseFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!exists)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ nsCOMPtr<nsIFileURL> dbFileUrl;
+ rv = GetDatabaseFileURL(aDatabaseFile,
+ aPersistenceType,
+ aGroup,
+ aOrigin,
+ aTelemetryId,
+ getter_AddRefs(dbFileUrl));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageService> ss =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = SetDefaultPragmas(connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = SetJournalMode(connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ connection.forget(aConnection);
+ return NS_OK;
+}
+
+nsresult
+GetStorageConnection(const nsAString& aDatabaseFilePath,
+ PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ uint32_t aTelemetryId,
+ mozIStorageConnection** aConnection)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
+ MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, NS_LITERAL_STRING(".sqlite")));
+ MOZ_ASSERT(aConnection);
+
+ nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
+ if (NS_WARN_IF(!dbFile)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ return GetStorageConnection(dbFile,
+ aPersistenceType,
+ aGroup,
+ aOrigin,
+ aTelemetryId,
+ aConnection);
+}
+
+/*******************************************************************************
+ * ConnectionPool declarations
+ ******************************************************************************/
+
+class DatabaseConnection final
+{
+ friend class ConnectionPool;
+
+ enum class CheckpointMode
+ {
+ Full,
+ Restart,
+ Truncate
+ };
+
+public:
+ class AutoSavepoint;
+ class CachedStatement;
+ class UpdateRefcountFunction;
+
+private:
+ nsCOMPtr<mozIStorageConnection> mStorageConnection;
+ RefPtr<FileManager> mFileManager;
+ nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
+ mCachedStatements;
+ RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
+ RefPtr<QuotaObject> mQuotaObject;
+ RefPtr<QuotaObject> mJournalQuotaObject;
+ bool mInReadTransaction;
+ bool mInWriteTransaction;
+
+#ifdef DEBUG
+ uint32_t mDEBUGSavepointCount;
+ PRThread* mDEBUGThread;
+#endif
+
+public:
+ void
+ AssertIsOnConnectionThread() const
+ {
+ MOZ_ASSERT(mDEBUGThread);
+ MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGThread);
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection)
+
+ mozIStorageConnection*
+ GetStorageConnection() const
+ {
+ if (mStorageConnection) {
+ AssertIsOnConnectionThread();
+ return mStorageConnection;
+ }
+
+ return nullptr;
+ }
+
+ UpdateRefcountFunction*
+ GetUpdateRefcountFunction() const
+ {
+ AssertIsOnConnectionThread();
+
+ return mUpdateRefcountFunction;
+ }
+
+ nsresult
+ GetCachedStatement(const nsACString& aQuery,
+ CachedStatement* aCachedStatement);
+
+ nsresult
+ BeginWriteTransaction();
+
+ nsresult
+ CommitWriteTransaction();
+
+ void
+ RollbackWriteTransaction();
+
+ void
+ FinishWriteTransaction();
+
+ nsresult
+ StartSavepoint();
+
+ nsresult
+ ReleaseSavepoint();
+
+ nsresult
+ RollbackSavepoint();
+
+ nsresult
+ Checkpoint()
+ {
+ AssertIsOnConnectionThread();
+
+ return CheckpointInternal(CheckpointMode::Full);
+ }
+
+ void
+ DoIdleProcessing(bool aNeedsCheckpoint);
+
+ void
+ Close();
+
+ nsresult
+ DisableQuotaChecks();
+
+ void
+ EnableQuotaChecks();
+
+private:
+ DatabaseConnection(mozIStorageConnection* aStorageConnection,
+ FileManager* aFileManager);
+
+ ~DatabaseConnection();
+
+ nsresult
+ Init();
+
+ nsresult
+ CheckpointInternal(CheckpointMode aMode);
+
+ nsresult
+ GetFreelistCount(CachedStatement& aCachedStatement, uint32_t* aFreelistCount);
+
+ nsresult
+ ReclaimFreePagesWhileIdle(CachedStatement& aFreelistStatement,
+ CachedStatement& aRollbackStatement,
+ uint32_t aFreelistCount,
+ bool aNeedsCheckpoint,
+ bool* aFreedSomePages);
+
+ nsresult
+ GetFileSize(const nsAString& aPath, int64_t* aResult);
+};
+
+class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final
+{
+ DatabaseConnection* mConnection;
+#ifdef DEBUG
+ const TransactionBase* mDEBUGTransaction;
+#endif
+
+public:
+ AutoSavepoint();
+ ~AutoSavepoint();
+
+ nsresult
+ Start(const TransactionBase* aConnection);
+
+ nsresult
+ Commit();
+};
+
+class DatabaseConnection::CachedStatement final
+{
+ friend class DatabaseConnection;
+
+ nsCOMPtr<mozIStorageStatement> mStatement;
+ Maybe<mozStorageStatementScoper> mScoper;
+
+#ifdef DEBUG
+ DatabaseConnection* mDEBUGConnection;
+#endif
+
+public:
+ CachedStatement();
+ ~CachedStatement();
+
+ void
+ AssertIsOnConnectionThread() const
+ {
+#ifdef DEBUG
+ if (mDEBUGConnection) {
+ mDEBUGConnection->AssertIsOnConnectionThread();
+ }
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+#endif
+ }
+
+ operator mozIStorageStatement*() const;
+
+ mozIStorageStatement*
+ operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN;
+
+ void
+ Reset();
+
+private:
+ // Only called by DatabaseConnection.
+ void
+ Assign(DatabaseConnection* aConnection,
+ already_AddRefed<mozIStorageStatement> aStatement);
+
+ // No funny business allowed.
+ CachedStatement(const CachedStatement&) = delete;
+ CachedStatement& operator=(const CachedStatement&) = delete;
+};
+
+class DatabaseConnection::UpdateRefcountFunction final
+ : public mozIStorageFunction
+{
+ class DatabaseUpdateFunction;
+ class FileInfoEntry;
+
+ enum class UpdateType
+ {
+ Increment,
+ Decrement
+ };
+
+ DatabaseConnection* mConnection;
+ FileManager* mFileManager;
+ nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
+ nsDataHashtable<nsUint64HashKey, FileInfoEntry*> mSavepointEntriesIndex;
+
+ nsTArray<int64_t> mJournalsToCreateBeforeCommit;
+ nsTArray<int64_t> mJournalsToRemoveAfterCommit;
+ nsTArray<int64_t> mJournalsToRemoveAfterAbort;
+
+ bool mInSavepoint;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ UpdateRefcountFunction(DatabaseConnection* aConnection,
+ FileManager* aFileManager);
+
+ nsresult
+ WillCommit();
+
+ void
+ DidCommit();
+
+ void
+ DidAbort();
+
+ void
+ StartSavepoint();
+
+ void
+ ReleaseSavepoint();
+
+ void
+ RollbackSavepoint();
+
+ void
+ Reset();
+
+private:
+ ~UpdateRefcountFunction()
+ { }
+
+ nsresult
+ ProcessValue(mozIStorageValueArray* aValues,
+ int32_t aIndex,
+ UpdateType aUpdateType);
+
+ nsresult
+ CreateJournals();
+
+ nsresult
+ RemoveJournals(const nsTArray<int64_t>& aJournals);
+};
+
+class DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction final
+{
+ CachedStatement mUpdateStatement;
+ CachedStatement mSelectStatement;
+ CachedStatement mInsertStatement;
+
+ UpdateRefcountFunction* mFunction;
+
+ nsresult mErrorCode;
+
+public:
+ explicit
+ DatabaseUpdateFunction(UpdateRefcountFunction* aFunction)
+ : mFunction(aFunction)
+ , mErrorCode(NS_OK)
+ {
+ MOZ_COUNT_CTOR(
+ DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction);
+ }
+
+ ~DatabaseUpdateFunction()
+ {
+ MOZ_COUNT_DTOR(
+ DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction);
+ }
+
+ bool
+ Update(int64_t aId, int32_t aDelta);
+
+ nsresult
+ ErrorCode() const
+ {
+ return mErrorCode;
+ }
+
+private:
+ nsresult
+ UpdateInternal(int64_t aId, int32_t aDelta);
+};
+
+class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final
+{
+ friend class UpdateRefcountFunction;
+
+ RefPtr<FileInfo> mFileInfo;
+ int32_t mDelta;
+ int32_t mSavepointDelta;
+
+public:
+ explicit
+ FileInfoEntry(FileInfo* aFileInfo)
+ : mFileInfo(aFileInfo)
+ , mDelta(0)
+ , mSavepointDelta(0)
+ {
+ MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
+ }
+
+ ~FileInfoEntry()
+ {
+ MOZ_COUNT_DTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
+ }
+};
+
+class ConnectionPool final
+{
+public:
+ class FinishCallback;
+
+private:
+ class ConnectionRunnable;
+ class CloseConnectionRunnable;
+ struct DatabaseInfo;
+ struct DatabasesCompleteCallback;
+ class FinishCallbackWrapper;
+ class IdleConnectionRunnable;
+ struct IdleDatabaseInfo;
+ struct IdleResource;
+ struct IdleThreadInfo;
+ struct ThreadInfo;
+ class ThreadRunnable;
+ class TransactionInfo;
+ struct TransactionInfoPair;
+
+ // This mutex guards mDatabases, see below.
+ Mutex mDatabasesMutex;
+
+ nsTArray<IdleThreadInfo> mIdleThreads;
+ nsTArray<IdleDatabaseInfo> mIdleDatabases;
+ nsTArray<DatabaseInfo*> mDatabasesPerformingIdleMaintenance;
+ nsCOMPtr<nsITimer> mIdleTimer;
+ TimeStamp mTargetIdleTime;
+
+ // Only modifed on the owning thread, but read on multiple threads. Therefore
+ // all modifications and all reads off the owning thread must be protected by
+ // mDatabasesMutex.
+ nsClassHashtable<nsCStringHashKey, DatabaseInfo> mDatabases;
+
+ nsClassHashtable<nsUint64HashKey, TransactionInfo> mTransactions;
+ nsTArray<TransactionInfo*> mQueuedTransactions;
+
+ nsTArray<nsAutoPtr<DatabasesCompleteCallback>> mCompleteCallbacks;
+
+ uint64_t mNextTransactionId;
+ uint32_t mTotalThreadCount;
+ bool mShutdownRequested;
+ bool mShutdownComplete;
+
+#ifdef DEBUG
+ PRThread* mDEBUGOwningThread;
+#endif
+
+public:
+ ConnectionPool();
+
+ void
+ AssertIsOnOwningThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ nsresult
+ GetOrCreateConnection(const Database* aDatabase,
+ DatabaseConnection** aConnection);
+
+ uint64_t
+ Start(const nsID& aBackgroundChildLoggingId,
+ const nsACString& aDatabaseId,
+ int64_t aLoggingSerialNumber,
+ const nsTArray<nsString>& aObjectStoreNames,
+ bool aIsWriteTransaction,
+ TransactionDatabaseOperationBase* aTransactionOp);
+
+ void
+ Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable);
+
+ void
+ Finish(uint64_t aTransactionId, FinishCallback* aCallback);
+
+ void
+ CloseDatabaseWhenIdle(const nsACString& aDatabaseId)
+ {
+ Unused << CloseDatabaseWhenIdleInternal(aDatabaseId);
+ }
+
+ void
+ WaitForDatabasesToComplete(nsTArray<nsCString>&& aDatabaseIds,
+ nsIRunnable* aCallback);
+
+ void
+ Shutdown();
+
+ NS_INLINE_DECL_REFCOUNTING(ConnectionPool)
+
+private:
+ ~ConnectionPool();
+
+ static void
+ IdleTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ void
+ Cleanup();
+
+ void
+ AdjustIdleTimer();
+
+ void
+ CancelIdleTimer();
+
+ void
+ ShutdownThread(ThreadInfo& aThreadInfo);
+
+ void
+ CloseIdleDatabases();
+
+ void
+ ShutdownIdleThreads();
+
+ bool
+ ScheduleTransaction(TransactionInfo* aTransactionInfo,
+ bool aFromQueuedTransactions);
+
+ void
+ NoteFinishedTransaction(uint64_t aTransactionId);
+
+ void
+ ScheduleQueuedTransactions(ThreadInfo& aThreadInfo);
+
+ void
+ NoteIdleDatabase(DatabaseInfo* aDatabaseInfo);
+
+ void
+ NoteClosedDatabase(DatabaseInfo* aDatabaseInfo);
+
+ bool
+ MaybeFireCallback(DatabasesCompleteCallback* aCallback);
+
+ void
+ PerformIdleDatabaseMaintenance(DatabaseInfo* aDatabaseInfo);
+
+ void
+ CloseDatabase(DatabaseInfo* aDatabaseInfo);
+
+ bool
+ CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId);
+};
+
+class ConnectionPool::ConnectionRunnable
+ : public Runnable
+{
+protected:
+ DatabaseInfo* mDatabaseInfo;
+ nsCOMPtr<nsIEventTarget> mOwningThread;
+
+ explicit
+ ConnectionRunnable(DatabaseInfo* aDatabaseInfo);
+
+ virtual
+ ~ConnectionRunnable()
+ { }
+};
+
+class ConnectionPool::IdleConnectionRunnable final
+ : public ConnectionRunnable
+{
+ bool mNeedsCheckpoint;
+
+public:
+ IdleConnectionRunnable(DatabaseInfo* aDatabaseInfo, bool aNeedsCheckpoint)
+ : ConnectionRunnable(aDatabaseInfo)
+ , mNeedsCheckpoint(aNeedsCheckpoint)
+ { }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~IdleConnectionRunnable()
+ { }
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class ConnectionPool::CloseConnectionRunnable final
+ : public ConnectionRunnable
+{
+public:
+ explicit
+ CloseConnectionRunnable(DatabaseInfo* aDatabaseInfo)
+ : ConnectionRunnable(aDatabaseInfo)
+ { }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~CloseConnectionRunnable()
+ { }
+
+ NS_DECL_NSIRUNNABLE
+};
+
+struct ConnectionPool::ThreadInfo
+{
+ nsCOMPtr<nsIThread> mThread;
+ RefPtr<ThreadRunnable> mRunnable;
+
+ ThreadInfo();
+
+ explicit
+ ThreadInfo(const ThreadInfo& aOther);
+
+ ~ThreadInfo();
+};
+
+struct ConnectionPool::DatabaseInfo final
+{
+ friend class nsAutoPtr<DatabaseInfo>;
+
+ RefPtr<ConnectionPool> mConnectionPool;
+ const nsCString mDatabaseId;
+ RefPtr<DatabaseConnection> mConnection;
+ nsClassHashtable<nsStringHashKey, TransactionInfoPair> mBlockingTransactions;
+ nsTArray<TransactionInfo*> mTransactionsScheduledDuringClose;
+ nsTArray<TransactionInfo*> mScheduledWriteTransactions;
+ TransactionInfo* mRunningWriteTransaction;
+ ThreadInfo mThreadInfo;
+ uint32_t mReadTransactionCount;
+ uint32_t mWriteTransactionCount;
+ bool mNeedsCheckpoint;
+ bool mIdle;
+ bool mCloseOnIdle;
+ bool mClosing;
+
+#ifdef DEBUG
+ PRThread* mDEBUGConnectionThread;
+#endif
+
+ DatabaseInfo(ConnectionPool* aConnectionPool,
+ const nsACString& aDatabaseId);
+
+ void
+ AssertIsOnConnectionThread() const
+ {
+ MOZ_ASSERT(mDEBUGConnectionThread);
+ MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGConnectionThread);
+ }
+
+ uint64_t
+ TotalTransactionCount() const
+ {
+ return mReadTransactionCount + mWriteTransactionCount;
+ }
+
+private:
+ ~DatabaseInfo();
+
+ DatabaseInfo(const DatabaseInfo&) = delete;
+ DatabaseInfo& operator=(const DatabaseInfo&) = delete;
+};
+
+struct ConnectionPool::DatabasesCompleteCallback final
+{
+ friend class nsAutoPtr<DatabasesCompleteCallback>;
+
+ nsTArray<nsCString> mDatabaseIds;
+ nsCOMPtr<nsIRunnable> mCallback;
+
+ DatabasesCompleteCallback(nsTArray<nsCString>&& aDatabaseIds,
+ nsIRunnable* aCallback);
+
+private:
+ ~DatabasesCompleteCallback();
+};
+
+class NS_NO_VTABLE ConnectionPool::FinishCallback
+ : public nsIRunnable
+{
+public:
+ // Called on the owning thread before any additional transactions are
+ // unblocked.
+ virtual void
+ TransactionFinishedBeforeUnblock() = 0;
+
+ // Called on the owning thread after additional transactions may have been
+ // unblocked.
+ virtual void
+ TransactionFinishedAfterUnblock() = 0;
+
+protected:
+ FinishCallback()
+ { }
+
+ virtual ~FinishCallback()
+ { }
+};
+
+class ConnectionPool::FinishCallbackWrapper final
+ : public Runnable
+{
+ RefPtr<ConnectionPool> mConnectionPool;
+ RefPtr<FinishCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mOwningThread;
+ uint64_t mTransactionId;
+ bool mHasRunOnce;
+
+public:
+ FinishCallbackWrapper(ConnectionPool* aConnectionPool,
+ uint64_t aTransactionId,
+ FinishCallback* aCallback);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~FinishCallbackWrapper();
+
+ NS_DECL_NSIRUNNABLE
+};
+
+struct ConnectionPool::IdleResource
+{
+ TimeStamp mIdleTime;
+
+protected:
+ explicit
+ IdleResource(const TimeStamp& aIdleTime);
+
+ explicit
+ IdleResource(const IdleResource& aOther) = delete;
+
+ ~IdleResource();
+};
+
+struct ConnectionPool::IdleDatabaseInfo final
+ : public IdleResource
+{
+ DatabaseInfo* mDatabaseInfo;
+
+public:
+ MOZ_IMPLICIT
+ IdleDatabaseInfo(DatabaseInfo* aDatabaseInfo);
+
+ explicit
+ IdleDatabaseInfo(const IdleDatabaseInfo& aOther) = delete;
+
+ ~IdleDatabaseInfo();
+
+ bool
+ operator==(const IdleDatabaseInfo& aOther) const
+ {
+ return mDatabaseInfo == aOther.mDatabaseInfo;
+ }
+
+ bool
+ operator<(const IdleDatabaseInfo& aOther) const
+ {
+ return mIdleTime < aOther.mIdleTime;
+ }
+};
+
+struct ConnectionPool::IdleThreadInfo final
+ : public IdleResource
+{
+ ThreadInfo mThreadInfo;
+
+public:
+ // Boo, this is needed because nsTArray::InsertElementSorted() doesn't yet
+ // work with rvalue references.
+ MOZ_IMPLICIT
+ IdleThreadInfo(const ThreadInfo& aThreadInfo);
+
+ explicit
+ IdleThreadInfo(const IdleThreadInfo& aOther) = delete;
+
+ ~IdleThreadInfo();
+
+ bool
+ operator==(const IdleThreadInfo& aOther) const
+ {
+ return mThreadInfo.mRunnable == aOther.mThreadInfo.mRunnable &&
+ mThreadInfo.mThread == aOther.mThreadInfo.mThread;
+ }
+
+ bool
+ operator<(const IdleThreadInfo& aOther) const
+ {
+ return mIdleTime < aOther.mIdleTime;
+ }
+};
+
+class ConnectionPool::ThreadRunnable final
+ : public Runnable
+{
+ // Only touched on the background thread.
+ static uint32_t sNextSerialNumber;
+
+ // Set at construction for logging.
+ const uint32_t mSerialNumber;
+
+ // These two values are only modified on the connection thread.
+ bool mFirstRun;
+ bool mContinueRunning;
+
+public:
+ ThreadRunnable();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ uint32_t
+ SerialNumber() const
+ {
+ return mSerialNumber;
+ }
+
+private:
+ ~ThreadRunnable();
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class ConnectionPool::TransactionInfo final
+{
+ friend class nsAutoPtr<TransactionInfo>;
+
+ nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlocking;
+ nsTArray<TransactionInfo*> mBlockingOrdered;
+
+public:
+ DatabaseInfo* mDatabaseInfo;
+ const nsID mBackgroundChildLoggingId;
+ const nsCString mDatabaseId;
+ const uint64_t mTransactionId;
+ const int64_t mLoggingSerialNumber;
+ const nsTArray<nsString> mObjectStoreNames;
+ nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlockedOn;
+ nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
+ const bool mIsWriteTransaction;
+ bool mRunning;
+
+#ifdef DEBUG
+ bool mFinished;
+#endif
+
+ TransactionInfo(DatabaseInfo* aDatabaseInfo,
+ const nsID& aBackgroundChildLoggingId,
+ const nsACString& aDatabaseId,
+ uint64_t aTransactionId,
+ int64_t aLoggingSerialNumber,
+ const nsTArray<nsString>& aObjectStoreNames,
+ bool aIsWriteTransaction,
+ TransactionDatabaseOperationBase* aTransactionOp);
+
+ void
+ AddBlockingTransaction(TransactionInfo* aTransactionInfo);
+
+ void
+ RemoveBlockingTransactions();
+
+private:
+ ~TransactionInfo();
+
+ void
+ MaybeUnblock(TransactionInfo* aTransactionInfo);
+};
+
+struct ConnectionPool::TransactionInfoPair final
+{
+ friend class nsAutoPtr<TransactionInfoPair>;
+
+ // Multiple reading transactions can block future writes.
+ nsTArray<TransactionInfo*> mLastBlockingWrites;
+ // But only a single writing transaction can block future reads.
+ TransactionInfo* mLastBlockingReads;
+
+ TransactionInfoPair();
+
+private:
+ ~TransactionInfoPair();
+};
+
+/*******************************************************************************
+ * Actor class declarations
+ ******************************************************************************/
+
+class DatabaseOperationBase
+ : public Runnable
+ , public mozIStorageProgressHandler
+{
+ friend class UpgradeFileIdsFunction;
+
+protected:
+ class AutoSetProgressHandler;
+
+ typedef nsDataHashtable<nsUint64HashKey, bool> UniqueIndexTable;
+
+ nsCOMPtr<nsIEventTarget> mOwningThread;
+ const nsID mBackgroundChildLoggingId;
+ const uint64_t mLoggingSerialNumber;
+ nsresult mResultCode;
+
+private:
+ Atomic<bool> mOperationMayProceed;
+ bool mActorDestroyed;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool
+ IsOnOwningThread() const
+ {
+ MOZ_ASSERT(mOwningThread);
+
+ bool current;
+ return NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)) && current;
+ }
+
+ void
+ AssertIsOnOwningThread() const
+ {
+ MOZ_ASSERT(IsOnBackgroundThread());
+ MOZ_ASSERT(IsOnOwningThread());
+ }
+
+ void
+ NoteActorDestroyed()
+ {
+ AssertIsOnOwningThread();
+
+ mActorDestroyed = true;
+ mOperationMayProceed = false;
+ }
+
+ bool
+ IsActorDestroyed() const
+ {
+ AssertIsOnOwningThread();
+
+ return mActorDestroyed;
+ }
+
+ // May be called on any thread, but you should call IsActorDestroyed() if
+ // you know you're on the background thread because it is slightly faster.
+ bool
+ OperationMayProceed() const
+ {
+ return mOperationMayProceed;
+ }
+
+ const nsID&
+ BackgroundChildLoggingId() const
+ {
+ return mBackgroundChildLoggingId;
+ }
+
+ uint64_t
+ LoggingSerialNumber() const
+ {
+ return mLoggingSerialNumber;
+ }
+
+ nsresult
+ ResultCode() const
+ {
+ return mResultCode;
+ }
+
+ void
+ SetFailureCode(nsresult aErrorCode)
+ {
+ MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+ MOZ_ASSERT(NS_FAILED(aErrorCode));
+
+ mResultCode = aErrorCode;
+ }
+
+protected:
+ DatabaseOperationBase(const nsID& aBackgroundChildLoggingId,
+ uint64_t aLoggingSerialNumber)
+ : mOwningThread(NS_GetCurrentThread())
+ , mBackgroundChildLoggingId(aBackgroundChildLoggingId)
+ , mLoggingSerialNumber(aLoggingSerialNumber)
+ , mResultCode(NS_OK)
+ , mOperationMayProceed(true)
+ , mActorDestroyed(false)
+ {
+ AssertIsOnOwningThread();
+ }
+
+ virtual
+ ~DatabaseOperationBase()
+ {
+ MOZ_ASSERT(mActorDestroyed);
+ }
+
+ static void
+ GetBindingClauseForKeyRange(const SerializedKeyRange& aKeyRange,
+ const nsACString& aKeyColumnName,
+ nsAutoCString& aBindingClause);
+
+ static uint64_t
+ ReinterpretDoubleAsUInt64(double aDouble);
+
+ static nsresult
+ GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement,
+ uint32_t aDataIndex,
+ uint32_t aFileIdsIndex,
+ FileManager* aFileManager,
+ StructuredCloneReadInfo* aInfo)
+ {
+ return GetStructuredCloneReadInfoFromSource(aStatement,
+ aDataIndex,
+ aFileIdsIndex,
+ aFileManager,
+ aInfo);
+ }
+
+ static nsresult
+ GetStructuredCloneReadInfoFromValueArray(mozIStorageValueArray* aValues,
+ uint32_t aDataIndex,
+ uint32_t aFileIdsIndex,
+ FileManager* aFileManager,
+ StructuredCloneReadInfo* aInfo)
+ {
+ return GetStructuredCloneReadInfoFromSource(aValues,
+ aDataIndex,
+ aFileIdsIndex,
+ aFileManager,
+ aInfo);
+ }
+
+ static nsresult
+ BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
+ mozIStorageStatement* aStatement);
+
+ static nsresult
+ BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
+ mozIStorageStatement* aStatement,
+ const nsCString& aLocale);
+
+ static void
+ AppendConditionClause(const nsACString& aColumnName,
+ const nsACString& aArgName,
+ bool aLessThan,
+ bool aEquals,
+ nsAutoCString& aResult);
+
+ static nsresult
+ GetUniqueIndexTableForObjectStore(
+ TransactionBase* aTransaction,
+ int64_t aObjectStoreId,
+ Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable);
+
+ static nsresult
+ IndexDataValuesFromUpdateInfos(const nsTArray<IndexUpdateInfo>& aUpdateInfos,
+ const UniqueIndexTable& aUniqueIndexTable,
+ nsTArray<IndexDataValue>& aIndexValues);
+
+ static nsresult
+ InsertIndexTableRows(DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ const Key& aObjectStoreKey,
+ const FallibleTArray<IndexDataValue>& aIndexValues);
+
+ static nsresult
+ DeleteIndexDataTableRows(DatabaseConnection* aConnection,
+ const Key& aObjectStoreKey,
+ const FallibleTArray<IndexDataValue>& aIndexValues);
+
+ static nsresult
+ DeleteObjectStoreDataTableRowsWithIndexes(DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ const OptionalKeyRange& aKeyRange);
+
+ static nsresult
+ UpdateIndexValues(DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ const Key& aObjectStoreKey,
+ const FallibleTArray<IndexDataValue>& aIndexValues);
+
+ static nsresult
+ ObjectStoreHasIndexes(DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ bool* aHasIndexes);
+
+private:
+ template <typename T>
+ static nsresult
+ GetStructuredCloneReadInfoFromSource(T* aSource,
+ uint32_t aDataIndex,
+ uint32_t aFileIdsIndex,
+ FileManager* aFileManager,
+ StructuredCloneReadInfo* aInfo);
+
+ static nsresult
+ GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
+ uint32_t aBlobDataLength,
+ FileManager* aFileManager,
+ const nsAString& aFileIds,
+ StructuredCloneReadInfo* aInfo);
+
+ static nsresult
+ GetStructuredCloneReadInfoFromExternalBlob(uint64_t aIntData,
+ FileManager* aFileManager,
+ const nsAString& aFileIds,
+ StructuredCloneReadInfo* aInfo);
+
+ // Not to be overridden by subclasses.
+ NS_DECL_MOZISTORAGEPROGRESSHANDLER
+};
+
+class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final
+{
+ mozIStorageConnection* mConnection;
+#ifdef DEBUG
+ DatabaseOperationBase* mDEBUGDatabaseOp;
+#endif
+
+public:
+ AutoSetProgressHandler();
+
+ ~AutoSetProgressHandler();
+
+ nsresult
+ Register(mozIStorageConnection* aConnection,
+ DatabaseOperationBase* aDatabaseOp);
+};
+
+class TransactionDatabaseOperationBase
+ : public DatabaseOperationBase
+{
+ enum class InternalState
+ {
+ Initial,
+ DatabaseWork,
+ SendingPreprocess,
+ WaitingForContinue,
+ SendingResults,
+ Completed
+ };
+
+ RefPtr<TransactionBase> mTransaction;
+ const int64_t mTransactionLoggingSerialNumber;
+ InternalState mInternalState;
+ const bool mTransactionIsAborted;
+
+public:
+ void
+ AssertIsOnConnectionThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ uint64_t
+ StartOnConnectionPool(const nsID& aBackgroundChildLoggingId,
+ const nsACString& aDatabaseId,
+ int64_t aLoggingSerialNumber,
+ const nsTArray<nsString>& aObjectStoreNames,
+ bool aIsWriteTransaction);
+
+ void
+ DispatchToConnectionPool();
+
+ TransactionBase*
+ Transaction() const
+ {
+ MOZ_ASSERT(mTransaction);
+
+ return mTransaction;
+ }
+
+ void
+ NoteContinueReceived();
+
+ // May be overridden by subclasses if they need to perform work on the
+ // background thread before being dispatched. Returning false will kill the
+ // child actors and prevent dispatch.
+ virtual bool
+ Init(TransactionBase* aTransaction);
+
+ // This callback will be called on the background thread before releasing the
+ // final reference to this request object. Subclasses may perform any
+ // additional cleanup here but must always call the base class implementation.
+ virtual void
+ Cleanup();
+
+protected:
+ explicit
+ TransactionDatabaseOperationBase(TransactionBase* aTransaction);
+
+ TransactionDatabaseOperationBase(TransactionBase* aTransaction,
+ uint64_t aLoggingSerialNumber);
+
+ virtual
+ ~TransactionDatabaseOperationBase();
+
+ virtual void
+ RunOnConnectionThread();
+
+ // Must be overridden in subclasses. Called on the target thread to allow the
+ // subclass to perform necessary database or file operations. A successful
+ // return value will trigger a SendSuccessResult callback on the background
+ // thread while a failure value will trigger a SendFailureResult callback.
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) = 0;
+
+ // May be overriden in subclasses. Called on the background thread to decide
+ // if the subclass needs to send any preprocess info to the child actor.
+ virtual bool
+ HasPreprocessInfo();
+
+ // May be overriden in subclasses. Called on the background thread to allow
+ // the subclass to serialize its preprocess info and send it to the child
+ // actor. A successful return value will trigger a wait for a
+ // NoteContinueReceived callback on the background thread while a failure
+ // value will trigger a SendFailureResult callback.
+ virtual nsresult
+ SendPreprocessInfo();
+
+ // Must be overridden in subclasses. Called on the background thread to allow
+ // the subclass to serialize its results and send them to the child actor. A
+ // failed return value will trigger a SendFailureResult callback.
+ virtual nsresult
+ SendSuccessResult() = 0;
+
+ // Must be overridden in subclasses. Called on the background thread to allow
+ // the subclass to send its failure code. Returning false will cause the
+ // transaction to be aborted with aResultCode. Returning true will not cause
+ // the transaction to be aborted.
+ virtual bool
+ SendFailureResult(nsresult aResultCode) = 0;
+
+private:
+ void
+ SendToConnectionPool();
+
+ void
+ SendPreprocess();
+
+ void
+ SendResults();
+
+ void
+ SendPreprocessInfoOrResults(bool aSendPreprocessInfo);
+
+ // Not to be overridden by subclasses.
+ NS_DECL_NSIRUNNABLE
+};
+
+class Factory final
+ : public PBackgroundIDBFactoryParent
+{
+
+ RefPtr<DatabaseLoggingInfo> mLoggingInfo;
+
+#ifdef DEBUG
+ bool mActorDestroyed;
+#endif
+
+public:
+ static already_AddRefed<Factory>
+ Create(const LoggingInfo& aLoggingInfo);
+
+ DatabaseLoggingInfo*
+ GetLoggingInfo() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mLoggingInfo);
+
+ return mLoggingInfo;
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Factory)
+
+private:
+ // Only constructed in Create().
+ explicit
+ Factory(already_AddRefed<DatabaseLoggingInfo> aLoggingInfo);
+
+ // Reference counted.
+ ~Factory();
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ RecvDeleteMe() override;
+
+ virtual bool
+ RecvIncrementLoggingRequestSerialNumber() override;
+
+ virtual PBackgroundIDBFactoryRequestParent*
+ AllocPBackgroundIDBFactoryRequestParent(const FactoryRequestParams& aParams)
+ override;
+
+ virtual bool
+ RecvPBackgroundIDBFactoryRequestConstructor(
+ PBackgroundIDBFactoryRequestParent* aActor,
+ const FactoryRequestParams& aParams)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBFactoryRequestParent(
+ PBackgroundIDBFactoryRequestParent* aActor)
+ override;
+
+ virtual PBackgroundIDBDatabaseParent*
+ AllocPBackgroundIDBDatabaseParent(
+ const DatabaseSpec& aSpec,
+ PBackgroundIDBFactoryRequestParent* aRequest)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBDatabaseParent(PBackgroundIDBDatabaseParent* aActor)
+ override;
+};
+
+class WaitForTransactionsHelper final
+ : public Runnable
+{
+ const nsCString mDatabaseId;
+ nsCOMPtr<nsIRunnable> mCallback;
+
+ enum class State
+ {
+ Initial = 0,
+ WaitingForTransactions,
+ WaitingForFileHandles,
+ Complete
+ } mState;
+
+public:
+ WaitForTransactionsHelper(const nsCString& aDatabaseId,
+ nsIRunnable* aCallback)
+ : mDatabaseId(aDatabaseId)
+ , mCallback(aCallback)
+ , mState(State::Initial)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!aDatabaseId.IsEmpty());
+ MOZ_ASSERT(aCallback);
+ }
+
+ void
+ WaitForTransactions();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~WaitForTransactionsHelper()
+ {
+ MOZ_ASSERT(!mCallback);
+ MOZ_ASSERT(mState == State::Complete);
+ }
+
+ void
+ MaybeWaitForTransactions();
+
+ void
+ MaybeWaitForFileHandles();
+
+ void
+ CallCallback();
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class Database final
+ : public PBackgroundIDBDatabaseParent
+{
+ friend class VersionChangeTransaction;
+
+ class StartTransactionOp;
+
+private:
+ RefPtr<Factory> mFactory;
+ RefPtr<FullDatabaseMetadata> mMetadata;
+ RefPtr<FileManager> mFileManager;
+ RefPtr<DirectoryLock> mDirectoryLock;
+ nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
+ nsTHashtable<nsPtrHashKey<MutableFile>> mMutableFiles;
+ RefPtr<DatabaseConnection> mConnection;
+ const PrincipalInfo mPrincipalInfo;
+ const Maybe<ContentParentId> mOptionalContentParentId;
+ const nsCString mGroup;
+ const nsCString mOrigin;
+ const nsCString mId;
+ const nsString mFilePath;
+ uint32_t mActiveMutableFileCount;
+ const uint32_t mTelemetryId;
+ const PersistenceType mPersistenceType;
+ const bool mFileHandleDisabled;
+ const bool mChromeWriteAccessAllowed;
+ bool mClosed;
+ bool mInvalidated;
+ bool mActorWasAlive;
+ bool mActorDestroyed;
+ bool mMetadataCleanedUp;
+
+public:
+ // Created by OpenDatabaseOp.
+ Database(Factory* aFactory,
+ const PrincipalInfo& aPrincipalInfo,
+ const Maybe<ContentParentId>& aOptionalContentParentId,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ uint32_t aTelemetryId,
+ FullDatabaseMetadata* aMetadata,
+ FileManager* aFileManager,
+ already_AddRefed<DirectoryLock> aDirectoryLock,
+ bool aFileHandleDisabled,
+ bool aChromeWriteAccessAllowed);
+
+ void
+ AssertIsOnConnectionThread() const
+ {
+#ifdef DEBUG
+ if (mConnection) {
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+ } else {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(mInvalidated);
+ }
+#endif
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Database)
+
+ void
+ Invalidate();
+
+ const PrincipalInfo&
+ GetPrincipalInfo() const
+ {
+ return mPrincipalInfo;
+ }
+
+ bool
+ IsOwnedByProcess(ContentParentId aContentParentId) const
+ {
+ return
+ mOptionalContentParentId &&
+ mOptionalContentParentId.value() == aContentParentId;
+ }
+
+ const nsCString&
+ Group() const
+ {
+ return mGroup;
+ }
+
+ const nsCString&
+ Origin() const
+ {
+ return mOrigin;
+ }
+
+ const nsCString&
+ Id() const
+ {
+ return mId;
+ }
+
+ uint32_t
+ TelemetryId() const
+ {
+ return mTelemetryId;
+ }
+
+ PersistenceType
+ Type() const
+ {
+ return mPersistenceType;
+ }
+
+ const nsString&
+ FilePath() const
+ {
+ return mFilePath;
+ }
+
+ FileManager*
+ GetFileManager() const
+ {
+ return mFileManager;
+ }
+
+ FullDatabaseMetadata*
+ Metadata() const
+ {
+ MOZ_ASSERT(mMetadata);
+ return mMetadata;
+ }
+
+ PBackgroundParent*
+ GetBackgroundParent() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ return Manager()->Manager();
+ }
+
+ DatabaseLoggingInfo*
+ GetLoggingInfo() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mFactory);
+
+ return mFactory->GetLoggingInfo();
+ }
+
+ void
+ ReleaseTransactionThreadObjects();
+
+ void
+ ReleaseBackgroundThreadObjects();
+
+ bool
+ RegisterTransaction(TransactionBase* aTransaction);
+
+ void
+ UnregisterTransaction(TransactionBase* aTransaction);
+
+ bool
+ IsFileHandleDisabled() const
+ {
+ return mFileHandleDisabled;
+ }
+
+ bool
+ RegisterMutableFile(MutableFile* aMutableFile);
+
+ void
+ UnregisterMutableFile(MutableFile* aMutableFile);
+
+ void
+ NoteActiveMutableFile();
+
+ void
+ NoteInactiveMutableFile();
+
+ void
+ SetActorAlive();
+
+ bool
+ IsActorAlive() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mActorWasAlive && !mActorDestroyed;
+ }
+
+ bool
+ IsActorDestroyed() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mActorWasAlive && mActorDestroyed;
+ }
+
+ bool
+ IsClosed() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mClosed;
+ }
+
+ bool
+ IsInvalidated() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mInvalidated;
+ }
+
+ nsresult
+ EnsureConnection();
+
+ DatabaseConnection*
+ GetConnection() const
+ {
+#ifdef DEBUG
+ if (mConnection) {
+ mConnection->AssertIsOnConnectionThread();
+ }
+#endif
+
+ return mConnection;
+ }
+
+private:
+ // Reference counted.
+ ~Database()
+ {
+ MOZ_ASSERT(mClosed);
+ MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
+ }
+
+ bool
+ CloseInternal();
+
+ void
+ MaybeCloseConnection();
+
+ void
+ ConnectionClosedCallback();
+
+ void
+ CleanupMetadata();
+
+ bool
+ VerifyRequestParams(const DatabaseRequestParams& aParams) const;
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual PBackgroundIDBDatabaseFileParent*
+ AllocPBackgroundIDBDatabaseFileParent(PBlobParent* aBlobParent)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBDatabaseFileParent(
+ PBackgroundIDBDatabaseFileParent* aActor)
+ override;
+
+ virtual PBackgroundIDBDatabaseRequestParent*
+ AllocPBackgroundIDBDatabaseRequestParent(const DatabaseRequestParams& aParams)
+ override;
+
+ virtual bool
+ RecvPBackgroundIDBDatabaseRequestConstructor(
+ PBackgroundIDBDatabaseRequestParent* aActor,
+ const DatabaseRequestParams& aParams)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBDatabaseRequestParent(
+ PBackgroundIDBDatabaseRequestParent* aActor)
+ override;
+
+ virtual PBackgroundIDBTransactionParent*
+ AllocPBackgroundIDBTransactionParent(
+ const nsTArray<nsString>& aObjectStoreNames,
+ const Mode& aMode)
+ override;
+
+ virtual bool
+ RecvPBackgroundIDBTransactionConstructor(
+ PBackgroundIDBTransactionParent* aActor,
+ InfallibleTArray<nsString>&& aObjectStoreNames,
+ const Mode& aMode)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBTransactionParent(
+ PBackgroundIDBTransactionParent* aActor)
+ override;
+
+ virtual PBackgroundIDBVersionChangeTransactionParent*
+ AllocPBackgroundIDBVersionChangeTransactionParent(
+ const uint64_t& aCurrentVersion,
+ const uint64_t& aRequestedVersion,
+ const int64_t& aNextObjectStoreId,
+ const int64_t& aNextIndexId)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBVersionChangeTransactionParent(
+ PBackgroundIDBVersionChangeTransactionParent* aActor)
+ override;
+
+ virtual PBackgroundMutableFileParent*
+ AllocPBackgroundMutableFileParent(const nsString& aName,
+ const nsString& aType) override;
+
+ virtual bool
+ DeallocPBackgroundMutableFileParent(PBackgroundMutableFileParent* aActor)
+ override;
+
+ virtual bool
+ RecvDeleteMe() override;
+
+ virtual bool
+ RecvBlocked() override;
+
+ virtual bool
+ RecvClose() override;
+};
+
+class Database::StartTransactionOp final
+ : public TransactionDatabaseOperationBase
+{
+ friend class Database;
+
+private:
+ explicit
+ StartTransactionOp(TransactionBase* aTransaction)
+ : TransactionDatabaseOperationBase(aTransaction,
+ /* aLoggingSerialNumber */ 0)
+ { }
+
+ ~StartTransactionOp()
+ { }
+
+ virtual void
+ RunOnConnectionThread() override;
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual nsresult
+ SendSuccessResult() override;
+
+ virtual bool
+ SendFailureResult(nsresult aResultCode) override;
+
+ virtual void
+ Cleanup() override;
+};
+
+/**
+ * In coordination with IDBDatabase's mFileActors weak-map on the child side, a
+ * long-lived mapping from a child process's live Blobs to their corresponding
+ * FileInfo in our owning database. Assists in avoiding redundant IPC traffic
+ * and disk storage. This includes both:
+ * - Blobs retrieved from this database and sent to the child that do not need
+ * to be written to disk because they already exist on disk in this database's
+ * files directory.
+ * - Blobs retrieved from other databases (that are therefore !IsShareable())
+ * or from anywhere else that will need to be written to this database's files
+ * directory. In this case we will hold a reference to its BlobImpl in
+ * mBlobImpl until we have successfully written the Blob to disk.
+ *
+ * Relevant Blob context: Blobs sent from the parent process to child processes
+ * are automatically linked back to their source BlobImpl when the child process
+ * references the Blob via IPC. (This is true even when a new "KnownBlob" actor
+ * must be created because the reference is occurring on a different thread than
+ * the PBlob actor created when the blob was sent to the child.) However, when
+ * getting an actor in the child process for sending an in-child-created Blob to
+ * the parent process, there is (currently) no Blob machinery to automatically
+ * establish and reuse a long-lived Actor. As a result, without IDB's weak-map
+ * cleverness, a memory-backed Blob repeatedly sent from the child to the parent
+ * would appear as a different Blob each time, requiring the Blob data to be
+ * sent over IPC each time as well as potentially needing to be written to disk
+ * each time.
+ *
+ * This object remains alive as long as there is an active child actor or an
+ * ObjectStoreAddOrPutRequestOp::StoredFileInfo for a queued or active add/put
+ * op is holding a reference to us.
+ */
+class DatabaseFile final
+ : public PBackgroundIDBDatabaseFileParent
+{
+ friend class Database;
+
+ // mBlobImpl's ownership lifecycle:
+ // - Initialized on the background thread at creation time. Then
+ // responsibility is handed off to the connection thread.
+ // - Checked and used by the connection thread to generate a stream to write
+ // the blob to disk by an add/put operation.
+ // - Cleared on the connection thread once the file has successfully been
+ // written to disk.
+ RefPtr<BlobImpl> mBlobImpl;
+ RefPtr<FileInfo> mFileInfo;
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::DatabaseFile);
+
+ FileInfo*
+ GetFileInfo() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mFileInfo;
+ }
+
+ /**
+ * If mBlobImpl is non-null (implying the contents of this file have not yet
+ * been written to disk), then return an input stream that is guaranteed to
+ * block on reads and never return NS_BASE_STREAM_WOULD_BLOCK. If mBlobImpl
+ * is null (because the contents have been written to disk), returns null.
+ *
+ * Because this method does I/O, it should only be called on a database I/O
+ * thread, not on PBackground. Note that we actually open the stream on this
+ * thread, where previously it was opened on PBackground. This is safe and
+ * equally efficient because blob implementations are thread-safe and blobs in
+ * the parent are fully populated. (This would not be efficient in the child
+ * where the open request would have to be dispatched back to the PBackground
+ * thread because of the need to potentially interact with actors.)
+ *
+ * We enforce this guarantee by wrapping the stream in a blocking pipe unless
+ * either is true:
+ * - The stream is already a blocking stream. (AKA it's not non-blocking.)
+ * This is the case for nsFileStreamBase-derived implementations and
+ * appropriately configured nsPipes (but not PSendStream-generated pipes).
+ * - The stream already contains the entire contents of the Blob. For
+ * example, nsStringInputStreams report as non-blocking, but also have their
+ * entire contents synchronously available, so they will never return
+ * NS_BASE_STREAM_WOULD_BLOCK. There is no need to wrap these in a pipe.
+ * (It's also very common for SendStream-based Blobs to have their contents
+ * entirely streamed into the parent process by the time this call is
+ * issued.)
+ *
+ * This additional logic is necessary because our database operations all
+ * are written in such a way that the operation is assumed to have completed
+ * when they yield control-flow, and:
+ * - When memory-backed blobs cross a certain threshold (1MiB at the time of
+ * writing), they will be sent up from the child via PSendStream in chunks
+ * to a non-blocking pipe that will return NS_BASE_STREAM_WOULD_BLOCK.
+ * - Other Blob types could potentially be non-blocking. (We're not making
+ * any assumptions.)
+ */
+ already_AddRefed<nsIInputStream>
+ GetBlockingInputStream(ErrorResult &rv) const;
+
+ /**
+ * To be called upon successful copying of the stream GetBlockingInputStream()
+ * returned so that we won't try and redundantly write the file to disk in the
+ * future. This is a separate step from GetBlockingInputStream() because
+ * the write could fail due to quota errors that happen now but that might
+ * not happen in a future attempt.
+ */
+ void
+ WriteSucceededClearBlobImpl()
+ {
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ mBlobImpl = nullptr;
+ }
+
+private:
+ // Called when sending to the child.
+ explicit DatabaseFile(FileInfo* aFileInfo)
+ : mFileInfo(aFileInfo)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileInfo);
+ }
+
+ // Called when receiving from the child.
+ DatabaseFile(BlobImpl* aBlobImpl, FileInfo* aFileInfo)
+ : mBlobImpl(aBlobImpl)
+ , mFileInfo(aFileInfo)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aBlobImpl);
+ MOZ_ASSERT(aFileInfo);
+ }
+
+ ~DatabaseFile()
+ { }
+
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override
+ {
+ AssertIsOnBackgroundThread();
+ }
+};
+
+already_AddRefed<nsIInputStream>
+DatabaseFile::GetBlockingInputStream(ErrorResult &rv) const
+{
+ // We should only be called from our DB connection thread, not the background
+ // thread.
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ if (!mBlobImpl) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ mBlobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ // If it's non-blocking we may need a pipe.
+ bool pipeNeeded;
+ rv = inputStream->IsNonBlocking(&pipeNeeded);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ // We don't need a pipe if all the bytes might already be available.
+ if (pipeNeeded) {
+ uint64_t available;
+ rv = inputStream->Available(&available);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ uint64_t blobSize = mBlobImpl->GetSize(rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ if (available == blobSize) {
+ pipeNeeded = false;
+ }
+ }
+
+ if (pipeNeeded) {
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (!target) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIInputStream> pipeInputStream;
+ nsCOMPtr<nsIOutputStream> pipeOutputStream;
+
+ rv = NS_NewPipe(
+ getter_AddRefs(pipeInputStream),
+ getter_AddRefs(pipeOutputStream),
+ 0, 0, // default buffering is fine;
+ false, // we absolutely want a blocking input stream
+ true); // we don't need the writer to block
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ rv = NS_AsyncCopy(inputStream, pipeOutputStream, target);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ inputStream = pipeInputStream;
+ }
+
+ return inputStream.forget();
+}
+
+
+class TransactionBase
+{
+ friend class Cursor;
+
+ class CommitOp;
+
+protected:
+ typedef IDBTransaction::Mode Mode;
+
+private:
+ RefPtr<Database> mDatabase;
+ nsTArray<RefPtr<FullObjectStoreMetadata>>
+ mModifiedAutoIncrementObjectStoreMetadataArray;
+ uint64_t mTransactionId;
+ const nsCString mDatabaseId;
+ const int64_t mLoggingSerialNumber;
+ uint64_t mActiveRequestCount;
+ Atomic<bool> mInvalidatedOnAnyThread;
+ const Mode mMode;
+ bool mHasBeenActive;
+ bool mHasBeenActiveOnConnectionThread;
+ bool mActorDestroyed;
+ bool mInvalidated;
+
+protected:
+ nsresult mResultCode;
+ bool mCommitOrAbortReceived;
+ bool mCommittedOrAborted;
+ bool mForceAborted;
+
+public:
+ void
+ AssertIsOnConnectionThread() const
+ {
+ MOZ_ASSERT(mDatabase);
+ mDatabase->AssertIsOnConnectionThread();
+ }
+
+ bool
+ IsActorDestroyed() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mActorDestroyed;
+ }
+
+ // Must be called on the background thread.
+ bool
+ IsInvalidated() const
+ {
+ MOZ_ASSERT(IsOnBackgroundThread(), "Use IsInvalidatedOnAnyThread()");
+ MOZ_ASSERT_IF(mInvalidated, NS_FAILED(mResultCode));
+
+ return mInvalidated;
+ }
+
+ // May be called on any thread, but is more expensive than IsInvalidated().
+ bool
+ IsInvalidatedOnAnyThread() const
+ {
+ return mInvalidatedOnAnyThread;
+ }
+
+ void
+ SetActive(uint64_t aTransactionId)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransactionId);
+
+ mTransactionId = aTransactionId;
+ mHasBeenActive = true;
+ }
+
+ void
+ SetActiveOnConnectionThread()
+ {
+ AssertIsOnConnectionThread();
+ mHasBeenActiveOnConnectionThread = true;
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(
+ mozilla::dom::indexedDB::TransactionBase)
+
+ void
+ Abort(nsresult aResultCode, bool aForce);
+
+ uint64_t
+ TransactionId() const
+ {
+ return mTransactionId;
+ }
+
+ const nsCString&
+ DatabaseId() const
+ {
+ return mDatabaseId;
+ }
+
+ Mode
+ GetMode() const
+ {
+ return mMode;
+ }
+
+ Database*
+ GetDatabase() const
+ {
+ MOZ_ASSERT(mDatabase);
+
+ return mDatabase;
+ }
+
+ DatabaseLoggingInfo*
+ GetLoggingInfo() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mDatabase);
+
+ return mDatabase->GetLoggingInfo();
+ }
+
+ int64_t
+ LoggingSerialNumber() const
+ {
+ return mLoggingSerialNumber;
+ }
+
+ bool
+ IsAborted() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return NS_FAILED(mResultCode);
+ }
+
+ already_AddRefed<FullObjectStoreMetadata>
+ GetMetadataForObjectStoreId(int64_t aObjectStoreId) const;
+
+ already_AddRefed<FullIndexMetadata>
+ GetMetadataForIndexId(FullObjectStoreMetadata* const aObjectStoreMetadata,
+ int64_t aIndexId) const;
+
+ PBackgroundParent*
+ GetBackgroundParent() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ return GetDatabase()->GetBackgroundParent();
+ }
+
+ void
+ NoteModifiedAutoIncrementObjectStore(FullObjectStoreMetadata* aMetadata);
+
+ void
+ ForgetModifiedAutoIncrementObjectStore(FullObjectStoreMetadata* aMetadata);
+
+ void
+ NoteActiveRequest();
+
+ void
+ NoteFinishedRequest();
+
+ void
+ Invalidate();
+
+protected:
+ TransactionBase(Database* aDatabase, Mode aMode);
+
+ virtual
+ ~TransactionBase();
+
+ void
+ NoteActorDestroyed()
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+ }
+
+#ifdef DEBUG
+ // Only called by VersionChangeTransaction.
+ void
+ FakeActorDestroyed()
+ {
+ mActorDestroyed = true;
+ }
+#endif
+
+ bool
+ RecvCommit();
+
+ bool
+ RecvAbort(nsresult aResultCode);
+
+ void
+ MaybeCommitOrAbort()
+ {
+ AssertIsOnBackgroundThread();
+
+ // If we've already committed or aborted then there's nothing else to do.
+ if (mCommittedOrAborted) {
+ return;
+ }
+
+ // If there are active requests then we have to wait for those requests to
+ // complete (see NoteFinishedRequest).
+ if (mActiveRequestCount) {
+ return;
+ }
+
+ // If we haven't yet received a commit or abort message then there could be
+ // additional requests coming so we should wait unless we're being forced to
+ // abort.
+ if (!mCommitOrAbortReceived && !mForceAborted) {
+ return;
+ }
+
+ CommitOrAbort();
+ }
+
+ PBackgroundIDBRequestParent*
+ AllocRequest(const RequestParams& aParams, bool aTrustParams);
+
+ bool
+ StartRequest(PBackgroundIDBRequestParent* aActor);
+
+ bool
+ DeallocRequest(PBackgroundIDBRequestParent* aActor);
+
+ PBackgroundIDBCursorParent*
+ AllocCursor(const OpenCursorParams& aParams, bool aTrustParams);
+
+ bool
+ StartCursor(PBackgroundIDBCursorParent* aActor,
+ const OpenCursorParams& aParams);
+
+ bool
+ DeallocCursor(PBackgroundIDBCursorParent* aActor);
+
+ virtual void
+ UpdateMetadata(nsresult aResult)
+ { }
+
+ virtual void
+ SendCompleteNotification(nsresult aResult) = 0;
+
+private:
+ bool
+ VerifyRequestParams(const RequestParams& aParams) const;
+
+ bool
+ VerifyRequestParams(const SerializedKeyRange& aKeyRange) const;
+
+ bool
+ VerifyRequestParams(const ObjectStoreAddPutParams& aParams) const;
+
+ bool
+ VerifyRequestParams(const OptionalKeyRange& aKeyRange) const;
+
+ void
+ CommitOrAbort();
+};
+
+class TransactionBase::CommitOp final
+ : public DatabaseOperationBase
+ , public ConnectionPool::FinishCallback
+{
+ friend class TransactionBase;
+
+ RefPtr<TransactionBase> mTransaction;
+ nsresult mResultCode;
+
+private:
+ CommitOp(TransactionBase* aTransaction, nsresult aResultCode);
+
+ ~CommitOp()
+ { }
+
+ // Writes new autoIncrement counts to database.
+ nsresult
+ WriteAutoIncrementCounts();
+
+ // Updates counts after a database activity has finished.
+ void
+ CommitOrRollbackAutoIncrementCounts();
+
+ void
+ AssertForeignKeyConsistency(DatabaseConnection* aConnection)
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ NS_DECL_NSIRUNNABLE
+
+ virtual void
+ TransactionFinishedBeforeUnblock() override;
+
+ virtual void
+ TransactionFinishedAfterUnblock() override;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+};
+
+class NormalTransaction final
+ : public TransactionBase
+ , public PBackgroundIDBTransactionParent
+{
+ friend class Database;
+
+ nsTArray<RefPtr<FullObjectStoreMetadata>> mObjectStores;
+
+private:
+ // This constructor is only called by Database.
+ NormalTransaction(Database* aDatabase,
+ TransactionBase::Mode aMode,
+ nsTArray<RefPtr<FullObjectStoreMetadata>>& aObjectStores);
+
+ // Reference counted.
+ ~NormalTransaction()
+ { }
+
+ bool
+ IsSameProcessActor();
+
+ // Only called by TransactionBase.
+ virtual void
+ SendCompleteNotification(nsresult aResult) override;
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ RecvDeleteMe() override;
+
+ virtual bool
+ RecvCommit() override;
+
+ virtual bool
+ RecvAbort(const nsresult& aResultCode) override;
+
+ virtual PBackgroundIDBRequestParent*
+ AllocPBackgroundIDBRequestParent(const RequestParams& aParams) override;
+
+ virtual bool
+ RecvPBackgroundIDBRequestConstructor(PBackgroundIDBRequestParent* aActor,
+ const RequestParams& aParams)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBRequestParent(PBackgroundIDBRequestParent* aActor)
+ override;
+
+ virtual PBackgroundIDBCursorParent*
+ AllocPBackgroundIDBCursorParent(const OpenCursorParams& aParams) override;
+
+ virtual bool
+ RecvPBackgroundIDBCursorConstructor(PBackgroundIDBCursorParent* aActor,
+ const OpenCursorParams& aParams)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBCursorParent(PBackgroundIDBCursorParent* aActor)
+ override;
+};
+
+class VersionChangeTransaction final
+ : public TransactionBase
+ , public PBackgroundIDBVersionChangeTransactionParent
+{
+ friend class OpenDatabaseOp;
+
+ RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
+ RefPtr<FullDatabaseMetadata> mOldMetadata;
+
+ bool mActorWasAlive;
+
+private:
+ // Only called by OpenDatabaseOp.
+ explicit VersionChangeTransaction(OpenDatabaseOp* aOpenDatabaseOp);
+
+ // Reference counted.
+ ~VersionChangeTransaction();
+
+ bool
+ IsSameProcessActor();
+
+ // Only called by OpenDatabaseOp.
+ bool
+ CopyDatabaseMetadata();
+
+ void
+ SetActorAlive();
+
+ // Only called by TransactionBase.
+ virtual void
+ UpdateMetadata(nsresult aResult) override;
+
+ // Only called by TransactionBase.
+ virtual void
+ SendCompleteNotification(nsresult aResult) override;
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ RecvDeleteMe() override;
+
+ virtual bool
+ RecvCommit() override;
+
+ virtual bool
+ RecvAbort(const nsresult& aResultCode) override;
+
+ virtual bool
+ RecvCreateObjectStore(const ObjectStoreMetadata& aMetadata) override;
+
+ virtual bool
+ RecvDeleteObjectStore(const int64_t& aObjectStoreId) override;
+
+ virtual bool
+ RecvRenameObjectStore(const int64_t& aObjectStoreId,
+ const nsString& aName) override;
+
+ virtual bool
+ RecvCreateIndex(const int64_t& aObjectStoreId,
+ const IndexMetadata& aMetadata) override;
+
+ virtual bool
+ RecvDeleteIndex(const int64_t& aObjectStoreId,
+ const int64_t& aIndexId) override;
+
+ virtual bool
+ RecvRenameIndex(const int64_t& aObjectStoreId,
+ const int64_t& aIndexId,
+ const nsString& aName) override;
+
+ virtual PBackgroundIDBRequestParent*
+ AllocPBackgroundIDBRequestParent(const RequestParams& aParams) override;
+
+ virtual bool
+ RecvPBackgroundIDBRequestConstructor(PBackgroundIDBRequestParent* aActor,
+ const RequestParams& aParams)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBRequestParent(PBackgroundIDBRequestParent* aActor)
+ override;
+
+ virtual PBackgroundIDBCursorParent*
+ AllocPBackgroundIDBCursorParent(const OpenCursorParams& aParams) override;
+
+ virtual bool
+ RecvPBackgroundIDBCursorConstructor(PBackgroundIDBCursorParent* aActor,
+ const OpenCursorParams& aParams)
+ override;
+
+ virtual bool
+ DeallocPBackgroundIDBCursorParent(PBackgroundIDBCursorParent* aActor)
+ override;
+};
+
+class MutableFile
+ : public BackgroundMutableFileParentBase
+{
+ RefPtr<Database> mDatabase;
+ RefPtr<FileInfo> mFileInfo;
+
+public:
+ static already_AddRefed<MutableFile>
+ Create(nsIFile* aFile,
+ Database* aDatabase,
+ FileInfo* aFileInfo);
+
+ Database*
+ GetDatabase() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mDatabase);
+
+ return mDatabase;
+ }
+
+ FileInfo*
+ GetFileInfo() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mFileInfo);
+
+ return mFileInfo;
+ }
+
+ virtual void
+ NoteActiveState() override;
+
+ virtual void
+ NoteInactiveState() override;
+
+ virtual PBackgroundParent*
+ GetBackgroundParent() const override;
+
+ virtual already_AddRefed<nsISupports>
+ CreateStream(bool aReadOnly) override;
+
+ virtual already_AddRefed<BlobImpl>
+ CreateBlobImpl() override;
+
+private:
+ MutableFile(nsIFile* aFile,
+ Database* aDatabase,
+ FileInfo* aFileInfo);
+
+ ~MutableFile();
+
+ virtual PBackgroundFileHandleParent*
+ AllocPBackgroundFileHandleParent(const FileMode& aMode) override;
+
+ virtual bool
+ RecvPBackgroundFileHandleConstructor(PBackgroundFileHandleParent* aActor,
+ const FileMode& aMode) override;
+
+ virtual bool
+ RecvGetFileId(int64_t* aFileId) override;
+};
+
+class FactoryOp
+ : public DatabaseOperationBase
+ , public OpenDirectoryListener
+ , public PBackgroundIDBFactoryRequestParent
+{
+public:
+ struct MaybeBlockedDatabaseInfo;
+
+protected:
+ enum class State
+ {
+ // Just created on the PBackground thread, dispatched to the main thread.
+ // Next step is either SendingResults if permission is denied,
+ // PermissionChallenge if the permission is unknown, or FinishOpen
+ // if permission is granted.
+ Initial,
+
+ // Sending a permission challenge message to the child on the PBackground
+ // thread. Next step is PermissionRetry.
+ PermissionChallenge,
+
+ // Retrying permission check after a challenge on the main thread. Next step
+ // is either SendingResults if permission is denied or FinishOpen
+ // if permission is granted.
+ PermissionRetry,
+
+ // Opening directory or initializing quota manager on the PBackground
+ // thread. Next step is either DirectoryOpenPending if quota manager is
+ // already initialized or QuotaManagerPending if quota manager needs to be
+ // initialized.
+ FinishOpen,
+
+ // Waiting for quota manager initialization to complete on the PBackground
+ // thread. Next step is either SendingResults if initialization failed or
+ // DirectoryOpenPending if initialization succeeded.
+ QuotaManagerPending,
+
+ // Waiting for directory open allowed on the PBackground thread. The next
+ // step is either SendingResults if directory lock failed to acquire, or
+ // DatabaseOpenPending if directory lock is acquired.
+ DirectoryOpenPending,
+
+ // Waiting for database open allowed on the PBackground thread. The next
+ // step is DatabaseWorkOpen.
+ DatabaseOpenPending,
+
+ // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
+ // either BeginVersionChange if the requested version doesn't match the
+ // existing database version or SendingResults if the versions match.
+ DatabaseWorkOpen,
+
+ // Starting a version change transaction or deleting a database on the
+ // PBackground thread. We need to notify other databases that a version
+ // change is about to happen, and maybe tell the request that a version
+ // change has been blocked. If databases are notified then the next step is
+ // WaitingForOtherDatabasesToClose. Otherwise the next step is
+ // WaitingForTransactionsToComplete.
+ BeginVersionChange,
+
+ // Waiting for other databases to close on the PBackground thread. This
+ // state may persist until all databases are closed. The next state is
+ // WaitingForTransactionsToComplete.
+ WaitingForOtherDatabasesToClose,
+
+ // Waiting for all transactions that could interfere with this operation to
+ // complete on the PBackground thread. Next state is
+ // DatabaseWorkVersionChange.
+ WaitingForTransactionsToComplete,
+
+ // Waiting to do/doing work on the "work thread". This involves waiting for
+ // the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a
+ // different implementation) to do its work. Eventually the state will
+ // transition to SendingResults.
+ DatabaseWorkVersionChange,
+
+ // Waiting to send/sending results on the PBackground thread. Next step is
+ // Completed.
+ SendingResults,
+
+ // All done.
+ Completed
+ };
+
+ // Must be released on the background thread!
+ RefPtr<Factory> mFactory;
+
+ // Must be released on the main thread!
+ RefPtr<ContentParent> mContentParent;
+
+ // Must be released on the main thread!
+ RefPtr<DirectoryLock> mDirectoryLock;
+
+ RefPtr<FactoryOp> mDelayedOp;
+ nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases;
+
+ const CommonFactoryRequestParams mCommonParams;
+ nsCString mSuffix;
+ nsCString mGroup;
+ nsCString mOrigin;
+ nsCString mDatabaseId;
+ nsString mDatabaseFilePath;
+ State mState;
+ bool mIsApp;
+ bool mEnforcingQuota;
+ const bool mDeleting;
+ bool mBlockedDatabaseOpen;
+ bool mChromeWriteAccessAllowed;
+ bool mFileHandleDisabled;
+
+public:
+ void
+ NoteDatabaseBlocked(Database* aDatabase);
+
+ virtual void
+ NoteDatabaseClosed(Database* aDatabase) = 0;
+
+#ifdef DEBUG
+ bool
+ HasBlockedDatabases() const
+ {
+ return !mMaybeBlockedDatabases.IsEmpty();
+ }
+#endif
+
+ const nsString&
+ DatabaseFilePath() const
+ {
+ return mDatabaseFilePath;
+ }
+
+protected:
+ FactoryOp(Factory* aFactory,
+ already_AddRefed<ContentParent> aContentParent,
+ const CommonFactoryRequestParams& aCommonParams,
+ bool aDeleting);
+
+ virtual
+ ~FactoryOp()
+ {
+ // Normally this would be out-of-line since it is a virtual function but
+ // MSVC 2010 fails to link for some reason if it is not inlined here...
+ MOZ_ASSERT_IF(OperationMayProceed(),
+ mState == State::Initial || mState == State::Completed);
+ }
+
+ nsresult
+ Open();
+
+ nsresult
+ ChallengePermission();
+
+ nsresult
+ RetryCheckPermission();
+
+ nsresult
+ DirectoryOpen();
+
+ nsresult
+ SendToIOThread();
+
+ void
+ WaitForTransactions();
+
+ void
+ FinishSendResults();
+
+ nsresult
+ SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
+ Database* aOpeningDatabase,
+ uint64_t aOldVersion,
+ const NullableVersion& aNewVersion);
+
+ // Methods that subclasses must implement.
+ virtual nsresult
+ DatabaseOpen() = 0;
+
+ virtual nsresult
+ DoDatabaseWork() = 0;
+
+ virtual nsresult
+ BeginVersionChange() = 0;
+
+ virtual nsresult
+ DispatchToWorkThread() = 0;
+
+ // Should only be called by Run().
+ virtual void
+ SendResults() = 0;
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Common nsIRunnable implementation that subclasses may not override.
+ NS_IMETHOD
+ Run() final;
+
+ // OpenDirectoryListener overrides.
+ virtual void
+ DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+ virtual void
+ DirectoryLockFailed() override;
+
+ // IPDL methods.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ RecvPermissionRetry() override;
+
+ virtual void
+ SendBlockedNotification() = 0;
+
+private:
+ nsresult
+ CheckPermission(ContentParent* aContentParent,
+ PermissionRequestBase::PermissionValue* aPermission);
+
+ static bool
+ CheckAtLeastOneAppHasPermission(ContentParent* aContentParent,
+ const nsACString& aPermissionString);
+
+ nsresult
+ FinishOpen();
+
+ nsresult
+ QuotaManagerOpen();
+
+ nsresult
+ OpenDirectory();
+
+ // Test whether this FactoryOp needs to wait for the given op.
+ bool
+ MustWaitFor(const FactoryOp& aExistingOp);
+};
+
+struct FactoryOp::MaybeBlockedDatabaseInfo final
+{
+ RefPtr<Database> mDatabase;
+ bool mBlocked;
+
+ MOZ_IMPLICIT MaybeBlockedDatabaseInfo(Database* aDatabase)
+ : mDatabase(aDatabase)
+ , mBlocked(false)
+ {
+ MOZ_ASSERT(aDatabase);
+
+ MOZ_COUNT_CTOR(FactoryOp::MaybeBlockedDatabaseInfo);
+ }
+
+ ~MaybeBlockedDatabaseInfo()
+ {
+ MOZ_COUNT_DTOR(FactoryOp::MaybeBlockedDatabaseInfo);
+ }
+
+ bool
+ operator==(const MaybeBlockedDatabaseInfo& aOther) const
+ {
+ return mDatabase == aOther.mDatabase;
+ }
+
+ bool
+ operator<(const MaybeBlockedDatabaseInfo& aOther) const
+ {
+ return mDatabase < aOther.mDatabase;
+ }
+
+ Database*
+ operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN
+ {
+ return mDatabase;
+ }
+};
+
+class OpenDatabaseOp final
+ : public FactoryOp
+{
+ friend class Database;
+ friend class VersionChangeTransaction;
+
+ class VersionChangeOp;
+
+ Maybe<ContentParentId> mOptionalContentParentId;
+
+ RefPtr<FullDatabaseMetadata> mMetadata;
+
+ uint64_t mRequestedVersion;
+ RefPtr<FileManager> mFileManager;
+
+ RefPtr<Database> mDatabase;
+ RefPtr<VersionChangeTransaction> mVersionChangeTransaction;
+
+ // This is only set while a VersionChangeOp is live. It holds a strong
+ // reference to its OpenDatabaseOp object so this is a weak pointer to avoid
+ // cycles.
+ VersionChangeOp* mVersionChangeOp;
+
+ uint32_t mTelemetryId;
+
+public:
+ OpenDatabaseOp(Factory* aFactory,
+ already_AddRefed<ContentParent> aContentParent,
+ const CommonFactoryRequestParams& aParams);
+
+ bool
+ IsOtherProcessActor() const
+ {
+ return mOptionalContentParentId.isSome();
+ }
+
+private:
+ ~OpenDatabaseOp()
+ {
+ MOZ_ASSERT(!mVersionChangeOp);
+ }
+
+ nsresult
+ LoadDatabaseInformation(mozIStorageConnection* aConnection);
+
+ nsresult
+ SendUpgradeNeeded();
+
+ void
+ EnsureDatabaseActor();
+
+ nsresult
+ EnsureDatabaseActorIsAlive();
+
+ void
+ MetadataToSpec(DatabaseSpec& aSpec);
+
+ void
+ AssertMetadataConsistency(const FullDatabaseMetadata* aMetadata)
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ void
+ ConnectionClosedCallback();
+
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual nsresult
+ DatabaseOpen() override;
+
+ virtual nsresult
+ DoDatabaseWork() override;
+
+ virtual nsresult
+ BeginVersionChange() override;
+
+ virtual void
+ NoteDatabaseClosed(Database* aDatabase) override;
+
+ virtual void
+ SendBlockedNotification() override;
+
+ virtual nsresult
+ DispatchToWorkThread() override;
+
+ virtual void
+ SendResults() override;
+
+#ifdef ENABLE_INTL_API
+ static nsresult
+ UpdateLocaleAwareIndex(mozIStorageConnection* aConnection,
+ const IndexMetadata& aIndexMetadata,
+ const nsCString& aLocale);
+#endif
+};
+
+class OpenDatabaseOp::VersionChangeOp final
+ : public TransactionDatabaseOperationBase
+{
+ friend class OpenDatabaseOp;
+
+ RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
+ const uint64_t mRequestedVersion;
+ uint64_t mPreviousVersion;
+
+private:
+ explicit
+ VersionChangeOp(OpenDatabaseOp* aOpenDatabaseOp)
+ : TransactionDatabaseOperationBase(
+ aOpenDatabaseOp->mVersionChangeTransaction,
+ aOpenDatabaseOp->LoggingSerialNumber())
+ , mOpenDatabaseOp(aOpenDatabaseOp)
+ , mRequestedVersion(aOpenDatabaseOp->mRequestedVersion)
+ , mPreviousVersion(aOpenDatabaseOp->mMetadata->mCommonMetadata.version())
+ {
+ MOZ_ASSERT(aOpenDatabaseOp);
+ MOZ_ASSERT(mRequestedVersion);
+ }
+
+ ~VersionChangeOp()
+ {
+ MOZ_ASSERT(!mOpenDatabaseOp);
+ }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual nsresult
+ SendSuccessResult() override;
+
+ virtual bool
+ SendFailureResult(nsresult aResultCode) override;
+
+ virtual void
+ Cleanup() override;
+};
+
+class DeleteDatabaseOp final
+ : public FactoryOp
+{
+ class VersionChangeOp;
+
+ nsString mDatabaseDirectoryPath;
+ nsString mDatabaseFilenameBase;
+ uint64_t mPreviousVersion;
+
+public:
+ DeleteDatabaseOp(Factory* aFactory,
+ already_AddRefed<ContentParent> aContentParent,
+ const CommonFactoryRequestParams& aParams)
+ : FactoryOp(aFactory, Move(aContentParent), aParams, /* aDeleting */ true)
+ , mPreviousVersion(0)
+ { }
+
+private:
+ ~DeleteDatabaseOp()
+ { }
+
+ void
+ LoadPreviousVersion(nsIFile* aDatabaseFile);
+
+ virtual nsresult
+ DatabaseOpen() override;
+
+ virtual nsresult
+ DoDatabaseWork() override;
+
+ virtual nsresult
+ BeginVersionChange() override;
+
+ virtual void
+ NoteDatabaseClosed(Database* aDatabase) override;
+
+ virtual void
+ SendBlockedNotification() override;
+
+ virtual nsresult
+ DispatchToWorkThread() override;
+
+ virtual void
+ SendResults() override;
+};
+
+class DeleteDatabaseOp::VersionChangeOp final
+ : public DatabaseOperationBase
+{
+ friend class DeleteDatabaseOp;
+
+ RefPtr<DeleteDatabaseOp> mDeleteDatabaseOp;
+
+private:
+ explicit
+ VersionChangeOp(DeleteDatabaseOp* aDeleteDatabaseOp)
+ : DatabaseOperationBase(aDeleteDatabaseOp->BackgroundChildLoggingId(),
+ aDeleteDatabaseOp->LoggingSerialNumber())
+ , mDeleteDatabaseOp(aDeleteDatabaseOp)
+ {
+ MOZ_ASSERT(aDeleteDatabaseOp);
+ MOZ_ASSERT(!aDeleteDatabaseOp->mDatabaseDirectoryPath.IsEmpty());
+ }
+
+ ~VersionChangeOp()
+ { }
+
+ nsresult
+ RunOnIOThread();
+
+ void
+ RunOnOwningThread();
+
+ nsresult
+ DeleteFile(nsIFile* aDirectory,
+ const nsAString& aFilename,
+ QuotaManager* aQuotaManager);
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class DatabaseOp
+ : public DatabaseOperationBase
+ , public PBackgroundIDBDatabaseRequestParent
+{
+protected:
+ RefPtr<Database> mDatabase;
+
+ enum class State
+ {
+ // Just created on the PBackground thread, dispatched to the main thread.
+ // Next step is DatabaseWork.
+ Initial,
+
+ // Waiting to do/doing work on the QuotaManager IO thread. Next step is
+ // SendingResults.
+ DatabaseWork,
+
+ // Waiting to send/sending results on the PBackground thread. Next step is
+ // Completed.
+ SendingResults,
+
+ // All done.
+ Completed
+ };
+
+ State mState;
+
+public:
+ void
+ RunImmediately()
+ {
+ MOZ_ASSERT(mState == State::Initial);
+
+ Unused << this->Run();
+ }
+
+protected:
+ DatabaseOp(Database* aDatabase);
+
+ virtual
+ ~DatabaseOp()
+ {
+ MOZ_ASSERT_IF(OperationMayProceed(),
+ mState == State::Initial || mState == State::Completed);
+ }
+
+ nsresult
+ SendToIOThread();
+
+ // Methods that subclasses must implement.
+ virtual nsresult
+ DoDatabaseWork() = 0;
+
+ virtual void
+ SendResults() = 0;
+
+ // Common nsIRunnable implementation that subclasses may not override.
+ NS_IMETHOD
+ Run() final;
+
+ // IPDL methods.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class CreateFileOp final
+ : public DatabaseOp
+{
+ const CreateFileParams mParams;
+
+ RefPtr<FileInfo> mFileInfo;
+
+public:
+ CreateFileOp(Database* aDatabase,
+ const DatabaseRequestParams& aParams);
+
+private:
+ ~CreateFileOp()
+ { }
+
+ nsresult
+ CreateMutableFile(MutableFile** aMutableFile);
+
+ virtual nsresult
+ DoDatabaseWork() override;
+
+ virtual void
+ SendResults() override;
+};
+
+class VersionChangeTransactionOp
+ : public TransactionDatabaseOperationBase
+{
+public:
+ virtual void
+ Cleanup() override;
+
+protected:
+ explicit VersionChangeTransactionOp(VersionChangeTransaction* aTransaction)
+ : TransactionDatabaseOperationBase(aTransaction)
+ { }
+
+ virtual
+ ~VersionChangeTransactionOp()
+ { }
+
+private:
+ virtual nsresult
+ SendSuccessResult() override;
+
+ virtual bool
+ SendFailureResult(nsresult aResultCode) override;
+};
+
+class CreateObjectStoreOp final
+ : public VersionChangeTransactionOp
+{
+ friend class VersionChangeTransaction;
+
+ const ObjectStoreMetadata mMetadata;
+
+private:
+ // Only created by VersionChangeTransaction.
+ CreateObjectStoreOp(VersionChangeTransaction* aTransaction,
+ const ObjectStoreMetadata& aMetadata)
+ : VersionChangeTransactionOp(aTransaction)
+ , mMetadata(aMetadata)
+ {
+ MOZ_ASSERT(aMetadata.id());
+ }
+
+ ~CreateObjectStoreOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+};
+
+class DeleteObjectStoreOp final
+ : public VersionChangeTransactionOp
+{
+ friend class VersionChangeTransaction;
+
+ const RefPtr<FullObjectStoreMetadata> mMetadata;
+ const bool mIsLastObjectStore;
+
+private:
+ // Only created by VersionChangeTransaction.
+ DeleteObjectStoreOp(VersionChangeTransaction* aTransaction,
+ FullObjectStoreMetadata* const aMetadata,
+ const bool aIsLastObjectStore)
+ : VersionChangeTransactionOp(aTransaction)
+ , mMetadata(aMetadata)
+ , mIsLastObjectStore(aIsLastObjectStore)
+ {
+ MOZ_ASSERT(aMetadata->mCommonMetadata.id());
+ }
+
+ ~DeleteObjectStoreOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+};
+
+class RenameObjectStoreOp final
+ : public VersionChangeTransactionOp
+{
+ friend class VersionChangeTransaction;
+
+ const int64_t mId;
+ const nsString mNewName;
+
+private:
+ // Only created by VersionChangeTransaction.
+ RenameObjectStoreOp(VersionChangeTransaction* aTransaction,
+ FullObjectStoreMetadata* const aMetadata)
+ : VersionChangeTransactionOp(aTransaction)
+ , mId(aMetadata->mCommonMetadata.id())
+ , mNewName(aMetadata->mCommonMetadata.name())
+ {
+ MOZ_ASSERT(mId);
+ }
+
+ ~RenameObjectStoreOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+};
+
+class CreateIndexOp final
+ : public VersionChangeTransactionOp
+{
+ friend class VersionChangeTransaction;
+
+ class ThreadLocalJSContext;
+ class UpdateIndexDataValuesFunction;
+
+ static const unsigned int kBadThreadLocalIndex =
+ static_cast<unsigned int>(-1);
+
+ static unsigned int sThreadLocalIndex;
+
+ const IndexMetadata mMetadata;
+ Maybe<UniqueIndexTable> mMaybeUniqueIndexTable;
+ RefPtr<FileManager> mFileManager;
+ const nsCString mDatabaseId;
+ const uint64_t mObjectStoreId;
+
+private:
+ // Only created by VersionChangeTransaction.
+ CreateIndexOp(VersionChangeTransaction* aTransaction,
+ const int64_t aObjectStoreId,
+ const IndexMetadata& aMetadata);
+
+ ~CreateIndexOp()
+ { }
+
+ nsresult
+ InsertDataFromObjectStore(DatabaseConnection* aConnection);
+
+ nsresult
+ InsertDataFromObjectStoreInternal(DatabaseConnection* aConnection);
+
+ virtual bool
+ Init(TransactionBase* aTransaction) override;
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+};
+
+class NormalJSContext
+{
+ friend class nsAutoPtr<NormalJSContext>;
+
+ static const JSClass sGlobalClass;
+ static const uint32_t kContextHeapSize = 768 * 1024;
+
+ JSContext* mContext;
+ JSObject* mGlobal;
+
+public:
+ static NormalJSContext*
+ Create();
+
+ JSContext*
+ Context() const
+ {
+ return mContext;
+ }
+
+ JSObject*
+ Global() const
+ {
+ return mGlobal;
+ }
+
+protected:
+ NormalJSContext()
+ : mContext(nullptr)
+ , mGlobal(nullptr)
+ {
+ MOZ_COUNT_CTOR(NormalJSContext);
+ }
+
+ ~NormalJSContext()
+ {
+ MOZ_COUNT_DTOR(NormalJSContext);
+
+ if (mContext) {
+ JS_DestroyContext(mContext);
+ }
+ }
+
+ bool
+ Init();
+};
+
+class CreateIndexOp::ThreadLocalJSContext final
+ : public NormalJSContext
+{
+ friend class CreateIndexOp;
+ friend class nsAutoPtr<ThreadLocalJSContext>;
+
+public:
+ static ThreadLocalJSContext*
+ GetOrCreate();
+
+private:
+ ThreadLocalJSContext()
+ {
+ MOZ_COUNT_CTOR(CreateIndexOp::ThreadLocalJSContext);
+ }
+
+ ~ThreadLocalJSContext()
+ {
+ MOZ_COUNT_DTOR(CreateIndexOp::ThreadLocalJSContext);
+ }
+};
+
+class CreateIndexOp::UpdateIndexDataValuesFunction final
+ : public mozIStorageFunction
+{
+ RefPtr<CreateIndexOp> mOp;
+ RefPtr<DatabaseConnection> mConnection;
+ JSContext* mCx;
+
+public:
+ UpdateIndexDataValuesFunction(CreateIndexOp* aOp,
+ DatabaseConnection* aConnection,
+ JSContext* aCx)
+ : mOp(aOp)
+ , mConnection(aConnection)
+ , mCx(aCx)
+ {
+ MOZ_ASSERT(aOp);
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(aCx);
+ }
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~UpdateIndexDataValuesFunction()
+ { }
+
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+class DeleteIndexOp final
+ : public VersionChangeTransactionOp
+{
+ friend class VersionChangeTransaction;
+
+ const int64_t mObjectStoreId;
+ const int64_t mIndexId;
+ const bool mUnique;
+ const bool mIsLastIndex;
+
+private:
+ // Only created by VersionChangeTransaction.
+ DeleteIndexOp(VersionChangeTransaction* aTransaction,
+ const int64_t aObjectStoreId,
+ const int64_t aIndexId,
+ const bool aUnique,
+ const bool aIsLastIndex);
+
+ ~DeleteIndexOp()
+ { }
+
+ nsresult
+ RemoveReferencesToIndex(DatabaseConnection* aConnection,
+ const Key& aObjectDataKey,
+ nsTArray<IndexDataValue>& aIndexValues);
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+};
+
+class RenameIndexOp final
+ : public VersionChangeTransactionOp
+{
+ friend class VersionChangeTransaction;
+
+ const int64_t mObjectStoreId;
+ const int64_t mIndexId;
+ const nsString mNewName;
+
+private:
+ // Only created by VersionChangeTransaction.
+ RenameIndexOp(VersionChangeTransaction* aTransaction,
+ FullIndexMetadata* const aMetadata,
+ int64_t aObjectStoreId)
+ : VersionChangeTransactionOp(aTransaction)
+ , mObjectStoreId(aObjectStoreId)
+ , mIndexId(aMetadata->mCommonMetadata.id())
+ , mNewName(aMetadata->mCommonMetadata.name())
+ {
+ MOZ_ASSERT(mIndexId);
+ }
+
+ ~RenameIndexOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+};
+
+class NormalTransactionOp
+ : public TransactionDatabaseOperationBase
+ , public PBackgroundIDBRequestParent
+{
+#ifdef DEBUG
+ bool mResponseSent;
+#endif
+
+public:
+ virtual void
+ Cleanup() override;
+
+protected:
+ explicit NormalTransactionOp(TransactionBase* aTransaction)
+ : TransactionDatabaseOperationBase(aTransaction)
+#ifdef DEBUG
+ , mResponseSent(false)
+#endif
+ { }
+
+ virtual
+ ~NormalTransactionOp()
+ { }
+
+ // An overload of DatabaseOperationBase's function that can avoid doing extra
+ // work on non-versionchange transactions.
+ static nsresult
+ ObjectStoreHasIndexes(NormalTransactionOp* aOp,
+ DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ const bool aMayHaveIndexes,
+ bool* aHasIndexes);
+
+ virtual nsresult
+ GetPreprocessParams(PreprocessParams& aParams);
+
+
+ // Subclasses use this override to set the IPDL response value.
+ virtual void
+ GetResponse(RequestResponse& aResponse) = 0;
+
+private:
+ virtual nsresult
+ SendPreprocessInfo() override;
+
+ virtual nsresult
+ SendSuccessResult() override;
+
+ virtual bool
+ SendFailureResult(nsresult aResultCode) override;
+
+ // IPDL methods.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ RecvContinue(const PreprocessResponse& aResponse) override;
+};
+
+class ObjectStoreAddOrPutRequestOp final
+ : public NormalTransactionOp
+{
+ friend class TransactionBase;
+
+ typedef mozilla::dom::quota::PersistenceType PersistenceType;
+
+ struct StoredFileInfo;
+ class SCInputStream;
+
+ const ObjectStoreAddPutParams mParams;
+ Maybe<UniqueIndexTable> mUniqueIndexTable;
+
+ // This must be non-const so that we can update the mNextAutoIncrementId field
+ // if we are modifying an autoIncrement objectStore.
+ RefPtr<FullObjectStoreMetadata> mMetadata;
+
+ FallibleTArray<StoredFileInfo> mStoredFileInfos;
+
+ Key mResponse;
+ const nsCString mGroup;
+ const nsCString mOrigin;
+ const PersistenceType mPersistenceType;
+ const bool mOverwrite;
+ bool mObjectStoreMayHaveIndexes;
+ bool mDataOverThreshold;
+
+private:
+ // Only created by TransactionBase.
+ ObjectStoreAddOrPutRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams);
+
+ ~ObjectStoreAddOrPutRequestOp()
+ { }
+
+ nsresult
+ RemoveOldIndexDataValues(DatabaseConnection* aConnection);
+
+ virtual bool
+ Init(TransactionBase* aTransaction) override;
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override;
+
+ virtual void
+ Cleanup() override;
+};
+
+struct ObjectStoreAddOrPutRequestOp::StoredFileInfo final
+{
+ RefPtr<DatabaseFile> mFileActor;
+ RefPtr<FileInfo> mFileInfo;
+ // A non-Blob-backed inputstream to write to disk. If null, mFileActor may
+ // still have a stream for us to write.
+ nsCOMPtr<nsIInputStream> mInputStream;
+ StructuredCloneFile::FileType mType;
+
+ StoredFileInfo()
+ : mType(StructuredCloneFile::eBlob)
+ {
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
+ }
+
+ ~StoredFileInfo()
+ {
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_DTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
+ }
+
+ void
+ Serialize(nsString& aText)
+ {
+ MOZ_ASSERT(mFileInfo);
+
+ const int64_t id = mFileInfo->Id();
+
+ switch (mType) {
+ case StructuredCloneFile::eBlob:
+ aText.AppendInt(id);
+ break;
+
+ case StructuredCloneFile::eMutableFile:
+ aText.AppendInt(-id);
+ break;
+
+ case StructuredCloneFile::eStructuredClone:
+ aText.Append('.');
+ aText.AppendInt(id);
+ break;
+
+ case StructuredCloneFile::eWasmBytecode:
+ aText.Append('/');
+ aText.AppendInt(id);
+ break;
+
+ case StructuredCloneFile::eWasmCompiled:
+ aText.Append('\\');
+ aText.AppendInt(id);
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ }
+};
+
+class ObjectStoreAddOrPutRequestOp::SCInputStream final
+ : public nsIInputStream
+{
+ const JSStructuredCloneData& mData;
+ JSStructuredCloneData::IterImpl mIter;
+
+public:
+ explicit SCInputStream(const JSStructuredCloneData& aData)
+ : mData(aData)
+ , mIter(aData.Iter())
+ { }
+
+private:
+ virtual ~SCInputStream()
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+};
+
+class ObjectStoreGetRequestOp final
+ : public NormalTransactionOp
+{
+ friend class TransactionBase;
+
+ const uint32_t mObjectStoreId;
+ RefPtr<Database> mDatabase;
+ const OptionalKeyRange mOptionalKeyRange;
+ AutoTArray<StructuredCloneReadInfo, 1> mResponse;
+ PBackgroundParent* mBackgroundParent;
+ uint32_t mPreprocessInfoCount;
+ const uint32_t mLimit;
+ const bool mGetAll;
+
+private:
+ // Only created by TransactionBase.
+ ObjectStoreGetRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams,
+ bool aGetAll);
+
+ ~ObjectStoreGetRequestOp()
+ { }
+
+ template <bool aForPreprocess, typename T>
+ nsresult
+ ConvertResponse(StructuredCloneReadInfo& aInfo, T& aResult);
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual bool
+ HasPreprocessInfo() override;
+
+ virtual nsresult
+ GetPreprocessParams(PreprocessParams& aParams) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override;
+};
+
+class ObjectStoreGetKeyRequestOp final
+ : public NormalTransactionOp
+{
+ friend class TransactionBase;
+
+ const uint32_t mObjectStoreId;
+ const OptionalKeyRange mOptionalKeyRange;
+ const uint32_t mLimit;
+ const bool mGetAll;
+ FallibleTArray<Key> mResponse;
+
+private:
+ // Only created by TransactionBase.
+ ObjectStoreGetKeyRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams,
+ bool aGetAll);
+
+ ~ObjectStoreGetKeyRequestOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override;
+};
+
+class ObjectStoreDeleteRequestOp final
+ : public NormalTransactionOp
+{
+ friend class TransactionBase;
+
+ const ObjectStoreDeleteParams mParams;
+ ObjectStoreDeleteResponse mResponse;
+ bool mObjectStoreMayHaveIndexes;
+
+private:
+ ObjectStoreDeleteRequestOp(TransactionBase* aTransaction,
+ const ObjectStoreDeleteParams& aParams);
+
+ ~ObjectStoreDeleteRequestOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override
+ {
+ aResponse = Move(mResponse);
+ }
+};
+
+class ObjectStoreClearRequestOp final
+ : public NormalTransactionOp
+{
+ friend class TransactionBase;
+
+ const ObjectStoreClearParams mParams;
+ ObjectStoreClearResponse mResponse;
+ bool mObjectStoreMayHaveIndexes;
+
+private:
+ ObjectStoreClearRequestOp(TransactionBase* aTransaction,
+ const ObjectStoreClearParams& aParams);
+
+ ~ObjectStoreClearRequestOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override
+ {
+ aResponse = Move(mResponse);
+ }
+};
+
+class ObjectStoreCountRequestOp final
+ : public NormalTransactionOp
+{
+ friend class TransactionBase;
+
+ const ObjectStoreCountParams mParams;
+ ObjectStoreCountResponse mResponse;
+
+private:
+ ObjectStoreCountRequestOp(TransactionBase* aTransaction,
+ const ObjectStoreCountParams& aParams)
+ : NormalTransactionOp(aTransaction)
+ , mParams(aParams)
+ { }
+
+ ~ObjectStoreCountRequestOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override
+ {
+ aResponse = Move(mResponse);
+ }
+};
+
+class IndexRequestOpBase
+ : public NormalTransactionOp
+{
+protected:
+ const RefPtr<FullIndexMetadata> mMetadata;
+
+protected:
+ IndexRequestOpBase(TransactionBase* aTransaction,
+ const RequestParams& aParams)
+ : NormalTransactionOp(aTransaction)
+ , mMetadata(IndexMetadataForParams(aTransaction, aParams))
+ { }
+
+ virtual
+ ~IndexRequestOpBase()
+ { }
+
+private:
+ static already_AddRefed<FullIndexMetadata>
+ IndexMetadataForParams(TransactionBase* aTransaction,
+ const RequestParams& aParams);
+};
+
+class IndexGetRequestOp final
+ : public IndexRequestOpBase
+{
+ friend class TransactionBase;
+
+ RefPtr<Database> mDatabase;
+ const OptionalKeyRange mOptionalKeyRange;
+ AutoTArray<StructuredCloneReadInfo, 1> mResponse;
+ PBackgroundParent* mBackgroundParent;
+ const uint32_t mLimit;
+ const bool mGetAll;
+
+private:
+ // Only created by TransactionBase.
+ IndexGetRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams,
+ bool aGetAll);
+
+ ~IndexGetRequestOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override;
+};
+
+class IndexGetKeyRequestOp final
+ : public IndexRequestOpBase
+{
+ friend class TransactionBase;
+
+ const OptionalKeyRange mOptionalKeyRange;
+ AutoTArray<Key, 1> mResponse;
+ const uint32_t mLimit;
+ const bool mGetAll;
+
+private:
+ // Only created by TransactionBase.
+ IndexGetKeyRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams,
+ bool aGetAll);
+
+ ~IndexGetKeyRequestOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override;
+};
+
+class IndexCountRequestOp final
+ : public IndexRequestOpBase
+{
+ friend class TransactionBase;
+
+ const IndexCountParams mParams;
+ IndexCountResponse mResponse;
+
+private:
+ // Only created by TransactionBase.
+ IndexCountRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams)
+ : IndexRequestOpBase(aTransaction, aParams)
+ , mParams(aParams.get_IndexCountParams())
+ { }
+
+ ~IndexCountRequestOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override
+ {
+ aResponse = Move(mResponse);
+ }
+};
+
+class Cursor final :
+ public PBackgroundIDBCursorParent
+{
+ friend class TransactionBase;
+
+ class ContinueOp;
+ class CursorOpBase;
+ class OpenOp;
+
+public:
+ typedef OpenCursorParams::Type Type;
+
+private:
+ RefPtr<TransactionBase> mTransaction;
+ RefPtr<Database> mDatabase;
+ RefPtr<FileManager> mFileManager;
+ PBackgroundParent* mBackgroundParent;
+
+ // These should only be touched on the PBackground thread to check whether the
+ // objectStore or index has been deleted. Holding these saves a hash lookup
+ // for every call to continue()/advance().
+ RefPtr<FullObjectStoreMetadata> mObjectStoreMetadata;
+ RefPtr<FullIndexMetadata> mIndexMetadata;
+
+ const int64_t mObjectStoreId;
+ const int64_t mIndexId;
+
+ nsCString mContinueQuery;
+ nsCString mContinueToQuery;
+ nsCString mContinuePrimaryKeyQuery;
+ nsCString mLocale;
+
+ Key mKey;
+ Key mObjectKey;
+ Key mRangeKey;
+ Key mSortKey;
+
+ CursorOpBase* mCurrentlyRunningOp;
+
+ const Type mType;
+ const Direction mDirection;
+
+ const bool mUniqueIndex;
+ const bool mIsSameProcessActor;
+ bool mActorDestroyed;
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Cursor)
+
+private:
+ // Only created by TransactionBase.
+ Cursor(TransactionBase* aTransaction,
+ Type aType,
+ FullObjectStoreMetadata* aObjectStoreMetadata,
+ FullIndexMetadata* aIndexMetadata,
+ Direction aDirection);
+
+ // Reference counted.
+ ~Cursor()
+ {
+ MOZ_ASSERT(mActorDestroyed);
+ }
+
+ bool
+ VerifyRequestParams(const CursorRequestParams& aParams) const;
+
+ // Only called by TransactionBase.
+ bool
+ Start(const OpenCursorParams& aParams);
+
+ void
+ SendResponseInternal(
+ CursorResponse& aResponse,
+ const nsTArray<FallibleTArray<StructuredCloneFile>>& aFiles);
+
+ // Must call SendResponseInternal!
+ bool
+ SendResponse(const CursorResponse& aResponse) = delete;
+
+ // IPDL methods.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ RecvDeleteMe() override;
+
+ virtual bool
+ RecvContinue(const CursorRequestParams& aParams) override;
+
+ bool
+ IsLocaleAware() const {
+ return !mLocale.IsEmpty();
+ }
+};
+
+class Cursor::CursorOpBase
+ : public TransactionDatabaseOperationBase
+{
+protected:
+ RefPtr<Cursor> mCursor;
+ nsTArray<FallibleTArray<StructuredCloneFile>> mFiles;
+
+ CursorResponse mResponse;
+
+#ifdef DEBUG
+ bool mResponseSent;
+#endif
+
+protected:
+ explicit CursorOpBase(Cursor* aCursor)
+ : TransactionDatabaseOperationBase(aCursor->mTransaction)
+ , mCursor(aCursor)
+#ifdef DEBUG
+ , mResponseSent(false)
+#endif
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aCursor);
+ }
+
+ virtual
+ ~CursorOpBase()
+ { }
+
+ virtual bool
+ SendFailureResult(nsresult aResultCode) override;
+
+ virtual void
+ Cleanup() override;
+
+ nsresult
+ PopulateResponseFromStatement(DatabaseConnection::CachedStatement& aStmt,
+ bool aInitializeResponse);
+};
+
+class Cursor::OpenOp final
+ : public Cursor::CursorOpBase
+{
+ friend class Cursor;
+
+ const OptionalKeyRange mOptionalKeyRange;
+
+private:
+ // Only created by Cursor.
+ OpenOp(Cursor* aCursor,
+ const OptionalKeyRange& aOptionalKeyRange)
+ : CursorOpBase(aCursor)
+ , mOptionalKeyRange(aOptionalKeyRange)
+ { }
+
+ // Reference counted.
+ ~OpenOp()
+ { }
+
+ void
+ GetRangeKeyInfo(bool aLowerBound, Key* aKey, bool* aOpen);
+
+ nsresult
+ DoObjectStoreDatabaseWork(DatabaseConnection* aConnection);
+
+ nsresult
+ DoObjectStoreKeyDatabaseWork(DatabaseConnection* aConnection);
+
+ nsresult
+ DoIndexDatabaseWork(DatabaseConnection* aConnection);
+
+ nsresult
+ DoIndexKeyDatabaseWork(DatabaseConnection* aConnection);
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual nsresult
+ SendSuccessResult() override;
+};
+
+class Cursor::ContinueOp final
+ : public Cursor::CursorOpBase
+{
+ friend class Cursor;
+
+ const CursorRequestParams mParams;
+
+private:
+ // Only created by Cursor.
+ ContinueOp(Cursor* aCursor,
+ const CursorRequestParams& aParams)
+ : CursorOpBase(aCursor)
+ , mParams(aParams)
+ {
+ MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
+ }
+
+ // Reference counted.
+ ~ContinueOp()
+ { }
+
+ virtual nsresult
+ DoDatabaseWork(DatabaseConnection* aConnection) override;
+
+ virtual nsresult
+ SendSuccessResult() override;
+};
+
+class Utils final
+ : public PBackgroundIndexedDBUtilsParent
+{
+#ifdef DEBUG
+ bool mActorDestroyed;
+#endif
+
+public:
+ Utils();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Utils)
+
+private:
+ // Reference counted.
+ ~Utils();
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ RecvDeleteMe() override;
+
+ virtual bool
+ RecvGetFileReferences(const PersistenceType& aPersistenceType,
+ const nsCString& aOrigin,
+ const nsString& aDatabaseName,
+ const int64_t& aFileId,
+ int32_t* aRefCnt,
+ int32_t* aDBRefCnt,
+ int32_t* aSliceRefCnt,
+ bool* aResult) override;
+};
+
+class GetFileReferencesHelper final
+ : public Runnable
+{
+ PersistenceType mPersistenceType;
+ nsCString mOrigin;
+ nsString mDatabaseName;
+ int64_t mFileId;
+
+ mozilla::Mutex mMutex;
+ mozilla::CondVar mCondVar;
+ int32_t mMemRefCnt;
+ int32_t mDBRefCnt;
+ int32_t mSliceRefCnt;
+ bool mResult;
+ bool mWaiting;
+
+public:
+ GetFileReferencesHelper(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ const nsAString& aDatabaseName,
+ int64_t aFileId)
+ : mPersistenceType(aPersistenceType)
+ , mOrigin(aOrigin)
+ , mDatabaseName(aDatabaseName)
+ , mFileId(aFileId)
+ , mMutex("GetFileReferencesHelper::mMutex")
+ , mCondVar(mMutex, "GetFileReferencesHelper::mCondVar")
+ , mMemRefCnt(-1)
+ , mDBRefCnt(-1)
+ , mSliceRefCnt(-1)
+ , mResult(false)
+ , mWaiting(true)
+ { }
+
+ nsresult
+ DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
+ int32_t* aDBRefCnt,
+ int32_t* aSliceRefCnt,
+ bool* aResult);
+
+private:
+ ~GetFileReferencesHelper() {}
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class FlushPendingFileDeletionsRunnable final
+ : public Runnable
+{
+private:
+ ~FlushPendingFileDeletionsRunnable()
+ { }
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class PermissionRequestHelper final
+ : public PermissionRequestBase
+ , public PIndexedDBPermissionRequestParent
+{
+ bool mActorDestroyed;
+
+public:
+ PermissionRequestHelper(Element* aOwnerElement,
+ nsIPrincipal* aPrincipal)
+ : PermissionRequestBase(aOwnerElement, aPrincipal)
+ , mActorDestroyed(false)
+ { }
+
+protected:
+ ~PermissionRequestHelper()
+ { }
+
+private:
+ virtual void
+ OnPromptComplete(PermissionValue aPermissionValue) override;
+
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+/*******************************************************************************
+ * Other class declarations
+ ******************************************************************************/
+
+struct DatabaseActorInfo final
+{
+ friend class nsAutoPtr<DatabaseActorInfo>;
+
+ RefPtr<FullDatabaseMetadata> mMetadata;
+ nsTArray<Database*> mLiveDatabases;
+ RefPtr<FactoryOp> mWaitingFactoryOp;
+
+ DatabaseActorInfo(FullDatabaseMetadata* aMetadata,
+ Database* aDatabase)
+ : mMetadata(aMetadata)
+ {
+ MOZ_ASSERT(aDatabase);
+
+ MOZ_COUNT_CTOR(DatabaseActorInfo);
+
+ mLiveDatabases.AppendElement(aDatabase);
+ }
+
+private:
+ ~DatabaseActorInfo()
+ {
+ MOZ_ASSERT(mLiveDatabases.IsEmpty());
+ MOZ_ASSERT(!mWaitingFactoryOp ||
+ !mWaitingFactoryOp->HasBlockedDatabases());
+
+ MOZ_COUNT_DTOR(DatabaseActorInfo);
+ }
+};
+
+class DatabaseLoggingInfo final
+{
+#ifdef DEBUG
+ // Just for potential warnings.
+ friend class Factory;
+#endif
+
+ LoggingInfo mLoggingInfo;
+
+public:
+ explicit
+ DatabaseLoggingInfo(const LoggingInfo& aLoggingInfo)
+ : mLoggingInfo(aLoggingInfo)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aLoggingInfo.nextTransactionSerialNumber());
+ MOZ_ASSERT(aLoggingInfo.nextVersionChangeTransactionSerialNumber());
+ MOZ_ASSERT(aLoggingInfo.nextRequestSerialNumber());
+ }
+
+ const nsID&
+ Id() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mLoggingInfo.backgroundChildLoggingId();
+ }
+
+ int64_t
+ NextTransactionSN(IDBTransaction::Mode aMode)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mLoggingInfo.nextTransactionSerialNumber() < INT64_MAX);
+ MOZ_ASSERT(mLoggingInfo.nextVersionChangeTransactionSerialNumber() >
+ INT64_MIN);
+
+ if (aMode == IDBTransaction::VERSION_CHANGE) {
+ return mLoggingInfo.nextVersionChangeTransactionSerialNumber()--;
+ }
+
+ return mLoggingInfo.nextTransactionSerialNumber()++;
+ }
+
+ uint64_t
+ NextRequestSN()
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mLoggingInfo.nextRequestSerialNumber() < UINT64_MAX);
+
+ return mLoggingInfo.nextRequestSerialNumber()++;
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(DatabaseLoggingInfo)
+
+private:
+ ~DatabaseLoggingInfo();
+};
+
+class BlobImplStoredFile final
+ : public BlobImplFile
+{
+ RefPtr<FileInfo> mFileInfo;
+ const bool mSnapshot;
+
+public:
+ BlobImplStoredFile(nsIFile* aFile, FileInfo* aFileInfo, bool aSnapshot)
+ : BlobImplFile(aFile)
+ , mFileInfo(aFileInfo)
+ , mSnapshot(aSnapshot)
+ {
+ AssertIsOnBackgroundThread();
+
+ // Getting the content type is not currently supported off the main thread.
+ // This isn't a problem here because:
+ //
+ // 1. The real content type is stored in the structured clone data and
+ // that's all that the DOM will see. This blob's data will be updated
+ // during RecvSetMysteryBlobInfo().
+ // 2. The nsExternalHelperAppService guesses the content type based only
+ // on the file extension. Our stored files have no extension so the
+ // current code path fails and sets the content type to the empty
+ // string.
+ //
+ // So, this is a hack to keep the nsExternalHelperAppService out of the
+ // picture entirely. Eventually we should probably fix this some other way.
+ mContentType.Truncate();
+ mIsFile = false;
+ }
+
+ bool
+ IsShareable(FileManager* aFileManager) const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mFileInfo->Manager() == aFileManager && !mSnapshot;
+ }
+
+ FileInfo*
+ GetFileInfo() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mFileInfo;
+ }
+
+private:
+ ~BlobImplStoredFile()
+ { }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECLARE_STATIC_IID_ACCESSOR(BLOB_IMPL_STORED_FILE_IID)
+
+ virtual int64_t
+ GetFileId() override
+ {
+ MOZ_ASSERT(mFileInfo);
+
+ return mFileInfo->Id();
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(BlobImplStoredFile, BLOB_IMPL_STORED_FILE_IID)
+
+class QuotaClient final
+ : public mozilla::dom::quota::Client
+{
+ static QuotaClient* sInstance;
+
+ nsCOMPtr<nsIEventTarget> mBackgroundThread;
+ nsTArray<RefPtr<Maintenance>> mMaintenanceQueue;
+ RefPtr<Maintenance> mCurrentMaintenance;
+ RefPtr<nsThreadPool> mMaintenanceThreadPool;
+ bool mShutdownRequested;
+
+public:
+ QuotaClient();
+
+ static QuotaClient*
+ GetInstance()
+ {
+ AssertIsOnBackgroundThread();
+
+ return sInstance;
+ }
+
+ static bool
+ IsShuttingDownOnBackgroundThread()
+ {
+ AssertIsOnBackgroundThread();
+
+ if (sInstance) {
+ return sInstance->IsShuttingDown();
+ }
+
+ return QuotaManager::IsShuttingDown();
+ }
+
+ static bool
+ IsShuttingDownOnNonBackgroundThread()
+ {
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ return QuotaManager::IsShuttingDown();
+ }
+
+ nsIEventTarget*
+ BackgroundThread() const
+ {
+ MOZ_ASSERT(mBackgroundThread);
+ return mBackgroundThread;
+ }
+
+ bool
+ IsShuttingDown() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mShutdownRequested;
+ }
+
+ already_AddRefed<Maintenance>
+ GetCurrentMaintenance() const
+ {
+ RefPtr<Maintenance> result = mCurrentMaintenance;
+ return result.forget();
+ }
+
+ void
+ NoteFinishedMaintenance(Maintenance* aMaintenance)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aMaintenance);
+ MOZ_ASSERT(mCurrentMaintenance == aMaintenance);
+
+ mCurrentMaintenance = nullptr;
+ ProcessMaintenanceQueue();
+ }
+
+ nsThreadPool*
+ GetOrCreateThreadPool();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaClient, override)
+
+ virtual mozilla::dom::quota::Client::Type
+ GetType() override;
+
+ virtual nsresult
+ InitOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ const AtomicBool& aCanceled,
+ UsageInfo* aUsageInfo) override;
+
+ virtual nsresult
+ GetUsageForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ const AtomicBool& aCanceled,
+ UsageInfo* aUsageInfo) override;
+
+ virtual void
+ OnOriginClearCompleted(PersistenceType aPersistenceType,
+ const nsACString& aOrigin)
+ override;
+
+ virtual void
+ ReleaseIOThreadObjects() override;
+
+ virtual void
+ AbortOperations(const nsACString& aOrigin) override;
+
+ virtual void
+ AbortOperationsForProcess(ContentParentId aContentParentId) override;
+
+ virtual void
+ StartIdleMaintenance() override;
+
+ virtual void
+ StopIdleMaintenance() override;
+
+ virtual void
+ ShutdownWorkThreads() override;
+
+ virtual void
+ DidInitialize(QuotaManager* aQuotaManager) override;
+
+ virtual void
+ WillShutdown() override;
+
+private:
+ ~QuotaClient();
+
+ nsresult
+ GetDirectory(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ nsIFile** aDirectory);
+
+ nsresult
+ GetUsageForDirectoryInternal(nsIFile* aDirectory,
+ const AtomicBool& aCanceled,
+ UsageInfo* aUsageInfo,
+ bool aDatabaseFiles);
+
+ // Runs on the PBackground thread. Checks to see if there's a queued
+ // Maintenance to run.
+ void
+ ProcessMaintenanceQueue();
+};
+
+class Maintenance final
+ : public Runnable
+ , public OpenDirectoryListener
+{
+ struct DirectoryInfo;
+
+ enum class State
+ {
+ // Newly created on the PBackground thread. Will proceed immediately or be
+ // added to the maintenance queue. The next step is either
+ // DirectoryOpenPending if IndexedDatabaseManager is running, or
+ // CreateIndexedDatabaseManager if not.
+ Initial = 0,
+
+ // Create IndexedDatabaseManager on the main thread. The next step is either
+ // Finishing if IndexedDatabaseManager initialization fails, or
+ // IndexedDatabaseManagerOpen if initialization succeeds.
+ CreateIndexedDatabaseManager,
+
+ // Call OpenDirectory() on the PBackground thread. The next step is
+ // DirectoryOpenPending.
+ IndexedDatabaseManagerOpen,
+
+ // Waiting for directory open allowed on the PBackground thread. The next
+ // step is either Finishing if directory lock failed to acquire, or
+ // DirectoryWorkOpen if directory lock is acquired.
+ DirectoryOpenPending,
+
+ // Waiting to do/doing work on the QuotaManager IO thread. The next step is
+ // BeginDatabaseMaintenance.
+ DirectoryWorkOpen,
+
+ // Dispatching a runnable for each database on the PBackground thread. The
+ // next state is either WaitingForDatabaseMaintenancesToComplete if at least
+ // one runnable has been dispatched, or Finishing otherwise.
+ BeginDatabaseMaintenance,
+
+ // Waiting for DatabaseMaintenance to finish on maintenance thread pool.
+ // The next state is Finishing if the last runnable has finished.
+ WaitingForDatabaseMaintenancesToComplete,
+
+ // Waiting to finish/finishing on the PBackground thread. The next step is
+ // Completed.
+ Finishing,
+
+ // All done.
+ Complete
+ };
+
+ RefPtr<QuotaClient> mQuotaClient;
+ PRTime mStartTime;
+ RefPtr<DirectoryLock> mDirectoryLock;
+ nsTArray<DirectoryInfo> mDirectoryInfos;
+ nsDataHashtable<nsStringHashKey, DatabaseMaintenance*> mDatabaseMaintenances;
+ nsresult mResultCode;
+ Atomic<bool> mAborted;
+ State mState;
+
+public:
+ explicit Maintenance(QuotaClient* aQuotaClient)
+ : mQuotaClient(aQuotaClient)
+ , mStartTime(PR_Now())
+ , mResultCode(NS_OK)
+ , mAborted(false)
+ , mState(State::Initial)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aQuotaClient);
+ MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
+ MOZ_ASSERT(mStartTime);
+ }
+
+ nsIEventTarget*
+ BackgroundThread() const
+ {
+ MOZ_ASSERT(mQuotaClient);
+ return mQuotaClient->BackgroundThread();
+ }
+
+ PRTime
+ StartTime() const
+ {
+ return mStartTime;
+ }
+
+ bool
+ IsAborted() const
+ {
+ return mAborted;
+ }
+
+ void
+ RunImmediately()
+ {
+ MOZ_ASSERT(mState == State::Initial);
+
+ Unused << this->Run();
+ }
+
+ void
+ Abort()
+ {
+ AssertIsOnBackgroundThread();
+
+ mAborted = true;
+ }
+
+ void
+ RegisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
+
+ void
+ UnregisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
+
+ already_AddRefed<DatabaseMaintenance>
+ GetDatabaseMaintenance(const nsAString& aDatabasePath) const
+ {
+ AssertIsOnBackgroundThread();
+
+ RefPtr<DatabaseMaintenance> result =
+ mDatabaseMaintenances.Get(aDatabasePath);
+ return result.forget();
+ }
+
+private:
+ ~Maintenance()
+ {
+ MOZ_ASSERT(mState == State::Complete);
+ MOZ_ASSERT(!mDatabaseMaintenances.Count());
+ }
+
+ // Runs on the PBackground thread. Checks if IndexedDatabaseManager is
+ // running. Calls OpenDirectory() or dispatches to the main thread on which
+ // CreateIndexedDatabaseManager() is called.
+ nsresult
+ Start();
+
+ // Runs on the main thread. Once IndexedDatabaseManager is created it will
+ // dispatch to the PBackground thread on which OpenDirectory() is called.
+ nsresult
+ CreateIndexedDatabaseManager();
+
+ // Runs on the PBackground thread. Once QuotaManager has given a lock it will
+ // call DirectoryOpen().
+ nsresult
+ OpenDirectory();
+
+ // Runs on the PBackground thread. Dispatches to the QuotaManager I/O thread.
+ nsresult
+ DirectoryOpen();
+
+ // Runs on the QuotaManager I/O thread. Once it finds databases it will
+ // dispatch to the PBackground thread on which BeginDatabaseMaintenance()
+ // is called.
+ nsresult
+ DirectoryWork();
+
+ // Runs on the PBackground thread. It dispatches a runnable for each database.
+ nsresult
+ BeginDatabaseMaintenance();
+
+ // Runs on the PBackground thread. Called when the maintenance is finished or
+ // if any of above methods fails.
+ void
+ Finish();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIRUNNABLE
+
+ // OpenDirectoryListener overrides.
+ virtual void
+ DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+ virtual void
+ DirectoryLockFailed() override;
+};
+
+struct Maintenance::DirectoryInfo final
+{
+ const nsCString mGroup;
+ const nsCString mOrigin;
+ nsTArray<nsString> mDatabasePaths;
+ const PersistenceType mPersistenceType;
+
+ DirectoryInfo(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ nsTArray<nsString>&& aDatabasePaths)
+ : mGroup(aGroup)
+ , mOrigin(aOrigin)
+ , mDatabasePaths(Move(aDatabasePaths))
+ , mPersistenceType(aPersistenceType)
+ {
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
+ MOZ_ASSERT(!aGroup.IsEmpty());
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+#ifdef DEBUG
+ MOZ_ASSERT(!mDatabasePaths.IsEmpty());
+ for (const nsString& databasePath : mDatabasePaths) {
+ MOZ_ASSERT(!databasePath.IsEmpty());
+ }
+#endif
+
+ MOZ_COUNT_CTOR(Maintenance::DirectoryInfo);
+ }
+
+ DirectoryInfo(DirectoryInfo&& aOther)
+ : mGroup(Move(aOther.mGroup))
+ , mOrigin(Move(aOther.mOrigin))
+ , mDatabasePaths(Move(aOther.mDatabasePaths))
+ , mPersistenceType(Move(aOther.mPersistenceType))
+ {
+#ifdef DEBUG
+ MOZ_ASSERT(!mDatabasePaths.IsEmpty());
+ for (const nsString& databasePath : mDatabasePaths) {
+ MOZ_ASSERT(!databasePath.IsEmpty());
+ }
+#endif
+
+ MOZ_COUNT_CTOR(Maintenance::DirectoryInfo);
+ }
+
+ ~DirectoryInfo()
+ {
+ MOZ_COUNT_DTOR(Maintenance::DirectoryInfo);
+ }
+
+ DirectoryInfo(const DirectoryInfo& aOther) = delete;
+};
+
+class DatabaseMaintenance final
+ : public Runnable
+{
+ // The minimum amount of time that has passed since the last vacuum before we
+ // will attempt to analyze the database for fragmentation.
+ static const PRTime kMinVacuumAge =
+ PRTime(PR_USEC_PER_SEC) * 60 * 60 * 24 * 7;
+
+ // If the percent of database pages that are not in contiguous order is higher
+ // than this percentage we will attempt a vacuum.
+ static const int32_t kPercentUnorderedThreshold = 30;
+
+ // If the percent of file size growth since the last vacuum is higher than
+ // this percentage we will attempt a vacuum.
+ static const int32_t kPercentFileSizeGrowthThreshold = 10;
+
+ // The number of freelist pages beyond which we will favor an incremental
+ // vacuum over a full vacuum.
+ static const int32_t kMaxFreelistThreshold = 5;
+
+ // If the percent of unused file bytes in the database exceeds this percentage
+ // then we will attempt a full vacuum.
+ static const int32_t kPercentUnusedThreshold = 20;
+
+ class AutoProgressHandler;
+
+ enum class MaintenanceAction
+ {
+ Nothing = 0,
+ IncrementalVacuum,
+ FullVacuum
+ };
+
+ RefPtr<Maintenance> mMaintenance;
+ const nsCString mGroup;
+ const nsCString mOrigin;
+ const nsString mDatabasePath;
+ nsCOMPtr<nsIRunnable> mCompleteCallback;
+ const PersistenceType mPersistenceType;
+
+public:
+ DatabaseMaintenance(Maintenance* aMaintenance,
+ PersistenceType aPersistenceType,
+ const nsCString& aGroup,
+ const nsCString& aOrigin,
+ const nsString& aDatabasePath)
+ : mMaintenance(aMaintenance)
+ , mGroup(aGroup)
+ , mOrigin(aOrigin)
+ , mDatabasePath(aDatabasePath)
+ , mPersistenceType(aPersistenceType)
+ { }
+
+ const nsString&
+ DatabasePath() const
+ {
+ return mDatabasePath;
+ }
+
+ void
+ WaitForCompletion(nsIRunnable* aCallback)
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mCompleteCallback);
+
+ mCompleteCallback = aCallback;
+ }
+
+private:
+ ~DatabaseMaintenance()
+ { }
+
+ // Runs on maintenance thread pool. Does maintenance on the database.
+ void
+ PerformMaintenanceOnDatabase();
+
+ // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
+ nsresult
+ CheckIntegrity(mozIStorageConnection* aConnection, bool* aOk);
+
+ // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
+ nsresult
+ DetermineMaintenanceAction(mozIStorageConnection* aConnection,
+ nsIFile* aDatabaseFile,
+ MaintenanceAction* aMaintenanceAction);
+
+ // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
+ void
+ IncrementalVacuum(mozIStorageConnection* aConnection);
+
+ // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
+ void
+ FullVacuum(mozIStorageConnection* aConnection,
+ nsIFile* aDatabaseFile);
+
+ // Runs on the PBackground thread. It dispatches a complete callback and
+ // unregisters from Maintenance.
+ void
+ RunOnOwningThread();
+
+ // Runs on maintenance thread pool. Once it performs database maintenance
+ // it will dispatch to the PBackground thread on which RunOnOwningThread()
+ // is called.
+ void
+ RunOnConnectionThread();
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class MOZ_STACK_CLASS DatabaseMaintenance::AutoProgressHandler final
+ : public mozIStorageProgressHandler
+{
+ Maintenance* mMaintenance;
+ mozIStorageConnection* mConnection;
+
+ NS_DECL_OWNINGTHREAD
+
+#ifdef DEBUG
+ // This class is stack-based so we never actually allow AddRef/Release to do
+ // anything. But we need to know if any consumer *thinks* that they have a
+ // reference to this object so we track the reference countin DEBUG builds.
+ nsrefcnt mDEBUGRefCnt;
+#endif
+
+public:
+ explicit AutoProgressHandler(Maintenance* aMaintenance)
+ : mMaintenance(aMaintenance)
+ , mConnection(nullptr)
+#ifdef DEBUG
+ , mDEBUGRefCnt(0)
+#endif
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
+ MOZ_ASSERT(aMaintenance);
+ }
+
+ ~AutoProgressHandler()
+ {
+ NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
+
+ if (mConnection) {
+ Unregister();
+ }
+
+ MOZ_ASSERT(!mDEBUGRefCnt);
+ }
+
+ nsresult
+ Register(mozIStorageConnection* aConnection);
+
+ // We don't want the mRefCnt member but this class does not "inherit"
+ // nsISupports.
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ void
+ Unregister();
+
+ NS_DECL_MOZISTORAGEPROGRESSHANDLER
+
+ // Not available for the heap!
+ void*
+ operator new(size_t) = delete;
+ void*
+ operator new[](size_t) = delete;
+ void
+ operator delete(void*) = delete;
+ void
+ operator delete[](void*) = delete;
+};
+
+class IntString : public nsAutoString
+{
+public:
+ explicit
+ IntString(int64_t aInteger)
+ {
+ AppendInt(aInteger);
+ }
+};
+
+#ifdef DEBUG
+
+class DEBUGThreadSlower final
+ : public nsIThreadObserver
+{
+public:
+ DEBUGThreadSlower()
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(kDEBUGThreadSleepMS);
+ }
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~DEBUGThreadSlower()
+ {
+ AssertIsOnBackgroundThread();
+ }
+
+ NS_DECL_NSITHREADOBSERVER
+};
+
+#endif // DEBUG
+
+/*******************************************************************************
+ * Helper classes
+ ******************************************************************************/
+
+class MOZ_STACK_CLASS FileHelper final
+{
+ RefPtr<FileManager> mFileManager;
+
+ nsCOMPtr<nsIFile> mFileDirectory;
+ nsCOMPtr<nsIFile> mJournalDirectory;
+
+public:
+ explicit FileHelper(FileManager* aFileManager)
+ : mFileManager(aFileManager)
+ { }
+
+ nsresult
+ Init();
+
+ already_AddRefed<nsIFile>
+ GetFile(FileInfo* aFileInfo);
+
+ already_AddRefed<nsIFile>
+ GetCheckedFile(FileInfo* aFileInfo);
+
+ already_AddRefed<nsIFile>
+ GetJournalFile(FileInfo* aFileInfo);
+
+ nsresult
+ CreateFileFromStream(nsIFile* aFile,
+ nsIFile* aJournalFile,
+ nsIInputStream* aInputStream,
+ bool aCompress);
+
+ nsresult
+ ReplaceFile(nsIFile* aFile,
+ nsIFile* aNewFile,
+ nsIFile* aNewJournalFile);
+
+ nsresult
+ RemoveFile(nsIFile* aFile,
+ nsIFile* aJournalFile);
+
+ already_AddRefed<FileInfo>
+ GetNewFileInfo();
+
+private:
+ nsresult
+ SyncCopy(nsIInputStream* aInputStream,
+ nsIOutputStream* aOutputStream,
+ char* aBuffer,
+ uint32_t aBufferSize);
+};
+
+/*******************************************************************************
+ * Helper Functions
+ ******************************************************************************/
+
+bool
+TokenizerIgnoreNothing(char16_t /* aChar */)
+{
+ return false;
+}
+
+nsresult
+DeserializeStructuredCloneFile(FileManager* aFileManager,
+ const nsString& aText,
+ StructuredCloneFile* aFile)
+{
+ MOZ_ASSERT(!aText.IsEmpty());
+ MOZ_ASSERT(aFile);
+
+ StructuredCloneFile::FileType type;
+
+ switch (aText.First()) {
+ case char16_t('-'):
+ type = StructuredCloneFile::eMutableFile;
+ break;
+
+ case char16_t('.'):
+ type = StructuredCloneFile::eStructuredClone;
+ break;
+
+ case char16_t('/'):
+ type = StructuredCloneFile::eWasmBytecode;
+ break;
+
+ case char16_t('\\'):
+ type = StructuredCloneFile::eWasmCompiled;
+ break;
+
+ default:
+ type = StructuredCloneFile::eBlob;
+ }
+
+ nsresult rv;
+ int32_t id;
+
+ if (type == StructuredCloneFile::eBlob) {
+ id = aText.ToInteger(&rv);
+ } else {
+ nsString text(Substring(aText, 1));
+
+ id = text.ToInteger(&rv);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<FileInfo> fileInfo = aFileManager->GetFileInfo(id);
+ MOZ_ASSERT(fileInfo);
+
+ aFile->mFileInfo.swap(fileInfo);
+ aFile->mType = type;
+
+ return NS_OK;
+}
+
+nsresult
+CheckWasmModule(FileHelper* aFileHelper,
+ StructuredCloneFile* aBytecodeFile,
+ StructuredCloneFile* aCompiledFile)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aFileHelper);
+ MOZ_ASSERT(aBytecodeFile);
+ MOZ_ASSERT(aCompiledFile);
+ MOZ_ASSERT(aBytecodeFile->mType == StructuredCloneFile::eWasmBytecode);
+ MOZ_ASSERT(aCompiledFile->mType == StructuredCloneFile::eWasmCompiled);
+
+ nsCOMPtr<nsIFile> compiledFile =
+ aFileHelper->GetCheckedFile(aCompiledFile->mFileInfo);
+ if (NS_WARN_IF(!compiledFile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ bool match;
+ {
+ ScopedPRFileDesc compiledFileDesc;
+ rv = compiledFile->OpenNSPRFileDesc(PR_RDONLY,
+ 0644,
+ &compiledFileDesc.rwget());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ JS::BuildIdCharVector buildId;
+ bool ok = GetBuildId(&buildId);
+ if (NS_WARN_IF(!ok)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ match = JS::CompiledWasmModuleAssumptionsMatch(compiledFileDesc,
+ Move(buildId));
+ }
+ if (match) {
+ return NS_OK;
+ }
+
+ // Re-compile the module. It would be preferable to do this in the child
+ // (content process) instead of here in the parent, but that would be way more
+ // complex and without significant memory allocation or security benefits.
+ // See the discussion starting from
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1312808#c9 for more details.
+ nsCOMPtr<nsIFile> bytecodeFile =
+ aFileHelper->GetCheckedFile(aBytecodeFile->mFileInfo);
+ if (NS_WARN_IF(!bytecodeFile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ScopedPRFileDesc bytecodeFileDesc;
+ rv = bytecodeFile->OpenNSPRFileDesc(PR_RDONLY,
+ 0644,
+ &bytecodeFileDesc.rwget());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ JS::BuildIdCharVector buildId;
+ bool ok = GetBuildId(&buildId);
+ if (NS_WARN_IF(!ok)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<JS::WasmModule> module = JS::DeserializeWasmModule(bytecodeFileDesc,
+ nullptr,
+ Move(buildId),
+ nullptr,
+ 0,
+ 0);
+ if (NS_WARN_IF(!module)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ size_t compiledSize;
+ module->serializedSize(nullptr, &compiledSize);
+
+ UniquePtr<uint8_t[]> compiled(new (fallible) uint8_t[compiledSize]);
+ if (NS_WARN_IF(!compiled)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ module->serialize(nullptr, 0, compiled.get(), compiledSize);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewByteInputStream(getter_AddRefs(inputStream),
+ reinterpret_cast<const char*>(compiled.get()),
+ compiledSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<FileInfo> newFileInfo = aFileHelper->GetNewFileInfo();
+
+ nsCOMPtr<nsIFile> newFile = aFileHelper->GetFile(newFileInfo);
+ if (NS_WARN_IF(!newFile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> newJournalFile =
+ aFileHelper->GetJournalFile(newFileInfo);
+ if (NS_WARN_IF(!newJournalFile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = aFileHelper->CreateFileFromStream(newFile,
+ newJournalFile,
+ inputStream,
+ /* aCompress */ false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsresult rv2 = aFileHelper->RemoveFile(newFile, newJournalFile);
+ if (NS_WARN_IF(NS_FAILED(rv2))) {
+ return rv;
+ }
+ return rv;
+ }
+
+ rv = aFileHelper->ReplaceFile(compiledFile, newFile, newJournalFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsresult rv2 = aFileHelper->RemoveFile(newFile, newJournalFile);
+ if (NS_WARN_IF(NS_FAILED(rv2))) {
+ return rv;
+ }
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DeserializeStructuredCloneFiles(FileManager* aFileManager,
+ const nsAString& aText,
+ nsTArray<StructuredCloneFile>& aResult,
+ bool* aHasPreprocessInfo)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
+ tokenizer(aText, ' ');
+
+ nsAutoString token;
+ nsresult rv;
+ Maybe<FileHelper> fileHelper;
+
+ while (tokenizer.hasMoreTokens()) {
+ token = tokenizer.nextToken();
+ MOZ_ASSERT(!token.IsEmpty());
+
+ StructuredCloneFile* file = aResult.AppendElement();
+ rv = DeserializeStructuredCloneFile(aFileManager, token, file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!aHasPreprocessInfo) {
+ continue;
+ }
+
+ if (file->mType == StructuredCloneFile::eWasmBytecode) {
+ *aHasPreprocessInfo = true;
+ }
+ else if (file->mType == StructuredCloneFile::eWasmCompiled) {
+ if (fileHelper.isNothing()) {
+ fileHelper.emplace(aFileManager);
+
+ rv = fileHelper->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ MOZ_ASSERT(aResult.Length() > 1);
+ MOZ_ASSERT(aResult[aResult.Length() - 2].mType ==
+ StructuredCloneFile::eWasmBytecode);
+
+ StructuredCloneFile* previousFile = &aResult[aResult.Length() - 2];
+
+ rv = CheckWasmModule(fileHelper.ptr(), previousFile, file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aHasPreprocessInfo = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+GetDatabaseBaseFilename(const nsAString& aFilename,
+ nsDependentSubstring& aDatabaseBaseFilename)
+{
+ MOZ_ASSERT(!aFilename.IsEmpty());
+ MOZ_ASSERT(aDatabaseBaseFilename.IsEmpty());
+
+ NS_NAMED_LITERAL_STRING(sqlite, ".sqlite");
+
+ if (!StringEndsWith(aFilename, sqlite) ||
+ aFilename.Length() == sqlite.Length()) {
+ return false;
+ }
+
+ MOZ_ASSERT(aFilename.Length() > sqlite.Length());
+
+ aDatabaseBaseFilename.Rebind(aFilename,
+ 0,
+ aFilename.Length() - sqlite.Length());
+ return true;
+}
+
+nsresult
+SerializeStructuredCloneFiles(
+ PBackgroundParent* aBackgroundActor,
+ Database* aDatabase,
+ const nsTArray<StructuredCloneFile>& aFiles,
+ bool aForPreprocess,
+ FallibleTArray<SerializedStructuredCloneFile>& aResult)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aBackgroundActor);
+ MOZ_ASSERT(aDatabase);
+ MOZ_ASSERT(aResult.IsEmpty());
+
+ if (aFiles.IsEmpty()) {
+ return NS_OK;
+ }
+
+ FileManager* fileManager = aDatabase->GetFileManager();
+
+ nsCOMPtr<nsIFile> directory = fileManager->GetCheckedDirectory();
+ if (NS_WARN_IF(!directory)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ const uint32_t count = aFiles.Length();
+
+ if (NS_WARN_IF(!aResult.SetCapacity(count, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t index = 0; index < count; index++) {
+ const StructuredCloneFile& file = aFiles[index];
+
+ if (aForPreprocess &&
+ file.mType != StructuredCloneFile::eWasmBytecode &&
+ file.mType != StructuredCloneFile::eWasmCompiled) {
+ continue;
+ }
+
+ const int64_t fileId = file.mFileInfo->Id();
+ MOZ_ASSERT(fileId > 0);
+
+ nsCOMPtr<nsIFile> nativeFile =
+ fileManager->GetCheckedFileForId(directory, fileId);
+ if (NS_WARN_IF(!nativeFile)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ switch (file.mType) {
+ case StructuredCloneFile::eBlob: {
+ RefPtr<BlobImpl> impl = new BlobImplStoredFile(nativeFile,
+ file.mFileInfo,
+ /* aSnapshot */ false);
+
+ PBlobParent* actor =
+ BackgroundParent::GetOrCreateActorForBlobImpl(aBackgroundActor, impl);
+ if (!actor) {
+ // This can only fail if the child has crashed.
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ SerializedStructuredCloneFile* file = aResult.AppendElement(fallible);
+ MOZ_ASSERT(file);
+
+ file->file() = actor;
+ file->type() = StructuredCloneFile::eBlob;
+
+ break;
+ }
+
+ case StructuredCloneFile::eMutableFile: {
+ if (aDatabase->IsFileHandleDisabled()) {
+ SerializedStructuredCloneFile* file = aResult.AppendElement(fallible);
+ MOZ_ASSERT(file);
+
+ file->file() = null_t();
+ file->type() = StructuredCloneFile::eMutableFile;
+ } else {
+ RefPtr<MutableFile> actor =
+ MutableFile::Create(nativeFile, aDatabase, file.mFileInfo);
+ if (!actor) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ // Transfer ownership to IPDL.
+ actor->SetActorAlive();
+
+ if (!aDatabase->SendPBackgroundMutableFileConstructor(actor,
+ EmptyString(),
+ EmptyString())) {
+ // This can only fail if the child has crashed.
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ SerializedStructuredCloneFile* file = aResult.AppendElement(fallible);
+ MOZ_ASSERT(file);
+
+ file->file() = actor;
+ file->type() = StructuredCloneFile::eMutableFile;
+ }
+
+ break;
+ }
+
+ case StructuredCloneFile::eStructuredClone: {
+ SerializedStructuredCloneFile* file = aResult.AppendElement(fallible);
+ MOZ_ASSERT(file);
+
+ file->file() = null_t();
+ file->type() = StructuredCloneFile::eStructuredClone;
+
+ break;
+ }
+
+ case StructuredCloneFile::eWasmBytecode:
+ case StructuredCloneFile::eWasmCompiled: {
+ if (!aForPreprocess) {
+ SerializedStructuredCloneFile* serializedFile =
+ aResult.AppendElement(fallible);
+ MOZ_ASSERT(serializedFile);
+
+ serializedFile->file() = null_t();
+ serializedFile->type() = file.mType;
+ } else {
+ RefPtr<BlobImpl> impl = new BlobImplStoredFile(nativeFile,
+ file.mFileInfo,
+ /* aSnapshot */ false);
+
+ PBlobParent* actor =
+ BackgroundParent::GetOrCreateActorForBlobImpl(aBackgroundActor,
+ impl);
+ if (!actor) {
+ // This can only fail if the child has crashed.
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ SerializedStructuredCloneFile* serializedFile =
+ aResult.AppendElement(fallible);
+ MOZ_ASSERT(serializedFile);
+
+ serializedFile->file() = actor;
+ serializedFile->type() = file.mType;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+GetFileForFileInfo(FileInfo* aFileInfo)
+{
+ FileManager* fileManager = aFileInfo->Manager();
+ nsCOMPtr<nsIFile> directory = fileManager->GetDirectory();
+ if (NS_WARN_IF(!directory)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFile> file = fileManager->GetFileForId(directory,
+ aFileInfo->Id());
+ if (NS_WARN_IF(!file)) {
+ return nullptr;
+ }
+
+ return file.forget();
+}
+
+/*******************************************************************************
+ * Globals
+ ******************************************************************************/
+
+// Counts the number of "live" Factory, FactoryOp and Database instances.
+uint64_t gBusyCount = 0;
+
+typedef nsTArray<RefPtr<FactoryOp>> FactoryOpArray;
+
+StaticAutoPtr<FactoryOpArray> gFactoryOps;
+
+// Maps a database id to information about live database actors.
+typedef nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>
+ DatabaseActorHashtable;
+
+StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;
+
+StaticRefPtr<ConnectionPool> gConnectionPool;
+
+StaticRefPtr<FileHandleThreadPool> gFileHandleThreadPool;
+
+typedef nsDataHashtable<nsIDHashKey, DatabaseLoggingInfo*>
+ DatabaseLoggingInfoHashtable;
+
+StaticAutoPtr<DatabaseLoggingInfoHashtable> gLoggingInfoHashtable;
+
+typedef nsDataHashtable<nsUint32HashKey, uint32_t> TelemetryIdHashtable;
+
+StaticAutoPtr<TelemetryIdHashtable> gTelemetryIdHashtable;
+
+// Protects all reads and writes to gTelemetryIdHashtable.
+StaticAutoPtr<Mutex> gTelemetryIdMutex;
+
+#ifdef DEBUG
+
+StaticRefPtr<DEBUGThreadSlower> gDEBUGThreadSlower;
+
+#endif // DEBUG
+
+
+void
+IncreaseBusyCount()
+{
+ AssertIsOnBackgroundThread();
+
+ // If this is the first instance then we need to do some initialization.
+ if (!gBusyCount) {
+ MOZ_ASSERT(!gFactoryOps);
+ gFactoryOps = new FactoryOpArray();
+
+ MOZ_ASSERT(!gLiveDatabaseHashtable);
+ gLiveDatabaseHashtable = new DatabaseActorHashtable();
+
+ MOZ_ASSERT(!gLoggingInfoHashtable);
+ gLoggingInfoHashtable = new DatabaseLoggingInfoHashtable();
+
+#ifdef DEBUG
+ if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
+ NS_WARNING("PBackground thread debugging enabled, priority has been "
+ "modified!");
+ nsCOMPtr<nsISupportsPriority> thread =
+ do_QueryInterface(NS_GetCurrentThread());
+ MOZ_ASSERT(thread);
+
+ MOZ_ALWAYS_SUCCEEDS(thread->SetPriority(kDEBUGThreadPriority));
+ }
+
+ if (kDEBUGThreadSleepMS) {
+ NS_WARNING("PBackground thread debugging enabled, sleeping after every "
+ "event!");
+ nsCOMPtr<nsIThreadInternal> thread =
+ do_QueryInterface(NS_GetCurrentThread());
+ MOZ_ASSERT(thread);
+
+ gDEBUGThreadSlower = new DEBUGThreadSlower();
+
+ MOZ_ALWAYS_SUCCEEDS(thread->AddObserver(gDEBUGThreadSlower));
+ }
+#endif // DEBUG
+ }
+
+ gBusyCount++;
+}
+
+void
+DecreaseBusyCount()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(gBusyCount);
+
+ // Clean up if there are no more instances.
+ if (--gBusyCount == 0) {
+ MOZ_ASSERT(gLoggingInfoHashtable);
+ gLoggingInfoHashtable = nullptr;
+
+ MOZ_ASSERT(gLiveDatabaseHashtable);
+ MOZ_ASSERT(!gLiveDatabaseHashtable->Count());
+ gLiveDatabaseHashtable = nullptr;
+
+ MOZ_ASSERT(gFactoryOps);
+ MOZ_ASSERT(gFactoryOps->IsEmpty());
+ gFactoryOps = nullptr;
+
+#ifdef DEBUG
+ if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
+ nsCOMPtr<nsISupportsPriority> thread =
+ do_QueryInterface(NS_GetCurrentThread());
+ MOZ_ASSERT(thread);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL));
+ }
+
+ if (kDEBUGThreadSleepMS) {
+ MOZ_ASSERT(gDEBUGThreadSlower);
+
+ nsCOMPtr<nsIThreadInternal> thread =
+ do_QueryInterface(NS_GetCurrentThread());
+ MOZ_ASSERT(thread);
+
+ MOZ_ALWAYS_SUCCEEDS(thread->RemoveObserver(gDEBUGThreadSlower));
+
+ gDEBUGThreadSlower = nullptr;
+ }
+#endif // DEBUG
+ }
+}
+
+uint32_t
+TelemetryIdForFile(nsIFile* aFile)
+{
+ // May be called on any thread!
+
+ MOZ_ASSERT(aFile);
+ MOZ_ASSERT(gTelemetryIdMutex);
+
+ // The storage directory is structured like this:
+ //
+ // <profile>/storage/<persistence>/<origin>/idb/<filename>.sqlite
+ //
+ // For the purposes of this function we're only concerned with the
+ // <persistence>, <origin>, and <filename> pieces.
+
+ nsString filename;
+ MOZ_ALWAYS_SUCCEEDS(aFile->GetLeafName(filename));
+
+ // Make sure we were given a database file.
+ NS_NAMED_LITERAL_STRING(sqliteExtension, ".sqlite");
+
+ MOZ_ASSERT(StringEndsWith(filename, sqliteExtension));
+
+ filename.Truncate(filename.Length() - sqliteExtension.Length());
+
+ // Get the "idb" directory.
+ nsCOMPtr<nsIFile> idbDirectory;
+ MOZ_ALWAYS_SUCCEEDS(aFile->GetParent(getter_AddRefs(idbDirectory)));
+
+ DebugOnly<nsString> idbLeafName;
+ MOZ_ASSERT(NS_SUCCEEDED(idbDirectory->GetLeafName(idbLeafName)));
+ MOZ_ASSERT(static_cast<nsString&>(idbLeafName).EqualsLiteral("idb"));
+
+ // Get the <origin> directory.
+ nsCOMPtr<nsIFile> originDirectory;
+ MOZ_ALWAYS_SUCCEEDS(
+ idbDirectory->GetParent(getter_AddRefs(originDirectory)));
+
+ nsString origin;
+ MOZ_ALWAYS_SUCCEEDS(originDirectory->GetLeafName(origin));
+
+ // Any databases in these directories are owned by the application and should
+ // not have their filenames masked. Hopefully they also appear in the
+ // Telemetry.cpp whitelist.
+ if (origin.EqualsLiteral("chrome") ||
+ origin.EqualsLiteral("moz-safe-about+home")) {
+ return 0;
+ }
+
+ // Get the <persistence> directory.
+ nsCOMPtr<nsIFile> persistenceDirectory;
+ MOZ_ALWAYS_SUCCEEDS(
+ originDirectory->GetParent(getter_AddRefs(persistenceDirectory)));
+
+ nsString persistence;
+ MOZ_ALWAYS_SUCCEEDS(persistenceDirectory->GetLeafName(persistence));
+
+ NS_NAMED_LITERAL_STRING(separator, "*");
+
+ uint32_t hashValue = HashString(persistence + separator +
+ origin + separator +
+ filename);
+
+ MutexAutoLock lock(*gTelemetryIdMutex);
+
+ if (!gTelemetryIdHashtable) {
+ gTelemetryIdHashtable = new TelemetryIdHashtable();
+ }
+
+ uint32_t id;
+ if (!gTelemetryIdHashtable->Get(hashValue, &id)) {
+ static uint32_t sNextId = 1;
+
+ // We're locked, no need for atomics.
+ id = sNextId++;
+
+ gTelemetryIdHashtable->Put(hashValue, id);
+ }
+
+ return id;
+}
+
+} // namespace
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+
+PBackgroundIDBFactoryParent*
+AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!aLoggingInfo.nextTransactionSerialNumber()) ||
+ NS_WARN_IF(!aLoggingInfo.nextVersionChangeTransactionSerialNumber()) ||
+ NS_WARN_IF(!aLoggingInfo.nextRequestSerialNumber())) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<Factory> actor = Factory::Create(aLoggingInfo);
+ MOZ_ASSERT(actor);
+
+ return actor.forget().take();
+}
+
+bool
+RecvPBackgroundIDBFactoryConstructor(PBackgroundIDBFactoryParent* aActor,
+ const LoggingInfo& /* aLoggingInfo */)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+
+ return true;
+}
+
+bool
+DeallocPBackgroundIDBFactoryParent(PBackgroundIDBFactoryParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<Factory> actor = dont_AddRef(static_cast<Factory*>(aActor));
+ return true;
+}
+
+PBackgroundIndexedDBUtilsParent*
+AllocPBackgroundIndexedDBUtilsParent()
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<Utils> actor = new Utils();
+
+ return actor.forget().take();
+}
+
+bool
+DeallocPBackgroundIndexedDBUtilsParent(PBackgroundIndexedDBUtilsParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<Utils> actor = dont_AddRef(static_cast<Utils*>(aActor));
+ return true;
+}
+
+bool
+RecvFlushPendingFileDeletions()
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<FlushPendingFileDeletionsRunnable> runnable =
+ new FlushPendingFileDeletionsRunnable();
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
+
+ return true;
+}
+
+PIndexedDBPermissionRequestParent*
+AllocPIndexedDBPermissionRequestParent(Element* aOwnerElement,
+ nsIPrincipal* aPrincipal)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<PermissionRequestHelper> actor =
+ new PermissionRequestHelper(aOwnerElement, aPrincipal);
+ return actor.forget().take();
+}
+
+bool
+RecvPIndexedDBPermissionRequestConstructor(
+ PIndexedDBPermissionRequestParent* aActor)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aActor);
+
+ auto* actor = static_cast<PermissionRequestHelper*>(aActor);
+
+ PermissionRequestBase::PermissionValue permission;
+ nsresult rv = actor->PromptIfNeeded(&permission);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (permission != PermissionRequestBase::kPermissionPrompt) {
+ Unused <<
+ PIndexedDBPermissionRequestParent::Send__delete__(actor, permission);
+ }
+
+ return true;
+}
+
+bool
+DeallocPIndexedDBPermissionRequestParent(
+ PIndexedDBPermissionRequestParent* aActor)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aActor);
+
+ RefPtr<PermissionRequestHelper> actor =
+ dont_AddRef(static_cast<PermissionRequestHelper*>(aActor));
+ return true;
+}
+
+already_AddRefed<mozilla::dom::quota::Client>
+CreateQuotaClient()
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<QuotaClient> client = new QuotaClient();
+ return client.forget();
+}
+
+FileHandleThreadPool*
+GetFileHandleThreadPool()
+{
+ AssertIsOnBackgroundThread();
+
+ if (!gFileHandleThreadPool) {
+ RefPtr<FileHandleThreadPool> fileHandleThreadPool =
+ FileHandleThreadPool::Create();
+ if (NS_WARN_IF(!fileHandleThreadPool)) {
+ return nullptr;
+ }
+
+ gFileHandleThreadPool = fileHandleThreadPool;
+ }
+
+ return gFileHandleThreadPool;
+}
+
+/*******************************************************************************
+ * DatabaseConnection implementation
+ ******************************************************************************/
+
+DatabaseConnection::DatabaseConnection(
+ mozIStorageConnection* aStorageConnection,
+ FileManager* aFileManager)
+ : mStorageConnection(aStorageConnection)
+ , mFileManager(aFileManager)
+ , mInReadTransaction(false)
+ , mInWriteTransaction(false)
+#ifdef DEBUG
+ , mDEBUGSavepointCount(0)
+ , mDEBUGThread(PR_GetCurrentThread())
+#endif
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(aStorageConnection);
+ MOZ_ASSERT(aFileManager);
+}
+
+DatabaseConnection::~DatabaseConnection()
+{
+ MOZ_ASSERT(!mStorageConnection);
+ MOZ_ASSERT(!mFileManager);
+ MOZ_ASSERT(!mCachedStatements.Count());
+ MOZ_ASSERT(!mUpdateRefcountFunction);
+ MOZ_ASSERT(!mInWriteTransaction);
+ MOZ_ASSERT(!mDEBUGSavepointCount);
+}
+
+nsresult
+DatabaseConnection::Init()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(!mInReadTransaction);
+ MOZ_ASSERT(!mInWriteTransaction);
+
+ CachedStatement stmt;
+ nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN;"), &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mInReadTransaction = true;
+
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::GetCachedStatement(const nsACString& aQuery,
+ CachedStatement* aCachedStatement)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(!aQuery.IsEmpty());
+ MOZ_ASSERT(aCachedStatement);
+ MOZ_ASSERT(mStorageConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::GetCachedStatement",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+
+ if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) {
+ nsresult rv =
+ mStorageConnection->CreateStatement(aQuery, getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ nsCString msg;
+ MOZ_ALWAYS_SUCCEEDS(mStorageConnection->GetLastErrorString(msg));
+
+ nsAutoCString error =
+ NS_LITERAL_CSTRING("The statement '") + aQuery +
+ NS_LITERAL_CSTRING("' failed to compile with the error message '") +
+ msg + NS_LITERAL_CSTRING("'.");
+
+ NS_WARNING(error.get());
+#endif
+ return rv;
+ }
+
+ mCachedStatements.Put(aQuery, stmt);
+ }
+
+ aCachedStatement->Assign(this, stmt.forget());
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::BeginWriteTransaction()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(mInReadTransaction);
+ MOZ_ASSERT(!mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::BeginWriteTransaction",
+ js::ProfileEntry::Category::STORAGE);
+
+ // Release our read locks.
+ CachedStatement rollbackStmt;
+ nsresult rv =
+ GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &rollbackStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = rollbackStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mInReadTransaction = false;
+
+ if (!mUpdateRefcountFunction) {
+ MOZ_ASSERT(mFileManager);
+
+ RefPtr<UpdateRefcountFunction> function =
+ new UpdateRefcountFunction(this, mFileManager);
+
+ rv =
+ mStorageConnection->CreateFunction(NS_LITERAL_CSTRING("update_refcount"),
+ /* aNumArguments */ 2,
+ function);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mUpdateRefcountFunction.swap(function);
+ }
+
+ CachedStatement beginStmt;
+ rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), &beginStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = beginStmt->Execute();
+ if (rv == NS_ERROR_STORAGE_BUSY) {
+ NS_WARNING("Received NS_ERROR_STORAGE_BUSY when attempting to start write "
+ "transaction, retrying for up to 10 seconds");
+
+ // Another thread must be using the database. Wait up to 10 seconds for
+ // that to complete.
+ TimeStamp start = TimeStamp::NowLoRes();
+
+ while (true) {
+ PR_Sleep(PR_MillisecondsToInterval(100));
+
+ rv = beginStmt->Execute();
+ if (rv != NS_ERROR_STORAGE_BUSY ||
+ TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
+ break;
+ }
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mInWriteTransaction = true;
+
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::CommitWriteTransaction()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(!mInReadTransaction);
+ MOZ_ASSERT(mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::CommitWriteTransaction",
+ js::ProfileEntry::Category::STORAGE);
+
+ CachedStatement stmt;
+ nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mInWriteTransaction = false;
+ return NS_OK;
+}
+
+void
+DatabaseConnection::RollbackWriteTransaction()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(!mInReadTransaction);
+ MOZ_ASSERT(mStorageConnection);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::RollbackWriteTransaction",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!mInWriteTransaction) {
+ return;
+ }
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // This may fail if SQLite already rolled back the transaction so ignore any
+ // errors.
+ Unused << stmt->Execute();
+
+ mInWriteTransaction = false;
+}
+
+void
+DatabaseConnection::FinishWriteTransaction()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(!mInReadTransaction);
+ MOZ_ASSERT(!mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::FinishWriteTransaction",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (mUpdateRefcountFunction) {
+ mUpdateRefcountFunction->Reset();
+ }
+
+ CachedStatement stmt;
+ nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN;"), &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ mInReadTransaction = true;
+}
+
+nsresult
+DatabaseConnection::StartSavepoint()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(mUpdateRefcountFunction);
+ MOZ_ASSERT(mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::StartSavepoint",
+ js::ProfileEntry::Category::STORAGE);
+
+ CachedStatement stmt;
+ nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING(SAVEPOINT_CLAUSE), &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mUpdateRefcountFunction->StartSavepoint();
+
+#ifdef DEBUG
+ MOZ_ASSERT(mDEBUGSavepointCount < UINT32_MAX);
+ mDEBUGSavepointCount++;
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::ReleaseSavepoint()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(mUpdateRefcountFunction);
+ MOZ_ASSERT(mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::ReleaseSavepoint",
+ js::ProfileEntry::Category::STORAGE);
+
+ CachedStatement stmt;
+ nsresult rv = GetCachedStatement(
+ NS_LITERAL_CSTRING("RELEASE " SAVEPOINT_CLAUSE),
+ &stmt);
+ if (NS_SUCCEEDED(rv)) {
+ rv = stmt->Execute();
+ if (NS_SUCCEEDED(rv)) {
+ mUpdateRefcountFunction->ReleaseSavepoint();
+
+#ifdef DEBUG
+ MOZ_ASSERT(mDEBUGSavepointCount);
+ mDEBUGSavepointCount--;
+#endif
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+DatabaseConnection::RollbackSavepoint()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(mUpdateRefcountFunction);
+ MOZ_ASSERT(mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::RollbackSavepoint",
+ js::ProfileEntry::Category::STORAGE);
+
+#ifdef DEBUG
+ MOZ_ASSERT(mDEBUGSavepointCount);
+ mDEBUGSavepointCount--;
+#endif
+
+ mUpdateRefcountFunction->RollbackSavepoint();
+
+ CachedStatement stmt;
+ nsresult rv = GetCachedStatement(
+ NS_LITERAL_CSTRING("ROLLBACK TO " SAVEPOINT_CLAUSE),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // This may fail if SQLite already rolled back the savepoint so ignore any
+ // errors.
+ Unused << stmt->Execute();
+
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::CheckpointInternal(CheckpointMode aMode)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(!mInReadTransaction);
+ MOZ_ASSERT(!mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::CheckpointInternal",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsAutoCString stmtString;
+ stmtString.AssignLiteral("PRAGMA wal_checkpoint(");
+
+ switch (aMode) {
+ case CheckpointMode::Full:
+ // Ensures that the database is completely checkpointed and flushed to
+ // disk.
+ stmtString.AppendLiteral("FULL");
+ break;
+
+ case CheckpointMode::Restart:
+ // Like Full, but also ensures that the next write will start overwriting
+ // the existing WAL file rather than letting the WAL file grow.
+ stmtString.AppendLiteral("RESTART");
+ break;
+
+ case CheckpointMode::Truncate:
+ // Like Restart but also truncates the existing WAL file.
+ stmtString.AppendLiteral("TRUNCATE");
+ break;
+
+ default:
+ MOZ_CRASH("Unknown CheckpointMode!");
+ }
+
+ stmtString.AppendLiteral(");");
+
+ CachedStatement stmt;
+ nsresult rv = GetCachedStatement(stmtString, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mInReadTransaction);
+ MOZ_ASSERT(!mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::DoIdleProcessing",
+ js::ProfileEntry::Category::STORAGE);
+
+ DatabaseConnection::CachedStatement freelistStmt;
+ uint32_t freelistCount;
+ nsresult rv = GetFreelistCount(freelistStmt, &freelistCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freelistCount = 0;
+ }
+
+ CachedStatement rollbackStmt;
+ CachedStatement beginStmt;
+ if (aNeedsCheckpoint || freelistCount) {
+ rv = GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &rollbackStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN;"), &beginStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // Release the connection's normal transaction. It's possible that it could
+ // fail, but that isn't a problem here.
+ Unused << rollbackStmt->Execute();
+
+ mInReadTransaction = false;
+ }
+
+ bool freedSomePages = false;
+
+ if (freelistCount) {
+ rv = ReclaimFreePagesWhileIdle(freelistStmt,
+ rollbackStmt,
+ freelistCount,
+ aNeedsCheckpoint,
+ &freedSomePages);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_ASSERT(!freedSomePages);
+ }
+
+ // Make sure we didn't leave a transaction running.
+ MOZ_ASSERT(!mInReadTransaction);
+ MOZ_ASSERT(!mInWriteTransaction);
+ }
+
+ // Truncate the WAL if we were asked to or if we managed to free some space.
+ if (aNeedsCheckpoint || freedSomePages) {
+ rv = CheckpointInternal(CheckpointMode::Truncate);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ // Finally try to restart the read transaction if we rolled it back earlier.
+ if (beginStmt) {
+ rv = beginStmt->Execute();
+ if (NS_SUCCEEDED(rv)) {
+ mInReadTransaction = true;
+ } else {
+ NS_WARNING("Falied to restart read transaction!");
+ }
+ }
+}
+
+nsresult
+DatabaseConnection::ReclaimFreePagesWhileIdle(
+ CachedStatement& aFreelistStatement,
+ CachedStatement& aRollbackStatement,
+ uint32_t aFreelistCount,
+ bool aNeedsCheckpoint,
+ bool* aFreedSomePages)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(aFreelistStatement);
+ MOZ_ASSERT(aRollbackStatement);
+ MOZ_ASSERT(aFreelistCount);
+ MOZ_ASSERT(aFreedSomePages);
+ MOZ_ASSERT(!mInReadTransaction);
+ MOZ_ASSERT(!mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::ReclaimFreePagesWhileIdle",
+ js::ProfileEntry::Category::STORAGE);
+
+ // Make sure we don't keep working if anything else needs this thread.
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ if (NS_HasPendingEvents(currentThread)) {
+ *aFreedSomePages = false;
+ return NS_OK;
+ }
+
+ // Only try to free 10% at a time so that we can bail out if this connection
+ // suddenly becomes active or if the thread is needed otherwise.
+ nsAutoCString stmtString;
+ stmtString.AssignLiteral("PRAGMA incremental_vacuum(");
+ stmtString.AppendInt(std::max(uint64_t(1), uint64_t(aFreelistCount / 10)));
+ stmtString.AppendLiteral(");");
+
+ // Make all the statements we'll need up front.
+ CachedStatement incrementalVacuumStmt;
+ nsresult rv = GetCachedStatement(stmtString, &incrementalVacuumStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ CachedStatement beginImmediateStmt;
+ rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"),
+ &beginImmediateStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ CachedStatement commitStmt;
+ rv = GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &commitStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aNeedsCheckpoint) {
+ // Freeing pages is a journaled operation, so it will require additional WAL
+ // space. However, we're idle and are about to checkpoint anyway, so doing a
+ // RESTART checkpoint here should allow us to reuse any existing space.
+ rv = CheckpointInternal(CheckpointMode::Restart);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Start the write transaction.
+ rv = beginImmediateStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mInWriteTransaction = true;
+
+ bool freedSomePages = false;
+
+ while (aFreelistCount) {
+ if (NS_HasPendingEvents(currentThread)) {
+ // Something else wants to use the thread so roll back this transaction.
+ // It's ok if we never make progress here because the idle service should
+ // eventually reclaim this space.
+ rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ break;
+ }
+
+ rv = incrementalVacuumStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ freedSomePages = true;
+
+ rv = GetFreelistCount(aFreelistStatement, &aFreelistCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && freedSomePages) {
+ // Commit the write transaction.
+ rv = commitStmt->Execute();
+ if (NS_SUCCEEDED(rv)) {
+ mInWriteTransaction = false;
+ } else {
+ NS_WARNING("Failed to commit!");
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(mInWriteTransaction);
+
+ // Something failed, make sure we roll everything back.
+ Unused << aRollbackStatement->Execute();
+
+ mInWriteTransaction = false;
+
+ return rv;
+ }
+
+ *aFreedSomePages = freedSomePages;
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::GetFreelistCount(CachedStatement& aCachedStatement,
+ uint32_t* aFreelistCount)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(aFreelistCount);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::GetFreelistCount",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv;
+
+ if (!aCachedStatement) {
+ rv = GetCachedStatement(NS_LITERAL_CSTRING("PRAGMA freelist_count;"),
+ &aCachedStatement);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ rv = aCachedStatement->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ // Make sure this statement is reset when leaving this function since we're
+ // not using the normal stack-based protection of CachedStatement.
+ mozStorageStatementScoper scoper(aCachedStatement);
+
+ int32_t freelistCount;
+ rv = aCachedStatement->GetInt32(0, &freelistCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(freelistCount >= 0);
+
+ *aFreelistCount = uint32_t(freelistCount);
+ return NS_OK;
+}
+
+void
+DatabaseConnection::Close()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStorageConnection);
+ MOZ_ASSERT(!mDEBUGSavepointCount);
+ MOZ_ASSERT(!mInWriteTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::Close",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (mUpdateRefcountFunction) {
+ MOZ_ALWAYS_SUCCEEDS(
+ mStorageConnection->RemoveFunction(
+ NS_LITERAL_CSTRING("update_refcount")));
+ mUpdateRefcountFunction = nullptr;
+ }
+
+ mCachedStatements.Clear();
+
+ MOZ_ALWAYS_SUCCEEDS(mStorageConnection->Close());
+ mStorageConnection = nullptr;
+
+ mFileManager = nullptr;
+}
+
+nsresult
+DatabaseConnection::DisableQuotaChecks()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStorageConnection);
+
+ if (!mQuotaObject) {
+ MOZ_ASSERT(!mJournalQuotaObject);
+
+ nsresult rv = mStorageConnection->GetQuotaObjects(
+ getter_AddRefs(mQuotaObject),
+ getter_AddRefs(mJournalQuotaObject));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(mQuotaObject);
+ MOZ_ASSERT(mJournalQuotaObject);
+ }
+
+ mQuotaObject->DisableQuotaCheck();
+ mJournalQuotaObject->DisableQuotaCheck();
+
+ return NS_OK;
+}
+
+void
+DatabaseConnection::EnableQuotaChecks()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mQuotaObject);
+ MOZ_ASSERT(mJournalQuotaObject);
+
+ RefPtr<QuotaObject> quotaObject;
+ RefPtr<QuotaObject> journalQuotaObject;
+
+ mQuotaObject.swap(quotaObject);
+ mJournalQuotaObject.swap(journalQuotaObject);
+
+ quotaObject->EnableQuotaCheck();
+ journalQuotaObject->EnableQuotaCheck();
+
+ int64_t fileSize;
+ nsresult rv = GetFileSize(quotaObject->Path(), &fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ int64_t journalFileSize;
+ rv = GetFileSize(journalQuotaObject->Path(), &journalFileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ DebugOnly<bool> result =
+ journalQuotaObject->MaybeUpdateSize(journalFileSize, /* aTruncate */ true);
+ MOZ_ASSERT(result);
+
+ result = quotaObject->MaybeUpdateSize(fileSize, /* aTruncate */ true);
+ MOZ_ASSERT(result);
+}
+
+nsresult
+DatabaseConnection::GetFileSize(const nsAString& aPath, int64_t* aResult)
+{
+ MOZ_ASSERT(!aPath.IsEmpty());
+ MOZ_ASSERT(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->InitWithPath(aPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t fileSize;
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ rv = file->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ fileSize = 0;
+ }
+
+ *aResult = fileSize;
+ return NS_OK;
+}
+
+DatabaseConnection::
+CachedStatement::CachedStatement()
+#ifdef DEBUG
+ : mDEBUGConnection(nullptr)
+#endif
+{
+ AssertIsOnConnectionThread();
+
+ MOZ_COUNT_CTOR(DatabaseConnection::CachedStatement);
+}
+
+DatabaseConnection::
+CachedStatement::~CachedStatement()
+{
+ AssertIsOnConnectionThread();
+
+ MOZ_COUNT_DTOR(DatabaseConnection::CachedStatement);
+}
+
+DatabaseConnection::
+CachedStatement::operator mozIStorageStatement*() const
+{
+ AssertIsOnConnectionThread();
+
+ return mStatement;
+}
+
+mozIStorageStatement*
+DatabaseConnection::
+CachedStatement::operator->() const
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(mStatement);
+
+ return mStatement;
+}
+
+void
+DatabaseConnection::
+CachedStatement::Reset()
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT_IF(mStatement, mScoper);
+
+ if (mStatement) {
+ mScoper.reset();
+ mScoper.emplace(mStatement);
+ }
+}
+
+void
+DatabaseConnection::
+CachedStatement::Assign(DatabaseConnection* aConnection,
+ already_AddRefed<mozIStorageStatement> aStatement)
+{
+#ifdef DEBUG
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT_IF(mDEBUGConnection, mDEBUGConnection == aConnection);
+
+ mDEBUGConnection = aConnection;
+#endif
+ AssertIsOnConnectionThread();
+
+ mScoper.reset();
+
+ mStatement = aStatement;
+
+ if (mStatement) {
+ mScoper.emplace(mStatement);
+ }
+}
+
+DatabaseConnection::
+AutoSavepoint::AutoSavepoint()
+ : mConnection(nullptr)
+#ifdef DEBUG
+ , mDEBUGTransaction(nullptr)
+#endif
+{
+ MOZ_COUNT_CTOR(DatabaseConnection::AutoSavepoint);
+}
+
+DatabaseConnection::
+AutoSavepoint::~AutoSavepoint()
+{
+ MOZ_COUNT_DTOR(DatabaseConnection::AutoSavepoint);
+
+ if (mConnection) {
+ mConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mDEBUGTransaction);
+ MOZ_ASSERT(mDEBUGTransaction->GetMode() == IDBTransaction::READ_WRITE ||
+ mDEBUGTransaction->GetMode() ==
+ IDBTransaction::READ_WRITE_FLUSH ||
+ mDEBUGTransaction->GetMode() == IDBTransaction::CLEANUP ||
+ mDEBUGTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
+
+ if (NS_FAILED(mConnection->RollbackSavepoint())) {
+ NS_WARNING("Failed to rollback savepoint!");
+ }
+ }
+}
+
+nsresult
+DatabaseConnection::
+AutoSavepoint::Start(const TransactionBase* aTransaction)
+{
+ MOZ_ASSERT(aTransaction);
+ MOZ_ASSERT(aTransaction->GetMode() == IDBTransaction::READ_WRITE ||
+ aTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
+ aTransaction->GetMode() == IDBTransaction::CLEANUP ||
+ aTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
+
+ DatabaseConnection* connection = aTransaction->GetDatabase()->GetConnection();
+ MOZ_ASSERT(connection);
+ connection->AssertIsOnConnectionThread();
+
+ MOZ_ASSERT(!mConnection);
+ MOZ_ASSERT(!mDEBUGTransaction);
+
+ nsresult rv = connection->StartSavepoint();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mConnection = connection;
+#ifdef DEBUG
+ mDEBUGTransaction = aTransaction;
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::
+AutoSavepoint::Commit()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mDEBUGTransaction);
+
+ nsresult rv = mConnection->ReleaseSavepoint();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mConnection = nullptr;
+#ifdef DEBUG
+ mDEBUGTransaction = nullptr;
+#endif
+
+ return NS_OK;
+}
+
+DatabaseConnection::
+UpdateRefcountFunction::UpdateRefcountFunction(DatabaseConnection* aConnection,
+ FileManager* aFileManager)
+ : mConnection(aConnection)
+ , mFileManager(aFileManager)
+ , mInSavepoint(false)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(aFileManager);
+}
+
+nsresult
+DatabaseConnection::
+UpdateRefcountFunction::WillCommit()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::UpdateRefcountFunction::WillCommit",
+ js::ProfileEntry::Category::STORAGE);
+
+ DatabaseUpdateFunction function(this);
+ for (auto iter = mFileInfoEntries.ConstIter(); !iter.Done(); iter.Next()) {
+ auto key = iter.Key();
+ FileInfoEntry* value = iter.Data();
+ MOZ_ASSERT(value);
+
+ if (value->mDelta && !function.Update(key, value->mDelta)) {
+ break;
+ }
+ }
+
+ nsresult rv = function.ErrorCode();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CreateJournals();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::DidCommit()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::UpdateRefcountFunction::DidCommit",
+ js::ProfileEntry::Category::STORAGE);
+
+ for (auto iter = mFileInfoEntries.ConstIter(); !iter.Done(); iter.Next()) {
+ FileInfoEntry* value = iter.Data();
+
+ MOZ_ASSERT(value);
+
+ if (value->mDelta) {
+ value->mFileInfo->UpdateDBRefs(value->mDelta);
+ }
+ }
+
+ if (NS_FAILED(RemoveJournals(mJournalsToRemoveAfterCommit))) {
+ NS_WARNING("RemoveJournals failed!");
+ }
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::DidAbort()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::UpdateRefcountFunction::DidAbort",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_FAILED(RemoveJournals(mJournalsToRemoveAfterAbort))) {
+ NS_WARNING("RemoveJournals failed!");
+ }
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::StartSavepoint()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(!mInSavepoint);
+ MOZ_ASSERT(!mSavepointEntriesIndex.Count());
+
+ mInSavepoint = true;
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::ReleaseSavepoint()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mInSavepoint);
+
+ mSavepointEntriesIndex.Clear();
+ mInSavepoint = false;
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::RollbackSavepoint()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(mInSavepoint);
+
+ for (auto iter = mSavepointEntriesIndex.ConstIter();
+ !iter.Done(); iter.Next()) {
+ auto value = iter.Data();
+ value->mDelta -= value->mSavepointDelta;
+ }
+
+ mInSavepoint = false;
+ mSavepointEntriesIndex.Clear();
+}
+
+void
+DatabaseConnection::
+UpdateRefcountFunction::Reset()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(!mSavepointEntriesIndex.Count());
+ MOZ_ASSERT(!mInSavepoint);
+
+ class MOZ_STACK_CLASS CustomCleanupCallback final
+ : public FileInfo::CustomCleanupCallback
+ {
+ nsCOMPtr<nsIFile> mDirectory;
+ nsCOMPtr<nsIFile> mJournalDirectory;
+
+ public:
+ virtual nsresult
+ Cleanup(FileManager* aFileManager, int64_t aId)
+ {
+ if (!mDirectory) {
+ MOZ_ASSERT(!mJournalDirectory);
+
+ mDirectory = aFileManager->GetDirectory();
+ if (NS_WARN_IF(!mDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mJournalDirectory = aFileManager->GetJournalDirectory();
+ if (NS_WARN_IF(!mJournalDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<nsIFile> file = aFileManager->GetFileForId(mDirectory, aId);
+ if (NS_WARN_IF(!file)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ int64_t fileSize;
+
+ if (aFileManager->EnforcingQuota()) {
+ rv = file->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = file->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aFileManager->EnforcingQuota()) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ quotaManager->DecreaseUsageForOrigin(aFileManager->Type(),
+ aFileManager->Group(),
+ aFileManager->Origin(),
+ fileSize);
+ }
+
+ file = aFileManager->GetFileForId(mJournalDirectory, aId);
+ if (NS_WARN_IF(!file)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = file->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+ };
+
+ mJournalsToCreateBeforeCommit.Clear();
+ mJournalsToRemoveAfterCommit.Clear();
+ mJournalsToRemoveAfterAbort.Clear();
+
+ // FileInfo implementation automatically removes unreferenced files, but it's
+ // done asynchronously and with a delay. We want to remove them (and decrease
+ // quota usage) before we fire the commit event.
+ for (auto iter = mFileInfoEntries.ConstIter(); !iter.Done(); iter.Next()) {
+ FileInfoEntry* value = iter.Data();
+
+ MOZ_ASSERT(value);
+
+ FileInfo* fileInfo = value->mFileInfo.forget().take();
+
+ MOZ_ASSERT(fileInfo);
+
+ CustomCleanupCallback customCleanupCallback;
+ fileInfo->Release(&customCleanupCallback);
+ }
+
+ mFileInfoEntries.Clear();
+}
+
+nsresult
+DatabaseConnection::
+UpdateRefcountFunction::ProcessValue(mozIStorageValueArray* aValues,
+ int32_t aIndex,
+ UpdateType aUpdateType)
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(aValues);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::UpdateRefcountFunction::ProcessValue",
+ js::ProfileEntry::Category::STORAGE);
+
+ int32_t type;
+ nsresult rv = aValues->GetTypeOfIndex(aIndex, &type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
+ return NS_OK;
+ }
+
+ nsString ids;
+ rv = aValues->GetString(aIndex, ids);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsTArray<StructuredCloneFile> files;
+ rv = DeserializeStructuredCloneFiles(mFileManager, ids, files, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (uint32_t i = 0; i < files.Length(); i++) {
+ const StructuredCloneFile& file = files[i];
+
+ const int64_t id = file.mFileInfo->Id();
+ MOZ_ASSERT(id > 0);
+
+ FileInfoEntry* entry;
+ if (!mFileInfoEntries.Get(id, &entry)) {
+ entry = new FileInfoEntry(file.mFileInfo);
+ mFileInfoEntries.Put(id, entry);
+ }
+
+ if (mInSavepoint) {
+ mSavepointEntriesIndex.Put(id, entry);
+ }
+
+ switch (aUpdateType) {
+ case UpdateType::Increment:
+ entry->mDelta++;
+ if (mInSavepoint) {
+ entry->mSavepointDelta++;
+ }
+ break;
+ case UpdateType::Decrement:
+ entry->mDelta--;
+ if (mInSavepoint) {
+ entry->mSavepointDelta--;
+ }
+ break;
+ default:
+ MOZ_CRASH("Unknown update type!");
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::
+UpdateRefcountFunction::CreateJournals()
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::UpdateRefcountFunction::CreateJournals",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsCOMPtr<nsIFile> journalDirectory = mFileManager->GetJournalDirectory();
+ if (NS_WARN_IF(!journalDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (uint32_t i = 0; i < mJournalsToCreateBeforeCommit.Length(); i++) {
+ int64_t id = mJournalsToCreateBeforeCommit[i];
+
+ nsCOMPtr<nsIFile> file =
+ mFileManager->GetFileForId(journalDirectory, id);
+ if (NS_WARN_IF(!file)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mJournalsToRemoveAfterAbort.AppendElement(id);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DatabaseConnection::
+UpdateRefcountFunction::RemoveJournals(const nsTArray<int64_t>& aJournals)
+{
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::UpdateRefcountFunction::RemoveJournals",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsCOMPtr<nsIFile> journalDirectory = mFileManager->GetJournalDirectory();
+ if (NS_WARN_IF(!journalDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (uint32_t index = 0; index < aJournals.Length(); index++) {
+ nsCOMPtr<nsIFile> file =
+ mFileManager->GetFileForId(journalDirectory, aJournals[index]);
+ if (NS_WARN_IF(!file)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(file->Remove(false))) {
+ NS_WARNING("Failed to removed journal!");
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(DatabaseConnection::UpdateRefcountFunction,
+ mozIStorageFunction)
+
+NS_IMETHODIMP
+DatabaseConnection::
+UpdateRefcountFunction::OnFunctionCall(mozIStorageValueArray* aValues,
+ nsIVariant** _retval)
+{
+ MOZ_ASSERT(aValues);
+ MOZ_ASSERT(_retval);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::UpdateRefcountFunction::OnFunctionCall",
+ js::ProfileEntry::Category::STORAGE);
+
+ uint32_t numEntries;
+ nsresult rv = aValues->GetNumEntries(&numEntries);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(numEntries == 2);
+
+#ifdef DEBUG
+ {
+ int32_t type1 = mozIStorageValueArray::VALUE_TYPE_NULL;
+ MOZ_ASSERT(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &type1)));
+
+ int32_t type2 = mozIStorageValueArray::VALUE_TYPE_NULL;
+ MOZ_ASSERT(NS_SUCCEEDED(aValues->GetTypeOfIndex(1, &type2)));
+
+ MOZ_ASSERT(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL &&
+ type2 == mozIStorageValueArray::VALUE_TYPE_NULL));
+ }
+#endif
+
+ rv = ProcessValue(aValues, 0, UpdateType::Decrement);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = ProcessValue(aValues, 1, UpdateType::Increment);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+DatabaseConnection::UpdateRefcountFunction::
+DatabaseUpdateFunction::Update(int64_t aId,
+ int32_t aDelta)
+{
+ nsresult rv = UpdateInternal(aId, aDelta);
+ if (NS_FAILED(rv)) {
+ mErrorCode = rv;
+ return false;
+ }
+
+ return true;
+}
+
+nsresult
+DatabaseConnection::UpdateRefcountFunction::
+DatabaseUpdateFunction::UpdateInternal(int64_t aId,
+ int32_t aDelta)
+{
+ MOZ_ASSERT(mFunction);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseConnection::UpdateRefcountFunction::"
+ "DatabaseUpdateFunction::UpdateInternal",
+ js::ProfileEntry::Category::STORAGE);
+
+ DatabaseConnection* connection = mFunction->mConnection;
+ MOZ_ASSERT(connection);
+ connection->AssertIsOnConnectionThread();
+
+ MOZ_ASSERT(connection->GetStorageConnection());
+
+ nsresult rv;
+ if (!mUpdateStatement) {
+ rv = connection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "UPDATE file "
+ "SET refcount = refcount + :delta "
+ "WHERE id = :id"),
+ &mUpdateStatement);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mozStorageStatementScoper updateScoper(mUpdateStatement);
+
+ rv = mUpdateStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mUpdateStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mUpdateStatement->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t rows;
+ rv = connection->GetStorageConnection()->GetAffectedRows(&rows);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (rows > 0) {
+ if (!mSelectStatement) {
+ rv = connection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT id "
+ "FROM file "
+ "WHERE id = :id"),
+ &mSelectStatement);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mozStorageStatementScoper selectScoper(mSelectStatement);
+
+ rv = mSelectStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = mSelectStatement->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!hasResult) {
+ // Don't have to create the journal here, we can create all at once,
+ // just before commit
+ mFunction->mJournalsToCreateBeforeCommit.AppendElement(aId);
+ }
+
+ return NS_OK;
+ }
+
+ if (!mInsertStatement) {
+ rv = connection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO file (id, refcount) "
+ "VALUES(:id, :delta)"),
+ &mInsertStatement);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mozStorageStatementScoper insertScoper(mInsertStatement);
+
+ rv = mInsertStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mInsertStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mInsertStatement->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mFunction->mJournalsToRemoveAfterCommit.AppendElement(aId);
+ return NS_OK;
+}
+
+/*******************************************************************************
+ * ConnectionPool implementation
+ ******************************************************************************/
+
+ConnectionPool::ConnectionPool()
+ : mDatabasesMutex("ConnectionPool::mDatabasesMutex")
+ , mIdleTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
+ , mNextTransactionId(0)
+ , mTotalThreadCount(0)
+ , mShutdownRequested(false)
+ , mShutdownComplete(false)
+#ifdef DEBUG
+ , mDEBUGOwningThread(PR_GetCurrentThread())
+#endif
+{
+ AssertIsOnOwningThread();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIdleTimer);
+}
+
+ConnectionPool::~ConnectionPool()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mIdleThreads.IsEmpty());
+ MOZ_ASSERT(mIdleDatabases.IsEmpty());
+ MOZ_ASSERT(!mIdleTimer);
+ MOZ_ASSERT(mTargetIdleTime.IsNull());
+ MOZ_ASSERT(!mDatabases.Count());
+ MOZ_ASSERT(!mTransactions.Count());
+ MOZ_ASSERT(mQueuedTransactions.IsEmpty());
+ MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
+ MOZ_ASSERT(!mTotalThreadCount);
+ MOZ_ASSERT(mShutdownRequested);
+ MOZ_ASSERT(mShutdownComplete);
+}
+
+#ifdef DEBUG
+
+void
+ConnectionPool::AssertIsOnOwningThread() const
+{
+ MOZ_ASSERT(mDEBUGOwningThread);
+ MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGOwningThread);
+}
+
+#endif // DEBUG
+
+// static
+void
+ConnectionPool::IdleTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ MOZ_ASSERT(aTimer);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::IdleTimerCallback",
+ js::ProfileEntry::Category::STORAGE);
+
+ auto* self = static_cast<ConnectionPool*>(aClosure);
+ MOZ_ASSERT(self);
+ MOZ_ASSERT(self->mIdleTimer);
+ MOZ_ASSERT(SameCOMIdentity(self->mIdleTimer, aTimer));
+ MOZ_ASSERT(!self->mTargetIdleTime.IsNull());
+ MOZ_ASSERT_IF(self->mIdleDatabases.IsEmpty(), !self->mIdleThreads.IsEmpty());
+ MOZ_ASSERT_IF(self->mIdleThreads.IsEmpty(), !self->mIdleDatabases.IsEmpty());
+
+ self->mTargetIdleTime = TimeStamp();
+
+ // Cheat a little.
+ TimeStamp now = TimeStamp::NowLoRes() + TimeDuration::FromMilliseconds(500);
+
+ uint32_t index = 0;
+
+ for (uint32_t count = self->mIdleDatabases.Length(); index < count; index++) {
+ IdleDatabaseInfo& info = self->mIdleDatabases[index];
+
+ if (now >= info.mIdleTime) {
+ if (info.mDatabaseInfo->mIdle) {
+ self->PerformIdleDatabaseMaintenance(info.mDatabaseInfo);
+ } else {
+ self->CloseDatabase(info.mDatabaseInfo);
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (index) {
+ self->mIdleDatabases.RemoveElementsAt(0, index);
+
+ index = 0;
+ }
+
+ for (uint32_t count = self->mIdleThreads.Length(); index < count; index++) {
+ IdleThreadInfo& info = self->mIdleThreads[index];
+ MOZ_ASSERT(info.mThreadInfo.mThread);
+ MOZ_ASSERT(info.mThreadInfo.mRunnable);
+
+ if (now >= info.mIdleTime) {
+ self->ShutdownThread(info.mThreadInfo);
+ } else {
+ break;
+ }
+ }
+
+ if (index) {
+ self->mIdleThreads.RemoveElementsAt(0, index);
+ }
+
+ self->AdjustIdleTimer();
+}
+
+nsresult
+ConnectionPool::GetOrCreateConnection(const Database* aDatabase,
+ DatabaseConnection** aConnection)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aDatabase);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::GetOrCreateConnection",
+ js::ProfileEntry::Category::STORAGE);
+
+ DatabaseInfo* dbInfo;
+ {
+ MutexAutoLock lock(mDatabasesMutex);
+
+ dbInfo = mDatabases.Get(aDatabase->Id());
+ }
+
+ MOZ_ASSERT(dbInfo);
+
+ RefPtr<DatabaseConnection> connection = dbInfo->mConnection;
+ if (!connection) {
+ MOZ_ASSERT(!dbInfo->mDEBUGConnectionThread);
+
+ nsCOMPtr<mozIStorageConnection> storageConnection;
+ nsresult rv =
+ GetStorageConnection(aDatabase->FilePath(),
+ aDatabase->Type(),
+ aDatabase->Group(),
+ aDatabase->Origin(),
+ aDatabase->TelemetryId(),
+ getter_AddRefs(storageConnection));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ connection =
+ new DatabaseConnection(storageConnection, aDatabase->GetFileManager());
+
+ rv = connection->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ dbInfo->mConnection = connection;
+
+ IDB_DEBUG_LOG(("ConnectionPool created connection 0x%p for '%s'",
+ dbInfo->mConnection.get(),
+ NS_ConvertUTF16toUTF8(aDatabase->FilePath()).get()));
+
+#ifdef DEBUG
+ dbInfo->mDEBUGConnectionThread = PR_GetCurrentThread();
+#endif
+ }
+
+ dbInfo->AssertIsOnConnectionThread();
+
+ connection.forget(aConnection);
+ return NS_OK;
+}
+
+uint64_t
+ConnectionPool::Start(const nsID& aBackgroundChildLoggingId,
+ const nsACString& aDatabaseId,
+ int64_t aLoggingSerialNumber,
+ const nsTArray<nsString>& aObjectStoreNames,
+ bool aIsWriteTransaction,
+ TransactionDatabaseOperationBase* aTransactionOp)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!aDatabaseId.IsEmpty());
+ MOZ_ASSERT(mNextTransactionId < UINT64_MAX);
+ MOZ_ASSERT(!mShutdownRequested);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::Start",
+ js::ProfileEntry::Category::STORAGE);
+
+ const uint64_t transactionId = ++mNextTransactionId;
+
+ DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId);
+
+ const bool databaseInfoIsNew = !dbInfo;
+
+ if (databaseInfoIsNew) {
+ dbInfo = new DatabaseInfo(this, aDatabaseId);
+
+ MutexAutoLock lock(mDatabasesMutex);
+
+ mDatabases.Put(aDatabaseId, dbInfo);
+ }
+
+ TransactionInfo* transactionInfo =
+ new TransactionInfo(dbInfo,
+ aBackgroundChildLoggingId,
+ aDatabaseId,
+ transactionId,
+ aLoggingSerialNumber,
+ aObjectStoreNames,
+ aIsWriteTransaction,
+ aTransactionOp);
+
+ MOZ_ASSERT(!mTransactions.Get(transactionId));
+ mTransactions.Put(transactionId, transactionInfo);
+
+ if (aIsWriteTransaction) {
+ MOZ_ASSERT(dbInfo->mWriteTransactionCount < UINT32_MAX);
+ dbInfo->mWriteTransactionCount++;
+ } else {
+ MOZ_ASSERT(dbInfo->mReadTransactionCount < UINT32_MAX);
+ dbInfo->mReadTransactionCount++;
+ }
+
+ auto& blockingTransactions = dbInfo->mBlockingTransactions;
+
+ for (uint32_t nameIndex = 0, nameCount = aObjectStoreNames.Length();
+ nameIndex < nameCount;
+ nameIndex++) {
+ const nsString& objectStoreName = aObjectStoreNames[nameIndex];
+
+ TransactionInfoPair* blockInfo = blockingTransactions.Get(objectStoreName);
+ if (!blockInfo) {
+ blockInfo = new TransactionInfoPair();
+ blockingTransactions.Put(objectStoreName, blockInfo);
+ }
+
+ // Mark what we are blocking on.
+ if (TransactionInfo* blockingRead = blockInfo->mLastBlockingReads) {
+ transactionInfo->mBlockedOn.PutEntry(blockingRead);
+ blockingRead->AddBlockingTransaction(transactionInfo);
+ }
+
+ if (aIsWriteTransaction) {
+ if (const uint32_t writeCount = blockInfo->mLastBlockingWrites.Length()) {
+ for (uint32_t writeIndex = 0; writeIndex < writeCount; writeIndex++) {
+ TransactionInfo* blockingWrite =
+ blockInfo->mLastBlockingWrites[writeIndex];
+ MOZ_ASSERT(blockingWrite);
+
+ transactionInfo->mBlockedOn.PutEntry(blockingWrite);
+ blockingWrite->AddBlockingTransaction(transactionInfo);
+ }
+ }
+
+ blockInfo->mLastBlockingReads = transactionInfo;
+ blockInfo->mLastBlockingWrites.Clear();
+ } else {
+ blockInfo->mLastBlockingWrites.AppendElement(transactionInfo);
+ }
+ }
+
+ if (!transactionInfo->mBlockedOn.Count()) {
+ Unused << ScheduleTransaction(transactionInfo,
+ /* aFromQueuedTransactions */ false);
+ }
+
+ if (!databaseInfoIsNew &&
+ (mIdleDatabases.RemoveElement(dbInfo) ||
+ mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo))) {
+ AdjustIdleTimer();
+ }
+
+ return transactionId;
+}
+
+void
+ConnectionPool::Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aRunnable);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::Dispatch",
+ js::ProfileEntry::Category::STORAGE);
+
+ TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
+ MOZ_ASSERT(transactionInfo);
+ MOZ_ASSERT(!transactionInfo->mFinished);
+
+ if (transactionInfo->mRunning) {
+ DatabaseInfo* dbInfo = transactionInfo->mDatabaseInfo;
+ MOZ_ASSERT(dbInfo);
+ MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
+ MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
+ MOZ_ASSERT(!dbInfo->mClosing);
+ MOZ_ASSERT_IF(transactionInfo->mIsWriteTransaction,
+ dbInfo->mRunningWriteTransaction == transactionInfo);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ dbInfo->mThreadInfo.mThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL));
+ } else {
+ transactionInfo->mQueuedRunnables.AppendElement(aRunnable);
+ }
+}
+
+void
+ConnectionPool::Finish(uint64_t aTransactionId, FinishCallback* aCallback)
+{
+ AssertIsOnOwningThread();
+
+#ifdef DEBUG
+ TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
+ MOZ_ASSERT(transactionInfo);
+ MOZ_ASSERT(!transactionInfo->mFinished);
+#endif
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::Finish",
+ js::ProfileEntry::Category::STORAGE);
+
+ RefPtr<FinishCallbackWrapper> wrapper =
+ new FinishCallbackWrapper(this, aTransactionId, aCallback);
+
+ Dispatch(aTransactionId, wrapper);
+
+#ifdef DEBUG
+ MOZ_ASSERT(!transactionInfo->mFinished);
+ transactionInfo->mFinished = true;
+#endif
+}
+
+void
+ConnectionPool::WaitForDatabasesToComplete(nsTArray<nsCString>&& aDatabaseIds,
+ nsIRunnable* aCallback)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!aDatabaseIds.IsEmpty());
+ MOZ_ASSERT(aCallback);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::WaitForDatabasesToComplete",
+ js::ProfileEntry::Category::STORAGE);
+
+ bool mayRunCallbackImmediately = true;
+
+ for (uint32_t index = 0, count = aDatabaseIds.Length();
+ index < count;
+ index++) {
+ const nsCString& databaseId = aDatabaseIds[index];
+ MOZ_ASSERT(!databaseId.IsEmpty());
+
+ if (CloseDatabaseWhenIdleInternal(databaseId)) {
+ mayRunCallbackImmediately = false;
+ }
+ }
+
+ if (mayRunCallbackImmediately) {
+ Unused << aCallback->Run();
+ return;
+ }
+
+ nsAutoPtr<DatabasesCompleteCallback> callback(
+ new DatabasesCompleteCallback(Move(aDatabaseIds), aCallback));
+ mCompleteCallbacks.AppendElement(callback.forget());
+}
+
+void
+ConnectionPool::Shutdown()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mShutdownRequested);
+ MOZ_ASSERT(!mShutdownComplete);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::Shutdown",
+ js::ProfileEntry::Category::STORAGE);
+
+ mShutdownRequested = true;
+
+ CancelIdleTimer();
+ MOZ_ASSERT(mTargetIdleTime.IsNull());
+
+ mIdleTimer = nullptr;
+
+ CloseIdleDatabases();
+
+ ShutdownIdleThreads();
+
+ if (!mDatabases.Count()) {
+ MOZ_ASSERT(!mTransactions.Count());
+
+ Cleanup();
+
+ MOZ_ASSERT(mShutdownComplete);
+ return;
+ }
+
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ while (!mShutdownComplete) {
+ MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
+ }
+}
+
+void
+ConnectionPool::Cleanup()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mShutdownRequested);
+ MOZ_ASSERT(!mShutdownComplete);
+ MOZ_ASSERT(!mDatabases.Count());
+ MOZ_ASSERT(!mTransactions.Count());
+ MOZ_ASSERT(mIdleThreads.IsEmpty());
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::Cleanup",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!mCompleteCallbacks.IsEmpty()) {
+ // Run all callbacks manually now.
+ for (uint32_t count = mCompleteCallbacks.Length(), index = 0;
+ index < count;
+ index++) {
+ nsAutoPtr<DatabasesCompleteCallback> completeCallback(
+ mCompleteCallbacks[index].forget());
+ MOZ_ASSERT(completeCallback);
+ MOZ_ASSERT(completeCallback->mCallback);
+
+ Unused << completeCallback->mCallback->Run();
+ }
+
+ mCompleteCallbacks.Clear();
+
+ // And make sure they get processed.
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_ProcessPendingEvents(currentThread));
+ }
+
+ mShutdownComplete = true;
+}
+
+void
+ConnectionPool::AdjustIdleTimer()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mIdleTimer);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::AdjustIdleTimer",
+ js::ProfileEntry::Category::STORAGE);
+
+ // Figure out the next time at which we should release idle resources. This
+ // includes both databases and threads.
+ TimeStamp newTargetIdleTime;
+ MOZ_ASSERT(newTargetIdleTime.IsNull());
+
+ if (!mIdleDatabases.IsEmpty()) {
+ newTargetIdleTime = mIdleDatabases[0].mIdleTime;
+ }
+
+ if (!mIdleThreads.IsEmpty()) {
+ const TimeStamp& idleTime = mIdleThreads[0].mIdleTime;
+
+ if (newTargetIdleTime.IsNull() || idleTime < newTargetIdleTime) {
+ newTargetIdleTime = idleTime;
+ }
+ }
+
+ MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleDatabases.IsEmpty());
+ MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleThreads.IsEmpty());
+
+ // Cancel the timer if it was running and the new target time is different.
+ if (!mTargetIdleTime.IsNull() &&
+ (newTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
+ CancelIdleTimer();
+
+ MOZ_ASSERT(mTargetIdleTime.IsNull());
+ }
+
+ // Schedule the timer if we have a target time different than before.
+ if (!newTargetIdleTime.IsNull() &&
+ (mTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
+ double delta = (newTargetIdleTime - TimeStamp::NowLoRes()).ToMilliseconds();
+
+ uint32_t delay;
+ if (delta > 0) {
+ delay = uint32_t(std::min(delta, double(UINT32_MAX)));
+ } else {
+ delay = 0;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mIdleTimer->InitWithFuncCallback(IdleTimerCallback,
+ this,
+ delay,
+ nsITimer::TYPE_ONE_SHOT));
+
+ mTargetIdleTime = newTargetIdleTime;
+ }
+}
+
+void
+ConnectionPool::CancelIdleTimer()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mIdleTimer);
+
+ if (!mTargetIdleTime.IsNull()) {
+ MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel());
+
+ mTargetIdleTime = TimeStamp();
+ MOZ_ASSERT(mTargetIdleTime.IsNull());
+ }
+}
+
+void
+ConnectionPool::ShutdownThread(ThreadInfo& aThreadInfo)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aThreadInfo.mThread);
+ MOZ_ASSERT(aThreadInfo.mRunnable);
+ MOZ_ASSERT(mTotalThreadCount);
+
+ RefPtr<ThreadRunnable> runnable;
+ aThreadInfo.mRunnable.swap(runnable);
+
+ nsCOMPtr<nsIThread> thread;
+ aThreadInfo.mThread.swap(thread);
+
+ IDB_DEBUG_LOG(("ConnectionPool shutting down thread %lu",
+ runnable->SerialNumber()));
+
+ // This should clean up the thread with the profiler.
+ MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(runnable.forget(),
+ NS_DISPATCH_NORMAL));
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
+ NewRunnableMethod(thread, &nsIThread::Shutdown)));
+
+ mTotalThreadCount--;
+}
+
+void
+ConnectionPool::CloseIdleDatabases()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mShutdownRequested);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::CloseIdleDatabases",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!mIdleDatabases.IsEmpty()) {
+ for (IdleDatabaseInfo& idleInfo : mIdleDatabases) {
+ CloseDatabase(idleInfo.mDatabaseInfo);
+ }
+ mIdleDatabases.Clear();
+ }
+
+ if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) {
+ for (DatabaseInfo* dbInfo : mDatabasesPerformingIdleMaintenance) {
+ MOZ_ASSERT(dbInfo);
+ CloseDatabase(dbInfo);
+ }
+ mDatabasesPerformingIdleMaintenance.Clear();
+ }
+}
+
+void
+ConnectionPool::ShutdownIdleThreads()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mShutdownRequested);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::ShutdownIdleThreads",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!mIdleThreads.IsEmpty()) {
+ for (uint32_t threadCount = mIdleThreads.Length(), threadIndex = 0;
+ threadIndex < threadCount;
+ threadIndex++) {
+ ShutdownThread(mIdleThreads[threadIndex].mThreadInfo);
+ }
+ mIdleThreads.Clear();
+ }
+}
+
+bool
+ConnectionPool::ScheduleTransaction(TransactionInfo* aTransactionInfo,
+ bool aFromQueuedTransactions)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aTransactionInfo);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::ScheduleTransaction",
+ js::ProfileEntry::Category::STORAGE);
+
+ DatabaseInfo* dbInfo = aTransactionInfo->mDatabaseInfo;
+ MOZ_ASSERT(dbInfo);
+
+ dbInfo->mIdle = false;
+
+ if (dbInfo->mClosing) {
+ MOZ_ASSERT(!mIdleDatabases.Contains(dbInfo));
+ MOZ_ASSERT(
+ !dbInfo->mTransactionsScheduledDuringClose.Contains(aTransactionInfo));
+
+ dbInfo->mTransactionsScheduledDuringClose.AppendElement(aTransactionInfo);
+ return true;
+ }
+
+ if (!dbInfo->mThreadInfo.mThread) {
+ MOZ_ASSERT(!dbInfo->mThreadInfo.mRunnable);
+
+ if (mIdleThreads.IsEmpty()) {
+ bool created = false;
+
+ if (mTotalThreadCount < kMaxConnectionThreadCount) {
+ // This will set the thread up with the profiler.
+ RefPtr<ThreadRunnable> runnable = new ThreadRunnable();
+
+ nsCOMPtr<nsIThread> newThread;
+ if (NS_SUCCEEDED(NS_NewThread(getter_AddRefs(newThread), runnable))) {
+ MOZ_ASSERT(newThread);
+
+ IDB_DEBUG_LOG(("ConnectionPool created thread %lu",
+ runnable->SerialNumber()));
+
+ dbInfo->mThreadInfo.mThread.swap(newThread);
+ dbInfo->mThreadInfo.mRunnable.swap(runnable);
+
+ mTotalThreadCount++;
+ created = true;
+ } else {
+ NS_WARNING("Failed to make new thread!");
+ }
+ } else if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) {
+ // We need a thread right now so force all idle processing to stop by
+ // posting a dummy runnable to each thread that might be doing idle
+ // maintenance.
+ nsCOMPtr<nsIRunnable> runnable = new Runnable();
+
+ for (uint32_t index = mDatabasesPerformingIdleMaintenance.Length();
+ index > 0;
+ index--) {
+ DatabaseInfo* dbInfo = mDatabasesPerformingIdleMaintenance[index - 1];
+ MOZ_ASSERT(dbInfo);
+ MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ dbInfo->mThreadInfo.mThread->Dispatch(runnable.forget(),
+ NS_DISPATCH_NORMAL));
+ }
+ }
+
+ if (!created) {
+ if (!aFromQueuedTransactions) {
+ MOZ_ASSERT(!mQueuedTransactions.Contains(aTransactionInfo));
+ mQueuedTransactions.AppendElement(aTransactionInfo);
+ }
+ return false;
+ }
+ } else {
+ const uint32_t lastIndex = mIdleThreads.Length() - 1;
+
+ ThreadInfo& threadInfo = mIdleThreads[lastIndex].mThreadInfo;
+
+ dbInfo->mThreadInfo.mRunnable.swap(threadInfo.mRunnable);
+ dbInfo->mThreadInfo.mThread.swap(threadInfo.mThread);
+
+ mIdleThreads.RemoveElementAt(lastIndex);
+
+ AdjustIdleTimer();
+ }
+ }
+
+ MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
+ MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
+
+ if (aTransactionInfo->mIsWriteTransaction) {
+ if (dbInfo->mRunningWriteTransaction) {
+ // SQLite only allows one write transaction at a time so queue this
+ // transaction for later.
+ MOZ_ASSERT(
+ !dbInfo->mScheduledWriteTransactions.Contains(aTransactionInfo));
+
+ dbInfo->mScheduledWriteTransactions.AppendElement(aTransactionInfo);
+ return true;
+ }
+
+ dbInfo->mRunningWriteTransaction = aTransactionInfo;
+ dbInfo->mNeedsCheckpoint = true;
+ }
+
+ MOZ_ASSERT(!aTransactionInfo->mRunning);
+ aTransactionInfo->mRunning = true;
+
+ nsTArray<nsCOMPtr<nsIRunnable>>& queuedRunnables =
+ aTransactionInfo->mQueuedRunnables;
+
+ if (!queuedRunnables.IsEmpty()) {
+ for (uint32_t index = 0, count = queuedRunnables.Length();
+ index < count;
+ index++) {
+ nsCOMPtr<nsIRunnable> runnable;
+ queuedRunnables[index].swap(runnable);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ dbInfo->mThreadInfo.mThread->Dispatch(runnable.forget(),
+ NS_DISPATCH_NORMAL));
+ }
+
+ queuedRunnables.Clear();
+ }
+
+ return true;
+}
+
+void
+ConnectionPool::NoteFinishedTransaction(uint64_t aTransactionId)
+{
+ AssertIsOnOwningThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::NoteFinishedTransaction",
+ js::ProfileEntry::Category::STORAGE);
+
+ TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
+ MOZ_ASSERT(transactionInfo);
+ MOZ_ASSERT(transactionInfo->mRunning);
+ MOZ_ASSERT(transactionInfo->mFinished);
+
+ transactionInfo->mRunning = false;
+
+ DatabaseInfo* dbInfo = transactionInfo->mDatabaseInfo;
+ MOZ_ASSERT(dbInfo);
+ MOZ_ASSERT(mDatabases.Get(transactionInfo->mDatabaseId) == dbInfo);
+ MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
+ MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
+
+ // Schedule the next write transaction if there are any queued.
+ if (dbInfo->mRunningWriteTransaction == transactionInfo) {
+ MOZ_ASSERT(transactionInfo->mIsWriteTransaction);
+ MOZ_ASSERT(dbInfo->mNeedsCheckpoint);
+
+ dbInfo->mRunningWriteTransaction = nullptr;
+
+ if (!dbInfo->mScheduledWriteTransactions.IsEmpty()) {
+ TransactionInfo* nextWriteTransaction =
+ dbInfo->mScheduledWriteTransactions[0];
+ MOZ_ASSERT(nextWriteTransaction);
+
+ dbInfo->mScheduledWriteTransactions.RemoveElementAt(0);
+
+ MOZ_ALWAYS_TRUE(ScheduleTransaction(nextWriteTransaction,
+ /* aFromQueuedTransactions */ false));
+ }
+ }
+
+ const nsTArray<nsString>& objectStoreNames =
+ transactionInfo->mObjectStoreNames;
+
+ for (uint32_t index = 0, count = objectStoreNames.Length();
+ index < count;
+ index++) {
+ TransactionInfoPair* blockInfo =
+ dbInfo->mBlockingTransactions.Get(objectStoreNames[index]);
+ MOZ_ASSERT(blockInfo);
+
+ if (transactionInfo->mIsWriteTransaction &&
+ blockInfo->mLastBlockingReads == transactionInfo) {
+ blockInfo->mLastBlockingReads = nullptr;
+ }
+
+ blockInfo->mLastBlockingWrites.RemoveElement(transactionInfo);
+ }
+
+ transactionInfo->RemoveBlockingTransactions();
+
+ if (transactionInfo->mIsWriteTransaction) {
+ MOZ_ASSERT(dbInfo->mWriteTransactionCount);
+ dbInfo->mWriteTransactionCount--;
+ } else {
+ MOZ_ASSERT(dbInfo->mReadTransactionCount);
+ dbInfo->mReadTransactionCount--;
+ }
+
+ mTransactions.Remove(aTransactionId);
+
+#ifdef DEBUG
+ // That just deleted |transactionInfo|.
+ transactionInfo = nullptr;
+#endif
+
+ if (!dbInfo->TotalTransactionCount()) {
+ MOZ_ASSERT(!dbInfo->mIdle);
+ dbInfo->mIdle = true;
+
+ NoteIdleDatabase(dbInfo);
+ }
+}
+
+void
+ConnectionPool::ScheduleQueuedTransactions(ThreadInfo& aThreadInfo)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aThreadInfo.mThread);
+ MOZ_ASSERT(aThreadInfo.mRunnable);
+ MOZ_ASSERT(!mQueuedTransactions.IsEmpty());
+ MOZ_ASSERT(!mIdleThreads.Contains(aThreadInfo));
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::ScheduleQueuedTransactions",
+ js::ProfileEntry::Category::STORAGE);
+
+ mIdleThreads.InsertElementSorted(aThreadInfo);
+
+ aThreadInfo.mRunnable = nullptr;
+ aThreadInfo.mThread = nullptr;
+
+ uint32_t index = 0;
+ for (uint32_t count = mQueuedTransactions.Length(); index < count; index++) {
+ if (!ScheduleTransaction(mQueuedTransactions[index],
+ /* aFromQueuedTransactions */ true)) {
+ break;
+ }
+ }
+
+ if (index) {
+ mQueuedTransactions.RemoveElementsAt(0, index);
+ }
+
+ AdjustIdleTimer();
+}
+
+void
+ConnectionPool::NoteIdleDatabase(DatabaseInfo* aDatabaseInfo)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aDatabaseInfo);
+ MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
+ MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
+ MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
+ MOZ_ASSERT(!mIdleDatabases.Contains(aDatabaseInfo));
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::NoteIdleDatabase",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool otherDatabasesWaiting = !mQueuedTransactions.IsEmpty();
+
+ if (mShutdownRequested ||
+ otherDatabasesWaiting ||
+ aDatabaseInfo->mCloseOnIdle) {
+ // Make sure we close the connection if we're shutting down or giving the
+ // thread to another database.
+ CloseDatabase(aDatabaseInfo);
+
+ if (otherDatabasesWaiting) {
+ // Let another database use this thread.
+ ScheduleQueuedTransactions(aDatabaseInfo->mThreadInfo);
+ } else if (mShutdownRequested) {
+ // If there are no other databases that need to run then we can shut this
+ // thread down immediately instead of going through the idle thread
+ // mechanism.
+ ShutdownThread(aDatabaseInfo->mThreadInfo);
+ }
+
+ return;
+ }
+
+ mIdleDatabases.InsertElementSorted(aDatabaseInfo);
+
+ AdjustIdleTimer();
+}
+
+void
+ConnectionPool::NoteClosedDatabase(DatabaseInfo* aDatabaseInfo)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aDatabaseInfo);
+ MOZ_ASSERT(aDatabaseInfo->mClosing);
+ MOZ_ASSERT(!mIdleDatabases.Contains(aDatabaseInfo));
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::NoteClosedDatabase",
+ js::ProfileEntry::Category::STORAGE);
+
+ aDatabaseInfo->mClosing = false;
+
+ // Figure out what to do with this database's thread. It may have already been
+ // given to another database, in which case there's nothing to do here.
+ // Otherwise we prioritize the thread as follows:
+ // 1. Databases that haven't had an opportunity to run at all are highest
+ // priority. Those live in the |mQueuedTransactions| list.
+ // 2. If this database has additional transactions that were started after
+ // we began closing the connection then the thread can be reused for
+ // those transactions.
+ // 3. If we're shutting down then we can get rid of the thread.
+ // 4. Finally, if nothing above took the thread then we can add it to our
+ // list of idle threads. It may be reused or it may time out. If we have
+ // too many idle threads then we will shut down the oldest.
+ if (aDatabaseInfo->mThreadInfo.mThread) {
+ MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
+
+ if (!mQueuedTransactions.IsEmpty()) {
+ // Give the thread to another database.
+ ScheduleQueuedTransactions(aDatabaseInfo->mThreadInfo);
+ } else if (!aDatabaseInfo->TotalTransactionCount()) {
+ if (mShutdownRequested) {
+ ShutdownThread(aDatabaseInfo->mThreadInfo);
+ } else {
+ MOZ_ASSERT(!mIdleThreads.Contains(aDatabaseInfo->mThreadInfo));
+
+ mIdleThreads.InsertElementSorted(aDatabaseInfo->mThreadInfo);
+
+ aDatabaseInfo->mThreadInfo.mRunnable = nullptr;
+ aDatabaseInfo->mThreadInfo.mThread = nullptr;
+
+ if (mIdleThreads.Length() > kMaxIdleConnectionThreadCount) {
+ ShutdownThread(mIdleThreads[0].mThreadInfo);
+ mIdleThreads.RemoveElementAt(0);
+ }
+
+ AdjustIdleTimer();
+ }
+ }
+ }
+
+ // Schedule any transactions that were started while we were closing the
+ // connection.
+ if (aDatabaseInfo->TotalTransactionCount()) {
+ nsTArray<TransactionInfo*>& scheduledTransactions =
+ aDatabaseInfo->mTransactionsScheduledDuringClose;
+
+ MOZ_ASSERT(!scheduledTransactions.IsEmpty());
+
+ for (uint32_t index = 0, count = scheduledTransactions.Length();
+ index < count;
+ index++) {
+ Unused << ScheduleTransaction(scheduledTransactions[index],
+ /* aFromQueuedTransactions */ false);
+ }
+
+ scheduledTransactions.Clear();
+
+ return;
+ }
+
+ // There are no more transactions and the connection has been closed. We're
+ // done with this database.
+ {
+ MutexAutoLock lock(mDatabasesMutex);
+
+ mDatabases.Remove(aDatabaseInfo->mDatabaseId);
+ }
+
+#ifdef DEBUG
+ // That just deleted |aDatabaseInfo|.
+ aDatabaseInfo = nullptr;
+#endif
+
+ // See if we need to fire any complete callbacks now that the database is
+ // finished.
+ for (uint32_t index = 0;
+ index < mCompleteCallbacks.Length();
+ /* conditionally incremented */) {
+ if (MaybeFireCallback(mCompleteCallbacks[index])) {
+ mCompleteCallbacks.RemoveElementAt(index);
+ } else {
+ index++;
+ }
+ }
+
+ // If that was the last database and we're supposed to be shutting down then
+ // we are finished.
+ if (mShutdownRequested && !mDatabases.Count()) {
+ MOZ_ASSERT(!mTransactions.Count());
+ Cleanup();
+ }
+}
+
+bool
+ConnectionPool::MaybeFireCallback(DatabasesCompleteCallback* aCallback)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT(!aCallback->mDatabaseIds.IsEmpty());
+ MOZ_ASSERT(aCallback->mCallback);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::MaybeFireCallback",
+ js::ProfileEntry::Category::STORAGE);
+
+ for (uint32_t count = aCallback->mDatabaseIds.Length(), index = 0;
+ index < count;
+ index++) {
+ const nsCString& databaseId = aCallback->mDatabaseIds[index];
+ MOZ_ASSERT(!databaseId.IsEmpty());
+
+ if (mDatabases.Get(databaseId)) {
+ return false;
+ }
+ }
+
+ Unused << aCallback->mCallback->Run();
+ return true;
+}
+
+void
+ConnectionPool::PerformIdleDatabaseMaintenance(DatabaseInfo* aDatabaseInfo)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aDatabaseInfo);
+ MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
+ MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
+ MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
+ MOZ_ASSERT(aDatabaseInfo->mIdle);
+ MOZ_ASSERT(!aDatabaseInfo->mCloseOnIdle);
+ MOZ_ASSERT(!aDatabaseInfo->mClosing);
+ MOZ_ASSERT(mIdleDatabases.Contains(aDatabaseInfo));
+ MOZ_ASSERT(!mDatabasesPerformingIdleMaintenance.Contains(aDatabaseInfo));
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new IdleConnectionRunnable(aDatabaseInfo, aDatabaseInfo->mNeedsCheckpoint);
+
+ aDatabaseInfo->mNeedsCheckpoint = false;
+ aDatabaseInfo->mIdle = false;
+
+ mDatabasesPerformingIdleMaintenance.AppendElement(aDatabaseInfo);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ aDatabaseInfo->mThreadInfo.mThread->Dispatch(runnable.forget(),
+ NS_DISPATCH_NORMAL));
+}
+
+void
+ConnectionPool::CloseDatabase(DatabaseInfo* aDatabaseInfo)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aDatabaseInfo);
+ MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
+ MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
+ MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
+ MOZ_ASSERT(!aDatabaseInfo->mClosing);
+
+ aDatabaseInfo->mIdle = false;
+ aDatabaseInfo->mNeedsCheckpoint = false;
+ aDatabaseInfo->mClosing = true;
+
+ nsCOMPtr<nsIRunnable> runnable = new CloseConnectionRunnable(aDatabaseInfo);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ aDatabaseInfo->mThreadInfo.mThread->Dispatch(runnable.forget(),
+ NS_DISPATCH_NORMAL));
+}
+
+bool
+ConnectionPool::CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!aDatabaseId.IsEmpty());
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::CloseDatabaseWhenIdleInternal",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId)) {
+ if (mIdleDatabases.RemoveElement(dbInfo) ||
+ mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo)) {
+ CloseDatabase(dbInfo);
+ AdjustIdleTimer();
+ } else {
+ dbInfo->mCloseOnIdle = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+ConnectionPool::
+ConnectionRunnable::ConnectionRunnable(DatabaseInfo* aDatabaseInfo)
+ : mDatabaseInfo(aDatabaseInfo)
+ , mOwningThread(do_GetCurrentThread())
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aDatabaseInfo);
+ MOZ_ASSERT(aDatabaseInfo->mConnectionPool);
+ aDatabaseInfo->mConnectionPool->AssertIsOnOwningThread();
+ MOZ_ASSERT(mOwningThread);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ConnectionPool::IdleConnectionRunnable,
+ ConnectionPool::ConnectionRunnable)
+
+NS_IMETHODIMP
+ConnectionPool::
+IdleConnectionRunnable::Run()
+{
+ MOZ_ASSERT(mDatabaseInfo);
+ MOZ_ASSERT(!mDatabaseInfo->mIdle);
+
+ nsCOMPtr<nsIEventTarget> owningThread;
+ mOwningThread.swap(owningThread);
+
+ if (owningThread) {
+ mDatabaseInfo->AssertIsOnConnectionThread();
+
+ // The connection could be null if EnsureConnection() didn't run or was not
+ // successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
+ if (mDatabaseInfo->mConnection) {
+ mDatabaseInfo->mConnection->DoIdleProcessing(mNeedsCheckpoint);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+ return NS_OK;
+ }
+ }
+
+ RefPtr<ConnectionPool> connectionPool = mDatabaseInfo->mConnectionPool;
+ MOZ_ASSERT(connectionPool);
+
+ if (mDatabaseInfo->mClosing || mDatabaseInfo->TotalTransactionCount()) {
+ MOZ_ASSERT(!connectionPool->
+ mDatabasesPerformingIdleMaintenance.Contains(mDatabaseInfo));
+ } else {
+ MOZ_ALWAYS_TRUE(
+ connectionPool->
+ mDatabasesPerformingIdleMaintenance.RemoveElement(mDatabaseInfo));
+
+ connectionPool->NoteIdleDatabase(mDatabaseInfo);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ConnectionPool::CloseConnectionRunnable,
+ ConnectionPool::ConnectionRunnable)
+
+NS_IMETHODIMP
+ConnectionPool::
+CloseConnectionRunnable::Run()
+{
+ MOZ_ASSERT(mDatabaseInfo);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::CloseConnectionRunnable::Run",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (mOwningThread) {
+ MOZ_ASSERT(mDatabaseInfo->mClosing);
+
+ nsCOMPtr<nsIEventTarget> owningThread;
+ mOwningThread.swap(owningThread);
+
+ // The connection could be null if EnsureConnection() didn't run or was not
+ // successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
+ if (mDatabaseInfo->mConnection) {
+ mDatabaseInfo->AssertIsOnConnectionThread();
+
+ mDatabaseInfo->mConnection->Close();
+
+ IDB_DEBUG_LOG(("ConnectionPool closed connection 0x%p",
+ mDatabaseInfo->mConnection.get()));
+
+ mDatabaseInfo->mConnection = nullptr;
+
+#ifdef DEBUG
+ mDatabaseInfo->mDEBUGConnectionThread = nullptr;
+#endif
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+ return NS_OK;
+ }
+
+ RefPtr<ConnectionPool> connectionPool = mDatabaseInfo->mConnectionPool;
+ MOZ_ASSERT(connectionPool);
+
+ connectionPool->NoteClosedDatabase(mDatabaseInfo);
+ return NS_OK;
+}
+
+ConnectionPool::
+DatabaseInfo::DatabaseInfo(ConnectionPool* aConnectionPool,
+ const nsACString& aDatabaseId)
+ : mConnectionPool(aConnectionPool)
+ , mDatabaseId(aDatabaseId)
+ , mRunningWriteTransaction(nullptr)
+ , mReadTransactionCount(0)
+ , mWriteTransactionCount(0)
+ , mNeedsCheckpoint(false)
+ , mIdle(false)
+ , mCloseOnIdle(false)
+ , mClosing(false)
+#ifdef DEBUG
+ , mDEBUGConnectionThread(nullptr)
+#endif
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aConnectionPool);
+ aConnectionPool->AssertIsOnOwningThread();
+ MOZ_ASSERT(!aDatabaseId.IsEmpty());
+
+ MOZ_COUNT_CTOR(ConnectionPool::DatabaseInfo);
+}
+
+ConnectionPool::
+DatabaseInfo::~DatabaseInfo()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mConnection);
+ MOZ_ASSERT(mScheduledWriteTransactions.IsEmpty());
+ MOZ_ASSERT(!mRunningWriteTransaction);
+ MOZ_ASSERT(!mThreadInfo.mThread);
+ MOZ_ASSERT(!mThreadInfo.mRunnable);
+ MOZ_ASSERT(!TotalTransactionCount());
+
+ MOZ_COUNT_DTOR(ConnectionPool::DatabaseInfo);
+}
+
+ConnectionPool::
+DatabasesCompleteCallback::DatabasesCompleteCallback(
+ nsTArray<nsCString>&& aDatabaseIds,
+ nsIRunnable* aCallback)
+ : mDatabaseIds(Move(aDatabaseIds))
+ , mCallback(aCallback)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mDatabaseIds.IsEmpty());
+ MOZ_ASSERT(aCallback);
+
+ MOZ_COUNT_CTOR(ConnectionPool::DatabasesCompleteCallback);
+}
+
+ConnectionPool::
+DatabasesCompleteCallback::~DatabasesCompleteCallback()
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_DTOR(ConnectionPool::DatabasesCompleteCallback);
+}
+
+ConnectionPool::
+FinishCallbackWrapper::FinishCallbackWrapper(ConnectionPool* aConnectionPool,
+ uint64_t aTransactionId,
+ FinishCallback* aCallback)
+ : mConnectionPool(aConnectionPool)
+ , mCallback(aCallback)
+ , mOwningThread(do_GetCurrentThread())
+ , mTransactionId(aTransactionId)
+ , mHasRunOnce(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aConnectionPool);
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT(mOwningThread);
+}
+
+ConnectionPool::
+FinishCallbackWrapper::~FinishCallbackWrapper()
+{
+ MOZ_ASSERT(!mConnectionPool);
+ MOZ_ASSERT(!mCallback);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ConnectionPool::FinishCallbackWrapper, Runnable)
+
+nsresult
+ConnectionPool::
+FinishCallbackWrapper::Run()
+{
+ MOZ_ASSERT(mConnectionPool);
+ MOZ_ASSERT(mCallback);
+ MOZ_ASSERT(mOwningThread);
+
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::FinishCallbackWrapper::Run",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!mHasRunOnce) {
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ mHasRunOnce = true;
+
+ Unused << mCallback->Run();
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+ }
+
+ mConnectionPool->AssertIsOnOwningThread();
+ MOZ_ASSERT(mHasRunOnce);
+
+ RefPtr<ConnectionPool> connectionPool = Move(mConnectionPool);
+ RefPtr<FinishCallback> callback = Move(mCallback);
+
+ callback->TransactionFinishedBeforeUnblock();
+
+ connectionPool->NoteFinishedTransaction(mTransactionId);
+
+ callback->TransactionFinishedAfterUnblock();
+
+ return NS_OK;
+}
+
+uint32_t ConnectionPool::ThreadRunnable::sNextSerialNumber = 0;
+
+ConnectionPool::
+ThreadRunnable::ThreadRunnable()
+ : mSerialNumber(++sNextSerialNumber)
+ , mFirstRun(true)
+ , mContinueRunning(true)
+{
+ AssertIsOnBackgroundThread();
+}
+
+ConnectionPool::
+ThreadRunnable::~ThreadRunnable()
+{
+ MOZ_ASSERT(!mFirstRun);
+ MOZ_ASSERT(!mContinueRunning);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ConnectionPool::ThreadRunnable, Runnable)
+
+nsresult
+ConnectionPool::
+ThreadRunnable::Run()
+{
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ char stackTopGuess;
+#endif // MOZ_ENABLE_PROFILER_SPS
+
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(mContinueRunning);
+
+ if (!mFirstRun) {
+ mContinueRunning = false;
+ return NS_OK;
+ }
+
+ mFirstRun = false;
+
+ {
+ // Scope for the thread name. Both PR_SetCurrentThreadName() and
+ // profiler_register_thread() copy the string so we don't need to keep it.
+ const nsPrintfCString threadName("IndexedDB #%lu", mSerialNumber);
+
+ PR_SetCurrentThreadName(threadName.get());
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ profiler_register_thread(threadName.get(), &stackTopGuess);
+#endif // MOZ_ENABLE_PROFILER_SPS
+ }
+
+ {
+ // Scope for the profiler label.
+ PROFILER_LABEL("IndexedDB",
+ "ConnectionPool::ThreadRunnable::Run",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+#ifdef DEBUG
+ if (kDEBUGTransactionThreadPriority !=
+ nsISupportsPriority::PRIORITY_NORMAL) {
+ NS_WARNING("ConnectionPool thread debugging enabled, priority has been "
+ "modified!");
+
+ nsCOMPtr<nsISupportsPriority> thread = do_QueryInterface(currentThread);
+ MOZ_ASSERT(thread);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ thread->SetPriority(kDEBUGTransactionThreadPriority));
+ }
+
+ if (kDEBUGTransactionThreadSleepMS) {
+ NS_WARNING("TransactionThreadPool thread debugging enabled, sleeping "
+ "after every event!");
+ }
+#endif // DEBUG
+
+ while (mContinueRunning) {
+ MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
+
+#ifdef DEBUG
+ if (kDEBUGTransactionThreadSleepMS) {
+ MOZ_ALWAYS_TRUE(
+ PR_Sleep(PR_MillisecondsToInterval(kDEBUGTransactionThreadSleepMS)) ==
+ PR_SUCCESS);
+ }
+#endif // DEBUG
+ }
+ }
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ profiler_unregister_thread();
+#endif // MOZ_ENABLE_PROFILER_SPS
+
+ return NS_OK;
+}
+
+ConnectionPool::
+ThreadInfo::ThreadInfo()
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
+}
+
+ConnectionPool::
+ThreadInfo::ThreadInfo(const ThreadInfo& aOther)
+ : mThread(aOther.mThread)
+ , mRunnable(aOther.mRunnable)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aOther.mThread);
+ MOZ_ASSERT(aOther.mRunnable);
+
+ MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
+}
+
+ConnectionPool::
+ThreadInfo::~ThreadInfo()
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_DTOR(ConnectionPool::ThreadInfo);
+}
+
+ConnectionPool::
+IdleResource::IdleResource(const TimeStamp& aIdleTime)
+ : mIdleTime(aIdleTime)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!aIdleTime.IsNull());
+
+ MOZ_COUNT_CTOR(ConnectionPool::IdleResource);
+}
+
+ConnectionPool::
+IdleResource::~IdleResource()
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_DTOR(ConnectionPool::IdleResource);
+}
+
+ConnectionPool::
+IdleDatabaseInfo::IdleDatabaseInfo(DatabaseInfo* aDatabaseInfo)
+ : IdleResource(TimeStamp::NowLoRes() +
+ (aDatabaseInfo->mIdle ?
+ TimeDuration::FromMilliseconds(kConnectionIdleMaintenanceMS) :
+ TimeDuration::FromMilliseconds(kConnectionIdleCloseMS)))
+ , mDatabaseInfo(aDatabaseInfo)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aDatabaseInfo);
+
+ MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
+}
+
+ConnectionPool::
+IdleDatabaseInfo::~IdleDatabaseInfo()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mDatabaseInfo);
+
+ MOZ_COUNT_DTOR(ConnectionPool::IdleDatabaseInfo);
+}
+
+ConnectionPool::
+IdleThreadInfo::IdleThreadInfo(const ThreadInfo& aThreadInfo)
+ : IdleResource(TimeStamp::NowLoRes() +
+ TimeDuration::FromMilliseconds(kConnectionThreadIdleMS))
+ , mThreadInfo(aThreadInfo)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aThreadInfo.mRunnable);
+ MOZ_ASSERT(aThreadInfo.mThread);
+
+ MOZ_COUNT_CTOR(ConnectionPool::IdleThreadInfo);
+}
+
+ConnectionPool::
+IdleThreadInfo::~IdleThreadInfo()
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_DTOR(ConnectionPool::IdleThreadInfo);
+}
+
+ConnectionPool::
+TransactionInfo::TransactionInfo(
+ DatabaseInfo* aDatabaseInfo,
+ const nsID& aBackgroundChildLoggingId,
+ const nsACString& aDatabaseId,
+ uint64_t aTransactionId,
+ int64_t aLoggingSerialNumber,
+ const nsTArray<nsString>& aObjectStoreNames,
+ bool aIsWriteTransaction,
+ TransactionDatabaseOperationBase* aTransactionOp)
+ : mDatabaseInfo(aDatabaseInfo)
+ , mBackgroundChildLoggingId(aBackgroundChildLoggingId)
+ , mDatabaseId(aDatabaseId)
+ , mTransactionId(aTransactionId)
+ , mLoggingSerialNumber(aLoggingSerialNumber)
+ , mObjectStoreNames(aObjectStoreNames)
+ , mIsWriteTransaction(aIsWriteTransaction)
+ , mRunning(false)
+#ifdef DEBUG
+ , mFinished(false)
+#endif
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aDatabaseInfo);
+ aDatabaseInfo->mConnectionPool->AssertIsOnOwningThread();
+
+ MOZ_COUNT_CTOR(ConnectionPool::TransactionInfo);
+
+ if (aTransactionOp) {
+ mQueuedRunnables.AppendElement(aTransactionOp);
+ }
+}
+
+ConnectionPool::
+TransactionInfo::~TransactionInfo()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mBlockedOn.Count());
+ MOZ_ASSERT(mQueuedRunnables.IsEmpty());
+ MOZ_ASSERT(!mRunning);
+ MOZ_ASSERT(mFinished);
+
+ MOZ_COUNT_DTOR(ConnectionPool::TransactionInfo);
+}
+
+void
+ConnectionPool::
+TransactionInfo::AddBlockingTransaction(TransactionInfo* aTransactionInfo)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransactionInfo);
+
+ if (!mBlocking.Contains(aTransactionInfo)) {
+ mBlocking.PutEntry(aTransactionInfo);
+ mBlockingOrdered.AppendElement(aTransactionInfo);
+ }
+}
+
+void
+ConnectionPool::
+TransactionInfo::RemoveBlockingTransactions()
+{
+ AssertIsOnBackgroundThread();
+
+ for (uint32_t index = 0, count = mBlockingOrdered.Length();
+ index < count;
+ index++) {
+ TransactionInfo* blockedInfo = mBlockingOrdered[index];
+ MOZ_ASSERT(blockedInfo);
+
+ blockedInfo->MaybeUnblock(this);
+ }
+
+ mBlocking.Clear();
+ mBlockingOrdered.Clear();
+}
+
+void
+ConnectionPool::
+TransactionInfo::MaybeUnblock(TransactionInfo* aTransactionInfo)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mBlockedOn.Contains(aTransactionInfo));
+
+ mBlockedOn.RemoveEntry(aTransactionInfo);
+ if (!mBlockedOn.Count()) {
+ MOZ_ASSERT(mDatabaseInfo);
+
+ ConnectionPool* connectionPool = mDatabaseInfo->mConnectionPool;
+ MOZ_ASSERT(connectionPool);
+ connectionPool->AssertIsOnOwningThread();
+
+ Unused <<
+ connectionPool->ScheduleTransaction(this,
+ /* aFromQueuedTransactions */ false);
+ }
+}
+
+ConnectionPool::
+TransactionInfoPair::TransactionInfoPair()
+ : mLastBlockingReads(nullptr)
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_CTOR(ConnectionPool::TransactionInfoPair);
+}
+
+ConnectionPool::
+TransactionInfoPair::~TransactionInfoPair()
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_DTOR(ConnectionPool::TransactionInfoPair);
+}
+
+/*******************************************************************************
+ * Metadata classes
+ ******************************************************************************/
+
+bool
+FullObjectStoreMetadata::HasLiveIndexes() const
+{
+ AssertIsOnBackgroundThread();
+
+ for (auto iter = mIndexes.ConstIter(); !iter.Done(); iter.Next()) {
+ if (!iter.Data()->mDeleted) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+already_AddRefed<FullDatabaseMetadata>
+FullDatabaseMetadata::Duplicate() const
+{
+ AssertIsOnBackgroundThread();
+
+ // FullDatabaseMetadata contains two hash tables of pointers that we need to
+ // duplicate so we can't just use the copy constructor.
+ RefPtr<FullDatabaseMetadata> newMetadata =
+ new FullDatabaseMetadata(mCommonMetadata);
+
+ newMetadata->mDatabaseId = mDatabaseId;
+ newMetadata->mFilePath = mFilePath;
+ newMetadata->mNextObjectStoreId = mNextObjectStoreId;
+ newMetadata->mNextIndexId = mNextIndexId;
+
+ for (auto iter = mObjectStores.ConstIter(); !iter.Done(); iter.Next()) {
+ auto key = iter.Key();
+ auto value = iter.Data();
+
+ RefPtr<FullObjectStoreMetadata> newOSMetadata =
+ new FullObjectStoreMetadata();
+
+ newOSMetadata->mCommonMetadata = value->mCommonMetadata;
+ newOSMetadata->mNextAutoIncrementId = value->mNextAutoIncrementId;
+ newOSMetadata->mCommittedAutoIncrementId = value->mCommittedAutoIncrementId;
+
+ for (auto iter = value->mIndexes.ConstIter(); !iter.Done(); iter.Next()) {
+ auto key = iter.Key();
+ auto value = iter.Data();
+
+ RefPtr<FullIndexMetadata> newIndexMetadata = new FullIndexMetadata();
+
+ newIndexMetadata->mCommonMetadata = value->mCommonMetadata;
+
+ if (NS_WARN_IF(!newOSMetadata->mIndexes.Put(key, newIndexMetadata,
+ fallible))) {
+ return nullptr;
+ }
+ }
+
+ MOZ_ASSERT(value->mIndexes.Count() == newOSMetadata->mIndexes.Count());
+
+ if (NS_WARN_IF(!newMetadata->mObjectStores.Put(key, newOSMetadata,
+ fallible))) {
+ return nullptr;
+ }
+ }
+
+ MOZ_ASSERT(mObjectStores.Count() == newMetadata->mObjectStores.Count());
+
+ return newMetadata.forget();
+}
+
+DatabaseLoggingInfo::~DatabaseLoggingInfo()
+{
+ AssertIsOnBackgroundThread();
+
+ if (gLoggingInfoHashtable) {
+ const nsID& backgroundChildLoggingId =
+ mLoggingInfo.backgroundChildLoggingId();
+
+ MOZ_ASSERT(gLoggingInfoHashtable->Get(backgroundChildLoggingId) == this);
+
+ gLoggingInfoHashtable->Remove(backgroundChildLoggingId);
+ }
+}
+
+/*******************************************************************************
+ * Factory
+ ******************************************************************************/
+
+Factory::Factory(already_AddRefed<DatabaseLoggingInfo> aLoggingInfo)
+ : mLoggingInfo(Move(aLoggingInfo))
+#ifdef DEBUG
+ , mActorDestroyed(false)
+#endif
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+}
+
+Factory::~Factory()
+{
+ MOZ_ASSERT(mActorDestroyed);
+}
+
+// static
+already_AddRefed<Factory>
+Factory::Create(const LoggingInfo& aLoggingInfo)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+
+ // Balanced in ActoryDestroy().
+ IncreaseBusyCount();
+
+ MOZ_ASSERT(gLoggingInfoHashtable);
+ RefPtr<DatabaseLoggingInfo> loggingInfo =
+ gLoggingInfoHashtable->Get(aLoggingInfo.backgroundChildLoggingId());
+ if (loggingInfo) {
+ MOZ_ASSERT(aLoggingInfo.backgroundChildLoggingId() == loggingInfo->Id());
+#if !DISABLE_ASSERTS_FOR_FUZZING
+ NS_WARNING_ASSERTION(
+ aLoggingInfo.nextTransactionSerialNumber() ==
+ loggingInfo->mLoggingInfo.nextTransactionSerialNumber(),
+ "NextTransactionSerialNumber doesn't match!");
+ NS_WARNING_ASSERTION(
+ aLoggingInfo.nextVersionChangeTransactionSerialNumber() ==
+ loggingInfo->mLoggingInfo.
+ nextVersionChangeTransactionSerialNumber(),
+ "NextVersionChangeTransactionSerialNumber doesn't match!");
+ NS_WARNING_ASSERTION(
+ aLoggingInfo.nextRequestSerialNumber() ==
+ loggingInfo->mLoggingInfo.nextRequestSerialNumber(),
+ "NextRequestSerialNumber doesn't match!");
+#endif // !DISABLE_ASSERTS_FOR_FUZZING
+ } else {
+ loggingInfo = new DatabaseLoggingInfo(aLoggingInfo);
+ gLoggingInfoHashtable->Put(aLoggingInfo.backgroundChildLoggingId(),
+ loggingInfo);
+ }
+
+ RefPtr<Factory> actor = new Factory(loggingInfo.forget());
+
+ return actor.forget();
+}
+
+void
+Factory::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+#ifdef DEBUG
+ mActorDestroyed = true;
+#endif
+
+ // Match the IncreaseBusyCount in Create().
+ DecreaseBusyCount();
+}
+
+bool
+Factory::RecvDeleteMe()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ return PBackgroundIDBFactoryParent::Send__delete__(this);
+}
+
+bool
+Factory::RecvIncrementLoggingRequestSerialNumber()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mLoggingInfo);
+
+ mLoggingInfo->NextRequestSN();
+ return true;
+}
+
+PBackgroundIDBFactoryRequestParent*
+Factory::AllocPBackgroundIDBFactoryRequestParent(
+ const FactoryRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
+ return nullptr;
+ }
+
+ const CommonFactoryRequestParams* commonParams;
+
+ switch (aParams.type()) {
+ case FactoryRequestParams::TOpenDatabaseRequestParams: {
+ const OpenDatabaseRequestParams& params =
+ aParams.get_OpenDatabaseRequestParams();
+ commonParams = &params.commonParams();
+ break;
+ }
+
+ case FactoryRequestParams::TDeleteDatabaseRequestParams: {
+ const DeleteDatabaseRequestParams& params =
+ aParams.get_DeleteDatabaseRequestParams();
+ commonParams = &params.commonParams();
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ MOZ_ASSERT(commonParams);
+
+ const DatabaseMetadata& metadata = commonParams->metadata();
+ if (NS_WARN_IF(metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT &&
+ metadata.persistenceType() != PERSISTENCE_TYPE_TEMPORARY &&
+ metadata.persistenceType() != PERSISTENCE_TYPE_DEFAULT)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ const PrincipalInfo& principalInfo = commonParams->principalInfo();
+ if (NS_WARN_IF(principalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
+ metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<ContentParent> contentParent =
+ BackgroundParent::GetContentParent(Manager());
+
+ RefPtr<FactoryOp> actor;
+ if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) {
+ actor = new OpenDatabaseOp(this,
+ contentParent.forget(),
+ *commonParams);
+ } else {
+ actor = new DeleteDatabaseOp(this, contentParent.forget(), *commonParams);
+ }
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+bool
+Factory::RecvPBackgroundIDBFactoryRequestConstructor(
+ PBackgroundIDBFactoryRequestParent* aActor,
+ const FactoryRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
+ MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+
+ auto* op = static_cast<FactoryOp*>(aActor);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(op));
+ return true;
+}
+
+bool
+Factory::DeallocPBackgroundIDBFactoryRequestParent(
+ PBackgroundIDBFactoryRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<FactoryOp> op = dont_AddRef(static_cast<FactoryOp*>(aActor));
+ return true;
+}
+
+PBackgroundIDBDatabaseParent*
+Factory::AllocPBackgroundIDBDatabaseParent(
+ const DatabaseSpec& aSpec,
+ PBackgroundIDBFactoryRequestParent* aRequest)
+{
+ MOZ_CRASH("PBackgroundIDBDatabaseParent actors should be constructed "
+ "manually!");
+}
+
+bool
+Factory::DeallocPBackgroundIDBDatabaseParent(
+ PBackgroundIDBDatabaseParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<Database> database = dont_AddRef(static_cast<Database*>(aActor));
+ return true;
+}
+
+/*******************************************************************************
+ * WaitForTransactionsHelper
+ ******************************************************************************/
+
+void
+WaitForTransactionsHelper::WaitForTransactions()
+{
+ MOZ_ASSERT(mState == State::Initial);
+
+ Unused << this->Run();
+}
+
+void
+WaitForTransactionsHelper::MaybeWaitForTransactions()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::Initial);
+
+ RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
+ if (connectionPool) {
+ nsTArray<nsCString> ids(1);
+ ids.AppendElement(mDatabaseId);
+
+ mState = State::WaitingForTransactions;
+
+ connectionPool->WaitForDatabasesToComplete(Move(ids), this);
+ return;
+ }
+
+ MaybeWaitForFileHandles();
+}
+
+void
+WaitForTransactionsHelper::MaybeWaitForFileHandles()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::Initial || mState == State::WaitingForTransactions);
+
+ RefPtr<FileHandleThreadPool> fileHandleThreadPool =
+ gFileHandleThreadPool.get();
+ if (fileHandleThreadPool) {
+ nsTArray<nsCString> ids(1);
+ ids.AppendElement(mDatabaseId);
+
+ mState = State::WaitingForFileHandles;
+
+ fileHandleThreadPool->WaitForDirectoriesToComplete(Move(ids), this);
+ return;
+ }
+
+ CallCallback();
+}
+
+void
+WaitForTransactionsHelper::CallCallback()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::Initial ||
+ mState == State::WaitingForTransactions ||
+ mState == State::WaitingForFileHandles);
+
+ nsCOMPtr<nsIRunnable> callback;
+ mCallback.swap(callback);
+
+ callback->Run();
+
+ mState = State::Complete;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WaitForTransactionsHelper, Runnable)
+
+NS_IMETHODIMP
+WaitForTransactionsHelper::Run()
+{
+ MOZ_ASSERT(mState != State::Complete);
+ MOZ_ASSERT(mCallback);
+
+ switch (mState) {
+ case State::Initial:
+ MaybeWaitForTransactions();
+ break;
+
+ case State::WaitingForTransactions:
+ MaybeWaitForFileHandles();
+ break;
+
+ case State::WaitingForFileHandles:
+ CallCallback();
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return NS_OK;
+}
+
+/*******************************************************************************
+ * Database
+ ******************************************************************************/
+
+Database::Database(Factory* aFactory,
+ const PrincipalInfo& aPrincipalInfo,
+ const Maybe<ContentParentId>& aOptionalContentParentId,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ uint32_t aTelemetryId,
+ FullDatabaseMetadata* aMetadata,
+ FileManager* aFileManager,
+ already_AddRefed<DirectoryLock> aDirectoryLock,
+ bool aFileHandleDisabled,
+ bool aChromeWriteAccessAllowed)
+ : mFactory(aFactory)
+ , mMetadata(aMetadata)
+ , mFileManager(aFileManager)
+ , mDirectoryLock(Move(aDirectoryLock))
+ , mPrincipalInfo(aPrincipalInfo)
+ , mOptionalContentParentId(aOptionalContentParentId)
+ , mGroup(aGroup)
+ , mOrigin(aOrigin)
+ , mId(aMetadata->mDatabaseId)
+ , mFilePath(aMetadata->mFilePath)
+ , mActiveMutableFileCount(0)
+ , mTelemetryId(aTelemetryId)
+ , mPersistenceType(aMetadata->mCommonMetadata.persistenceType())
+ , mFileHandleDisabled(aFileHandleDisabled)
+ , mChromeWriteAccessAllowed(aChromeWriteAccessAllowed)
+ , mClosed(false)
+ , mInvalidated(false)
+ , mActorWasAlive(false)
+ , mActorDestroyed(false)
+ , mMetadataCleanedUp(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFactory);
+ MOZ_ASSERT(aMetadata);
+ MOZ_ASSERT(aFileManager);
+ MOZ_ASSERT_IF(aChromeWriteAccessAllowed,
+ aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo);
+}
+
+void
+Database::Invalidate()
+{
+ AssertIsOnBackgroundThread();
+
+ class MOZ_STACK_CLASS Helper final
+ {
+ public:
+ static bool
+ InvalidateTransactions(nsTHashtable<nsPtrHashKey<TransactionBase>>& aTable)
+ {
+ AssertIsOnBackgroundThread();
+
+ const uint32_t count = aTable.Count();
+ if (!count) {
+ return true;
+ }
+
+ FallibleTArray<RefPtr<TransactionBase>> transactions;
+ if (NS_WARN_IF(!transactions.SetCapacity(count, fallible))) {
+ return false;
+ }
+
+ for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
+ if (NS_WARN_IF(!transactions.AppendElement(iter.Get()->GetKey(),
+ fallible))) {
+ return false;
+ }
+ }
+
+ if (count) {
+ IDB_REPORT_INTERNAL_ERR();
+
+ for (uint32_t index = 0; index < count; index++) {
+ RefPtr<TransactionBase> transaction = transactions[index].forget();
+ MOZ_ASSERT(transaction);
+
+ transaction->Invalidate();
+ }
+ }
+
+ return true;
+ }
+
+ static bool
+ InvalidateMutableFiles(nsTHashtable<nsPtrHashKey<MutableFile>>& aTable)
+ {
+ AssertIsOnBackgroundThread();
+
+ const uint32_t count = aTable.Count();
+ if (!count) {
+ return true;
+ }
+
+ FallibleTArray<RefPtr<MutableFile>> mutableFiles;
+ if (NS_WARN_IF(!mutableFiles.SetCapacity(count, fallible))) {
+ return false;
+ }
+
+ for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
+ if (NS_WARN_IF(!mutableFiles.AppendElement(iter.Get()->GetKey(),
+ fallible))) {
+ return false;
+ }
+ }
+
+ if (count) {
+ IDB_REPORT_INTERNAL_ERR();
+
+ for (uint32_t index = 0; index < count; index++) {
+ RefPtr<MutableFile> mutableFile = mutableFiles[index].forget();
+ MOZ_ASSERT(mutableFile);
+
+ mutableFile->Invalidate();
+ }
+ }
+
+ return true;
+ }
+ };
+
+ if (mInvalidated) {
+ return;
+ }
+
+ mInvalidated = true;
+
+ if (mActorWasAlive && !mActorDestroyed) {
+ Unused << SendInvalidate();
+ }
+
+ if (!Helper::InvalidateTransactions(mTransactions)) {
+ NS_WARNING("Failed to abort all transactions!");
+ }
+
+ if (!Helper::InvalidateMutableFiles(mMutableFiles)) {
+ NS_WARNING("Failed to abort all mutable files!");
+ }
+
+ MOZ_ALWAYS_TRUE(CloseInternal());
+
+ CleanupMetadata();
+}
+
+nsresult
+Database::EnsureConnection()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ PROFILER_LABEL("IndexedDB",
+ "Database::EnsureConnection",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!mConnection || !mConnection->GetStorageConnection()) {
+ nsresult rv =
+ gConnectionPool->GetOrCreateConnection(this, getter_AddRefs(mConnection));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ AssertIsOnConnectionThread();
+
+ return NS_OK;
+}
+
+bool
+Database::RegisterTransaction(TransactionBase* aTransaction)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransaction);
+ MOZ_ASSERT(!mTransactions.GetEntry(aTransaction));
+ MOZ_ASSERT(mDirectoryLock);
+ MOZ_ASSERT(!mInvalidated);
+ MOZ_ASSERT(!mClosed);
+
+ if (NS_WARN_IF(!mTransactions.PutEntry(aTransaction, fallible))) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+Database::UnregisterTransaction(TransactionBase* aTransaction)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransaction);
+ MOZ_ASSERT(mTransactions.GetEntry(aTransaction));
+
+ mTransactions.RemoveEntry(aTransaction);
+
+ MaybeCloseConnection();
+}
+
+bool
+Database::RegisterMutableFile(MutableFile* aMutableFile)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aMutableFile);
+ MOZ_ASSERT(!mMutableFiles.GetEntry(aMutableFile));
+ MOZ_ASSERT(mDirectoryLock);
+
+ if (NS_WARN_IF(!mMutableFiles.PutEntry(aMutableFile, fallible))) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+Database::UnregisterMutableFile(MutableFile* aMutableFile)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aMutableFile);
+ MOZ_ASSERT(mMutableFiles.GetEntry(aMutableFile));
+
+ mMutableFiles.RemoveEntry(aMutableFile);
+}
+
+void
+Database::NoteActiveMutableFile()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mDirectoryLock);
+ MOZ_ASSERT(mActiveMutableFileCount < UINT32_MAX);
+
+ ++mActiveMutableFileCount;
+}
+
+void
+Database::NoteInactiveMutableFile()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveMutableFileCount > 0);
+
+ --mActiveMutableFileCount;
+
+ MaybeCloseConnection();
+}
+
+void
+Database::SetActorAlive()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorWasAlive);
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorWasAlive = true;
+
+ // This reference will be absorbed by IPDL and released when the actor is
+ // destroyed.
+ AddRef();
+}
+
+bool
+Database::CloseInternal()
+{
+ AssertIsOnBackgroundThread();
+
+ if (mClosed) {
+ if (NS_WARN_IF(!IsInvalidated())) {
+ // Kill misbehaving child for sending the close message twice.
+ return false;
+ }
+
+ // Ignore harmless race when we just invalidated the database.
+ return true;
+ }
+
+ mClosed = true;
+
+ if (gConnectionPool) {
+ gConnectionPool->CloseDatabaseWhenIdle(Id());
+ }
+
+ DatabaseActorInfo* info;
+ MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
+
+ MOZ_ASSERT(info->mLiveDatabases.Contains(this));
+
+ if (info->mWaitingFactoryOp) {
+ info->mWaitingFactoryOp->NoteDatabaseClosed(this);
+ }
+
+ MaybeCloseConnection();
+
+ return true;
+}
+
+void
+Database::MaybeCloseConnection()
+{
+ AssertIsOnBackgroundThread();
+
+ if (!mTransactions.Count() &&
+ !mActiveMutableFileCount &&
+ IsClosed() &&
+ mDirectoryLock) {
+ nsCOMPtr<nsIRunnable> callback =
+ NewRunnableMethod(this, &Database::ConnectionClosedCallback);
+
+ RefPtr<WaitForTransactionsHelper> helper =
+ new WaitForTransactionsHelper(Id(), callback);
+ helper->WaitForTransactions();
+ }
+}
+
+void
+Database::ConnectionClosedCallback()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mClosed);
+ MOZ_ASSERT(!mTransactions.Count());
+ MOZ_ASSERT(!mActiveMutableFileCount);
+
+ mDirectoryLock = nullptr;
+
+ CleanupMetadata();
+
+ if (IsInvalidated() && IsActorAlive()) {
+ // Step 3 and 4 of "5.2 Closing a Database":
+ // 1. Wait for all transactions to complete.
+ // 2. Fire a close event if forced flag is set, i.e., IsInvalidated() in our
+ // implementation.
+ Unused << SendCloseAfterInvalidationComplete();
+ }
+}
+
+void
+Database::CleanupMetadata()
+{
+ AssertIsOnBackgroundThread();
+
+ if (!mMetadataCleanedUp) {
+ mMetadataCleanedUp = true;
+
+ DatabaseActorInfo* info;
+ MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
+ MOZ_ALWAYS_TRUE(info->mLiveDatabases.RemoveElement(this));
+
+ if (info->mLiveDatabases.IsEmpty()) {
+ MOZ_ASSERT(!info->mWaitingFactoryOp ||
+ !info->mWaitingFactoryOp->HasBlockedDatabases());
+ gLiveDatabaseHashtable->Remove(Id());
+ }
+
+ // Match the IncreaseBusyCount in OpenDatabaseOp::EnsureDatabaseActor().
+ DecreaseBusyCount();
+ }
+}
+
+bool
+Database::VerifyRequestParams(const DatabaseRequestParams& aParams) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != DatabaseRequestParams::T__None);
+
+ switch (aParams.type()) {
+ case DatabaseRequestParams::TCreateFileParams: {
+ if (NS_WARN_IF(mFileHandleDisabled)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const CreateFileParams& params = aParams.get_CreateFileParams();
+
+ if (NS_WARN_IF(params.name().IsEmpty())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+void
+Database::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+
+ if (!IsInvalidated()) {
+ Invalidate();
+ }
+}
+
+PBackgroundIDBDatabaseFileParent*
+Database::AllocPBackgroundIDBDatabaseFileParent(PBlobParent* aBlobParent)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aBlobParent);
+
+ RefPtr<BlobImpl> blobImpl =
+ static_cast<BlobParent*>(aBlobParent)->GetBlobImpl();
+ MOZ_ASSERT(blobImpl);
+
+ RefPtr<FileInfo> fileInfo;
+ RefPtr<DatabaseFile> actor;
+
+ RefPtr<BlobImplStoredFile> storedFileImpl = do_QueryObject(blobImpl);
+ if (storedFileImpl && storedFileImpl->IsShareable(mFileManager)) {
+ // This blob was previously shared with the child.
+ fileInfo = storedFileImpl->GetFileInfo();
+ MOZ_ASSERT(fileInfo);
+
+ actor = new DatabaseFile(fileInfo);
+ } else {
+ // This is a blob we haven't seen before.
+ fileInfo = mFileManager->GetNewFileInfo();
+ MOZ_ASSERT(fileInfo);
+
+ actor = new DatabaseFile(blobImpl, fileInfo);
+ }
+
+ MOZ_ASSERT(actor);
+
+ return actor.forget().take();
+}
+
+bool
+Database::DeallocPBackgroundIDBDatabaseFileParent(
+ PBackgroundIDBDatabaseFileParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<DatabaseFile> actor =
+ dont_AddRef(static_cast<DatabaseFile*>(aActor));
+ return true;
+}
+
+PBackgroundIDBDatabaseRequestParent*
+Database::AllocPBackgroundIDBDatabaseRequestParent(
+ const DatabaseRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != DatabaseRequestParams::T__None);
+
+#ifdef DEBUG
+ // Always verify parameters in DEBUG builds!
+ bool trustParams = false;
+#else
+ PBackgroundParent* backgroundActor = GetBackgroundParent();
+ MOZ_ASSERT(backgroundActor);
+
+ bool trustParams = !BackgroundParent::IsOtherProcessActor(backgroundActor);
+#endif
+
+ if (NS_WARN_IF(!trustParams && !VerifyRequestParams(aParams))) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<DatabaseOp> actor;
+
+ switch (aParams.type()) {
+ case DatabaseRequestParams::TCreateFileParams: {
+ actor = new CreateFileOp(this, aParams);
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ MOZ_ASSERT(actor);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+bool
+Database::RecvPBackgroundIDBDatabaseRequestConstructor(
+ PBackgroundIDBDatabaseRequestParent* aActor,
+ const DatabaseRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != DatabaseRequestParams::T__None);
+
+ auto* op = static_cast<DatabaseOp*>(aActor);
+
+ op->RunImmediately();
+
+ return true;
+}
+
+bool
+Database::DeallocPBackgroundIDBDatabaseRequestParent(
+ PBackgroundIDBDatabaseRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<DatabaseOp> op = dont_AddRef(static_cast<DatabaseOp*>(aActor));
+ return true;
+}
+
+PBackgroundIDBTransactionParent*
+Database::AllocPBackgroundIDBTransactionParent(
+ const nsTArray<nsString>& aObjectStoreNames,
+ const Mode& aMode)
+{
+ AssertIsOnBackgroundThread();
+
+ // Once a database is closed it must not try to open new transactions.
+ if (NS_WARN_IF(mClosed)) {
+ if (!mInvalidated) {
+ ASSERT_UNLESS_FUZZING();
+ }
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(aObjectStoreNames.IsEmpty())) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(aMode != IDBTransaction::READ_ONLY &&
+ aMode != IDBTransaction::READ_WRITE &&
+ aMode != IDBTransaction::READ_WRITE_FLUSH &&
+ aMode != IDBTransaction::CLEANUP)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ // If this is a readwrite transaction to a chrome database make sure the child
+ // has write access.
+ if (NS_WARN_IF((aMode == IDBTransaction::READ_WRITE ||
+ aMode == IDBTransaction::READ_WRITE_FLUSH ||
+ aMode == IDBTransaction::CLEANUP) &&
+ mPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
+ !mChromeWriteAccessAllowed)) {
+ return nullptr;
+ }
+
+ const ObjectStoreTable& objectStores = mMetadata->mObjectStores;
+ const uint32_t nameCount = aObjectStoreNames.Length();
+
+ if (NS_WARN_IF(nameCount > objectStores.Count())) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ FallibleTArray<RefPtr<FullObjectStoreMetadata>> fallibleObjectStores;
+ if (NS_WARN_IF(!fallibleObjectStores.SetCapacity(nameCount, fallible))) {
+ return nullptr;
+ }
+
+ for (uint32_t nameIndex = 0; nameIndex < nameCount; nameIndex++) {
+ const nsString& name = aObjectStoreNames[nameIndex];
+
+ if (nameIndex) {
+ // Make sure that this name is sorted properly and not a duplicate.
+ if (NS_WARN_IF(name <= aObjectStoreNames[nameIndex - 1])) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ }
+
+ for (auto iter = objectStores.ConstIter(); !iter.Done(); iter.Next()) {
+ auto value = iter.Data();
+ MOZ_ASSERT(iter.Key());
+
+ if (name == value->mCommonMetadata.name() && !value->mDeleted) {
+ if (NS_WARN_IF(!fallibleObjectStores.AppendElement(value, fallible))) {
+ return nullptr;
+ }
+ break;
+ }
+ }
+ }
+
+ nsTArray<RefPtr<FullObjectStoreMetadata>> infallibleObjectStores;
+ infallibleObjectStores.SwapElements(fallibleObjectStores);
+
+ RefPtr<NormalTransaction> transaction =
+ new NormalTransaction(this, aMode, infallibleObjectStores);
+
+ MOZ_ASSERT(infallibleObjectStores.IsEmpty());
+
+ return transaction.forget().take();
+}
+
+bool
+Database::RecvPBackgroundIDBTransactionConstructor(
+ PBackgroundIDBTransactionParent* aActor,
+ InfallibleTArray<nsString>&& aObjectStoreNames,
+ const Mode& aMode)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
+ MOZ_ASSERT(aMode == IDBTransaction::READ_ONLY ||
+ aMode == IDBTransaction::READ_WRITE ||
+ aMode == IDBTransaction::READ_WRITE_FLUSH ||
+ aMode == IDBTransaction::CLEANUP);
+ MOZ_ASSERT(!mClosed);
+
+ if (IsInvalidated()) {
+ // This is an expected race. We don't want the child to die here, just don't
+ // actually do any work.
+ return true;
+ }
+
+ if (!gConnectionPool) {
+ gConnectionPool = new ConnectionPool();
+ }
+
+ auto* transaction = static_cast<NormalTransaction*>(aActor);
+
+ RefPtr<StartTransactionOp> startOp = new StartTransactionOp(transaction);
+
+ uint64_t transactionId =
+ startOp->StartOnConnectionPool(GetLoggingInfo()->Id(),
+ mMetadata->mDatabaseId,
+ transaction->LoggingSerialNumber(),
+ aObjectStoreNames,
+ aMode != IDBTransaction::READ_ONLY);
+
+ transaction->SetActive(transactionId);
+
+ if (NS_WARN_IF(!RegisterTransaction(transaction))) {
+ IDB_REPORT_INTERNAL_ERR();
+ transaction->Abort(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, /* aForce */ false);
+ return true;
+ }
+
+ return true;
+}
+
+bool
+Database::DeallocPBackgroundIDBTransactionParent(
+ PBackgroundIDBTransactionParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<NormalTransaction> transaction =
+ dont_AddRef(static_cast<NormalTransaction*>(aActor));
+ return true;
+}
+
+PBackgroundIDBVersionChangeTransactionParent*
+Database::AllocPBackgroundIDBVersionChangeTransactionParent(
+ const uint64_t& aCurrentVersion,
+ const uint64_t& aRequestedVersion,
+ const int64_t& aNextObjectStoreId,
+ const int64_t& aNextIndexId)
+{
+ MOZ_CRASH("PBackgroundIDBVersionChangeTransactionParent actors should be "
+ "constructed manually!");
+}
+
+bool
+Database::DeallocPBackgroundIDBVersionChangeTransactionParent(
+ PBackgroundIDBVersionChangeTransactionParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<VersionChangeTransaction> transaction =
+ dont_AddRef(static_cast<VersionChangeTransaction*>(aActor));
+ return true;
+}
+
+Database::PBackgroundMutableFileParent*
+Database::AllocPBackgroundMutableFileParent(const nsString& aName,
+ const nsString& aType)
+{
+ MOZ_CRASH("PBackgroundMutableFileParent actors should be constructed "
+ "manually!");
+}
+
+bool
+Database::DeallocPBackgroundMutableFileParent(
+ PBackgroundMutableFileParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<MutableFile> mutableFile =
+ dont_AddRef(static_cast<MutableFile*>(aActor));
+ return true;
+}
+
+bool
+Database::RecvDeleteMe()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ return PBackgroundIDBDatabaseParent::Send__delete__(this);
+}
+
+bool
+Database::RecvBlocked()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mClosed)) {
+ return false;
+ }
+
+ DatabaseActorInfo* info;
+ MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
+
+ MOZ_ASSERT(info->mLiveDatabases.Contains(this));
+ MOZ_ASSERT(info->mWaitingFactoryOp);
+
+ info->mWaitingFactoryOp->NoteDatabaseBlocked(this);
+
+ return true;
+}
+
+bool
+Database::RecvClose()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!CloseInternal())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ return true;
+}
+
+void
+Database::
+StartTransactionOp::RunOnConnectionThread()
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(Transaction());
+ MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+
+ IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld]: "
+ "Beginning database work",
+ "IndexedDB %s: P T[%lld]: DB Start",
+ IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
+ mLoggingSerialNumber);
+
+ TransactionDatabaseOperationBase::RunOnConnectionThread();
+}
+
+nsresult
+Database::
+StartTransactionOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ Transaction()->SetActiveOnConnectionThread();
+
+ if (Transaction()->GetMode() == IDBTransaction::CLEANUP) {
+ nsresult rv = aConnection->DisableQuotaChecks();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (Transaction()->GetMode() != IDBTransaction::READ_ONLY) {
+ nsresult rv = aConnection->BeginWriteTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Database::
+StartTransactionOp::SendSuccessResult()
+{
+ // We don't need to do anything here.
+ return NS_OK;
+}
+
+bool
+Database::
+StartTransactionOp::SendFailureResult(nsresult /* aResultCode */)
+{
+ IDB_REPORT_INTERNAL_ERR();
+
+ // Abort the transaction.
+ return false;
+}
+
+void
+Database::
+StartTransactionOp::Cleanup()
+{
+#ifdef DEBUG
+ // StartTransactionOp is not a normal database operation that is tied to an
+ // actor. Do this to make our assertions happy.
+ NoteActorDestroyed();
+#endif
+
+ TransactionDatabaseOperationBase::Cleanup();
+}
+
+/*******************************************************************************
+ * TransactionBase
+ ******************************************************************************/
+
+TransactionBase::TransactionBase(Database* aDatabase, Mode aMode)
+ : mDatabase(aDatabase)
+ , mTransactionId(0)
+ , mDatabaseId(aDatabase->Id())
+ , mLoggingSerialNumber(aDatabase->GetLoggingInfo()->NextTransactionSN(aMode))
+ , mActiveRequestCount(0)
+ , mInvalidatedOnAnyThread(false)
+ , mMode(aMode)
+ , mHasBeenActive(false)
+ , mHasBeenActiveOnConnectionThread(false)
+ , mActorDestroyed(false)
+ , mInvalidated(false)
+ , mResultCode(NS_OK)
+ , mCommitOrAbortReceived(false)
+ , mCommittedOrAborted(false)
+ , mForceAborted(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aDatabase);
+ MOZ_ASSERT(mLoggingSerialNumber);
+}
+
+TransactionBase::~TransactionBase()
+{
+ MOZ_ASSERT(!mActiveRequestCount);
+ MOZ_ASSERT(mActorDestroyed);
+ MOZ_ASSERT_IF(mHasBeenActive, mCommittedOrAborted);
+}
+
+void
+TransactionBase::Abort(nsresult aResultCode, bool aForce)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(NS_FAILED(aResultCode));
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = aResultCode;
+ }
+
+ if (aForce) {
+ mForceAborted = true;
+ }
+
+ MaybeCommitOrAbort();
+}
+
+bool
+TransactionBase::RecvCommit()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ mCommitOrAbortReceived = true;
+
+ MaybeCommitOrAbort();
+ return true;
+}
+
+bool
+TransactionBase::RecvAbort(nsresult aResultCode)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(NS_SUCCEEDED(aResultCode))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(NS_ERROR_GET_MODULE(aResultCode) !=
+ NS_ERROR_MODULE_DOM_INDEXEDDB)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ mCommitOrAbortReceived = true;
+
+ Abort(aResultCode, /* aForce */ false);
+ return true;
+}
+
+void
+TransactionBase::CommitOrAbort()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mCommittedOrAborted);
+
+ mCommittedOrAborted = true;
+
+ if (!mHasBeenActive) {
+ return;
+ }
+
+ RefPtr<CommitOp> commitOp =
+ new CommitOp(this, ClampResultCode(mResultCode));
+
+ gConnectionPool->Finish(TransactionId(), commitOp);
+}
+
+already_AddRefed<FullObjectStoreMetadata>
+TransactionBase::GetMetadataForObjectStoreId(int64_t aObjectStoreId) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aObjectStoreId);
+
+ if (!aObjectStoreId) {
+ return nullptr;
+ }
+
+ RefPtr<FullObjectStoreMetadata> metadata;
+ if (!mDatabase->Metadata()->mObjectStores.Get(aObjectStoreId,
+ getter_AddRefs(metadata)) ||
+ metadata->mDeleted) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(metadata->mCommonMetadata.id() == aObjectStoreId);
+
+ return metadata.forget();
+}
+
+already_AddRefed<FullIndexMetadata>
+TransactionBase::GetMetadataForIndexId(
+ FullObjectStoreMetadata* const aObjectStoreMetadata,
+ int64_t aIndexId) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aIndexId);
+
+ if (!aIndexId) {
+ return nullptr;
+ }
+
+ RefPtr<FullIndexMetadata> metadata;
+ if (!aObjectStoreMetadata->mIndexes.Get(aIndexId, getter_AddRefs(metadata)) ||
+ metadata->mDeleted) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(metadata->mCommonMetadata.id() == aIndexId);
+
+ return metadata.forget();
+}
+
+void
+TransactionBase::NoteModifiedAutoIncrementObjectStore(
+ FullObjectStoreMetadata* aMetadata)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(aMetadata);
+
+ if (!mModifiedAutoIncrementObjectStoreMetadataArray.Contains(aMetadata)) {
+ mModifiedAutoIncrementObjectStoreMetadataArray.AppendElement(aMetadata);
+ }
+}
+
+void
+TransactionBase::ForgetModifiedAutoIncrementObjectStore(
+ FullObjectStoreMetadata* aMetadata)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(aMetadata);
+
+ mModifiedAutoIncrementObjectStoreMetadataArray.RemoveElement(aMetadata);
+}
+
+bool
+TransactionBase::VerifyRequestParams(const RequestParams& aParams) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ switch (aParams.type()) {
+ case RequestParams::TObjectStoreAddParams: {
+ const ObjectStoreAddPutParams& params =
+ aParams.get_ObjectStoreAddParams().commonParams();
+ if (NS_WARN_IF(!VerifyRequestParams(params))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TObjectStorePutParams: {
+ const ObjectStoreAddPutParams& params =
+ aParams.get_ObjectStorePutParams().commonParams();
+ if (NS_WARN_IF(!VerifyRequestParams(params))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TObjectStoreGetParams: {
+ const ObjectStoreGetParams& params = aParams.get_ObjectStoreGetParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TObjectStoreGetKeyParams: {
+ const ObjectStoreGetKeyParams& params =
+ aParams.get_ObjectStoreGetKeyParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TObjectStoreGetAllParams: {
+ const ObjectStoreGetAllParams& params =
+ aParams.get_ObjectStoreGetAllParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TObjectStoreGetAllKeysParams: {
+ const ObjectStoreGetAllKeysParams& params =
+ aParams.get_ObjectStoreGetAllKeysParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TObjectStoreDeleteParams: {
+ if (NS_WARN_IF(mMode != IDBTransaction::READ_WRITE &&
+ mMode != IDBTransaction::READ_WRITE_FLUSH &&
+ mMode != IDBTransaction::CLEANUP &&
+ mMode != IDBTransaction::VERSION_CHANGE)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const ObjectStoreDeleteParams& params =
+ aParams.get_ObjectStoreDeleteParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TObjectStoreClearParams: {
+ if (NS_WARN_IF(mMode != IDBTransaction::READ_WRITE &&
+ mMode != IDBTransaction::READ_WRITE_FLUSH &&
+ mMode != IDBTransaction::CLEANUP &&
+ mMode != IDBTransaction::VERSION_CHANGE)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const ObjectStoreClearParams& params =
+ aParams.get_ObjectStoreClearParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TObjectStoreCountParams: {
+ const ObjectStoreCountParams& params =
+ aParams.get_ObjectStoreCountParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+
+ case RequestParams::TIndexGetParams: {
+ const IndexGetParams& params = aParams.get_IndexGetParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ const RefPtr<FullIndexMetadata> indexMetadata =
+ GetMetadataForIndexId(objectStoreMetadata, params.indexId());
+ if (NS_WARN_IF(!indexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TIndexGetKeyParams: {
+ const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ const RefPtr<FullIndexMetadata> indexMetadata =
+ GetMetadataForIndexId(objectStoreMetadata, params.indexId());
+ if (NS_WARN_IF(!indexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TIndexGetAllParams: {
+ const IndexGetAllParams& params = aParams.get_IndexGetAllParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ const RefPtr<FullIndexMetadata> indexMetadata =
+ GetMetadataForIndexId(objectStoreMetadata, params.indexId());
+ if (NS_WARN_IF(!indexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TIndexGetAllKeysParams: {
+ const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ const RefPtr<FullIndexMetadata> indexMetadata =
+ GetMetadataForIndexId(objectStoreMetadata, params.indexId());
+ if (NS_WARN_IF(!indexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ case RequestParams::TIndexCountParams: {
+ const IndexCountParams& params = aParams.get_IndexCountParams();
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ const RefPtr<FullIndexMetadata> indexMetadata =
+ GetMetadataForIndexId(objectStoreMetadata, params.indexId());
+ if (NS_WARN_IF(!indexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+bool
+TransactionBase::VerifyRequestParams(const SerializedKeyRange& aParams) const
+{
+ AssertIsOnBackgroundThread();
+
+ // XXX Check more here?
+
+ if (aParams.isOnly()) {
+ if (NS_WARN_IF(aParams.lower().IsUnset())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!aParams.upper().IsUnset())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(aParams.lowerOpen())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(aParams.upperOpen())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ } else if (NS_WARN_IF(aParams.lower().IsUnset() &&
+ aParams.upper().IsUnset())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ return true;
+}
+
+bool
+TransactionBase::VerifyRequestParams(const ObjectStoreAddPutParams& aParams)
+ const
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mMode != IDBTransaction::READ_WRITE &&
+ mMode != IDBTransaction::READ_WRITE_FLUSH &&
+ mMode != IDBTransaction::VERSION_CHANGE)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullObjectStoreMetadata> objMetadata =
+ GetMetadataForObjectStoreId(aParams.objectStoreId());
+ if (NS_WARN_IF(!objMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!aParams.cloneInfo().data().data.Size())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (objMetadata->mCommonMetadata.autoIncrement() &&
+ objMetadata->mCommonMetadata.keyPath().IsValid() &&
+ aParams.key().IsUnset()) {
+ const SerializedStructuredCloneWriteInfo cloneInfo = aParams.cloneInfo();
+
+ if (NS_WARN_IF(!cloneInfo.offsetToKeyProp())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(cloneInfo.data().data.Size() < sizeof(uint64_t))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(cloneInfo.offsetToKeyProp() >
+ (cloneInfo.data().data.Size() - sizeof(uint64_t)))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ } else if (NS_WARN_IF(aParams.cloneInfo().offsetToKeyProp())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const nsTArray<IndexUpdateInfo>& updates = aParams.indexUpdateInfos();
+
+ for (uint32_t index = 0; index < updates.Length(); index++) {
+ RefPtr<FullIndexMetadata> indexMetadata =
+ GetMetadataForIndexId(objMetadata, updates[index].indexId());
+ if (NS_WARN_IF(!indexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(updates[index].value().IsUnset())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ MOZ_ASSERT(!updates[index].value().GetBuffer().IsEmpty());
+ }
+
+ const nsTArray<FileAddInfo>& fileAddInfos = aParams.fileAddInfos();
+
+ for (uint32_t index = 0; index < fileAddInfos.Length(); index++) {
+ const FileAddInfo& fileAddInfo = fileAddInfos[index];
+
+ const DatabaseOrMutableFile& file = fileAddInfo.file();
+ MOZ_ASSERT(file.type() != DatabaseOrMutableFile::T__None);
+
+ switch (fileAddInfo.type()) {
+ case StructuredCloneFile::eBlob:
+ if (NS_WARN_IF(file.type() !=
+ DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!file.get_PBackgroundIDBDatabaseFileParent())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+
+ case StructuredCloneFile::eMutableFile: {
+ if (NS_WARN_IF(file.type() !=
+ DatabaseOrMutableFile::TPBackgroundMutableFileParent)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mDatabase->IsFileHandleDisabled())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ auto mutableFile =
+ static_cast<MutableFile*>(file.get_PBackgroundMutableFileParent());
+
+ if (NS_WARN_IF(!mutableFile)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ Database* database = mutableFile->GetDatabase();
+ if (NS_WARN_IF(!database)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(database->Id() != mDatabase->Id())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case StructuredCloneFile::eStructuredClone:
+ ASSERT_UNLESS_FUZZING();
+ return false;
+
+ case StructuredCloneFile::eWasmBytecode:
+ case StructuredCloneFile::eWasmCompiled:
+ if (NS_WARN_IF(file.type() !=
+ DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ if (NS_WARN_IF(!file.get_PBackgroundIDBDatabaseFileParent())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+
+ case StructuredCloneFile::eEndGuard:
+ ASSERT_UNLESS_FUZZING();
+ return false;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ }
+
+ return true;
+}
+
+bool
+TransactionBase::VerifyRequestParams(const OptionalKeyRange& aParams) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != OptionalKeyRange::T__None);
+
+ switch (aParams.type()) {
+ case OptionalKeyRange::TSerializedKeyRange:
+ if (NS_WARN_IF(!VerifyRequestParams(aParams.get_SerializedKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+
+ case OptionalKeyRange::Tvoid_t:
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+void
+TransactionBase::NoteActiveRequest()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveRequestCount < UINT64_MAX);
+
+ mActiveRequestCount++;
+}
+
+void
+TransactionBase::NoteFinishedRequest()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveRequestCount);
+
+ mActiveRequestCount--;
+
+ MaybeCommitOrAbort();
+}
+
+void
+TransactionBase::Invalidate()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread);
+
+ if (!mInvalidated) {
+ mInvalidated = true;
+ mInvalidatedOnAnyThread = true;
+
+ Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, /* aForce */ false);
+ }
+}
+
+PBackgroundIDBRequestParent*
+TransactionBase::AllocRequest(const RequestParams& aParams, bool aTrustParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+#ifdef DEBUG
+ // Always verify parameters in DEBUG builds!
+ aTrustParams = false;
+#endif
+
+ if (!aTrustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<NormalTransactionOp> actor;
+
+ switch (aParams.type()) {
+ case RequestParams::TObjectStoreAddParams:
+ case RequestParams::TObjectStorePutParams:
+ actor = new ObjectStoreAddOrPutRequestOp(this, aParams);
+ break;
+
+ case RequestParams::TObjectStoreGetParams:
+ actor =
+ new ObjectStoreGetRequestOp(this, aParams, /* aGetAll */ false);
+ break;
+
+ case RequestParams::TObjectStoreGetAllParams:
+ actor =
+ new ObjectStoreGetRequestOp(this, aParams, /* aGetAll */ true);
+ break;
+
+ case RequestParams::TObjectStoreGetKeyParams:
+ actor =
+ new ObjectStoreGetKeyRequestOp(this, aParams, /* aGetAll */ false);
+ break;
+
+ case RequestParams::TObjectStoreGetAllKeysParams:
+ actor =
+ new ObjectStoreGetKeyRequestOp(this, aParams, /* aGetAll */ true);
+ break;
+
+ case RequestParams::TObjectStoreDeleteParams:
+ actor =
+ new ObjectStoreDeleteRequestOp(this,
+ aParams.get_ObjectStoreDeleteParams());
+ break;
+
+ case RequestParams::TObjectStoreClearParams:
+ actor =
+ new ObjectStoreClearRequestOp(this,
+ aParams.get_ObjectStoreClearParams());
+ break;
+
+ case RequestParams::TObjectStoreCountParams:
+ actor =
+ new ObjectStoreCountRequestOp(this,
+ aParams.get_ObjectStoreCountParams());
+ break;
+
+ case RequestParams::TIndexGetParams:
+ actor = new IndexGetRequestOp(this, aParams, /* aGetAll */ false);
+ break;
+
+ case RequestParams::TIndexGetKeyParams:
+ actor = new IndexGetKeyRequestOp(this, aParams, /* aGetAll */ false);
+ break;
+
+ case RequestParams::TIndexGetAllParams:
+ actor = new IndexGetRequestOp(this, aParams, /* aGetAll */ true);
+ break;
+
+ case RequestParams::TIndexGetAllKeysParams:
+ actor = new IndexGetKeyRequestOp(this, aParams, /* aGetAll */ true);
+ break;
+
+ case RequestParams::TIndexCountParams:
+ actor = new IndexCountRequestOp(this, aParams);
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ MOZ_ASSERT(actor);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+bool
+TransactionBase::StartRequest(PBackgroundIDBRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ auto* op = static_cast<NormalTransactionOp*>(aActor);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ op->Cleanup();
+ return false;
+ }
+
+ op->DispatchToConnectionPool();
+ return true;
+}
+
+bool
+TransactionBase::DeallocRequest(PBackgroundIDBRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<NormalTransactionOp> actor =
+ dont_AddRef(static_cast<NormalTransactionOp*>(aActor));
+ return true;
+}
+
+PBackgroundIDBCursorParent*
+TransactionBase::AllocCursor(const OpenCursorParams& aParams, bool aTrustParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
+
+#ifdef DEBUG
+ // Always verify parameters in DEBUG builds!
+ aTrustParams = false;
+#endif
+
+ OpenCursorParams::Type type = aParams.type();
+ RefPtr<FullObjectStoreMetadata> objectStoreMetadata;
+ RefPtr<FullIndexMetadata> indexMetadata;
+ Cursor::Direction direction;
+
+ switch (type) {
+ case OpenCursorParams::TObjectStoreOpenCursorParams: {
+ const ObjectStoreOpenCursorParams& params =
+ aParams.get_ObjectStoreOpenCursorParams();
+ objectStoreMetadata = GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ if (aTrustParams &&
+ NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ direction = params.direction();
+ break;
+ }
+
+ case OpenCursorParams::TObjectStoreOpenKeyCursorParams: {
+ const ObjectStoreOpenKeyCursorParams& params =
+ aParams.get_ObjectStoreOpenKeyCursorParams();
+ objectStoreMetadata = GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ if (aTrustParams &&
+ NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ direction = params.direction();
+ break;
+ }
+
+ case OpenCursorParams::TIndexOpenCursorParams: {
+ const IndexOpenCursorParams& params = aParams.get_IndexOpenCursorParams();
+ objectStoreMetadata = GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ indexMetadata =
+ GetMetadataForIndexId(objectStoreMetadata, params.indexId());
+ if (NS_WARN_IF(!indexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ if (aTrustParams &&
+ NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ direction = params.direction();
+ break;
+ }
+
+ case OpenCursorParams::TIndexOpenKeyCursorParams: {
+ const IndexOpenKeyCursorParams& params =
+ aParams.get_IndexOpenKeyCursorParams();
+ objectStoreMetadata = GetMetadataForObjectStoreId(params.objectStoreId());
+ if (NS_WARN_IF(!objectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ indexMetadata =
+ GetMetadataForIndexId(objectStoreMetadata, params.indexId());
+ if (NS_WARN_IF(!indexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ if (aTrustParams &&
+ NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ direction = params.direction();
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<Cursor> actor =
+ new Cursor(this, type, objectStoreMetadata, indexMetadata, direction);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+bool
+TransactionBase::StartCursor(PBackgroundIDBCursorParent* aActor,
+ const OpenCursorParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
+
+ auto* op = static_cast<Cursor*>(aActor);
+
+ if (NS_WARN_IF(!op->Start(aParams))) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+TransactionBase::DeallocCursor(PBackgroundIDBCursorParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<Cursor> actor = dont_AddRef(static_cast<Cursor*>(aActor));
+ return true;
+}
+
+/*******************************************************************************
+ * NormalTransaction
+ ******************************************************************************/
+
+NormalTransaction::NormalTransaction(
+ Database* aDatabase,
+ TransactionBase::Mode aMode,
+ nsTArray<RefPtr<FullObjectStoreMetadata>>& aObjectStores)
+ : TransactionBase(aDatabase, aMode)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!aObjectStores.IsEmpty());
+
+ mObjectStores.SwapElements(aObjectStores);
+}
+
+bool
+NormalTransaction::IsSameProcessActor()
+{
+ AssertIsOnBackgroundThread();
+
+ PBackgroundParent* actor = Manager()->Manager()->Manager();
+ MOZ_ASSERT(actor);
+
+ return !BackgroundParent::IsOtherProcessActor(actor);
+}
+
+void
+NormalTransaction::SendCompleteNotification(nsresult aResult)
+{
+ AssertIsOnBackgroundThread();
+
+ if (!IsActorDestroyed()) {
+ Unused << SendComplete(aResult);
+ }
+}
+
+void
+NormalTransaction::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+
+ NoteActorDestroyed();
+
+ if (!mCommittedOrAborted) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ IDB_REPORT_INTERNAL_ERR();
+ mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mForceAborted = true;
+
+ MaybeCommitOrAbort();
+ }
+}
+
+bool
+NormalTransaction::RecvDeleteMe()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ return PBackgroundIDBTransactionParent::Send__delete__(this);
+}
+
+bool
+NormalTransaction::RecvCommit()
+{
+ AssertIsOnBackgroundThread();
+
+ return TransactionBase::RecvCommit();
+}
+
+bool
+NormalTransaction::RecvAbort(const nsresult& aResultCode)
+{
+ AssertIsOnBackgroundThread();
+
+ return TransactionBase::RecvAbort(aResultCode);
+}
+
+PBackgroundIDBRequestParent*
+NormalTransaction::AllocPBackgroundIDBRequestParent(
+ const RequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ return AllocRequest(aParams, IsSameProcessActor());
+}
+
+bool
+NormalTransaction::RecvPBackgroundIDBRequestConstructor(
+ PBackgroundIDBRequestParent* aActor,
+ const RequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ return StartRequest(aActor);
+}
+
+bool
+NormalTransaction::DeallocPBackgroundIDBRequestParent(
+ PBackgroundIDBRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return DeallocRequest(aActor);
+}
+
+PBackgroundIDBCursorParent*
+NormalTransaction::AllocPBackgroundIDBCursorParent(
+ const OpenCursorParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+
+ return AllocCursor(aParams, IsSameProcessActor());
+}
+
+bool
+NormalTransaction::RecvPBackgroundIDBCursorConstructor(
+ PBackgroundIDBCursorParent* aActor,
+ const OpenCursorParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
+
+ return StartCursor(aActor, aParams);
+}
+
+bool
+NormalTransaction::DeallocPBackgroundIDBCursorParent(
+ PBackgroundIDBCursorParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return DeallocCursor(aActor);
+}
+
+/*******************************************************************************
+ * VersionChangeTransaction
+ ******************************************************************************/
+
+VersionChangeTransaction::VersionChangeTransaction(
+ OpenDatabaseOp* aOpenDatabaseOp)
+ : TransactionBase(aOpenDatabaseOp->mDatabase,
+ IDBTransaction::VERSION_CHANGE)
+ , mOpenDatabaseOp(aOpenDatabaseOp)
+ , mActorWasAlive(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aOpenDatabaseOp);
+}
+
+VersionChangeTransaction::~VersionChangeTransaction()
+{
+#ifdef DEBUG
+ // Silence the base class' destructor assertion if we never made this actor
+ // live.
+ FakeActorDestroyed();
+#endif
+}
+
+bool
+VersionChangeTransaction::IsSameProcessActor()
+{
+ AssertIsOnBackgroundThread();
+
+ PBackgroundParent* actor = Manager()->Manager()->Manager();
+ MOZ_ASSERT(actor);
+
+ return !BackgroundParent::IsOtherProcessActor(actor);
+}
+
+void
+VersionChangeTransaction::SetActorAlive()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorWasAlive);
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ mActorWasAlive = true;
+
+ // This reference will be absorbed by IPDL and released when the actor is
+ // destroyed.
+ AddRef();
+}
+
+bool
+VersionChangeTransaction::CopyDatabaseMetadata()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mOldMetadata);
+
+ const RefPtr<FullDatabaseMetadata> origMetadata =
+ GetDatabase()->Metadata();
+ MOZ_ASSERT(origMetadata);
+
+ RefPtr<FullDatabaseMetadata> newMetadata = origMetadata->Duplicate();
+ if (NS_WARN_IF(!newMetadata)) {
+ return false;
+ }
+
+ // Replace the live metadata with the new mutable copy.
+ DatabaseActorInfo* info;
+ MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(origMetadata->mDatabaseId,
+ &info));
+ MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
+ MOZ_ASSERT(info->mMetadata == origMetadata);
+
+ mOldMetadata = info->mMetadata.forget();
+ info->mMetadata.swap(newMetadata);
+
+ // Replace metadata pointers for all live databases.
+ for (uint32_t count = info->mLiveDatabases.Length(), index = 0;
+ index < count;
+ index++) {
+ info->mLiveDatabases[index]->mMetadata = info->mMetadata;
+ }
+
+ return true;
+}
+
+void
+VersionChangeTransaction::UpdateMetadata(nsresult aResult)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(GetDatabase());
+ MOZ_ASSERT(mOpenDatabaseOp);
+ MOZ_ASSERT(!!mActorWasAlive == !!mOpenDatabaseOp->mDatabase);
+ MOZ_ASSERT_IF(mActorWasAlive, !mOpenDatabaseOp->mDatabaseId.IsEmpty());
+
+ if (IsActorDestroyed() || !mActorWasAlive) {
+ return;
+ }
+
+ RefPtr<FullDatabaseMetadata> oldMetadata;
+ mOldMetadata.swap(oldMetadata);
+
+ DatabaseActorInfo* info;
+ if (!gLiveDatabaseHashtable->Get(oldMetadata->mDatabaseId, &info)) {
+ return;
+ }
+
+ MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
+
+ if (NS_SUCCEEDED(aResult)) {
+ // Remove all deleted objectStores and indexes, then mark immutable.
+ for (auto objectStoreIter = info->mMetadata->mObjectStores.Iter();
+ !objectStoreIter.Done();
+ objectStoreIter.Next()) {
+ MOZ_ASSERT(objectStoreIter.Key());
+ RefPtr<FullObjectStoreMetadata>& metadata = objectStoreIter.Data();
+ MOZ_ASSERT(metadata);
+
+ if (metadata->mDeleted) {
+ objectStoreIter.Remove();
+ continue;
+ }
+
+ for (auto indexIter = metadata->mIndexes.Iter();
+ !indexIter.Done();
+ indexIter.Next()) {
+ MOZ_ASSERT(indexIter.Key());
+ RefPtr<FullIndexMetadata>& index = indexIter.Data();
+ MOZ_ASSERT(index);
+
+ if (index->mDeleted) {
+ indexIter.Remove();
+ }
+ }
+#ifdef DEBUG
+ metadata->mIndexes.MarkImmutable();
+#endif
+ }
+#ifdef DEBUG
+ info->mMetadata->mObjectStores.MarkImmutable();
+#endif
+ } else {
+ // Replace metadata pointers for all live databases.
+ info->mMetadata = oldMetadata.forget();
+
+ for (uint32_t count = info->mLiveDatabases.Length(), index = 0;
+ index < count;
+ index++) {
+ info->mLiveDatabases[index]->mMetadata = info->mMetadata;
+ }
+ }
+}
+
+void
+VersionChangeTransaction::SendCompleteNotification(nsresult aResult)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mOpenDatabaseOp);
+ MOZ_ASSERT_IF(!mActorWasAlive, NS_FAILED(mOpenDatabaseOp->mResultCode));
+ MOZ_ASSERT_IF(!mActorWasAlive,
+ mOpenDatabaseOp->mState > OpenDatabaseOp::State::SendingResults);
+
+ RefPtr<OpenDatabaseOp> openDatabaseOp;
+ mOpenDatabaseOp.swap(openDatabaseOp);
+
+ if (!mActorWasAlive) {
+ return;
+ }
+
+ if (NS_FAILED(aResult) && NS_SUCCEEDED(openDatabaseOp->mResultCode)) {
+ // 3.3.1 Opening a database:
+ // "If the upgrade transaction was aborted, run the steps for closing a
+ // database connection with connection, create and return a new AbortError
+ // exception and abort these steps."
+ openDatabaseOp->mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
+ }
+
+ openDatabaseOp->mState = OpenDatabaseOp::State::SendingResults;
+
+ if (!IsActorDestroyed()) {
+ Unused << SendComplete(aResult);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(openDatabaseOp->Run());
+}
+
+void
+VersionChangeTransaction::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+
+ NoteActorDestroyed();
+
+ if (!mCommittedOrAborted) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ IDB_REPORT_INTERNAL_ERR();
+ mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mForceAborted = true;
+
+ MaybeCommitOrAbort();
+ }
+}
+
+bool
+VersionChangeTransaction::RecvDeleteMe()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ return PBackgroundIDBVersionChangeTransactionParent::Send__delete__(this);
+}
+
+bool
+VersionChangeTransaction::RecvCommit()
+{
+ AssertIsOnBackgroundThread();
+
+ return TransactionBase::RecvCommit();
+}
+
+bool
+VersionChangeTransaction::RecvAbort(const nsresult& aResultCode)
+{
+ AssertIsOnBackgroundThread();
+
+ return TransactionBase::RecvAbort(aResultCode);
+}
+
+bool
+VersionChangeTransaction::RecvCreateObjectStore(
+ const ObjectStoreMetadata& aMetadata)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!aMetadata.id())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
+ MOZ_ASSERT(dbMetadata);
+
+ if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ auto* foundMetadata =
+ MetadataNameOrIdMatcher<FullObjectStoreMetadata>::Match(
+ dbMetadata->mObjectStores, aMetadata.id(), aMetadata.name());
+
+ if (NS_WARN_IF(foundMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullObjectStoreMetadata> newMetadata = new FullObjectStoreMetadata();
+ newMetadata->mCommonMetadata = aMetadata;
+ newMetadata->mNextAutoIncrementId = aMetadata.autoIncrement() ? 1 : 0;
+ newMetadata->mCommittedAutoIncrementId = newMetadata->mNextAutoIncrementId;
+
+ if (NS_WARN_IF(!dbMetadata->mObjectStores.Put(aMetadata.id(), newMetadata,
+ fallible))) {
+ return false;
+ }
+
+ dbMetadata->mNextObjectStoreId++;
+
+ RefPtr<CreateObjectStoreOp> op = new CreateObjectStoreOp(this, aMetadata);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ op->Cleanup();
+ return false;
+ }
+
+ op->DispatchToConnectionPool();
+
+ return true;
+}
+
+bool
+VersionChangeTransaction::RecvDeleteObjectStore(const int64_t& aObjectStoreId)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!aObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
+ MOZ_ASSERT(dbMetadata);
+ MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
+
+ if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullObjectStoreMetadata> foundMetadata =
+ GetMetadataForObjectStoreId(aObjectStoreId);
+
+ if (NS_WARN_IF(!foundMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ foundMetadata->mDeleted = true;
+
+ bool isLastObjectStore = true;
+ DebugOnly<bool> foundTargetId = false;
+ for (auto iter = dbMetadata->mObjectStores.Iter();
+ !iter.Done();
+ iter.Next()) {
+ if (uint64_t(aObjectStoreId) == iter.Key()) {
+ foundTargetId = true;
+ } else if (!iter.UserData()->mDeleted) {
+ isLastObjectStore = false;
+ break;
+ }
+ }
+ MOZ_ASSERT_IF(isLastObjectStore, foundTargetId);
+
+ RefPtr<DeleteObjectStoreOp> op =
+ new DeleteObjectStoreOp(this, foundMetadata, isLastObjectStore);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ op->Cleanup();
+ return false;
+ }
+
+ op->DispatchToConnectionPool();
+
+ return true;
+}
+
+bool
+VersionChangeTransaction::RecvRenameObjectStore(const int64_t& aObjectStoreId,
+ const nsString& aName)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!aObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
+ MOZ_ASSERT(dbMetadata);
+ MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
+
+ if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullObjectStoreMetadata> foundMetadata =
+ GetMetadataForObjectStoreId(aObjectStoreId);
+
+ if (NS_WARN_IF(!foundMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ foundMetadata->mCommonMetadata.name() = aName;
+
+ RefPtr<RenameObjectStoreOp> renameOp =
+ new RenameObjectStoreOp(this, foundMetadata);
+
+ if (NS_WARN_IF(!renameOp->Init(this))) {
+ renameOp->Cleanup();
+ return false;
+ }
+
+ renameOp->DispatchToConnectionPool();
+
+ return true;
+}
+
+bool
+VersionChangeTransaction::RecvCreateIndex(const int64_t& aObjectStoreId,
+ const IndexMetadata& aMetadata)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!aObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!aMetadata.id())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
+ MOZ_ASSERT(dbMetadata);
+
+ if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextIndexId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
+ GetMetadataForObjectStoreId(aObjectStoreId);
+
+ if (NS_WARN_IF(!foundObjectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullIndexMetadata> foundIndexMetadata =
+ MetadataNameOrIdMatcher<FullIndexMetadata>::Match(
+ foundObjectStoreMetadata->mIndexes, aMetadata.id(), aMetadata.name());
+
+ if (NS_WARN_IF(foundIndexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullIndexMetadata> newMetadata = new FullIndexMetadata();
+ newMetadata->mCommonMetadata = aMetadata;
+
+ if (NS_WARN_IF(!foundObjectStoreMetadata->mIndexes.Put(aMetadata.id(),
+ newMetadata,
+ fallible))) {
+ return false;
+ }
+
+ dbMetadata->mNextIndexId++;
+
+ RefPtr<CreateIndexOp> op =
+ new CreateIndexOp(this, aObjectStoreId, aMetadata);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ op->Cleanup();
+ return false;
+ }
+
+ op->DispatchToConnectionPool();
+
+ return true;
+}
+
+bool
+VersionChangeTransaction::RecvDeleteIndex(const int64_t& aObjectStoreId,
+ const int64_t& aIndexId)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!aObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!aIndexId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
+ MOZ_ASSERT(dbMetadata);
+ MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
+ MOZ_ASSERT(dbMetadata->mNextIndexId > 0);
+
+ if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
+ GetMetadataForObjectStoreId(aObjectStoreId);
+
+ if (NS_WARN_IF(!foundObjectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullIndexMetadata> foundIndexMetadata =
+ GetMetadataForIndexId(foundObjectStoreMetadata, aIndexId);
+
+ if (NS_WARN_IF(!foundIndexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ foundIndexMetadata->mDeleted = true;
+
+ bool isLastIndex = true;
+ DebugOnly<bool> foundTargetId = false;
+ for (auto iter = foundObjectStoreMetadata->mIndexes.ConstIter();
+ !iter.Done();
+ iter.Next()) {
+ if (uint64_t(aIndexId) == iter.Key()) {
+ foundTargetId = true;
+ } else if (!iter.UserData()->mDeleted) {
+ isLastIndex = false;
+ break;
+ }
+ }
+ MOZ_ASSERT_IF(isLastIndex, foundTargetId);
+
+ RefPtr<DeleteIndexOp> op =
+ new DeleteIndexOp(this,
+ aObjectStoreId,
+ aIndexId,
+ foundIndexMetadata->mCommonMetadata.unique(),
+ isLastIndex);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ op->Cleanup();
+ return false;
+ }
+
+ op->DispatchToConnectionPool();
+
+ return true;
+}
+
+bool
+VersionChangeTransaction::RecvRenameIndex(const int64_t& aObjectStoreId,
+ const int64_t& aIndexId,
+ const nsString& aName)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!aObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!aIndexId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
+ MOZ_ASSERT(dbMetadata);
+ MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
+ MOZ_ASSERT(dbMetadata->mNextIndexId > 0);
+
+ if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
+ GetMetadataForObjectStoreId(aObjectStoreId);
+
+ if (NS_WARN_IF(!foundObjectStoreMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<FullIndexMetadata> foundIndexMetadata =
+ GetMetadataForIndexId(foundObjectStoreMetadata, aIndexId);
+
+ if (NS_WARN_IF(!foundIndexMetadata)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ foundIndexMetadata->mCommonMetadata.name() = aName;
+
+ RefPtr<RenameIndexOp> renameOp =
+ new RenameIndexOp(this, foundIndexMetadata, aObjectStoreId);
+
+ if (NS_WARN_IF(!renameOp->Init(this))) {
+ renameOp->Cleanup();
+ return false;
+ }
+
+ renameOp->DispatchToConnectionPool();
+
+ return true;
+}
+
+PBackgroundIDBRequestParent*
+VersionChangeTransaction::AllocPBackgroundIDBRequestParent(
+ const RequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ return AllocRequest(aParams, IsSameProcessActor());
+}
+
+bool
+VersionChangeTransaction::RecvPBackgroundIDBRequestConstructor(
+ PBackgroundIDBRequestParent* aActor,
+ const RequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ return StartRequest(aActor);
+}
+
+bool
+VersionChangeTransaction::DeallocPBackgroundIDBRequestParent(
+ PBackgroundIDBRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return DeallocRequest(aActor);
+}
+
+PBackgroundIDBCursorParent*
+VersionChangeTransaction::AllocPBackgroundIDBCursorParent(
+ const OpenCursorParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+
+ return AllocCursor(aParams, IsSameProcessActor());
+}
+
+bool
+VersionChangeTransaction::RecvPBackgroundIDBCursorConstructor(
+ PBackgroundIDBCursorParent* aActor,
+ const OpenCursorParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
+
+ return StartCursor(aActor, aParams);
+}
+
+bool
+VersionChangeTransaction::DeallocPBackgroundIDBCursorParent(
+ PBackgroundIDBCursorParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return DeallocCursor(aActor);
+}
+
+/*******************************************************************************
+ * Cursor
+ ******************************************************************************/
+
+Cursor::Cursor(TransactionBase* aTransaction,
+ Type aType,
+ FullObjectStoreMetadata* aObjectStoreMetadata,
+ FullIndexMetadata* aIndexMetadata,
+ Direction aDirection)
+ : mTransaction(aTransaction)
+ , mBackgroundParent(nullptr)
+ , mObjectStoreMetadata(aObjectStoreMetadata)
+ , mIndexMetadata(aIndexMetadata)
+ , mObjectStoreId(aObjectStoreMetadata->mCommonMetadata.id())
+ , mIndexId(aIndexMetadata ? aIndexMetadata->mCommonMetadata.id() : 0)
+ , mCurrentlyRunningOp(nullptr)
+ , mType(aType)
+ , mDirection(aDirection)
+ , mUniqueIndex(aIndexMetadata ?
+ aIndexMetadata->mCommonMetadata.unique() :
+ false)
+ , mIsSameProcessActor(!BackgroundParent::IsOtherProcessActor(
+ aTransaction->GetBackgroundParent()))
+ , mActorDestroyed(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransaction);
+ MOZ_ASSERT(aType != OpenCursorParams::T__None);
+ MOZ_ASSERT(aObjectStoreMetadata);
+ MOZ_ASSERT_IF(aType == OpenCursorParams::TIndexOpenCursorParams ||
+ aType == OpenCursorParams::TIndexOpenKeyCursorParams,
+ aIndexMetadata);
+
+ if (mType == OpenCursorParams::TObjectStoreOpenCursorParams ||
+ mType == OpenCursorParams::TIndexOpenCursorParams) {
+ mDatabase = aTransaction->GetDatabase();
+ MOZ_ASSERT(mDatabase);
+
+ mFileManager = mDatabase->GetFileManager();
+ MOZ_ASSERT(mFileManager);
+
+ mBackgroundParent = aTransaction->GetBackgroundParent();
+ MOZ_ASSERT(mBackgroundParent);
+ }
+
+ if (aIndexMetadata) {
+ mLocale = aIndexMetadata->mCommonMetadata.locale();
+ }
+
+ static_assert(OpenCursorParams::T__None == 0 &&
+ OpenCursorParams::T__Last == 4,
+ "Lots of code here assumes only four types of cursors!");
+}
+
+bool
+Cursor::VerifyRequestParams(const CursorRequestParams& aParams) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
+ MOZ_ASSERT(mObjectStoreMetadata);
+ MOZ_ASSERT_IF(mType == OpenCursorParams::TIndexOpenCursorParams ||
+ mType == OpenCursorParams::TIndexOpenKeyCursorParams,
+ mIndexMetadata);
+
+#ifdef DEBUG
+ {
+ RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ mTransaction->GetMetadataForObjectStoreId(mObjectStoreId);
+ if (objectStoreMetadata) {
+ MOZ_ASSERT(objectStoreMetadata == mObjectStoreMetadata);
+ } else {
+ MOZ_ASSERT(mObjectStoreMetadata->mDeleted);
+ }
+
+ if (objectStoreMetadata &&
+ (mType == OpenCursorParams::TIndexOpenCursorParams ||
+ mType == OpenCursorParams::TIndexOpenKeyCursorParams)) {
+ RefPtr<FullIndexMetadata> indexMetadata =
+ mTransaction->GetMetadataForIndexId(objectStoreMetadata, mIndexId);
+ if (indexMetadata) {
+ MOZ_ASSERT(indexMetadata == mIndexMetadata);
+ } else {
+ MOZ_ASSERT(mIndexMetadata->mDeleted);
+ }
+ }
+ }
+#endif
+
+ if (NS_WARN_IF(mObjectStoreMetadata->mDeleted) ||
+ (mIndexMetadata && NS_WARN_IF(mIndexMetadata->mDeleted))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const Key& sortKey = IsLocaleAware() ? mSortKey : mKey;
+
+ switch (aParams.type()) {
+ case CursorRequestParams::TContinueParams: {
+ const Key& key = aParams.get_ContinueParams().key();
+ if (!key.IsUnset()) {
+ switch (mDirection) {
+ case IDBCursor::NEXT:
+ case IDBCursor::NEXT_UNIQUE:
+ if (NS_WARN_IF(key <= sortKey)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+
+ case IDBCursor::PREV:
+ case IDBCursor::PREV_UNIQUE:
+ if (NS_WARN_IF(key >= sortKey)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ }
+ break;
+ }
+
+ case CursorRequestParams::TContinuePrimaryKeyParams: {
+ const Key& key = aParams.get_ContinuePrimaryKeyParams().key();
+ const Key& primaryKey = aParams.get_ContinuePrimaryKeyParams().primaryKey();
+ MOZ_ASSERT(!key.IsUnset());
+ MOZ_ASSERT(!primaryKey.IsUnset());
+ switch (mDirection) {
+ case IDBCursor::NEXT:
+ if (NS_WARN_IF(key < sortKey ||
+ (key == sortKey && primaryKey <= mObjectKey))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+
+ case IDBCursor::PREV:
+ if (NS_WARN_IF(key > sortKey ||
+ (key == sortKey && primaryKey >= mObjectKey))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ break;
+ }
+
+ case CursorRequestParams::TAdvanceParams:
+ if (NS_WARN_IF(!aParams.get_AdvanceParams().count())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+bool
+Cursor::Start(const OpenCursorParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() == mType);
+ MOZ_ASSERT(!mActorDestroyed);
+
+ if (NS_WARN_IF(mCurrentlyRunningOp)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const OptionalKeyRange& optionalKeyRange =
+ mType == OpenCursorParams::TObjectStoreOpenCursorParams ?
+ aParams.get_ObjectStoreOpenCursorParams().optionalKeyRange() :
+ mType == OpenCursorParams::TObjectStoreOpenKeyCursorParams ?
+ aParams.get_ObjectStoreOpenKeyCursorParams().optionalKeyRange() :
+ mType == OpenCursorParams::TIndexOpenCursorParams ?
+ aParams.get_IndexOpenCursorParams().optionalKeyRange() :
+ aParams.get_IndexOpenKeyCursorParams().optionalKeyRange();
+
+ RefPtr<OpenOp> openOp = new OpenOp(this, optionalKeyRange);
+
+ if (NS_WARN_IF(!openOp->Init(mTransaction))) {
+ openOp->Cleanup();
+ return false;
+ }
+
+ openOp->DispatchToConnectionPool();
+ mCurrentlyRunningOp = openOp;
+
+ return true;
+}
+
+void
+Cursor::SendResponseInternal(
+ CursorResponse& aResponse,
+ const nsTArray<FallibleTArray<StructuredCloneFile>>& aFiles)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aResponse.type() != CursorResponse::T__None);
+ MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult,
+ NS_FAILED(aResponse.get_nsresult()));
+ MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult,
+ NS_ERROR_GET_MODULE(aResponse.get_nsresult()) ==
+ NS_ERROR_MODULE_DOM_INDEXEDDB);
+ MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tvoid_t, mKey.IsUnset());
+ MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tvoid_t,
+ mRangeKey.IsUnset());
+ MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tvoid_t,
+ mObjectKey.IsUnset());
+ MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult ||
+ aResponse.type() == CursorResponse::Tvoid_t ||
+ aResponse.type() ==
+ CursorResponse::TObjectStoreKeyCursorResponse ||
+ aResponse.type() == CursorResponse::TIndexKeyCursorResponse,
+ aFiles.IsEmpty());
+ MOZ_ASSERT(!mActorDestroyed);
+ MOZ_ASSERT(mCurrentlyRunningOp);
+
+ for (size_t i = 0; i < aFiles.Length(); ++i) {
+ const auto& files = aFiles[i];
+ if (!files.IsEmpty()) {
+ MOZ_ASSERT(aResponse.type() ==
+ CursorResponse::TArrayOfObjectStoreCursorResponse ||
+ aResponse.type() == CursorResponse::TIndexCursorResponse);
+ MOZ_ASSERT(mDatabase);
+ MOZ_ASSERT(mBackgroundParent);
+
+ FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
+ nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
+ mDatabase,
+ files,
+ /* aForPreprocess */ false,
+ serializedFiles);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResponse = ClampResultCode(rv);
+ break;
+ }
+
+ SerializedStructuredCloneReadInfo* serializedInfo = nullptr;
+ switch (aResponse.type()) {
+ case CursorResponse::TArrayOfObjectStoreCursorResponse: {
+ auto& responses = aResponse.get_ArrayOfObjectStoreCursorResponse();
+ MOZ_ASSERT(i < responses.Length());
+ serializedInfo = &responses[i].cloneInfo();
+ break;
+ }
+
+ case CursorResponse::TIndexCursorResponse:
+ MOZ_ASSERT(i == 0);
+ serializedInfo = &aResponse.get_IndexCursorResponse().cloneInfo();
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ MOZ_ASSERT(serializedInfo);
+ MOZ_ASSERT(serializedInfo->files().IsEmpty());
+
+ serializedInfo->files().SwapElements(serializedFiles);
+ }
+ }
+
+ // Work around the deleted function by casting to the base class.
+ auto* base = static_cast<PBackgroundIDBCursorParent*>(this);
+ if (!base->SendResponse(aResponse)) {
+ NS_WARNING("Failed to send response!");
+ }
+
+ mCurrentlyRunningOp = nullptr;
+}
+
+void
+Cursor::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+
+ if (mCurrentlyRunningOp) {
+ mCurrentlyRunningOp->NoteActorDestroyed();
+ }
+
+ mBackgroundParent = nullptr;
+
+ mObjectStoreMetadata = nullptr;
+ mIndexMetadata = nullptr;
+}
+
+bool
+Cursor::RecvDeleteMe()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ if (NS_WARN_IF(mCurrentlyRunningOp)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ return PBackgroundIDBCursorParent::Send__delete__(this);
+}
+
+bool
+Cursor::RecvContinue(const CursorRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
+ MOZ_ASSERT(!mActorDestroyed);
+ MOZ_ASSERT(mObjectStoreMetadata);
+ MOZ_ASSERT_IF(mType == OpenCursorParams::TIndexOpenCursorParams ||
+ mType == OpenCursorParams::TIndexOpenKeyCursorParams,
+ mIndexMetadata);
+
+ const bool trustParams =
+#ifdef DEBUG
+ // Always verify parameters in DEBUG builds!
+ false
+#else
+ mIsSameProcessActor
+#endif
+ ;
+
+ if (!trustParams && !VerifyRequestParams(aParams)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mCurrentlyRunningOp)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(mTransaction->mCommitOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<ContinueOp> continueOp = new ContinueOp(this, aParams);
+ if (NS_WARN_IF(!continueOp->Init(mTransaction))) {
+ continueOp->Cleanup();
+ return false;
+ }
+
+ continueOp->DispatchToConnectionPool();
+ mCurrentlyRunningOp = continueOp;
+
+ return true;
+}
+
+/*******************************************************************************
+ * FileManager
+ ******************************************************************************/
+
+FileManager::FileManager(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ const nsAString& aDatabaseName,
+ bool aEnforcingQuota)
+ : mPersistenceType(aPersistenceType)
+ , mGroup(aGroup)
+ , mOrigin(aOrigin)
+ , mDatabaseName(aDatabaseName)
+ , mLastFileId(0)
+ , mIsApp(aIsApp)
+ , mEnforcingQuota(aEnforcingQuota)
+ , mInvalidated(false)
+{ }
+
+FileManager::~FileManager()
+{ }
+
+nsresult
+FileManager::Init(nsIFile* aDirectory,
+ mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aConnection);
+
+ bool exists;
+ nsresult rv = aDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ bool isDirectory;
+ rv = aDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = aDirectory->GetPath(mDirectoryPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> journalDirectory;
+ rv = aDirectory->Clone(getter_AddRefs(journalDirectory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = journalDirectory->Append(NS_LITERAL_STRING(JOURNAL_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = journalDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ bool isDirectory;
+ rv = journalDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ rv = journalDirectory->GetPath(mJournalDirectoryPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, refcount "
+ "FROM file"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ int64_t id;
+ rv = stmt->GetInt64(0, &id);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t refcount;
+ rv = stmt->GetInt32(1, &refcount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(refcount > 0);
+
+ RefPtr<FileInfo> fileInfo = FileInfo::Create(this, id);
+ fileInfo->mDBRefCnt = static_cast<nsrefcnt>(refcount);
+
+ mFileInfos.Put(id, fileInfo);
+
+ mLastFileId = std::max(id, mLastFileId);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FileManager::Invalidate()
+{
+ if (IndexedDatabaseManager::IsClosed()) {
+ MOZ_ASSERT(false, "Shouldn't be called after shutdown!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+
+ MOZ_ASSERT(!mInvalidated);
+ mInvalidated = true;
+
+ for (auto iter = mFileInfos.Iter(); !iter.Done(); iter.Next()) {
+ FileInfo* info = iter.Data();
+ MOZ_ASSERT(info);
+
+ if (!info->LockedClearDBRefs()) {
+ iter.Remove();
+ }
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+FileManager::GetDirectory()
+{
+ return GetFileForPath(mDirectoryPath);
+}
+
+already_AddRefed<nsIFile>
+FileManager::GetCheckedDirectory()
+{
+ nsCOMPtr<nsIFile> directory = GetDirectory();
+ if (NS_WARN_IF(!directory)) {
+ return nullptr;
+ }
+
+ DebugOnly<bool> exists;
+ MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)));
+ MOZ_ASSERT(exists);
+
+ DebugOnly<bool> isDirectory;
+ MOZ_ASSERT(NS_SUCCEEDED(directory->IsDirectory(&isDirectory)));
+ MOZ_ASSERT(isDirectory);
+
+ return directory.forget();
+}
+
+already_AddRefed<nsIFile>
+FileManager::GetJournalDirectory()
+{
+ return GetFileForPath(mJournalDirectoryPath);
+}
+
+already_AddRefed<nsIFile>
+FileManager::EnsureJournalDirectory()
+{
+ // This can happen on the IO or on a transaction thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> journalDirectory = GetFileForPath(mJournalDirectoryPath);
+ if (NS_WARN_IF(!journalDirectory)) {
+ return nullptr;
+ }
+
+ bool exists;
+ nsresult rv = journalDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ if (exists) {
+ bool isDirectory;
+ rv = journalDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ return nullptr;
+ }
+ } else {
+ rv = journalDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ }
+
+ return journalDirectory.forget();
+}
+
+already_AddRefed<FileInfo>
+FileManager::GetFileInfo(int64_t aId)
+{
+ if (IndexedDatabaseManager::IsClosed()) {
+ MOZ_ASSERT(false, "Shouldn't be called after shutdown!");
+ return nullptr;
+ }
+
+ FileInfo* fileInfo;
+ {
+ MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+ fileInfo = mFileInfos.Get(aId);
+ }
+
+ RefPtr<FileInfo> result = fileInfo;
+ return result.forget();
+}
+
+already_AddRefed<FileInfo>
+FileManager::GetNewFileInfo()
+{
+ MOZ_ASSERT(!IndexedDatabaseManager::IsClosed());
+
+ FileInfo* fileInfo;
+ {
+ MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+
+ int64_t id = mLastFileId + 1;
+
+ fileInfo = FileInfo::Create(this, id);
+
+ mFileInfos.Put(id, fileInfo);
+
+ mLastFileId = id;
+ }
+
+ RefPtr<FileInfo> result = fileInfo;
+ return result.forget();
+}
+
+// static
+already_AddRefed<nsIFile>
+FileManager::GetFileForId(nsIFile* aDirectory, int64_t aId)
+{
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aId > 0);
+
+ nsAutoString id;
+ id.AppendInt(aId);
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aDirectory->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ rv = file->Append(id);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ return file.forget();
+}
+
+// static
+already_AddRefed<nsIFile>
+FileManager::GetCheckedFileForId(nsIFile* aDirectory, int64_t aId)
+{
+ nsCOMPtr<nsIFile> file = GetFileForId(aDirectory, aId);
+ if (NS_WARN_IF(!file)) {
+ return nullptr;
+ }
+
+ DebugOnly<bool> exists;
+ MOZ_ASSERT(NS_SUCCEEDED(file->Exists(&exists)));
+ MOZ_ASSERT(exists);
+
+ DebugOnly<bool> isFile;
+ MOZ_ASSERT(NS_SUCCEEDED(file->IsFile(&isFile)));
+ MOZ_ASSERT(isFile);
+
+ return file.forget();
+}
+
+// static
+nsresult
+FileManager::InitDirectory(nsIFile* aDirectory,
+ nsIFile* aDatabaseFile,
+ PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ uint32_t aTelemetryId)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aDatabaseFile);
+
+ bool exists;
+ nsresult rv = aDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ return NS_OK;
+ }
+
+ bool isDirectory;
+ rv = aDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> journalDirectory;
+ rv = aDirectory->Clone(getter_AddRefs(journalDirectory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = journalDirectory->Append(NS_LITERAL_STRING(JOURNAL_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = journalDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ rv = journalDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = journalDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasElements;
+ rv = entries->HasMoreElements(&hasElements);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasElements) {
+ nsCOMPtr<mozIStorageConnection> connection;
+ rv = CreateStorageConnection(aDatabaseFile,
+ aDirectory,
+ NullString(),
+ aPersistenceType,
+ aGroup,
+ aOrigin,
+ aTelemetryId,
+ getter_AddRefs(connection));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mozStorageTransaction transaction(connection, false);
+
+ rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE VIRTUAL TABLE fs USING filesystem;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT name, (name IN (SELECT id FROM file)) "
+ "FROM fs "
+ "WHERE path = :path"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString path;
+ rv = journalDirectory->GetPath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("path"), path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ nsString name;
+ rv = stmt->GetString(0, name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t flag = stmt->AsInt32(1);
+
+ if (!flag) {
+ nsCOMPtr<nsIFile> file;
+ rv = aDirectory->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->Append(name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_FAILED(file->Remove(false))) {
+ NS_WARNING("Failed to remove orphaned file!");
+ }
+ }
+
+ nsCOMPtr<nsIFile> journalFile;
+ rv = journalDirectory->Clone(getter_AddRefs(journalFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = journalFile->Append(name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_FAILED(journalFile->Remove(false))) {
+ NS_WARNING("Failed to remove journal file!");
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE fs;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = transaction.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+FileManager::GetUsage(nsIFile* aDirectory, uint64_t* aUsage)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aUsage);
+
+ bool exists;
+ nsresult rv = aDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ *aUsage = 0;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint64_t usage = 0;
+
+ bool hasMore;
+ while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
+ nsCOMPtr<nsISupports> entry;
+ rv = entries->GetNext(getter_AddRefs(entry));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ MOZ_ASSERT(file);
+
+ nsString leafName;
+ rv = file->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (leafName.EqualsLiteral(JOURNAL_DIRECTORY_NAME)) {
+ continue;
+ }
+
+ int64_t fileSize;
+ rv = file->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ UsageInfo::IncrementUsage(&usage, uint64_t(fileSize));
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aUsage = usage;
+ return NS_OK;
+}
+
+/*******************************************************************************
+ * FileImplStoredFile
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS_INHERITED(BlobImplStoredFile,
+ BlobImplFile,
+ BlobImplStoredFile)
+
+/*******************************************************************************
+ * QuotaClient
+ ******************************************************************************/
+
+QuotaClient* QuotaClient::sInstance = nullptr;
+
+QuotaClient::QuotaClient()
+ : mShutdownRequested(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
+ MOZ_ASSERT(!gTelemetryIdMutex);
+
+ // Always create this so that later access to gTelemetryIdHashtable can be
+ // properly synchronized.
+ gTelemetryIdMutex = new Mutex("IndexedDB gTelemetryIdMutex");
+
+ sInstance = this;
+}
+
+QuotaClient::~QuotaClient()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
+ MOZ_ASSERT(gTelemetryIdMutex);
+ MOZ_ASSERT(!mMaintenanceThreadPool);
+
+ // No one else should be able to touch gTelemetryIdHashtable now that the
+ // QuotaClient has gone away.
+ gTelemetryIdHashtable = nullptr;
+ gTelemetryIdMutex = nullptr;
+
+ sInstance = nullptr;
+}
+
+nsThreadPool*
+QuotaClient::GetOrCreateThreadPool()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mShutdownRequested);
+
+ if (!mMaintenanceThreadPool) {
+ RefPtr<nsThreadPool> threadPool = new nsThreadPool();
+
+ // PR_GetNumberOfProcessors() can return -1 on error, so make sure we
+ // don't set some huge number here. We add 2 in case some threads block on
+ // the disk I/O.
+ const uint32_t threadCount =
+ std::max(int32_t(PR_GetNumberOfProcessors()), int32_t(1)) +
+ 2;
+
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(threadCount));
+
+ // Don't keep more than one idle thread.
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadLimit(1));
+
+ // Don't keep idle threads alive very long.
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadTimeout(5 * PR_MSEC_PER_SEC));
+
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetName(NS_LITERAL_CSTRING("IndexedDB Mnt")));
+
+ mMaintenanceThreadPool = Move(threadPool);
+ }
+
+ return mMaintenanceThreadPool;
+}
+
+mozilla::dom::quota::Client::Type
+QuotaClient::GetType()
+{
+ return QuotaClient::IDB;
+}
+
+struct FileManagerInitInfo
+{
+ nsCOMPtr<nsIFile> mDirectory;
+ nsCOMPtr<nsIFile> mDatabaseFile;
+ nsCOMPtr<nsIFile> mDatabaseWALFile;
+};
+
+nsresult
+QuotaClient::InitOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ const AtomicBool& aCanceled,
+ UsageInfo* aUsageInfo)
+{
+ AssertIsOnIOThread();
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv =
+ GetDirectory(aPersistenceType, aOrigin, getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We need to see if there are any files in the directory already. If they
+ // are database files then we need to cleanup stored files (if it's needed)
+ // and also get the usage.
+
+ AutoTArray<nsString, 20> subdirsToProcess;
+ nsTArray<nsCOMPtr<nsIFile>> unknownFiles;
+ nsTHashtable<nsStringHashKey> validSubdirs(20);
+ AutoTArray<FileManagerInitInfo, 20> initInfos;
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ const NS_ConvertASCIItoUTF16 filesSuffix(
+ kFileManagerDirectoryNameSuffix,
+ LiteralStringLength(kFileManagerDirectoryNameSuffix));
+
+ const NS_ConvertASCIItoUTF16 journalSuffix(
+ kSQLiteJournalSuffix,
+ LiteralStringLength(kSQLiteJournalSuffix));
+ const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
+ LiteralStringLength(kSQLiteSHMSuffix));
+ const NS_ConvertASCIItoUTF16 walSuffix(kSQLiteWALSuffix,
+ LiteralStringLength(kSQLiteWALSuffix));
+
+ bool hasMore;
+ while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
+ hasMore &&
+ !aCanceled) {
+ nsCOMPtr<nsISupports> entry;
+ rv = entries->GetNext(getter_AddRefs(entry));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ MOZ_ASSERT(file);
+
+ nsString leafName;
+ rv = file->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isDirectory;
+ rv = file->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (isDirectory) {
+ if (!StringEndsWith(leafName, filesSuffix) ||
+ !validSubdirs.GetEntry(leafName)) {
+ subdirsToProcess.AppendElement(leafName);
+ }
+ continue;
+ }
+
+ // Skip Desktop Service Store (.DS_Store) files. These files are only used
+ // on Mac OS X, but the profile can be shared across different operating
+ // systems, so we check it on all platforms.
+ if (leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ continue;
+ }
+
+ // Skip SQLite temporary files. These files take up space on disk but will
+ // be deleted as soon as the database is opened, so we don't count them
+ // towards quota.
+ if (StringEndsWith(leafName, journalSuffix) ||
+ StringEndsWith(leafName, shmSuffix)) {
+ continue;
+ }
+
+ // The SQLite WAL file does count towards quota, but it is handled below
+ // once we find the actual database file.
+ if (StringEndsWith(leafName, walSuffix)) {
+ continue;
+ }
+
+ nsDependentSubstring dbBaseFilename;
+ if (!GetDatabaseBaseFilename(leafName, dbBaseFilename)) {
+ unknownFiles.AppendElement(file);
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> fmDirectory;
+ rv = directory->Clone(getter_AddRefs(fmDirectory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString fmDirectoryBaseName = dbBaseFilename + filesSuffix;
+
+ rv = fmDirectory->Append(fmDirectoryBaseName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> walFile;
+ if (aUsageInfo) {
+ rv = directory->Clone(getter_AddRefs(walFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = walFile->Append(dbBaseFilename + walSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ FileManagerInitInfo* initInfo = initInfos.AppendElement();
+ initInfo->mDirectory.swap(fmDirectory);
+ initInfo->mDatabaseFile.swap(file);
+ initInfo->mDatabaseWALFile.swap(walFile);
+
+ validSubdirs.PutEntry(fmDirectoryBaseName);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (uint32_t count = subdirsToProcess.Length(), i = 0; i < count; i++) {
+ const nsString& subdirName = subdirsToProcess[i];
+
+ // If the directory has the correct suffix then it must exist in
+ // validSubdirs.
+ if (StringEndsWith(subdirName, filesSuffix)) {
+ if (NS_WARN_IF(!validSubdirs.GetEntry(subdirName))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ continue;
+ }
+
+ // The directory didn't have the right suffix but we might need to rename
+ // it. Check to see if we have a database that references this directory.
+ nsString subdirNameWithSuffix = subdirName + filesSuffix;
+ if (!validSubdirs.GetEntry(subdirNameWithSuffix)) {
+ // Windows doesn't allow a directory to end with a dot ('.'), so we have
+ // to check that possibility here too.
+ // We do this on all platforms, because the origin directory may have
+ // been created on Windows and now accessed on different OS.
+ subdirNameWithSuffix = subdirName + NS_LITERAL_STRING(".") + filesSuffix;
+ if (NS_WARN_IF(!validSubdirs.GetEntry(subdirNameWithSuffix))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ // We do have a database that uses this directory so we should rename it
+ // now. However, first check to make sure that we're not overwriting
+ // something else.
+ nsCOMPtr<nsIFile> subdir;
+ rv = directory->Clone(getter_AddRefs(subdir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = subdir->Append(subdirNameWithSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = subdir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ rv = directory->Clone(getter_AddRefs(subdir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = subdir->Append(subdirName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ DebugOnly<bool> isDirectory;
+ MOZ_ASSERT(NS_SUCCEEDED(subdir->IsDirectory(&isDirectory)));
+ MOZ_ASSERT(isDirectory);
+
+ rv = subdir->RenameTo(nullptr, subdirNameWithSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ for (uint32_t count = initInfos.Length(), i = 0;
+ i < count && !aCanceled;
+ i++) {
+ FileManagerInitInfo& initInfo = initInfos[i];
+ MOZ_ASSERT(initInfo.mDirectory);
+ MOZ_ASSERT(initInfo.mDatabaseFile);
+ MOZ_ASSERT_IF(aUsageInfo, initInfo.mDatabaseWALFile);
+
+ rv = FileManager::InitDirectory(initInfo.mDirectory,
+ initInfo.mDatabaseFile,
+ aPersistenceType,
+ aGroup,
+ aOrigin,
+ TelemetryIdForFile(initInfo.mDatabaseFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aUsageInfo) {
+ int64_t fileSize;
+ rv = initInfo.mDatabaseFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(fileSize >= 0);
+
+ aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
+
+ rv = initInfo.mDatabaseWALFile->GetFileSize(&fileSize);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(fileSize >= 0);
+ aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
+ } else if (NS_WARN_IF(rv != NS_ERROR_FILE_NOT_FOUND &&
+ rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)) {
+ return rv;
+ }
+
+ uint64_t usage;
+ rv = FileManager::GetUsage(initInfo.mDirectory, &usage);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aUsageInfo->AppendToFileUsage(usage);
+ }
+ }
+
+ // We have to do this after file manager initialization.
+ if (!unknownFiles.IsEmpty()) {
+#ifdef DEBUG
+ for (uint32_t count = unknownFiles.Length(), i = 0; i < count; i++) {
+ nsCOMPtr<nsIFile>& unknownFile = unknownFiles[i];
+
+ nsString leafName;
+ MOZ_ALWAYS_SUCCEEDS(unknownFile->GetLeafName(leafName));
+
+ MOZ_ASSERT(!StringEndsWith(leafName, journalSuffix));
+ MOZ_ASSERT(!StringEndsWith(leafName, shmSuffix));
+ MOZ_ASSERT(!StringEndsWith(leafName, walSuffix));
+
+ nsString path;
+ MOZ_ALWAYS_SUCCEEDS(unknownFile->GetPath(path));
+ MOZ_ASSERT(!path.IsEmpty());
+
+ nsPrintfCString warning("Refusing to open databases for \"%s\" because "
+ "an unexpected file exists in the storage "
+ "area: \"%s\"",
+ PromiseFlatCString(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(path).get());
+ NS_WARNING(warning.get());
+ }
+#endif
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ const AtomicBool& aCanceled,
+ UsageInfo* aUsageInfo)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aUsageInfo);
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv =
+ GetDirectory(aPersistenceType, aOrigin, getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = GetUsageForDirectoryInternal(directory, aCanceled, aUsageInfo, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
+ const nsACString& aOrigin)
+{
+ AssertIsOnIOThread();
+
+ if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
+ mgr->InvalidateFileManagers(aPersistenceType, aOrigin);
+ }
+}
+
+void
+QuotaClient::ReleaseIOThreadObjects()
+{
+ AssertIsOnIOThread();
+
+ if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
+ mgr->InvalidateAllFileManagers();
+ }
+}
+
+void
+QuotaClient::AbortOperations(const nsACString& aOrigin)
+{
+ AssertIsOnBackgroundThread();
+
+ if (!gLiveDatabaseHashtable) {
+ return;
+ }
+
+ nsTArray<RefPtr<Database>> databases;
+
+ for (auto iter = gLiveDatabaseHashtable->ConstIter();
+ !iter.Done(); iter.Next()) {
+ for (Database* database : iter.Data()->mLiveDatabases) {
+ if (aOrigin.IsVoid() || database->Origin() == aOrigin) {
+ databases.AppendElement(database);
+ }
+ }
+ }
+
+ for (Database* database : databases) {
+ database->Invalidate();
+ }
+
+ databases.Clear();
+}
+
+void
+QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId)
+{
+ AssertIsOnBackgroundThread();
+
+ if (!gLiveDatabaseHashtable) {
+ return;
+ }
+
+ nsTArray<RefPtr<Database>> databases;
+
+ for (auto iter = gLiveDatabaseHashtable->ConstIter();
+ !iter.Done(); iter.Next()) {
+ for (Database* database : iter.Data()->mLiveDatabases) {
+ if (database->IsOwnedByProcess(aContentParentId)) {
+ databases.AppendElement(database);
+ }
+ }
+ }
+
+ for (Database* database : databases) {
+ database->Invalidate();
+ }
+
+ databases.Clear();
+}
+
+void
+QuotaClient::StartIdleMaintenance()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mShutdownRequested);
+
+ mBackgroundThread = do_GetCurrentThread();
+
+ RefPtr<Maintenance> maintenance = new Maintenance(this);
+
+ mMaintenanceQueue.AppendElement(maintenance.forget());
+ ProcessMaintenanceQueue();
+}
+
+void
+QuotaClient::StopIdleMaintenance()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mShutdownRequested);
+
+ if (mCurrentMaintenance) {
+ mCurrentMaintenance->Abort();
+ }
+
+ for (RefPtr<Maintenance>& maintenance : mMaintenanceQueue) {
+ maintenance->Abort();
+ }
+}
+
+void
+QuotaClient::ShutdownWorkThreads()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mShutdownRequested);
+
+ mShutdownRequested = true;
+
+ if (mMaintenanceThreadPool) {
+ mMaintenanceThreadPool->Shutdown();
+ mMaintenanceThreadPool = nullptr;
+ }
+
+ RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
+ if (connectionPool) {
+ connectionPool->Shutdown();
+
+ gConnectionPool = nullptr;
+ }
+
+ RefPtr<FileHandleThreadPool> fileHandleThreadPool =
+ gFileHandleThreadPool.get();
+ if (fileHandleThreadPool) {
+ fileHandleThreadPool->Shutdown();
+
+ gFileHandleThreadPool = nullptr;
+ }
+}
+
+void
+QuotaClient::DidInitialize(QuotaManager* aQuotaManager)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
+ mgr->NoteLiveQuotaManager(aQuotaManager);
+ }
+}
+
+void
+QuotaClient::WillShutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
+ mgr->NoteShuttingDownQuotaManager();
+ }
+}
+
+nsresult
+QuotaClient::GetDirectory(PersistenceType aPersistenceType,
+ const nsACString& aOrigin, nsIFile** aDirectory)
+{
+ QuotaManager* quotaManager = QuotaManager::Get();
+ NS_ASSERTION(quotaManager, "This should never fail!");
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin,
+ getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(directory);
+
+ rv = directory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ directory.forget(aDirectory);
+ return NS_OK;
+}
+
+nsresult
+QuotaClient::GetUsageForDirectoryInternal(nsIFile* aDirectory,
+ const AtomicBool& aCanceled,
+ UsageInfo* aUsageInfo,
+ bool aDatabaseFiles)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aUsageInfo);
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!entries) {
+ return NS_OK;
+ }
+
+ const NS_ConvertASCIItoUTF16 journalSuffix(
+ kSQLiteJournalSuffix,
+ LiteralStringLength(kSQLiteJournalSuffix));
+ const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
+ LiteralStringLength(kSQLiteSHMSuffix));
+
+ bool hasMore;
+ while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
+ hasMore &&
+ !aCanceled) {
+ nsCOMPtr<nsISupports> entry;
+ rv = entries->GetNext(getter_AddRefs(entry));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ MOZ_ASSERT(file);
+
+ nsString leafName;
+ rv = file->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Journal files and sqlite-shm files don't count towards usage.
+ if (StringEndsWith(leafName, journalSuffix) ||
+ StringEndsWith(leafName, shmSuffix)) {
+ continue;
+ }
+
+ bool isDirectory;
+ rv = file->IsDirectory(&isDirectory);
+ if (rv == NS_ERROR_FILE_NOT_FOUND ||
+ rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ continue;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (isDirectory) {
+ if (aDatabaseFiles) {
+ rv = GetUsageForDirectoryInternal(file, aCanceled, aUsageInfo, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ nsString leafName;
+ rv = file->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!leafName.EqualsLiteral(JOURNAL_DIRECTORY_NAME)) {
+ NS_WARNING("Unknown directory found!");
+ }
+ }
+
+ continue;
+ }
+
+ int64_t fileSize;
+ rv = file->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(fileSize >= 0);
+
+ if (aDatabaseFiles) {
+ aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
+ } else {
+ aUsageInfo->AppendToFileUsage(uint64_t(fileSize));
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+QuotaClient::ProcessMaintenanceQueue()
+{
+ AssertIsOnBackgroundThread();
+
+ if (mCurrentMaintenance || mMaintenanceQueue.IsEmpty()) {
+ return;
+ }
+
+ mCurrentMaintenance = mMaintenanceQueue[0];
+ mMaintenanceQueue.RemoveElementAt(0);
+
+ mCurrentMaintenance->RunImmediately();
+}
+
+void
+Maintenance::RegisterDatabaseMaintenance(
+ DatabaseMaintenance* aDatabaseMaintenance)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aDatabaseMaintenance);
+ MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
+ MOZ_ASSERT(!mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
+
+ mDatabaseMaintenances.Put(aDatabaseMaintenance->DatabasePath(),
+ aDatabaseMaintenance);
+}
+
+void
+Maintenance::UnregisterDatabaseMaintenance(
+ DatabaseMaintenance* aDatabaseMaintenance)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aDatabaseMaintenance);
+ MOZ_ASSERT(mState == State::WaitingForDatabaseMaintenancesToComplete);
+ MOZ_ASSERT(mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
+
+ mDatabaseMaintenances.Remove(aDatabaseMaintenance->DatabasePath());
+
+ if (mDatabaseMaintenances.Count()) {
+ return;
+ }
+
+ mState = State::Finishing;
+ Finish();
+}
+
+nsresult
+Maintenance::Start()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::Initial);
+
+ if (IsAborted()) {
+ return NS_ERROR_ABORT;
+ }
+
+ // Make sure that the IndexedDatabaseManager is running so that we can check
+ // for low disk space mode.
+
+ if (IndexedDatabaseManager::Get()) {
+ OpenDirectory();
+ return NS_OK;
+ }
+
+ mState = State::CreateIndexedDatabaseManager;
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ return NS_OK;
+}
+
+nsresult
+Maintenance::CreateIndexedDatabaseManager()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mState == State::CreateIndexedDatabaseManager);
+
+ if (IsAborted()) {
+ return NS_ERROR_ABORT;
+ }
+
+ IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
+ if (NS_WARN_IF(!mgr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mState = State::IndexedDatabaseManagerOpen;
+ MOZ_ALWAYS_SUCCEEDS(
+ mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+}
+
+nsresult
+Maintenance::OpenDirectory()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::Initial ||
+ mState == State::IndexedDatabaseManagerOpen);
+ MOZ_ASSERT(!mDirectoryLock);
+ MOZ_ASSERT(QuotaManager::Get());
+
+ if (IsAborted()) {
+ return NS_ERROR_ABORT;
+ }
+
+ // Get a shared lock for <profile>/storage/*/*/idb
+
+ mState = State::DirectoryOpenPending;
+ QuotaManager::Get()->OpenDirectoryInternal(
+ Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ Nullable<Client::Type>(Client::IDB),
+ /* aExclusive */ false,
+ this);
+
+ return NS_OK;
+}
+
+nsresult
+Maintenance::DirectoryOpen()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::DirectoryOpenPending);
+ MOZ_ASSERT(mDirectoryLock);
+
+ if (IsAborted()) {
+ return NS_ERROR_ABORT;
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ mState = State::DirectoryWorkOpen;
+
+ nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Maintenance::DirectoryWork()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mState == State::DirectoryWorkOpen);
+
+ // The storage directory is structured like this:
+ //
+ // <profile>/storage/<persistence>/<origin>/idb/*.sqlite
+ //
+ // We have to find all database files that match any persistence type and any
+ // origin. We ignore anything out of the ordinary for now.
+
+ if (IsAborted()) {
+ return NS_ERROR_ABORT;
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ nsresult rv = quotaManager->EnsureStorageIsInitialized();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> storageDir = GetFileForPath(quotaManager->GetStoragePath());
+ if (NS_WARN_IF(!storageDir)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool exists;
+ rv = storageDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ bool isDirectory;
+ rv = storageDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // There are currently only 3 persistence types, and we want to iterate them
+ // in this order:
+ static const PersistenceType kPersistenceTypes[] = {
+ PERSISTENCE_TYPE_PERSISTENT,
+ PERSISTENCE_TYPE_DEFAULT,
+ PERSISTENCE_TYPE_TEMPORARY
+ };
+
+ static_assert((sizeof(kPersistenceTypes) / sizeof(kPersistenceTypes[0])) ==
+ size_t(PERSISTENCE_TYPE_INVALID),
+ "Something changed with available persistence types!");
+
+ NS_NAMED_LITERAL_STRING(idbDirName, IDB_DIRECTORY_NAME);
+ NS_NAMED_LITERAL_STRING(sqliteExtension, ".sqlite");
+
+ for (const PersistenceType persistenceType : kPersistenceTypes) {
+ // Loop over "<persistence>" directories.
+ if (IsAborted()) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsAutoCString persistenceTypeString;
+ if (persistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+ // XXX This shouldn't be a special case...
+ persistenceTypeString.AssignLiteral("permanent");
+ } else {
+ PersistenceTypeToText(persistenceType, persistenceTypeString);
+ }
+
+ nsCOMPtr<nsIFile> persistenceDir;
+ rv = storageDir->Clone(getter_AddRefs(persistenceDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = persistenceDir->Append(NS_ConvertASCIItoUTF16(persistenceTypeString));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = persistenceDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ continue;
+ }
+
+ rv = persistenceDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ continue;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> persistenceDirEntries;
+ rv = persistenceDir->GetDirectoryEntries(
+ getter_AddRefs(persistenceDirEntries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!persistenceDirEntries) {
+ continue;
+ }
+
+ while (true) {
+ // Loop over "<origin>/idb" directories.
+ if (IsAborted()) {
+ return NS_ERROR_ABORT;
+ }
+
+ bool persistenceDirHasMoreEntries;
+ rv = persistenceDirEntries->HasMoreElements(
+ &persistenceDirHasMoreEntries);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!persistenceDirHasMoreEntries) {
+ break;
+ }
+
+ nsCOMPtr<nsISupports> persistenceDirEntry;
+ rv = persistenceDirEntries->GetNext(getter_AddRefs(persistenceDirEntry));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> originDir = do_QueryInterface(persistenceDirEntry);
+ MOZ_ASSERT(originDir);
+
+ rv = originDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(exists);
+
+ rv = originDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isDirectory) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> idbDir;
+ rv = originDir->Clone(getter_AddRefs(idbDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = idbDir->Append(idbDirName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = idbDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ continue;
+ }
+
+ rv = idbDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ continue;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> idbDirEntries;
+ rv = idbDir->GetDirectoryEntries(getter_AddRefs(idbDirEntries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!idbDirEntries) {
+ continue;
+ }
+
+ nsCString group;
+ nsCString origin;
+ nsTArray<nsString> databasePaths;
+
+ while (true) {
+ // Loop over files in the "idb" directory.
+ if (IsAborted()) {
+ return NS_ERROR_ABORT;
+ }
+
+ bool idbDirHasMoreEntries;
+ rv = idbDirEntries->HasMoreElements(&idbDirHasMoreEntries);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!idbDirHasMoreEntries) {
+ break;
+ }
+
+ nsCOMPtr<nsISupports> idbDirEntry;
+ rv = idbDirEntries->GetNext(getter_AddRefs(idbDirEntry));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> idbDirFile = do_QueryInterface(idbDirEntry);
+ MOZ_ASSERT(idbDirFile);
+
+ nsString idbFilePath;
+ rv = idbDirFile->GetPath(idbFilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!StringEndsWith(idbFilePath, sqliteExtension)) {
+ continue;
+ }
+
+ rv = idbDirFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(exists);
+
+ rv = idbDirFile->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (isDirectory) {
+ continue;
+ }
+
+ // Found a database.
+ if (databasePaths.IsEmpty()) {
+ MOZ_ASSERT(group.IsEmpty());
+ MOZ_ASSERT(origin.IsEmpty());
+
+ int64_t dummyTimeStamp;
+ nsCString dummySuffix;
+ bool dummyIsApp;
+ if (NS_WARN_IF(NS_FAILED(
+ quotaManager->GetDirectoryMetadata2(originDir,
+ &dummyTimeStamp,
+ dummySuffix,
+ group,
+ origin,
+ &dummyIsApp)))) {
+ // Not much we can do here...
+ continue;
+ }
+ }
+
+ MOZ_ASSERT(!databasePaths.Contains(idbFilePath));
+
+ databasePaths.AppendElement(idbFilePath);
+ }
+
+ if (!databasePaths.IsEmpty()) {
+ mDirectoryInfos.AppendElement(DirectoryInfo(persistenceType,
+ group,
+ origin,
+ Move(databasePaths)));
+ }
+ }
+ }
+
+ mState = State::BeginDatabaseMaintenance;
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+}
+
+nsresult
+Maintenance::BeginDatabaseMaintenance()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
+
+ class MOZ_STACK_CLASS Helper final
+ {
+ public:
+ static bool
+ IsSafeToRunMaintenance(const nsAString& aDatabasePath)
+ {
+ if (gFactoryOps) {
+ for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
+ RefPtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
+
+ MOZ_ASSERT(!existingOp->DatabaseFilePath().IsEmpty());
+
+ if (existingOp->DatabaseFilePath() == aDatabasePath) {
+ return false;
+ }
+ }
+ }
+
+ if (gLiveDatabaseHashtable) {
+ for (auto iter = gLiveDatabaseHashtable->ConstIter();
+ !iter.Done(); iter.Next()) {
+ for (Database* database : iter.Data()->mLiveDatabases) {
+ if (database->FilePath() == aDatabasePath) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ };
+
+ RefPtr<nsThreadPool> threadPool;
+
+ for (DirectoryInfo& directoryInfo : mDirectoryInfos) {
+ for (const nsString& databasePath : directoryInfo.mDatabasePaths) {
+ if (Helper::IsSafeToRunMaintenance(databasePath)) {
+ RefPtr<DatabaseMaintenance> databaseMaintenance =
+ new DatabaseMaintenance(this,
+ directoryInfo.mPersistenceType,
+ directoryInfo.mGroup,
+ directoryInfo.mOrigin,
+ databasePath);
+
+ if (!threadPool) {
+ threadPool = mQuotaClient->GetOrCreateThreadPool();
+ MOZ_ASSERT(threadPool);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ threadPool->Dispatch(databaseMaintenance, NS_DISPATCH_NORMAL));
+
+ RegisterDatabaseMaintenance(databaseMaintenance);
+ }
+ }
+ }
+
+ mDirectoryInfos.Clear();
+
+ if (mDatabaseMaintenances.Count()) {
+ mState = State::WaitingForDatabaseMaintenancesToComplete;
+ } else {
+ mState = State::Finishing;
+ Finish();
+ }
+
+ return NS_OK;
+}
+
+void
+Maintenance::Finish()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::Finishing);
+
+ if (NS_FAILED(mResultCode)) {
+ nsCString errorName;
+ GetErrorName(mResultCode, errorName);
+
+ IDB_WARNING("Maintenance finished with error: %s", errorName.get());
+ }
+
+ mDirectoryLock = nullptr;
+
+ // It can happen that we are only referenced by mCurrentMaintenance which is
+ // cleared in NoteFinishedMaintenance()
+ RefPtr<Maintenance> kungFuDeathGrip = this;
+
+ mQuotaClient->NoteFinishedMaintenance(this);
+
+ mState = State::Complete;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(Maintenance, Runnable)
+
+NS_IMETHODIMP
+Maintenance::Run()
+{
+ MOZ_ASSERT(mState != State::Complete);
+
+ nsresult rv;
+
+ switch (mState) {
+ case State::Initial:
+ rv = Start();
+ break;
+
+ case State::CreateIndexedDatabaseManager:
+ rv = CreateIndexedDatabaseManager();
+ break;
+
+ case State::IndexedDatabaseManagerOpen:
+ rv = OpenDirectory();
+ break;
+
+ case State::DirectoryWorkOpen:
+ rv = DirectoryWork();
+ break;
+
+ case State::BeginDatabaseMaintenance:
+ rv = BeginDatabaseMaintenance();
+ break;
+
+ case State::Finishing:
+ Finish();
+ return NS_OK;
+
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ // Must set mState before dispatching otherwise we will race with the owning
+ // thread.
+ mState = State::Finishing;
+
+ if (IsOnBackgroundThread()) {
+ Finish();
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(
+ mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+Maintenance::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::DirectoryOpenPending);
+ MOZ_ASSERT(!mDirectoryLock);
+
+ mDirectoryLock = aLock;
+
+ nsresult rv = DirectoryOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ mState = State::Finishing;
+ Finish();
+
+ return;
+ }
+}
+
+void
+Maintenance::DirectoryLockFailed()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == State::DirectoryOpenPending);
+ MOZ_ASSERT(!mDirectoryLock);
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = NS_ERROR_FAILURE;
+ }
+
+ mState = State::Finishing;
+ Finish();
+}
+
+void
+DatabaseMaintenance::PerformMaintenanceOnDatabase()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(mMaintenance);
+ MOZ_ASSERT(mMaintenance->StartTime());
+ MOZ_ASSERT(!mDatabasePath.IsEmpty());
+ MOZ_ASSERT(!mGroup.IsEmpty());
+ MOZ_ASSERT(!mOrigin.IsEmpty());
+
+ class MOZ_STACK_CLASS AutoClose final
+ {
+ nsCOMPtr<mozIStorageConnection> mConnection;
+
+ public:
+ explicit AutoClose(mozIStorageConnection* aConnection)
+ : mConnection(aConnection)
+ {
+ MOZ_ASSERT(aConnection);
+ }
+
+ ~AutoClose()
+ {
+ MOZ_ASSERT(mConnection);
+
+ MOZ_ALWAYS_SUCCEEDS(mConnection->Close());
+ }
+ };
+
+ nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath);
+ MOZ_ASSERT(databaseFile);
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ nsresult rv = GetStorageConnection(databaseFile,
+ mPersistenceType,
+ mGroup,
+ mOrigin,
+ TelemetryIdForFile(databaseFile),
+ getter_AddRefs(connection));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ AutoClose autoClose(connection);
+
+ if (mMaintenance->IsAborted()) {
+ return;
+ }
+
+ AutoProgressHandler progressHandler(mMaintenance);
+ if (NS_WARN_IF(NS_FAILED(progressHandler.Register(connection)))) {
+ return;
+ }
+
+ bool databaseIsOk;
+ rv = CheckIntegrity(connection, &databaseIsOk);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (NS_WARN_IF(!databaseIsOk)) {
+ // XXX Handle this somehow! Probably need to clear all storage for the
+ // origin. Needs followup.
+ MOZ_ASSERT(false, "Database corruption detected!");
+ return;
+ }
+
+ if (mMaintenance->IsAborted()) {
+ return;
+ }
+
+ MaintenanceAction maintenanceAction;
+ rv = DetermineMaintenanceAction(connection, databaseFile, &maintenanceAction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (mMaintenance->IsAborted()) {
+ return;
+ }
+
+ switch (maintenanceAction) {
+ case MaintenanceAction::Nothing:
+ break;
+
+ case MaintenanceAction::IncrementalVacuum:
+ IncrementalVacuum(connection);
+ break;
+
+ case MaintenanceAction::FullVacuum:
+ FullVacuum(connection, databaseFile);
+ break;
+
+ default:
+ MOZ_CRASH("Unknown MaintenanceAction!");
+ }
+}
+
+nsresult
+DatabaseMaintenance::CheckIntegrity(mozIStorageConnection* aConnection,
+ bool* aOk)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(aOk);
+
+ nsresult rv;
+
+ // First do a full integrity_check. Scope statements tightly here because
+ // later operations require zero live statements.
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "PRAGMA integrity_check(1);"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ nsString result;
+ rv = stmt->GetString(0, result);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!result.EqualsLiteral("ok"))) {
+ *aOk = false;
+ return NS_OK;
+ }
+ }
+
+ // Now enable and check for foreign key constraints.
+ {
+ int32_t foreignKeysWereEnabled;
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "PRAGMA foreign_keys;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ rv = stmt->GetInt32(0, &foreignKeysWereEnabled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (!foreignKeysWereEnabled) {
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA foreign_keys = ON;"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool foreignKeyError;
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "PRAGMA foreign_key_check;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->ExecuteStep(&foreignKeyError);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (!foreignKeysWereEnabled) {
+ nsAutoCString stmtSQL;
+ stmtSQL.AssignLiteral("PRAGMA foreign_keys = ");
+ stmtSQL.AppendLiteral("OFF");
+ stmtSQL.Append(';');
+
+ rv = aConnection->ExecuteSimpleSQL(stmtSQL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (foreignKeyError) {
+ *aOk = false;
+ return NS_OK;
+ }
+ }
+
+ *aOk = true;
+ return NS_OK;
+}
+
+nsresult
+DatabaseMaintenance::DetermineMaintenanceAction(
+ mozIStorageConnection* aConnection,
+ nsIFile* aDatabaseFile,
+ MaintenanceAction* aMaintenanceAction)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(aDatabaseFile);
+ MOZ_ASSERT(aMaintenanceAction);
+
+ int32_t schemaVersion;
+ nsresult rv = aConnection->GetSchemaVersion(&schemaVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Don't do anything if the schema version is less than 18; before that
+ // version no databases had |auto_vacuum == INCREMENTAL| set and we didn't
+ // track the values needed for the heuristics below.
+ if (schemaVersion < MakeSchemaVersion(18, 0)) {
+ *aMaintenanceAction = MaintenanceAction::Nothing;
+ return NS_OK;
+ }
+
+ bool lowDiskSpace = IndexedDatabaseManager::InLowDiskSpaceMode();
+
+ if (QuotaManager::IsRunningXPCShellTests()) {
+ // If we're running XPCShell then we want to test both the low disk space
+ // and normal disk space code paths so pick semi-randomly based on the
+ // current time.
+ lowDiskSpace = ((PR_Now() / PR_USEC_PER_MSEC) % 2) == 0;
+ }
+
+ // If we're low on disk space then the best we can hope for is that an
+ // incremental vacuum might free some space. That is a journaled operation so
+ // it may not be possible even then.
+ if (lowDiskSpace) {
+ *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
+ return NS_OK;
+ }
+
+ // This method shouldn't make any permanent changes to the database, so make
+ // sure everything gets rolled back when we leave.
+ mozStorageTransaction transaction(aConnection,
+ /* aCommitOnComplete */ false);
+
+ // Check to see when we last vacuumed this database.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT last_vacuum_time, last_vacuum_size "
+ "FROM database;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ PRTime lastVacuumTime;
+ rv = stmt->GetInt64(0, &lastVacuumTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t lastVacuumSize;
+ rv = stmt->GetInt64(1, &lastVacuumSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NS_ASSERTION(lastVacuumSize > 0, "Thy last vacuum size shall be greater than zero, less than zero shall thy last vacuum size not be. Zero is right out.");
+
+ PRTime startTime = mMaintenance->StartTime();
+
+ // This shouldn't really be possible...
+ if (NS_WARN_IF(startTime <= lastVacuumTime)) {
+ *aMaintenanceAction = MaintenanceAction::Nothing;
+ return NS_OK;
+ }
+
+ if (startTime - lastVacuumTime < kMinVacuumAge) {
+ *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
+ return NS_OK;
+ }
+
+ // It has been more than a week since the database was vacuumed, so gather
+ // statistics on its usage to see if vacuuming is worthwhile.
+
+ // Create a temporary copy of the dbstat table to speed up the queries that
+ // come later.
+ rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE VIRTUAL TABLE __stats__ USING dbstat;"
+ "CREATE TEMP TABLE __temp_stats__ AS SELECT * FROM __stats__;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Calculate the percentage of the database pages that are not in contiguous
+ // order.
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT SUM(__ts1__.pageno != __ts2__.pageno + 1) * 100.0 / COUNT(*) "
+ "FROM __temp_stats__ AS __ts1__, __temp_stats__ AS __ts2__ "
+ "WHERE __ts1__.name = __ts2__.name "
+ "AND __ts1__.rowid = __ts2__.rowid + 1;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ int32_t percentUnordered;
+ rv = stmt->GetInt32(0, &percentUnordered);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(percentUnordered >= 0);
+ MOZ_ASSERT(percentUnordered <= 100);
+
+ if (percentUnordered >= kPercentUnorderedThreshold) {
+ *aMaintenanceAction = MaintenanceAction::FullVacuum;
+ return NS_OK;
+ }
+
+ // Don't try a full vacuum if the file hasn't grown by 10%.
+ int64_t currentFileSize;
+ rv = aDatabaseFile->GetFileSize(&currentFileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (currentFileSize <= lastVacuumSize ||
+ (((currentFileSize - lastVacuumSize) * 100 / currentFileSize) <
+ kPercentFileSizeGrowthThreshold)) {
+ *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
+ return NS_OK;
+ }
+
+ // See if there are any free pages that we can reclaim.
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "PRAGMA freelist_count;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ int32_t freelistCount;
+ rv = stmt->GetInt32(0, &freelistCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(freelistCount >= 0);
+
+ // If we have too many free pages then we should try an incremental vacuum. If
+ // that causes too much fragmentation then we'll try a full vacuum later.
+ if (freelistCount > kMaxFreelistThreshold) {
+ *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
+ return NS_OK;
+ }
+
+ // Calculate the percentage of unused bytes on pages in the database.
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT SUM(unused) * 100.0 / SUM(pgsize) "
+ "FROM __temp_stats__;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(hasResult);
+
+ int32_t percentUnused;
+ rv = stmt->GetInt32(0, &percentUnused);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(percentUnused >= 0);
+ MOZ_ASSERT(percentUnused <= 100);
+
+ *aMaintenanceAction = percentUnused >= kPercentUnusedThreshold ?
+ MaintenanceAction::FullVacuum :
+ MaintenanceAction::IncrementalVacuum;
+ return NS_OK;
+}
+
+void
+DatabaseMaintenance::IncrementalVacuum(mozIStorageConnection* aConnection)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aConnection);
+
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA incremental_vacuum;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+}
+
+void
+DatabaseMaintenance::FullVacuum(mozIStorageConnection* aConnection,
+ nsIFile* aDatabaseFile)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(aDatabaseFile);
+
+ nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "VACUUM;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ PRTime vacuumTime = PR_Now();
+ MOZ_ASSERT(vacuumTime > 0);
+
+ int64_t fileSize;
+ rv = aDatabaseFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ MOZ_ASSERT(fileSize > 0);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE database "
+ "SET last_vacuum_time = :time"
+ ", last_vacuum_size = :size;"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("time"), vacuumTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("size"), fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+}
+
+void
+DatabaseMaintenance::RunOnOwningThread()
+{
+ AssertIsOnBackgroundThread();
+
+ if (mCompleteCallback) {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget()));
+ }
+
+ mMaintenance->UnregisterDatabaseMaintenance(this);
+}
+
+void
+DatabaseMaintenance::RunOnConnectionThread()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ PerformMaintenanceOnDatabase();
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mMaintenance->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+NS_IMETHODIMP
+DatabaseMaintenance::Run()
+{
+ if (IsOnBackgroundThread()) {
+ RunOnOwningThread();
+ } else {
+ RunOnConnectionThread();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DatabaseMaintenance::
+AutoProgressHandler::Register(mozIStorageConnection* aConnection)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aConnection);
+
+ // We want to quickly bail out of any operation if the user becomes active, so
+ // use a small granularity here since database performance isn't critical.
+ static const int32_t kProgressGranularity = 50;
+
+ nsCOMPtr<mozIStorageProgressHandler> oldHandler;
+ nsresult rv = aConnection->SetProgressHandler(kProgressGranularity,
+ this,
+ getter_AddRefs(oldHandler));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(!oldHandler);
+ mConnection = aConnection;
+
+ return NS_OK;
+}
+
+void
+DatabaseMaintenance::
+AutoProgressHandler::Unregister()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(mConnection);
+
+ nsCOMPtr<mozIStorageProgressHandler> oldHandler;
+ nsresult rv = mConnection->RemoveProgressHandler(getter_AddRefs(oldHandler));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ MOZ_ASSERT_IF(NS_SUCCEEDED(rv), oldHandler == this);
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+DatabaseMaintenance::
+AutoProgressHandler::AddRef()
+{
+ NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
+
+#ifdef DEBUG
+ mDEBUGRefCnt++;
+#endif
+ return 2;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+DatabaseMaintenance::
+AutoProgressHandler::Release()
+{
+ NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
+
+#ifdef DEBUG
+ mDEBUGRefCnt--;
+#endif
+ return 1;
+}
+
+NS_IMPL_QUERY_INTERFACE(DatabaseMaintenance::AutoProgressHandler,
+ mozIStorageProgressHandler)
+
+NS_IMETHODIMP
+DatabaseMaintenance::
+AutoProgressHandler::OnProgress(mozIStorageConnection* aConnection,
+ bool* _retval)
+{
+ NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(mConnection == aConnection);
+ MOZ_ASSERT(_retval);
+
+ *_retval = mMaintenance->IsAborted();
+
+ return NS_OK;
+}
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(CompressDataBlobsFunction, mozIStorageFunction)
+NS_IMPL_ISUPPORTS(EncodeKeysFunction, mozIStorageFunction)
+
+#if !defined(MOZ_B2G)
+
+nsresult
+UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory,
+ mozIStorageConnection* aConnection)
+{
+ // This file manager doesn't need real origin info, etc. The only purpose is
+ // to store file ids without adding more complexity or code duplication.
+ RefPtr<FileManager> fileManager =
+ new FileManager(PERSISTENCE_TYPE_INVALID,
+ EmptyCString(),
+ EmptyCString(),
+ false,
+ EmptyString(),
+ false);
+
+ nsresult rv = fileManager->Init(aFMDirectory, aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoPtr<NormalJSContext> context(NormalJSContext::Create());
+ if (NS_WARN_IF(!context)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mFileManager.swap(fileManager);
+ mContext = context;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UpgradeFileIdsFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+UpgradeFileIdsFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult)
+{
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+ MOZ_ASSERT(mFileManager);
+ MOZ_ASSERT(mContext);
+
+ PROFILER_LABEL("IndexedDB",
+ "UpgradeFileIdsFunction::OnFunctionCall",
+ js::ProfileEntry::Category::STORAGE);
+
+ uint32_t argc;
+ nsresult rv = aArguments->GetNumEntries(&argc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (argc != 2) {
+ NS_WARNING("Don't call me with the wrong number of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ StructuredCloneReadInfo cloneInfo;
+ DatabaseOperationBase::GetStructuredCloneReadInfoFromValueArray(aArguments,
+ 1,
+ 0,
+ mFileManager,
+ &cloneInfo);
+
+ JSContext* cx = mContext->Context();
+ JSAutoRequest ar(cx);
+ JSAutoCompartment ac(cx, mContext->Global());
+
+ JS::Rooted<JS::Value> clone(cx);
+ if (NS_WARN_IF(!IDBObjectStore::DeserializeUpgradeValue(cx, cloneInfo,
+ &clone))) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ nsAutoString fileIds;
+
+ for (uint32_t count = cloneInfo.mFiles.Length(), index = 0;
+ index < count;
+ index++) {
+ StructuredCloneFile& file = cloneInfo.mFiles[index];
+ MOZ_ASSERT(file.mFileInfo);
+
+ const int64_t id = file.mFileInfo->Id();
+
+ if (index) {
+ fileIds.Append(' ');
+ }
+ fileIds.AppendInt(file.mType == StructuredCloneFile::eBlob ? id : -id);
+ }
+
+ nsCOMPtr<nsIVariant> result = new mozilla::storage::TextVariant(fileIds);
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+#endif // MOZ_B2G
+
+// static
+void
+DatabaseOperationBase::GetBindingClauseForKeyRange(
+ const SerializedKeyRange& aKeyRange,
+ const nsACString& aKeyColumnName,
+ nsAutoCString& aBindingClause)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(!aKeyColumnName.IsEmpty());
+
+ NS_NAMED_LITERAL_CSTRING(andStr, " AND ");
+ NS_NAMED_LITERAL_CSTRING(spacecolon, " :");
+ NS_NAMED_LITERAL_CSTRING(lowerKey, "lower_key");
+
+ if (aKeyRange.isOnly()) {
+ // Both keys equal.
+ aBindingClause = andStr + aKeyColumnName + NS_LITERAL_CSTRING(" =") +
+ spacecolon + lowerKey;
+ return;
+ }
+
+ aBindingClause.Truncate();
+
+ if (!aKeyRange.lower().IsUnset()) {
+ // Lower key is set.
+ aBindingClause.Append(andStr + aKeyColumnName);
+ aBindingClause.AppendLiteral(" >");
+ if (!aKeyRange.lowerOpen()) {
+ aBindingClause.AppendLiteral("=");
+ }
+ aBindingClause.Append(spacecolon + lowerKey);
+ }
+
+ if (!aKeyRange.upper().IsUnset()) {
+ // Upper key is set.
+ aBindingClause.Append(andStr + aKeyColumnName);
+ aBindingClause.AppendLiteral(" <");
+ if (!aKeyRange.upperOpen()) {
+ aBindingClause.AppendLiteral("=");
+ }
+ aBindingClause.Append(spacecolon + NS_LITERAL_CSTRING("upper_key"));
+ }
+
+ MOZ_ASSERT(!aBindingClause.IsEmpty());
+}
+
+// static
+uint64_t
+DatabaseOperationBase::ReinterpretDoubleAsUInt64(double aDouble)
+{
+ // This is a duplicate of the js engine's byte munging in StructuredClone.cpp
+ return BitwiseCast<uint64_t>(aDouble);
+}
+
+// static
+template <typename T>
+nsresult
+DatabaseOperationBase::GetStructuredCloneReadInfoFromSource(
+ T* aSource,
+ uint32_t aDataIndex,
+ uint32_t aFileIdsIndex,
+ FileManager* aFileManager,
+ StructuredCloneReadInfo* aInfo)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aSource);
+ MOZ_ASSERT(aFileManager);
+ MOZ_ASSERT(aInfo);
+
+ int32_t columnType;
+ nsresult rv = aSource->GetTypeOfIndex(aDataIndex, &columnType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB ||
+ columnType == mozIStorageStatement::VALUE_TYPE_INTEGER);
+
+ bool isNull;
+ rv = aSource->GetIsNull(aFileIdsIndex, &isNull);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString fileIds;
+
+ if (isNull) {
+ fileIds.SetIsVoid(true);
+ } else {
+ rv = aSource->GetString(aFileIdsIndex, fileIds);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (columnType == mozIStorageStatement::VALUE_TYPE_INTEGER) {
+ uint64_t intData;
+ rv = aSource->GetInt64(aDataIndex, reinterpret_cast<int64_t*>(&intData));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = GetStructuredCloneReadInfoFromExternalBlob(intData,
+ aFileManager,
+ fileIds,
+ aInfo);
+ } else {
+ const uint8_t* blobData;
+ uint32_t blobDataLength;
+ nsresult rv =
+ aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = GetStructuredCloneReadInfoFromBlob(blobData,
+ blobDataLength,
+ aFileManager,
+ fileIds,
+ aInfo);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob(
+ const uint8_t* aBlobData,
+ uint32_t aBlobDataLength,
+ FileManager* aFileManager,
+ const nsAString& aFileIds,
+ StructuredCloneReadInfo* aInfo)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aFileManager);
+ MOZ_ASSERT(aInfo);
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob",
+ js::ProfileEntry::Category::STORAGE);
+
+ const char* compressed = reinterpret_cast<const char*>(aBlobData);
+ size_t compressedLength = size_t(aBlobDataLength);
+
+ size_t uncompressedLength;
+ if (NS_WARN_IF(!snappy::GetUncompressedLength(compressed, compressedLength,
+ &uncompressedLength))) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ AutoTArray<uint8_t, 512> uncompressed;
+ if (NS_WARN_IF(!uncompressed.SetLength(uncompressedLength, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char* uncompressedBuffer = reinterpret_cast<char*>(uncompressed.Elements());
+
+ if (NS_WARN_IF(!snappy::RawUncompress(compressed, compressedLength,
+ uncompressedBuffer))) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (!aInfo->mData.WriteBytes(uncompressedBuffer, uncompressed.Length())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!aFileIds.IsVoid()) {
+ nsresult rv = DeserializeStructuredCloneFiles(aFileManager,
+ aFileIds,
+ aInfo->mFiles,
+ &aInfo->mHasPreprocessInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::GetStructuredCloneReadInfoFromExternalBlob(
+ uint64_t aIntData,
+ FileManager* aFileManager,
+ const nsAString& aFileIds,
+ StructuredCloneReadInfo* aInfo)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aFileManager);
+ MOZ_ASSERT(aInfo);
+
+ PROFILER_LABEL(
+ "IndexedDB",
+ "DatabaseOperationBase::GetStructuredCloneReadInfoFromExternalBlob",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv;
+
+ if (!aFileIds.IsVoid()) {
+ rv = DeserializeStructuredCloneFiles(aFileManager,
+ aFileIds,
+ aInfo->mFiles,
+ &aInfo->mHasPreprocessInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Higher and lower 32 bits described
+ // in ObjectStoreAddOrPutRequestOp::DoDatabaseWork.
+ uint32_t index = uint32_t(aIntData & 0xFFFFFFFF);
+
+ if (index >= aInfo->mFiles.Length()) {
+ MOZ_ASSERT(false, "Bad index value!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ StructuredCloneFile& file = aInfo->mFiles[index];
+ MOZ_ASSERT(file.mFileInfo);
+ MOZ_ASSERT(file.mType == StructuredCloneFile::eStructuredClone);
+
+ nsCOMPtr<nsIFile> nativeFile = GetFileForFileInfo(file.mFileInfo);
+ if (NS_WARN_IF(!nativeFile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStream> fileInputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), nativeFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<SnappyUncompressInputStream> snappyInputStream =
+ new SnappyUncompressInputStream(fileInputStream);
+
+ do {
+ char buffer[kFileCopyBufferSize];
+
+ uint32_t numRead;
+ rv = snappyInputStream->Read(buffer, sizeof(buffer), &numRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ if (!numRead) {
+ break;
+ }
+
+ if (NS_WARN_IF(!aInfo->mData.WriteBytes(buffer, numRead))) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ } while (true);
+
+ return rv;
+}
+
+// static
+nsresult
+DatabaseOperationBase::BindKeyRangeToStatement(
+ const SerializedKeyRange& aKeyRange,
+ mozIStorageStatement* aStatement)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aStatement);
+
+ nsresult rv = NS_OK;
+
+ if (!aKeyRange.lower().IsUnset()) {
+ rv = aKeyRange.lower().BindToStatement(aStatement, NS_LITERAL_CSTRING("lower_key"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (aKeyRange.isOnly()) {
+ return rv;
+ }
+
+ if (!aKeyRange.upper().IsUnset()) {
+ rv = aKeyRange.upper().BindToStatement(aStatement, NS_LITERAL_CSTRING("upper_key"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::BindKeyRangeToStatement(
+ const SerializedKeyRange& aKeyRange,
+ mozIStorageStatement* aStatement,
+ const nsCString& aLocale)
+{
+#ifndef ENABLE_INTL_API
+ return BindKeyRangeToStatement(aKeyRange, aStatement);
+#else
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aStatement);
+ MOZ_ASSERT(!aLocale.IsEmpty());
+
+ nsresult rv = NS_OK;
+
+ if (!aKeyRange.lower().IsUnset()) {
+ Key lower;
+ rv = aKeyRange.lower().ToLocaleBasedKey(lower, aLocale);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = lower.BindToStatement(aStatement, NS_LITERAL_CSTRING("lower_key"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (aKeyRange.isOnly()) {
+ return rv;
+ }
+
+ if (!aKeyRange.upper().IsUnset()) {
+ Key upper;
+ rv = aKeyRange.upper().ToLocaleBasedKey(upper, aLocale);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = upper.BindToStatement(aStatement, NS_LITERAL_CSTRING("upper_key"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+#endif
+}
+
+// static
+void
+DatabaseOperationBase::AppendConditionClause(const nsACString& aColumnName,
+ const nsACString& aArgName,
+ bool aLessThan,
+ bool aEquals,
+ nsAutoCString& aResult)
+{
+ aResult += NS_LITERAL_CSTRING(" AND ") + aColumnName +
+ NS_LITERAL_CSTRING(" ");
+
+ if (aLessThan) {
+ aResult.Append('<');
+ }
+ else {
+ aResult.Append('>');
+ }
+
+ if (aEquals) {
+ aResult.Append('=');
+ }
+
+ aResult += NS_LITERAL_CSTRING(" :") + aArgName;
+}
+
+// static
+nsresult
+DatabaseOperationBase::GetUniqueIndexTableForObjectStore(
+ TransactionBase* aTransaction,
+ int64_t aObjectStoreId,
+ Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransaction);
+ MOZ_ASSERT(aObjectStoreId);
+ MOZ_ASSERT(aMaybeUniqueIndexTable.isNothing());
+
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ aTransaction->GetMetadataForObjectStoreId(aObjectStoreId);
+ MOZ_ASSERT(objectStoreMetadata);
+
+ if (!objectStoreMetadata->mIndexes.Count()) {
+ return NS_OK;
+ }
+
+ const uint32_t indexCount = objectStoreMetadata->mIndexes.Count();
+ MOZ_ASSERT(indexCount > 0);
+
+ aMaybeUniqueIndexTable.emplace();
+ UniqueIndexTable* uniqueIndexTable = aMaybeUniqueIndexTable.ptr();
+ MOZ_ASSERT(uniqueIndexTable);
+
+ for (auto iter = objectStoreMetadata->mIndexes.Iter(); !iter.Done(); iter.Next()) {
+ FullIndexMetadata* value = iter.UserData();
+ MOZ_ASSERT(!uniqueIndexTable->Get(value->mCommonMetadata.id()));
+
+ if (NS_WARN_IF(!uniqueIndexTable->Put(value->mCommonMetadata.id(),
+ value->mCommonMetadata.unique(),
+ fallible))) {
+ break;
+ }
+ }
+
+ if (NS_WARN_IF(aMaybeUniqueIndexTable.ref().Count() != indexCount)) {
+ IDB_REPORT_INTERNAL_ERR();
+ aMaybeUniqueIndexTable.reset();
+ NS_WARNING("out of memory");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+#ifdef DEBUG
+ aMaybeUniqueIndexTable.ref().MarkImmutable();
+#endif
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::IndexDataValuesFromUpdateInfos(
+ const nsTArray<IndexUpdateInfo>& aUpdateInfos,
+ const UniqueIndexTable& aUniqueIndexTable,
+ nsTArray<IndexDataValue>& aIndexValues)
+{
+ MOZ_ASSERT(aIndexValues.IsEmpty());
+ MOZ_ASSERT_IF(!aUpdateInfos.IsEmpty(), aUniqueIndexTable.Count());
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseOperationBase::IndexDataValuesFromUpdateInfos",
+ js::ProfileEntry::Category::STORAGE);
+
+ const uint32_t count = aUpdateInfos.Length();
+
+ if (!count) {
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!aIndexValues.SetCapacity(count, fallible))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t idxIndex = 0; idxIndex < count; idxIndex++) {
+ const IndexUpdateInfo& updateInfo = aUpdateInfos[idxIndex];
+ const int64_t& indexId = updateInfo.indexId();
+ const Key& key = updateInfo.value();
+ const Key& sortKey = updateInfo.localizedValue();
+
+ bool unique = false;
+ MOZ_ALWAYS_TRUE(aUniqueIndexTable.Get(indexId, &unique));
+
+ IndexDataValue idv(indexId, unique, key, sortKey);
+
+ MOZ_ALWAYS_TRUE(
+ aIndexValues.InsertElementSorted(idv, fallible));
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::InsertIndexTableRows(
+ DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ const Key& aObjectStoreKey,
+ const FallibleTArray<IndexDataValue>& aIndexValues)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(!aObjectStoreKey.IsUnset());
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseOperationBase::InsertIndexTableRows",
+ js::ProfileEntry::Category::STORAGE);
+
+ const uint32_t count = aIndexValues.Length();
+ if (!count) {
+ return NS_OK;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
+ NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
+ NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
+ NS_NAMED_LITERAL_CSTRING(valueString, "value");
+ NS_NAMED_LITERAL_CSTRING(valueLocaleString, "value_locale");
+
+ DatabaseConnection::CachedStatement insertUniqueStmt;
+ DatabaseConnection::CachedStatement insertStmt;
+
+ nsresult rv;
+
+ for (uint32_t index = 0; index < count; index++) {
+ const IndexDataValue& info = aIndexValues[index];
+
+ DatabaseConnection::CachedStatement& stmt =
+ info.mUnique ? insertUniqueStmt : insertStmt;
+
+ if (stmt) {
+ stmt.Reset();
+ } else if (info.mUnique) {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO unique_index_data "
+ "(index_id, value, object_store_id, object_data_key, value_locale) "
+ "VALUES (:index_id, :value, :object_store_id, :object_data_key, :value_locale);"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO index_data "
+ "(index_id, value, object_data_key, object_store_id, value_locale) "
+ "VALUES (:index_id, :value, :object_data_key, :object_store_id, :value_locale);"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = stmt->BindInt64ByName(indexIdString, info.mIndexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = info.mKey.BindToStatement(stmt, valueString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = info.mSortKey.BindToStatement(stmt, valueLocaleString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aObjectStoreKey.BindToStatement(stmt, objectDataKeyString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (rv == NS_ERROR_STORAGE_CONSTRAINT && info.mUnique) {
+ // If we're inserting multiple entries for the same unique index, then
+ // we might have failed to insert due to colliding with another entry for
+ // the same index in which case we should ignore it.
+ for (int32_t index2 = int32_t(index) - 1;
+ index2 >= 0 && aIndexValues[index2].mIndexId == info.mIndexId;
+ --index2) {
+ if (info.mKey == aIndexValues[index2].mKey) {
+ // We found a key with the same value for the same index. So we
+ // must have had a collision with a value we just inserted.
+ rv = NS_OK;
+ break;
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::DeleteIndexDataTableRows(
+ DatabaseConnection* aConnection,
+ const Key& aObjectStoreKey,
+ const FallibleTArray<IndexDataValue>& aIndexValues)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(!aObjectStoreKey.IsUnset());
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseOperationBase::DeleteIndexDataTableRows",
+ js::ProfileEntry::Category::STORAGE);
+
+ const uint32_t count = aIndexValues.Length();
+ if (!count) {
+ return NS_OK;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
+ NS_NAMED_LITERAL_CSTRING(valueString, "value");
+ NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
+
+ DatabaseConnection::CachedStatement deleteUniqueStmt;
+ DatabaseConnection::CachedStatement deleteStmt;
+
+ nsresult rv;
+
+ for (uint32_t index = 0; index < count; index++) {
+ const IndexDataValue& indexValue = aIndexValues[index];
+
+ DatabaseConnection::CachedStatement& stmt =
+ indexValue.mUnique ? deleteUniqueStmt : deleteStmt;
+
+ if (stmt) {
+ stmt.Reset();
+ } else if (indexValue.mUnique) {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM unique_index_data "
+ "WHERE index_id = :index_id "
+ "AND value = :value;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM index_data "
+ "WHERE index_id = :index_id "
+ "AND value = :value "
+ "AND object_data_key = :object_data_key;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = stmt->BindInt64ByName(indexIdString, indexValue.mIndexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = indexValue.mKey.BindToStatement(stmt, valueString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!indexValue.mUnique) {
+ rv = aObjectStoreKey.BindToStatement(stmt, objectDataKeyString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes(
+ DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ const OptionalKeyRange& aKeyRange)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(aObjectStoreId);
+
+#ifdef DEBUG
+ {
+ bool hasIndexes = false;
+ MOZ_ASSERT(NS_SUCCEEDED(
+ ObjectStoreHasIndexes(aConnection, aObjectStoreId, &hasIndexes)));
+ MOZ_ASSERT(hasIndexes,
+ "Don't use this slow method if there are no indexes!");
+ }
+#endif
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaseOperationBase::"
+ "DeleteObjectStoreDataTableRowsWithIndexes",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool singleRowOnly =
+ aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange &&
+ aKeyRange.get_SerializedKeyRange().isOnly();
+
+ NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
+ NS_NAMED_LITERAL_CSTRING(keyString, "key");
+
+ nsresult rv;
+ Key objectStoreKey;
+ DatabaseConnection::CachedStatement selectStmt;
+
+ if (singleRowOnly) {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT index_data_values "
+ "FROM object_data "
+ "WHERE object_store_id = :object_store_id "
+ "AND key = :key;"),
+ &selectStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ objectStoreKey = aKeyRange.get_SerializedKeyRange().lower();
+
+ rv = objectStoreKey.BindToStatement(selectStmt, keyString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ nsAutoCString keyRangeClause;
+ if (aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange) {
+ GetBindingClauseForKeyRange(aKeyRange.get_SerializedKeyRange(),
+ keyString,
+ keyRangeClause);
+ }
+
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT index_data_values, key "
+ "FROM object_data "
+ "WHERE object_store_id = :") +
+ objectStoreIdString +
+ keyRangeClause +
+ NS_LITERAL_CSTRING(";"),
+ &selectStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange) {
+ rv = BindKeyRangeToStatement(aKeyRange, selectStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ rv = selectStmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ DatabaseConnection::CachedStatement deleteStmt;
+ AutoTArray<IndexDataValue, 32> indexValues;
+
+ DebugOnly<uint32_t> resultCountDEBUG = 0;
+
+ bool hasResult;
+ while (NS_SUCCEEDED(rv = selectStmt->ExecuteStep(&hasResult)) && hasResult) {
+ if (!singleRowOnly) {
+ rv = objectStoreKey.SetFromStatement(selectStmt, 1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ indexValues.ClearAndRetainStorage();
+ }
+
+ rv = ReadCompressedIndexDataValues(selectStmt, 0, indexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = DeleteIndexDataTableRows(aConnection, objectStoreKey, indexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (deleteStmt) {
+ MOZ_ALWAYS_SUCCEEDS(deleteStmt->Reset());
+ } else {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_data "
+ "WHERE object_store_id = :object_store_id "
+ "AND key = :key;"),
+ &deleteStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = deleteStmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = objectStoreKey.BindToStatement(deleteStmt, keyString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = deleteStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ resultCountDEBUG++;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT_IF(singleRowOnly, resultCountDEBUG <= 1);
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::UpdateIndexValues(
+ DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ const Key& aObjectStoreKey,
+ const FallibleTArray<IndexDataValue>& aIndexValues)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(!aObjectStoreKey.IsUnset());
+
+ PROFILER_LABEL("IndexedDB",
+ "DatabaunseOperationBase::UpdateIndexValues",
+ js::ProfileEntry::Category::STORAGE);
+
+ UniqueFreePtr<uint8_t> indexDataValues;
+ uint32_t indexDataValuesLength;
+ nsresult rv = MakeCompressedIndexDataValues(aIndexValues,
+ indexDataValues,
+ &indexDataValuesLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(!indexDataValuesLength == !(indexDataValues.get()));
+
+ DatabaseConnection::CachedStatement updateStmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "UPDATE object_data "
+ "SET index_data_values = :index_data_values "
+ "WHERE object_store_id = :object_store_id "
+ "AND key = :key;"),
+ &updateStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(indexDataValuesString, "index_data_values");
+
+ if (indexDataValues) {
+ rv = updateStmt->BindAdoptedBlobByName(indexDataValuesString,
+ indexDataValues.release(),
+ indexDataValuesLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ rv = updateStmt->BindNullByName(indexDataValuesString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ aObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aObjectStoreKey.BindToStatement(updateStmt, NS_LITERAL_CSTRING("key"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = updateStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DatabaseOperationBase::ObjectStoreHasIndexes(DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ bool* aHasIndexes)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(aObjectStoreId);
+ MOZ_ASSERT(aHasIndexes);
+
+ DatabaseConnection::CachedStatement stmt;
+
+ nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT id "
+ "FROM object_store_index "
+ "WHERE object_store_id = :object_store_id "
+ "LIMIT 1;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ aObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aHasIndexes = hasResult;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(DatabaseOperationBase,
+ Runnable,
+ mozIStorageProgressHandler)
+
+NS_IMETHODIMP
+DatabaseOperationBase::OnProgress(mozIStorageConnection* aConnection,
+ bool* _retval)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(_retval);
+
+ // This is intentionally racy.
+ *_retval = !OperationMayProceed();
+ return NS_OK;
+}
+
+DatabaseOperationBase::
+AutoSetProgressHandler::AutoSetProgressHandler()
+ : mConnection(nullptr)
+#ifdef DEBUG
+ , mDEBUGDatabaseOp(nullptr)
+#endif
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+}
+
+DatabaseOperationBase::
+AutoSetProgressHandler::~AutoSetProgressHandler()
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ if (mConnection) {
+ nsCOMPtr<mozIStorageProgressHandler> oldHandler;
+ MOZ_ALWAYS_SUCCEEDS(
+ mConnection->RemoveProgressHandler(getter_AddRefs(oldHandler)));
+ MOZ_ASSERT(oldHandler == mDEBUGDatabaseOp);
+ }
+}
+
+nsresult
+DatabaseOperationBase::
+AutoSetProgressHandler::Register(mozIStorageConnection* aConnection,
+ DatabaseOperationBase* aDatabaseOp)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(aDatabaseOp);
+ MOZ_ASSERT(!mConnection);
+
+ nsCOMPtr<mozIStorageProgressHandler> oldProgressHandler;
+
+ nsresult rv =
+ aConnection->SetProgressHandler(kStorageProgressGranularity,
+ aDatabaseOp,
+ getter_AddRefs(oldProgressHandler));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(!oldProgressHandler);
+
+ mConnection = aConnection;
+#ifdef DEBUG
+ mDEBUGDatabaseOp = aDatabaseOp;
+#endif
+
+ return NS_OK;
+}
+
+MutableFile::MutableFile(nsIFile* aFile,
+ Database* aDatabase,
+ FileInfo* aFileInfo)
+ : BackgroundMutableFileParentBase(FILE_HANDLE_STORAGE_IDB,
+ aDatabase->Id(),
+ IntString(aFileInfo->Id()),
+ aFile)
+ , mDatabase(aDatabase)
+ , mFileInfo(aFileInfo)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aDatabase);
+ MOZ_ASSERT(aFileInfo);
+}
+
+MutableFile::~MutableFile()
+{
+ mDatabase->UnregisterMutableFile(this);
+}
+
+already_AddRefed<MutableFile>
+MutableFile::Create(nsIFile* aFile,
+ Database* aDatabase,
+ FileInfo* aFileInfo)
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<MutableFile> newMutableFile =
+ new MutableFile(aFile, aDatabase, aFileInfo);
+
+ if (!aDatabase->RegisterMutableFile(newMutableFile)) {
+ return nullptr;
+ }
+
+ return newMutableFile.forget();
+}
+
+void
+MutableFile::NoteActiveState()
+{
+ AssertIsOnBackgroundThread();
+
+ mDatabase->NoteActiveMutableFile();
+}
+
+void
+MutableFile::NoteInactiveState()
+{
+ AssertIsOnBackgroundThread();
+
+ mDatabase->NoteInactiveMutableFile();
+}
+
+PBackgroundParent*
+MutableFile::GetBackgroundParent() const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ return GetDatabase()->GetBackgroundParent();
+}
+
+already_AddRefed<nsISupports>
+MutableFile::CreateStream(bool aReadOnly)
+{
+ AssertIsOnBackgroundThread();
+
+ PersistenceType persistenceType = mDatabase->Type();
+ const nsACString& group = mDatabase->Group();
+ const nsACString& origin = mDatabase->Origin();
+
+ nsCOMPtr<nsISupports> result;
+
+ if (aReadOnly) {
+ RefPtr<FileInputStream> stream =
+ FileInputStream::Create(persistenceType, group, origin, mFile, -1, -1,
+ nsIFileInputStream::DEFER_OPEN);
+ result = NS_ISUPPORTS_CAST(nsIFileInputStream*, stream);
+ }
+ else {
+ RefPtr<FileStream> stream =
+ FileStream::Create(persistenceType, group, origin, mFile, -1, -1,
+ nsIFileStream::DEFER_OPEN);
+ result = NS_ISUPPORTS_CAST(nsIFileStream*, stream);
+ }
+ if (NS_WARN_IF(!result)) {
+ return nullptr;
+ }
+
+ return result.forget();
+}
+
+already_AddRefed<BlobImpl>
+MutableFile::CreateBlobImpl()
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<BlobImpl> blobImpl =
+ new BlobImplStoredFile(mFile, mFileInfo, /* aSnapshot */ true);
+ return blobImpl.forget();
+}
+
+PBackgroundFileHandleParent*
+MutableFile::AllocPBackgroundFileHandleParent(const FileMode& aMode)
+{
+ AssertIsOnBackgroundThread();
+
+ // Once a database is closed it must not try to open new file handles.
+ if (NS_WARN_IF(mDatabase->IsClosed())) {
+ if (!mDatabase->IsInvalidated()) {
+ ASSERT_UNLESS_FUZZING();
+ }
+ return nullptr;
+ }
+
+ if (!gFileHandleThreadPool) {
+ RefPtr<FileHandleThreadPool> fileHandleThreadPool =
+ FileHandleThreadPool::Create();
+ if (NS_WARN_IF(!fileHandleThreadPool)) {
+ return nullptr;
+ }
+
+ gFileHandleThreadPool = fileHandleThreadPool;
+ }
+
+ return BackgroundMutableFileParentBase::AllocPBackgroundFileHandleParent(
+ aMode);
+}
+
+bool
+MutableFile::RecvPBackgroundFileHandleConstructor(
+ PBackgroundFileHandleParent* aActor,
+ const FileMode& aMode)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mDatabase->IsClosed());
+
+ if (NS_WARN_IF(mDatabase->IsInvalidated())) {
+ // This is an expected race. We don't want the child to die here, just don't
+ // actually do any work.
+ return true;
+ }
+
+ return BackgroundMutableFileParentBase::RecvPBackgroundFileHandleConstructor(
+ aActor, aMode);
+}
+
+bool
+MutableFile::RecvGetFileId(int64_t* aFileId)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mFileInfo);
+
+ if (NS_WARN_IF(!IndexedDatabaseManager::InTestingMode())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ *aFileId = mFileInfo->Id();
+ return true;
+}
+
+FactoryOp::FactoryOp(Factory* aFactory,
+ already_AddRefed<ContentParent> aContentParent,
+ const CommonFactoryRequestParams& aCommonParams,
+ bool aDeleting)
+ : DatabaseOperationBase(aFactory->GetLoggingInfo()->Id(),
+ aFactory->GetLoggingInfo()->NextRequestSN())
+ , mFactory(aFactory)
+ , mContentParent(Move(aContentParent))
+ , mCommonParams(aCommonParams)
+ , mState(State::Initial)
+ , mIsApp(false)
+ , mEnforcingQuota(true)
+ , mDeleting(aDeleting)
+ , mBlockedDatabaseOpen(false)
+ , mChromeWriteAccessAllowed(false)
+ , mFileHandleDisabled(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFactory);
+ MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+}
+
+nsresult
+FactoryOp::Open()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mState == State::Initial);
+
+ // Swap this to the stack now to ensure that we release it on this thread.
+ RefPtr<ContentParent> contentParent;
+ mContentParent.swap(contentParent);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+ !OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ PermissionRequestBase::PermissionValue permission;
+ nsresult rv = CheckPermission(contentParent, &permission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
+ permission == PermissionRequestBase::kPermissionDenied ||
+ permission == PermissionRequestBase::kPermissionPrompt);
+
+ if (permission == PermissionRequestBase::kPermissionDenied) {
+ return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+ }
+
+ {
+ // These services have to be started on the main thread currently.
+
+ IndexedDatabaseManager* mgr;
+ if (NS_WARN_IF(!(mgr = IndexedDatabaseManager::GetOrCreate()))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ nsCOMPtr<mozIStorageService> ss;
+ if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+ }
+
+ const DatabaseMetadata& metadata = mCommonParams.metadata();
+
+ QuotaManager::GetStorageId(metadata.persistenceType(),
+ mOrigin,
+ Client::IDB,
+ mDatabaseId);
+
+ mDatabaseId.Append('*');
+ mDatabaseId.Append(NS_ConvertUTF16toUTF8(metadata.name()));
+
+ if (permission == PermissionRequestBase::kPermissionPrompt) {
+ mState = State::PermissionChallenge;
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed);
+
+ mState = State::FinishOpen;
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+}
+
+nsresult
+FactoryOp::ChallengePermission()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::PermissionChallenge);
+
+ const PrincipalInfo& principalInfo = mCommonParams.principalInfo();
+ MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
+
+ if (NS_WARN_IF(!SendPermissionChallenge(principalInfo))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FactoryOp::RetryCheckPermission()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mState == State::PermissionRetry);
+ MOZ_ASSERT(mCommonParams.principalInfo().type() ==
+ PrincipalInfo::TContentPrincipalInfo);
+
+ // Swap this to the stack now to ensure that we release it on this thread.
+ RefPtr<ContentParent> contentParent;
+ mContentParent.swap(contentParent);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+ !OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ PermissionRequestBase::PermissionValue permission;
+ nsresult rv = CheckPermission(contentParent, &permission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
+ permission == PermissionRequestBase::kPermissionDenied ||
+ permission == PermissionRequestBase::kPermissionPrompt);
+
+ if (permission == PermissionRequestBase::kPermissionDenied ||
+ permission == PermissionRequestBase::kPermissionPrompt) {
+ return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+ }
+
+ MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed);
+
+ mState = State::FinishOpen;
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+}
+
+nsresult
+FactoryOp::DirectoryOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::DirectoryOpenPending);
+ MOZ_ASSERT(mDirectoryLock);
+ MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
+
+ // gFactoryOps could be null here if the child process crashed or something
+ // and that cleaned up the last Factory actor.
+ if (!gFactoryOps) {
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ // See if this FactoryOp needs to wait.
+ bool delayed = false;
+ for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
+ RefPtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
+ if (MustWaitFor(*existingOp)) {
+ // Only one op can be delayed.
+ MOZ_ASSERT(!existingOp->mDelayedOp);
+ existingOp->mDelayedOp = this;
+ delayed = true;
+ break;
+ }
+ }
+
+ // Adding this to the factory ops list will block any additional ops from
+ // proceeding until this one is done.
+ gFactoryOps->AppendElement(this);
+
+ if (!delayed) {
+ QuotaClient* quotaClient = QuotaClient::GetInstance();
+ MOZ_ASSERT(quotaClient);
+
+ if (RefPtr<Maintenance> currentMaintenance =
+ quotaClient->GetCurrentMaintenance()) {
+ if (RefPtr<DatabaseMaintenance> databaseMaintenance =
+ currentMaintenance->GetDatabaseMaintenance(mDatabaseFilePath)) {
+ databaseMaintenance->WaitForCompletion(this);
+ delayed = true;
+ }
+ }
+ }
+
+ mBlockedDatabaseOpen = true;
+
+ // Balanced in FinishSendResults().
+ IncreaseBusyCount();
+
+ mState = State::DatabaseOpenPending;
+ if (!delayed) {
+ nsresult rv = DatabaseOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FactoryOp::SendToIOThread()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::DatabaseOpenPending);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+ !OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ 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))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+}
+
+void
+FactoryOp::WaitForTransactions()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::BeginVersionChange ||
+ mState == State::WaitingForOtherDatabasesToClose);
+ MOZ_ASSERT(!mDatabaseId.IsEmpty());
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ mState = State::WaitingForTransactionsToComplete;
+
+ RefPtr<WaitForTransactionsHelper> helper =
+ new WaitForTransactionsHelper(mDatabaseId, this);
+ helper->WaitForTransactions();
+}
+
+void
+FactoryOp::FinishSendResults()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::SendingResults);
+ MOZ_ASSERT(mFactory);
+
+ // Make sure to release the factory on this thread.
+ RefPtr<Factory> factory;
+ mFactory.swap(factory);
+
+ if (mBlockedDatabaseOpen) {
+ if (mDelayedOp) {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget()));
+ }
+
+ MOZ_ASSERT(gFactoryOps);
+ gFactoryOps->RemoveElement(this);
+
+ // Match the IncreaseBusyCount in DirectoryOpen().
+ DecreaseBusyCount();
+ }
+
+ mState = State::Completed;
+}
+
+nsresult
+FactoryOp::CheckPermission(ContentParent* aContentParent,
+ PermissionRequestBase::PermissionValue* aPermission)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mState == State::Initial || mState == State::PermissionRetry);
+
+ const PrincipalInfo& principalInfo = mCommonParams.principalInfo();
+ if (principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
+ if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo) {
+ if (aContentParent) {
+ // We just want ContentPrincipalInfo or SystemPrincipalInfo.
+ aContentParent->KillHard("IndexedDB CheckPermission 0");
+ }
+
+ return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+ }
+
+ if (NS_WARN_IF(!Preferences::GetBool(kPrefIndexedDBEnabled, false))) {
+ if (aContentParent) {
+ // The DOM in the other process should have kept us from receiving any
+ // indexedDB messages so assume that the child is misbehaving.
+ aContentParent->KillHard("IndexedDB CheckPermission 1");
+ }
+
+ return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+ }
+
+ const ContentPrincipalInfo& contentPrincipalInfo =
+ principalInfo.get_ContentPrincipalInfo();
+ if (contentPrincipalInfo.attrs().mPrivateBrowsingId != 0) {
+ // IndexedDB is currently disabled in privateBrowsing.
+ return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+ }
+ }
+
+ mFileHandleDisabled = !Preferences::GetBool(kPrefFileHandleEnabled);
+
+ PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
+
+ MOZ_ASSERT(principalInfo.type() != PrincipalInfo::TNullPrincipalInfo);
+
+ if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
+ MOZ_ASSERT(mState == State::Initial);
+ MOZ_ASSERT(persistenceType == PERSISTENCE_TYPE_PERSISTENT);
+
+ if (aContentParent) {
+ // Check to make sure that the child process has access to the database it
+ // is accessing.
+ NS_NAMED_LITERAL_CSTRING(permissionStringBase,
+ PERMISSION_STRING_CHROME_BASE);
+ NS_ConvertUTF16toUTF8 databaseName(mCommonParams.metadata().name());
+ NS_NAMED_LITERAL_CSTRING(readSuffix, PERMISSION_STRING_CHROME_READ_SUFFIX);
+ NS_NAMED_LITERAL_CSTRING(writeSuffix, PERMISSION_STRING_CHROME_WRITE_SUFFIX);
+
+ const nsAutoCString permissionStringWrite =
+ permissionStringBase + databaseName + writeSuffix;
+ const nsAutoCString permissionStringRead =
+ permissionStringBase + databaseName + readSuffix;
+
+ bool canWrite =
+ CheckAtLeastOneAppHasPermission(aContentParent, permissionStringWrite);
+
+ bool canRead;
+ if (canWrite) {
+ MOZ_ASSERT(CheckAtLeastOneAppHasPermission(aContentParent,
+ permissionStringRead));
+ canRead = true;
+ } else {
+ canRead =
+ CheckAtLeastOneAppHasPermission(aContentParent, permissionStringRead);
+ }
+
+ // Deleting a database requires write permissions.
+ if (mDeleting && !canWrite) {
+ aContentParent->KillHard("IndexedDB CheckPermission 2");
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ // Opening or deleting requires read permissions.
+ if (!canRead) {
+ aContentParent->KillHard("IndexedDB CheckPermission 3");
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mChromeWriteAccessAllowed = canWrite;
+ } else {
+ mChromeWriteAccessAllowed = true;
+ }
+
+ if (State::Initial == mState) {
+ QuotaManager::GetInfoForChrome(&mSuffix, &mGroup, &mOrigin, &mIsApp);
+
+ MOZ_ASSERT(!QuotaManager::IsFirstPromptRequired(persistenceType, mOrigin,
+ mIsApp));
+
+ mEnforcingQuota =
+ QuotaManager::IsQuotaEnforced(persistenceType, mOrigin, mIsApp);
+ }
+
+ *aPermission = PermissionRequestBase::kPermissionAllowed;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
+
+ nsresult rv;
+ nsCOMPtr<nsIPrincipal> principal =
+ PrincipalInfoToPrincipal(principalInfo, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString suffix;
+ nsCString group;
+ nsCString origin;
+ bool isApp;
+ rv = QuotaManager::GetInfoFromPrincipal(principal,
+ &suffix,
+ &group,
+ &origin,
+ &isApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef IDB_MOBILE
+ if (persistenceType == PERSISTENCE_TYPE_PERSISTENT &&
+ !QuotaManager::IsOriginInternal(origin) &&
+ !isApp) {
+ return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+ }
+#endif
+
+ PermissionRequestBase::PermissionValue permission;
+
+ if (QuotaManager::IsFirstPromptRequired(persistenceType, origin, isApp)) {
+ rv = PermissionRequestBase::GetCurrentPermission(principal, &permission);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ permission = PermissionRequestBase::kPermissionAllowed;
+ }
+
+ if (permission != PermissionRequestBase::kPermissionDenied &&
+ State::Initial == mState) {
+ mSuffix = suffix;
+ mGroup = group;
+ mOrigin = origin;
+ mIsApp = isApp;
+
+ mEnforcingQuota =
+ QuotaManager::IsQuotaEnforced(persistenceType, mOrigin, mIsApp);
+ }
+
+ *aPermission = permission;
+ return NS_OK;
+}
+
+nsresult
+FactoryOp::SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
+ Database* aOpeningDatabase,
+ uint64_t aOldVersion,
+ const NullableVersion& aNewVersion)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aDatabaseActorInfo);
+ MOZ_ASSERT(mState == State::BeginVersionChange);
+ MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ const uint32_t expectedCount = mDeleting ? 0 : 1;
+ const uint32_t liveCount = aDatabaseActorInfo->mLiveDatabases.Length();
+ if (liveCount > expectedCount) {
+ FallibleTArray<MaybeBlockedDatabaseInfo> maybeBlockedDatabases;
+ for (uint32_t index = 0; index < liveCount; index++) {
+ Database* database = aDatabaseActorInfo->mLiveDatabases[index];
+ if ((!aOpeningDatabase || database != aOpeningDatabase) &&
+ !database->IsClosed() &&
+ NS_WARN_IF(!maybeBlockedDatabases.AppendElement(database, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ if (!maybeBlockedDatabases.IsEmpty()) {
+ mMaybeBlockedDatabases.SwapElements(maybeBlockedDatabases);
+ }
+ }
+
+ if (!mMaybeBlockedDatabases.IsEmpty()) {
+ for (uint32_t count = mMaybeBlockedDatabases.Length(), index = 0;
+ index < count;
+ /* incremented conditionally */) {
+ if (mMaybeBlockedDatabases[index]->SendVersionChange(aOldVersion,
+ aNewVersion)) {
+ index++;
+ } else {
+ // We don't want to wait forever if we were not able to send the
+ // message.
+ mMaybeBlockedDatabases.RemoveElementAt(index);
+ count--;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+bool
+FactoryOp::CheckAtLeastOneAppHasPermission(ContentParent* aContentParent,
+ const nsACString& aPermissionString)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aContentParent);
+ MOZ_ASSERT(!aPermissionString.IsEmpty());
+
+ return true;
+}
+
+nsresult
+FactoryOp::FinishOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::FinishOpen);
+ MOZ_ASSERT(!mContentParent);
+
+ if (QuotaManager::Get()) {
+ nsresult rv = OpenDirectory();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ mState = State::QuotaManagerPending;
+ QuotaManager::GetOrCreate(this);
+
+ return NS_OK;
+}
+
+nsresult
+FactoryOp::QuotaManagerOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::QuotaManagerPending);
+
+ if (NS_WARN_IF(!QuotaManager::Get())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = OpenDirectory();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FactoryOp::OpenDirectory()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::FinishOpen ||
+ mState == State::QuotaManagerPending);
+ MOZ_ASSERT(!mOrigin.IsEmpty());
+ MOZ_ASSERT(!mDirectoryLock);
+ MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+ MOZ_ASSERT(QuotaManager::Get());
+
+ // Need to get database file path in advance.
+ const nsString& databaseName = mCommonParams.metadata().name();
+ PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ nsCOMPtr<nsIFile> dbFile;
+ nsresult rv = quotaManager->GetDirectoryForOrigin(persistenceType,
+ mOrigin,
+ getter_AddRefs(dbFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = dbFile->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString filename;
+ GetDatabaseFilename(databaseName, filename);
+
+ rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = dbFile->GetPath(mDatabaseFilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mState = State::DirectoryOpenPending;
+
+ quotaManager->OpenDirectory(persistenceType,
+ mGroup,
+ mOrigin,
+ mIsApp,
+ Client::IDB,
+ /* aExclusive */ false,
+ this);
+
+ return NS_OK;
+}
+
+bool
+FactoryOp::MustWaitFor(const FactoryOp& aExistingOp)
+{
+ AssertIsOnOwningThread();
+
+ // Things for the same persistence type, the same origin and the same
+ // database must wait.
+ return aExistingOp.mCommonParams.metadata().persistenceType() ==
+ mCommonParams.metadata().persistenceType() &&
+ aExistingOp.mOrigin == mOrigin &&
+ aExistingOp.mDatabaseId == mDatabaseId;
+}
+
+void
+FactoryOp::NoteDatabaseBlocked(Database* aDatabase)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
+ MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
+ MOZ_ASSERT(mMaybeBlockedDatabases.Contains(aDatabase));
+
+ // Only send the blocked event if all databases have reported back. If the
+ // database was closed then it will have been removed from the array.
+ // Otherwise if it was blocked its |mBlocked| flag will be true.
+ bool sendBlockedEvent = true;
+
+ for (uint32_t count = mMaybeBlockedDatabases.Length(), index = 0;
+ index < count;
+ index++) {
+ MaybeBlockedDatabaseInfo& info = mMaybeBlockedDatabases[index];
+ if (info == aDatabase) {
+ // This database was blocked, mark accordingly.
+ info.mBlocked = true;
+ } else if (!info.mBlocked) {
+ // A database has not yet reported back yet, don't send the event yet.
+ sendBlockedEvent = false;
+ }
+ }
+
+ if (sendBlockedEvent) {
+ SendBlockedNotification();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(FactoryOp, DatabaseOperationBase)
+
+// Run() assumes that the caller holds a strong reference to the object that
+// can't be cleared while Run() is being executed.
+// So if you call Run() directly (as opposed to dispatching to an event queue)
+// you need to make sure there's such a reference.
+// See bug 1356824 for more details.
+NS_IMETHODIMP
+FactoryOp::Run()
+{
+ nsresult rv;
+
+ switch (mState) {
+ case State::Initial:
+ rv = Open();
+ break;
+
+ case State::PermissionChallenge:
+ rv = ChallengePermission();
+ break;
+
+ case State::PermissionRetry:
+ rv = RetryCheckPermission();
+ break;
+
+ case State::FinishOpen:
+ rv = FinishOpen();
+ break;
+
+ case State::QuotaManagerPending:
+ rv = QuotaManagerOpen();
+ break;
+
+ case State::DatabaseOpenPending:
+ rv = DatabaseOpen();
+ break;
+
+ case State::DatabaseWorkOpen:
+ rv = DoDatabaseWork();
+ break;
+
+ case State::BeginVersionChange:
+ rv = BeginVersionChange();
+ break;
+
+ case State::WaitingForTransactionsToComplete:
+ rv = DispatchToWorkThread();
+ break;
+
+ case State::SendingResults:
+ SendResults();
+ return NS_OK;
+
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ // Must set mState before dispatching otherwise we will race with the owning
+ // thread.
+ mState = State::SendingResults;
+
+ if (IsOnOwningThread()) {
+ SendResults();
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+FactoryOp::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::DirectoryOpenPending);
+ MOZ_ASSERT(!mDirectoryLock);
+
+ mDirectoryLock = aLock;
+
+ nsresult rv = DirectoryOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ // The caller holds a strong reference to us, no need for a self reference
+ // before calling Run().
+
+ mState = State::SendingResults;
+ MOZ_ALWAYS_SUCCEEDS(Run());
+
+ return;
+ }
+}
+
+void
+FactoryOp::DirectoryLockFailed()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::DirectoryOpenPending);
+ MOZ_ASSERT(!mDirectoryLock);
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ IDB_REPORT_INTERNAL_ERR();
+ mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ // The caller holds a strong reference to us, no need for a self reference
+ // before calling Run().
+
+ mState = State::SendingResults;
+ MOZ_ALWAYS_SUCCEEDS(Run());
+}
+
+void
+FactoryOp::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+
+ NoteActorDestroyed();
+}
+
+bool
+FactoryOp::RecvPermissionRetry()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+ MOZ_ASSERT(mState == State::PermissionChallenge);
+
+ mContentParent = BackgroundParent::GetContentParent(Manager()->Manager());
+
+ mState = State::PermissionRetry;
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ return true;
+}
+
+OpenDatabaseOp::OpenDatabaseOp(Factory* aFactory,
+ already_AddRefed<ContentParent> aContentParent,
+ const CommonFactoryRequestParams& aParams)
+ : FactoryOp(aFactory, Move(aContentParent), aParams, /* aDeleting */ false)
+ , mMetadata(new FullDatabaseMetadata(aParams.metadata()))
+ , mRequestedVersion(aParams.metadata().version())
+ , mVersionChangeOp(nullptr)
+ , mTelemetryId(0)
+{
+ if (mContentParent) {
+ // This is a little scary but it looks safe to call this off the main thread
+ // for now.
+ mOptionalContentParentId = Some(mContentParent->ChildID());
+ }
+}
+
+void
+OpenDatabaseOp::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnOwningThread();
+
+ FactoryOp::ActorDestroy(aWhy);
+
+ if (mVersionChangeOp) {
+ mVersionChangeOp->NoteActorDestroyed();
+ }
+}
+
+nsresult
+OpenDatabaseOp::DatabaseOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::DatabaseOpenPending);
+
+ nsresult rv = SendToIOThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+OpenDatabaseOp::DoDatabaseWork()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mState == State::DatabaseWorkOpen);
+
+ PROFILER_LABEL("IndexedDB",
+ "OpenDatabaseOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+ !OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ const nsString& databaseName = mCommonParams.metadata().name();
+ PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ nsCOMPtr<nsIFile> dbDirectory;
+
+ nsresult rv =
+ quotaManager->EnsureOriginIsInitialized(persistenceType,
+ mSuffix,
+ mGroup,
+ mOrigin,
+ mIsApp,
+ getter_AddRefs(dbDirectory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = dbDirectory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = dbDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+#ifdef DEBUG
+ else {
+ bool isDirectory;
+ MOZ_ASSERT(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory)));
+ MOZ_ASSERT(isDirectory);
+ }
+#endif
+
+ nsAutoString filename;
+ GetDatabaseFilename(databaseName, filename);
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = dbDirectory->Clone(getter_AddRefs(dbFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mTelemetryId = TelemetryIdForFile(dbFile);
+
+#ifdef DEBUG
+ nsString databaseFilePath;
+ rv = dbFile->GetPath(databaseFilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(databaseFilePath == mDatabaseFilePath);
+#endif
+
+ nsCOMPtr<nsIFile> fmDirectory;
+ rv = dbDirectory->Clone(getter_AddRefs(fmDirectory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ const NS_ConvertASCIItoUTF16 filesSuffix(kFileManagerDirectoryNameSuffix);
+
+ rv = fmDirectory->Append(filename + filesSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ rv = CreateStorageConnection(dbFile,
+ fmDirectory,
+ databaseName,
+ persistenceType,
+ mGroup,
+ mOrigin,
+ mTelemetryId,
+ getter_AddRefs(connection));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ AutoSetProgressHandler asph;
+ rv = asph.Register(connection, this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = LoadDatabaseInformation(connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(mMetadata->mNextObjectStoreId > mMetadata->mObjectStores.Count());
+ MOZ_ASSERT(mMetadata->mNextIndexId > 0);
+
+ // See if we need to do a versionchange transaction
+
+ // Optional version semantics.
+ if (!mRequestedVersion) {
+ // If the requested version was not specified and the database was created,
+ // treat it as if version 1 were requested.
+ if (mMetadata->mCommonMetadata.version() == 0) {
+ mRequestedVersion = 1;
+ } else {
+ // Otherwise, treat it as if the current version were requested.
+ mRequestedVersion = mMetadata->mCommonMetadata.version();
+ }
+ }
+
+ if (NS_WARN_IF(mMetadata->mCommonMetadata.version() > mRequestedVersion)) {
+ return NS_ERROR_DOM_INDEXEDDB_VERSION_ERR;
+ }
+
+ IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
+ MOZ_ASSERT(mgr);
+
+ RefPtr<FileManager> fileManager =
+ mgr->GetFileManager(persistenceType, mOrigin, databaseName);
+ if (!fileManager) {
+ fileManager = new FileManager(persistenceType,
+ mGroup,
+ mOrigin,
+ mIsApp,
+ databaseName,
+ mEnforcingQuota);
+
+ rv = fileManager->Init(fmDirectory, connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mgr->AddFileManager(fileManager);
+ }
+
+ mFileManager = fileManager.forget();
+
+ // Must set mState before dispatching otherwise we will race with the owning
+ // thread.
+ mState = (mMetadata->mCommonMetadata.version() == mRequestedVersion) ?
+ State::SendingResults :
+ State::BeginVersionChange;
+
+ rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+OpenDatabaseOp::LoadDatabaseInformation(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(mMetadata);
+
+ // Load version information.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT name, origin, version "
+ "FROM database"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!hasResult)) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ nsString databaseName;
+ rv = stmt->GetString(0, databaseName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(mCommonParams.metadata().name() != databaseName)) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ nsCString origin;
+ rv = stmt->GetUTF8String(1, origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mOrigin != origin) {
+ NS_WARNING("Origins don't match!");
+ }
+
+ int64_t version;
+ rv = stmt->GetInt64(2, &version);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mMetadata->mCommonMetadata.version() = uint64_t(version);
+
+ ObjectStoreTable& objectStores = mMetadata->mObjectStores;
+
+ // Load object store names and ids.
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, auto_increment, name, key_path "
+ "FROM object_store"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ Maybe<nsTHashtable<nsUint64HashKey>> usedIds;
+ Maybe<nsTHashtable<nsStringHashKey>> usedNames;
+
+ int64_t lastObjectStoreId = 0;
+
+ while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ int64_t objectStoreId;
+ rv = stmt->GetInt64(0, &objectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!usedIds) {
+ usedIds.emplace();
+ }
+
+ if (NS_WARN_IF(objectStoreId <= 0) ||
+ NS_WARN_IF(usedIds.ref().Contains(objectStoreId))) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (NS_WARN_IF(!usedIds.ref().PutEntry(objectStoreId, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsString name;
+ rv = stmt->GetString(2, name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!usedNames) {
+ usedNames.emplace();
+ }
+
+ if (NS_WARN_IF(usedNames.ref().Contains(name))) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (NS_WARN_IF(!usedNames.ref().PutEntry(name, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr<FullObjectStoreMetadata> metadata = new FullObjectStoreMetadata();
+ metadata->mCommonMetadata.id() = objectStoreId;
+ metadata->mCommonMetadata.name() = name;
+
+ int32_t columnType;
+ rv = stmt->GetTypeOfIndex(3, &columnType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
+ metadata->mCommonMetadata.keyPath() = KeyPath(0);
+ } else {
+ MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_TEXT);
+
+ nsString keyPathSerialization;
+ rv = stmt->GetString(3, keyPathSerialization);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ metadata->mCommonMetadata.keyPath() =
+ KeyPath::DeserializeFromString(keyPathSerialization);
+ if (NS_WARN_IF(!metadata->mCommonMetadata.keyPath().IsValid())) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ }
+
+ int64_t nextAutoIncrementId;
+ rv = stmt->GetInt64(1, &nextAutoIncrementId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ metadata->mCommonMetadata.autoIncrement() = !!nextAutoIncrementId;
+ metadata->mNextAutoIncrementId = nextAutoIncrementId;
+ metadata->mCommittedAutoIncrementId = nextAutoIncrementId;
+
+ if (NS_WARN_IF(!objectStores.Put(objectStoreId, metadata, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ lastObjectStoreId = std::max(lastObjectStoreId, objectStoreId);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ usedIds.reset();
+ usedNames.reset();
+
+ // Load index information
+ rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT "
+ "id, object_store_id, name, key_path, unique_index, multientry, "
+ "locale, is_auto_locale "
+ "FROM object_store_index"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t lastIndexId = 0;
+
+ while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ int64_t objectStoreId;
+ rv = stmt->GetInt64(1, &objectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<FullObjectStoreMetadata> objectStoreMetadata;
+ if (NS_WARN_IF(!objectStores.Get(objectStoreId,
+ getter_AddRefs(objectStoreMetadata)))) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ MOZ_ASSERT(objectStoreMetadata->mCommonMetadata.id() == objectStoreId);
+
+ int64_t indexId;
+ rv = stmt->GetInt64(0, &indexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!usedIds) {
+ usedIds.emplace();
+ }
+
+ if (NS_WARN_IF(indexId <= 0) ||
+ NS_WARN_IF(usedIds.ref().Contains(indexId))) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (NS_WARN_IF(!usedIds.ref().PutEntry(indexId, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsString name;
+ rv = stmt->GetString(2, name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString hashName;
+ hashName.AppendInt(indexId);
+ hashName.Append(':');
+ hashName.Append(name);
+
+ if (!usedNames) {
+ usedNames.emplace();
+ }
+
+ if (NS_WARN_IF(usedNames.ref().Contains(hashName))) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (NS_WARN_IF(!usedNames.ref().PutEntry(hashName, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr<FullIndexMetadata> indexMetadata = new FullIndexMetadata();
+ indexMetadata->mCommonMetadata.id() = indexId;
+ indexMetadata->mCommonMetadata.name() = name;
+
+#ifdef DEBUG
+ {
+ int32_t columnType;
+ rv = stmt->GetTypeOfIndex(3, &columnType);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(columnType != mozIStorageStatement::VALUE_TYPE_NULL);
+ }
+#endif
+
+ nsString keyPathSerialization;
+ rv = stmt->GetString(3, keyPathSerialization);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ indexMetadata->mCommonMetadata.keyPath() =
+ KeyPath::DeserializeFromString(keyPathSerialization);
+ if (NS_WARN_IF(!indexMetadata->mCommonMetadata.keyPath().IsValid())) {
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ int32_t scratch;
+ rv = stmt->GetInt32(4, &scratch);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ indexMetadata->mCommonMetadata.unique() = !!scratch;
+
+ rv = stmt->GetInt32(5, &scratch);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ indexMetadata->mCommonMetadata.multiEntry() = !!scratch;
+
+#ifdef ENABLE_INTL_API
+ const bool localeAware = !stmt->IsNull(6);
+ if (localeAware) {
+ rv = stmt->GetUTF8String(6, indexMetadata->mCommonMetadata.locale());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->GetInt32(7, &scratch);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ indexMetadata->mCommonMetadata.autoLocale() = !!scratch;
+
+ // Update locale-aware indexes if necessary
+ const nsCString& indexedLocale = indexMetadata->mCommonMetadata.locale();
+ const bool& isAutoLocale = indexMetadata->mCommonMetadata.autoLocale();
+ nsCString systemLocale = IndexedDatabaseManager::GetLocale();
+ if (!systemLocale.IsEmpty() &&
+ isAutoLocale &&
+ !indexedLocale.EqualsASCII(systemLocale.get())) {
+ rv = UpdateLocaleAwareIndex(aConnection,
+ indexMetadata->mCommonMetadata,
+ systemLocale);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+#endif
+
+ if (NS_WARN_IF(!objectStoreMetadata->mIndexes.Put(indexId, indexMetadata,
+ fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ lastIndexId = std::max(lastIndexId, indexId);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(lastObjectStoreId == INT64_MAX) ||
+ NS_WARN_IF(lastIndexId == INT64_MAX)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mMetadata->mNextObjectStoreId = lastObjectStoreId + 1;
+ mMetadata->mNextIndexId = lastIndexId + 1;
+
+ return NS_OK;
+}
+
+#ifdef ENABLE_INTL_API
+/* static */
+nsresult
+OpenDatabaseOp::UpdateLocaleAwareIndex(mozIStorageConnection* aConnection,
+ const IndexMetadata& aIndexMetadata,
+ const nsCString& aLocale)
+{
+ nsresult rv;
+
+ nsCString indexTable;
+ if (aIndexMetadata.unique()) {
+ indexTable.AssignLiteral("unique_index_data");
+ }
+ else {
+ indexTable.AssignLiteral("index_data");
+ }
+
+ nsCString readQuery = NS_LITERAL_CSTRING("SELECT value, object_data_key FROM ") +
+ indexTable +
+ NS_LITERAL_CSTRING(" WHERE index_id = :index_id");
+ nsCOMPtr<mozIStorageStatement> readStmt;
+ rv = aConnection->CreateStatement(readQuery, getter_AddRefs(readStmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = readStmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
+ aIndexMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageStatement> writeStmt;
+ bool needCreateWriteQuery = true;
+ bool hasResult;
+ while (NS_SUCCEEDED((rv = readStmt->ExecuteStep(&hasResult))) && hasResult) {
+ if (needCreateWriteQuery) {
+ needCreateWriteQuery = false;
+ nsCString writeQuery = NS_LITERAL_CSTRING("UPDATE ") + indexTable +
+ NS_LITERAL_CSTRING("SET value_locale = :value_locale "
+ "WHERE index_id = :index_id AND "
+ "value = :value AND "
+ "object_data_key = :object_data_key");
+ rv = aConnection->CreateStatement(writeQuery, getter_AddRefs(writeStmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mozStorageStatementScoper scoper(writeStmt);
+ rv = writeStmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
+ aIndexMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ Key oldKey, newSortKey, objectKey;
+ rv = oldKey.SetFromStatement(readStmt, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = oldKey.BindToStatement(writeStmt, NS_LITERAL_CSTRING("value"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = oldKey.ToLocaleBasedKey(newSortKey, aLocale);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = newSortKey.BindToStatement(writeStmt,
+ NS_LITERAL_CSTRING("value_locale"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = objectKey.SetFromStatement(readStmt, 1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = objectKey.BindToStatement(writeStmt,
+ NS_LITERAL_CSTRING("object_data_key"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = writeStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsCString metaQuery = NS_LITERAL_CSTRING("UPDATE object_store_index SET "
+ "locale = :locale WHERE id = :id");
+ nsCOMPtr<mozIStorageStatement> metaStmt;
+ rv = aConnection->CreateStatement(metaQuery, getter_AddRefs(metaStmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString locale;
+ locale.AssignWithConversion(aLocale);
+ rv = metaStmt->BindStringByName(NS_LITERAL_CSTRING("locale"), locale);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = metaStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aIndexMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = metaStmt->Execute();
+ return rv;
+}
+#endif
+
+nsresult
+OpenDatabaseOp::BeginVersionChange()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::BeginVersionChange);
+ MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+ MOZ_ASSERT(mMetadata->mCommonMetadata.version() <= mRequestedVersion);
+ MOZ_ASSERT(!mDatabase);
+ MOZ_ASSERT(!mVersionChangeTransaction);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+ IsActorDestroyed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ EnsureDatabaseActor();
+
+ if (mDatabase->IsInvalidated()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ MOZ_ASSERT(!mDatabase->IsClosed());
+
+ DatabaseActorInfo* info;
+ MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
+
+ MOZ_ASSERT(info->mLiveDatabases.Contains(mDatabase));
+ MOZ_ASSERT(!info->mWaitingFactoryOp);
+ MOZ_ASSERT(info->mMetadata == mMetadata);
+
+ RefPtr<VersionChangeTransaction> transaction =
+ new VersionChangeTransaction(this);
+
+ if (NS_WARN_IF(!transaction->CopyDatabaseMetadata())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ MOZ_ASSERT(info->mMetadata != mMetadata);
+ mMetadata = info->mMetadata;
+
+ NullableVersion newVersion = mRequestedVersion;
+
+ nsresult rv =
+ SendVersionChangeMessages(info,
+ mDatabase,
+ mMetadata->mCommonMetadata.version(),
+ newVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mVersionChangeTransaction.swap(transaction);
+
+ if (mMaybeBlockedDatabases.IsEmpty()) {
+ // We don't need to wait on any databases, just jump to the transaction
+ // pool.
+ WaitForTransactions();
+ return NS_OK;
+ }
+
+ info->mWaitingFactoryOp = this;
+
+ mState = State::WaitingForOtherDatabasesToClose;
+ return NS_OK;
+}
+
+void
+OpenDatabaseOp::NoteDatabaseClosed(Database* aDatabase)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aDatabase);
+ MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose ||
+ mState == State::WaitingForTransactionsToComplete ||
+ mState == State::DatabaseWorkVersionChange);
+
+ if (mState != State::WaitingForOtherDatabasesToClose) {
+ MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+ MOZ_ASSERT(mRequestedVersion >
+ aDatabase->Metadata()->mCommonMetadata.version(),
+ "Must only be closing databases for a previous version!");
+ return;
+ }
+
+ MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
+
+ bool actorDestroyed = IsActorDestroyed() || mDatabase->IsActorDestroyed();
+
+ nsresult rv;
+ if (actorDestroyed) {
+ IDB_REPORT_INTERNAL_ERR();
+ rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ } else {
+ rv = NS_OK;
+ }
+
+ // We are being called with an assuption that mWaitingFactoryOp holds a strong
+ // reference to us.
+ RefPtr<OpenDatabaseOp> kungFuDeathGrip;
+
+ if (mMaybeBlockedDatabases.RemoveElement(aDatabase) &&
+ mMaybeBlockedDatabases.IsEmpty()) {
+ if (actorDestroyed) {
+ DatabaseActorInfo* info;
+ MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
+ MOZ_ASSERT(info->mWaitingFactoryOp == this);
+ kungFuDeathGrip =
+ static_cast<OpenDatabaseOp*>(info->mWaitingFactoryOp.get());
+ info->mWaitingFactoryOp = nullptr;
+ } else {
+ WaitForTransactions();
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ // A strong reference is held in kungFuDeathGrip, so it's safe to call Run()
+ // directly.
+
+ mState = State::SendingResults;
+ MOZ_ALWAYS_SUCCEEDS(Run());
+ }
+}
+
+void
+OpenDatabaseOp::SendBlockedNotification()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
+
+ if (!IsActorDestroyed()) {
+ Unused << SendBlocked(mMetadata->mCommonMetadata.version());
+ }
+}
+
+nsresult
+OpenDatabaseOp::DispatchToWorkThread()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
+ MOZ_ASSERT(mVersionChangeTransaction);
+ MOZ_ASSERT(mVersionChangeTransaction->GetMode() ==
+ IDBTransaction::VERSION_CHANGE);
+ MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+ IsActorDestroyed() ||
+ mDatabase->IsInvalidated()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mState = State::DatabaseWorkVersionChange;
+
+ // Intentionally empty.
+ nsTArray<nsString> objectStoreNames;
+
+ const int64_t loggingSerialNumber =
+ mVersionChangeTransaction->LoggingSerialNumber();
+ const nsID& backgroundChildLoggingId =
+ mVersionChangeTransaction->GetLoggingInfo()->Id();
+
+ if (NS_WARN_IF(!mDatabase->RegisterTransaction(mVersionChangeTransaction))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!gConnectionPool) {
+ gConnectionPool = new ConnectionPool();
+ }
+
+ RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
+
+ uint64_t transactionId =
+ versionChangeOp->StartOnConnectionPool(
+ backgroundChildLoggingId,
+ mVersionChangeTransaction->DatabaseId(),
+ loggingSerialNumber,
+ objectStoreNames,
+ /* aIsWriteTransaction */ true);
+
+ mVersionChangeOp = versionChangeOp;
+
+ mVersionChangeTransaction->NoteActiveRequest();
+ mVersionChangeTransaction->SetActive(transactionId);
+
+ return NS_OK;
+}
+
+nsresult
+OpenDatabaseOp::SendUpgradeNeeded()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::DatabaseWorkVersionChange);
+ MOZ_ASSERT(mVersionChangeTransaction);
+ MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+ MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+ MOZ_ASSERT_IF(!IsActorDestroyed(), mDatabase);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+ IsActorDestroyed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ RefPtr<VersionChangeTransaction> transaction;
+ mVersionChangeTransaction.swap(transaction);
+
+ nsresult rv = EnsureDatabaseActorIsAlive();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Transfer ownership to IPDL.
+ transaction->SetActorAlive();
+
+ if (!mDatabase->SendPBackgroundIDBVersionChangeTransactionConstructor(
+ transaction,
+ mMetadata->mCommonMetadata.version(),
+ mRequestedVersion,
+ mMetadata->mNextObjectStoreId,
+ mMetadata->mNextIndexId)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+}
+
+void
+OpenDatabaseOp::SendResults()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::SendingResults);
+ MOZ_ASSERT_IF(NS_SUCCEEDED(mResultCode), mMaybeBlockedDatabases.IsEmpty());
+ MOZ_ASSERT_IF(NS_SUCCEEDED(mResultCode), !mVersionChangeTransaction);
+
+ mMaybeBlockedDatabases.Clear();
+
+ DatabaseActorInfo* info;
+ if (gLiveDatabaseHashtable &&
+ gLiveDatabaseHashtable->Get(mDatabaseId, &info) &&
+ info->mWaitingFactoryOp) {
+ MOZ_ASSERT(info->mWaitingFactoryOp == this);
+ // SendResults() should only be called by Run() and Run() should only be
+ // called if there's a strong reference to the object that can't be cleared
+ // here, so it's safe to clear mWaitingFactoryOp without adding additional
+ // strong reference.
+ info->mWaitingFactoryOp = nullptr;
+ }
+
+ if (mVersionChangeTransaction) {
+ MOZ_ASSERT(NS_FAILED(mResultCode));
+
+ mVersionChangeTransaction->Abort(mResultCode, /* aForce */ true);
+ mVersionChangeTransaction = nullptr;
+ }
+
+ if (IsActorDestroyed()) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+ } else {
+ FactoryRequestResponse response;
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ // If we just successfully completed a versionchange operation then we
+ // need to update the version in our metadata.
+ mMetadata->mCommonMetadata.version() = mRequestedVersion;
+
+ nsresult rv = EnsureDatabaseActorIsAlive();
+ if (NS_SUCCEEDED(rv)) {
+ // We successfully opened a database so use its actor as the success
+ // result for this request.
+ OpenDatabaseRequestResponse openResponse;
+ openResponse.databaseParent() = mDatabase;
+ response = openResponse;
+ } else {
+ response = ClampResultCode(rv);
+#ifdef DEBUG
+ mResultCode = response.get_nsresult();
+#endif
+ }
+ } else {
+#ifdef DEBUG
+ // If something failed then our metadata pointer is now bad. No one should
+ // ever touch it again though so just null it out in DEBUG builds to make
+ // sure we find such cases.
+ mMetadata = nullptr;
+#endif
+ response = ClampResultCode(mResultCode);
+ }
+
+ Unused <<
+ PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
+ }
+
+ if (mDatabase) {
+ MOZ_ASSERT(!mDirectoryLock);
+
+ if (NS_FAILED(mResultCode)) {
+ mDatabase->Invalidate();
+ }
+
+ // Make sure to release the database on this thread.
+ mDatabase = nullptr;
+ } else if (mDirectoryLock) {
+ nsCOMPtr<nsIRunnable> callback =
+ NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
+
+ RefPtr<WaitForTransactionsHelper> helper =
+ new WaitForTransactionsHelper(mDatabaseId, callback);
+ helper->WaitForTransactions();
+ }
+
+ FinishSendResults();
+}
+
+void
+OpenDatabaseOp::ConnectionClosedCallback()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NS_FAILED(mResultCode));
+ MOZ_ASSERT(mDirectoryLock);
+
+ mDirectoryLock = nullptr;
+}
+
+void
+OpenDatabaseOp::EnsureDatabaseActor()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::BeginVersionChange ||
+ mState == State::DatabaseWorkVersionChange ||
+ mState == State::SendingResults);
+ MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+ MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ if (mDatabase) {
+ return;
+ }
+
+ MOZ_ASSERT(mMetadata->mDatabaseId.IsEmpty());
+ mMetadata->mDatabaseId = mDatabaseId;
+
+ MOZ_ASSERT(mMetadata->mFilePath.IsEmpty());
+ mMetadata->mFilePath = mDatabaseFilePath;
+
+ DatabaseActorInfo* info;
+ if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) {
+ AssertMetadataConsistency(info->mMetadata);
+ mMetadata = info->mMetadata;
+ }
+
+ auto factory = static_cast<Factory*>(Manager());
+
+ mDatabase = new Database(factory,
+ mCommonParams.principalInfo(),
+ mOptionalContentParentId,
+ mGroup,
+ mOrigin,
+ mTelemetryId,
+ mMetadata,
+ mFileManager,
+ mDirectoryLock.forget(),
+ mFileHandleDisabled,
+ mChromeWriteAccessAllowed);
+
+ if (info) {
+ info->mLiveDatabases.AppendElement(mDatabase);
+ } else {
+ info = new DatabaseActorInfo(mMetadata, mDatabase);
+ gLiveDatabaseHashtable->Put(mDatabaseId, info);
+ }
+
+ // Balanced in Database::CleanupMetadata().
+ IncreaseBusyCount();
+}
+
+nsresult
+OpenDatabaseOp::EnsureDatabaseActorIsAlive()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::DatabaseWorkVersionChange ||
+ mState == State::SendingResults);
+ MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ EnsureDatabaseActor();
+
+ if (mDatabase->IsActorAlive()) {
+ return NS_OK;
+ }
+
+ auto factory = static_cast<Factory*>(Manager());
+
+ DatabaseSpec spec;
+ MetadataToSpec(spec);
+
+ // Transfer ownership to IPDL.
+ mDatabase->SetActorAlive();
+
+ if (!factory->SendPBackgroundIDBDatabaseConstructor(mDatabase, spec, this)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+}
+
+void
+OpenDatabaseOp::MetadataToSpec(DatabaseSpec& aSpec)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mMetadata);
+
+ aSpec.metadata() = mMetadata->mCommonMetadata;
+
+ for (auto objectStoreIter = mMetadata->mObjectStores.ConstIter();
+ !objectStoreIter.Done();
+ objectStoreIter.Next()) {
+ FullObjectStoreMetadata* metadata = objectStoreIter.UserData();
+ MOZ_ASSERT(objectStoreIter.Key());
+ MOZ_ASSERT(metadata);
+
+ // XXX This should really be fallible...
+ ObjectStoreSpec* objectStoreSpec = aSpec.objectStores().AppendElement();
+ objectStoreSpec->metadata() = metadata->mCommonMetadata;
+
+ for (auto indexIter = metadata->mIndexes.Iter();
+ !indexIter.Done();
+ indexIter.Next()) {
+ FullIndexMetadata* indexMetadata = indexIter.UserData();
+ MOZ_ASSERT(indexIter.Key());
+ MOZ_ASSERT(indexMetadata);
+
+ // XXX This should really be fallible...
+ IndexMetadata* metadata = objectStoreSpec->indexes().AppendElement();
+ *metadata = indexMetadata->mCommonMetadata;
+ }
+ }
+}
+
+#ifdef DEBUG
+
+void
+OpenDatabaseOp::AssertMetadataConsistency(const FullDatabaseMetadata* aMetadata)
+{
+ AssertIsOnBackgroundThread();
+
+ const FullDatabaseMetadata* thisDB = mMetadata;
+ const FullDatabaseMetadata* otherDB = aMetadata;
+
+ MOZ_ASSERT(thisDB);
+ MOZ_ASSERT(otherDB);
+ MOZ_ASSERT(thisDB != otherDB);
+
+ MOZ_ASSERT(thisDB->mCommonMetadata.name() == otherDB->mCommonMetadata.name());
+ MOZ_ASSERT(thisDB->mCommonMetadata.version() ==
+ otherDB->mCommonMetadata.version());
+ MOZ_ASSERT(thisDB->mCommonMetadata.persistenceType() ==
+ otherDB->mCommonMetadata.persistenceType());
+ MOZ_ASSERT(thisDB->mDatabaseId == otherDB->mDatabaseId);
+ MOZ_ASSERT(thisDB->mFilePath == otherDB->mFilePath);
+
+ // |thisDB| reflects the latest objectStore and index ids that have committed
+ // to disk. The in-memory metadata |otherDB| keeps track of objectStores and
+ // indexes that were created and then removed as well, so the next ids for
+ // |otherDB| may be higher than for |thisDB|.
+ MOZ_ASSERT(thisDB->mNextObjectStoreId <= otherDB->mNextObjectStoreId);
+ MOZ_ASSERT(thisDB->mNextIndexId <= otherDB->mNextIndexId);
+
+ MOZ_ASSERT(thisDB->mObjectStores.Count() == otherDB->mObjectStores.Count());
+
+ for (auto objectStoreIter = thisDB->mObjectStores.ConstIter();
+ !objectStoreIter.Done();
+ objectStoreIter.Next()) {
+ FullObjectStoreMetadata* thisObjectStore = objectStoreIter.UserData();
+ MOZ_ASSERT(thisObjectStore);
+ MOZ_ASSERT(!thisObjectStore->mDeleted);
+
+ auto* otherObjectStore =
+ MetadataNameOrIdMatcher<FullObjectStoreMetadata>::Match(
+ otherDB->mObjectStores, thisObjectStore->mCommonMetadata.id());
+ MOZ_ASSERT(otherObjectStore);
+
+ MOZ_ASSERT(thisObjectStore != otherObjectStore);
+
+ MOZ_ASSERT(thisObjectStore->mCommonMetadata.id() ==
+ otherObjectStore->mCommonMetadata.id());
+ MOZ_ASSERT(thisObjectStore->mCommonMetadata.name() ==
+ otherObjectStore->mCommonMetadata.name());
+ MOZ_ASSERT(thisObjectStore->mCommonMetadata.autoIncrement() ==
+ otherObjectStore->mCommonMetadata.autoIncrement());
+ MOZ_ASSERT(thisObjectStore->mCommonMetadata.keyPath() ==
+ otherObjectStore->mCommonMetadata.keyPath());
+ // mNextAutoIncrementId and mCommittedAutoIncrementId may be modified
+ // concurrently with this OpenOp, so it is not possible to assert equality
+ // here. It's also possible that we've written the new ids to disk but not
+ // yet updated the in-memory count.
+ MOZ_ASSERT(thisObjectStore->mNextAutoIncrementId <=
+ otherObjectStore->mNextAutoIncrementId);
+ MOZ_ASSERT(thisObjectStore->mCommittedAutoIncrementId <=
+ otherObjectStore->mCommittedAutoIncrementId ||
+ thisObjectStore->mCommittedAutoIncrementId ==
+ otherObjectStore->mNextAutoIncrementId);
+ MOZ_ASSERT(!otherObjectStore->mDeleted);
+
+ MOZ_ASSERT(thisObjectStore->mIndexes.Count() ==
+ otherObjectStore->mIndexes.Count());
+
+ for (auto indexIter = thisObjectStore->mIndexes.Iter();
+ !indexIter.Done();
+ indexIter.Next()) {
+ FullIndexMetadata* thisIndex = indexIter.UserData();
+ MOZ_ASSERT(thisIndex);
+ MOZ_ASSERT(!thisIndex->mDeleted);
+
+ auto* otherIndex =
+ MetadataNameOrIdMatcher<FullIndexMetadata>::
+ Match(otherObjectStore->mIndexes, thisIndex->mCommonMetadata.id());
+ MOZ_ASSERT(otherIndex);
+
+ MOZ_ASSERT(thisIndex != otherIndex);
+
+ MOZ_ASSERT(thisIndex->mCommonMetadata.id() ==
+ otherIndex->mCommonMetadata.id());
+ MOZ_ASSERT(thisIndex->mCommonMetadata.name() ==
+ otherIndex->mCommonMetadata.name());
+ MOZ_ASSERT(thisIndex->mCommonMetadata.keyPath() ==
+ otherIndex->mCommonMetadata.keyPath());
+ MOZ_ASSERT(thisIndex->mCommonMetadata.unique() ==
+ otherIndex->mCommonMetadata.unique());
+ MOZ_ASSERT(thisIndex->mCommonMetadata.multiEntry() ==
+ otherIndex->mCommonMetadata.multiEntry());
+ MOZ_ASSERT(!otherIndex->mDeleted);
+ }
+ }
+}
+
+#endif // DEBUG
+
+nsresult
+OpenDatabaseOp::
+VersionChangeOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mOpenDatabaseOp);
+ MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+ !OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ PROFILER_LABEL("IndexedDB",
+ "OpenDatabaseOp::VersionChangeOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld]: "
+ "Beginning database work",
+ "IndexedDB %s: P T[%lld]: DB Start",
+ IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
+ mLoggingSerialNumber);
+
+ Transaction()->SetActiveOnConnectionThread();
+
+ nsresult rv = aConnection->BeginWriteTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ DatabaseConnection::CachedStatement updateStmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "UPDATE database "
+ "SET version = :version;"),
+ &updateStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("version"),
+ int64_t(mRequestedVersion));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = updateStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+OpenDatabaseOp::
+VersionChangeOp::SendSuccessResult()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mOpenDatabaseOp);
+ MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
+ MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
+
+ nsresult rv = mOpenDatabaseOp->SendUpgradeNeeded();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+OpenDatabaseOp::
+VersionChangeOp::SendFailureResult(nsresult aResultCode)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mOpenDatabaseOp);
+ MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
+ MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
+
+ mOpenDatabaseOp->SetFailureCode(aResultCode);
+ mOpenDatabaseOp->mState = State::SendingResults;
+
+ MOZ_ALWAYS_SUCCEEDS(mOpenDatabaseOp->Run());
+
+ return false;
+}
+
+void
+OpenDatabaseOp::
+VersionChangeOp::Cleanup()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mOpenDatabaseOp);
+ MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
+
+ mOpenDatabaseOp->mVersionChangeOp = nullptr;
+ mOpenDatabaseOp = nullptr;
+
+#ifdef DEBUG
+ // A bit hacky but the VersionChangeOp is not generated in response to a
+ // child request like most other database operations. Do this to make our
+ // assertions happy.
+ NoteActorDestroyed();
+#endif
+
+ TransactionDatabaseOperationBase::Cleanup();
+}
+
+void
+DeleteDatabaseOp::LoadPreviousVersion(nsIFile* aDatabaseFile)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDatabaseFile);
+ MOZ_ASSERT(mState == State::DatabaseWorkOpen);
+ MOZ_ASSERT(!mPreviousVersion);
+
+ PROFILER_LABEL("IndexedDB",
+ "DeleteDatabaseOp::LoadPreviousVersion",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv;
+
+ nsCOMPtr<mozIStorageService> ss =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ rv = OpenDatabaseAndHandleBusy(ss, aDatabaseFile, getter_AddRefs(connection));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ connection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT name "
+ "FROM database"
+ ), getter_AddRefs(stmt)));
+
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
+
+ nsString databaseName;
+ MOZ_ALWAYS_SUCCEEDS(stmt->GetString(0, databaseName));
+
+ MOZ_ASSERT(mCommonParams.metadata().name() == databaseName);
+ }
+#endif
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT version "
+ "FROM database"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (NS_WARN_IF(!hasResult)) {
+ return;
+ }
+
+ int64_t version;
+ rv = stmt->GetInt64(0, &version);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ mPreviousVersion = uint64_t(version);
+}
+
+nsresult
+DeleteDatabaseOp::DatabaseOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::DatabaseOpenPending);
+
+ // Swap this to the stack now to ensure that we release it on this thread.
+ RefPtr<ContentParent> contentParent;
+ mContentParent.swap(contentParent);
+
+ nsresult rv = SendToIOThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DeleteDatabaseOp::DoDatabaseWork()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mState == State::DatabaseWorkOpen);
+
+ PROFILER_LABEL("IndexedDB",
+ "DeleteDatabaseOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+ !OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ const nsString& databaseName = mCommonParams.metadata().name();
+ PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = quotaManager->GetDirectoryForOrigin(persistenceType,
+ mOrigin,
+ getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = directory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = directory->GetPath(mDatabaseDirectoryPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString filename;
+ GetDatabaseFilename(databaseName, filename);
+
+ mDatabaseFilenameBase = filename;
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = directory->Clone(getter_AddRefs(dbFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ nsString databaseFilePath;
+ rv = dbFile->GetPath(databaseFilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(databaseFilePath == mDatabaseFilePath);
+#endif
+
+ bool exists;
+ rv = dbFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ // Parts of this function may fail but that shouldn't prevent us from
+ // deleting the file eventually.
+ LoadPreviousVersion(dbFile);
+
+ mState = State::BeginVersionChange;
+ } else {
+ mState = State::SendingResults;
+ }
+
+ rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DeleteDatabaseOp::BeginVersionChange()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::BeginVersionChange);
+ MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+ IsActorDestroyed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ DatabaseActorInfo* info;
+ if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) {
+ MOZ_ASSERT(!info->mWaitingFactoryOp);
+
+ NullableVersion newVersion = null_t();
+
+ nsresult rv =
+ SendVersionChangeMessages(info, nullptr, mPreviousVersion, newVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mMaybeBlockedDatabases.IsEmpty()) {
+ info->mWaitingFactoryOp = this;
+
+ mState = State::WaitingForOtherDatabasesToClose;
+ return NS_OK;
+ }
+ }
+
+ // No other databases need to be notified, just make sure that all
+ // transactions are complete.
+ WaitForTransactions();
+ return NS_OK;
+}
+
+nsresult
+DeleteDatabaseOp::DispatchToWorkThread()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
+ MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+ IsActorDestroyed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mState = State::DatabaseWorkVersionChange;
+
+ RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ nsresult rv =
+ quotaManager->IOThread()->Dispatch(versionChangeOp.forget(),
+ NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+}
+
+void
+DeleteDatabaseOp::NoteDatabaseClosed(Database* aDatabase)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
+ MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
+
+ bool actorDestroyed = IsActorDestroyed();
+
+ nsresult rv;
+ if (actorDestroyed) {
+ IDB_REPORT_INTERNAL_ERR();
+ rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ } else {
+ rv = NS_OK;
+ }
+
+ // We are being called with an assuption that mWaitingFactoryOp holds a strong
+ // reference to us.
+ RefPtr<OpenDatabaseOp> kungFuDeathGrip;
+
+ if (mMaybeBlockedDatabases.RemoveElement(aDatabase) &&
+ mMaybeBlockedDatabases.IsEmpty()) {
+ if (actorDestroyed) {
+ DatabaseActorInfo* info;
+ MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
+ MOZ_ASSERT(info->mWaitingFactoryOp == this);
+ kungFuDeathGrip =
+ static_cast<OpenDatabaseOp*>(info->mWaitingFactoryOp.get());
+ info->mWaitingFactoryOp = nullptr;
+ } else {
+ WaitForTransactions();
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ // A strong reference is held in kungFuDeathGrip, so it's safe to call Run()
+ // directly.
+
+ mState = State::SendingResults;
+ MOZ_ALWAYS_SUCCEEDS(Run());
+ }
+}
+
+void
+DeleteDatabaseOp::SendBlockedNotification()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
+
+ if (!IsActorDestroyed()) {
+ Unused << SendBlocked(mPreviousVersion);
+ }
+}
+
+void
+DeleteDatabaseOp::SendResults()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::SendingResults);
+
+ if (!IsActorDestroyed()) {
+ FactoryRequestResponse response;
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ response = DeleteDatabaseRequestResponse(mPreviousVersion);
+ } else {
+ response = ClampResultCode(mResultCode);
+ }
+
+ Unused <<
+ PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
+ }
+
+ mDirectoryLock = nullptr;
+
+ FinishSendResults();
+}
+
+nsresult
+DeleteDatabaseOp::
+VersionChangeOp::DeleteFile(nsIFile* aDirectory,
+ const nsAString& aFilename,
+ QuotaManager* aQuotaManager)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(!aFilename.IsEmpty());
+ MOZ_ASSERT_IF(aQuotaManager, mDeleteDatabaseOp->mEnforcingQuota);
+
+ MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
+
+ PROFILER_LABEL("IndexedDB",
+ "DeleteDatabaseOp::VersionChangeOp::DeleteFile",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aDirectory->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->Append(aFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t fileSize;
+
+ if (aQuotaManager) {
+ rv = file->GetFileSize(&fileSize);
+ if (rv == NS_ERROR_FILE_NOT_FOUND ||
+ rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(fileSize >= 0);
+ }
+
+ rv = file->Remove(false);
+ if (rv == NS_ERROR_FILE_NOT_FOUND ||
+ rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aQuotaManager && fileSize > 0) {
+ const PersistenceType& persistenceType =
+ mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
+
+ aQuotaManager->DecreaseUsageForOrigin(persistenceType,
+ mDeleteDatabaseOp->mGroup,
+ mDeleteDatabaseOp->mOrigin,
+ fileSize);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DeleteDatabaseOp::
+VersionChangeOp::RunOnIOThread()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
+
+ PROFILER_LABEL("IndexedDB",
+ "DeleteDatabaseOp::VersionChangeOp::RunOnIOThread",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+ !OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ const PersistenceType& persistenceType =
+ mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
+
+ QuotaManager* quotaManager =
+ mDeleteDatabaseOp->mEnforcingQuota ?
+ QuotaManager::Get() :
+ nullptr;
+
+ MOZ_ASSERT_IF(mDeleteDatabaseOp->mEnforcingQuota, quotaManager);
+
+ nsCOMPtr<nsIFile> directory =
+ GetFileForPath(mDeleteDatabaseOp->mDatabaseDirectoryPath);
+ if (NS_WARN_IF(!directory)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ // The database file counts towards quota.
+ nsAutoString filename =
+ mDeleteDatabaseOp->mDatabaseFilenameBase + NS_LITERAL_STRING(".sqlite");
+
+ nsresult rv = DeleteFile(directory, filename, quotaManager);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // .sqlite-journal files don't count towards quota.
+ const NS_ConvertASCIItoUTF16 journalSuffix(
+ kSQLiteJournalSuffix,
+ LiteralStringLength(kSQLiteJournalSuffix));
+
+ filename = mDeleteDatabaseOp->mDatabaseFilenameBase + journalSuffix;
+
+ rv = DeleteFile(directory, filename, /* doesn't count */ nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // .sqlite-shm files don't count towards quota.
+ const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
+ LiteralStringLength(kSQLiteSHMSuffix));
+
+ filename = mDeleteDatabaseOp->mDatabaseFilenameBase + shmSuffix;
+
+ rv = DeleteFile(directory, filename, /* doesn't count */ nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // .sqlite-wal files do count towards quota.
+ const NS_ConvertASCIItoUTF16 walSuffix(kSQLiteWALSuffix,
+ LiteralStringLength(kSQLiteWALSuffix));
+
+ filename = mDeleteDatabaseOp->mDatabaseFilenameBase + walSuffix;
+
+ rv = DeleteFile(directory, filename, quotaManager);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> fmDirectory;
+ rv = directory->Clone(getter_AddRefs(fmDirectory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The files directory counts towards quota.
+ const NS_ConvertASCIItoUTF16 filesSuffix(
+ kFileManagerDirectoryNameSuffix,
+ LiteralStringLength(kFileManagerDirectoryNameSuffix));
+
+ rv = fmDirectory->Append(mDeleteDatabaseOp->mDatabaseFilenameBase +
+ filesSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = fmDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ bool isDirectory;
+ rv = fmDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ uint64_t usage = 0;
+
+ if (mDeleteDatabaseOp->mEnforcingQuota) {
+ rv = FileManager::GetUsage(fmDirectory, &usage);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = fmDirectory->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // We may have deleted some files, check if we can and update quota
+ // information before returning the error.
+ if (mDeleteDatabaseOp->mEnforcingQuota) {
+ uint64_t newUsage;
+ if (NS_SUCCEEDED(FileManager::GetUsage(fmDirectory, &newUsage))) {
+ MOZ_ASSERT(newUsage <= usage);
+ usage = usage - newUsage;
+ }
+ }
+ }
+
+ if (mDeleteDatabaseOp->mEnforcingQuota && usage) {
+ quotaManager->DecreaseUsageForOrigin(persistenceType,
+ mDeleteDatabaseOp->mGroup,
+ mDeleteDatabaseOp->mOrigin,
+ usage);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
+ MOZ_ASSERT(mgr);
+
+ const nsString& databaseName =
+ mDeleteDatabaseOp->mCommonParams.metadata().name();
+
+ mgr->InvalidateFileManager(persistenceType,
+ mDeleteDatabaseOp->mOrigin,
+ databaseName);
+
+ rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+DeleteDatabaseOp::
+VersionChangeOp::RunOnOwningThread()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
+
+ RefPtr<DeleteDatabaseOp> deleteOp;
+ mDeleteDatabaseOp.swap(deleteOp);
+
+ if (deleteOp->IsActorDestroyed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ deleteOp->SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ } else {
+ DatabaseActorInfo* info;
+ if (gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId, &info) &&
+ info->mWaitingFactoryOp) {
+ MOZ_ASSERT(info->mWaitingFactoryOp == deleteOp);
+ info->mWaitingFactoryOp = nullptr;
+ }
+
+ if (NS_FAILED(mResultCode)) {
+ if (NS_SUCCEEDED(deleteOp->ResultCode())) {
+ deleteOp->SetFailureCode(mResultCode);
+ }
+ } else {
+ // Inform all the other databases that they are now invalidated. That
+ // should remove the previous metadata from our table.
+ if (info) {
+ MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
+
+ FallibleTArray<Database*> liveDatabases;
+ if (NS_WARN_IF(!liveDatabases.AppendElements(info->mLiveDatabases,
+ fallible))) {
+ deleteOp->SetFailureCode(NS_ERROR_OUT_OF_MEMORY);
+ } else {
+#ifdef DEBUG
+ // The code below should result in the deletion of |info|. Set to null
+ // here to make sure we find invalid uses later.
+ info = nullptr;
+#endif
+ for (uint32_t count = liveDatabases.Length(), index = 0;
+ index < count;
+ index++) {
+ RefPtr<Database> database = liveDatabases[index];
+ database->Invalidate();
+ }
+
+ MOZ_ASSERT(!gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId));
+ }
+ }
+ }
+ }
+
+ // We hold a strong ref to the deleteOp, so it's safe to call Run() directly.
+
+ deleteOp->mState = State::SendingResults;
+ MOZ_ALWAYS_SUCCEEDS(deleteOp->Run());
+
+#ifdef DEBUG
+ // A bit hacky but the DeleteDatabaseOp::VersionChangeOp is not really a
+ // normal database operation that is tied to an actor. Do this to make our
+ // assertions happy.
+ NoteActorDestroyed();
+#endif
+}
+
+nsresult
+DeleteDatabaseOp::
+VersionChangeOp::Run()
+{
+ nsresult rv;
+
+ if (IsOnIOThread()) {
+ rv = RunOnIOThread();
+ } else {
+ RunOnOwningThread();
+ rv = NS_OK;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+ }
+
+ return NS_OK;
+}
+
+TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
+ TransactionBase* aTransaction)
+ : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
+ aTransaction->GetLoggingInfo()->NextRequestSN())
+ , mTransaction(aTransaction)
+ , mTransactionLoggingSerialNumber(aTransaction->LoggingSerialNumber())
+ , mInternalState(InternalState::Initial)
+ , mTransactionIsAborted(aTransaction->IsAborted())
+{
+ MOZ_ASSERT(aTransaction);
+ MOZ_ASSERT(LoggingSerialNumber());
+}
+
+TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
+ TransactionBase* aTransaction,
+ uint64_t aLoggingSerialNumber)
+ : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
+ aLoggingSerialNumber)
+ , mTransaction(aTransaction)
+ , mTransactionLoggingSerialNumber(aTransaction->LoggingSerialNumber())
+ , mInternalState(InternalState::Initial)
+ , mTransactionIsAborted(aTransaction->IsAborted())
+{
+ MOZ_ASSERT(aTransaction);
+}
+
+TransactionDatabaseOperationBase::~TransactionDatabaseOperationBase()
+{
+ MOZ_ASSERT(mInternalState == InternalState::Completed);
+ MOZ_ASSERT(!mTransaction,
+ "TransactionDatabaseOperationBase::Cleanup() was not called by a "
+ "subclass!");
+}
+
+#ifdef DEBUG
+
+void
+TransactionDatabaseOperationBase::AssertIsOnConnectionThread() const
+{
+ MOZ_ASSERT(mTransaction);
+ mTransaction->AssertIsOnConnectionThread();
+}
+
+#endif // DEBUG
+
+uint64_t
+TransactionDatabaseOperationBase::StartOnConnectionPool(
+ const nsID& aBackgroundChildLoggingId,
+ const nsACString& aDatabaseId,
+ int64_t aLoggingSerialNumber,
+ const nsTArray<nsString>& aObjectStoreNames,
+ bool aIsWriteTransaction)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mInternalState == InternalState::Initial);
+
+ // Must set mInternalState before dispatching otherwise we will race with the
+ // connection thread.
+ mInternalState = InternalState::DatabaseWork;
+
+ return gConnectionPool->Start(aBackgroundChildLoggingId,
+ aDatabaseId,
+ aLoggingSerialNumber,
+ aObjectStoreNames,
+ aIsWriteTransaction,
+ this);
+}
+
+void
+TransactionDatabaseOperationBase::DispatchToConnectionPool()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mInternalState == InternalState::Initial);
+
+ Unused << this->Run();
+}
+
+void
+TransactionDatabaseOperationBase::RunOnConnectionThread()
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(mInternalState == InternalState::DatabaseWork);
+ MOZ_ASSERT(mTransaction);
+ MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+
+ PROFILER_LABEL("IndexedDB",
+ "TransactionDatabaseOperationBase::RunOnConnectionThread",
+ js::ProfileEntry::Category::STORAGE);
+
+ // There are several cases where we don't actually have to to any work here.
+
+ if (mTransactionIsAborted || mTransaction->IsInvalidatedOnAnyThread()) {
+ // This transaction is already set to be aborted or invalidated.
+ mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
+ } else if (!OperationMayProceed()) {
+ // The operation was canceled in some way, likely because the child process
+ // has crashed.
+ IDB_REPORT_INTERNAL_ERR();
+ mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ } else {
+ Database* database = mTransaction->GetDatabase();
+ MOZ_ASSERT(database);
+
+ // Here we're actually going to perform the database operation.
+ nsresult rv = database->EnsureConnection();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mResultCode = rv;
+ } else {
+ DatabaseConnection* connection = database->GetConnection();
+ MOZ_ASSERT(connection);
+ MOZ_ASSERT(connection->GetStorageConnection());
+
+ AutoSetProgressHandler autoProgress;
+ if (mLoggingSerialNumber) {
+ rv = autoProgress.Register(connection->GetStorageConnection(), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mResultCode = rv;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mLoggingSerialNumber) {
+ IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld] Request[%llu]: "
+ "Beginning database work",
+ "IndexedDB %s: P T[%lld] R[%llu]: DB Start",
+ IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
+ mTransactionLoggingSerialNumber,
+ mLoggingSerialNumber);
+ }
+
+ rv = DoDatabaseWork(connection);
+
+ if (mLoggingSerialNumber) {
+ IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld] Request[%llu]: "
+ "Finished database work",
+ "IndexedDB %s: P T[%lld] R[%llu]: DB End",
+ IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
+ mTransactionLoggingSerialNumber,
+ mLoggingSerialNumber);
+ }
+
+ if (NS_FAILED(rv)) {
+ mResultCode = rv;
+ }
+ }
+ }
+ }
+
+ // Must set mInternalState before dispatching otherwise we will race with the
+ // owning thread.
+ if (HasPreprocessInfo()) {
+ mInternalState = InternalState::SendingPreprocess;
+ } else {
+ mInternalState = InternalState::SendingResults;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+bool
+TransactionDatabaseOperationBase::HasPreprocessInfo()
+{
+ return false;
+}
+
+nsresult
+TransactionDatabaseOperationBase::SendPreprocessInfo()
+{
+ return NS_OK;
+}
+
+void
+TransactionDatabaseOperationBase::NoteContinueReceived()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mInternalState == InternalState::WaitingForContinue);
+
+ mInternalState = InternalState::SendingResults;
+
+ // This TransactionDatabaseOperationBase can only be held alive by the IPDL.
+ // Run() can end up with clearing that last reference. So we need to add
+ // a self reference here.
+ RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip = this;
+
+ Unused << this->Run();
+}
+
+void
+TransactionDatabaseOperationBase::SendToConnectionPool()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mInternalState == InternalState::Initial);
+
+ // Must set mInternalState before dispatching otherwise we will race with the
+ // connection thread.
+ mInternalState = InternalState::DatabaseWork;
+
+ gConnectionPool->Dispatch(mTransaction->TransactionId(), this);
+
+ mTransaction->NoteActiveRequest();
+}
+
+void
+TransactionDatabaseOperationBase::SendPreprocess()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess);
+
+ SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ true);
+}
+
+void
+TransactionDatabaseOperationBase::SendResults()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mInternalState == InternalState::SendingResults);
+
+ SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ false);
+}
+
+void
+TransactionDatabaseOperationBase::SendPreprocessInfoOrResults(
+ bool aSendPreprocessInfo)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess ||
+ mInternalState == InternalState::SendingResults);
+ MOZ_ASSERT(mTransaction);
+
+ if (NS_WARN_IF(IsActorDestroyed())) {
+ // Don't send any notifications if the actor was destroyed already.
+ if (NS_SUCCEEDED(mResultCode)) {
+ IDB_REPORT_INTERNAL_ERR();
+ mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+ } else {
+ if (mTransaction->IsInvalidated() || mTransaction->IsAborted()) {
+ // Aborted transactions always see their requests fail with ABORT_ERR,
+ // even if the request succeeded or failed with another error.
+ mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
+ } else if (NS_SUCCEEDED(mResultCode)) {
+ if (aSendPreprocessInfo) {
+ // This should not release the IPDL reference.
+ mResultCode = SendPreprocessInfo();
+ } else {
+ // This may release the IPDL reference.
+ mResultCode = SendSuccessResult();
+ }
+ }
+
+ if (NS_FAILED(mResultCode)) {
+ // This should definitely release the IPDL reference.
+ if (!SendFailureResult(mResultCode)) {
+ // Abort the transaction.
+ mTransaction->Abort(mResultCode, /* aForce */ false);
+ }
+ }
+ }
+
+ if (aSendPreprocessInfo && NS_SUCCEEDED(mResultCode)) {
+ mInternalState = InternalState::WaitingForContinue;
+ } else {
+ if (mLoggingSerialNumber) {
+ mTransaction->NoteFinishedRequest();
+ }
+
+ Cleanup();
+
+ mInternalState = InternalState::Completed;
+ }
+}
+
+bool
+TransactionDatabaseOperationBase::Init(TransactionBase* aTransaction)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mInternalState == InternalState::Initial);
+ MOZ_ASSERT(aTransaction);
+
+ return true;
+}
+
+void
+TransactionDatabaseOperationBase::Cleanup()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mInternalState == InternalState::SendingResults);
+ MOZ_ASSERT(mTransaction);
+
+ mTransaction = nullptr;
+}
+
+NS_IMETHODIMP
+TransactionDatabaseOperationBase::Run()
+{
+ switch (mInternalState) {
+ case InternalState::Initial:
+ SendToConnectionPool();
+ return NS_OK;
+
+ case InternalState::DatabaseWork:
+ RunOnConnectionThread();
+ return NS_OK;
+
+ case InternalState::SendingPreprocess:
+ SendPreprocess();
+ return NS_OK;
+
+ case InternalState::SendingResults:
+ SendResults();
+ return NS_OK;
+
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+}
+
+TransactionBase::
+CommitOp::CommitOp(TransactionBase* aTransaction, nsresult aResultCode)
+ : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
+ aTransaction->GetLoggingInfo()->NextRequestSN())
+ , mTransaction(aTransaction)
+ , mResultCode(aResultCode)
+{
+ MOZ_ASSERT(aTransaction);
+ MOZ_ASSERT(LoggingSerialNumber());
+}
+
+nsresult
+TransactionBase::
+CommitOp::WriteAutoIncrementCounts()
+{
+ MOZ_ASSERT(mTransaction);
+ mTransaction->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::READ_WRITE ||
+ mTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
+ mTransaction->GetMode() == IDBTransaction::CLEANUP ||
+ mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
+
+ const nsTArray<RefPtr<FullObjectStoreMetadata>>& metadataArray =
+ mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray;
+
+ if (!metadataArray.IsEmpty()) {
+ NS_NAMED_LITERAL_CSTRING(osid, "osid");
+ NS_NAMED_LITERAL_CSTRING(ai, "ai");
+
+ Database* database = mTransaction->GetDatabase();
+ MOZ_ASSERT(database);
+
+ DatabaseConnection* connection = database->GetConnection();
+ MOZ_ASSERT(connection);
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv;
+
+ for (uint32_t count = metadataArray.Length(), index = 0;
+ index < count;
+ index++) {
+ const RefPtr<FullObjectStoreMetadata>& metadata = metadataArray[index];
+ MOZ_ASSERT(!metadata->mDeleted);
+ MOZ_ASSERT(metadata->mNextAutoIncrementId > 1);
+
+ if (stmt) {
+ MOZ_ALWAYS_SUCCEEDS(stmt->Reset());
+ } else {
+ rv = connection->GetCachedStatement(
+ NS_LITERAL_CSTRING("UPDATE object_store "
+ "SET auto_increment = :") + ai +
+ NS_LITERAL_CSTRING(" WHERE id = :") + osid +
+ NS_LITERAL_CSTRING(";"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = stmt->BindInt64ByName(osid, metadata->mCommonMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(ai, metadata->mNextAutoIncrementId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+TransactionBase::
+CommitOp::CommitOrRollbackAutoIncrementCounts()
+{
+ MOZ_ASSERT(mTransaction);
+ mTransaction->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::READ_WRITE ||
+ mTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
+ mTransaction->GetMode() == IDBTransaction::CLEANUP ||
+ mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
+
+ nsTArray<RefPtr<FullObjectStoreMetadata>>& metadataArray =
+ mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray;
+
+ if (!metadataArray.IsEmpty()) {
+ bool committed = NS_SUCCEEDED(mResultCode);
+
+ for (uint32_t count = metadataArray.Length(), index = 0;
+ index < count;
+ index++) {
+ RefPtr<FullObjectStoreMetadata>& metadata = metadataArray[index];
+
+ if (committed) {
+ metadata->mCommittedAutoIncrementId = metadata->mNextAutoIncrementId;
+ } else {
+ metadata->mNextAutoIncrementId = metadata->mCommittedAutoIncrementId;
+ }
+ }
+ }
+}
+
+#ifdef DEBUG
+
+void
+TransactionBase::
+CommitOp::AssertForeignKeyConsistency(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(mTransaction);
+ mTransaction->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mTransaction->GetMode() != IDBTransaction::READ_ONLY);
+
+ DatabaseConnection::CachedStatement pragmaStmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetCachedStatement(NS_LITERAL_CSTRING("PRAGMA foreign_keys;"),
+ &pragmaStmt));
+
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(pragmaStmt->ExecuteStep(&hasResult));
+
+ MOZ_ASSERT(hasResult);
+
+ int32_t foreignKeysEnabled;
+ MOZ_ALWAYS_SUCCEEDS(pragmaStmt->GetInt32(0, &foreignKeysEnabled));
+
+ MOZ_ASSERT(foreignKeysEnabled, "Database doesn't have foreign keys enabled!");
+
+ DatabaseConnection::CachedStatement checkStmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetCachedStatement(
+ NS_LITERAL_CSTRING("PRAGMA foreign_key_check;"),
+ &checkStmt));
+
+ MOZ_ALWAYS_SUCCEEDS(checkStmt->ExecuteStep(&hasResult));
+
+ MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");
+}
+
+#endif // DEBUG
+
+NS_IMPL_ISUPPORTS_INHERITED0(TransactionBase::CommitOp, DatabaseOperationBase)
+
+NS_IMETHODIMP
+TransactionBase::
+CommitOp::Run()
+{
+ MOZ_ASSERT(mTransaction);
+ mTransaction->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "TransactionBase::CommitOp::Run",
+ js::ProfileEntry::Category::STORAGE);
+
+ IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld] Request[%llu]: "
+ "Beginning database work",
+ "IndexedDB %s: P T[%lld] R[%llu]: DB Start",
+ IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
+ mTransaction->LoggingSerialNumber(),
+ mLoggingSerialNumber);
+
+ if (mTransaction->GetMode() != IDBTransaction::READ_ONLY &&
+ mTransaction->mHasBeenActiveOnConnectionThread) {
+ Database* database = mTransaction->GetDatabase();
+ MOZ_ASSERT(database);
+
+ if (DatabaseConnection* connection = database->GetConnection()) {
+ // May be null if the VersionChangeOp was canceled.
+ DatabaseConnection::UpdateRefcountFunction* fileRefcountFunction =
+ connection->GetUpdateRefcountFunction();
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ if (fileRefcountFunction) {
+ mResultCode = fileRefcountFunction->WillCommit();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode),
+ "WillCommit() failed!");
+ }
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = WriteAutoIncrementCounts();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode),
+ "WriteAutoIncrementCounts() failed!");
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ AssertForeignKeyConsistency(connection);
+
+ mResultCode = connection->CommitWriteTransaction();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode), "Commit failed!");
+
+ if (NS_SUCCEEDED(mResultCode) &&
+ mTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH) {
+ mResultCode = connection->Checkpoint();
+ }
+
+ if (NS_SUCCEEDED(mResultCode) && fileRefcountFunction) {
+ fileRefcountFunction->DidCommit();
+ }
+ }
+ }
+ }
+
+ if (NS_FAILED(mResultCode)) {
+ if (fileRefcountFunction) {
+ fileRefcountFunction->DidAbort();
+ }
+
+ connection->RollbackWriteTransaction();
+ }
+
+ CommitOrRollbackAutoIncrementCounts();
+
+ connection->FinishWriteTransaction();
+
+ if (mTransaction->GetMode() == IDBTransaction::CLEANUP) {
+ connection->DoIdleProcessing(/* aNeedsCheckpoint */ true);
+
+ connection->EnableQuotaChecks();
+ }
+ }
+ }
+
+ IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld] Request[%llu]: "
+ "Finished database work",
+ "IndexedDB %s: P T[%lld] R[%llu]: DB End",
+ IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
+ mTransaction->LoggingSerialNumber(),
+ mLoggingSerialNumber);
+
+ IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld]: "
+ "Finished database work",
+ "IndexedDB %s: P T[%lld]: DB End",
+ IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
+ mLoggingSerialNumber);
+
+ return NS_OK;
+}
+
+void
+TransactionBase::
+CommitOp::TransactionFinishedBeforeUnblock()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mTransaction);
+
+ PROFILER_LABEL("IndexedDB",
+ "CommitOp::TransactionFinishedBeforeUnblock",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!IsActorDestroyed()) {
+ mTransaction->UpdateMetadata(mResultCode);
+ }
+}
+
+void
+TransactionBase::
+CommitOp::TransactionFinishedAfterUnblock()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mTransaction);
+
+ IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld]: "
+ "Finished with result 0x%x",
+ "IndexedDB %s: P T[%lld]: Transaction finished (0x%x)",
+ IDB_LOG_ID_STRING(mTransaction->GetLoggingInfo()->Id()),
+ mTransaction->LoggingSerialNumber(),
+ mResultCode);
+
+ mTransaction->SendCompleteNotification(ClampResultCode(mResultCode));
+
+ Database* database = mTransaction->GetDatabase();
+ MOZ_ASSERT(database);
+
+ database->UnregisterTransaction(mTransaction);
+
+ mTransaction = nullptr;
+
+#ifdef DEBUG
+ // A bit hacky but the CommitOp is not really a normal database operation
+ // that is tied to an actor. Do this to make our assertions happy.
+ NoteActorDestroyed();
+#endif
+}
+
+DatabaseOp::DatabaseOp(Database* aDatabase)
+ : DatabaseOperationBase(aDatabase->GetLoggingInfo()->Id(),
+ aDatabase->GetLoggingInfo()->NextRequestSN())
+ , mDatabase(aDatabase)
+ , mState(State::Initial)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aDatabase);
+}
+
+nsresult
+DatabaseOp::SendToIOThread()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::Initial);
+
+ if (!OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (NS_WARN_IF(!quotaManager)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ // Must set this before dispatching otherwise we will race with the IO thread.
+ mState = State::DatabaseWork;
+
+ nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DatabaseOp::Run()
+{
+ nsresult rv;
+
+ switch (mState) {
+ case State::Initial:
+ rv = SendToIOThread();
+ break;
+
+ case State::DatabaseWork:
+ rv = DoDatabaseWork();
+ break;
+
+ case State::SendingResults:
+ SendResults();
+ return NS_OK;
+
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ // Must set mState before dispatching otherwise we will race with the owning
+ // thread.
+ mState = State::SendingResults;
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+ }
+
+ return NS_OK;
+}
+
+void
+DatabaseOp::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+
+ NoteActorDestroyed();
+}
+
+CreateFileOp::CreateFileOp(Database* aDatabase,
+ const DatabaseRequestParams& aParams)
+ : DatabaseOp(aDatabase)
+ , mParams(aParams.get_CreateFileParams())
+{
+ MOZ_ASSERT(aParams.type() == DatabaseRequestParams::TCreateFileParams);
+}
+
+nsresult
+CreateFileOp::CreateMutableFile(MutableFile** aMutableFile)
+{
+ nsCOMPtr<nsIFile> file = GetFileForFileInfo(mFileInfo);
+ if (NS_WARN_IF(!file)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ RefPtr<MutableFile> mutableFile =
+ MutableFile::Create(file, mDatabase, mFileInfo);
+ if (NS_WARN_IF(!mutableFile)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ // Transfer ownership to IPDL.
+ mutableFile->SetActorAlive();
+
+ if (!mDatabase->SendPBackgroundMutableFileConstructor(mutableFile,
+ mParams.name(),
+ mParams.type())) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mutableFile.forget(aMutableFile);
+ return NS_OK;
+}
+
+nsresult
+CreateFileOp::DoDatabaseWork()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mState == State::DatabaseWork);
+
+ PROFILER_LABEL("IndexedDB",
+ "CreateFileOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
+ NS_WARNING("Refusing to create file because disk space is low!");
+ return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+
+ if (NS_WARN_IF(QuotaManager::IsShuttingDown()) || !OperationMayProceed()) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ FileManager* fileManager = mDatabase->GetFileManager();
+
+ mFileInfo = fileManager->GetNewFileInfo();
+ if (NS_WARN_IF(!mFileInfo)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ const int64_t fileId = mFileInfo->Id();
+
+ nsCOMPtr<nsIFile> journalDirectory = fileManager->EnsureJournalDirectory();
+ if (NS_WARN_IF(!journalDirectory)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ nsCOMPtr<nsIFile> journalFile =
+ fileManager->GetFileForId(journalDirectory, fileId);
+ if (NS_WARN_IF(!journalFile)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ nsresult rv = journalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> fileDirectory = fileManager->GetDirectory();
+ if (NS_WARN_IF(!fileDirectory)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ nsCOMPtr<nsIFile> file = fileManager->GetFileForId(fileDirectory, fileId);
+ if (NS_WARN_IF(!file)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Must set mState before dispatching otherwise we will race with the owning
+ // thread.
+ mState = State::SendingResults;
+
+ rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+CreateFileOp::SendResults()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::SendingResults);
+
+ if (!IsActorDestroyed() && !mDatabase->IsInvalidated()) {
+ DatabaseRequestResponse response;
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ RefPtr<MutableFile> mutableFile;
+ nsresult rv = CreateMutableFile(getter_AddRefs(mutableFile));
+ if (NS_SUCCEEDED(rv)) {
+ // We successfully created a mutable file so use its actor as the
+ // success result for this request.
+ CreateFileRequestResponse createResponse;
+ createResponse.mutableFileParent() = mutableFile;
+ response = createResponse;
+ } else {
+ response = ClampResultCode(rv);
+#ifdef DEBUG
+ mResultCode = response.get_nsresult();
+#endif
+ }
+ } else {
+ response = ClampResultCode(mResultCode);
+ }
+
+ Unused <<
+ PBackgroundIDBDatabaseRequestParent::Send__delete__(this, response);
+ }
+
+ mState = State::Completed;
+}
+
+nsresult
+VersionChangeTransactionOp::SendSuccessResult()
+{
+ AssertIsOnOwningThread();
+
+ // Nothing to send here, the API assumes that this request always succeeds.
+ return NS_OK;
+}
+
+bool
+VersionChangeTransactionOp::SendFailureResult(nsresult aResultCode)
+{
+ AssertIsOnOwningThread();
+
+ // The only option here is to cause the transaction to abort.
+ return false;
+}
+
+void
+VersionChangeTransactionOp::Cleanup()
+{
+ AssertIsOnOwningThread();
+
+#ifdef DEBUG
+ // A bit hacky but the VersionChangeTransactionOp is not generated in response
+ // to a child request like most other database operations. Do this to make our
+ // assertions happy.
+ NoteActorDestroyed();
+#endif
+
+ TransactionDatabaseOperationBase::Cleanup();
+}
+
+nsresult
+CreateObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "CreateObjectStoreOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
+ return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+
+#ifdef DEBUG
+ {
+ // Make sure that we're not creating an object store with the same name as
+ // another that already exists. This should be impossible because we should
+ // have thrown an error long before now...
+ DatabaseConnection::CachedStatement stmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT name "
+ "FROM object_store "
+ "WHERE name = :name;"),
+ &stmt));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name()));
+
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
+ MOZ_ASSERT(!hasResult);
+ }
+#endif
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO object_store (id, auto_increment, name, key_path) "
+ "VALUES (:id, :auto_increment, :name, :key_path);"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("auto_increment"),
+ mMetadata.autoIncrement() ? 1 : 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(keyPath, "key_path");
+
+ if (mMetadata.keyPath().IsValid()) {
+ nsAutoString keyPathSerialization;
+ mMetadata.keyPath().SerializeToString(keyPathSerialization);
+
+ rv = stmt->BindStringByName(keyPath, keyPathSerialization);
+ } else {
+ rv = stmt->BindNullByName(keyPath);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ {
+ int64_t id;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetStorageConnection()->GetLastInsertRowID(&id));
+ MOZ_ASSERT(mMetadata.id() == id);
+ }
+#endif
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DeleteObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "DeleteObjectStoreOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
+
+#ifdef DEBUG
+ {
+ // Make sure |mIsLastObjectStore| is telling the truth.
+ DatabaseConnection::CachedStatement stmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT id "
+ "FROM object_store;"),
+ &stmt));
+
+ bool foundThisObjectStore = false;
+ bool foundOtherObjectStore = false;
+
+ while (true) {
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
+
+ if (!hasResult) {
+ break;
+ }
+
+ int64_t id;
+ MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id));
+
+ if (id == mMetadata->mCommonMetadata.id()) {
+ foundThisObjectStore = true;
+ } else {
+ foundOtherObjectStore = true;
+ }
+ }
+
+ MOZ_ASSERT_IF(mIsLastObjectStore,
+ foundThisObjectStore && !foundOtherObjectStore);
+ MOZ_ASSERT_IF(!mIsLastObjectStore,
+ foundThisObjectStore && foundOtherObjectStore);
+ }
+#endif
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mIsLastObjectStore) {
+ // We can just delete everything if this is the last object store.
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM index_data;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM unique_index_data;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_data;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_store_index;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_store;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ bool hasIndexes;
+ rv = ObjectStoreHasIndexes(aConnection,
+ mMetadata->mCommonMetadata.id(),
+ &hasIndexes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasIndexes) {
+ rv = DeleteObjectStoreDataTableRowsWithIndexes(
+ aConnection,
+ mMetadata->mCommonMetadata.id(),
+ void_t());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now clean up the object store index table.
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_store_index "
+ "WHERE object_store_id = :object_store_id;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(objectStoreIdString,
+ mMetadata->mCommonMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ // We only have to worry about object data if this object store has no
+ // indexes.
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_data "
+ "WHERE object_store_id = :object_store_id;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(objectStoreIdString,
+ mMetadata->mCommonMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_store "
+ "WHERE id = :object_store_id;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(objectStoreIdString,
+ mMetadata->mCommonMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ {
+ int32_t deletedRowCount;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetStorageConnection()->
+ GetAffectedRows(&deletedRowCount));
+ MOZ_ASSERT(deletedRowCount == 1);
+ }
+#endif
+ }
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mMetadata->mCommonMetadata.autoIncrement()) {
+ Transaction()->ForgetModifiedAutoIncrementObjectStore(mMetadata);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+RenameObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "RenameObjectStoreOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
+ return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+
+#ifdef DEBUG
+ {
+ // Make sure that we're not renaming an object store with the same name as
+ // another that already exists. This should be impossible because we should
+ // have thrown an error long before now...
+ DatabaseConnection::CachedStatement stmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT name "
+ "FROM object_store "
+ "WHERE name = :name "
+ "AND id != :id;"),
+ &stmt));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mNewName));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mId));
+
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
+ MOZ_ASSERT(!hasResult);
+ }
+#endif
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "UPDATE object_store "
+ "SET name = :name "
+ "WHERE id = :id;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mNewName);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mId);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+CreateIndexOp::CreateIndexOp(VersionChangeTransaction* aTransaction,
+ const int64_t aObjectStoreId,
+ const IndexMetadata& aMetadata)
+ : VersionChangeTransactionOp(aTransaction)
+ , mMetadata(aMetadata)
+ , mFileManager(aTransaction->GetDatabase()->GetFileManager())
+ , mDatabaseId(aTransaction->DatabaseId())
+ , mObjectStoreId(aObjectStoreId)
+{
+ MOZ_ASSERT(aObjectStoreId);
+ MOZ_ASSERT(aMetadata.id());
+ MOZ_ASSERT(mFileManager);
+ MOZ_ASSERT(!mDatabaseId.IsEmpty());
+}
+
+unsigned int CreateIndexOp::sThreadLocalIndex = kBadThreadLocalIndex;
+
+nsresult
+CreateIndexOp::InsertDataFromObjectStore(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(!IndexedDatabaseManager::InLowDiskSpaceMode());
+ MOZ_ASSERT(mMaybeUniqueIndexTable);
+
+ PROFILER_LABEL("IndexedDB",
+ "CreateIndexOp::InsertDataFromObjectStore",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsCOMPtr<mozIStorageConnection> storageConnection =
+ aConnection->GetStorageConnection();
+ MOZ_ASSERT(storageConnection);
+
+ ThreadLocalJSContext* context = ThreadLocalJSContext::GetOrCreate();
+ if (NS_WARN_IF(!context)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ JSContext* cx = context->Context();
+ JSAutoRequest ar(cx);
+ JSAutoCompartment ac(cx, context->Global());
+
+ RefPtr<UpdateIndexDataValuesFunction> updateFunction =
+ new UpdateIndexDataValuesFunction(this, aConnection, cx);
+
+ NS_NAMED_LITERAL_CSTRING(updateFunctionName, "update_index_data_values");
+
+ nsresult rv =
+ storageConnection->CreateFunction(updateFunctionName,
+ 4,
+ updateFunction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = InsertDataFromObjectStoreInternal(aConnection);
+
+ MOZ_ALWAYS_SUCCEEDS(storageConnection->RemoveFunction(updateFunctionName));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CreateIndexOp::InsertDataFromObjectStoreInternal(
+ DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(!IndexedDatabaseManager::InLowDiskSpaceMode());
+ MOZ_ASSERT(mMaybeUniqueIndexTable);
+
+ DebugOnly<void*> storageConnection = aConnection->GetStorageConnection();
+ MOZ_ASSERT(storageConnection);
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "UPDATE object_data "
+ "SET index_data_values = update_index_data_values "
+ "(key, index_data_values, file_ids, data) "
+ "WHERE object_store_id = :object_store_id;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ mObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+CreateIndexOp::Init(TransactionBase* aTransaction)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransaction);
+
+ struct MOZ_STACK_CLASS Helper final
+ {
+ static void
+ Destroy(void* aThreadLocal)
+ {
+ delete static_cast<ThreadLocalJSContext*>(aThreadLocal);
+ }
+ };
+
+ if (sThreadLocalIndex == kBadThreadLocalIndex) {
+ if (NS_WARN_IF(PR_SUCCESS !=
+ PR_NewThreadPrivateIndex(&sThreadLocalIndex,
+ &Helper::Destroy))) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex);
+
+ nsresult rv =
+ GetUniqueIndexTableForObjectStore(aTransaction,
+ mObjectStoreId,
+ mMaybeUniqueIndexTable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+}
+
+nsresult
+CreateIndexOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "CreateIndexOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
+ return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+
+#ifdef DEBUG
+ {
+ // Make sure that we're not creating an index with the same name and object
+ // store as another that already exists. This should be impossible because
+ // we should have thrown an error long before now...
+ DatabaseConnection::CachedStatement stmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT name "
+ "FROM object_store_index "
+ "WHERE object_store_id = :osid "
+ "AND name = :name;"),
+ &stmt));
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId));
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name()));
+
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
+
+ MOZ_ASSERT(!hasResult);
+ }
+#endif
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO object_store_index (id, name, key_path, unique_index, "
+ "multientry, object_store_id, locale, "
+ "is_auto_locale) "
+ "VALUES (:id, :name, :key_path, :unique, :multientry, :osid, :locale, "
+ ":is_auto_locale)"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString keyPathSerialization;
+ mMetadata.keyPath().SerializeToString(keyPathSerialization);
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
+ keyPathSerialization);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("unique"),
+ mMetadata.unique() ? 1 : 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("multientry"),
+ mMetadata.multiEntry() ? 1 : 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mMetadata.locale().IsEmpty()) {
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("locale"));
+ } else {
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("locale"),
+ mMetadata.locale());
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("is_auto_locale"),
+ mMetadata.autoLocale());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ {
+ int64_t id;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetStorageConnection()->GetLastInsertRowID(&id));
+ MOZ_ASSERT(mMetadata.id() == id);
+ }
+#endif
+
+ rv = InsertDataFromObjectStore(aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+static const JSClassOps sNormalJSContextGlobalClassOps = {
+ /* addProperty */ nullptr,
+ /* delProperty */ nullptr,
+ /* getProperty */ nullptr,
+ /* setProperty */ nullptr,
+ /* enumerate */ nullptr,
+ /* resolve */ nullptr,
+ /* mayResolve */ nullptr,
+ /* finalize */ nullptr,
+ /* call */ nullptr,
+ /* hasInstance */ nullptr,
+ /* construct */ nullptr,
+ /* trace */ JS_GlobalObjectTraceHook
+};
+
+const JSClass NormalJSContext::sGlobalClass = {
+ "IndexedDBTransactionThreadGlobal",
+ JSCLASS_GLOBAL_FLAGS,
+ &sNormalJSContextGlobalClassOps
+};
+
+bool
+NormalJSContext::Init()
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ mContext = JS_NewContext(kContextHeapSize);
+ if (NS_WARN_IF(!mContext)) {
+ return false;
+ }
+
+ // Let everyone know that we might be able to call JS. This alerts the
+ // profiler about certain possible deadlocks.
+ NS_GetCurrentThread()->SetCanInvokeJS(true);
+
+ // Not setting this will cause JS_CHECK_RECURSION to report false positives.
+ JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024);
+
+ if (NS_WARN_IF(!JS::InitSelfHostedCode(mContext))) {
+ return false;
+ }
+
+ JSAutoRequest ar(mContext);
+
+ JS::CompartmentOptions options;
+ mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
+ JS::FireOnNewGlobalHook, options);
+ if (NS_WARN_IF(!mGlobal)) {
+ return false;
+ }
+
+ return true;
+}
+
+// static
+NormalJSContext*
+NormalJSContext::Create()
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ nsAutoPtr<NormalJSContext> newContext(new NormalJSContext());
+
+ if (NS_WARN_IF(!newContext->Init())) {
+ return nullptr;
+ }
+
+ return newContext.forget();
+}
+
+// static
+auto
+CreateIndexOp::
+ThreadLocalJSContext::GetOrCreate() -> ThreadLocalJSContext*
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(CreateIndexOp::kBadThreadLocalIndex !=
+ CreateIndexOp::sThreadLocalIndex);
+
+ auto* context = static_cast<ThreadLocalJSContext*>(
+ PR_GetThreadPrivate(CreateIndexOp::sThreadLocalIndex));
+ if (context) {
+ return context;
+ }
+
+ nsAutoPtr<ThreadLocalJSContext> newContext(new ThreadLocalJSContext());
+
+ if (NS_WARN_IF(!newContext->Init())) {
+ return nullptr;
+ }
+
+ DebugOnly<PRStatus> status =
+ PR_SetThreadPrivate(CreateIndexOp::sThreadLocalIndex, newContext);
+ MOZ_ASSERT(status == PR_SUCCESS);
+
+ return newContext.forget();
+}
+
+NS_IMPL_ISUPPORTS(CreateIndexOp::UpdateIndexDataValuesFunction,
+ mozIStorageFunction);
+
+NS_IMETHODIMP
+CreateIndexOp::
+UpdateIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
+ nsIVariant** _retval)
+{
+ MOZ_ASSERT(aValues);
+ MOZ_ASSERT(_retval);
+ MOZ_ASSERT(mConnection);
+ mConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mOp);
+ MOZ_ASSERT(mCx);
+
+ PROFILER_LABEL("IndexedDB",
+ "CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall",
+ js::ProfileEntry::Category::STORAGE);
+
+#ifdef DEBUG
+ {
+ uint32_t argCount;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
+ MOZ_ASSERT(argCount == 4); // key, index_data_values, file_ids, data
+
+ int32_t valueType;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
+ valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
+ valueType == mozIStorageValueArray::VALUE_TYPE_TEXT);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB ||
+ valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
+ }
+#endif
+
+ StructuredCloneReadInfo cloneInfo;
+ nsresult rv =
+ GetStructuredCloneReadInfoFromValueArray(aValues,
+ /* aDataIndex */ 3,
+ /* aFileIdsIndex */ 2,
+ mOp->mFileManager,
+ &cloneInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ JS::Rooted<JS::Value> clone(mCx);
+ if (NS_WARN_IF(!IDBObjectStore::DeserializeIndexValue(mCx,
+ cloneInfo,
+ &clone))) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ const IndexMetadata& metadata = mOp->mMetadata;
+ const int64_t& objectStoreId = mOp->mObjectStoreId;
+
+ AutoTArray<IndexUpdateInfo, 32> updateInfos;
+ rv = IDBObjectStore::AppendIndexUpdateInfo(metadata.id(),
+ metadata.keyPath(),
+ metadata.unique(),
+ metadata.multiEntry(),
+ metadata.locale(),
+ mCx,
+ clone,
+ updateInfos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (updateInfos.IsEmpty()) {
+ // XXX See if we can do this without copying...
+
+ nsCOMPtr<nsIVariant> unmodifiedValue;
+
+ // No changes needed, just return the original value.
+ int32_t valueType;
+ rv = aValues->GetTypeOfIndex(1, &valueType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
+ valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+
+ if (valueType == mozIStorageValueArray::VALUE_TYPE_NULL) {
+ unmodifiedValue = new storage::NullVariant();
+ unmodifiedValue.forget(_retval);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+
+ const uint8_t* blobData;
+ uint32_t blobDataLength;
+ rv = aValues->GetSharedBlob(1, &blobDataLength, &blobData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ std::pair<uint8_t *, int> copiedBlobDataPair(
+ static_cast<uint8_t*>(malloc(blobDataLength)),
+ blobDataLength);
+
+ if (!copiedBlobDataPair.first) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy(copiedBlobDataPair.first, blobData, blobDataLength);
+
+ unmodifiedValue = new storage::AdoptedBlobVariant(copiedBlobDataPair);
+ unmodifiedValue.forget(_retval);
+
+ return NS_OK;
+ }
+
+ Key key;
+ rv = key.SetFromValueArray(aValues, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ AutoTArray<IndexDataValue, 32> indexValues;
+ rv = ReadCompressedIndexDataValues(aValues, 1, indexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ const bool hadPreviousIndexValues = !indexValues.IsEmpty();
+
+ const uint32_t updateInfoCount = updateInfos.Length();
+
+ if (NS_WARN_IF(!indexValues.SetCapacity(indexValues.Length() +
+ updateInfoCount, fallible))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // First construct the full list to update the index_data_values row.
+ for (uint32_t index = 0; index < updateInfoCount; index++) {
+ const IndexUpdateInfo& info = updateInfos[index];
+
+ MOZ_ALWAYS_TRUE(
+ indexValues.InsertElementSorted(IndexDataValue(metadata.id(),
+ metadata.unique(),
+ info.value(),
+ info.localizedValue()),
+ fallible));
+ }
+
+ UniqueFreePtr<uint8_t> indexValuesBlob;
+ uint32_t indexValuesBlobLength;
+ rv = MakeCompressedIndexDataValues(indexValues,
+ indexValuesBlob,
+ &indexValuesBlobLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(!indexValuesBlobLength == !(indexValuesBlob.get()));
+
+ nsCOMPtr<nsIVariant> value;
+
+ if (!indexValuesBlob) {
+ value = new storage::NullVariant();
+
+ value.forget(_retval);
+ return NS_OK;
+ }
+
+ // Now insert the new table rows. We only need to construct a new list if
+ // the full list is different.
+ if (hadPreviousIndexValues) {
+ indexValues.ClearAndRetainStorage();
+
+ MOZ_ASSERT(indexValues.Capacity() >= updateInfoCount);
+
+ for (uint32_t index = 0; index < updateInfoCount; index++) {
+ const IndexUpdateInfo& info = updateInfos[index];
+
+ MOZ_ALWAYS_TRUE(
+ indexValues.InsertElementSorted(IndexDataValue(metadata.id(),
+ metadata.unique(),
+ info.value(),
+ info.localizedValue()),
+ fallible));
+ }
+ }
+
+ rv = InsertIndexTableRows(mConnection, objectStoreId, key, indexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ std::pair<uint8_t *, int> copiedBlobDataPair(indexValuesBlob.release(),
+ indexValuesBlobLength);
+
+ value = new storage::AdoptedBlobVariant(copiedBlobDataPair);
+
+ value.forget(_retval);
+ return NS_OK;
+}
+
+DeleteIndexOp::DeleteIndexOp(VersionChangeTransaction* aTransaction,
+ const int64_t aObjectStoreId,
+ const int64_t aIndexId,
+ const bool aUnique,
+ const bool aIsLastIndex)
+ : VersionChangeTransactionOp(aTransaction)
+ , mObjectStoreId(aObjectStoreId)
+ , mIndexId(aIndexId)
+ , mUnique(aUnique)
+ , mIsLastIndex(aIsLastIndex)
+{
+ MOZ_ASSERT(aObjectStoreId);
+ MOZ_ASSERT(aIndexId);
+}
+
+nsresult
+DeleteIndexOp::RemoveReferencesToIndex(DatabaseConnection* aConnection,
+ const Key& aObjectStoreKey,
+ nsTArray<IndexDataValue>& aIndexValues)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(!aObjectStoreKey.IsUnset());
+ MOZ_ASSERT_IF(!mIsLastIndex, !aIndexValues.IsEmpty());
+
+ struct MOZ_STACK_CLASS IndexIdComparator final
+ {
+ bool
+ Equals(const IndexDataValue& aA, const IndexDataValue& aB) const
+ {
+ // Ignore everything but the index id.
+ return aA.mIndexId == aB.mIndexId;
+ };
+
+ bool
+ LessThan(const IndexDataValue& aA, const IndexDataValue& aB) const
+ {
+ return aA.mIndexId < aB.mIndexId;
+ };
+ };
+
+ PROFILER_LABEL("IndexedDB",
+ "DeleteIndexOp::RemoveReferencesToIndex",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (mIsLastIndex) {
+ // There is no need to parse the previous entry in the index_data_values
+ // column if this is the last index. Simply set it to NULL.
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "UPDATE object_data "
+ "SET index_data_values = NULL "
+ "WHERE object_store_id = :object_store_id "
+ "AND key = :key;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ mObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aObjectStoreKey.BindToStatement(stmt, NS_LITERAL_CSTRING("key"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ IndexDataValue search;
+ search.mIndexId = mIndexId;
+
+ // This returns the first element that matches our index id found during a
+ // binary search. However, there could still be other elements before that.
+ size_t firstElementIndex =
+ aIndexValues.BinaryIndexOf(search, IndexIdComparator());
+ if (NS_WARN_IF(firstElementIndex == aIndexValues.NoIndex) ||
+ NS_WARN_IF(aIndexValues[firstElementIndex].mIndexId != mIndexId)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ MOZ_ASSERT(aIndexValues[firstElementIndex].mIndexId == mIndexId);
+
+ // Walk backwards to find the real first index.
+ while (firstElementIndex) {
+ if (aIndexValues[firstElementIndex - 1].mIndexId == mIndexId) {
+ firstElementIndex--;
+ } else {
+ break;
+ }
+ }
+
+ MOZ_ASSERT(aIndexValues[firstElementIndex].mIndexId == mIndexId);
+
+ const size_t indexValuesLength = aIndexValues.Length();
+
+ // Find the last element with the same index id.
+ size_t lastElementIndex = firstElementIndex;
+
+ while (lastElementIndex < indexValuesLength) {
+ if (aIndexValues[lastElementIndex].mIndexId == mIndexId) {
+ lastElementIndex++;
+ } else {
+ break;
+ }
+ }
+
+ MOZ_ASSERT(lastElementIndex > firstElementIndex);
+ MOZ_ASSERT_IF(lastElementIndex < indexValuesLength,
+ aIndexValues[lastElementIndex].mIndexId != mIndexId);
+ MOZ_ASSERT(aIndexValues[lastElementIndex - 1].mIndexId == mIndexId);
+
+ aIndexValues.RemoveElementsAt(firstElementIndex,
+ lastElementIndex - firstElementIndex);
+
+ nsresult rv = UpdateIndexValues(aConnection,
+ mObjectStoreId,
+ aObjectStoreKey,
+ aIndexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DeleteIndexOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+#ifdef DEBUG
+ {
+ // Make sure |mIsLastIndex| is telling the truth.
+ DatabaseConnection::CachedStatement stmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT id "
+ "FROM object_store_index "
+ "WHERE object_store_id = :object_store_id;"),
+ &stmt));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ mObjectStoreId));
+
+ bool foundThisIndex = false;
+ bool foundOtherIndex = false;
+
+ while (true) {
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
+
+ if (!hasResult) {
+ break;
+ }
+
+ int64_t id;
+ MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id));
+
+ if (id == mIndexId) {
+ foundThisIndex = true;
+ } else {
+ foundOtherIndex = true;
+ }
+ }
+
+ MOZ_ASSERT_IF(mIsLastIndex, foundThisIndex && !foundOtherIndex);
+ MOZ_ASSERT_IF(!mIsLastIndex, foundThisIndex && foundOtherIndex);
+ }
+#endif
+
+ PROFILER_LABEL("IndexedDB",
+ "DeleteIndexOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ DatabaseConnection::CachedStatement selectStmt;
+
+ // mozStorage warns that these statements trigger a sort operation but we
+ // don't care because this is a very rare call and we expect it to be slow.
+ // The cost of having an index on this field is too high.
+ if (mUnique) {
+ if (mIsLastIndex) {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "/* do not warn (bug someone else) */ "
+ "SELECT value, object_data_key "
+ "FROM unique_index_data "
+ "WHERE index_id = :index_id "
+ "ORDER BY object_data_key ASC;"),
+ &selectStmt);
+ } else {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "/* do not warn (bug out) */ "
+ "SELECT unique_index_data.value, "
+ "unique_index_data.object_data_key, "
+ "object_data.index_data_values "
+ "FROM unique_index_data "
+ "JOIN object_data "
+ "ON unique_index_data.object_data_key = object_data.key "
+ "WHERE unique_index_data.index_id = :index_id "
+ "AND object_data.object_store_id = :object_store_id "
+ "ORDER BY unique_index_data.object_data_key ASC;"),
+ &selectStmt);
+ }
+ } else {
+ if (mIsLastIndex) {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "/* do not warn (bug me not) */ "
+ "SELECT value, object_data_key "
+ "FROM index_data "
+ "WHERE index_id = :index_id "
+ "AND object_store_id = :object_store_id "
+ "ORDER BY object_data_key ASC;"),
+ &selectStmt);
+ } else {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "/* do not warn (bug off) */ "
+ "SELECT index_data.value, "
+ "index_data.object_data_key, "
+ "object_data.index_data_values "
+ "FROM index_data "
+ "JOIN object_data "
+ "ON index_data.object_data_key = object_data.key "
+ "WHERE index_data.index_id = :index_id "
+ "AND object_data.object_store_id = :object_store_id "
+ "ORDER BY index_data.object_data_key ASC;"),
+ &selectStmt);
+ }
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
+
+ rv = selectStmt->BindInt64ByName(indexIdString, mIndexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mUnique || !mIsLastIndex) {
+ rv = selectStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ mObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ NS_NAMED_LITERAL_CSTRING(valueString, "value");
+ NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
+
+ DatabaseConnection::CachedStatement deleteIndexRowStmt;
+ DatabaseConnection::CachedStatement nullIndexDataValuesStmt;
+
+ Key lastObjectStoreKey;
+ AutoTArray<IndexDataValue, 32> lastIndexValues;
+
+ bool hasResult;
+ while (NS_SUCCEEDED(rv = selectStmt->ExecuteStep(&hasResult)) && hasResult) {
+ // We always need the index key to delete the index row.
+ Key indexKey;
+ rv = indexKey.SetFromStatement(selectStmt, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(indexKey.IsUnset())) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Don't call |lastObjectStoreKey.BindToStatement()| directly because we
+ // don't want to copy the same key multiple times.
+ const uint8_t* objectStoreKeyData;
+ uint32_t objectStoreKeyDataLength;
+ rv = selectStmt->GetSharedBlob(1,
+ &objectStoreKeyDataLength,
+ &objectStoreKeyData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!objectStoreKeyDataLength)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ nsDependentCString currentObjectStoreKeyBuffer(
+ reinterpret_cast<const char*>(objectStoreKeyData),
+ objectStoreKeyDataLength);
+ if (currentObjectStoreKeyBuffer != lastObjectStoreKey.GetBuffer()) {
+ // We just walked to the next object store key.
+ if (!lastObjectStoreKey.IsUnset()) {
+ // Before we move on to the next key we need to update the previous
+ // key's index_data_values column.
+ rv = RemoveReferencesToIndex(aConnection,
+ lastObjectStoreKey,
+ lastIndexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Save the object store key.
+ lastObjectStoreKey = Key(currentObjectStoreKeyBuffer);
+
+ // And the |index_data_values| row if this isn't the only index.
+ if (!mIsLastIndex) {
+ lastIndexValues.ClearAndRetainStorage();
+ rv = ReadCompressedIndexDataValues(selectStmt, 2, lastIndexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(lastIndexValues.IsEmpty())) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ }
+ }
+
+ // Now delete the index row.
+ if (deleteIndexRowStmt) {
+ MOZ_ALWAYS_SUCCEEDS(deleteIndexRowStmt->Reset());
+ } else {
+ if (mUnique) {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM unique_index_data "
+ "WHERE index_id = :index_id "
+ "AND value = :value;"),
+ &deleteIndexRowStmt);
+ } else {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM index_data "
+ "WHERE index_id = :index_id "
+ "AND value = :value "
+ "AND object_data_key = :object_data_key;"),
+ &deleteIndexRowStmt);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = deleteIndexRowStmt->BindInt64ByName(indexIdString, mIndexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = indexKey.BindToStatement(deleteIndexRowStmt, valueString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mUnique) {
+ rv = lastObjectStoreKey.BindToStatement(deleteIndexRowStmt,
+ objectDataKeyString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = deleteIndexRowStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Take care of the last key.
+ if (!lastObjectStoreKey.IsUnset()) {
+ MOZ_ASSERT_IF(!mIsLastIndex, !lastIndexValues.IsEmpty());
+
+ rv = RemoveReferencesToIndex(aConnection,
+ lastObjectStoreKey,
+ lastIndexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ DatabaseConnection::CachedStatement deleteStmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_store_index "
+ "WHERE id = :index_id;"),
+ &deleteStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = deleteStmt->BindInt64ByName(indexIdString, mIndexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = deleteStmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ {
+ int32_t deletedRowCount;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetStorageConnection()->GetAffectedRows(&deletedRowCount));
+ MOZ_ASSERT(deletedRowCount == 1);
+ }
+#endif
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+RenameIndexOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "RenameIndexOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
+ return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+
+#ifdef DEBUG
+ {
+ // Make sure that we're not renaming an index with the same name as another
+ // that already exists. This should be impossible because we should have
+ // thrown an error long before now...
+ DatabaseConnection::CachedStatement stmt;
+ MOZ_ALWAYS_SUCCEEDS(
+ aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT name "
+ "FROM object_store_index "
+ "WHERE object_store_id = :object_store_id "
+ "AND name = :name "
+ "AND id != :id;"),
+ &stmt));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ mObjectStoreId));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mNewName));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mIndexId));
+
+ bool hasResult;
+ MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
+ MOZ_ASSERT(!hasResult);
+ }
+#else
+ Unused << mObjectStoreId;
+#endif
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "UPDATE object_store_index "
+ "SET name = :name "
+ "WHERE id = :id;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mNewName);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mIndexId);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+
+ return NS_OK;
+}
+
+// static
+nsresult
+NormalTransactionOp::ObjectStoreHasIndexes(NormalTransactionOp* aOp,
+ DatabaseConnection* aConnection,
+ const int64_t aObjectStoreId,
+ const bool aMayHaveIndexes,
+ bool* aHasIndexes)
+{
+ MOZ_ASSERT(aOp);
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(aObjectStoreId);
+ MOZ_ASSERT(aHasIndexes);
+
+ bool hasIndexes;
+ if (aOp->Transaction()->GetMode() == IDBTransaction::VERSION_CHANGE &&
+ aMayHaveIndexes) {
+ // If this is a version change transaction then mObjectStoreMayHaveIndexes
+ // could be wrong (e.g. if a unique index failed to be created due to a
+ // constraint error). We have to check on this thread by asking the database
+ // directly.
+ nsresult rv =
+ DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
+ aObjectStoreId,
+ &hasIndexes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ MOZ_ASSERT(NS_SUCCEEDED(
+ DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
+ aObjectStoreId,
+ &hasIndexes)));
+ MOZ_ASSERT(aMayHaveIndexes == hasIndexes);
+
+ hasIndexes = aMayHaveIndexes;
+ }
+
+ *aHasIndexes = hasIndexes;
+ return NS_OK;
+}
+
+nsresult
+NormalTransactionOp::GetPreprocessParams(PreprocessParams& aParams)
+{
+ return NS_OK;
+}
+
+nsresult
+NormalTransactionOp::SendPreprocessInfo()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ PreprocessParams params;
+ nsresult rv = GetPreprocessParams(params);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(params.type() != PreprocessParams::T__None);
+
+ if (NS_WARN_IF(!PBackgroundIDBRequestParent::SendPreprocess(params))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+NormalTransactionOp::SendSuccessResult()
+{
+ AssertIsOnOwningThread();
+
+ if (!IsActorDestroyed()) {
+ RequestResponse response;
+ GetResponse(response);
+
+ MOZ_ASSERT(response.type() != RequestResponse::T__None);
+
+ if (response.type() == RequestResponse::Tnsresult) {
+ MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
+
+ return response.get_nsresult();
+ }
+
+ if (NS_WARN_IF(!PBackgroundIDBRequestParent::Send__delete__(this,
+ response))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+ }
+
+#ifdef DEBUG
+ mResponseSent = true;
+#endif
+
+ return NS_OK;
+}
+
+bool
+NormalTransactionOp::SendFailureResult(nsresult aResultCode)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NS_FAILED(aResultCode));
+
+ bool result = false;
+
+ if (!IsActorDestroyed()) {
+ result =
+ PBackgroundIDBRequestParent::Send__delete__(this,
+ ClampResultCode(aResultCode));
+ }
+
+#ifdef DEBUG
+ mResponseSent = true;
+#endif
+
+ return result;
+}
+
+void
+NormalTransactionOp::Cleanup()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
+
+ TransactionDatabaseOperationBase::Cleanup();
+}
+
+void
+NormalTransactionOp::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnOwningThread();
+
+ NoteActorDestroyed();
+}
+
+bool
+NormalTransactionOp::RecvContinue(const PreprocessResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+
+ switch (aResponse.type()) {
+ case PreprocessResponse::Tnsresult:
+ mResultCode = aResponse.get_nsresult();
+ break;
+
+ case PreprocessResponse::TObjectStoreGetPreprocessResponse:
+ break;
+
+ case PreprocessResponse::TObjectStoreGetAllPreprocessResponse:
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ NoteContinueReceived();
+
+ return true;
+}
+
+ObjectStoreAddOrPutRequestOp::ObjectStoreAddOrPutRequestOp(
+ TransactionBase* aTransaction,
+ const RequestParams& aParams)
+ : NormalTransactionOp(aTransaction)
+ , mParams(aParams.type() == RequestParams::TObjectStoreAddParams ?
+ aParams.get_ObjectStoreAddParams().commonParams() :
+ aParams.get_ObjectStorePutParams().commonParams())
+ , mGroup(aTransaction->GetDatabase()->Group())
+ , mOrigin(aTransaction->GetDatabase()->Origin())
+ , mPersistenceType(aTransaction->GetDatabase()->Type())
+ , mOverwrite(aParams.type() == RequestParams::TObjectStorePutParams)
+ , mObjectStoreMayHaveIndexes(false)
+{
+ MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreAddParams ||
+ aParams.type() == RequestParams::TObjectStorePutParams);
+
+ mMetadata =
+ aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
+ MOZ_ASSERT(mMetadata);
+
+ mObjectStoreMayHaveIndexes = mMetadata->HasLiveIndexes();
+
+ mDataOverThreshold =
+ snappy::MaxCompressedLength(mParams.cloneInfo().data().data.Size()) >
+ IndexedDatabaseManager::DataThreshold();
+}
+
+nsresult
+ObjectStoreAddOrPutRequestOp::RemoveOldIndexDataValues(
+ DatabaseConnection* aConnection)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(aConnection);
+ MOZ_ASSERT(mOverwrite);
+ MOZ_ASSERT(!mResponse.IsUnset());
+
+#ifdef DEBUG
+ {
+ bool hasIndexes = false;
+ MOZ_ASSERT(NS_SUCCEEDED(
+ DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
+ mParams.objectStoreId(),
+ &hasIndexes)));
+ MOZ_ASSERT(hasIndexes,
+ "Don't use this slow method if there are no indexes!");
+ }
+#endif
+
+ DatabaseConnection::CachedStatement indexValuesStmt;
+ nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "SELECT index_data_values "
+ "FROM object_data "
+ "WHERE object_store_id = :object_store_id "
+ "AND key = :key;"),
+ &indexValuesStmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = indexValuesStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ mParams.objectStoreId());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mResponse.BindToStatement(indexValuesStmt, NS_LITERAL_CSTRING("key"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasResult;
+ rv = indexValuesStmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasResult) {
+ AutoTArray<IndexDataValue, 32> existingIndexValues;
+ rv = ReadCompressedIndexDataValues(indexValuesStmt,
+ 0,
+ existingIndexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = DeleteIndexDataTableRows(aConnection, mResponse, existingIndexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+ObjectStoreAddOrPutRequestOp::Init(TransactionBase* aTransaction)
+{
+ AssertIsOnOwningThread();
+
+ const nsTArray<IndexUpdateInfo>& indexUpdateInfos =
+ mParams.indexUpdateInfos();
+
+ if (!indexUpdateInfos.IsEmpty()) {
+ const uint32_t count = indexUpdateInfos.Length();
+
+ mUniqueIndexTable.emplace();
+
+ for (uint32_t index = 0; index < count; index++) {
+ const IndexUpdateInfo& updateInfo = indexUpdateInfos[index];
+
+ RefPtr<FullIndexMetadata> indexMetadata;
+ MOZ_ALWAYS_TRUE(mMetadata->mIndexes.Get(updateInfo.indexId(),
+ getter_AddRefs(indexMetadata)));
+
+ MOZ_ASSERT(!indexMetadata->mDeleted);
+
+ const int64_t& indexId = indexMetadata->mCommonMetadata.id();
+ const bool& unique = indexMetadata->mCommonMetadata.unique();
+
+ MOZ_ASSERT(indexId == updateInfo.indexId());
+ MOZ_ASSERT_IF(!indexMetadata->mCommonMetadata.multiEntry(),
+ !mUniqueIndexTable.ref().Get(indexId));
+
+ if (NS_WARN_IF(!mUniqueIndexTable.ref().Put(indexId, unique, fallible))) {
+ return false;
+ }
+ }
+ } else if (mOverwrite) {
+ mUniqueIndexTable.emplace();
+ }
+
+#ifdef DEBUG
+ if (mUniqueIndexTable.isSome()) {
+ mUniqueIndexTable.ref().MarkImmutable();
+ }
+#endif
+
+ const nsTArray<FileAddInfo>& fileAddInfos = mParams.fileAddInfos();
+
+ if (!fileAddInfos.IsEmpty()) {
+ const uint32_t count = fileAddInfos.Length();
+
+ if (NS_WARN_IF(!mStoredFileInfos.SetCapacity(count, fallible))) {
+ return false;
+ }
+
+ for (uint32_t index = 0; index < count; index++) {
+ const FileAddInfo& fileAddInfo = fileAddInfos[index];
+
+ MOZ_ASSERT(fileAddInfo.type() == StructuredCloneFile::eBlob ||
+ fileAddInfo.type() == StructuredCloneFile::eMutableFile ||
+ fileAddInfo.type() == StructuredCloneFile::eWasmBytecode ||
+ fileAddInfo.type() == StructuredCloneFile::eWasmCompiled);
+
+ const DatabaseOrMutableFile& file = fileAddInfo.file();
+
+ StoredFileInfo* storedFileInfo = mStoredFileInfos.AppendElement(fallible);
+ MOZ_ASSERT(storedFileInfo);
+
+ switch (fileAddInfo.type()) {
+ case StructuredCloneFile::eBlob: {
+ MOZ_ASSERT(file.type() ==
+ DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent);
+
+ storedFileInfo->mFileActor =
+ static_cast<DatabaseFile*>(
+ file.get_PBackgroundIDBDatabaseFileParent());
+ MOZ_ASSERT(storedFileInfo->mFileActor);
+
+ storedFileInfo->mFileInfo = storedFileInfo->mFileActor->GetFileInfo();
+ MOZ_ASSERT(storedFileInfo->mFileInfo);
+
+ storedFileInfo->mType = StructuredCloneFile::eBlob;
+ break;
+ }
+
+ case StructuredCloneFile::eMutableFile: {
+ MOZ_ASSERT(file.type() ==
+ DatabaseOrMutableFile::TPBackgroundMutableFileParent);
+
+ auto mutableFileActor =
+ static_cast<MutableFile*>(
+ file.get_PBackgroundMutableFileParent());
+ MOZ_ASSERT(mutableFileActor);
+
+ storedFileInfo->mFileInfo = mutableFileActor->GetFileInfo();
+ MOZ_ASSERT(storedFileInfo->mFileInfo);
+
+ storedFileInfo->mType = StructuredCloneFile::eMutableFile;
+ break;
+ }
+
+ case StructuredCloneFile::eWasmBytecode:
+ case StructuredCloneFile::eWasmCompiled: {
+ MOZ_ASSERT(file.type() ==
+ DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent);
+
+ storedFileInfo->mFileActor =
+ static_cast<DatabaseFile*>(
+ file.get_PBackgroundIDBDatabaseFileParent());
+ MOZ_ASSERT(storedFileInfo->mFileActor);
+
+ storedFileInfo->mFileInfo = storedFileInfo->mFileActor->GetFileInfo();
+ MOZ_ASSERT(storedFileInfo->mFileInfo);
+
+ storedFileInfo->mType = fileAddInfo.type();
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ }
+ }
+
+ if (mDataOverThreshold) {
+ StoredFileInfo* storedFileInfo = mStoredFileInfos.AppendElement(fallible);
+ MOZ_ASSERT(storedFileInfo);
+
+ RefPtr<FileManager> fileManager =
+ aTransaction->GetDatabase()->GetFileManager();
+ MOZ_ASSERT(fileManager);
+
+ storedFileInfo->mFileInfo = fileManager->GetNewFileInfo();
+
+ storedFileInfo->mInputStream =
+ new SCInputStream(mParams.cloneInfo().data().data);
+
+ storedFileInfo->mType = StructuredCloneFile::eStructuredClone;
+ }
+
+ return true;
+}
+
+nsresult
+ObjectStoreAddOrPutRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(aConnection->GetStorageConnection());
+
+ PROFILER_LABEL("IndexedDB",
+ "ObjectStoreAddOrPutRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
+ return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool objectStoreHasIndexes;
+ rv = ObjectStoreHasIndexes(this,
+ aConnection,
+ mParams.objectStoreId(),
+ mObjectStoreMayHaveIndexes,
+ &objectStoreHasIndexes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // This will be the final key we use.
+ Key& key = mResponse;
+ key = mParams.key();
+
+ const bool keyUnset = key.IsUnset();
+ const int64_t osid = mParams.objectStoreId();
+
+ // First delete old index_data_values if we're overwriting something and we
+ // have indexes.
+ if (mOverwrite && !keyUnset && objectStoreHasIndexes) {
+ rv = RemoveOldIndexDataValues(aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // The "|| keyUnset" here is mostly a debugging tool. If a key isn't
+ // specified we should never have a collision and so it shouldn't matter
+ // if we allow overwrite or not. By not allowing overwrite we raise
+ // detectable errors rather than corrupting data.
+ DatabaseConnection::CachedStatement stmt;
+ if (!mOverwrite || keyUnset) {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO object_data "
+ "(object_store_id, key, file_ids, data) "
+ "VALUES (:osid, :key, :file_ids, :data);"),
+ &stmt);
+ } else {
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "INSERT OR REPLACE INTO object_data "
+ "(object_store_id, key, file_ids, data) "
+ "VALUES (:osid, :key, :file_ids, :data);"),
+ &stmt);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), osid);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ const SerializedStructuredCloneWriteInfo& cloneInfo = mParams.cloneInfo();
+ const JSStructuredCloneData& cloneData = cloneInfo.data().data;
+ size_t cloneDataSize = cloneData.Size();
+
+ MOZ_ASSERT(!keyUnset || mMetadata->mCommonMetadata.autoIncrement(),
+ "Should have key unless autoIncrement");
+
+ int64_t autoIncrementNum = 0;
+
+ if (mMetadata->mCommonMetadata.autoIncrement()) {
+ if (keyUnset) {
+ autoIncrementNum = mMetadata->mNextAutoIncrementId;
+
+ MOZ_ASSERT(autoIncrementNum > 0);
+
+ if (autoIncrementNum > (1LL << 53)) {
+ return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
+ }
+
+ key.SetFromInteger(autoIncrementNum);
+ } else if (key.IsFloat() &&
+ key.ToFloat() >= mMetadata->mNextAutoIncrementId) {
+ autoIncrementNum = floor(key.ToFloat());
+ }
+
+ if (keyUnset && mMetadata->mCommonMetadata.keyPath().IsValid()) {
+ const SerializedStructuredCloneWriteInfo& cloneInfo = mParams.cloneInfo();
+ MOZ_ASSERT(cloneInfo.offsetToKeyProp());
+ MOZ_ASSERT(cloneDataSize > sizeof(uint64_t));
+ MOZ_ASSERT(cloneInfo.offsetToKeyProp() <=
+ (cloneDataSize - sizeof(uint64_t)));
+
+ // Special case where someone put an object into an autoIncrement'ing
+ // objectStore with no key in its keyPath set. We needed to figure out
+ // which row id we would get above before we could set that properly.
+ uint64_t keyPropValue =
+ ReinterpretDoubleAsUInt64(static_cast<double>(autoIncrementNum));
+
+ static const size_t keyPropSize = sizeof(uint64_t);
+
+ char keyPropBuffer[keyPropSize];
+ LittleEndian::writeUint64(keyPropBuffer, keyPropValue);
+
+ auto iter = cloneData.Iter();
+ DebugOnly<bool> result =
+ iter.AdvanceAcrossSegments(cloneData, cloneInfo.offsetToKeyProp());
+ MOZ_ASSERT(result);
+
+ for (uint32_t index = 0; index < keyPropSize; index++) {
+ char* keyPropPointer = iter.Data();
+ *keyPropPointer = keyPropBuffer[index];
+
+ result = iter.AdvanceAcrossSegments(cloneData, 1);
+ MOZ_ASSERT(result);
+ }
+ }
+ }
+
+ key.BindToStatement(stmt, NS_LITERAL_CSTRING("key"));
+
+ if (mDataOverThreshold) {
+ // The data we store in the SQLite database is a (signed) 64-bit integer.
+ // The flags are left-shifted 32 bits so the max value is 0xFFFFFFFF.
+ // The file_ids index occupies the lower 32 bits and its max is 0xFFFFFFFF.
+ static const uint32_t kCompressedFlag = (1<<0);
+
+ uint32_t flags = 0;
+ flags |= kCompressedFlag;
+
+ uint32_t index = mStoredFileInfos.Length() - 1;
+
+ int64_t data = (uint64_t(flags) << 32) | index;
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("data"), data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ nsCString flatCloneData;
+ flatCloneData.SetLength(cloneDataSize);
+ auto iter = cloneData.Iter();
+ cloneData.ReadBytes(iter, flatCloneData.BeginWriting(), cloneDataSize);
+
+ // Compress the bytes before adding into the database.
+ const char* uncompressed = flatCloneData.BeginReading();
+ size_t uncompressedLength = cloneDataSize;
+
+ size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
+
+ UniqueFreePtr<char> compressed(
+ static_cast<char*>(malloc(compressedLength)));
+ if (NS_WARN_IF(!compressed)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(),
+ &compressedLength);
+
+ uint8_t* dataBuffer = reinterpret_cast<uint8_t*>(compressed.release());
+ size_t dataBufferLength = compressedLength;
+
+ rv = stmt->BindAdoptedBlobByName(NS_LITERAL_CSTRING("data"), dataBuffer,
+ dataBufferLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (!mStoredFileInfos.IsEmpty()) {
+ // Moved outside the loop to allow it to be cached when demanded by the
+ // first write. (We may have mStoredFileInfos without any required writes.)
+ Maybe<FileHelper> fileHelper;
+ nsAutoString fileIds;
+
+ for (uint32_t count = mStoredFileInfos.Length(), index = 0;
+ index < count;
+ index++) {
+ StoredFileInfo& storedFileInfo = mStoredFileInfos[index];
+ MOZ_ASSERT(storedFileInfo.mFileInfo);
+
+ // If there is a StoredFileInfo, then one of the following is true:
+ // - This was an overflow structured clone and storedFileInfo.mInputStream
+ // MUST be non-null.
+ // - This is a reference to a Blob that may or may not have already been
+ // written to disk. storedFileInfo.mFileActor MUST be non-null, but
+ // its GetBlockingInputStream may return null (so don't assert on them).
+ // - It's a mutable file. No writing will be performed.
+ MOZ_ASSERT(storedFileInfo.mInputStream || storedFileInfo.mFileActor ||
+ storedFileInfo.mType == StructuredCloneFile::eMutableFile);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ // Check for an explicit stream, like a structured clone stream.
+ storedFileInfo.mInputStream.swap(inputStream);
+ // Check for a blob-backed stream otherwise.
+ if (!inputStream && storedFileInfo.mFileActor) {
+ ErrorResult streamRv;
+ inputStream =
+ storedFileInfo.mFileActor->GetBlockingInputStream(streamRv);
+ if (NS_WARN_IF(streamRv.Failed())) {
+ return streamRv.StealNSResult();
+ }
+ }
+
+ if (inputStream) {
+ if (fileHelper.isNothing()) {
+ RefPtr<FileManager> fileManager =
+ Transaction()->GetDatabase()->GetFileManager();
+ MOZ_ASSERT(fileManager);
+
+ fileHelper.emplace(fileManager);
+ rv = fileHelper->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+ }
+
+ RefPtr<FileInfo>& fileInfo = storedFileInfo.mFileInfo;
+
+ nsCOMPtr<nsIFile> file = fileHelper->GetFile(fileInfo);
+ if (NS_WARN_IF(!file)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ nsCOMPtr<nsIFile> journalFile =
+ fileHelper->GetJournalFile(fileInfo);
+ if (NS_WARN_IF(!journalFile)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ bool compress =
+ storedFileInfo.mType == StructuredCloneFile::eStructuredClone;
+
+ rv = fileHelper->CreateFileFromStream(file,
+ journalFile,
+ inputStream,
+ compress);
+ if (NS_FAILED(rv) &&
+ NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) {
+ IDB_REPORT_INTERNAL_ERR();
+ rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Try to remove the file if the copy failed.
+ nsresult rv2 = fileHelper->RemoveFile(file, journalFile);
+ if (NS_WARN_IF(NS_FAILED(rv2))) {
+ return rv;
+ }
+ return rv;
+ }
+
+ if (storedFileInfo.mFileActor) {
+ storedFileInfo.mFileActor->WriteSucceededClearBlobImpl();
+ }
+ }
+
+ if (index) {
+ fileIds.Append(' ');
+ }
+ storedFileInfo.Serialize(fileIds);
+ }
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = stmt->Execute();
+ if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
+ MOZ_ASSERT(!keyUnset, "Generated key had a collision!");
+ return rv;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update our indexes if needed.
+ if (!mParams.indexUpdateInfos().IsEmpty()) {
+ MOZ_ASSERT(mUniqueIndexTable.isSome());
+
+ // Write the index_data_values column.
+ AutoTArray<IndexDataValue, 32> indexValues;
+ rv = IndexDataValuesFromUpdateInfos(mParams.indexUpdateInfos(),
+ mUniqueIndexTable.ref(),
+ indexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = UpdateIndexValues(aConnection, osid, key, indexValues);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = InsertIndexTableRows(aConnection, osid, key, indexValues);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (autoIncrementNum) {
+ mMetadata->mNextAutoIncrementId = autoIncrementNum + 1;
+ Transaction()->NoteModifiedAutoIncrementObjectStore(mMetadata);
+ }
+
+ return NS_OK;
+}
+
+void
+ObjectStoreAddOrPutRequestOp::GetResponse(RequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+
+ if (mOverwrite) {
+ aResponse = ObjectStorePutResponse(mResponse);
+ } else {
+ aResponse = ObjectStoreAddResponse(mResponse);
+ }
+}
+
+void
+ObjectStoreAddOrPutRequestOp::Cleanup()
+{
+ AssertIsOnOwningThread();
+
+ mStoredFileInfos.Clear();
+
+ NormalTransactionOp::Cleanup();
+}
+
+NS_IMPL_ISUPPORTS(ObjectStoreAddOrPutRequestOp::SCInputStream, nsIInputStream)
+
+NS_IMETHODIMP
+ObjectStoreAddOrPutRequestOp::
+SCInputStream::Close()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObjectStoreAddOrPutRequestOp::
+SCInputStream::Available(uint64_t* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObjectStoreAddOrPutRequestOp::
+SCInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+ObjectStoreAddOrPutRequestOp::
+SCInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* _retval)
+{
+ *_retval = 0;
+
+ while (aCount) {
+ uint32_t count = std::min(uint32_t(mIter.RemainingInSegment()), aCount);
+ if (!count) {
+ // We've run out of data in the last segment.
+ break;
+ }
+
+ uint32_t written;
+ nsresult rv =
+ aWriter(this, aClosure, mIter.Data(), *_retval, count, &written);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // InputStreams do not propagate errors to caller.
+ return NS_OK;
+ }
+
+ // Writer should write what we asked it to write.
+ MOZ_ASSERT(written == count);
+
+ *_retval += count;
+ aCount -= count;
+
+ mIter.Advance(mData, count);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObjectStoreAddOrPutRequestOp::
+SCInputStream::IsNonBlocking(bool* _retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+ObjectStoreGetRequestOp::ObjectStoreGetRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams,
+ bool aGetAll)
+ : NormalTransactionOp(aTransaction)
+ , mObjectStoreId(aGetAll ?
+ aParams.get_ObjectStoreGetAllParams().objectStoreId() :
+ aParams.get_ObjectStoreGetParams().objectStoreId())
+ , mDatabase(aTransaction->GetDatabase())
+ , mOptionalKeyRange(aGetAll ?
+ aParams.get_ObjectStoreGetAllParams()
+ .optionalKeyRange() :
+ OptionalKeyRange(aParams.get_ObjectStoreGetParams()
+ .keyRange()))
+ , mBackgroundParent(aTransaction->GetBackgroundParent())
+ , mPreprocessInfoCount(0)
+ , mLimit(aGetAll ? aParams.get_ObjectStoreGetAllParams().limit() : 1)
+ , mGetAll(aGetAll)
+{
+ MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetParams ||
+ aParams.type() == RequestParams::TObjectStoreGetAllParams);
+ MOZ_ASSERT(mObjectStoreId);
+ MOZ_ASSERT(mDatabase);
+ MOZ_ASSERT_IF(!aGetAll,
+ mOptionalKeyRange.type() ==
+ OptionalKeyRange::TSerializedKeyRange);
+ MOZ_ASSERT(mBackgroundParent);
+}
+
+template <typename T>
+void MoveData(StructuredCloneReadInfo& aInfo, T& aResult);
+
+template <>
+void
+MoveData<SerializedStructuredCloneReadInfo>(
+ StructuredCloneReadInfo& aInfo,
+ SerializedStructuredCloneReadInfo& aResult)
+{
+ aResult.data().data = Move(aInfo.mData);
+ aResult.hasPreprocessInfo() = aInfo.mHasPreprocessInfo;
+}
+
+template <>
+void
+MoveData<WasmModulePreprocessInfo>(StructuredCloneReadInfo& aInfo,
+ WasmModulePreprocessInfo& aResult)
+{
+}
+
+template <bool aForPreprocess, typename T>
+nsresult
+ObjectStoreGetRequestOp::ConvertResponse(StructuredCloneReadInfo& aInfo,
+ T& aResult)
+{
+ MoveData(aInfo, aResult);
+
+ FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
+ nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
+ mDatabase,
+ aInfo.mFiles,
+ aForPreprocess,
+ serializedFiles);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(aResult.files().IsEmpty());
+
+ aResult.files().SwapElements(serializedFiles);
+
+ return NS_OK;
+}
+
+nsresult
+ObjectStoreGetRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT_IF(!mGetAll,
+ mOptionalKeyRange.type() ==
+ OptionalKeyRange::TSerializedKeyRange);
+ MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
+
+ PROFILER_LABEL("IndexedDB",
+ "ObjectStoreGetRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool hasKeyRange =
+ mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
+
+ nsAutoCString keyRangeClause;
+ if (hasKeyRange) {
+ GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
+ NS_LITERAL_CSTRING("key"),
+ keyRangeClause);
+ }
+
+ nsCString limitClause;
+ if (mLimit) {
+ limitClause.AssignLiteral(" LIMIT ");
+ limitClause.AppendInt(mLimit);
+ }
+
+ nsCString query =
+ NS_LITERAL_CSTRING("SELECT file_ids, data "
+ "FROM object_data "
+ "WHERE object_store_id = :osid") +
+ keyRangeClause +
+ NS_LITERAL_CSTRING(" ORDER BY key ASC") +
+ limitClause;
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(query, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasKeyRange) {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ StructuredCloneReadInfo* cloneInfo = mResponse.AppendElement(fallible);
+ if (NS_WARN_IF(!cloneInfo)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 0,
+ mDatabase->GetFileManager(),
+ cloneInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (cloneInfo->mHasPreprocessInfo) {
+ mPreprocessInfoCount++;
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
+
+ return NS_OK;
+}
+
+bool
+ObjectStoreGetRequestOp::HasPreprocessInfo()
+{
+ return mPreprocessInfoCount > 0;
+}
+
+nsresult
+ObjectStoreGetRequestOp::GetPreprocessParams(PreprocessParams& aParams)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mResponse.IsEmpty());
+
+ if (mGetAll) {
+ aParams = ObjectStoreGetAllPreprocessParams();
+
+ FallibleTArray<WasmModulePreprocessInfo> falliblePreprocessInfos;
+ if (NS_WARN_IF(!falliblePreprocessInfos.SetLength(mPreprocessInfoCount,
+ fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t fallibleIndex = 0;
+ for (uint32_t count = mResponse.Length(), index = 0;
+ index < count;
+ index++) {
+ StructuredCloneReadInfo& info = mResponse[index];
+
+ if (info.mHasPreprocessInfo) {
+ nsresult rv =
+ ConvertResponse<true>(info, falliblePreprocessInfos[fallibleIndex++]);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ nsTArray<WasmModulePreprocessInfo>& preprocessInfos =
+ aParams.get_ObjectStoreGetAllPreprocessParams().preprocessInfos();
+
+ falliblePreprocessInfos.SwapElements(preprocessInfos);
+
+ return NS_OK;
+ }
+
+ aParams = ObjectStoreGetPreprocessParams();
+
+ WasmModulePreprocessInfo& preprocessInfo =
+ aParams.get_ObjectStoreGetPreprocessParams().preprocessInfo();
+
+ nsresult rv = ConvertResponse<true>(mResponse[0], preprocessInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+ObjectStoreGetRequestOp::GetResponse(RequestResponse& aResponse)
+{
+ MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
+
+ if (mGetAll) {
+ aResponse = ObjectStoreGetAllResponse();
+
+ if (!mResponse.IsEmpty()) {
+ FallibleTArray<SerializedStructuredCloneReadInfo> fallibleCloneInfos;
+ if (NS_WARN_IF(!fallibleCloneInfos.SetLength(mResponse.Length(),
+ fallible))) {
+ aResponse = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ for (uint32_t count = mResponse.Length(), index = 0;
+ index < count;
+ index++) {
+ nsresult rv =
+ ConvertResponse<false>(mResponse[index], fallibleCloneInfos[index]);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResponse = rv;
+ return;
+ }
+ }
+
+ nsTArray<SerializedStructuredCloneReadInfo>& cloneInfos =
+ aResponse.get_ObjectStoreGetAllResponse().cloneInfos();
+
+ fallibleCloneInfos.SwapElements(cloneInfos);
+ }
+
+ return;
+ }
+
+ aResponse = ObjectStoreGetResponse();
+
+ if (!mResponse.IsEmpty()) {
+ SerializedStructuredCloneReadInfo& serializedInfo =
+ aResponse.get_ObjectStoreGetResponse().cloneInfo();
+
+ nsresult rv = ConvertResponse<false>(mResponse[0], serializedInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResponse = rv;
+ }
+ }
+}
+
+ObjectStoreGetKeyRequestOp::ObjectStoreGetKeyRequestOp(
+ TransactionBase* aTransaction,
+ const RequestParams& aParams,
+ bool aGetAll)
+ : NormalTransactionOp(aTransaction)
+ , mObjectStoreId(aGetAll ?
+ aParams.get_ObjectStoreGetAllKeysParams().objectStoreId() :
+ aParams.get_ObjectStoreGetKeyParams().objectStoreId())
+ , mOptionalKeyRange(aGetAll ?
+ aParams.get_ObjectStoreGetAllKeysParams()
+ .optionalKeyRange() :
+ OptionalKeyRange(aParams.get_ObjectStoreGetKeyParams()
+ .keyRange()))
+ , mLimit(aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().limit() : 1)
+ , mGetAll(aGetAll)
+{
+ MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetKeyParams ||
+ aParams.type() == RequestParams::TObjectStoreGetAllKeysParams);
+ MOZ_ASSERT(mObjectStoreId);
+ MOZ_ASSERT_IF(!aGetAll,
+ mOptionalKeyRange.type() ==
+ OptionalKeyRange::TSerializedKeyRange);
+}
+
+nsresult
+ObjectStoreGetKeyRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "ObjectStoreGetKeyRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool hasKeyRange =
+ mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
+
+ nsAutoCString keyRangeClause;
+ if (hasKeyRange) {
+ GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
+ NS_LITERAL_CSTRING("key"),
+ keyRangeClause);
+ }
+
+ nsAutoCString limitClause;
+ if (mLimit) {
+ limitClause.AssignLiteral(" LIMIT ");
+ limitClause.AppendInt(mLimit);
+ }
+
+ nsCString query =
+ NS_LITERAL_CSTRING("SELECT key "
+ "FROM object_data "
+ "WHERE object_store_id = :osid") +
+ keyRangeClause +
+ NS_LITERAL_CSTRING(" ORDER BY key ASC") +
+ limitClause;
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(query, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasKeyRange) {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ Key* key = mResponse.AppendElement(fallible);
+ if (NS_WARN_IF(!key)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = key->SetFromStatement(stmt, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
+
+ return NS_OK;
+}
+
+void
+ObjectStoreGetKeyRequestOp::GetResponse(RequestResponse& aResponse)
+{
+ MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
+
+ if (mGetAll) {
+ aResponse = ObjectStoreGetAllKeysResponse();
+
+ if (!mResponse.IsEmpty()) {
+ nsTArray<Key>& response =
+ aResponse.get_ObjectStoreGetAllKeysResponse().keys();
+ mResponse.SwapElements(response);
+ }
+
+ return;
+ }
+
+ aResponse = ObjectStoreGetKeyResponse();
+
+ if (!mResponse.IsEmpty()) {
+ aResponse.get_ObjectStoreGetKeyResponse().key() = Move(mResponse[0]);
+ }
+}
+
+ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp(
+ TransactionBase* aTransaction,
+ const ObjectStoreDeleteParams& aParams)
+ : NormalTransactionOp(aTransaction)
+ , mParams(aParams)
+ , mObjectStoreMayHaveIndexes(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransaction);
+
+ RefPtr<FullObjectStoreMetadata> metadata =
+ aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
+ MOZ_ASSERT(metadata);
+
+ mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes();
+}
+
+nsresult
+ObjectStoreDeleteRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ PROFILER_LABEL("IndexedDB",
+ "ObjectStoreDeleteRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool objectStoreHasIndexes;
+ rv = ObjectStoreHasIndexes(this,
+ aConnection,
+ mParams.objectStoreId(),
+ mObjectStoreMayHaveIndexes,
+ &objectStoreHasIndexes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (objectStoreHasIndexes) {
+ rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection,
+ mParams.objectStoreId(),
+ mParams.keyRange());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
+
+ nsAutoCString keyRangeClause;
+ GetBindingClauseForKeyRange(mParams.keyRange(),
+ NS_LITERAL_CSTRING("key"),
+ keyRangeClause);
+
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(
+ NS_LITERAL_CSTRING("DELETE FROM object_data "
+ "WHERE object_store_id = :") + objectStoreIdString +
+ keyRangeClause +
+ NS_LITERAL_CSTRING(";"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(objectStoreIdString, mParams.objectStoreId());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = BindKeyRangeToStatement(mParams.keyRange(), stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+ObjectStoreClearRequestOp::ObjectStoreClearRequestOp(
+ TransactionBase* aTransaction,
+ const ObjectStoreClearParams& aParams)
+ : NormalTransactionOp(aTransaction)
+ , mParams(aParams)
+ , mObjectStoreMayHaveIndexes(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransaction);
+
+ RefPtr<FullObjectStoreMetadata> metadata =
+ aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
+ MOZ_ASSERT(metadata);
+
+ mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes();
+}
+
+nsresult
+ObjectStoreClearRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "ObjectStoreClearRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ DatabaseConnection::AutoSavepoint autoSave;
+ nsresult rv = autoSave.Start(Transaction());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool objectStoreHasIndexes;
+ rv = ObjectStoreHasIndexes(this,
+ aConnection,
+ mParams.objectStoreId(),
+ mObjectStoreMayHaveIndexes,
+ &objectStoreHasIndexes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (objectStoreHasIndexes) {
+ rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection,
+ mParams.objectStoreId(),
+ void_t());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ DatabaseConnection::CachedStatement stmt;
+ rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM object_data "
+ "WHERE object_store_id = :object_store_id;"),
+ &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+ mParams.objectStoreId());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = autoSave.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ObjectStoreCountRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "ObjectStoreCountRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool hasKeyRange =
+ mParams.optionalKeyRange().type() == OptionalKeyRange::TSerializedKeyRange;
+
+ nsAutoCString keyRangeClause;
+ if (hasKeyRange) {
+ GetBindingClauseForKeyRange(
+ mParams.optionalKeyRange().get_SerializedKeyRange(),
+ NS_LITERAL_CSTRING("key"),
+ keyRangeClause);
+ }
+
+ nsCString query =
+ NS_LITERAL_CSTRING("SELECT count(*) "
+ "FROM object_data "
+ "WHERE object_store_id = :osid") +
+ keyRangeClause;
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(query, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"),
+ mParams.objectStoreId());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasKeyRange) {
+ rv = BindKeyRangeToStatement(
+ mParams.optionalKeyRange().get_SerializedKeyRange(),
+ stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!hasResult)) {
+ MOZ_ASSERT(false, "This should never be possible!");
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ int64_t count = stmt->AsInt64(0);
+ if (NS_WARN_IF(count < 0)) {
+ MOZ_ASSERT(false, "This should never be possible!");
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mResponse.count() = count;
+
+ return NS_OK;
+}
+
+// static
+already_AddRefed<FullIndexMetadata>
+IndexRequestOpBase::IndexMetadataForParams(TransactionBase* aTransaction,
+ const RequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aTransaction);
+ MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams ||
+ aParams.type() == RequestParams::TIndexGetKeyParams ||
+ aParams.type() == RequestParams::TIndexGetAllParams ||
+ aParams.type() == RequestParams::TIndexGetAllKeysParams ||
+ aParams.type() == RequestParams::TIndexCountParams);
+
+ uint64_t objectStoreId;
+ uint64_t indexId;
+
+ switch (aParams.type()) {
+ case RequestParams::TIndexGetParams: {
+ const IndexGetParams& params = aParams.get_IndexGetParams();
+ objectStoreId = params.objectStoreId();
+ indexId = params.indexId();
+ break;
+ }
+
+ case RequestParams::TIndexGetKeyParams: {
+ const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams();
+ objectStoreId = params.objectStoreId();
+ indexId = params.indexId();
+ break;
+ }
+
+ case RequestParams::TIndexGetAllParams: {
+ const IndexGetAllParams& params = aParams.get_IndexGetAllParams();
+ objectStoreId = params.objectStoreId();
+ indexId = params.indexId();
+ break;
+ }
+
+ case RequestParams::TIndexGetAllKeysParams: {
+ const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams();
+ objectStoreId = params.objectStoreId();
+ indexId = params.indexId();
+ break;
+ }
+
+ case RequestParams::TIndexCountParams: {
+ const IndexCountParams& params = aParams.get_IndexCountParams();
+ objectStoreId = params.objectStoreId();
+ indexId = params.indexId();
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
+ aTransaction->GetMetadataForObjectStoreId(objectStoreId);
+ MOZ_ASSERT(objectStoreMetadata);
+
+ RefPtr<FullIndexMetadata> indexMetadata =
+ aTransaction->GetMetadataForIndexId(objectStoreMetadata, indexId);
+ MOZ_ASSERT(indexMetadata);
+
+ return indexMetadata.forget();
+}
+
+IndexGetRequestOp::IndexGetRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams,
+ bool aGetAll)
+ : IndexRequestOpBase(aTransaction, aParams)
+ , mDatabase(aTransaction->GetDatabase())
+ , mOptionalKeyRange(aGetAll ?
+ aParams.get_IndexGetAllParams().optionalKeyRange() :
+ OptionalKeyRange(aParams.get_IndexGetParams()
+ .keyRange()))
+ , mBackgroundParent(aTransaction->GetBackgroundParent())
+ , mLimit(aGetAll ? aParams.get_IndexGetAllParams().limit() : 1)
+ , mGetAll(aGetAll)
+{
+ MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams ||
+ aParams.type() == RequestParams::TIndexGetAllParams);
+ MOZ_ASSERT(mDatabase);
+ MOZ_ASSERT_IF(!aGetAll,
+ mOptionalKeyRange.type() ==
+ OptionalKeyRange::TSerializedKeyRange);
+ MOZ_ASSERT(mBackgroundParent);
+}
+
+nsresult
+IndexGetRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT_IF(!mGetAll,
+ mOptionalKeyRange.type() ==
+ OptionalKeyRange::TSerializedKeyRange);
+ MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
+
+ PROFILER_LABEL("IndexedDB",
+ "IndexGetRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool hasKeyRange =
+ mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
+
+ nsCString indexTable;
+ if (mMetadata->mCommonMetadata.unique()) {
+ indexTable.AssignLiteral("unique_index_data ");
+ }
+ else {
+ indexTable.AssignLiteral("index_data ");
+ }
+
+ nsAutoCString keyRangeClause;
+ if (hasKeyRange) {
+ GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
+ NS_LITERAL_CSTRING("value"),
+ keyRangeClause);
+ }
+
+ nsCString limitClause;
+ if (mLimit) {
+ limitClause.AssignLiteral(" LIMIT ");
+ limitClause.AppendInt(mLimit);
+ }
+
+ nsCString query =
+ NS_LITERAL_CSTRING("SELECT file_ids, data "
+ "FROM object_data "
+ "INNER JOIN ") +
+ indexTable +
+ NS_LITERAL_CSTRING("AS index_table "
+ "ON object_data.object_store_id = "
+ "index_table.object_store_id "
+ "AND object_data.key = "
+ "index_table.object_data_key "
+ "WHERE index_id = :index_id") +
+ keyRangeClause +
+ limitClause;
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(query, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
+ mMetadata->mCommonMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasKeyRange) {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ StructuredCloneReadInfo* cloneInfo = mResponse.AppendElement(fallible);
+ if (NS_WARN_IF(!cloneInfo)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 0,
+ mDatabase->GetFileManager(),
+ cloneInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (cloneInfo->mHasPreprocessInfo) {
+ IDB_WARNING("Preprocessing for indexes not yet implemented!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
+
+ return NS_OK;
+}
+
+void
+IndexGetRequestOp::GetResponse(RequestResponse& aResponse)
+{
+ MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
+
+ if (mGetAll) {
+ aResponse = IndexGetAllResponse();
+
+ if (!mResponse.IsEmpty()) {
+ FallibleTArray<SerializedStructuredCloneReadInfo> fallibleCloneInfos;
+ if (NS_WARN_IF(!fallibleCloneInfos.SetLength(mResponse.Length(),
+ fallible))) {
+ aResponse = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ for (uint32_t count = mResponse.Length(), index = 0;
+ index < count;
+ index++) {
+ StructuredCloneReadInfo& info = mResponse[index];
+
+ SerializedStructuredCloneReadInfo& serializedInfo =
+ fallibleCloneInfos[index];
+
+ serializedInfo.data().data = Move(info.mData);
+
+ FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
+ nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
+ mDatabase,
+ info.mFiles,
+ /* aForPreprocess */ false,
+ serializedFiles);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResponse = rv;
+ return;
+ }
+
+ MOZ_ASSERT(serializedInfo.files().IsEmpty());
+
+ serializedInfo.files().SwapElements(serializedFiles);
+ }
+
+ nsTArray<SerializedStructuredCloneReadInfo>& cloneInfos =
+ aResponse.get_IndexGetAllResponse().cloneInfos();
+
+ fallibleCloneInfos.SwapElements(cloneInfos);
+ }
+
+ return;
+ }
+
+ aResponse = IndexGetResponse();
+
+ if (!mResponse.IsEmpty()) {
+ StructuredCloneReadInfo& info = mResponse[0];
+
+ SerializedStructuredCloneReadInfo& serializedInfo =
+ aResponse.get_IndexGetResponse().cloneInfo();
+
+ serializedInfo.data().data = Move(info.mData);
+
+ FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
+ nsresult rv =
+ SerializeStructuredCloneFiles(mBackgroundParent,
+ mDatabase,
+ info.mFiles,
+ /* aForPreprocess */ false,
+ serializedFiles);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResponse = rv;
+ return;
+ }
+
+ MOZ_ASSERT(serializedInfo.files().IsEmpty());
+
+ serializedInfo.files().SwapElements(serializedFiles);
+ }
+}
+
+IndexGetKeyRequestOp::IndexGetKeyRequestOp(TransactionBase* aTransaction,
+ const RequestParams& aParams,
+ bool aGetAll)
+ : IndexRequestOpBase(aTransaction, aParams)
+ , mOptionalKeyRange(aGetAll ?
+ aParams.get_IndexGetAllKeysParams().optionalKeyRange() :
+ OptionalKeyRange(aParams.get_IndexGetKeyParams()
+ .keyRange()))
+ , mLimit(aGetAll ? aParams.get_IndexGetAllKeysParams().limit() : 1)
+ , mGetAll(aGetAll)
+{
+ MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetKeyParams ||
+ aParams.type() == RequestParams::TIndexGetAllKeysParams);
+ MOZ_ASSERT_IF(!aGetAll,
+ mOptionalKeyRange.type() ==
+ OptionalKeyRange::TSerializedKeyRange);
+}
+
+nsresult
+IndexGetKeyRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT_IF(!mGetAll,
+ mOptionalKeyRange.type() ==
+ OptionalKeyRange::TSerializedKeyRange);
+ MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
+
+ PROFILER_LABEL("IndexedDB",
+ "IndexGetKeyRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool hasKeyRange =
+ mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
+
+ nsCString indexTable;
+ if (mMetadata->mCommonMetadata.unique()) {
+ indexTable.AssignLiteral("unique_index_data ");
+ }
+ else {
+ indexTable.AssignLiteral("index_data ");
+ }
+
+ nsAutoCString keyRangeClause;
+ if (hasKeyRange) {
+ GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
+ NS_LITERAL_CSTRING("value"),
+ keyRangeClause);
+ }
+
+ nsCString limitClause;
+ if (mLimit) {
+ limitClause.AssignLiteral(" LIMIT ");
+ limitClause.AppendInt(mLimit);
+ }
+
+ nsCString query =
+ NS_LITERAL_CSTRING("SELECT object_data_key "
+ "FROM ") +
+ indexTable +
+ NS_LITERAL_CSTRING("WHERE index_id = :index_id") +
+ keyRangeClause +
+ limitClause;
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(query, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
+ mMetadata->mCommonMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasKeyRange) {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ Key* key = mResponse.AppendElement(fallible);
+ if (NS_WARN_IF(!key)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = key->SetFromStatement(stmt, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
+
+ return NS_OK;
+}
+
+void
+IndexGetKeyRequestOp::GetResponse(RequestResponse& aResponse)
+{
+ MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
+
+ if (mGetAll) {
+ aResponse = IndexGetAllKeysResponse();
+
+ if (!mResponse.IsEmpty()) {
+ mResponse.SwapElements(aResponse.get_IndexGetAllKeysResponse().keys());
+ }
+
+ return;
+ }
+
+ aResponse = IndexGetKeyResponse();
+
+ if (!mResponse.IsEmpty()) {
+ aResponse.get_IndexGetKeyResponse().key() = Move(mResponse[0]);
+ }
+}
+
+nsresult
+IndexCountRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+
+ PROFILER_LABEL("IndexedDB",
+ "IndexCountRequestOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool hasKeyRange =
+ mParams.optionalKeyRange().type() == OptionalKeyRange::TSerializedKeyRange;
+
+ nsCString indexTable;
+ if (mMetadata->mCommonMetadata.unique()) {
+ indexTable.AssignLiteral("unique_index_data ");
+ }
+ else {
+ indexTable.AssignLiteral("index_data ");
+ }
+
+ nsAutoCString keyRangeClause;
+ if (hasKeyRange) {
+ GetBindingClauseForKeyRange(
+ mParams.optionalKeyRange().get_SerializedKeyRange(),
+ NS_LITERAL_CSTRING("value"),
+ keyRangeClause);
+ }
+
+ nsCString query =
+ NS_LITERAL_CSTRING("SELECT count(*) "
+ "FROM ") +
+ indexTable +
+ NS_LITERAL_CSTRING("WHERE index_id = :index_id") +
+ keyRangeClause;
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(query, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
+ mMetadata->mCommonMetadata.id());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasKeyRange) {
+ rv = BindKeyRangeToStatement(
+ mParams.optionalKeyRange().get_SerializedKeyRange(),
+ stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!hasResult)) {
+ MOZ_ASSERT(false, "This should never be possible!");
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ int64_t count = stmt->AsInt64(0);
+ if (NS_WARN_IF(count < 0)) {
+ MOZ_ASSERT(false, "This should never be possible!");
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+ }
+
+ mResponse.count() = count;
+
+ return NS_OK;
+}
+
+bool
+Cursor::
+CursorOpBase::SendFailureResult(nsresult aResultCode)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NS_FAILED(aResultCode));
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
+ MOZ_ASSERT(!mResponseSent);
+
+ if (!IsActorDestroyed()) {
+ mResponse = ClampResultCode(aResultCode);
+
+ // This is an expected race when the transaction is invalidated after
+ // data is retrieved from database. We clear the retrieved files to prevent
+ // the assertion failure in SendResponseInternal when mResponse.type() is
+ // CursorResponse::Tnsresult.
+ if (Transaction()->IsInvalidated() && !mFiles.IsEmpty()) {
+ mFiles.Clear();
+ }
+
+ mCursor->SendResponseInternal(mResponse, mFiles);
+ }
+
+#ifdef DEBUG
+ mResponseSent = true;
+#endif
+ return false;
+}
+
+void
+Cursor::
+CursorOpBase::Cleanup()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
+
+ mCursor = nullptr;
+
+#ifdef DEBUG
+ // A bit hacky but the CursorOp request is not generated in response to a
+ // child request like most other database operations. Do this to make our
+ // assertions happy.
+ NoteActorDestroyed();
+#endif
+
+ TransactionDatabaseOperationBase::Cleanup();
+}
+
+nsresult
+Cursor::
+CursorOpBase::PopulateResponseFromStatement(
+ DatabaseConnection::CachedStatement& aStmt,
+ bool aInitializeResponse)
+{
+ Transaction()->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mResponse.type() == CursorResponse::T__None);
+ MOZ_ASSERT_IF(mFiles.IsEmpty(), aInitializeResponse);
+
+ nsresult rv = mCursor->mKey.SetFromStatement(aStmt, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ switch (mCursor->mType) {
+ case OpenCursorParams::TObjectStoreOpenCursorParams: {
+ StructuredCloneReadInfo cloneInfo;
+ rv = GetStructuredCloneReadInfoFromStatement(aStmt,
+ 2,
+ 1,
+ mCursor->mFileManager,
+ &cloneInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (cloneInfo.mHasPreprocessInfo) {
+ IDB_WARNING("Preprocessing for cursors not yet implemented!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (aInitializeResponse) {
+ mResponse = nsTArray<ObjectStoreCursorResponse>();
+ } else {
+ MOZ_ASSERT(mResponse.type() ==
+ CursorResponse::TArrayOfObjectStoreCursorResponse);
+ }
+
+ auto& responses = mResponse.get_ArrayOfObjectStoreCursorResponse();
+ auto& response = *responses.AppendElement();
+ response.cloneInfo().data().data = Move(cloneInfo.mData);
+ response.key() = mCursor->mKey;
+
+ mFiles.AppendElement(Move(cloneInfo.mFiles));
+ break;
+ }
+
+ case OpenCursorParams::TObjectStoreOpenKeyCursorParams: {
+ MOZ_ASSERT(aInitializeResponse);
+ mResponse = ObjectStoreKeyCursorResponse(mCursor->mKey);
+ break;
+ }
+
+ case OpenCursorParams::TIndexOpenCursorParams: {
+ rv = mCursor->mSortKey.SetFromStatement(aStmt, 1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mCursor->mObjectKey.SetFromStatement(aStmt, 2);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ StructuredCloneReadInfo cloneInfo;
+ rv = GetStructuredCloneReadInfoFromStatement(aStmt,
+ 4,
+ 3,
+ mCursor->mFileManager,
+ &cloneInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (cloneInfo.mHasPreprocessInfo) {
+ IDB_WARNING("Preprocessing for cursors not yet implemented!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ MOZ_ASSERT(aInitializeResponse);
+ mResponse = IndexCursorResponse();
+
+ auto& response = mResponse.get_IndexCursorResponse();
+ response.cloneInfo().data().data = Move(cloneInfo.mData);
+ response.key() = mCursor->mKey;
+ response.sortKey() = mCursor->mSortKey;
+ response.objectKey() = mCursor->mObjectKey;
+
+ mFiles.AppendElement(Move(cloneInfo.mFiles));
+ break;
+ }
+
+ case OpenCursorParams::TIndexOpenKeyCursorParams: {
+ rv = mCursor->mSortKey.SetFromStatement(aStmt, 1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mCursor->mObjectKey.SetFromStatement(aStmt, 2);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(aInitializeResponse);
+ mResponse = IndexKeyCursorResponse(mCursor->mKey,
+ mCursor->mSortKey,
+ mCursor->mObjectKey);
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return NS_OK;
+}
+
+void
+Cursor::
+OpenOp::GetRangeKeyInfo(bool aLowerBound, Key* aKey, bool* aOpen)
+{
+ AssertIsOnConnectionThread();
+ MOZ_ASSERT(aKey);
+ MOZ_ASSERT(aKey->IsUnset());
+ MOZ_ASSERT(aOpen);
+
+ if (mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange) {
+ const SerializedKeyRange& range =
+ mOptionalKeyRange.get_SerializedKeyRange();
+ if (range.isOnly()) {
+ *aKey = range.lower();
+ *aOpen = false;
+#ifdef ENABLE_INTL_API
+ if (mCursor->IsLocaleAware()) {
+ range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale);
+ }
+#endif
+ } else {
+ *aKey = aLowerBound ? range.lower() : range.upper();
+ *aOpen = aLowerBound ? range.lowerOpen() : range.upperOpen();
+#ifdef ENABLE_INTL_API
+ if (mCursor->IsLocaleAware()) {
+ if (aLowerBound) {
+ range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale);
+ } else {
+ range.upper().ToLocaleBasedKey(*aKey, mCursor->mLocale);
+ }
+ }
+#endif
+ }
+ } else {
+ *aOpen = false;
+ }
+}
+
+nsresult
+Cursor::
+OpenOp::DoObjectStoreDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mType == OpenCursorParams::TObjectStoreOpenCursorParams);
+ MOZ_ASSERT(mCursor->mObjectStoreId);
+ MOZ_ASSERT(mCursor->mFileManager);
+
+ PROFILER_LABEL("IndexedDB",
+ "Cursor::OpenOp::DoObjectStoreDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool usingKeyRange =
+ mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
+
+ NS_NAMED_LITERAL_CSTRING(keyString, "key");
+ NS_NAMED_LITERAL_CSTRING(id, "id");
+ NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
+
+ nsCString queryStart =
+ NS_LITERAL_CSTRING("SELECT ") +
+ keyString +
+ NS_LITERAL_CSTRING(", file_ids, data "
+ "FROM object_data "
+ "WHERE object_store_id = :") +
+ id;
+
+ nsAutoCString keyRangeClause;
+ if (usingKeyRange) {
+ GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
+ keyString,
+ keyRangeClause);
+ }
+
+ nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyString;
+ switch (mCursor->mDirection) {
+ case IDBCursor::NEXT:
+ case IDBCursor::NEXT_UNIQUE:
+ directionClause.AppendLiteral(" ASC");
+ break;
+
+ case IDBCursor::PREV:
+ case IDBCursor::PREV_UNIQUE:
+ directionClause.AppendLiteral(" DESC");
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ // Note: Changing the number or order of SELECT columns in the query will
+ // require changes to CursorOpBase::PopulateResponseFromStatement.
+ nsCString firstQuery =
+ queryStart +
+ keyRangeClause +
+ directionClause +
+ openLimit +
+ NS_LITERAL_CSTRING("1");
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(firstQuery, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(id, mCursor->mObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (usingKeyRange) {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!hasResult) {
+ mResponse = void_t();
+ return NS_OK;
+ }
+
+ rv = PopulateResponseFromStatement(stmt, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now we need to make the query to get the next match.
+ keyRangeClause.Truncate();
+ nsAutoCString continueToKeyRangeClause;
+
+ NS_NAMED_LITERAL_CSTRING(currentKey, "current_key");
+ NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
+
+ switch (mCursor->mDirection) {
+ case IDBCursor::NEXT:
+ case IDBCursor::NEXT_UNIQUE: {
+ Key upper;
+ bool open;
+ GetRangeKeyInfo(false, &upper, &open);
+ AppendConditionClause(keyString, currentKey, false, false,
+ keyRangeClause);
+ AppendConditionClause(keyString, currentKey, false, true,
+ continueToKeyRangeClause);
+ if (usingKeyRange && !upper.IsUnset()) {
+ AppendConditionClause(keyString, rangeKey, true, !open, keyRangeClause);
+ AppendConditionClause(keyString, rangeKey, true, !open,
+ continueToKeyRangeClause);
+ mCursor->mRangeKey = upper;
+ }
+ break;
+ }
+
+ case IDBCursor::PREV:
+ case IDBCursor::PREV_UNIQUE: {
+ Key lower;
+ bool open;
+ GetRangeKeyInfo(true, &lower, &open);
+ AppendConditionClause(keyString, currentKey, true, false, keyRangeClause);
+ AppendConditionClause(keyString, currentKey, true, true,
+ continueToKeyRangeClause);
+ if (usingKeyRange && !lower.IsUnset()) {
+ AppendConditionClause(keyString, rangeKey, false, !open,
+ keyRangeClause);
+ AppendConditionClause(keyString, rangeKey, false, !open,
+ continueToKeyRangeClause);
+ mCursor->mRangeKey = lower;
+ }
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ mCursor->mContinueQuery =
+ queryStart +
+ keyRangeClause +
+ directionClause +
+ openLimit;
+
+ mCursor->mContinueToQuery =
+ queryStart +
+ continueToKeyRangeClause +
+ directionClause +
+ openLimit;
+
+ return NS_OK;
+}
+
+nsresult
+Cursor::
+OpenOp::DoObjectStoreKeyDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mType ==
+ OpenCursorParams::TObjectStoreOpenKeyCursorParams);
+ MOZ_ASSERT(mCursor->mObjectStoreId);
+
+ PROFILER_LABEL("IndexedDB",
+ "Cursor::OpenOp::DoObjectStoreKeyDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool usingKeyRange =
+ mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
+
+ NS_NAMED_LITERAL_CSTRING(keyString, "key");
+ NS_NAMED_LITERAL_CSTRING(id, "id");
+ NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
+
+ nsCString queryStart =
+ NS_LITERAL_CSTRING("SELECT ") +
+ keyString +
+ NS_LITERAL_CSTRING(" FROM object_data "
+ "WHERE object_store_id = :") +
+ id;
+
+ nsAutoCString keyRangeClause;
+ if (usingKeyRange) {
+ GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
+ keyString,
+ keyRangeClause);
+ }
+
+ nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyString;
+ switch (mCursor->mDirection) {
+ case IDBCursor::NEXT:
+ case IDBCursor::NEXT_UNIQUE:
+ directionClause.AppendLiteral(" ASC");
+ break;
+
+ case IDBCursor::PREV:
+ case IDBCursor::PREV_UNIQUE:
+ directionClause.AppendLiteral(" DESC");
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ // Note: Changing the number or order of SELECT columns in the query will
+ // require changes to CursorOpBase::PopulateResponseFromStatement.
+ nsCString firstQuery =
+ queryStart +
+ keyRangeClause +
+ directionClause +
+ openLimit +
+ NS_LITERAL_CSTRING("1");
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(firstQuery, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(id, mCursor->mObjectStoreId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (usingKeyRange) {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!hasResult) {
+ mResponse = void_t();
+ return NS_OK;
+ }
+
+ rv = PopulateResponseFromStatement(stmt, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now we need to make the query to get the next match.
+ keyRangeClause.Truncate();
+ nsAutoCString continueToKeyRangeClause;
+
+ NS_NAMED_LITERAL_CSTRING(currentKey, "current_key");
+ NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
+
+ switch (mCursor->mDirection) {
+ case IDBCursor::NEXT:
+ case IDBCursor::NEXT_UNIQUE: {
+ Key upper;
+ bool open;
+ GetRangeKeyInfo(false, &upper, &open);
+ AppendConditionClause(keyString, currentKey, false, false,
+ keyRangeClause);
+ AppendConditionClause(keyString, currentKey, false, true,
+ continueToKeyRangeClause);
+ if (usingKeyRange && !upper.IsUnset()) {
+ AppendConditionClause(keyString, rangeKey, true, !open, keyRangeClause);
+ AppendConditionClause(keyString, rangeKey, true, !open,
+ continueToKeyRangeClause);
+ mCursor->mRangeKey = upper;
+ }
+ break;
+ }
+
+ case IDBCursor::PREV:
+ case IDBCursor::PREV_UNIQUE: {
+ Key lower;
+ bool open;
+ GetRangeKeyInfo(true, &lower, &open);
+ AppendConditionClause(keyString, currentKey, true, false, keyRangeClause);
+ AppendConditionClause(keyString, currentKey, true, true,
+ continueToKeyRangeClause);
+ if (usingKeyRange && !lower.IsUnset()) {
+ AppendConditionClause(keyString, rangeKey, false, !open,
+ keyRangeClause);
+ AppendConditionClause(keyString, rangeKey, false, !open,
+ continueToKeyRangeClause);
+ mCursor->mRangeKey = lower;
+ }
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ mCursor->mContinueQuery =
+ queryStart +
+ keyRangeClause +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ continueToKeyRangeClause +
+ directionClause +
+ openLimit;
+
+ return NS_OK;
+}
+
+nsresult
+Cursor::
+OpenOp::DoIndexDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mType == OpenCursorParams::TIndexOpenCursorParams);
+ MOZ_ASSERT(mCursor->mObjectStoreId);
+ MOZ_ASSERT(mCursor->mIndexId);
+
+ PROFILER_LABEL("IndexedDB",
+ "Cursor::OpenOp::DoIndexDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool usingKeyRange =
+ mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
+
+ nsCString indexTable = mCursor->mUniqueIndex ?
+ NS_LITERAL_CSTRING("unique_index_data") :
+ NS_LITERAL_CSTRING("index_data");
+
+ NS_NAMED_LITERAL_CSTRING(sortColumn, "sort_column");
+ NS_NAMED_LITERAL_CSTRING(id, "id");
+ NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
+
+ nsAutoCString sortColumnAlias;
+ if (mCursor->IsLocaleAware()) {
+ sortColumnAlias = "SELECT index_table.value, "
+ "index_table.value_locale as sort_column, ";
+ } else {
+ sortColumnAlias = "SELECT index_table.value as sort_column, "
+ "index_table.value_locale, ";
+ }
+
+ nsAutoCString queryStart =
+ sortColumnAlias +
+ NS_LITERAL_CSTRING( "index_table.object_data_key, "
+ "object_data.file_ids, "
+ "object_data.data "
+ "FROM ") +
+ indexTable +
+ NS_LITERAL_CSTRING(" AS index_table "
+ "JOIN object_data "
+ "ON index_table.object_store_id = "
+ "object_data.object_store_id "
+ "AND index_table.object_data_key = "
+ "object_data.key "
+ "WHERE index_table.index_id = :") +
+ id;
+
+ nsAutoCString keyRangeClause;
+ if (usingKeyRange) {
+ GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
+ sortColumn,
+ keyRangeClause);
+ }
+
+ nsAutoCString directionClause =
+ NS_LITERAL_CSTRING(" ORDER BY ") +
+ sortColumn;
+
+ switch (mCursor->mDirection) {
+ case IDBCursor::NEXT:
+ case IDBCursor::NEXT_UNIQUE:
+ directionClause.AppendLiteral(" ASC, index_table.object_data_key ASC");
+ break;
+
+ case IDBCursor::PREV:
+ directionClause.AppendLiteral(" DESC, index_table.object_data_key DESC");
+ break;
+
+ case IDBCursor::PREV_UNIQUE:
+ directionClause.AppendLiteral(" DESC, index_table.object_data_key ASC");
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ // Note: Changing the number or order of SELECT columns in the query will
+ // require changes to CursorOpBase::PopulateResponseFromStatement.
+ nsCString firstQuery =
+ queryStart +
+ keyRangeClause +
+ directionClause +
+ openLimit +
+ NS_LITERAL_CSTRING("1");
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(firstQuery, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(id, mCursor->mIndexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (usingKeyRange) {
+ if (mCursor->IsLocaleAware()) {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt,
+ mCursor->mLocale);
+ } else {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!hasResult) {
+ mResponse = void_t();
+ return NS_OK;
+ }
+
+ rv = PopulateResponseFromStatement(stmt, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now we need to make the query to get the next match.
+ NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
+
+ switch (mCursor->mDirection) {
+ case IDBCursor::NEXT: {
+ Key upper;
+ bool open;
+ GetRangeKeyInfo(false, &upper, &open);
+ if (usingKeyRange && !upper.IsUnset()) {
+ AppendConditionClause(sortColumn, rangeKey, true, !open, queryStart);
+ mCursor->mRangeKey = upper;
+ }
+ mCursor->mContinueQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column >= :current_key "
+ "AND ( sort_column > :current_key OR "
+ "index_table.object_data_key > :object_key ) "
+ ) +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column >= :current_key") +
+ directionClause +
+ openLimit;
+ mCursor->mContinuePrimaryKeyQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column >= :current_key "
+ "AND index_table.object_data_key >= :object_key "
+ ) +
+ directionClause +
+ openLimit;
+ break;
+ }
+
+ case IDBCursor::NEXT_UNIQUE: {
+ Key upper;
+ bool open;
+ GetRangeKeyInfo(false, &upper, &open);
+ if (usingKeyRange && !upper.IsUnset()) {
+ AppendConditionClause(sortColumn, rangeKey, true, !open, queryStart);
+ mCursor->mRangeKey = upper;
+ }
+ mCursor->mContinueQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column > :current_key") +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column >= :current_key") +
+ directionClause +
+ openLimit;
+ break;
+ }
+
+ case IDBCursor::PREV: {
+ Key lower;
+ bool open;
+ GetRangeKeyInfo(true, &lower, &open);
+ if (usingKeyRange && !lower.IsUnset()) {
+ AppendConditionClause(sortColumn, rangeKey, false, !open, queryStart);
+ mCursor->mRangeKey = lower;
+ }
+ mCursor->mContinueQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column <= :current_key "
+ "AND ( sort_column < :current_key OR "
+ "index_table.object_data_key < :object_key ) "
+ ) +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column <= :current_key") +
+ directionClause +
+ openLimit;
+ mCursor->mContinuePrimaryKeyQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column <= :current_key "
+ "AND index_table.object_data_key <= :object_key "
+ ) +
+ directionClause +
+ openLimit;
+ break;
+ }
+
+ case IDBCursor::PREV_UNIQUE: {
+ Key lower;
+ bool open;
+ GetRangeKeyInfo(true, &lower, &open);
+ if (usingKeyRange && !lower.IsUnset()) {
+ AppendConditionClause(sortColumn, rangeKey, false, !open, queryStart);
+ mCursor->mRangeKey = lower;
+ }
+ mCursor->mContinueQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column < :current_key") +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column <= :current_key") +
+ directionClause +
+ openLimit;
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Cursor::
+OpenOp::DoIndexKeyDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mType == OpenCursorParams::TIndexOpenKeyCursorParams);
+ MOZ_ASSERT(mCursor->mObjectStoreId);
+ MOZ_ASSERT(mCursor->mIndexId);
+
+ PROFILER_LABEL("IndexedDB",
+ "Cursor::OpenOp::DoIndexKeyDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ const bool usingKeyRange =
+ mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
+
+ nsCString table = mCursor->mUniqueIndex ?
+ NS_LITERAL_CSTRING("unique_index_data") :
+ NS_LITERAL_CSTRING("index_data");
+
+ NS_NAMED_LITERAL_CSTRING(sortColumn, "sort_column");
+ NS_NAMED_LITERAL_CSTRING(id, "id");
+ NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
+
+ nsAutoCString sortColumnAlias;
+ if (mCursor->IsLocaleAware()) {
+ sortColumnAlias = "SELECT value, "
+ "value_locale as sort_column, ";
+ } else {
+ sortColumnAlias = "SELECT value as sort_column, "
+ "value_locale, ";
+ }
+
+ nsAutoCString queryStart =
+ sortColumnAlias +
+ NS_LITERAL_CSTRING( "object_data_key "
+ " FROM ") +
+ table +
+ NS_LITERAL_CSTRING(" WHERE index_id = :") +
+ id;
+
+ nsAutoCString keyRangeClause;
+ if (usingKeyRange) {
+ GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
+ sortColumn,
+ keyRangeClause);
+ }
+
+ nsAutoCString directionClause =
+ NS_LITERAL_CSTRING(" ORDER BY ") +
+ sortColumn;
+
+ switch (mCursor->mDirection) {
+ case IDBCursor::NEXT:
+ case IDBCursor::NEXT_UNIQUE:
+ directionClause.AppendLiteral(" ASC, object_data_key ASC");
+ break;
+
+ case IDBCursor::PREV:
+ directionClause.AppendLiteral(" DESC, object_data_key DESC");
+ break;
+
+ case IDBCursor::PREV_UNIQUE:
+ directionClause.AppendLiteral(" DESC, object_data_key ASC");
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ // Note: Changing the number or order of SELECT columns in the query will
+ // require changes to CursorOpBase::PopulateResponseFromStatement.
+ nsCString firstQuery =
+ queryStart +
+ keyRangeClause +
+ directionClause +
+ openLimit +
+ NS_LITERAL_CSTRING("1");
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(firstQuery, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByName(id, mCursor->mIndexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (usingKeyRange) {
+ if (mCursor->IsLocaleAware()) {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt,
+ mCursor->mLocale);
+ } else {
+ rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
+ stmt);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!hasResult) {
+ mResponse = void_t();
+ return NS_OK;
+ }
+
+ rv = PopulateResponseFromStatement(stmt, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now we need to make the query to get the next match.
+ NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
+
+ switch (mCursor->mDirection) {
+ case IDBCursor::NEXT: {
+ Key upper;
+ bool open;
+ GetRangeKeyInfo(false, &upper, &open);
+ if (usingKeyRange && !upper.IsUnset()) {
+ AppendConditionClause(sortColumn, rangeKey, true, !open, queryStart);
+ mCursor->mRangeKey = upper;
+ }
+ mCursor->mContinueQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column >= :current_key "
+ "AND ( sort_column > :current_key OR "
+ "object_data_key > :object_key )") +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column >= :current_key ") +
+ directionClause +
+ openLimit;
+ mCursor->mContinuePrimaryKeyQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column >= :current_key "
+ "AND object_data_key >= :object_key "
+ ) +
+ directionClause +
+ openLimit;
+ break;
+ }
+
+ case IDBCursor::NEXT_UNIQUE: {
+ Key upper;
+ bool open;
+ GetRangeKeyInfo(false, &upper, &open);
+ if (usingKeyRange && !upper.IsUnset()) {
+ AppendConditionClause(sortColumn, rangeKey, true, !open, queryStart);
+ mCursor->mRangeKey = upper;
+ }
+ mCursor->mContinueQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column > :current_key") +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column >= :current_key") +
+ directionClause +
+ openLimit;
+ break;
+ }
+
+ case IDBCursor::PREV: {
+ Key lower;
+ bool open;
+ GetRangeKeyInfo(true, &lower, &open);
+ if (usingKeyRange && !lower.IsUnset()) {
+ AppendConditionClause(sortColumn, rangeKey, false, !open, queryStart);
+ mCursor->mRangeKey = lower;
+ }
+
+ mCursor->mContinueQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column <= :current_key "
+ "AND ( sort_column < :current_key OR "
+ "object_data_key < :object_key )") +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column <= :current_key ") +
+ directionClause +
+ openLimit;
+ mCursor->mContinuePrimaryKeyQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column <= :current_key "
+ "AND object_data_key <= :object_key "
+ ) +
+ directionClause +
+ openLimit;
+ break;
+ }
+
+ case IDBCursor::PREV_UNIQUE: {
+ Key lower;
+ bool open;
+ GetRangeKeyInfo(true, &lower, &open);
+ if (usingKeyRange && !lower.IsUnset()) {
+ AppendConditionClause(sortColumn, rangeKey, false, !open, queryStart);
+ mCursor->mRangeKey = lower;
+ }
+ mCursor->mContinueQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column < :current_key") +
+ directionClause +
+ openLimit;
+ mCursor->mContinueToQuery =
+ queryStart +
+ NS_LITERAL_CSTRING(" AND sort_column <= :current_key") +
+ directionClause +
+ openLimit;
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Cursor::
+OpenOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mContinueQuery.IsEmpty());
+ MOZ_ASSERT(mCursor->mContinueToQuery.IsEmpty());
+ MOZ_ASSERT(mCursor->mContinuePrimaryKeyQuery.IsEmpty());
+ MOZ_ASSERT(mCursor->mKey.IsUnset());
+ MOZ_ASSERT(mCursor->mRangeKey.IsUnset());
+
+ PROFILER_LABEL("IndexedDB",
+ "Cursor::OpenOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv;
+
+ switch (mCursor->mType) {
+ case OpenCursorParams::TObjectStoreOpenCursorParams:
+ rv = DoObjectStoreDatabaseWork(aConnection);
+ break;
+
+ case OpenCursorParams::TObjectStoreOpenKeyCursorParams:
+ rv = DoObjectStoreKeyDatabaseWork(aConnection);
+ break;
+
+ case OpenCursorParams::TIndexOpenCursorParams:
+ rv = DoIndexDatabaseWork(aConnection);
+ break;
+
+ case OpenCursorParams::TIndexOpenKeyCursorParams:
+ rv = DoIndexKeyDatabaseWork(aConnection);
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Cursor::
+OpenOp::SendSuccessResult()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
+ MOZ_ASSERT(mResponse.type() != CursorResponse::T__None);
+ MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
+ mCursor->mKey.IsUnset());
+ MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
+ mCursor->mSortKey.IsUnset());
+ MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
+ mCursor->mRangeKey.IsUnset());
+ MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
+ mCursor->mObjectKey.IsUnset());
+
+ if (IsActorDestroyed()) {
+ return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
+ }
+
+ mCursor->SendResponseInternal(mResponse, mFiles);
+
+#ifdef DEBUG
+ mResponseSent = true;
+#endif
+ return NS_OK;
+}
+
+nsresult
+Cursor::
+ContinueOp::DoDatabaseWork(DatabaseConnection* aConnection)
+{
+ MOZ_ASSERT(aConnection);
+ aConnection->AssertIsOnConnectionThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mObjectStoreId);
+ MOZ_ASSERT(!mCursor->mContinueQuery.IsEmpty());
+ MOZ_ASSERT(!mCursor->mContinueToQuery.IsEmpty());
+ MOZ_ASSERT(!mCursor->mKey.IsUnset());
+
+ const bool isIndex =
+ mCursor->mType == OpenCursorParams::TIndexOpenCursorParams ||
+ mCursor->mType == OpenCursorParams::TIndexOpenKeyCursorParams;
+
+ MOZ_ASSERT_IF(isIndex &&
+ (mCursor->mDirection == IDBCursor::NEXT ||
+ mCursor->mDirection == IDBCursor::PREV),
+ !mCursor->mContinuePrimaryKeyQuery.IsEmpty());
+ MOZ_ASSERT_IF(isIndex, mCursor->mIndexId);
+ MOZ_ASSERT_IF(isIndex, !mCursor->mObjectKey.IsUnset());
+
+ PROFILER_LABEL("IndexedDB",
+ "Cursor::ContinueOp::DoDatabaseWork",
+ js::ProfileEntry::Category::STORAGE);
+
+ // We need to pick a query based on whether or not a key was passed to the
+ // continue function. If not we'll grab the the next item in the database that
+ // is greater than (or less than, if we're running a PREV cursor) the current
+ // key. If a key was passed we'll grab the next item in the database that is
+ // greater than (or less than, if we're running a PREV cursor) or equal to the
+ // key that was specified.
+
+ // Note: Changing the number or order of SELECT columns in the query will
+ // require changes to CursorOpBase::PopulateResponseFromStatement.
+ bool hasContinueKey = false;
+ bool hasContinuePrimaryKey = false;
+ uint32_t advanceCount = 1;
+ Key& currentKey = mCursor->IsLocaleAware() ? mCursor->mSortKey : mCursor->mKey;
+
+ switch (mParams.type()) {
+ case CursorRequestParams::TContinueParams:
+ if (!mParams.get_ContinueParams().key().IsUnset()) {
+ hasContinueKey = true;
+ currentKey = mParams.get_ContinueParams().key();
+ }
+ break;
+ case CursorRequestParams::TContinuePrimaryKeyParams:
+ MOZ_ASSERT(!mParams.get_ContinuePrimaryKeyParams().key().IsUnset());
+ MOZ_ASSERT(!mParams.get_ContinuePrimaryKeyParams().primaryKey().IsUnset());
+ MOZ_ASSERT(mCursor->mDirection == IDBCursor::NEXT ||
+ mCursor->mDirection == IDBCursor::PREV);
+ hasContinueKey = true;
+ hasContinuePrimaryKey = true;
+ currentKey = mParams.get_ContinuePrimaryKeyParams().key();
+ break;
+ case CursorRequestParams::TAdvanceParams:
+ advanceCount = mParams.get_AdvanceParams().count();
+ break;
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ const nsCString& continueQuery =
+ hasContinuePrimaryKey ? mCursor->mContinuePrimaryKeyQuery :
+ hasContinueKey ? mCursor->mContinueToQuery : mCursor->mContinueQuery;
+
+ MOZ_ASSERT(advanceCount > 0);
+ nsAutoCString countString;
+ countString.AppendInt(advanceCount);
+
+ nsCString query = continueQuery + countString;
+
+ NS_NAMED_LITERAL_CSTRING(currentKeyName, "current_key");
+ NS_NAMED_LITERAL_CSTRING(rangeKeyName, "range_key");
+ NS_NAMED_LITERAL_CSTRING(objectKeyName, "object_key");
+
+ const bool usingRangeKey = !mCursor->mRangeKey.IsUnset();
+
+ DatabaseConnection::CachedStatement stmt;
+ nsresult rv = aConnection->GetCachedStatement(query, &stmt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t id = isIndex ? mCursor->mIndexId : mCursor->mObjectStoreId;
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), id);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Bind current key.
+ rv = currentKey.BindToStatement(stmt, currentKeyName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Bind range key if it is specified.
+ if (usingRangeKey) {
+ rv = mCursor->mRangeKey.BindToStatement(stmt, rangeKeyName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Bind object key if duplicates are allowed and we're not continuing to a
+ // specific key.
+ if (isIndex &&
+ !hasContinueKey &&
+ (mCursor->mDirection == IDBCursor::NEXT ||
+ mCursor->mDirection == IDBCursor::PREV)) {
+ rv = mCursor->mObjectKey.BindToStatement(stmt, objectKeyName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Bind object key if primaryKey is specified.
+ if (hasContinuePrimaryKey) {
+ rv = mParams.get_ContinuePrimaryKeyParams().primaryKey()
+ .BindToStatement(stmt, objectKeyName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+
+ bool hasResult;
+ for (uint32_t index = 0; index < advanceCount; index++) {
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!hasResult) {
+ mCursor->mKey.Unset();
+ mCursor->mSortKey.Unset();
+ mCursor->mRangeKey.Unset();
+ mCursor->mObjectKey.Unset();
+ mResponse = void_t();
+ return NS_OK;
+ }
+ }
+
+ rv = PopulateResponseFromStatement(stmt, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Cursor::
+ContinueOp::SendSuccessResult()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mCursor);
+ MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
+ MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
+ mCursor->mKey.IsUnset());
+ MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
+ mCursor->mRangeKey.IsUnset());
+ MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
+ mCursor->mObjectKey.IsUnset());
+
+ if (IsActorDestroyed()) {
+ return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
+ }
+
+ mCursor->SendResponseInternal(mResponse, mFiles);
+
+#ifdef DEBUG
+ mResponseSent = true;
+#endif
+ return NS_OK;
+}
+
+Utils::Utils()
+#ifdef DEBUG
+ : mActorDestroyed(false)
+#endif
+{
+ AssertIsOnBackgroundThread();
+}
+
+Utils::~Utils()
+{
+ MOZ_ASSERT(mActorDestroyed);
+}
+
+void
+Utils::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+#ifdef DEBUG
+ mActorDestroyed = true;
+#endif
+}
+
+bool
+Utils::RecvDeleteMe()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ return PBackgroundIndexedDBUtilsParent::Send__delete__(this);
+}
+
+bool
+Utils::RecvGetFileReferences(const PersistenceType& aPersistenceType,
+ const nsCString& aOrigin,
+ const nsString& aDatabaseName,
+ const int64_t& aFileId,
+ int32_t* aRefCnt,
+ int32_t* aDBRefCnt,
+ int32_t* aSliceRefCnt,
+ bool* aResult)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aRefCnt);
+ MOZ_ASSERT(aDBRefCnt);
+ MOZ_ASSERT(aSliceRefCnt);
+ MOZ_ASSERT(aResult);
+ MOZ_ASSERT(!mActorDestroyed);
+
+ if (NS_WARN_IF(!IndexedDatabaseManager::Get() ||
+ !QuotaManager::Get())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!IndexedDatabaseManager::InTestingMode())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(aPersistenceType != quota::PERSISTENCE_TYPE_PERSISTENT &&
+ aPersistenceType != quota::PERSISTENCE_TYPE_TEMPORARY &&
+ aPersistenceType != quota::PERSISTENCE_TYPE_DEFAULT)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(aOrigin.IsEmpty())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(aDatabaseName.IsEmpty())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(aFileId == 0)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ RefPtr<GetFileReferencesHelper> helper =
+ new GetFileReferencesHelper(aPersistenceType, aOrigin, aDatabaseName,
+ aFileId);
+
+ nsresult rv =
+ helper->DispatchAndReturnFileReferences(aRefCnt, aDBRefCnt,
+ aSliceRefCnt, aResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+}
+
+nsresult
+GetFileReferencesHelper::DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
+ int32_t* aDBRefCnt,
+ int32_t* aSliceRefCnt,
+ bool* aResult)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aMemRefCnt);
+ MOZ_ASSERT(aDBRefCnt);
+ MOZ_ASSERT(aSliceRefCnt);
+ MOZ_ASSERT(aResult);
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ nsresult rv =
+ quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mozilla::MutexAutoLock autolock(mMutex);
+ while (mWaiting) {
+ mCondVar.Wait();
+ }
+
+ *aMemRefCnt = mMemRefCnt;
+ *aDBRefCnt = mDBRefCnt;
+ *aSliceRefCnt = mSliceRefCnt;
+ *aResult = mResult;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GetFileReferencesHelper::Run()
+{
+ AssertIsOnIOThread();
+
+ IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
+ MOZ_ASSERT(mgr);
+
+ RefPtr<FileManager> fileManager =
+ mgr->GetFileManager(mPersistenceType, mOrigin, mDatabaseName);
+
+ if (fileManager) {
+ RefPtr<FileInfo> fileInfo = fileManager->GetFileInfo(mFileId);
+
+ if (fileInfo) {
+ fileInfo->GetReferences(&mMemRefCnt, &mDBRefCnt, &mSliceRefCnt);
+
+ if (mMemRefCnt != -1) {
+ // We added an extra temp ref, so account for that accordingly.
+ mMemRefCnt--;
+ }
+
+ mResult = true;
+ }
+ }
+
+ mozilla::MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mWaiting);
+
+ mWaiting = false;
+ mCondVar.Notify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FlushPendingFileDeletionsRunnable::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
+ if (NS_WARN_IF(!mgr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = mgr->FlushPendingFileDeletions();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+PermissionRequestHelper::OnPromptComplete(PermissionValue aPermissionValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mActorDestroyed) {
+ Unused <<
+ PIndexedDBPermissionRequestParent::Send__delete__(this, aPermissionValue);
+ }
+}
+
+void
+PermissionRequestHelper::ActorDestroy(ActorDestroyReason aWhy)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+}
+
+#ifdef DEBUG
+
+NS_IMPL_ISUPPORTS(DEBUGThreadSlower, nsIThreadObserver)
+
+NS_IMETHODIMP
+DEBUGThreadSlower::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
+{
+ MOZ_CRASH("Should never be called!");
+}
+
+NS_IMETHODIMP
+DEBUGThreadSlower::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
+ bool /* aMayWait */)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DEBUGThreadSlower::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
+ bool /* aEventWasProcessed */)
+{
+ MOZ_ASSERT(kDEBUGThreadSleepMS);
+
+ MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(kDEBUGThreadSleepMS)) ==
+ PR_SUCCESS);
+ return NS_OK;
+}
+
+#endif // DEBUG
+
+nsresult
+FileHelper::Init()
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(mFileManager);
+
+ nsCOMPtr<nsIFile> fileDirectory = mFileManager->GetCheckedDirectory();
+ if (NS_WARN_IF(!fileDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> journalDirectory = mFileManager->EnsureJournalDirectory();
+ if (NS_WARN_IF(!journalDirectory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DebugOnly<bool> exists;
+ MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists)));
+ MOZ_ASSERT(exists);
+
+ DebugOnly<bool> isDirectory;
+ MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory)));
+ MOZ_ASSERT(isDirectory);
+
+ mFileDirectory = Move(fileDirectory);
+ mJournalDirectory= Move(journalDirectory);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetFile(FileInfo* aFileInfo)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aFileInfo);
+ MOZ_ASSERT(mFileManager);
+ MOZ_ASSERT(mFileDirectory);
+
+ const int64_t fileId = aFileInfo->Id();
+ MOZ_ASSERT(fileId > 0);
+
+ nsCOMPtr<nsIFile> file =
+ mFileManager->GetFileForId(mFileDirectory, fileId);
+ return file.forget();
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetCheckedFile(FileInfo* aFileInfo)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aFileInfo);
+ MOZ_ASSERT(mFileManager);
+ MOZ_ASSERT(mFileDirectory);
+
+ const int64_t fileId = aFileInfo->Id();
+ MOZ_ASSERT(fileId > 0);
+
+ nsCOMPtr<nsIFile> file =
+ mFileManager->GetCheckedFileForId(mFileDirectory, fileId);
+ return file.forget();
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetJournalFile(FileInfo* aFileInfo)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aFileInfo);
+ MOZ_ASSERT(mFileManager);
+ MOZ_ASSERT(mJournalDirectory);
+
+ const int64_t fileId = aFileInfo->Id();
+ MOZ_ASSERT(fileId > 0);
+
+ nsCOMPtr<nsIFile> file =
+ mFileManager->GetFileForId(mJournalDirectory, fileId);
+ return file.forget();
+}
+
+nsresult
+FileHelper::CreateFileFromStream(nsIFile* aFile,
+ nsIFile* aJournalFile,
+ nsIInputStream* aInputStream,
+ bool aCompress)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aFile);
+ MOZ_ASSERT(aJournalFile);
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(mFileManager);
+ MOZ_ASSERT(mFileDirectory);
+ MOZ_ASSERT(mJournalDirectory);
+
+ bool exists;
+ nsresult rv = aFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // DOM blobs that are being stored in IDB are cached by calling
+ // IDBDatabase::GetOrCreateFileActorForBlob. So if the same DOM blob is stored
+ // again under a different key or in a different object store, we just add
+ // a new reference instead of creating a new copy (all such stored blobs share
+ // the same id).
+ // However, it can happen that CreateFileFromStream failed due to quota
+ // exceeded error and for some reason the orphaned file couldn't be deleted
+ // immediately. Now, if the operation is being repeated, the DOM blob is
+ // already cached, so it has the same file id which clashes with the orphaned
+ // file. We could do some tricks to restore previous copy loop, but it's safer
+ // to just delete the orphaned file and start from scratch.
+ // This corner case is partially simulated in test_file_copy_failure.js
+ if (exists) {
+ bool isFile;
+ rv = aFile->IsFile(&isFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isFile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = aJournalFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!exists)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = aJournalFile->IsFile(&isFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isFile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ IDB_WARNING("Deleting orphaned file!");
+
+ rv = RemoveFile(aFile, aJournalFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Create a journal file first.
+ rv = aJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now try to copy the stream.
+ RefPtr<FileOutputStream> fileOutputStream =
+ FileOutputStream::Create(mFileManager->Type(),
+ mFileManager->Group(),
+ mFileManager->Origin(),
+ aFile);
+ if (NS_WARN_IF(!fileOutputStream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aCompress) {
+ RefPtr<SnappyCompressOutputStream> snappyOutputStream =
+ new SnappyCompressOutputStream(fileOutputStream);
+
+ UniquePtr<char[]> buffer(new char[snappyOutputStream->BlockSize()]);
+
+ rv = SyncCopy(aInputStream,
+ snappyOutputStream,
+ buffer.get(),
+ snappyOutputStream->BlockSize());
+ } else {
+ char buffer[kFileCopyBufferSize];
+
+ rv = SyncCopy(aInputStream,
+ fileOutputStream,
+ buffer,
+ kFileCopyBufferSize);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FileHelper::ReplaceFile(nsIFile* aFile,
+ nsIFile* aNewFile,
+ nsIFile* aNewJournalFile)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aFile);
+ MOZ_ASSERT(aNewFile);
+ MOZ_ASSERT(aNewJournalFile);
+ MOZ_ASSERT(mFileManager);
+ MOZ_ASSERT(mFileDirectory);
+ MOZ_ASSERT(mJournalDirectory);
+
+ nsresult rv;
+
+ int64_t fileSize;
+
+ if (mFileManager->EnforcingQuota()) {
+ rv = aFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsAutoString fileName;
+ rv = aFile->GetLeafName(fileName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aNewFile->RenameTo(nullptr, fileName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mFileManager->EnforcingQuota()) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
+ mFileManager->Group(),
+ mFileManager->Origin(),
+ fileSize);
+ }
+
+ rv = aNewJournalFile->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FileHelper::RemoveFile(nsIFile* aFile,
+ nsIFile* aJournalFile)
+{
+ nsresult rv;
+
+ int64_t fileSize;
+
+ if (mFileManager->EnforcingQuota()) {
+ rv = aFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = aFile->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mFileManager->EnforcingQuota()) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
+ mFileManager->Group(),
+ mFileManager->Origin(),
+ fileSize);
+ }
+
+ rv = aJournalFile->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<FileInfo>
+FileHelper::GetNewFileInfo()
+{
+ MOZ_ASSERT(mFileManager);
+
+ return mFileManager->GetNewFileInfo();
+}
+
+nsresult
+FileHelper::SyncCopy(nsIInputStream* aInputStream,
+ nsIOutputStream* aOutputStream,
+ char* aBuffer,
+ uint32_t aBufferSize)
+{
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aOutputStream);
+
+ PROFILER_LABEL("IndexedDB",
+ "FileHelper::SyncCopy",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsresult rv;
+
+ do {
+ uint32_t numRead;
+ rv = aInputStream->Read(aBuffer, aBufferSize, &numRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ if (!numRead) {
+ break;
+ }
+
+ uint32_t numWrite;
+ rv = aOutputStream->Write(aBuffer, numRead, &numWrite);
+ if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+ rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ if (NS_WARN_IF(numWrite != numRead)) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ } while (true);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = aOutputStream->Flush();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsresult rv2 = aOutputStream->Close();
+ if (NS_WARN_IF(NS_FAILED(rv2))) {
+ return NS_SUCCEEDED(rv) ? rv2 : rv;
+ }
+
+ return rv;
+}
+
+} // namespace indexedDB
+} // namespace dom
+} // namespace mozilla
+
+#undef IDB_MOBILE
+#undef IDB_DEBUG_LOG
+#undef ASSERT_UNLESS_FUZZING
+#undef DISABLE_ASSERTS_FOR_FUZZING