no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD
cy -> fe475b7bab2af983d4cee8e65e4972bfbcf66cec
en-CA -> a3ef65c7edaf9177bc6d86a575da123b8724a329
en-GB -> a5a0d021121c2b7a8df335e57bfb0b432f7c7ca4
fr -> 4970122e206d2d09f9c62e143b6c762f0cbe1a9a
fy-NL -> 291db3aaa83f252be9f5da671b265bb6fb45a28c
gn -> 788acda2302098b31a0812bac3041737be91f8e3
it -> 12c20d432cac1df7c787e5c3d3aa112079c2378d
nn-NO -> 2b14fbbce8aab53ad5eebbe7c2f13022a0fddd0f
si -> 579629b003e5dd54eefdef80b4fd0c41bc167ee7
tr -> 1ace5ae3f35fafb4c08e4f2c6b43f87716f2bd7e
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ActorsParent.h"
#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <iterator>
#include <new>
#include <numeric>
#include <tuple>
#include <type_traits>
#include <utility>
#include "ActorsParentCommon.h"
#include "CrashAnnotations.h"
#include "DatabaseFileInfo.h"
#include "DatabaseFileManager.h"
#include "DatabaseFileManagerImpl.h"
#include "DBSchema.h"
#include "ErrorList.h"
#include "IDBCursorType.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDBCommon.h"
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "KeyPath.h"
#include "MainThreadUtils.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"
#include "SafeRefPtr.h"
#include "SchemaUpgrades.h"
#include "chrome/common/ipc_channel.h"
#include "ipc/IPCMessageUtils.h"
#include "js/RootingAPI.h"
#include "js/StructuredClone.h"
#include "js/Value.h"
#include "jsapi.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageFunction.h"
#include "mozIStorageProgressHandler.h"
#include "mozIStorageService.h"
#include "mozIStorageStatement.h"
#include "mozIStorageValueArray.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "mozilla/Algorithm.h"
#include "mozilla/ArrayAlgorithm.h"
#include "mozilla/ArrayIterator.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/Casting.h"
#include "mozilla/CondVar.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/Logging.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Maybe.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/NotNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/RefCountType.h"
#include "mozilla/RefCounted.h"
#include "mozilla/RemoteLazyInputStreamParent.h"
#include "mozilla/RemoteLazyInputStreamStorage.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/Scoped.h"
#include "mozilla/SnappyCompressOutputStream.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TaskCategory.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Variant.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/FileHandleStorage.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/IDBCursorBinding.h"
#include "mozilla/dom/IPCBlob.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/IndexedDatabase.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PBackgroundMutableFileParent.h"
#include "mozilla/dom/PContentParent.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/filehandle/ActorsParent.h"
#include "mozilla/dom/indexedDB/IDBResult.h"
#include "mozilla/dom/indexedDB/Key.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBCursor.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabase.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactory.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBRequest.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/dom/quota/CachingDatabaseConnection.h"
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/ClientImpl.h"
#include "mozilla/dom/quota/DirectoryLock.h"
#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/dom/quota/OriginScope.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/fallible.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/InputStreamParams.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/mozalloc.h"
#include "mozilla/storage/Variant.h"
#include "nsBaseHashtable.h"
#include "nsCOMPtr.h"
#include "nsClassHashtable.h"
#include "nsContentUtils.h"
#include "nsTHashMap.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsEscape.h"
#include "nsHashKeys.h"
#include "nsIAsyncInputStream.h"
#include "nsID.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIFileProtocolHandler.h"
#include "nsIFileStreams.h"
#include "nsIFileURL.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIProtocolHandler.h"
#include "nsIRunnable.h"
#include "nsISupports.h"
#include "nsISupportsPriority.h"
#include "nsISupportsUtils.h"
#include "nsIThread.h"
#include "nsIThreadInternal.h"
#include "nsITimer.h"
#include "nsIURIMutator.h"
#include "nsIVariant.h"
#include "nsLiteralString.h"
#include "nsNetCID.h"
#include "nsPrintfCString.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsStringFlags.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTHashSet.h"
#include "nsTHashtable.h"
#include "nsTLiteralString.h"
#include "nsTStringRepr.h"
#include "nsThreadPool.h"
#include "nsThreadUtils.h"
#include "nscore.h"
#include "prinrval.h"
#include "prio.h"
#include "prsystem.h"
#include "prthread.h"
#include "prtime.h"
#include "prtypes.h"
#include "snappy/snappy.h"
struct JSContext;
class JSObject;
template <class T>
class nsPtrHashKey;
#define IDB_DEBUG_LOG(_args) \
MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), LogLevel::Debug, _args)
#if defined(MOZ_WIDGET_ANDROID)
# define IDB_MOBILE
#endif
// Helper macros to reduce assertion verbosity
// AUUF == ASSERT_UNREACHABLE_UNLESS_FUZZING
#ifdef DEBUG
# ifdef FUZZING
# define NS_AUUF_OR_WARN(...) NS_WARNING(__VA_ARGS__)
# else
# define NS_AUUF_OR_WARN(...) MOZ_ASSERT(false, __VA_ARGS__)
# endif
# define NS_AUUF_OR_WARN_IF(cond) \
[](bool aCond) { \
if (MOZ_UNLIKELY(aCond)) { \
NS_AUUF_OR_WARN(#cond); \
} \
return aCond; \
}((cond))
#else
# define NS_AUUF_OR_WARN(...) \
do { \
} while (false)
# define NS_AUUF_OR_WARN_IF(cond) static_cast<bool>(cond)
#endif
namespace mozilla {
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
PR_Close);
namespace dom::indexedDB {
using namespace mozilla::dom::quota;
using namespace mozilla::ipc;
using mozilla::dom::quota::Client;
namespace {
class ConnectionPool;
class Database;
struct DatabaseActorInfo;
class DatabaseFile;
class DatabaseLoggingInfo;
class DatabaseMaintenance;
class Factory;
class Maintenance;
class MutableFile;
class OpenDatabaseOp;
class TransactionBase;
class TransactionDatabaseOperationBase;
class VersionChangeTransaction;
template <bool StatementHasIndexKeyBindings>
struct ValuePopulateResponseHelper;
/*******************************************************************************
* Constants
******************************************************************************/
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;"_ns
// For efficiency reasons, kEncryptedStreamBlockSize must be a multiple of large
// 4k disk sectors.
static_assert(kEncryptedStreamBlockSize % 4096 == 0);
// Similarly, the file copy buffer size must be a multiple of the encrypted
// block size.
static_assert(kFileCopyBufferSize % kEncryptedStreamBlockSize == 0);
constexpr auto kFileManagerDirectoryNameSuffix = u".files"_ns;
constexpr auto kSQLiteSuffix = u".sqlite"_ns;
constexpr auto kSQLiteJournalSuffix = u".sqlite-journal"_ns;
constexpr auto kSQLiteSHMSuffix = u".sqlite-shm"_ns;
constexpr auto kSQLiteWALSuffix = u".sqlite-wal"_ns;
constexpr auto kPermissionStringBase = "indexedDB-chrome-"_ns;
constexpr auto kPermissionReadSuffix = "-read"_ns;
constexpr auto kPermissionWriteSuffix = "-write"_ns;
// The following constants define all names of binding parameters in statements,
// where they are bound by name. This should include all parameter names which
// are bound by name. Binding may be done by index when the statement definition
// and binding are done in the same local scope, and no other reasons prevent
// using the indexes (e.g. multiple statement variants with differing number or
// order of parameters). Neither the styles of specifying parameter names
// (literally vs. via these constants) nor the binding styles (by index vs. by
// name) should not be mixed for the same statement. The decision must be made
// for each statement based on the proximity of statement and binding calls.
constexpr auto kStmtParamNameCurrentKey = "current_key"_ns;
constexpr auto kStmtParamNameRangeBound = "range_bound"_ns;
constexpr auto kStmtParamNameObjectStorePosition = "object_store_position"_ns;
constexpr auto kStmtParamNameLowerKey = "lower_key"_ns;
constexpr auto kStmtParamNameUpperKey = "upper_key"_ns;
constexpr auto kStmtParamNameKey = "key"_ns;
constexpr auto kStmtParamNameObjectStoreId = "object_store_id"_ns;
constexpr auto kStmtParamNameIndexId = "index_id"_ns;
// TODO: Maybe the uses of kStmtParamNameId should be replaced by more
// specific constants such as kStmtParamNameObjectStoreId.
constexpr auto kStmtParamNameId = "id"_ns;
constexpr auto kStmtParamNameValue = "value"_ns;
constexpr auto kStmtParamNameObjectDataKey = "object_data_key"_ns;
constexpr auto kStmtParamNameIndexDataValues = "index_data_values"_ns;
constexpr auto kStmtParamNameData = "data"_ns;
constexpr auto kStmtParamNameFileIds = "file_ids"_ns;
constexpr auto kStmtParamNameValueLocale = "value_locale"_ns;
constexpr auto kStmtParamNameLimit = "limit"_ns;
// The following constants define some names of columns in tables, which are
// referred to in remote locations, e.g. in calls to
// GetBindingClauseForKeyRange.
constexpr auto kColumnNameKey = "key"_ns;
constexpr auto kColumnNameValue = "value"_ns;
constexpr auto kColumnNameAliasSortKey = "sort_column"_ns;
// SQL fragments used at multiple locations.
constexpr auto kOpenLimit = " LIMIT "_ns;
// The deletion marker file is created before RemoveDatabaseFilesAndDirectory
// begins deleting a database. It is removed as the last step of deletion. If a
// deletion marker file is found when initializing the origin, the deletion
// routine is run again to ensure that the database and all of its related files
// are removed. The primary goal of this mechanism is to avoid situations where
// a database has been partially deleted, leading to inconsistent state for the
// origin.
constexpr auto kIdbDeletionMarkerFilePrefix = u"idb-deleting-"_ns;
const uint32_t kDeleteTimeoutMs = 1000;
#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
/*******************************************************************************
* Metadata classes
******************************************************************************/
// Can be instantiated either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
struct FullIndexMetadata {
IndexMetadata mCommonMetadata = {0, nsString(), KeyPath(0), nsCString(),
false, false, false};
FlippedOnce<false> mDeleted;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
private:
~FullIndexMetadata() = default;
};
using IndexTable = nsTHashMap<nsUint64HashKey, SafeRefPtr<FullIndexMetadata>>;
// Can be instantiated either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
struct FullObjectStoreMetadata {
ObjectStoreMetadata mCommonMetadata;
IndexTable mIndexes;
// The auto increment ids are touched on both the background thread and the
// transaction I/O thread, and they must be kept in sync, so we need a mutex
// to protect them.
struct AutoIncrementIds {
int64_t next;
int64_t committed;
};
DataMutex<AutoIncrementIds> mAutoIncrementIds;
FlippedOnce<false> mDeleted;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
bool HasLiveIndexes() const;
FullObjectStoreMetadata(ObjectStoreMetadata aCommonMetadata,
const AutoIncrementIds& aAutoIncrementIds)
: mCommonMetadata{std::move(aCommonMetadata)},
mAutoIncrementIds{AutoIncrementIds{aAutoIncrementIds},
"FullObjectStoreMetadata"} {}
private:
~FullObjectStoreMetadata() = default;
};
using ObjectStoreTable =
nsTHashMap<nsUint64HashKey, SafeRefPtr<FullObjectStoreMetadata>>;
static_assert(
std::is_same_v<IndexOrObjectStoreId,
std::remove_cv_t<std::remove_reference_t<
decltype(std::declval<const ObjectStoreGetParams&>()
.objectStoreId())>>>);
static_assert(
std::is_same_v<
IndexOrObjectStoreId,
std::remove_cv_t<std::remove_reference_t<
decltype(std::declval<const IndexGetParams&>().objectStoreId())>>>);
struct FullDatabaseMetadata final : AtomicSafeRefCounted<FullDatabaseMetadata> {
DatabaseMetadata mCommonMetadata;
nsCString mDatabaseId;
nsString mFilePath;
ObjectStoreTable mObjectStores;
IndexOrObjectStoreId mNextObjectStoreId = 0;
IndexOrObjectStoreId mNextIndexId = 0;
public:
explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
: mCommonMetadata(aCommonMetadata) {
AssertIsOnBackgroundThread();
}
[[nodiscard]] SafeRefPtr<FullDatabaseMetadata> Duplicate() const;
MOZ_DECLARE_REFCOUNTED_TYPENAME(FullDatabaseMetadata)
};
template <class Enumerable>
auto MatchMetadataNameOrId(const Enumerable& aEnumerable,
IndexOrObjectStoreId aId,
Maybe<const nsAString&> aName = Nothing()) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aId);
const auto it = std::find_if(
aEnumerable.cbegin(), aEnumerable.cend(),
[aId, aName](const auto& entry) {
MOZ_ASSERT(entry.GetKey() != 0);
const auto& value = entry.GetData();
MOZ_ASSERT(value);
return !value->mDeleted &&
(aId == value->mCommonMetadata.id() ||
(aName && *aName == value->mCommonMetadata.name()));
});
return ToMaybeRef(it != aEnumerable.cend() ? it->GetData().unsafeGetRawPtr()
: nullptr);
}
/*******************************************************************************
* SQLite functions
******************************************************************************/
// WARNING: the hash function used for the database name must not change.
// That's why this function exists separately from mozilla::HashString(), even
// though it is (at the time of writing) equivalent. See bug 780408 and bug
// 940315 for details.
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;
return std::accumulate(aName.BeginReading(), aName.EndReading(), uint32_t(0),
[](uint32_t hash, char16_t ch) {
return kGoldenRatioU32 *
(Helper::RotateBitsLeft32(hash, 5) ^ ch);
});
}
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%" PRIX32
") to "
"NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
static_cast<uint32_t>(aResultCode));
NS_WARNING(message.get());
#else
;
#endif
}
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
nsAutoString GetDatabaseFilenameBase(const nsAString& aDatabaseName) {
nsAutoString databaseFilenameBase;
// WARNING: do not change this hash function. See the comment in HashName()
// for details.
databaseFilenameBase.AppendInt(HashName(aDatabaseName));
nsAutoCString escapedName;
if (!NS_Escape(NS_ConvertUTF16toUTF8(aDatabaseName), 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++);
}
}
databaseFilenameBase.AppendASCII(substring.get(), substring.Length());
return databaseFilenameBase;
}
Result<nsCOMPtr<nsIFileURL>, nsresult> GetDatabaseFileURL(
nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
const Maybe<CipherKey>& aMaybeKey) {
MOZ_ASSERT(aDirectoryLockId >= -1);
QM_TRY_INSPECT(
const auto& protocolHandler,
MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIProtocolHandler>,
MOZ_SELECT_OVERLOAD(do_GetService),
NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"));
QM_TRY_INSPECT(const auto& fileHandler,
MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFileProtocolHandler>,
MOZ_SELECT_OVERLOAD(do_QueryInterface),
protocolHandler));
QM_TRY_INSPECT(const auto& mutator, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<nsIURIMutator>, fileHandler,
NewFileURIMutator, &aDatabaseFile));
// aDirectoryLockId should only be -1 when we are called
// - from DatabaseFileManager::InitDirectory when the temporary storage
// hasn't been initialized yet. At that time, the in-memory objects (e.g.
// OriginInfo) are only being created so it doesn't make sense to tunnel
// quota information to TelemetryVFS to get corresponding QuotaObject
// instances for SQLite files.
// - from DeleteDatabaseOp::LoadPreviousVersion, since this might require
// temporarily exceeding the quota limit before the database can be
// deleted.
const auto directoryLockIdClause =
aDirectoryLockId >= 0
? "&directoryLockId="_ns + IntToCString(aDirectoryLockId)
: EmptyCString();
const auto keyClause = [&aMaybeKey] {
nsAutoCString keyClause;
if (aMaybeKey) {
keyClause.AssignLiteral("&key=");
for (uint8_t byte : IndexedDBCipherStrategy::SerializeKey(*aMaybeKey)) {
keyClause.AppendPrintf("%02x", byte);
}
}
return keyClause;
}();
QM_TRY_UNWRAP(auto result, ([&mutator, &directoryLockIdClause, &keyClause] {
nsCOMPtr<nsIFileURL> result;
nsresult rv = NS_MutateURI(mutator)
.SetQuery("cache=private"_ns +
directoryLockIdClause + keyClause)
.Finalize(result);
return NS_SUCCEEDED(rv)
? Result<nsCOMPtr<nsIFileURL>, nsresult>{result}
: Err(rv);
}()));
return result;
}
nsresult SetDefaultPragmas(mozIStorageConnection& aConnection) {
MOZ_ASSERT(!NS_IsMainThread());
static constexpr auto 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;"_ns;
QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(kBuiltInPragmas)));
QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsAutoCString{
"PRAGMA synchronous = "_ns +
(IndexedDatabaseManager::FullSynchronous() ? "FULL"_ns : "NORMAL"_ns) +
";"_ns})));
#ifndef IDB_MOBILE
if (kSQLiteGrowthIncrement) {
// This is just an optimization so ignore the failure if the disk is
// currently too full.
QM_TRY(QM_OR_ELSE_WARN_IF(
// Expression.
MOZ_TO_RESULT(
aConnection.SetGrowthIncrement(kSQLiteGrowthIncrement, ""_ns)),
// Predicate.
IsSpecificError<NS_ERROR_FILE_TOO_BIG>,
// Fallback.
ErrToDefaultOk<>));
}
#endif // IDB_MOBILE
return NS_OK;
}
nsresult SetJournalMode(mozIStorageConnection& aConnection) {
MOZ_ASSERT(!NS_IsMainThread());
// Try enabling WAL mode. This can fail in various circumstances so we have to
// check the results here.
constexpr auto journalModeQueryStart = "PRAGMA journal_mode = "_ns;
constexpr auto journalModeWAL = "wal"_ns;
QM_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement(
aConnection, journalModeQueryStart + journalModeWAL));
QM_TRY_INSPECT(
const auto& journalMode,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt, GetUTF8String, 0));
if (journalMode.Equals(journalModeWAL)) {
// WAL mode successfully enabled. Maybe set limits on its size here.
if (kMaxWALPages >= 0) {
QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
"PRAGMA wal_autocheckpoint = "_ns + IntToCString(kMaxWALPages))));
}
} else {
NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
#ifdef IDB_MOBILE
QM_TRY(MOZ_TO_RESULT(
aConnection.ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns)));
#endif
}
return NS_OK;
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> OpenDatabase(
mozIStorageService& aStorageService, nsIFileURL& aFileURL,
const uint32_t aTelemetryId = 0) {
const nsAutoCString telemetryFilename =
aTelemetryId ? "indexedDB-"_ns + IntToCString(aTelemetryId) +
NS_ConvertUTF16toUTF8(kSQLiteSuffix)
: nsAutoCString();
QM_TRY_UNWRAP(auto connection,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<mozIStorageConnection>, aStorageService,
OpenDatabaseWithFileURL, &aFileURL, telemetryFilename,
mozIStorageService::CONNECTION_DEFAULT));
return WrapMovingNotNull(std::move(connection));
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
OpenDatabaseAndHandleBusy(mozIStorageService& aStorageService,
nsIFileURL& aFileURL,
const uint32_t aTelemetryId = 0) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
using ConnectionType = Maybe<MovingNotNull<nsCOMPtr<mozIStorageConnection>>>;
QM_TRY_UNWRAP(auto connection,
QM_OR_ELSE_WARN_IF(
// Expression
OpenDatabase(aStorageService, aFileURL, aTelemetryId)
.map([](auto connection) -> ConnectionType {
return Some(std::move(connection));
}),
// Predicate.
IsSpecificError<NS_ERROR_STORAGE_BUSY>,
// Fallback.
ErrToDefaultOk<ConnectionType>));
if (connection.isNothing()) {
#ifdef DEBUG
{
nsCString path;
MOZ_ALWAYS_SUCCEEDS(aFileURL.GetFileName(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.
const TimeStamp start = TimeStamp::NowLoRes();
do {
PR_Sleep(PR_MillisecondsToInterval(100));
QM_TRY_UNWRAP(connection,
QM_OR_ELSE_WARN_IF(
// Expression.
OpenDatabase(aStorageService, aFileURL, aTelemetryId)
.map([](auto connection) -> ConnectionType {
return Some(std::move(connection));
}),
// Predicate.
([&start](nsresult aValue) {
return aValue == NS_ERROR_STORAGE_BUSY &&
TimeStamp::NowLoRes() - start <=
TimeDuration::FromSeconds(10);
}),
// Fallback.
ErrToDefaultOk<ConnectionType>));
} while (connection.isNothing());
}
return connection.extract();
}
// Returns true if a given nsIFile exists and is a directory. Returns false if
// it doesn't exist. Returns an error if it exists, but is not a directory, or
// any other error occurs.
Result<bool, nsresult> ExistsAsDirectory(nsIFile& aDirectory) {
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
if (exists) {
QM_TRY_INSPECT(const bool& isDirectory,
MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE));
}
return exists;
}
constexpr nsresult mapNoDeviceSpaceError(nsresult aRv) {
if (aRv == 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.
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
}
return aRv;
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
CreateStorageConnection(nsIFile& aDBFile, nsIFile& aFMDirectory,
const nsAString& aName, const nsACString& aOrigin,
const int64_t aDirectoryLockId,
const uint32_t aTelemetryId,
const Maybe<CipherKey>& aMaybeKey) {
AssertIsOnIOThread();
MOZ_ASSERT(aDirectoryLockId >= -1);
AUTO_PROFILER_LABEL("CreateStorageConnection", DOM);
QM_TRY_INSPECT(const auto& dbFileUrl,
GetDatabaseFileURL(aDBFile, aDirectoryLockId, aMaybeKey));
QM_TRY_INSPECT(const auto& storageService,
MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
MOZ_SELECT_OVERLOAD(do_GetService),
MOZ_STORAGE_SERVICE_CONTRACTID));
QM_TRY_UNWRAP(
auto connection,
QM_OR_ELSE_WARN_IF(
// Expression.
OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId)
.map([](auto connection) -> nsCOMPtr<mozIStorageConnection> {
return std::move(connection).unwrapBasePtr();
}),
// Predicate.
([&aName](nsresult aValue) {
// 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.
return IsDatabaseCorruptionError(aValue) && !aName.IsVoid();
}),
// Fallback.
ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
if (!connection) {
// XXX Shouldn't we also update quota usage?
// Nuke the database file.
QM_TRY(MOZ_TO_RESULT(aDBFile.Remove(false)));
QM_TRY_INSPECT(const bool& existsAsDirectory,
ExistsAsDirectory(aFMDirectory));
if (existsAsDirectory) {
QM_TRY(MOZ_TO_RESULT(aFMDirectory.Remove(true)));
}
QM_TRY_UNWRAP(connection, OpenDatabaseAndHandleBusy(
*storageService, *dbFileUrl, aTelemetryId));
}
QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection)));
QM_TRY(MOZ_TO_RESULT(connection->EnableModule("filesystem"_ns)));
// Check to make sure that the database schema is correct.
QM_TRY_INSPECT(const int32_t& schemaVersion,
MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion));
// Unknown schema will fail origin initialization too.
QM_TRY(OkIf(schemaVersion || !aName.IsVoid()),
Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
IDB_WARNING("Unable to open IndexedDB database, schema is not set!");
});
QM_TRY(
OkIf(schemaVersion <= kSQLiteSchemaVersion),
Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
});
bool journalModeSet = false;
if (schemaVersion != kSQLiteSchemaVersion) {
const bool newDatabase = !schemaVersion;
if (newDatabase) {
// Set the page size first.
const auto sqlitePageSizeOverride =
aMaybeKey ? 8192 : kSQLitePageSizeOverride;
if (sqlitePageSizeOverride) {
QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
"PRAGMA page_size = %" PRIu32 ";", sqlitePageSizeOverride))));
}
// We have to set the auto_vacuum mode before opening a transaction.
QM_TRY((MOZ_TO_RESULT_INVOKE_MEMBER(
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).
"PRAGMA auto_vacuum = FULL;"_ns
#else
// Turn on incremental auto_vacuum mode on desktop builds.
"PRAGMA auto_vacuum = INCREMENTAL;"_ns
#endif
)
.mapErr(mapNoDeviceSpaceError)));
QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
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("PRAGMA foreign_keys = OFF;"_ns));
#endif
}
bool vacuumNeeded = false;
mozStorageTransaction transaction(
connection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
QM_TRY(MOZ_TO_RESULT(transaction.Start()));
if (newDatabase) {
QM_TRY(MOZ_TO_RESULT(CreateTables(*connection)));
#ifdef DEBUG
{
QM_TRY_INSPECT(
const int32_t& schemaVersion,
MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion),
QM_ASSERT_UNREACHABLE);
MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
}
#endif
// The parameter names are not used, parameters are bound by index only
// locally in the same function.
QM_TRY_INSPECT(
const auto& stmt,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
"INSERT INTO database (name, origin) "
"VALUES (:name, :origin)"_ns));
QM_TRY(MOZ_TO_RESULT(stmt->BindStringByIndex(0, aName)));
QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByIndex(1, aOrigin)));
QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
} else {
QM_TRY_UNWRAP(vacuumNeeded, MaybeUpgradeSchema(*connection, schemaVersion,
aFMDirectory, aOrigin));
}
QM_TRY(MOZ_TO_RESULT_INVOKE_MEMBER(transaction, Commit)
.mapErr(mapNoDeviceSpaceError));
#ifdef DEBUG
if (!newDatabase) {
// Re-enable foreign key support after doing a foreign key check.
QM_TRY_INSPECT(const bool& foreignKeyError,
CreateAndExecuteSingleStepStatement<
SingleStepResult::ReturnNullIfNoResult>(
*connection, "PRAGMA foreign_key_check;"_ns),
QM_ASSERT_UNREACHABLE);
MOZ_ASSERT(!foreignKeyError, "Database has inconsisistent foreign keys!");
MOZ_ALWAYS_SUCCEEDS(
connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
}
#endif
if (kSQLitePageSizeOverride && !newDatabase) {
QM_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement(
*connection, "PRAGMA page_size;"_ns));
QM_TRY_INSPECT(const int32_t& pageSize,
MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
// We must not be in WAL journal mode to change the page size.
QM_TRY(MOZ_TO_RESULT(
connection->ExecuteSimpleSQL("PRAGMA journal_mode = DELETE;"_ns)));
QM_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement(
*connection, "PRAGMA journal_mode;"_ns));
QM_TRY_INSPECT(const auto& journalMode,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt,
GetUTF8String, 0));
if (journalMode.EqualsLiteral("delete")) {
// Successfully set to rollback journal mode so changing the page size
// is possible with a VACUUM.
QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
"PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
// 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) {
QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("VACUUM;"_ns)));
}
if (newDatabase || vacuumNeeded) {
if (journalModeSet) {
// Make sure we checkpoint to get an accurate file size.
QM_TRY(MOZ_TO_RESULT(
connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(FULL);"_ns)));
}
QM_TRY_INSPECT(const int64_t& fileSize,
MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, GetFileSize));
MOZ_ASSERT(fileSize > 0);
PRTime vacuumTime = PR_Now();
MOZ_ASSERT(vacuumTime);
// The parameter names are not used, parameters are bound by index only
// locally in the same function.
QM_TRY_INSPECT(
const auto& vacuumTimeStmt,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<mozIStorageStatement>,
connection, CreateStatement,
"UPDATE database "
"SET last_vacuum_time = :time"
", last_vacuum_size = :size;"_ns));
QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(0, vacuumTime)));
QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(1, fileSize)));
QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->Execute()));
}
}
if (!journalModeSet) {
QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
}
return WrapMovingNotNullUnchecked(std::move(connection));
}
nsCOMPtr<nsIFile> GetFileForPath(const nsAString& aPath) {
MOZ_ASSERT(!aPath.IsEmpty());
QM_TRY_RETURN(QM_NewLocalFile(aPath), nullptr);
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
GetStorageConnection(nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
const uint32_t aTelemetryId,
const Maybe<CipherKey>& aMaybeKey) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(aDirectoryLockId >= 0);
AUTO_PROFILER_LABEL("GetStorageConnection", DOM);
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, Exists));
QM_TRY(OkIf(exists), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
IDB_REPORT_INTERNAL_ERR_LAMBDA);
QM_TRY_INSPECT(
const auto& dbFileUrl,
GetDatabaseFileURL(aDatabaseFile, aDirectoryLockId, aMaybeKey));
QM_TRY_INSPECT(const auto& storageService,
MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
MOZ_SELECT_OVERLOAD(do_GetService),
MOZ_STORAGE_SERVICE_CONTRACTID));
QM_TRY_UNWRAP(
nsCOMPtr<mozIStorageConnection> connection,
OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId));
QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection)));
QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
return WrapMovingNotNullUnchecked(std::move(connection));
}
Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
GetStorageConnection(const nsAString& aDatabaseFilePath,
const int64_t aDirectoryLockId,
const uint32_t aTelemetryId,
const Maybe<CipherKey>& aMaybeKey) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, kSQLiteSuffix));
MOZ_ASSERT(aDirectoryLockId >= 0);
nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
QM_TRY(OkIf(dbFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
IDB_REPORT_INTERNAL_ERR_LAMBDA);
return GetStorageConnection(*dbFile, aDirectoryLockId, aTelemetryId,
aMaybeKey);
}
/*******************************************************************************
* ConnectionPool declarations
******************************************************************************/
class DatabaseConnection final : public CachingDatabaseConnection {
friend class ConnectionPool;
enum class CheckpointMode { Full, Restart, Truncate };
public:
class AutoSavepoint;
class UpdateRefcountFunction;
private:
InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileManager>>> mFileManager;
RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
RefPtr<QuotaObject> mQuotaObject;
RefPtr<QuotaObject> mJournalQuotaObject;
bool mInReadTransaction;
bool mInWriteTransaction;
#ifdef DEBUG
uint32_t mDEBUGSavepointCount;
#endif
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection)
UpdateRefcountFunction* GetUpdateRefcountFunction() const {
AssertIsOnConnectionThread();
return mUpdateRefcountFunction;
}
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(
MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection,
MovingNotNull<SafeRefPtr<DatabaseFileManager>> aFileManager);
~DatabaseConnection();
nsresult Init();
nsresult CheckpointInternal(CheckpointMode aMode);
Result<uint32_t, nsresult> GetFreelistCount(
CachedStatement& aCachedStatement);
/**
* On success, returns whether some pages were freed.
*/
Result<bool, nsresult> ReclaimFreePagesWhileIdle(
CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement,
uint32_t aFreelistCount, bool aNeedsCheckpoint);
Result<int64_t, nsresult> GetFileSize(const nsAString& aPath);
};
class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final {
DatabaseConnection* mConnection;
#ifdef DEBUG
const TransactionBase* mDEBUGTransaction;
#endif
public:
AutoSavepoint();
~AutoSavepoint();
nsresult Start(const TransactionBase& aTransaction);
nsresult Commit();
};
class DatabaseConnection::UpdateRefcountFunction final
: public mozIStorageFunction {
class FileInfoEntry;
enum class UpdateType { Increment, Decrement };
DatabaseConnection* const mConnection;
DatabaseFileManager& mFileManager;
nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
nsTHashMap<nsUint64HashKey, NotNull<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,
DatabaseFileManager& aFileManager);
nsresult WillCommit();
void DidCommit();
void DidAbort();
void StartSavepoint();
void ReleaseSavepoint();
void RollbackSavepoint();
void Reset();
private:
~UpdateRefcountFunction() = default;
nsresult ProcessValue(mozIStorageValueArray* aValues, int32_t aIndex,
UpdateType aUpdateType);
nsresult CreateJournals();
nsresult RemoveJournals(const nsTArray<int64_t>& aJournals);
};
class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final {
SafeRefPtr<DatabaseFileInfo> mFileInfo;
int32_t mDelta;
int32_t mSavepointDelta;
public:
explicit FileInfoEntry(SafeRefPtr<DatabaseFileInfo> aFileInfo)
: mFileInfo(std::move(aFileInfo)), mDelta(0), mSavepointDelta(0) {
MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
}
void IncDeltas(bool aUpdateSavepointDelta) {
++mDelta;
if (aUpdateSavepointDelta) {
++mSavepointDelta;
}
}
void DecDeltas(bool aUpdateSavepointDelta) {
--mDelta;
if (aUpdateSavepointDelta) {
--mSavepointDelta;
}
}
void DecBySavepointDelta() { mDelta -= mSavepointDelta; }
SafeRefPtr<DatabaseFileInfo> ReleaseFileInfo() {
return std::move(mFileInfo);
}
void MaybeUpdateDBRefs() {
if (mDelta) {
mFileInfo->UpdateDBRefs(mDelta);
}
}
int32_t Delta() const { return mDelta; }
int32_t SavepointDelta() const { return mSavepointDelta; }
~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;
class ThreadRunnable;
class TransactionInfo;
struct TransactionInfoPair;
struct IdleResource {
TimeStamp mIdleTime;
IdleResource(const IdleResource& aOther) = delete;
IdleResource(IdleResource&& aOther) noexcept
: IdleResource(aOther.mIdleTime) {}
IdleResource& operator=(const IdleResource& aOther) = delete;
IdleResource& operator=(IdleResource&& aOther) = delete;
protected:
explicit IdleResource(const TimeStamp& aIdleTime);
~IdleResource();
};
struct IdleDatabaseInfo final : public IdleResource {
InitializedOnce<const NotNull<DatabaseInfo*>> mDatabaseInfo;
public:
explicit IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo);
IdleDatabaseInfo(const IdleDatabaseInfo& aOther) = delete;
IdleDatabaseInfo(IdleDatabaseInfo&& aOther) noexcept
: IdleResource(std::move(aOther)),
mDatabaseInfo{std::move(aOther.mDatabaseInfo)} {
MOZ_ASSERT(mDatabaseInfo);
MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
}
IdleDatabaseInfo& operator=(const IdleDatabaseInfo& aOther) = delete;
IdleDatabaseInfo& operator=(IdleDatabaseInfo&& aOther) = delete;
~IdleDatabaseInfo();
bool operator==(const IdleDatabaseInfo& aOther) const {
return *mDatabaseInfo == *aOther.mDatabaseInfo;
}
bool operator==(const DatabaseInfo* aDatabaseInfo) const {
return *mDatabaseInfo == aDatabaseInfo;
}
bool operator<(const IdleDatabaseInfo& aOther) const {
return mIdleTime < aOther.mIdleTime;
}
};
class ThreadInfo {
public:
ThreadInfo();
ThreadInfo(nsCOMPtr<nsIThread> aThread, RefPtr<ThreadRunnable> aRunnable)
: mThread{std::move(aThread)}, mRunnable{std::move(aRunnable)} {
AssertIsOnBackgroundThread();
AssertValid();
MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
}
ThreadInfo(const ThreadInfo& aOther) = delete;
ThreadInfo& operator=(const ThreadInfo& aOther) = delete;
ThreadInfo(ThreadInfo&& aOther) noexcept;
ThreadInfo& operator=(ThreadInfo&& aOther) = default;
bool IsValid() const {
const bool res = mThread;
if (res) {
AssertValid();
} else {
AssertEmpty();
}
return res;
}
void AssertValid() const {
MOZ_ASSERT(mThread);
MOZ_ASSERT(mRunnable);
}
void AssertEmpty() const {
MOZ_ASSERT(!mThread);
MOZ_ASSERT(!mRunnable);
}
nsIThread& ThreadRef() {
AssertValid();
return *mThread;
}
std::tuple<nsCOMPtr<nsIThread>, RefPtr<ThreadRunnable>> Forget() {
AssertValid();
return {std::move(mThread), std::move(mRunnable)};
}
~ThreadInfo();
bool operator==(const ThreadInfo& aOther) const {
return mThread == aOther.mThread && mRunnable == aOther.mRunnable;
}
private:
nsCOMPtr<nsIThread> mThread;
RefPtr<ThreadRunnable> mRunnable;
};
struct IdleThreadInfo final : public IdleResource {
ThreadInfo mThreadInfo;
explicit IdleThreadInfo(ThreadInfo aThreadInfo);
IdleThreadInfo(const IdleThreadInfo& aOther) = delete;
IdleThreadInfo(IdleThreadInfo&& aOther) noexcept
: IdleResource(std::move(aOther)),
mThreadInfo(std::move(aOther.mThreadInfo)) {
AssertIsOnBackgroundThread();
mThreadInfo.AssertValid();
MOZ_COUNT_CTOR(ConnectionPool::IdleThreadInfo);
}
IdleThreadInfo& operator=(const IdleThreadInfo& aOther) = delete;
IdleThreadInfo& operator=(IdleThreadInfo&& aOther) = delete;
~IdleThreadInfo();
bool operator==(const IdleThreadInfo& aOther) const {
return mThreadInfo == aOther.mThreadInfo;
}
bool operator<(const IdleThreadInfo& aOther) const {
return mIdleTime < aOther.mIdleTime;
}
};
// This mutex guards mDatabases, see below.
Mutex mDatabasesMutex MOZ_UNANNOTATED;
nsTArray<IdleThreadInfo> mIdleThreads;
nsTArray<IdleDatabaseInfo> mIdleDatabases;
nsTArray<NotNull<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<NotNull<TransactionInfo*>> mQueuedTransactions;
nsTArray<UniquePtr<DatabasesCompleteCallback>> mCompleteCallbacks;
uint64_t mNextTransactionId;
uint32_t mTotalThreadCount;
FlippedOnce<false> mShutdownRequested;
FlippedOnce<false> mShutdownComplete;
public:
ConnectionPool();
void AssertIsOnOwningThread() const {
NS_ASSERT_OWNINGTHREAD(ConnectionPool);
}
Result<RefPtr<DatabaseConnection>, nsresult> GetOrCreateConnection(
const Database& aDatabase);
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) const;
bool CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId);
};
class ConnectionPool::ConnectionRunnable : public Runnable {
protected:
DatabaseInfo& mDatabaseInfo;
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
explicit ConnectionRunnable(DatabaseInfo& aDatabaseInfo);
~ConnectionRunnable() override = default;
};
class ConnectionPool::IdleConnectionRunnable final : public ConnectionRunnable {
const bool mNeedsCheckpoint;
public:
IdleConnectionRunnable(DatabaseInfo& aDatabaseInfo, bool aNeedsCheckpoint)
: ConnectionRunnable(aDatabaseInfo), mNeedsCheckpoint(aNeedsCheckpoint) {}
NS_INLINE_DECL_REFCOUNTING_INHERITED(IdleConnectionRunnable,
ConnectionRunnable)
private:
~IdleConnectionRunnable() override = default;
NS_DECL_NSIRUNNABLE
};
class ConnectionPool::CloseConnectionRunnable final
: public ConnectionRunnable {
public:
explicit CloseConnectionRunnable(DatabaseInfo& aDatabaseInfo)
: ConnectionRunnable(aDatabaseInfo) {}
NS_INLINE_DECL_REFCOUNTING_INHERITED(CloseConnectionRunnable,
ConnectionRunnable)
private:
~CloseConnectionRunnable() override = default;
NS_DECL_NSIRUNNABLE
};
struct ConnectionPool::DatabaseInfo final {
friend class mozilla::DefaultDelete<DatabaseInfo>;
RefPtr<ConnectionPool> mConnectionPool;
const nsCString mDatabaseId;
RefPtr<DatabaseConnection> mConnection;
nsClassHashtable<nsStringHashKey, TransactionInfoPair> mBlockingTransactions;
nsTArray<NotNull<TransactionInfo*>> mTransactionsScheduledDuringClose;
nsTArray<NotNull<TransactionInfo*>> mScheduledWriteTransactions;
Maybe<TransactionInfo&> mRunningWriteTransaction;
ThreadInfo mThreadInfo;
uint32_t mReadTransactionCount;
uint32_t mWriteTransactionCount;
bool mNeedsCheckpoint;
bool mIdle;
FlippedOnce<false> 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 DefaultDelete<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() = default;
virtual ~FinishCallback() = default;
};
class ConnectionPool::FinishCallbackWrapper final : public Runnable {
RefPtr<ConnectionPool> mConnectionPool;
RefPtr<FinishCallback> mCallback;
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
uint64_t mTransactionId;
bool mHasRunOnce;
public:
FinishCallbackWrapper(ConnectionPool* aConnectionPool,
uint64_t aTransactionId, FinishCallback* aCallback);
NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishCallbackWrapper, Runnable)
private:
~FinishCallbackWrapper() override;
NS_DECL_NSIRUNNABLE
};
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.
FlippedOnce<true> mFirstRun;
FlippedOnce<true> mContinueRunning;
public:
ThreadRunnable();
NS_INLINE_DECL_REFCOUNTING_INHERITED(ThreadRunnable, Runnable)
uint32_t SerialNumber() const { return mSerialNumber; }
nsCString GetThreadName() const {
return nsPrintfCString("IndexedDB #%" PRIu32, mSerialNumber);
}
private:
~ThreadRunnable() override;
NS_DECL_NSIRUNNABLE
};
class ConnectionPool::TransactionInfo final {
friend class mozilla::DefaultDelete<TransactionInfo>;
nsTHashSet<TransactionInfo*> mBlocking;
nsTArray<NotNull<TransactionInfo*>> mBlockingOrdered;
public:
DatabaseInfo& mDatabaseInfo;
const nsID mBackgroundChildLoggingId;
const nsCString mDatabaseId;
const uint64_t mTransactionId;
const int64_t mLoggingSerialNumber;
const nsTArray<nsString> mObjectStoreNames;
nsTHashSet<TransactionInfo*> mBlockedOn;
nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
const bool mIsWriteTransaction;
bool mRunning;
#ifdef DEBUG
FlippedOnce<false> 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 {
// Multiple reading transactions can block future writes.
nsTArray<NotNull<TransactionInfo*>> mLastBlockingWrites;
// But only a single writing transaction can block future reads.
Maybe<TransactionInfo&> mLastBlockingReads;
#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
TransactionInfoPair();
~TransactionInfoPair();
#endif
};
/*******************************************************************************
* Actor class declarations
******************************************************************************/
template <IDBCursorType CursorType>
class CommonOpenOpHelper;
template <IDBCursorType CursorType>
class IndexOpenOpHelper;
template <IDBCursorType CursorType>
class ObjectStoreOpenOpHelper;
template <IDBCursorType CursorType>
class OpenOpHelper;
class DatabaseOperationBase : public Runnable,
public mozIStorageProgressHandler {
template <IDBCursorType CursorType>
friend class OpenOpHelper;
protected:
class AutoSetProgressHandler;
using UniqueIndexTable = nsTHashMap<nsUint64HashKey, bool>;
const nsCOMPtr<nsIEventTarget> mOwningEventTarget;
const nsID mBackgroundChildLoggingId;
const uint64_t mLoggingSerialNumber;
private:
nsresult mResultCode = NS_OK;
Atomic<bool> mOperationMayProceed;
FlippedOnce<false> mActorDestroyed;
public:
NS_DECL_ISUPPORTS_INHERITED
bool IsOnOwningThread() const {
MOZ_ASSERT(mOwningEventTarget);
bool current;
return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) &&
current;
}
void AssertIsOnOwningThread() const {
MOZ_ASSERT(IsOnBackgroundThread());
MOZ_ASSERT(IsOnOwningThread());
}
void NoteActorDestroyed() {
AssertIsOnOwningThread();
mActorDestroyed.EnsureFlipped();
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 aFailureCode) {
MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
OverrideFailureCode(aFailureCode);
}
void SetFailureCodeIfUnset(nsresult aFailureCode) {
if (NS_SUCCEEDED(mResultCode)) {
OverrideFailureCode(aFailureCode);
}
}
bool HasFailed() const { return NS_FAILED(mResultCode); }
protected:
DatabaseOperationBase(const nsID& aBackgroundChildLoggingId,
uint64_t aLoggingSerialNumber)
: Runnable("dom::indexedDB::DatabaseOperationBase"),
mOwningEventTarget(GetCurrentEventTarget()),
mBackgroundChildLoggingId(aBackgroundChildLoggingId),
mLoggingSerialNumber(aLoggingSerialNumber),
mOperationMayProceed(true) {
AssertIsOnOwningThread();
}
~DatabaseOperationBase() override { MOZ_ASSERT(mActorDestroyed); }
void OverrideFailureCode(nsresult aFailureCode) {
MOZ_ASSERT(NS_FAILED(aFailureCode));
mResultCode = aFailureCode;
}
static nsAutoCString MaybeGetBindingClauseForKeyRange(
const Maybe<SerializedKeyRange>& aOptionalKeyRange,
const nsACString& aKeyColumnName);
static nsAutoCString GetBindingClauseForKeyRange(
const SerializedKeyRange& aKeyRange, const nsACString& aKeyColumnName);
static uint64_t ReinterpretDoubleAsUInt64(double aDouble);
static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
mozIStorageStatement* aStatement);
static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
mozIStorageStatement* aStatement,
const nsCString& aLocale);
static Result<IndexDataValuesAutoArray, nsresult>
IndexDataValuesFromUpdateInfos(const nsTArray<IndexUpdateInfo>& aUpdateInfos,
const UniqueIndexTable& aUniqueIndexTable);
static nsresult InsertIndexTableRows(
DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues);
static nsresult DeleteIndexDataTableRows(
DatabaseConnection* aConnection, const Key& aObjectStoreKey,
const nsTArray<IndexDataValue>& aIndexValues);
static nsresult DeleteObjectStoreDataTableRowsWithIndexes(
DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
const Maybe<SerializedKeyRange>& aKeyRange);
static nsresult UpdateIndexValues(
DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues);
static Result<bool, nsresult> ObjectStoreHasIndexes(
DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId);
private:
template <typename KeyTransformation>
static nsresult MaybeBindKeyToStatement(
const Key& aKey, mozIStorageStatement* aStatement,
const nsACString& aParameterName,
const KeyTransformation& aKeyTransformation);
template <typename KeyTransformation>
static nsresult BindTransformedKeyRangeToStatement(
const SerializedKeyRange& aKeyRange, mozIStorageStatement* aStatement,
const KeyTransformation& aKeyTransformation);
// Not to be overridden by subclasses.
NS_DECL_MOZISTORAGEPROGRESSHANDLER
};
class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final {
Maybe<mozIStorageConnection&> mConnection;
#ifdef DEBUG
DatabaseOperationBase* mDEBUGDatabaseOp;
#endif
public:
AutoSetProgressHandler();
~AutoSetProgressHandler();
nsresult Register(mozIStorageConnection& aConnection,
DatabaseOperationBase* aDatabaseOp);
void Unregister();
};
class TransactionDatabaseOperationBase : public DatabaseOperationBase {
enum class InternalState {
Initial,
DatabaseWork,
SendingPreprocess,
WaitingForContinue,
SendingResults,
Completed
};
InitializedOnce<const NotNull<SafeRefPtr<TransactionBase>>> mTransaction;
InternalState mInternalState = InternalState::Initial;
bool mWaitingForContinue = false;
const bool mTransactionIsAborted;
protected:
const int64_t mTransactionLoggingSerialNumber;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
protected:
// A check only enables when the diagnostic assert turns on. It assumes the
// mUpdateRefcountFunction is a nullptr because the previous
// StartTransactionOp failed on the connection thread and the next write
// operation (e.g. ObjectstoreAddOrPutRequestOp) doesn't have enough time to
// catch up the failure information.
bool mAssumingPreviousOperationFail = false;
#endif
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() { return **mTransaction; }
const TransactionBase& Transaction() const { return **mTransaction; }
bool IsWaitingForContinue() const {
AssertIsOnOwningThread();
return mWaitingForContinue;
}
void NoteContinueReceived();
int64_t TransactionLoggingSerialNumber() const {
return mTransactionLoggingSerialNumber;
}
// 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(
SafeRefPtr<TransactionBase> aTransaction);
TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction,
uint64_t aLoggingSerialNumber);
~TransactionDatabaseOperationBase() override;
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;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
auto MakeAutoSavepointCleanupHandler(DatabaseConnection& aConnection) {
return [this, &aConnection](const auto) {
if (!aConnection.GetUpdateRefcountFunction()) {
mAssumingPreviousOperationFail = true;
}
};
}
#endif
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,
public AtomicSafeRefCounted<Factory> {
RefPtr<DatabaseLoggingInfo> mLoggingInfo;
#ifdef DEBUG
bool mActorDestroyed;
#endif
// Reference counted.
~Factory() override;
public:
[[nodiscard]] static SafeRefPtr<Factory> Create(
const LoggingInfo& aLoggingInfo);
DatabaseLoggingInfo* GetLoggingInfo() const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mLoggingInfo);
return mLoggingInfo;
}
MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Factory)
MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(Factory, AtomicSafeRefCounted)
// Only constructed in Create().
explicit Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo);
// IPDL methods are only called by IPDL.
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvDeleteMe() override;
PBackgroundIDBFactoryRequestParent* AllocPBackgroundIDBFactoryRequestParent(
const FactoryRequestParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBFactoryRequestConstructor(
PBackgroundIDBFactoryRequestParent* aActor,
const FactoryRequestParams& aParams) override;
bool DeallocPBackgroundIDBFactoryRequestParent(
PBackgroundIDBFactoryRequestParent* aActor) override;
PBackgroundIDBDatabaseParent* AllocPBackgroundIDBDatabaseParent(
const DatabaseSpec& aSpec,
PBackgroundIDBFactoryRequestParent* aRequest) override;
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 nsACString& aDatabaseId,
nsIRunnable* aCallback)
: Runnable("dom::indexedDB::WaitForTransactionsHelper"),
mDatabaseId(aDatabaseId),
mCallback(aCallback),
mState(State::Initial) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!aDatabaseId.IsEmpty());
MOZ_ASSERT(aCallback);
}
void WaitForTransactions();
NS_INLINE_DECL_REFCOUNTING_INHERITED(WaitForTransactionsHelper, Runnable)
private:
~WaitForTransactionsHelper() override {
MOZ_ASSERT(!mCallback);
MOZ_ASSERT(mState == State::Complete);
}
void MaybeWaitForTransactions();
void MaybeWaitForFileHandles();
void CallCallback();
NS_DECL_NSIRUNNABLE
};
class Database final
: public PBackgroundIDBDatabaseParent,
public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>>,
public AtomicSafeRefCounted<Database> {
friend class VersionChangeTransaction;
class StartTransactionOp;
class UnmapBlobCallback;
private:
SafeRefPtr<Factory> mFactory;
SafeRefPtr<FullDatabaseMetadata> mMetadata;
SafeRefPtr<DatabaseFileManager> mFileManager;
RefPtr<DirectoryLock> mDirectoryLock;
nsTHashSet<TransactionBase*> mTransactions;
nsTHashSet<MutableFile*> mMutableFiles;
nsTHashMap<nsIDHashKey, SafeRefPtr<DatabaseFileInfo>> mMappedBlobs;
RefPtr<DatabaseConnection> mConnection;
const PrincipalInfo mPrincipalInfo;
const Maybe<ContentParentId> mOptionalContentParentId;
// XXX Consider changing this to ClientMetadata.
const quota::OriginMetadata mOriginMetadata;
const nsCString mId;
const nsString mFilePath;
const Maybe<const CipherKey> mKey;
uint32_t mActiveMutableFileCount;
uint32_t mPendingCreateFileOpCount;
int64_t mDirectoryLockId;
const uint32_t mTelemetryId;
const PersistenceType mPersistenceType;
const bool mFileHandleDisabled;
const bool mChromeWriteAccessAllowed;
const bool mInPrivateBrowsing;
FlippedOnce<false> mClosed;
FlippedOnce<false> mInvalidated;
FlippedOnce<false> mActorWasAlive;
FlippedOnce<false> mActorDestroyed;
nsCOMPtr<nsIEventTarget> mBackgroundThread;
#ifdef DEBUG
bool mAllBlobsUnmapped;
#endif
public:
// Created by OpenDatabaseOp.
Database(SafeRefPtr<Factory> aFactory, const PrincipalInfo& aPrincipalInfo,
const Maybe<ContentParentId>& aOptionalContentParentId,
const quota::OriginMetadata& aOriginMetadata, uint32_t aTelemetryId,
SafeRefPtr<FullDatabaseMetadata> aMetadata,
SafeRefPtr<DatabaseFileManager> aFileManager,
RefPtr<DirectoryLock> aDirectoryLock, bool aFileHandleDisabled,
bool aChromeWriteAccessAllowed, bool aInPrivateBrowsing,
const Maybe<const CipherKey>& aMaybeKey);
void AssertIsOnConnectionThread() const {
#ifdef DEBUG
if (mConnection) {
MOZ_ASSERT(mConnection);
mConnection->AssertIsOnConnectionThread();
} else {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(mInvalidated);
}
#endif
}
MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Database)
void Invalidate();
bool IsOwnedByProcess(ContentParentId aContentParentId) const {
return mOptionalContentParentId &&
mOptionalContentParentId.value() == aContentParentId;
}
const quota::OriginMetadata& OriginMetadata() const {
return mOriginMetadata;
}
const nsCString& Id() const { return mId; }
Maybe<DirectoryLock&> MaybeDirectoryLockRef() const {
AssertIsOnBackgroundThread();
return ToMaybeRef(mDirectoryLock.get());
}
int64_t DirectoryLockId() const { return mDirectoryLockId; }
uint32_t TelemetryId() const { return mTelemetryId; }
PersistenceType Type() const { return mPersistenceType; }
const nsString& FilePath() const { return mFilePath; }
DatabaseFileManager& GetFileManager() const { return *mFileManager; }
MovingNotNull<SafeRefPtr<DatabaseFileManager>> GetFileManagerPtr() const {
return WrapMovingNotNull(mFileManager.clonePtr());
}
const FullDatabaseMetadata& Metadata() const {
MOZ_ASSERT(mMetadata);
return *mMetadata;
}
SafeRefPtr<FullDatabaseMetadata> MetadataPtr() const {
MOZ_ASSERT(mMetadata);
return mMetadata.clonePtr();
}
PBackgroundParent* GetBackgroundParent() const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!IsActorDestroyed());
return Manager()->Manager();
}
DatabaseLoggingInfo* GetLoggingInfo() const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mFactory);
return mFactory->GetLoggingInfo();
}
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 NotePendingCreateFileOp();
void NoteCompletedCreateFileOp();
void SetActorAlive();
void MapBlob(const IPCBlob& aIPCBlob, SafeRefPtr<DatabaseFileInfo> aFileInfo);
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;
}
void Stringify(nsACString& aResult) const;
bool IsInPrivateBrowsing() const {
AssertIsOnBackgroundThread();
return mInPrivateBrowsing;
}
const Maybe<const CipherKey>& MaybeKeyRef() const {
// This can be called on any thread, as it is const.
MOZ_ASSERT(mKey.isSome() == mInPrivateBrowsing);
return mKey;
}
~Database() override {
MOZ_ASSERT(mClosed);
MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
NS_ProxyRelease("ReleaseIDBFactory", mBackgroundThread.get(),
mFactory.forget());
}
private:
[[nodiscard]] SafeRefPtr<DatabaseFileInfo> GetBlob(const IPCBlob& aIPCBlob);
void UnmapBlob(const nsID& aID);
void UnmapAllBlobs();
bool CloseInternal();
void MaybeCloseConnection();
void ConnectionClosedCallback();
void CleanupMetadata();
bool VerifyRequestParams(const DatabaseRequestParams& aParams) const;
// IPDL methods are only called by IPDL.
void ActorDestroy(ActorDestroyReason aWhy) override;
PBackgroundIDBDatabaseFileParent* AllocPBackgroundIDBDatabaseFileParent(
const IPCBlob& aIPCBlob) override;
bool DeallocPBackgroundIDBDatabaseFileParent(
PBackgroundIDBDatabaseFileParent* aActor) override;
PBackgroundIDBDatabaseRequestParent* AllocPBackgroundIDBDatabaseRequestParent(
const DatabaseRequestParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBDatabaseRequestConstructor(
PBackgroundIDBDatabaseRequestParent* aActor,
const DatabaseRequestParams& aParams) override;
bool DeallocPBackgroundIDBDatabaseRequestParent(
PBackgroundIDBDatabaseRequestParent* aActor) override;
already_AddRefed<PBackgroundIDBTransactionParent>
AllocPBackgroundIDBTransactionParent(
const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBTransactionConstructor(
PBackgroundIDBTransactionParent* aActor,
nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode) override;
PBackgroundMutableFileParent* AllocPBackgroundMutableFileParent(
const nsAString& aName, const nsAString& aType) override;
bool DeallocPBackgroundMutableFileParent(
PBackgroundMutableFileParent* aActor) override;
mozilla::ipc::IPCResult RecvDeleteMe() override;
mozilla::ipc::IPCResult RecvBlocked() override;
mozilla::ipc::IPCResult RecvClose() override;
template <typename T>
static bool InvalidateAll(const nsTBaseHashSet<nsPtrHashKey<T>>& aTable);
};
class Database::StartTransactionOp final
: public TransactionDatabaseOperationBase {
friend class Database;
private:
explicit StartTransactionOp(SafeRefPtr<TransactionBase> aTransaction)
: TransactionDatabaseOperationBase(std::move(aTransaction),
/* aLoggingSerialNumber */ 0) {}
~StartTransactionOp() override = default;
void RunOnConnectionThread() override;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
nsresult SendSuccessResult() override;
bool SendFailureResult(nsresult aResultCode) override;
void Cleanup() override;
};
class Database::UnmapBlobCallback final
: public RemoteLazyInputStreamParentCallback {
SafeRefPtr<Database> mDatabase;
nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
public:
explicit UnmapBlobCallback(SafeRefPtr<Database> aDatabase)
: mDatabase(std::move(aDatabase)),
mBackgroundThread(GetCurrentSerialEventTarget()) {
AssertIsOnBackgroundThread();
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Database::UnmapBlobCallback, override)
void ActorDestroyed(const nsID& aID) override {
MOZ_ASSERT(mDatabase);
mBackgroundThread->Dispatch(NS_NewRunnableFunction(
"UnmapBlobCallback", [aID, database = std::move(mDatabase)] {
AssertIsOnBackgroundThread();
database->UnmapBlob(aID);
}));
}
private:
~UnmapBlobCallback() = default;
};
/**
* 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
* DatabaseFileInfo 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 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 done using the internal IPCBlob
* inputStream actor ID to DatabaseFileInfo mapping. 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 {
// 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.
InitializedOnce<const RefPtr<BlobImpl>> mBlobImpl;
const SafeRefPtr<DatabaseFileInfo> mFileInfo;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::DatabaseFile);
const DatabaseFileInfo& GetFileInfo() const {
AssertIsOnBackgroundThread();
return *mFileInfo;
}
SafeRefPtr<DatabaseFileInfo> GetFileInfoPtr() const {
AssertIsOnBackgroundThread();
return mFileInfo.clonePtr();
}
/**
* If mBlobImpl is non-null (implying the contents of this file have not yet
* been written to disk), then return an input stream. Otherwise, if mBlobImpl
* is null (because the contents have been written to disk), returns null.
*/
[[nodiscard]] nsCOMPtr<nsIInputStream> GetInputStream(ErrorResult& rv) const;
/**
* To be called upon successful copying of the stream GetInputStream()
* returned so that we won't try and redundantly write the file to disk in the
* future. This is a separate step from GetInputStream() 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());
MOZ_ASSERT(*mBlobImpl);
mBlobImpl.destroy();
}
public:
// Called when sending to the child.
explicit DatabaseFile(SafeRefPtr<DatabaseFileInfo> aFileInfo)
: mBlobImpl{nullptr}, mFileInfo(std::move(aFileInfo)) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mFileInfo);
}
// Called when receiving from the child.
DatabaseFile(RefPtr<BlobImpl> aBlobImpl,
SafeRefPtr<DatabaseFileInfo> aFileInfo)
: mBlobImpl(std::move(aBlobImpl)), mFileInfo(std::move(aFileInfo)) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(*mBlobImpl);
MOZ_ASSERT(mFileInfo);
}
private:
~DatabaseFile() override = default;
void ActorDestroy(ActorDestroyReason aWhy) override {
AssertIsOnBackgroundThread();
}
};
nsCOMPtr<nsIInputStream> DatabaseFile::GetInputStream(ErrorResult& rv) const {
// We should only be called from our DB connection thread, not the background
// thread.
MOZ_ASSERT(!IsOnBackgroundThread());
// If we were constructed without a BlobImpl, or WriteSucceededClearBlobImpl
// was already called, return nullptr.
if (!mBlobImpl || !*mBlobImpl) {
return nullptr;
}
nsCOMPtr<nsIInputStream> inputStream;
(*mBlobImpl)->CreateInputStream(getter_AddRefs(inputStream), rv);
if (rv.Failed()) {
return nullptr;
}
return inputStream;
}
class TransactionBase : public AtomicSafeRefCounted<TransactionBase> {
friend class CursorBase;
template <IDBCursorType CursorType>
friend class Cursor;
class CommitOp;
protected:
using Mode = IDBTransaction::Mode;
private:
const SafeRefPtr<Database> mDatabase;
nsTArray<SafeRefPtr<FullObjectStoreMetadata>>
mModifiedAutoIncrementObjectStoreMetadataArray;
LazyInitializedOnceNotNull<const uint64_t> mTransactionId;
const nsCString mDatabaseId;
const int64_t mLoggingSerialNumber;
uint64_t mActiveRequestCount;
Atomic<bool> mInvalidatedOnAnyThread;
const Mode mMode;
FlippedOnce<false> mInitialized;
FlippedOnce<false> mHasBeenActiveOnConnectionThread;
FlippedOnce<false> mActorDestroyed;
FlippedOnce<false> mInvalidated;
protected:
nsresult mResultCode;
FlippedOnce<false> mCommitOrAbortReceived;
FlippedOnce<false> mCommittedOrAborted;
FlippedOnce<false> mForceAborted;
LazyInitializedOnce<const Maybe<int64_t>> mLastRequestBeforeCommit;
Maybe<int64_t> mLastFailedRequest;
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 Init(const uint64_t aTransactionId) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aTransactionId);
mTransactionId.init(aTransactionId);
mInitialized.Flip();
}
void SetActiveOnConnectionThread() {
AssertIsOnConnectionThread();
mHasBeenActiveOnConnectionThread.Flip();
}
MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::TransactionBase)
void Abort(nsresult aResultCode, bool aForce);
uint64_t TransactionId() const { return *mTransactionId; }
const nsACString& DatabaseId() const { return mDatabaseId; }
Mode GetMode() const { return mMode; }
const Database& GetDatabase() const {
MOZ_ASSERT(mDatabase);
return *mDatabase;
}
Database& GetMutableDatabase() const {
MOZ_ASSERT(mDatabase);
return *mDatabase;
}
SafeRefPtr<Database> GetDatabasePtr() const {
MOZ_ASSERT(mDatabase);
return mDatabase.clonePtr();
}
DatabaseLoggingInfo* GetLoggingInfo() const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mDatabase);
return mDatabase->GetLoggingInfo();
}
int64_t LoggingSerialNumber() const { return mLoggingSerialNumber; }
bool IsAborted() const {
AssertIsOnBackgroundThread();
return NS_FAILED(mResultCode);
}
[[nodiscard]] SafeRefPtr<FullObjectStoreMetadata> GetMetadataForObjectStoreId(
IndexOrObjectStoreId aObjectStoreId) const;
[[nodiscard]] SafeRefPtr<FullIndexMetadata> GetMetadataForIndexId(
FullObjectStoreMetadata& aObjectStoreMetadata,
IndexOrObjectStoreId aIndexId) const;
PBackgroundParent* GetBackgroundParent() const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!IsActorDestroyed());
return GetDatabase().GetBackgroundParent();
}
void NoteModifiedAutoIncrementObjectStore(
const SafeRefPtr<FullObjectStoreMetadata>& aMetadata);
void ForgetModifiedAutoIncrementObjectStore(
FullObjectStoreMetadata& aMetadata);
void NoteActiveRequest();
void NoteFinishedRequest(int64_t aRequestId, nsresult aResultCode);
void Invalidate();
virtual ~TransactionBase();
protected:
TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode);
void NoteActorDestroyed() {
AssertIsOnBackgroundThread();
mActorDestroyed.Flip();
}
#ifdef DEBUG
// Only called by VersionChangeTransaction.
void FakeActorDestroyed() { mActorDestroyed.EnsureFlipped(); }
#endif
mozilla::ipc::IPCResult RecvCommit(IProtocol* aActor,
const Maybe<int64_t> aLastRequest);
mozilla::ipc::IPCResult RecvAbort(IProtocol* aActor, 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(RequestParams&& aParams,
bool aTrustParams);
bool StartRequest(PBackgroundIDBRequestParent* aActor);
bool DeallocRequest(PBackgroundIDBRequestParent* aActor);
already_AddRefed<PBackgroundIDBCursorParent> AllocCursor(
const OpenCursorParams& aParams, bool aTrustParams);
bool StartCursor(PBackgroundIDBCursorParent* aActor,
const OpenCursorParams& aParams);
virtual void UpdateMetadata(nsresult aResult) {}
virtual void SendCompleteNotification(nsresult aResult) = 0;
private:
bool VerifyRequestParams(const RequestParams& aParams) const;
bool VerifyRequestParams(const SerializedKeyRange& aParams) const;
bool VerifyRequestParams(const ObjectStoreAddPutParams& aParams) const;
bool VerifyRequestParams(const Maybe<SerializedKeyRange>& aParams) const;
void CommitOrAbort();
};
class TransactionBase::CommitOp final : public DatabaseOperationBase,
public ConnectionPool::FinishCallback {
friend class TransactionBase;
SafeRefPtr<TransactionBase> mTransaction;
nsresult mResultCode; ///< TODO: There is also a mResultCode in
///< DatabaseOperationBase. Is there a reason not to
///< use that? At least a more specific name should be
///< given to this one.
private:
CommitOp(SafeRefPtr<TransactionBase> aTransaction, nsresult aResultCode);
~CommitOp() override = default;
// 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
void TransactionFinishedBeforeUnblock() override;
void TransactionFinishedAfterUnblock() override;
public:
// We need to declare all of nsISupports, because FinishCallback has
// a pure-virtual nsISupports declaration.
NS_DECL_ISUPPORTS_INHERITED
};
class NormalTransaction final : public TransactionBase,
public PBackgroundIDBTransactionParent {
nsTArray<SafeRefPtr<FullObjectStoreMetadata>> mObjectStores;
// Reference counted.
~NormalTransaction() override = default;
bool IsSameProcessActor();
// Only called by TransactionBase.
void SendCompleteNotification(nsresult aResult) override;
// IPDL methods are only called by IPDL.
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvDeleteMe() override;
mozilla::ipc::IPCResult RecvCommit(
const Maybe<int64_t>& aLastRequest) override;
mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override;
PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent(
const RequestParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor(
PBackgroundIDBRequestParent* aActor,
const RequestParams& aParams) override;
bool DeallocPBackgroundIDBRequestParent(
PBackgroundIDBRequestParent* aActor) override;
already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent(
const OpenCursorParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor(
PBackgroundIDBCursorParent* aActor,
const OpenCursorParams& aParams) override;
public:
// This constructor is only called by Database.
NormalTransaction(
SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode,
nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores);
MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(NormalTransaction, TransactionBase)
};
class VersionChangeTransaction final
: public TransactionBase,
public PBackgroundIDBVersionChangeTransactionParent {
friend class OpenDatabaseOp;
RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
SafeRefPtr<FullDatabaseMetadata> mOldMetadata;
FlippedOnce<false> mActorWasAlive;
public:
// Only called by OpenDatabaseOp.
explicit VersionChangeTransaction(OpenDatabaseOp* aOpenDatabaseOp);
MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(VersionChangeTransaction,
TransactionBase)
private:
// Reference counted.
~VersionChangeTransaction() override;
bool IsSameProcessActor();
// Only called by OpenDatabaseOp.
bool CopyDatabaseMetadata();
void SetActorAlive();
// Only called by TransactionBase.
void UpdateMetadata(nsresult aResult) override;
// Only called by TransactionBase.
void SendCompleteNotification(nsresult aResult) override;
// IPDL methods are only called by IPDL.
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvDeleteMe() override;
mozilla::ipc::IPCResult RecvCommit(
const Maybe<int64_t>& aLastRequest) override;
mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override;
mozilla::ipc::IPCResult RecvCreateObjectStore(
const ObjectStoreMetadata& aMetadata) override;
mozilla::ipc::IPCResult RecvDeleteObjectStore(
const IndexOrObjectStoreId& aObjectStoreId) override;
mozilla::ipc::IPCResult RecvRenameObjectStore(
const IndexOrObjectStoreId& aObjectStoreId,
const nsAString& aName) override;
mozilla::ipc::IPCResult RecvCreateIndex(
const IndexOrObjectStoreId& aObjectStoreId,
const IndexMetadata& aMetadata) override;
mozilla::ipc::IPCResult RecvDeleteIndex(
const IndexOrObjectStoreId& aObjectStoreId,
const IndexOrObjectStoreId& aIndexId) override;
mozilla::ipc::IPCResult RecvRenameIndex(
const IndexOrObjectStoreId& aObjectStoreId,
const IndexOrObjectStoreId& aIndexId, const nsAString& aName) override;
PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent(
const RequestParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor(
PBackgroundIDBRequestParent* aActor,
const RequestParams& aParams) override;
bool DeallocPBackgroundIDBRequestParent(
PBackgroundIDBRequestParent* aActor) override;
already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent(
const OpenCursorParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor(
PBackgroundIDBCursorParent* aActor,
const OpenCursorParams& aParams) override;
};
class MutableFile : public BackgroundMutableFileParentBase {
const SafeRefPtr<Database> mDatabase;
const SafeRefPtr<DatabaseFileInfo> mFileInfo;
public:
[[nodiscard]] static RefPtr<MutableFile> Create(
nsIFile* aFile, SafeRefPtr<Database> aDatabase,
SafeRefPtr<DatabaseFileInfo> aFileInfo);
const Database& GetDatabase() const {
AssertIsOnBackgroundThread();
return *mDatabase;
}
SafeRefPtr<DatabaseFileInfo> GetFileInfoPtr() const {
AssertIsOnBackgroundThread();
return mFileInfo.clonePtr();
}
void NoteActiveState() override;
void NoteInactiveState() override;
PBackgroundParent* GetBackgroundParent() const override;
already_AddRefed<nsISupports> CreateStream(bool aReadOnly) override;
already_AddRefed<BlobImpl> CreateBlobImpl() override;
private:
MutableFile(nsIFile* aFile, SafeRefPtr<Database> aDatabase,
SafeRefPtr<DatabaseFileInfo> aFileInfo);
~MutableFile() override;
PBackgroundFileHandleParent* AllocPBackgroundFileHandleParent(
const FileMode& aMode) final;
mozilla::ipc::IPCResult RecvPBackgroundFileHandleConstructor(
PBackgroundFileHandleParent* aActor, const FileMode& aMode) final;
mozilla::ipc::IPCResult RecvGetFileId(int64_t* aFileId) override;
};
class FactoryOp
: public DatabaseOperationBase,
public OpenDirectoryListener,
public PBackgroundIDBFactoryRequestParent,
public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
public:
struct MaybeBlockedDatabaseInfo final {
SafeRefPtr<Database> mDatabase;
bool mBlocked;
MaybeBlockedDatabaseInfo(MaybeBlockedDatabaseInfo&&) = default;
MaybeBlockedDatabaseInfo& operator=(MaybeBlockedDatabaseInfo&&) = default;
MOZ_IMPLICIT MaybeBlockedDatabaseInfo(SafeRefPtr<Database> aDatabase)
: mDatabase(std::move(aDatabase)), mBlocked(false) {
MOZ_ASSERT(mDatabase);
MOZ_COUNT_CTOR(FactoryOp::MaybeBlockedDatabaseInfo);
}
~MaybeBlockedDatabaseInfo() {
MOZ_COUNT_DTOR(FactoryOp::MaybeBlockedDatabaseInfo);
}
bool operator==(const Database* aOther) const {
return mDatabase == aOther;
}
Database* operator->() const& MOZ_NO_ADDREF_RELEASE_ON_RETURN {
return mDatabase.unsafeGetRawPtr();
}
};
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,
// Ensuring quota manager is created and opening directory on the
// PBackground thread. Next step is either SendingResults if quota manager
// is not available or DirectoryOpenPending if quota manager is available.
FinishOpen,
// 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!
SafeRefPtr<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;
OriginMetadata mOriginMetadata;
nsCString mDatabaseId;
nsString mDatabaseFilePath;
int64_t mDirectoryLockId;
State mState;
bool mWaitingForPermissionRetry;
bool mEnforcingQuota;
const bool mDeleting;
bool mChromeWriteAccessAllowed;
bool mFileHandleDisabled;
FlippedOnce<false> mInPrivateBrowsing;
public:
const nsACString& Origin() const {
AssertIsOnOwningThread();
return mOriginMetadata.mOrigin;
}
bool DatabaseFilePathIsKnown() const {
AssertIsOnOwningThread();
return !mDatabaseFilePath.IsEmpty();
}
const nsAString& DatabaseFilePath() const {
AssertIsOnOwningThread();
MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
return mDatabaseFilePath;
}
void NoteDatabaseBlocked(Database* aDatabase);
void NoteDatabaseClosed(Database* aDatabase);
#ifdef DEBUG
bool HasBlockedDatabases() const { return !mMaybeBlockedDatabases.IsEmpty(); }
#endif
void StringifyState(nsACString& aResult) const;
void Stringify(nsACString& aResult) const;
protected:
FactoryOp(SafeRefPtr<Factory> aFactory, RefPtr<ContentParent> aContentParent,
const CommonFactoryRequestParams& aCommonParams, bool aDeleting);
~FactoryOp() override {
// 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 DirectoryOpen();
nsresult SendToIOThread();
void WaitForTransactions();
void CleanupMetadata();
void FinishSendResults();
nsresult SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
Maybe<Database&> aOpeningDatabase,
uint64_t aOldVersion,
const Maybe<uint64_t>& aNewVersion);
// Methods that subclasses must implement.
virtual nsresult DatabaseOpen() = 0;
virtual nsresult DoDatabaseWork() = 0;
virtual nsresult BeginVersionChange() = 0;
virtual bool AreActorsAlive() = 0;
virtual nsresult DispatchToWorkThread() = 0;
// Should only be called by Run().
virtual void SendResults() = 0;
// We need to declare refcounting unconditionally, because
// OpenDirectoryListener has pure-virtual refcounting.
NS_DECL_ISUPPORTS_INHERITED
// Common nsIRunnable implementation that subclasses may not override.
NS_IMETHOD
Run() final;
// OpenDirectoryListener overrides.
void DirectoryLockAcquired(DirectoryLock* aLock) override;
void DirectoryLockFailed() override;
// IPDL methods.
void ActorDestroy(ActorDestroyReason aWhy) override;
virtual void SendBlockedNotification() = 0;
private:
mozilla::Result<PermissionValue, nsresult> CheckPermission(
ContentParent* aContentParent);
static bool CheckAtLeastOneAppHasPermission(
ContentParent* aContentParent, const nsACString& aPermissionString);
nsresult FinishOpen();
// Test whether this FactoryOp needs to wait for the given op.
bool MustWaitFor(const FactoryOp& aExistingOp);
};
class OpenDatabaseOp final : public FactoryOp {
friend class Database;
friend class VersionChangeTransaction;
class VersionChangeOp;
Maybe<ContentParentId> mOptionalContentParentId;
SafeRefPtr<FullDatabaseMetadata> mMetadata;
uint64_t mRequestedVersion;
SafeRefPtr<DatabaseFileManager> mFileManager;
SafeRefPtr<Database> mDatabase;
SafeRefPtr<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(SafeRefPtr<Factory> aFactory,
RefPtr<ContentParent> aContentParent,
const CommonFactoryRequestParams& aParams);
private:
~OpenDatabaseOp() override { MOZ_ASSERT(!mVersionChangeOp); }
nsresult LoadDatabaseInformation(mozIStorageConnection& aConnection);
nsresult SendUpgradeNeeded();
void EnsureDatabaseActor();
nsresult EnsureDatabaseActorIsAlive();
mozilla::Result<DatabaseSpec, nsresult> MetadataToSpec() const;
void AssertMetadataConsistency(const FullDatabaseMetadata& aMetadata)
#ifdef DEBUG
;
#else
{
}
#endif
void ConnectionClosedCallback();
void ActorDestroy(ActorDestroyReason aWhy) override;
nsresult DatabaseOpen() override;
nsresult DoDatabaseWork() override;
nsresult BeginVersionChange() override;
bool AreActorsAlive() override;
void SendBlockedNotification() override;
nsresult DispatchToWorkThread() override;
void SendResults() override;
static nsresult UpdateLocaleAwareIndex(mozIStorageConnection& aConnection,
const IndexMetadata& aIndexMetadata,
const nsCString& aLocale);
};
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.clonePtr(),
aOpenDatabaseOp->LoggingSerialNumber()),
mOpenDatabaseOp(aOpenDatabaseOp),
mRequestedVersion(aOpenDatabaseOp->mRequestedVersion),
mPreviousVersion(
aOpenDatabaseOp->mMetadata->mCommonMetadata.version()) {
MOZ_ASSERT(aOpenDatabaseOp);
MOZ_ASSERT(mRequestedVersion);
}
~VersionChangeOp() override { MOZ_ASSERT(!mOpenDatabaseOp); }
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
nsresult SendSuccessResult() override;
bool SendFailureResult(nsresult aResultCode) override;
void Cleanup() override;
};
class DeleteDatabaseOp final : public FactoryOp {
class VersionChangeOp;
nsString mDatabaseDirectoryPath;
nsString mDatabaseFilenameBase;
uint64_t mPreviousVersion;
public:
DeleteDatabaseOp(SafeRefPtr<Factory> aFactory,
RefPtr<ContentParent> aContentParent,
const CommonFactoryRequestParams& aParams)
: FactoryOp(std::move(aFactory), std::move(aContentParent), aParams,
/* aDeleting */ true),
mPreviousVersion(0) {}
private:
~DeleteDatabaseOp() override = default;
void LoadPreviousVersion(nsIFile& aDatabaseFile);
nsresult DatabaseOpen() override;
nsresult DoDatabaseWork() override;
nsresult BeginVersionChange() override;
bool AreActorsAlive() override;
void SendBlockedNotification() override;
nsresult DispatchToWorkThread() override;
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() override = default;
nsresult RunOnIOThread();
void RunOnOwningThread();
NS_DECL_NSIRUNNABLE
};
class DatabaseOp : public DatabaseOperationBase,
public PBackgroundIDBDatabaseRequestParent {
protected:
SafeRefPtr<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(SafeRefPtr<Database> aDatabase);
~DatabaseOp() override {
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.
void ActorDestroy(ActorDestroyReason aWhy) override;
};
class CreateFileOp final : public DatabaseOp {
const CreateFileParams mParams;
LazyInitializedOnce<const SafeRefPtr<DatabaseFileInfo>> mFileInfo;
public:
CreateFileOp(SafeRefPtr<Database> aDatabase,
const DatabaseRequestParams& aParams);
private:
~CreateFileOp() override = default;
mozilla::Result<RefPtr<MutableFile>, nsresult> CreateMutableFile();
nsresult DoDatabaseWork() override;
void SendResults() override;
};
class VersionChangeTransactionOp : public TransactionDatabaseOperationBase {
public:
void Cleanup() override;
protected:
explicit VersionChangeTransactionOp(
SafeRefPtr<VersionChangeTransaction> aTransaction)
: TransactionDatabaseOperationBase(std::move(aTransaction)) {}
~VersionChangeTransactionOp() override = default;
private:
nsresult SendSuccessResult() override;
bool SendFailureResult(nsresult aResultCode) override;
};
class CreateObjectStoreOp final : public VersionChangeTransactionOp {
friend class VersionChangeTransaction;
const ObjectStoreMetadata mMetadata;
private:
// Only created by VersionChangeTransaction.
CreateObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
const ObjectStoreMetadata& aMetadata)
: VersionChangeTransactionOp(std::move(aTransaction)),
mMetadata(aMetadata) {
MOZ_ASSERT(aMetadata.id());
}
~CreateObjectStoreOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
};
class DeleteObjectStoreOp final : public VersionChangeTransactionOp {
friend class VersionChangeTransaction;
const SafeRefPtr<FullObjectStoreMetadata> mMetadata;
const bool mIsLastObjectStore;
private:
// Only created by VersionChangeTransaction.
DeleteObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
SafeRefPtr<FullObjectStoreMetadata> aMetadata,
const bool aIsLastObjectStore)
: VersionChangeTransactionOp(std::move(aTransaction)),
mMetadata(std::move(aMetadata)),
mIsLastObjectStore(aIsLastObjectStore) {
MOZ_ASSERT(mMetadata->mCommonMetadata.id());
}
~DeleteObjectStoreOp() override = default;
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(SafeRefPtr<VersionChangeTransaction> aTransaction,
FullObjectStoreMetadata& aMetadata)
: VersionChangeTransactionOp(std::move(aTransaction)),
mId(aMetadata.mCommonMetadata.id()),
mNewName(aMetadata.mCommonMetadata.name()) {
MOZ_ASSERT(mId);
}
~RenameObjectStoreOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
};
class CreateIndexOp final : public VersionChangeTransactionOp {
friend class VersionChangeTransaction;
class UpdateIndexDataValuesFunction;
const IndexMetadata mMetadata;
Maybe<UniqueIndexTable> mMaybeUniqueIndexTable;
const SafeRefPtr<DatabaseFileManager> mFileManager;
const nsCString mDatabaseId;
const IndexOrObjectStoreId mObjectStoreId;
private:
// Only created by VersionChangeTransaction.
CreateIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
IndexOrObjectStoreId aObjectStoreId,
const IndexMetadata& aMetadata);
~CreateIndexOp() override = default;
nsresult InsertDataFromObjectStore(DatabaseConnection* aConnection);
nsresult InsertDataFromObjectStoreInternal(
DatabaseConnection* aConnection) const;
bool Init(TransactionBase& aTransaction) override;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
};
class CreateIndexOp::UpdateIndexDataValuesFunction final
: public mozIStorageFunction {
RefPtr<CreateIndexOp> mOp;
RefPtr<DatabaseConnection> mConnection;
const NotNull<SafeRefPtr<Database>> mDatabase;
public:
UpdateIndexDataValuesFunction(CreateIndexOp* aOp,
DatabaseConnection* aConnection,
SafeRefPtr<Database> aDatabase)
: mOp(aOp),
mConnection(aConnection),
mDatabase(WrapNotNull(std::move(aDatabase))) {
MOZ_ASSERT(aOp);
MOZ_ASSERT(aConnection);
aConnection->AssertIsOnConnectionThread();
}
NS_DECL_ISUPPORTS
private:
~UpdateIndexDataValuesFunction() = default;
NS_DECL_MOZISTORAGEFUNCTION
};
class DeleteIndexOp final : public VersionChangeTransactionOp {
friend class VersionChangeTransaction;
const IndexOrObjectStoreId mObjectStoreId;
const IndexOrObjectStoreId mIndexId;
const bool mUnique;
const bool mIsLastIndex;
private:
// Only created by VersionChangeTransaction.
DeleteIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
IndexOrObjectStoreId aObjectStoreId,
IndexOrObjectStoreId aIndexId, const bool aUnique,
const bool aIsLastIndex);
~DeleteIndexOp() override = default;
nsresult RemoveReferencesToIndex(
DatabaseConnection* aConnection, const Key& aObjectDataKey,
nsTArray<IndexDataValue>& aIndexValues) const;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
};
class RenameIndexOp final : public VersionChangeTransactionOp {
friend class VersionChangeTransaction;
const IndexOrObjectStoreId mObjectStoreId;
const IndexOrObjectStoreId mIndexId;
const nsString mNewName;
private:
// Only created by VersionChangeTransaction.
RenameIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
FullIndexMetadata& aMetadata,
IndexOrObjectStoreId aObjectStoreId)
: VersionChangeTransactionOp(std::move(aTransaction)),
mObjectStoreId(aObjectStoreId),
mIndexId(aMetadata.mCommonMetadata.id()),
mNewName(aMetadata.mCommonMetadata.name()) {
MOZ_ASSERT(mIndexId);
}
~RenameIndexOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
};
class NormalTransactionOp : public TransactionDatabaseOperationBase,
public PBackgroundIDBRequestParent {
#ifdef DEBUG
bool mResponseSent;
#endif
public:
void Cleanup() override;
protected:
explicit NormalTransactionOp(SafeRefPtr<TransactionBase> aTransaction)
: TransactionDatabaseOperationBase(std::move(aTransaction))
#ifdef DEBUG
,
mResponseSent(false)
#endif
{
}
~NormalTransactionOp() override = default;
// An overload of DatabaseOperationBase's function that can avoid doing extra
// work on non-versionchange transactions.
mozilla::Result<bool, nsresult> ObjectStoreHasIndexes(
DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId,
bool aMayHaveIndexes);
virtual mozilla::Result<PreprocessParams, nsresult> GetPreprocessParams();
// Subclasses use this override to set the IPDL response value.
virtual void GetResponse(RequestResponse& aResponse,
size_t* aResponseSize) = 0;
private:
nsresult SendPreprocessInfo() override;
nsresult SendSuccessResult() override;
bool SendFailureResult(nsresult aResultCode) override;
// IPDL methods.
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvContinue(
const PreprocessResponse& aResponse) final;
};
class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp {
friend class TransactionBase;
using PersistenceType = mozilla::dom::quota::PersistenceType;
class StoredFileInfo final {
InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileInfo>>> mFileInfo;
// Either nothing, a file actor or a non-Blob-backed inputstream to write to
// disk.
using FileActorOrInputStream =
Variant<Nothing, RefPtr<DatabaseFile>, nsCOMPtr<nsIInputStream>>;
InitializedOnce<const FileActorOrInputStream> mFileActorOrInputStream;
#ifdef DEBUG
const StructuredCloneFileBase::FileType mType;
#endif
void AssertInvariants() const;
explicit StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo);
StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo,
RefPtr<DatabaseFile> aFileActor);
StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo,
nsCOMPtr<nsIInputStream> aInputStream);
public:
#if defined(NS_BUILD_REFCNT_LOGGING)
// Only for MOZ_COUNT_CTOR.
StoredFileInfo(StoredFileInfo&& aOther)
: mFileInfo{std::move(aOther.mFileInfo)},
mFileActorOrInputStream{std::move(aOther.mFileActorOrInputStream)}
# ifdef DEBUG
,
mType{aOther.mType}
# endif
{
MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
}
#else
StoredFileInfo(StoredFileInfo&&) = default;
#endif
static StoredFileInfo CreateForMutableFile(
SafeRefPtr<DatabaseFileInfo> aFileInfo);
static StoredFileInfo CreateForBlob(SafeRefPtr<DatabaseFileInfo> aFileInfo,
RefPtr<DatabaseFile> aFileActor);
static StoredFileInfo CreateForStructuredClone(
SafeRefPtr<DatabaseFileInfo> aFileInfo,
nsCOMPtr<nsIInputStream> aInputStream);
#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
~StoredFileInfo() {
AssertIsOnBackgroundThread();
AssertInvariants();
MOZ_COUNT_DTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
}
#endif
bool IsValid() const { return static_cast<bool>(mFileInfo); }
const DatabaseFileInfo& GetFileInfo() const { return **mFileInfo; }
bool ShouldCompress() const;
void NotifyWriteSucceeded() const;
using InputStreamResult =
mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult>;
InputStreamResult GetInputStream();
void Serialize(nsString& aText) const;
};
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.
SafeRefPtr<FullObjectStoreMetadata> mMetadata;
nsTArray<StoredFileInfo> mStoredFileInfos;
Key mResponse;
const OriginMetadata mOriginMetadata;
const PersistenceType mPersistenceType;
const bool mOverwrite;
bool mObjectStoreMayHaveIndexes;
bool mDataOverThreshold;
private:
// Only created by TransactionBase.
ObjectStoreAddOrPutRequestOp(SafeRefPtr<TransactionBase> aTransaction,
RequestParams&& aParams);
~ObjectStoreAddOrPutRequestOp() override = default;
nsresult RemoveOldIndexDataValues(DatabaseConnection* aConnection);
bool Init(TransactionBase& aTransaction) override;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
void Cleanup() override;
};
void ObjectStoreAddOrPutRequestOp::StoredFileInfo::AssertInvariants() const {
// The only allowed types are eStructuredClone, eBlob and eMutableFile.
MOZ_ASSERT(StructuredCloneFileBase::eStructuredClone == mType ||
StructuredCloneFileBase::eBlob == mType ||
StructuredCloneFileBase::eMutableFile == mType);
// mFileInfo and a file actor in mFileActorOrInputStream are present until
// the object is moved away, but an inputStream in mFileActorOrInputStream
// can be released early.
MOZ_ASSERT_IF(static_cast<bool>(mFileActorOrInputStream) &&
mFileActorOrInputStream->is<RefPtr<DatabaseFile>>(),
static_cast<bool>(mFileInfo));
if (mFileInfo) {
// In a non-moved StoredFileInfo, one of the following is true:
// - This was an overflow structured clone (eStructuredClone) and
// storedFileInfo.mFileActorOrInputStream CAN be a non-nullptr input
// stream (but that might have been release by ReleaseInputStream).
MOZ_ASSERT_IF(
StructuredCloneFileBase::eStructuredClone == mType,
!mFileActorOrInputStream ||
(mFileActorOrInputStream->is<nsCOMPtr<nsIInputStream>>() &&
mFileActorOrInputStream->as<nsCOMPtr<nsIInputStream>>()));
// - This is a reference to a Blob (eBlob) that may or may not have
// already been written to disk. storedFileInfo.mFileActorOrInputStream
// MUST be a non-null file actor, but its GetInputStream may return
// nullptr (so don't assert on that).
MOZ_ASSERT_IF(StructuredCloneFileBase::eBlob == mType,
mFileActorOrInputStream->is<RefPtr<DatabaseFile>>() &&
mFileActorOrInputStream->as<RefPtr<DatabaseFile>>());
// - It's a mutable file (eMutableFile). No writing will be performed,
// and storedFileInfo.mFileActorOrInputStream is Nothing.
MOZ_ASSERT_IF(StructuredCloneFileBase::eMutableFile == mType,
mFileActorOrInputStream->is<Nothing>());
}
}
ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
SafeRefPtr<DatabaseFileInfo> aFileInfo)
: mFileInfo{WrapNotNull(std::move(aFileInfo))},
mFileActorOrInputStream{Nothing{}}
#ifdef DEBUG
,
mType{StructuredCloneFileBase::eMutableFile}
#endif
{
AssertIsOnBackgroundThread();
AssertInvariants();
MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
}
ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
SafeRefPtr<DatabaseFileInfo> aFileInfo, RefPtr<DatabaseFile> aFileActor)
: mFileInfo{WrapNotNull(std::move(aFileInfo))},
mFileActorOrInputStream{std::move(aFileActor)}
#ifdef DEBUG
,
mType{StructuredCloneFileBase::eBlob}
#endif
{
AssertIsOnBackgroundThread();
AssertInvariants();
MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
}
ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
SafeRefPtr<DatabaseFileInfo> aFileInfo,
nsCOMPtr<nsIInputStream> aInputStream)
: mFileInfo{WrapNotNull(std::move(aFileInfo))},
mFileActorOrInputStream{std::move(aInputStream)}
#ifdef DEBUG
,
mType{StructuredCloneFileBase::eStructuredClone}
#endif
{
AssertIsOnBackgroundThread();
AssertInvariants();
MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
}
ObjectStoreAddOrPutRequestOp::StoredFileInfo
ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForMutableFile(
SafeRefPtr<DatabaseFileInfo> aFileInfo) {
return StoredFileInfo{std::move(aFileInfo)};
}
ObjectStoreAddOrPutRequestOp::StoredFileInfo
ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForBlob(
SafeRefPtr<DatabaseFileInfo> aFileInfo, RefPtr<DatabaseFile> aFileActor) {
return {std::move(aFileInfo), std::move(aFileActor)};
}
ObjectStoreAddOrPutRequestOp::StoredFileInfo
ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForStructuredClone(
SafeRefPtr<DatabaseFileInfo> aFileInfo,
nsCOMPtr<nsIInputStream> aInputStream) {
return {std::move(aFileInfo), std::move(aInputStream)};
}
bool ObjectStoreAddOrPutRequestOp::StoredFileInfo::ShouldCompress() const {
// Must not be called after moving.
MOZ_ASSERT(IsValid());
// Compression is only necessary for eStructuredClone, i.e. when
// mFileActorOrInputStream stored an input stream. However, this is only
// called after GetInputStream, when mFileActorOrInputStream has been
// cleared, which is only possible for this type.
const bool res = !mFileActorOrInputStream;
MOZ_ASSERT(res == (StructuredCloneFileBase::eStructuredClone == mType));
return res;
}
void ObjectStoreAddOrPutRequestOp::StoredFileInfo::NotifyWriteSucceeded()
const {
MOZ_ASSERT(IsValid());
// For eBlob, clear the blob implementation.
if (mFileActorOrInputStream &&
mFileActorOrInputStream->is<RefPtr<DatabaseFile>>()) {
mFileActorOrInputStream->as<RefPtr<DatabaseFile>>()
->WriteSucceededClearBlobImpl();
}
// For the other types, no action is necessary.
}
ObjectStoreAddOrPutRequestOp::StoredFileInfo::InputStreamResult
ObjectStoreAddOrPutRequestOp::StoredFileInfo::GetInputStream() {
if (!mFileActorOrInputStream) {
MOZ_ASSERT(StructuredCloneFileBase::eStructuredClone == mType);
return nsCOMPtr<nsIInputStream>{};
}
// For the different cases, see also the comments in AssertInvariants.
return mFileActorOrInputStream->match(
[](const Nothing&) -> InputStreamResult {
return nsCOMPtr<nsIInputStream>{};
},
[](const RefPtr<DatabaseFile>& databaseActor) -> InputStreamResult {
ErrorResult rv;
auto inputStream = databaseActor->GetInputStream(rv);
if (NS_WARN_IF(rv.Failed())) {
return Err(rv.StealNSResult());
}
return inputStream;
},
[this](const nsCOMPtr<nsIInputStream>& inputStream) -> InputStreamResult {
auto res = inputStream;
// destroy() clears the inputStream parameter, so we needed to make a
// copy before
mFileActorOrInputStream.destroy();
AssertInvariants();
return res;
});
}
void ObjectStoreAddOrPutRequestOp::StoredFileInfo::Serialize(
nsString& aText) const {
AssertInvariants();
MOZ_ASSERT(IsValid());
const int64_t id = (*mFileInfo)->Id();
auto structuredCloneHandler = [&aText, id](const nsCOMPtr<nsIInputStream>&) {
// eStructuredClone
aText.Append('.');
aText.AppendInt(id);
};
// If mFileActorOrInputStream was moved, we had an inputStream before.
if (!mFileActorOrInputStream) {
structuredCloneHandler(nullptr);
return;
}
// This encoding is parsed in DeserializeStructuredCloneFile.
mFileActorOrInputStream->match(
[&aText, id](const Nothing&) {
// eMutableFile
aText.AppendInt(-id);
},
[&aText, id](const RefPtr<DatabaseFile>&) {
// eBlob
aText.AppendInt(id);
},
structuredCloneHandler);
}
class ObjectStoreAddOrPutRequestOp::SCInputStream final
: public nsIInputStream {
const JSStructuredCloneData& mData;
JSStructuredCloneData::Iterator mIter;
public:
explicit SCInputStream(const JSStructuredCloneData& aData)
: mData(aData), mIter(aData.Start()) {}
private:
virtual ~SCInputStream() = default;
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIINPUTSTREAM
};
class ObjectStoreGetRequestOp final : public NormalTransactionOp {
friend class TransactionBase;
const IndexOrObjectStoreId mObjectStoreId;
SafeRefPtr<Database> mDatabase;
const Maybe<SerializedKeyRange> mOptionalKeyRange;
AutoTArray<StructuredCloneReadInfoParent, 1> mResponse;
PBackgroundParent* mBackgroundParent;
uint32_t mPreprocessInfoCount;
const uint32_t mLimit;
const bool mGetAll;
private:
// Only created by TransactionBase.
ObjectStoreGetRequestOp(SafeRefPtr<TransactionBase> aTransaction,
const RequestParams& aParams, bool aGetAll);
~ObjectStoreGetRequestOp() override = default;
template <typename T>
mozilla::Result<T, nsresult> ConvertResponse(
StructuredCloneReadInfoParent&& aInfo);
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
bool HasPreprocessInfo() override;
mozilla::Result<PreprocessParams, nsresult> GetPreprocessParams() override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
};
class ObjectStoreGetKeyRequestOp final : public NormalTransactionOp {
friend class TransactionBase;
const IndexOrObjectStoreId mObjectStoreId;
const Maybe<SerializedKeyRange> mOptionalKeyRange;
const uint32_t mLimit;
const bool mGetAll;
nsTArray<Key> mResponse;
private:
// Only created by TransactionBase.
ObjectStoreGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction,
const RequestParams& aParams, bool aGetAll);
~ObjectStoreGetKeyRequestOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
};
class ObjectStoreDeleteRequestOp final : public NormalTransactionOp {
friend class TransactionBase;
const ObjectStoreDeleteParams mParams;
ObjectStoreDeleteResponse mResponse;
bool mObjectStoreMayHaveIndexes;
private:
ObjectStoreDeleteRequestOp(SafeRefPtr<TransactionBase> aTransaction,
const ObjectStoreDeleteParams& aParams);
~ObjectStoreDeleteRequestOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
aResponse = std::move(mResponse);
*aResponseSize = 0;
}
};
class ObjectStoreClearRequestOp final : public NormalTransactionOp {
friend class TransactionBase;
const ObjectStoreClearParams mParams;
ObjectStoreClearResponse mResponse;
bool mObjectStoreMayHaveIndexes;
private:
ObjectStoreClearRequestOp(SafeRefPtr<TransactionBase> aTransaction,
const ObjectStoreClearParams& aParams);
~ObjectStoreClearRequestOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
aResponse = std::move(mResponse);
*aResponseSize = 0;
}
};
class ObjectStoreCountRequestOp final : public NormalTransactionOp {
friend class TransactionBase;
const ObjectStoreCountParams mParams;
ObjectStoreCountResponse mResponse;
private:
ObjectStoreCountRequestOp(SafeRefPtr<TransactionBase> aTransaction,
const ObjectStoreCountParams& aParams)
: NormalTransactionOp(std::move(aTransaction)), mParams(aParams) {}
~ObjectStoreCountRequestOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
aResponse = std::move(mResponse);
*aResponseSize = sizeof(uint64_t);
}
};
class IndexRequestOpBase : public NormalTransactionOp {
protected:
const SafeRefPtr<FullIndexMetadata> mMetadata;
protected:
IndexRequestOpBase(SafeRefPtr<TransactionBase> aTransaction,
const RequestParams& aParams)
: NormalTransactionOp(std::move(aTransaction)),
mMetadata(IndexMetadataForParams(Transaction(), aParams)) {}
~IndexRequestOpBase() override = default;
private:
static SafeRefPtr<FullIndexMetadata> IndexMetadataForParams(
const TransactionBase& aTransaction, const RequestParams& aParams);
};
class IndexGetRequestOp final : public IndexRequestOpBase {
friend class TransactionBase;
SafeRefPtr<Database> mDatabase;
const Maybe<SerializedKeyRange> mOptionalKeyRange;
AutoTArray<StructuredCloneReadInfoParent, 1> mResponse;
PBackgroundParent* mBackgroundParent;
const uint32_t mLimit;
const bool mGetAll;
private:
// Only created by TransactionBase.
IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction,
const RequestParams& aParams, bool aGetAll);
~IndexGetRequestOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
};
class IndexGetKeyRequestOp final : public IndexRequestOpBase {
friend class TransactionBase;
const Maybe<SerializedKeyRange> mOptionalKeyRange;
AutoTArray<Key, 1> mResponse;
const uint32_t mLimit;
const bool mGetAll;
private:
// Only created by TransactionBase.
IndexGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction,
const RequestParams& aParams, bool aGetAll);
~IndexGetKeyRequestOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
};
class IndexCountRequestOp final : public IndexRequestOpBase {
friend class TransactionBase;
const IndexCountParams mParams;
IndexCountResponse mResponse;
private:
// Only created by TransactionBase.
IndexCountRequestOp(SafeRefPtr<TransactionBase> aTransaction,
const RequestParams& aParams)
: IndexRequestOpBase(std::move(aTransaction), aParams),
mParams(aParams.get_IndexCountParams()) {}
~IndexCountRequestOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
aResponse = std::move(mResponse);
*aResponseSize = sizeof(uint64_t);
}
};
template <IDBCursorType CursorType>
class Cursor;
constexpr IDBCursorType ToKeyOnlyType(const IDBCursorType aType) {
MOZ_ASSERT(aType == IDBCursorType::ObjectStore ||
aType == IDBCursorType::ObjectStoreKey ||
aType == IDBCursorType::Index || aType == IDBCursorType::IndexKey);
switch (aType) {
case IDBCursorType::ObjectStore:
case IDBCursorType::ObjectStoreKey:
return IDBCursorType::ObjectStoreKey;
case IDBCursorType::Index:
case IDBCursorType::IndexKey:
return IDBCursorType::IndexKey;
}
}
template <IDBCursorType CursorType>
using CursorPosition = CursorData<ToKeyOnlyType(CursorType)>;
#ifdef DEBUG
constexpr indexedDB::OpenCursorParams::Type ToOpenCursorParamsType(
const IDBCursorType aType) {
MOZ_ASSERT(aType == IDBCursorType::ObjectStore ||
aType == IDBCursorType::ObjectStoreKey ||
aType == IDBCursorType::Index || aType == IDBCursorType::IndexKey);
switch (aType) {
case IDBCursorType::ObjectStore:
return indexedDB::OpenCursorParams::TObjectStoreOpenCursorParams;
case IDBCursorType::ObjectStoreKey:
return indexedDB::OpenCursorParams::TObjectStoreOpenKeyCursorParams;
case IDBCursorType::Index:
return indexedDB::OpenCursorParams::TIndexOpenCursorParams;
case IDBCursorType::IndexKey:
return indexedDB::OpenCursorParams::TIndexOpenKeyCursorParams;
}
}
#endif
class CursorBase : public PBackgroundIDBCursorParent {
friend class TransactionBase;
template <IDBCursorType CursorType>
friend class CommonOpenOpHelper;
protected:
const SafeRefPtr<TransactionBase> mTransaction;
// This should only be touched on the PBackground thread to check whether
// the objectStore has been deleted. Holding these saves a hash lookup for
// every call to continue()/advance().
InitializedOnce<const NotNull<SafeRefPtr<FullObjectStoreMetadata>>>
mObjectStoreMetadata;
const IndexOrObjectStoreId mObjectStoreId;
LazyInitializedOnce<const Key>
mLocaleAwareRangeBound; ///< If the cursor is based on a key range, the
///< bound in the direction of iteration (e.g.
///< the upper bound in case of mDirection ==
///< NEXT). If the cursor is based on a key, it
///< is unset. If mLocale is set, this was
///< converted to mLocale.
const Direction mDirection;
const int32_t mMaxExtraCount;
const bool mIsSameProcessActor;
struct ConstructFromTransactionBase {};
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::CursorBase,
final)
CursorBase(SafeRefPtr<TransactionBase> aTransaction,
SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
Direction aDirection,
ConstructFromTransactionBase aConstructionTag);
protected:
// Reference counted.
~CursorBase() override { MOZ_ASSERT(!mObjectStoreMetadata); }
private:
virtual bool Start(const OpenCursorParams& aParams) = 0;
};
class IndexCursorBase : public CursorBase {
public:
bool IsLocaleAware() const { return !mLocale.IsEmpty(); }
IndexCursorBase(SafeRefPtr<TransactionBase> aTransaction,
SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
SafeRefPtr<FullIndexMetadata> aIndexMetadata,
Direction aDirection,
ConstructFromTransactionBase aConstructionTag)
: CursorBase{std::move(aTransaction), std::move(aObjectStoreMetadata),
aDirection, aConstructionTag},
mIndexMetadata(WrapNotNull(std::move(aIndexMetadata))),
mIndexId((*mIndexMetadata)->mCommonMetadata.id()),
mUniqueIndex((*mIndexMetadata)->mCommonMetadata.unique()),
mLocale((*mIndexMetadata)->mCommonMetadata.locale()) {}
protected:
IndexOrObjectStoreId Id() const { return mIndexId; }
// This should only be touched on the PBackground thread to check whether
// the index has been deleted. Holding these saves a hash lookup for every
// call to continue()/advance().
InitializedOnce<const NotNull<SafeRefPtr<FullIndexMetadata>>> mIndexMetadata;
const IndexOrObjectStoreId mIndexId;
const bool mUniqueIndex;
const nsCString
mLocale; ///< The locale if the cursor is locale-aware, otherwise empty.
struct ContinueQueries {
nsCString mContinueQuery;
nsCString mContinueToQuery;
nsCString mContinuePrimaryKeyQuery;
const nsACString& GetContinueQuery(const bool hasContinueKey,
const bool hasContinuePrimaryKey) const {
return hasContinuePrimaryKey ? mContinuePrimaryKeyQuery
: hasContinueKey ? mContinueToQuery
: mContinueQuery;
}
};
};
class ObjectStoreCursorBase : public CursorBase {
public:
using CursorBase::CursorBase;
static constexpr bool IsLocaleAware() { return false; }
protected:
IndexOrObjectStoreId Id() const { return mObjectStoreId; }
struct ContinueQueries {
nsCString mContinueQuery;
nsCString mContinueToQuery;
const nsACString& GetContinueQuery(const bool hasContinueKey,
const bool hasContinuePrimaryKey) const {
MOZ_ASSERT(!hasContinuePrimaryKey);
return hasContinueKey ? mContinueToQuery : mContinueQuery;
}
};
};
using FilesArray = nsTArray<nsTArray<StructuredCloneFileParent>>;
struct PseudoFilesArray {
static constexpr bool IsEmpty() { return true; }
static constexpr void Clear() {}
};
template <IDBCursorType CursorType>
using FilesArrayT =
std::conditional_t<!CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
FilesArray, PseudoFilesArray>;
class ValueCursorBase {
friend struct ValuePopulateResponseHelper<true>;
friend struct ValuePopulateResponseHelper<false>;
protected:
explicit ValueCursorBase(TransactionBase* const aTransaction)
: mDatabase(aTransaction->GetDatabasePtr()),
mFileManager(mDatabase->GetFileManagerPtr()),
mBackgroundParent(WrapNotNull(aTransaction->GetBackgroundParent())) {
MOZ_ASSERT(mDatabase);
}
void ProcessFiles(CursorResponse& aResponse, const FilesArray& aFiles);
~ValueCursorBase() { MOZ_ASSERT(!mBackgroundParent); }
const SafeRefPtr<Database> mDatabase;
const NotNull<SafeRefPtr<DatabaseFileManager>> mFileManager;
InitializedOnce<const NotNull<PBackgroundParent*>> mBackgroundParent;
};
class KeyCursorBase {
protected:
explicit KeyCursorBase(TransactionBase* const /*aTransaction*/) {}
static constexpr void ProcessFiles(CursorResponse& aResponse,
const PseudoFilesArray& aFiles) {}
};
template <IDBCursorType CursorType>
class CursorOpBaseHelperBase;
template <IDBCursorType CursorType>
class Cursor final
: public std::conditional_t<
CursorTypeTraits<CursorType>::IsObjectStoreCursor,
ObjectStoreCursorBase, IndexCursorBase>,
public std::conditional_t<CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
KeyCursorBase, ValueCursorBase> {
using Base =
std::conditional_t<CursorTypeTraits<CursorType>::IsObjectStoreCursor,
ObjectStoreCursorBase, IndexCursorBase>;
using KeyValueBase =
std::conditional_t<CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
KeyCursorBase, ValueCursorBase>;
static constexpr bool IsIndexCursor =
!CursorTypeTraits<CursorType>::IsObjectStoreCursor;
static constexpr bool IsValueCursor =
!CursorTypeTraits<CursorType>::IsKeyOnlyCursor;
class CursorOpBase;
class OpenOp;
class ContinueOp;
using Base::Id;
using CursorBase::Manager;
using CursorBase::mDirection;
using CursorBase::mObjectStoreId;
using CursorBase::mTransaction;
using typename CursorBase::ActorDestroyReason;
using TypedOpenOpHelper =
std::conditional_t<IsIndexCursor, IndexOpenOpHelper<CursorType>,
ObjectStoreOpenOpHelper<CursorType>>;
friend class CursorOpBaseHelperBase<CursorType>;
friend class CommonOpenOpHelper<CursorType>;
friend TypedOpenOpHelper;
friend class OpenOpHelper<CursorType>;
CursorOpBase* mCurrentlyRunningOp = nullptr;
LazyInitializedOnce<const typename Base::ContinueQueries> mContinueQueries;
// Only called by TransactionBase.
bool Start(const OpenCursorParams& aParams) final;
void SendResponseInternal(CursorResponse& aResponse,
const FilesArrayT<CursorType>& aFiles);
// Must call SendResponseInternal!
bool SendResponse(const CursorResponse& aResponse) = delete;
// IPDL methods.
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvDeleteMe() override;
mozilla::ipc::IPCResult RecvContinue(
const CursorRequestParams& aParams, const Key& aCurrentKey,
const Key& aCurrentObjectStoreKey) override;
public:
Cursor(SafeRefPtr<TransactionBase> aTransaction,
SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
SafeRefPtr<FullIndexMetadata> aIndexMetadata,
typename Base::Direction aDirection,
typename Base::ConstructFromTransactionBase aConstructionTag)
: Base{std::move(aTransaction), std::move(aObjectStoreMetadata),
std::move(aIndexMetadata), aDirection, aConstructionTag},
KeyValueBase{this->mTransaction.unsafeGetRawPtr()} {}
Cursor(SafeRefPtr<TransactionBase> aTransaction,
SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
typename Base::Direction aDirection,
typename Base::ConstructFromTransactionBase aConstructionTag)
: Base{std::move(aTransaction), std::move(aObjectStoreMetadata),
aDirection, aConstructionTag},
KeyValueBase{this->mTransaction.unsafeGetRawPtr()} {}
private:
void SetOptionalKeyRange(const Maybe<SerializedKeyRange>& aOptionalKeyRange,
bool* aOpen);
bool VerifyRequestParams(const CursorRequestParams& aParams,
const CursorPosition<CursorType>& aPosition) const;
~Cursor() final = default;
};
template <IDBCursorType CursorType>
class Cursor<CursorType>::CursorOpBase
: public TransactionDatabaseOperationBase {
friend class CursorOpBaseHelperBase<CursorType>;
protected:
RefPtr<Cursor> mCursor;
FilesArrayT<CursorType> mFiles; // TODO: Consider removing this member
// entirely if we are no value cursor.
CursorResponse mResponse;
#ifdef DEBUG
bool mResponseSent;
#endif
protected:
explicit CursorOpBase(Cursor* aCursor)
: TransactionDatabaseOperationBase(aCursor->mTransaction.clonePtr()),
mCursor(aCursor)
#ifdef DEBUG
,
mResponseSent(false)
#endif
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aCursor);
}
~CursorOpBase() override = default;
bool SendFailureResult(nsresult aResultCode) final;
nsresult SendSuccessResult() final;
void Cleanup() override;
};
template <IDBCursorType CursorType>
class OpenOpHelper;
using ResponseSizeOrError = Result<size_t, nsresult>;
template <IDBCursorType CursorType>
class CursorOpBaseHelperBase {
public:
explicit CursorOpBaseHelperBase(
typename Cursor<CursorType>::CursorOpBase& aOp)
: mOp{aOp} {}
ResponseSizeOrError PopulateResponseFromStatement(mozIStorageStatement* aStmt,
bool aInitializeResponse,
Key* const aOptOutSortKey);
void PopulateExtraResponses(mozIStorageStatement* aStmt,
uint32_t aMaxExtraCount,
const size_t aInitialResponseSize,
const nsACString& aOperation,
Key* const aOptPreviousSortKey);
protected:
Cursor<CursorType>& GetCursor() {
MOZ_ASSERT(mOp.mCursor);
return *mOp.mCursor;
}
void SetResponse(CursorResponse aResponse) {
mOp.mResponse = std::move(aResponse);
}
protected:
typename Cursor<CursorType>::CursorOpBase& mOp;
};
class CommonOpenOpHelperBase {
protected:
static void AppendConditionClause(const nsACString& aColumnName,
const nsACString& aStatementParameterName,
bool aLessThan, bool aEquals,
nsCString& aResult);
};
template <IDBCursorType CursorType>
class CommonOpenOpHelper : public CursorOpBaseHelperBase<CursorType>,
protected CommonOpenOpHelperBase {
public:
explicit CommonOpenOpHelper(typename Cursor<CursorType>::OpenOp& aOp)
: CursorOpBaseHelperBase<CursorType>{aOp} {}
protected:
using CursorOpBaseHelperBase<CursorType>::GetCursor;
using CursorOpBaseHelperBase<CursorType>::PopulateExtraResponses;
using CursorOpBaseHelperBase<CursorType>::PopulateResponseFromStatement;
using CursorOpBaseHelperBase<CursorType>::SetResponse;
const Maybe<SerializedKeyRange>& GetOptionalKeyRange() const {
// This downcast is safe, since we initialized mOp from an OpenOp in the
// ctor.
return static_cast<typename Cursor<CursorType>::OpenOp&>(this->mOp)
.mOptionalKeyRange;
}
nsresult ProcessStatementSteps(mozIStorageStatement* aStmt);
};
template <IDBCursorType CursorType>
class ObjectStoreOpenOpHelper : protected CommonOpenOpHelper<CursorType> {
public:
using CommonOpenOpHelper<CursorType>::CommonOpenOpHelper;
protected:
using CommonOpenOpHelper<CursorType>::GetCursor;
using CommonOpenOpHelper<CursorType>::GetOptionalKeyRange;
using CommonOpenOpHelper<CursorType>::AppendConditionClause;
void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
const nsACString& aQueryStart);
};
template <IDBCursorType CursorType>
class IndexOpenOpHelper : protected CommonOpenOpHelper<CursorType> {
public:
using CommonOpenOpHelper<CursorType>::CommonOpenOpHelper;
protected:
using CommonOpenOpHelper<CursorType>::GetCursor;
using CommonOpenOpHelper<CursorType>::GetOptionalKeyRange;
using CommonOpenOpHelper<CursorType>::AppendConditionClause;
void PrepareIndexKeyConditionClause(
const nsACString& aDirectionClause,
const nsLiteralCString& aObjectDataKeyPrefix, nsAutoCString aQueryStart);
};
template <>
class OpenOpHelper<IDBCursorType::ObjectStore>
: public ObjectStoreOpenOpHelper<IDBCursorType::ObjectStore> {
public:
using ObjectStoreOpenOpHelper<
IDBCursorType::ObjectStore>::ObjectStoreOpenOpHelper;
nsresult DoDatabaseWork(DatabaseConnection* aConnection);
};
template <>
class OpenOpHelper<IDBCursorType::ObjectStoreKey>
: public ObjectStoreOpenOpHelper<IDBCursorType::ObjectStoreKey> {
public:
using ObjectStoreOpenOpHelper<
IDBCursorType::ObjectStoreKey>::ObjectStoreOpenOpHelper;
nsresult DoDatabaseWork(DatabaseConnection* aConnection);
};
template <>
class OpenOpHelper<IDBCursorType::Index>
: IndexOpenOpHelper<IDBCursorType::Index> {
private:
void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
nsAutoCString aQueryStart) {
PrepareIndexKeyConditionClause(aDirectionClause, "index_table."_ns,
std::move(aQueryStart));
}
public:
using IndexOpenOpHelper<IDBCursorType::Index>::IndexOpenOpHelper;
nsresult DoDatabaseWork(DatabaseConnection* aConnection);
};
template <>
class OpenOpHelper<IDBCursorType::IndexKey>
: IndexOpenOpHelper<IDBCursorType::IndexKey> {
private:
void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
nsAutoCString aQueryStart) {
PrepareIndexKeyConditionClause(aDirectionClause, ""_ns,
std::move(aQueryStart));
}
public:
using IndexOpenOpHelper<IDBCursorType::IndexKey>::IndexOpenOpHelper;
nsresult DoDatabaseWork(DatabaseConnection* aConnection);
};
template <IDBCursorType CursorType>
class Cursor<CursorType>::OpenOp final : public CursorOpBase {
friend class Cursor<CursorType>;
friend class CommonOpenOpHelper<CursorType>;
const Maybe<SerializedKeyRange> mOptionalKeyRange;
using CursorOpBase::mCursor;
using CursorOpBase::mResponse;
// Only created by Cursor.
OpenOp(Cursor* const aCursor,
const Maybe<SerializedKeyRange>& aOptionalKeyRange)
: CursorOpBase(aCursor), mOptionalKeyRange(aOptionalKeyRange) {}
// Reference counted.
~OpenOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
};
template <IDBCursorType CursorType>
class Cursor<CursorType>::ContinueOp final
: public Cursor<CursorType>::CursorOpBase {
friend class Cursor<CursorType>;
using CursorOpBase::mCursor;
using CursorOpBase::mResponse;
const CursorRequestParams mParams;
// Only created by Cursor.
ContinueOp(Cursor* const aCursor, CursorRequestParams aParams,
CursorPosition<CursorType> aPosition)
: CursorOpBase(aCursor),
mParams(std::move(aParams)),
mCurrentPosition{std::move(aPosition)} {
MOZ_ASSERT(mParams.type() != CursorRequestParams::T__None);
}
// Reference counted.
~ContinueOp() override = default;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
const CursorPosition<CursorType> mCurrentPosition;
};
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() override;
// IPDL methods are only called by IPDL.
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvDeleteMe() override;
mozilla::ipc::IPCResult RecvGetFileReferences(
const PersistenceType& aPersistenceType, const nsACString& aOrigin,
const nsAString& aDatabaseName, const int64_t& aFileId, int32_t* aRefCnt,
int32_t* aDBRefCnt, bool* aResult) override;
};
/*******************************************************************************
* Other class declarations
******************************************************************************/
struct DatabaseActorInfo final {
friend class mozilla::DefaultDelete<DatabaseActorInfo>;
SafeRefPtr<FullDatabaseMetadata> mMetadata;
nsTArray<NotNull<CheckedUnsafePtr<Database>>> mLiveDatabases;
RefPtr<FactoryOp> mWaitingFactoryOp;
DatabaseActorInfo(SafeRefPtr<FullDatabaseMetadata> aMetadata,
NotNull<Database*> aDatabase)
: mMetadata(std::move(aMetadata)) {
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::Mode::VersionChange) {
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 QuotaClient final : public mozilla::dom::quota::Client {
static QuotaClient* sInstance;
nsCOMPtr<nsIEventTarget> mBackgroundThread;
nsCOMPtr<nsITimer> mDeleteTimer;
nsTArray<RefPtr<Maintenance>> mMaintenanceQueue;
RefPtr<Maintenance> mCurrentMaintenance;
RefPtr<nsThreadPool> mMaintenanceThreadPool;
nsClassHashtable<nsRefPtrHashKey<DatabaseFileManager>, nsTArray<int64_t>>
mPendingDeleteInfos;
public:
QuotaClient();
static QuotaClient* GetInstance() {
AssertIsOnBackgroundThread();
return sInstance;
}
nsIEventTarget* BackgroundThread() const {
MOZ_ASSERT(mBackgroundThread);
return mBackgroundThread;
}
nsresult AsyncDeleteFile(DatabaseFileManager* aFileManager, int64_t aFileId);
nsresult FlushPendingFileDeletions();
RefPtr<Maintenance> GetCurrentMaintenance() const {
return mCurrentMaintenance;
}
void NoteFinishedMaintenance(Maintenance* aMaintenance) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aMaintenance);
MOZ_ASSERT(mCurrentMaintenance == aMaintenance);
mCurrentMaintenance = nullptr;
QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::IDB,
"Maintenance finished"_ns);
ProcessMaintenanceQueue();
}
nsThreadPool* GetOrCreateThreadPool();
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::QuotaClient,
override)
mozilla::dom::quota::Client::Type GetType() override;
nsresult UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) override;
nsresult UpgradeStorageFrom2_1To2_2(nsIFile* aDirectory) override;
Result<UsageInfo, nsresult> InitOrigin(PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata,
const AtomicBool& aCanceled) override;
nsresult InitOriginWithoutTracking(PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata,
const AtomicBool& aCanceled) override;
Result<UsageInfo, nsresult> GetUsageForOrigin(
PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
const AtomicBool& aCanceled) override;
void OnOriginClearCompleted(PersistenceType aPersistenceType,
const nsACString& aOrigin) override;
void ReleaseIOThreadObjects() override;
void AbortOperationsForLocks(
const DirectoryLockIdTable& aDirectoryLockIds) override;
void AbortOperationsForProcess(ContentParentId aContentParentId) override;
void AbortAllOperations() override;
void StartIdleMaintenance() override;
void StopIdleMaintenance() override;
private:
~QuotaClient() override;
void InitiateShutdown() override;
bool IsShutdownCompleted() const override;
nsCString GetShutdownStatus() const override;
void ForceKillActors() override;
void FinalizeShutdown() override;
static void DeleteTimerCallback(nsITimer* aTimer, void* aClosure);
Result<nsCOMPtr<nsIFile>, nsresult> GetDirectory(
PersistenceType aPersistenceType, const nsACString& aOrigin);
struct SubdirectoriesToProcessAndDatabaseFilenames {
AutoTArray<nsString, 20> subdirsToProcess;
nsTHashSet<nsString> databaseFilenames{20};
};
struct SubdirectoriesToProcessAndDatabaseFilenamesAndObsoleteFilenames {
AutoTArray<nsString, 20> subdirsToProcess;
nsTHashSet<nsString> databaseFilenames{20};
nsTHashSet<nsString> obsoleteFilenames{20};
};
enum class ObsoleteFilenamesHandling { Include, Omit };
template <ObsoleteFilenamesHandling ObsoleteFilenames>
using GetDatabaseFilenamesResult = std::conditional_t<
ObsoleteFilenames == ObsoleteFilenamesHandling::Include,
SubdirectoriesToProcessAndDatabaseFilenamesAndObsoleteFilenames,
SubdirectoriesToProcessAndDatabaseFilenames>;
// Returns a two-part or three-part structure:
//
// The first part is an array of subdirectories to process.
//
// The second part is a hashtable of database filenames.
//
// When ObsoleteFilenames is ObsoleteFilenamesHandling::Include, will also
// collect files based on the marker files. For now,
// GetUsageForOriginInternal() is the only consumer of this result because it
// checks those unfinished deletion and clean them up after that.
template <ObsoleteFilenamesHandling ObsoleteFilenames =
ObsoleteFilenamesHandling::Omit>
Result<GetDatabaseFilenamesResult<ObsoleteFilenames>, nsresult>
GetDatabaseFilenames(nsIFile& aDirectory, const AtomicBool& aCanceled);
nsresult GetUsageForOriginInternal(PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata,
const AtomicBool& aCanceled,
bool aInitializing, UsageInfo* aUsageInfo);
// Runs on the PBackground thread. Checks to see if there's a queued
// Maintenance to run.
void ProcessMaintenanceQueue();
};
class DeleteFilesRunnable final : public Runnable,
public OpenDirectoryListener {
using DirectoryLock = mozilla::dom::quota::DirectoryLock;
enum State {
// Just created on the PBackground thread. Next step is
// State_DirectoryOpenPending.
State_Initial,
// Waiting for directory open allowed on the main thread. The next step is
// State_DatabaseWorkOpen.
State_DirectoryOpenPending,
// Waiting to do/doing work on the QuotaManager IO thread. The next step is
// State_UnblockingOpen.
State_DatabaseWorkOpen,
// Notifying the QuotaManager that it can proceed to the next operation on
// the main thread. Next step is State_Completed.
State_UnblockingOpen,
// All done.
State_Completed
};
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
SafeRefPtr<DatabaseFileManager> mFileManager;
RefPtr<DirectoryLock> mDirectoryLock;
nsTArray<int64_t> mFileIds;
State mState;
public:
DeleteFilesRunnable(SafeRefPtr<DatabaseFileManager> aFileManager,
nsTArray<int64_t>&& aFileIds);
void RunImmediately();
private:
~DeleteFilesRunnable() = default;
void Open();
void DoDatabaseWork();
void Finish();
void UnblockOpen();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIRUNNABLE
// OpenDirectoryListener overrides.
virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
virtual void DirectoryLockFailed() override;
};
class Maintenance final : public Runnable, public OpenDirectoryListener {
struct DirectoryInfo final {
InitializedOnce<const FullOriginMetadata> mFullOriginMetadata;
InitializedOnce<const nsTArray<nsString>> mDatabasePaths;
const PersistenceType mPersistenceType;
DirectoryInfo(PersistenceType aPersistenceType,
FullOriginMetadata aFullOriginMetadata,
nsTArray<nsString>&& aDatabasePaths);
DirectoryInfo(const DirectoryInfo& aOther) = delete;
DirectoryInfo(DirectoryInfo&& aOther) = delete;
~DirectoryInfo() { MOZ_COUNT_DTOR(Maintenance::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<UniversalDirectoryLock> mPendingDirectoryLock;
RefPtr<UniversalDirectoryLock> mDirectoryLock;
nsTArray<DirectoryInfo> mDirectoryInfos;
nsTHashMap<nsStringHashKey, DatabaseMaintenance*> mDatabaseMaintenances;
nsresult mResultCode;
Atomic<bool> mAborted;
State mState;
public:
explicit Maintenance(QuotaClient* aQuotaClient)
: Runnable("dom::indexedDB::Maintenance"),
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);
RefPtr<DatabaseMaintenance> GetDatabaseMaintenance(
const nsAString& aDatabasePath) const {
AssertIsOnBackgroundThread();
return mDatabaseMaintenances.Get(aDatabasePath);
}
void Stringify(nsACString& aResult) const;
private:
~Maintenance() override {
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();
// We need to declare refcounting unconditionally, because
// OpenDirectoryListener has pure-virtual refcounting.
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIRUNNABLE
// OpenDirectoryListener overrides.
void DirectoryLockAcquired(DirectoryLock* aLock) override;
void DirectoryLockFailed() override;
};
Maintenance::DirectoryInfo::DirectoryInfo(
PersistenceType aPersistenceType, FullOriginMetadata aFullOriginMetadata,
nsTArray<nsString>&& aDatabasePaths)
: mFullOriginMetadata(std::move(aFullOriginMetadata)),
mDatabasePaths(std::move(aDatabasePaths)),
mPersistenceType(aPersistenceType) {
MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
MOZ_ASSERT(!mFullOriginMetadata->mGroup.IsEmpty());
MOZ_ASSERT(!mFullOriginMetadata->mOrigin.IsEmpty());
#ifdef DEBUG
MOZ_ASSERT(!mDatabasePaths->IsEmpty());
for (const nsAString& databasePath : *mDatabasePaths) {
MOZ_ASSERT(!databasePath.IsEmpty());
}
#endif
MOZ_COUNT_CTOR(Maintenance::DirectoryInfo);
}
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;
RefPtr<DirectoryLock> mDirectoryLock;
const OriginMetadata mOriginMetadata;
const nsString mDatabasePath;
int64_t mDirectoryLockId;
nsCOMPtr<nsIRunnable> mCompleteCallback;
const PersistenceType mPersistenceType;
const Maybe<CipherKey> mMaybeKey;
public:
DatabaseMaintenance(Maintenance* aMaintenance, DirectoryLock* aDirectoryLock,
PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata,
const nsAString& aDatabasePath,
const Maybe<CipherKey>& aMaybeKey)
: Runnable("dom::indexedDB::DatabaseMaintenance"),
mMaintenance(aMaintenance),
mDirectoryLock(aDirectoryLock),
mOriginMetadata(aOriginMetadata),
mDatabasePath(aDatabasePath),
mPersistenceType(aPersistenceType),
mMaybeKey{aMaybeKey} {
MOZ_ASSERT(aDirectoryLock);
MOZ_ASSERT(mDirectoryLock->Id() >= 0);
mDirectoryLockId = mDirectoryLock->Id();
}
const nsAString& DatabasePath() const { return mDatabasePath; }
void WaitForCompletion(nsIRunnable* aCallback) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mCompleteCallback);
mCompleteCallback = aCallback;
}
void Stringify(nsACString& aResult) const;
private:
~DatabaseMaintenance() override = default;
// 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;
LazyInitializedOnce<const NotNull<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()
#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(NotNull<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;
};
#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
******************************************************************************/
// XXX Get rid of FileHelper and move the functions into DatabaseFileManager.
// Then, DatabaseFileManager::Get(Journal)Directory and
// DatabaseFileManager::GetFileForId might eventually be made private.
class MOZ_STACK_CLASS FileHelper final {
const SafeRefPtr<DatabaseFileManager> mFileManager;
LazyInitializedOnce<const NotNull<nsCOMPtr<nsIFile>>> mFileDirectory;
LazyInitializedOnce<const NotNull<nsCOMPtr<nsIFile>>> mJournalDirectory;
class ReadCallback;
LazyInitializedOnce<const NotNull<RefPtr<ReadCallback>>> mReadCallback;
public:
explicit FileHelper(SafeRefPtr<DatabaseFileManager>&& aFileManager)
: mFileManager(std::move(aFileManager)) {
MOZ_ASSERT(mFileManager);
}
nsresult Init();
[[nodiscard]] nsCOMPtr<nsIFile> GetFile(const DatabaseFileInfo& aFileInfo);
[[nodiscard]] nsCOMPtr<nsIFile> GetJournalFile(
const DatabaseFileInfo& aFileInfo);
nsresult CreateFileFromStream(nsIFile& aFile, nsIFile& aJournalFile,
nsIInputStream& aInputStream, bool aCompress,
const Maybe<CipherKey>& aMaybeKey);
private:
nsresult SyncCopy(nsIInputStream& aInputStream,
nsIOutputStream& aOutputStream, char* aBuffer,
uint32_t aBufferSize);
nsresult SyncRead(nsIInputStream& aInputStream, char* aBuffer,
uint32_t aBufferSize, uint32_t* aRead);
};
/*******************************************************************************
* Helper Functions
******************************************************************************/
bool GetFilenameBase(const nsAString& aFilename, const nsAString& aSuffix,
nsDependentSubstring& aFilenameBase) {
MOZ_ASSERT(!aFilename.IsEmpty());
MOZ_ASSERT(aFilenameBase.IsEmpty());
if (!StringEndsWith(aFilename, aSuffix) ||
aFilename.Length() == aSuffix.Length()) {
return false;
}
MOZ_ASSERT(aFilename.Length() > aSuffix.Length());
aFilenameBase.Rebind(aFilename, 0, aFilename.Length() - aSuffix.Length());
return true;
}
class EncryptedFileBlobImpl final : public FileBlobImpl {
public:
EncryptedFileBlobImpl(const nsCOMPtr<nsIFile>& aNativeFile,
const DatabaseFileInfo::IdType aId,
const CipherKey& aKey)
: FileBlobImpl{aNativeFile}, mKey{aKey} {
SetFileId(aId);
}
uint64_t GetSize(ErrorResult& aRv) override {
nsCOMPtr<nsIInputStream> inputStream;
CreateInputStream(getter_AddRefs(inputStream), aRv);
if (aRv.Failed()) {
return 0;
}
MOZ_ASSERT(inputStream);
QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(inputStream, Available), 0,
[&aRv](const nsresult rv) { aRv = rv; });
}
void CreateInputStream(nsIInputStream** aInputStream,
ErrorResult& aRv) const override {
nsCOMPtr<nsIInputStream> baseInputStream;
FileBlobImpl::CreateInputStream(getter_AddRefs(baseInputStream), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
*aInputStream =
MakeAndAddRef<DecryptingInputStream<IndexedDBCipherStrategy>>(
WrapNotNull(std::move(baseInputStream)), kEncryptedStreamBlockSize,
mKey)
.take();
}
void GetBlobImplType(nsAString& aBlobImplType) const override {
aBlobImplType = u"EncryptedFileBlobImpl"_ns;
}
already_AddRefed<BlobImpl> CreateSlice(uint64_t aStart, uint64_t aLength,
const nsAString& aContentType,
ErrorResult& aRv) const override {
MOZ_CRASH("Not implemented because this should be unreachable.");
}
private:
const CipherKey& mKey;
};
RefPtr<BlobImpl> CreateFileBlobImpl(const Database& aDatabase,
const nsCOMPtr<nsIFile>& aNativeFile,
const DatabaseFileInfo::IdType aId) {
const auto& maybeKey = aDatabase.MaybeKeyRef();
if (maybeKey) {
return MakeRefPtr<EncryptedFileBlobImpl>(aNativeFile, aId, *maybeKey);
}
auto impl = MakeRefPtr<FileBlobImpl>(aNativeFile);
impl->SetFileId(aId);
return impl;
}
Result<nsTArray<SerializedStructuredCloneFile>, nsresult>
SerializeStructuredCloneFiles(const SafeRefPtr<Database>& aDatabase,
const nsTArray<StructuredCloneFileParent>& aFiles,
bool aForPreprocess) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aDatabase);
if (aFiles.IsEmpty()) {
return nsTArray<SerializedStructuredCloneFile>{};
}
const nsCOMPtr<nsIFile> directory =
aDatabase->GetFileManager().GetCheckedDirectory();
QM_TRY(OkIf(directory), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
IDB_REPORT_INTERNAL_ERR_LAMBDA);
nsTArray<SerializedStructuredCloneFile> serializedStructuredCloneFiles;
QM_TRY(OkIf(serializedStructuredCloneFiles.SetCapacity(aFiles.Length(),
fallible)),
Err(NS_ERROR_OUT_OF_MEMORY));
QM_TRY(TransformIfAbortOnErr(
aFiles, MakeBackInserter(serializedStructuredCloneFiles),
[aForPreprocess](const auto& file) {
return !aForPreprocess ||
file.Type() == StructuredCloneFileBase::eStructuredClone;
},
[&directory, &aDatabase, aForPreprocess](
const auto& file) -> Result<SerializedStructuredCloneFile, nsresult> {
const int64_t fileId = file.FileInfo().Id();
MOZ_ASSERT(fileId > 0);
const nsCOMPtr<nsIFile> nativeFile =
mozilla::dom::indexedDB::DatabaseFileManager::GetCheckedFileForId(
directory, fileId);
QM_TRY(OkIf(nativeFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
IDB_REPORT_INTERNAL_ERR_LAMBDA);
switch (file.Type()) {
case StructuredCloneFileBase::eStructuredClone:
if (!aForPreprocess) {
return SerializedStructuredCloneFile{
null_t(), StructuredCloneFileBase::eStructuredClone};
}
[[fallthrough]];
case StructuredCloneFileBase::eBlob: {
const auto impl = CreateFileBlobImpl(*aDatabase, nativeFile,
file.FileInfo().Id());
IPCBlob ipcBlob;
// This can only fail if the child has crashed.
QM_TRY(MOZ_TO_RESULT(IPCBlobUtils::Serialize(impl, ipcBlob)),
Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
IDB_REPORT_INTERNAL_ERR_LAMBDA);
aDatabase->MapBlob(ipcBlob, file.FileInfoPtr());
return SerializedStructuredCloneFile{ipcBlob, file.Type()};
}
case StructuredCloneFileBase::eMutableFile: {
if (aDatabase->IsFileHandleDisabled()) {
return SerializedStructuredCloneFile{
null_t(), StructuredCloneFileBase::eMutableFile};
}
const RefPtr<MutableFile> actor = MutableFile::Create(
nativeFile, aDatabase.clonePtr(), file.FileInfoPtr());
QM_TRY(OkIf(actor), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
IDB_REPORT_INTERNAL_ERR_LAMBDA);
// Transfer ownership to IPDL.
actor->SetActorAlive();
if (!aDatabase->SendPBackgroundMutableFileConstructor(actor, u""_ns,
u""_ns)) {
// This can only fail if the child has crashed.
IDB_REPORT_INTERNAL_ERR();
return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
}
return SerializedStructuredCloneFile{
actor.get(), StructuredCloneFileBase::eMutableFile};
}
case StructuredCloneFileBase::eWasmBytecode:
case StructuredCloneFileBase::eWasmCompiled: {
// Set file() to null, support for storing WebAssembly.Modules has
// been removed in bug 1469395. Support for de-serialization of
// WebAssembly.Modules modules has been removed in bug 1561876. Full
// removal is tracked in bug 1487479.
return SerializedStructuredCloneFile{null_t(), file.Type()};
}
default:
MOZ_CRASH("Should never get here!");
}
}));
return std::move(serializedStructuredCloneFiles);
}
bool IsFileNotFoundError(const nsresult aRv) {
return aRv == NS_ERROR_FILE_NOT_FOUND;
}
enum struct Idempotency { Yes, No };
// Delete a file, decreasing the quota usage as appropriate. If the file no
// longer exists but aIdempotency is Idempotency::Yes, success is returned,
// although quota usage can't be decreased. (With the assumption being that the
// file was already deleted prior to this logic running, and the non-existent
// file was no longer tracked by quota because it didn't exist at
// initialization time or a previous deletion call updated the usage.)
nsresult DeleteFile(nsIFile& aFile, QuotaManager* const aQuotaManager,
const PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata,
const Idempotency aIdempotency) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
// Callers which pass Idempotency::Yes call this function without checking if
// the file already exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used
// here since we just want to log NS_ERROR_FILE_NOT_FOUND results and not spam
// the reports.
// Theoretically, there should be no QM_OR_ELSE_(WARN|LOG_VERBOSE)_IF when a
// caller passes Idempotency::No, but it's simpler when the predicate just
// always returns false in that case.
const auto isIgnorableError = [&aIdempotency]() -> bool (*)(nsresult) {
if (aIdempotency == Idempotency::Yes) {
return IsFileNotFoundError;
}
return [](const nsresult rv) { return false; };
}();
QM_TRY_INSPECT(
const auto& fileSize,
([aQuotaManager, &aFile,
isIgnorableError]() -> Result<Maybe<int64_t>, nsresult> {
if (aQuotaManager) {
QM_TRY_INSPECT(
const Maybe<int64_t>& fileSize,
QM_OR_ELSE_LOG_VERBOSE_IF(
// Expression.
MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize)
.map([](const int64_t val) { return Some(val); }),
// Predicate.
isIgnorableError,
// Fallback.
ErrToDefaultOk<Maybe<int64_t>>));
// XXX Can we really assert that the file size is not 0 if
// it existed? This might be violated by external
// influences.
MOZ_ASSERT(!fileSize || fileSize.value() >= 0);
return fileSize;
}
return Some(int64_t(0));
}()));
if (!fileSize) {
return NS_OK;
}
QM_TRY_INSPECT(const auto& didExist,
QM_OR_ELSE_LOG_VERBOSE_IF(
// Expression.
MOZ_TO_RESULT(aFile.Remove(false)).map(Some<Ok>),
// Predicate.
isIgnorableError,
// Fallback.
ErrToDefaultOk<Maybe<Ok>>));
if (!didExist) {
// XXX If we get here, this means that the file still existed when we
// queried its size, but no longer when we tried to remove it. Not sure if
// this should really be silently accepted in idempotent mode.
return NS_OK;
}
if (fileSize.value() > 0) {
MOZ_ASSERT(aQuotaManager);
aQuotaManager->DecreaseUsageForClient(
ClientMetadata{aOriginMetadata, Client::IDB}, fileSize.value());
}
return NS_OK;
}
nsresult DeleteFile(nsIFile& aDirectory, const nsAString& aFilename,
QuotaManager* const aQuotaManager,
const PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata,
const Idempotency aIdempotent) {
AssertIsOnIOThread();
MOZ_ASSERT(!aFilename.IsEmpty());
QM_TRY_INSPECT(const auto& file, CloneFileAndAppend(aDirectory, aFilename));
return DeleteFile(*file, aQuotaManager, aPersistenceType, aOriginMetadata,
aIdempotent);
}
// Delete files in a directory that you think exists. If the directory doesn't
// exist, an error will not be returned, but warning telemetry will be
// generated! So only call this on directories that you know exist (idempotent
// usage, but it's not recommended).
nsresult DeleteFilesNoQuota(nsIFile& aFile) {
AssertIsOnIOThread();
QM_TRY_INSPECT(const auto& didExist,
QM_OR_ELSE_WARN_IF(
// Expression.
MOZ_TO_RESULT(aFile.Remove(true)).map(Some<Ok>),
// Predicate.
IsFileNotFoundError,
// Fallback.
ErrToDefaultOk<Maybe<Ok>>));
Unused << didExist;
return NS_OK;
}
nsresult DeleteFilesNoQuota(nsIFile* aDirectory, const nsAString& aFilename) {
AssertIsOnIOThread();
MOZ_ASSERT(aDirectory);
MOZ_ASSERT(!aFilename.IsEmpty());
// The current using function hasn't initialized the origin, so in here we
// don't update the size of origin. Adding this assertion for preventing from
// misusing.
DebugOnly<QuotaManager*> quotaManager = QuotaManager::Get();
MOZ_ASSERT(!quotaManager->IsTemporaryStorageInitialized());
QM_TRY_INSPECT(const auto& file, CloneFileAndAppend(*aDirectory, aFilename));
QM_TRY(MOZ_TO_RESULT(DeleteFilesNoQuota(*file)));
return NS_OK;
}
// CreateMarkerFile and RemoveMarkerFile are a pair of functions to indicate
// whether having removed all the files successfully. The marker file should
// be checked before executing the next operation or initialization.
Result<nsCOMPtr<nsIFile>, nsresult> CreateMarkerFile(
nsIFile& aBaseDirectory, const nsAString& aDatabaseNameBase) {
AssertIsOnIOThread();
MOZ_ASSERT(!aDatabaseNameBase.IsEmpty());
QM_TRY_INSPECT(
const auto& markerFile,
CloneFileAndAppend(aBaseDirectory,
kIdbDeletionMarkerFilePrefix + aDatabaseNameBase));
// Callers call this function without checking if the file already exists
// (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we just want
// to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the reports.
//
// TODO: In theory if this file exists, then RemoveDatabaseFilesAndDirectory
// should have cleaned it up, but obviously we can crash and not clean it up,
// which is the whole point of the marker file. In that case, we'll realize
// the marker file exists in OpenDatabaseOp::DoDatabaseWork or
// GetUsageForOriginInternal and resume the removal by calling
// RemoveDatabaseFilesAndDirectory again, but we will also try to create the
// marker file again, so if we see this marker file, it is part
// of our standard operating procedure to redundantly try and create the
// marker here. We currently treat this as idempotent usage, but we could
// add an additional argument to RemoveDatabaseFilesAndDirectory which would
// indicate that we are resuming an unfinished removal, so the marker already
// exists and doesn't have to be created, and change
// QM_OR_ELSE_LOG_VERBOSE_IF to QM_OR_ELSE_WARN_IF in the end.
QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
// Expression.
MOZ_TO_RESULT(markerFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)),
// Predicate.
IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
// Fallback.
ErrToDefaultOk<>));
return markerFile;
}
nsresult RemoveMarkerFile(nsIFile* aMarkerFile) {
AssertIsOnIOThread();
MOZ_ASSERT(aMarkerFile);
DebugOnly<bool> exists;
MOZ_ASSERT(NS_SUCCEEDED(aMarkerFile->Exists(&exists)));
MOZ_ASSERT(exists);
QM_TRY(MOZ_TO_RESULT(aMarkerFile->Remove(false)));
return NS_OK;
}
Result<Ok, nsresult> DeleteFileManagerDirectory(
nsIFile& aFileManagerDirectory, QuotaManager* aQuotaManager,
const PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata) {
// XXX In theory, deleting can continue for other files in case of a failure,
// leaving only those files behind that cause the problem actually. However,
// the current architecture doesn't allow having more databases (for the same
// name) on disk, so trying to delete as much as possible won't help much
// because we need to delete entire .files directory in the end anyway.
QM_TRY(DatabaseFileManager::TraverseFiles(
aFileManagerDirectory,
// KnownDirEntryOp
[&aQuotaManager, aPersistenceType, &aOriginMetadata](
nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
if (isDirectory) {
// The journal directory doesn't count towards quota.
QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file)));
}
// Stored files do count towards quota.
QM_TRY_RETURN(
MOZ_TO_RESULT(DeleteFile(file, aQuotaManager, aPersistenceType,
aOriginMetadata, Idempotency::Yes)));
},
// UnknownDirEntryOp
[aPersistenceType, &aOriginMetadata](
nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
// Unknown files and directories don't count towards quota.
if (isDirectory) {
QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file)));
}
QM_TRY_RETURN(MOZ_TO_RESULT(
DeleteFile(file, /* doesn't count */ nullptr, aPersistenceType,
aOriginMetadata, Idempotency::Yes)));
}));
QM_TRY_RETURN(
MOZ_TO_RESULT_INVOKE_MEMBER(aFileManagerDirectory, Remove, false));
}
// Idempotently delete all the parts of an IndexedDB database including its
// SQLite database file, its WAL journal, it's shared-memory file, and its
// Blob/Files sub-directory. A marker file is created prior to performing the
// deletion so that in the event we crash or fail to successfully delete the
// database and its files, we will re-attempt the deletion the next time the
// origin is initialized using this method. Because this means the method may be
// called on a partially deleted database, this method uses DeleteFile which
// succeeds when the file we ask it to delete does not actually exist. The
// marker file is removed once deletion has successfully completed.
nsresult RemoveDatabaseFilesAndDirectory(nsIFile& aBaseDirectory,
const nsAString& aDatabaseFilenameBase,
QuotaManager* aQuotaManager,
const PersistenceType aPersistenceType,
const OriginMetadata& aOriginMetadata,
const nsAString& aDatabaseName) {
AssertIsOnIOThread();
MOZ_ASSERT(!aDatabaseFilenameBase.IsEmpty());
AUTO_PROFILER_LABEL("RemoveDatabaseFilesAndDirectory", DOM);
QM_TRY_UNWRAP(auto markerFile,
CreateMarkerFile(aBaseDirectory, aDatabaseFilenameBase));
// The database file counts towards quota.
QM_TRY(MOZ_TO_RESULT(DeleteFile(
aBaseDirectory, aDatabaseFilenameBase + kSQLiteSuffix, aQuotaManager,
aPersistenceType, aOriginMetadata, Idempotency::Yes)));
// .sqlite-journal files don't count towards quota.
QM_TRY(MOZ_TO_RESULT(DeleteFile(aBaseDirectory,
aDatabaseFilenameBase + kSQLiteJournalSuffix,
/* doesn't count */ nullptr, aPersistenceType,
aOriginMetadata, Idempotency::Yes)));
// .sqlite-shm files don't count towards quota.
QM_TRY(MOZ_TO_RESULT(DeleteFile(aBaseDirectory,
aDatabaseFilenameBase + kSQLiteSHMSuffix,
/* doesn't count */ nullptr, aPersistenceType,
aOriginMetadata, Idempotency::Yes)));
// .sqlite-wal files do count towards quota.
QM_TRY(MOZ_TO_RESULT(DeleteFile(
aBaseDirectory, aDatabaseFilenameBase + kSQLiteWALSuffix, aQuotaManager,
aPersistenceType, aOriginMetadata, Idempotency::Yes)));
// The files directory counts towards quota.
QM_TRY_INSPECT(
const auto& fmDirectory,
CloneFileAndAppend(aBaseDirectory, aDatabaseFilenameBase +
kFileManagerDirectoryNameSuffix));
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(fmDirectory, Exists));
if (exists) {
QM_TRY_INSPECT(const bool& isDirectory,
MOZ_TO_RESULT_INVOKE_MEMBER(fmDirectory, IsDirectory));
QM_TRY(OkIf(isDirectory), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
QM_TRY(DeleteFileManagerDirectory(*fmDirectory, aQuotaManager,
aPersistenceType, aOriginMetadata));
}
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
MOZ_ASSERT_IF(aQuotaManager, mgr);
if (mgr) {
mgr->InvalidateFileManager(aPersistenceType, aOriginMetadata.mOrigin,
aDatabaseName);
}
QM_TRY(MOZ_TO_RESULT(RemoveMarkerFile(markerFile)));
return NS_OK;
}
/*******************************************************************************
* Globals
******************************************************************************/
// Counts the number of "live" Factory, FactoryOp and Database instances.
uint64_t gBusyCount = 0;
using FactoryOpArray = nsTArray<CheckedUnsafePtr<FactoryOp>>;
StaticAutoPtr<FactoryOpArray> gFactoryOps;
// Maps a database id to information about live database actors.
using DatabaseActorHashtable =
nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>;
StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;
using PrivateBrowsingInfoHashtable = nsTHashMap<nsCStringHashKey, CipherKey>;
// XXX Maybe we can avoid a mutex here by moving all accesses to the background
// thread.
StaticAutoPtr<DataMutex<PrivateBrowsingInfoHashtable>>
gPrivateBrowsingInfoHashtable;
StaticRefPtr<ConnectionPool> gConnectionPool;
StaticRefPtr<FileHandleThreadPool> gFileHandleThreadPool;
using DatabaseLoggingInfoHashtable =
nsTHashMap<nsIDHashKey, DatabaseLoggingInfo*>;
StaticAutoPtr<DatabaseLoggingInfoHashtable> gLoggingInfoHashtable;
using TelemetryIdHashtable = nsTHashMap<nsUint32HashKey, uint32_t>;
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(!gPrivateBrowsingInfoHashtable);
gPrivateBrowsingInfoHashtable = new DataMutex<PrivateBrowsingInfoHashtable>(
"gPrivateBrowsingInfoHashtable");
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(gPrivateBrowsingInfoHashtable);
// XXX After we add the private browsing session end listener, we can assert
// this.
// MOZ_ASSERT(!gPrivateBrowsingInfoHashtable->Count());
gPrivateBrowsingInfoHashtable = 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
}
}
template <typename Condition>
void InvalidateLiveDatabasesMatching(const Condition& aCondition) {
AssertIsOnBackgroundThread();
if (!gLiveDatabaseHashtable) {
return;
}
// Invalidating a Database will cause it to be removed from the
// gLiveDatabaseHashtable entries' mLiveDatabases, and, if it was the last
// element in mLiveDatabases, to remove the whole hashtable entry. Therefore,
// we need to make a temporary list of the databases to invalidate to avoid
// iterator invalidation.
nsTArray<SafeRefPtr<Database>> databases;
for (const auto& liveDatabasesEntry : gLiveDatabaseHashtable->Values()) {
for (const auto& database : liveDatabasesEntry->mLiveDatabases) {
if (aCondition(*database)) {
databases.AppendElement(
SafeRefPtr{database.get(), AcquireStrongRefFromRawPtr{}});
}
}
}
for (const auto& database : databases) {
database->Invalidate();
}
}
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.
MOZ_ASSERT(StringEndsWith(filename, kSQLiteSuffix));
filename.Truncate(filename.Length() - kSQLiteSuffix.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));
constexpr auto separator = u"*"_ns;
uint32_t hashValue =
HashString(persistence + separator + origin + separator + filename);
MutexAutoLock lock(*gTelemetryIdMutex);
if (!gTelemetryIdHashtable) {
gTelemetryIdHashtable = new TelemetryIdHashtable();
}
return gTelemetryIdHashtable->LookupOrInsertWith(hashValue, [] {
static uint32_t sNextId = 1;
// We're locked, no need for atomics.
return sNextId++;
});
}
const CommonIndexOpenCursorParams& GetCommonIndexOpenCursorParams(
const OpenCursorParams& aParams) {
switch (aParams.type()) {
case OpenCursorParams::TIndexOpenCursorParams:
return aParams.get_IndexOpenCursorParams().commonIndexParams();
case OpenCursorParams::TIndexOpenKeyCursorParams:
return aParams.get_IndexOpenKeyCursorParams().commonIndexParams();
default:
MOZ_CRASH("Should never get here!");
}
}
const CommonOpenCursorParams& GetCommonOpenCursorParams(
const OpenCursorParams& aParams) {
switch (aParams.type()) {
case OpenCursorParams::TObjectStoreOpenCursorParams:
return aParams.get_ObjectStoreOpenCursorParams().commonParams();
case OpenCursorParams::TObjectStoreOpenKeyCursorParams:
return aParams.get_ObjectStoreOpenKeyCursorParams().commonParams();
case OpenCursorParams::TIndexOpenCursorParams:
case OpenCursorParams::TIndexOpenKeyCursorParams:
return GetCommonIndexOpenCursorParams(aParams).commonParams();
default:
MOZ_CRASH("Should never get here!");
}
}
// TODO: Using nsCString as a return type here seems to lead to a dependency on
// some temporaries, which I did not expect. Is it a good idea that the default
// operator+ behaviour constructs such strings? It is certainly useful as an
// optimization, but this should be better done via an appropriately named
// function rather than an operator.
nsAutoCString MakeColumnPairSelectionList(
const nsLiteralCString& aPlainColumnName,
const nsLiteralCString& aLocaleAwareColumnName,
const nsLiteralCString& aSortColumnAlias, const bool aIsLocaleAware) {
return aPlainColumnName +
(aIsLocaleAware ? EmptyCString() : " as "_ns + aSortColumnAlias) +
", "_ns + aLocaleAwareColumnName +
(aIsLocaleAware ? " as "_ns + aSortColumnAlias : EmptyCString());
}
constexpr bool IsIncreasingOrder(const IDBCursorDirection aDirection) {
MOZ_ASSERT(aDirection == IDBCursorDirection::Next ||
aDirection == IDBCursorDirection::Nextunique ||
aDirection == IDBCursorDirection::Prev ||
aDirection == IDBCursorDirection::Prevunique);
return aDirection == IDBCursorDirection::Next ||
aDirection == IDBCursorDirection::Nextunique;
}
constexpr bool IsUnique(const IDBCursorDirection aDirection) {
MOZ_ASSERT(aDirection == IDBCursorDirection::Next ||
aDirection == IDBCursorDirection::Nextunique ||
aDirection == IDBCursorDirection::Prev ||
aDirection == IDBCursorDirection::Prevunique);
return aDirection == IDBCursorDirection::Nextunique ||
aDirection == IDBCursorDirection::Prevunique;
}
// TODO: In principle, this could be constexpr, if operator+(nsLiteralCString,
// nsLiteralCString) were constexpr and returned a literal type.
nsAutoCString MakeDirectionClause(const IDBCursorDirection aDirection) {
return " ORDER BY "_ns + kColumnNameKey +
(IsIncreasingOrder(aDirection) ? " ASC"_ns : " DESC"_ns);
}
enum struct ComparisonOperator {
LessThan,
LessOrEquals,
Equals,
GreaterThan,
GreaterOrEquals,
};
constexpr nsLiteralCString GetComparisonOperatorString(
const ComparisonOperator aComparisonOperator) {
switch (aComparisonOperator) {
case ComparisonOperator::LessThan:
return "<"_ns;
case ComparisonOperator::LessOrEquals:
return "<="_ns;
case ComparisonOperator::Equals:
return "=="_ns;
case ComparisonOperator::GreaterThan:
return ">"_ns;
case ComparisonOperator::GreaterOrEquals:
return ">="_ns;
}
// TODO: This is just to silence the "control reaches end of non-void
// function" warning. Cannot use MOZ_CRASH in a constexpr function,
// unfortunately.
return ""_ns;
}
nsAutoCString GetKeyClause(const nsACString& aColumnName,
const ComparisonOperator aComparisonOperator,
const nsLiteralCString& aStmtParamName) {
return aColumnName + " "_ns +
GetComparisonOperatorString(aComparisonOperator) + " :"_ns +
aStmtParamName;
}
nsAutoCString GetSortKeyClause(const ComparisonOperator aComparisonOperator,
const nsLiteralCString& aStmtParamName) {
return GetKeyClause(kColumnNameAliasSortKey, aComparisonOperator,
aStmtParamName);
}
template <IDBCursorType CursorType>
struct PopulateResponseHelper;
struct CommonPopulateResponseHelper {
explicit CommonPopulateResponseHelper(
const TransactionDatabaseOperationBase& aOp)
: mOp{aOp} {}
nsresult GetKeys(mozIStorageStatement* const aStmt,
Key* const aOptOutSortKey) {
QM_TRY(MOZ_TO_RESULT(GetCommonKeys(aStmt)));
if (aOptOutSortKey) {
*aOptOutSortKey = mPosition;
}
return NS_OK;
}
nsresult GetCommonKeys(mozIStorageStatement* const aStmt) {
MOZ_ASSERT(mPosition.IsUnset());
QM_TRY(MOZ_TO_RESULT(mPosition.SetFromStatement(aStmt, 0)));
IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
"PRELOAD: Populating response with key %s", "Populating%.0s",
IDB_LOG_ID_STRING(mOp.BackgroundChildLoggingId()),
mOp.TransactionLoggingSerialNumber(), mOp.LoggingSerialNumber(),
mPosition.GetBuffer().get());
return NS_OK;
}
template <typename Response>
void FillKeys(Response& aResponse) {
MOZ_ASSERT(!mPosition.IsUnset());
aResponse.key() = std::move(mPosition);
}
template <typename Response>
static size_t GetKeySize(const Response& aResponse) {
return aResponse.key().GetBuffer().Length();
}
protected:
const Key& GetPosition() const { return mPosition; }
private:
const TransactionDatabaseOperationBase& mOp;
Key mPosition;
};
struct IndexPopulateResponseHelper : CommonPopulateResponseHelper {
using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
nsresult GetKeys(mozIStorageStatement* const aStmt,
Key* const aOptOutSortKey) {
MOZ_ASSERT(mLocaleAwarePosition.IsUnset());
MOZ_ASSERT(mObjectStorePosition.IsUnset());
QM_TRY(MOZ_TO_RESULT(CommonPopulateResponseHelper::GetCommonKeys(aStmt)));
QM_TRY(MOZ_TO_RESULT(mLocaleAwarePosition.SetFromStatement(aStmt, 1)));
QM_TRY(MOZ_TO_RESULT(mObjectStorePosition.SetFromStatement(aStmt, 2)));
if (aOptOutSortKey) {
*aOptOutSortKey =
mLocaleAwarePosition.IsUnset() ? GetPosition() : mLocaleAwarePosition;
}
return NS_OK;
}
template <typename Response>
void FillKeys(Response& aResponse) {
MOZ_ASSERT(!mLocaleAwarePosition.IsUnset());
MOZ_ASSERT(!mObjectStorePosition.IsUnset());
CommonPopulateResponseHelper::FillKeys(aResponse);
aResponse.sortKey() = std::move(mLocaleAwarePosition);
aResponse.objectKey() = std::move(mObjectStorePosition);
}
template <typename Response>
static size_t GetKeySize(Response& aResponse) {
return CommonPopulateResponseHelper::GetKeySize(aResponse) +
aResponse.sortKey().GetBuffer().Length() +
aResponse.objectKey().GetBuffer().Length();
}
private:
Key mLocaleAwarePosition, mObjectStorePosition;
};
struct KeyPopulateResponseHelper {
static constexpr nsresult MaybeGetCloneInfo(
mozIStorageStatement* const /*aStmt*/, const CursorBase& /*aCursor*/) {
return NS_OK;
}
template <typename Response>
static constexpr void MaybeFillCloneInfo(Response& /*aResponse*/,
FilesArray* const /*aFiles*/) {}
template <typename Response>
static constexpr size_t MaybeGetCloneInfoSize(const Response& /*aResponse*/) {
return 0;
}
};
template <bool StatementHasIndexKeyBindings>
struct ValuePopulateResponseHelper {
nsresult MaybeGetCloneInfo(mozIStorageStatement* const aStmt,
const ValueCursorBase& aCursor) {
constexpr auto offset = StatementHasIndexKeyBindings ? 2 : 0;
QM_TRY_UNWRAP(auto cloneInfo,
GetStructuredCloneReadInfoFromStatement(
aStmt, 2 + offset, 1 + offset, *aCursor.mFileManager,
aCursor.mDatabase->MaybeKeyRef()));
mCloneInfo.init(std::move(cloneInfo));
if (mCloneInfo->HasPreprocessInfo()) {
IDB_WARNING("Preprocessing for cursors not yet implemented!");
return NS_ERROR_NOT_IMPLEMENTED;
}
return NS_OK;
}
template <typename Response>
void MaybeFillCloneInfo(Response& aResponse, FilesArray* const aFiles) {
auto cloneInfo = mCloneInfo.release();
aResponse.cloneInfo().data().data = cloneInfo.ReleaseData();
aFiles->AppendElement(cloneInfo.ReleaseFiles());
}
template <typename Response>
static size_t MaybeGetCloneInfoSize(const Response& aResponse) {
return aResponse.cloneInfo().data().data.Size();
}
private:
LazyInitializedOnceEarlyDestructible<const StructuredCloneReadInfoParent>
mCloneInfo;
};
template <>
struct PopulateResponseHelper<IDBCursorType::ObjectStore>
: ValuePopulateResponseHelper<false>, CommonPopulateResponseHelper {
using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
static auto& GetTypedResponse(CursorResponse* const aResponse) {
return aResponse->get_ArrayOfObjectStoreCursorResponse();
}
};
template <>
struct PopulateResponseHelper<IDBCursorType::ObjectStoreKey>
: KeyPopulateResponseHelper, CommonPopulateResponseHelper {
using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
static auto& GetTypedResponse(CursorResponse* const aResponse) {
return aResponse->get_ArrayOfObjectStoreKeyCursorResponse();
}
};
template <>
struct PopulateResponseHelper<IDBCursorType::Index>
: ValuePopulateResponseHelper<true>, IndexPopulateResponseHelper {
using IndexPopulateResponseHelper::IndexPopulateResponseHelper;
static auto& GetTypedResponse(CursorResponse* const aResponse) {
return aResponse->get_ArrayOfIndexCursorResponse();
}
};
template <>
struct PopulateResponseHelper<IDBCursorType::IndexKey>
: KeyPopulateResponseHelper, IndexPopulateResponseHelper {
using IndexPopulateResponseHelper::IndexPopulateResponseHelper;
static auto& GetTypedResponse(CursorResponse* const aResponse) {
return aResponse->get_ArrayOfIndexKeyCursorResponse();
}
};
nsresult DispatchAndReturnFileReferences(
PersistenceType aPersistenceType, const nsACString& aOrigin,
const nsAString& aDatabaseName, const int64_t aFileId,
int32_t* const aMemRefCnt, int32_t* const aDBRefCnt, bool* const aResult) {