Source code

Revision control

Copy as Markdown

Other Tools

/* 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 <iterator>
#include <new>
#include <numeric>
#include <type_traits>
#include <utility>
#include "ActorsParentCommon.h"
#include "CrashAnnotations.h"
#include "DBSchema.h"
#include "DatabaseFileInfo.h"
#include "DatabaseFileManager.h"
#include "DatabaseFileManagerImpl.h"
#include "ErrorList.h"
#include "IDBCursorType.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDBCipherKeyManager.h"
#include "IndexedDBCommon.h"
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "KeyPath.h"
#include "MainThreadUtils.h"
#include "NotifyUtils.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"
#include "SafeRefPtr.h"
#include "SchemaUpgrades.h"
#include "TransactionOpResult.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/GeckoTrace.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/Logging.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/SnappyCompressOutputStream.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Variant.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/IDBCursorBinding.h"
#include "mozilla/dom/IDBFactory.h"
#include "mozilla/dom/IPCBlob.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/IndexedDatabase.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PContentParent.h"
#include "mozilla/dom/ScriptSettings.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/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/Assertions.h"
#include "mozilla/dom/quota/CachingDatabaseConnection.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/ClientDirectoryLock.h"
#include "mozilla/dom/quota/ClientDirectoryLockHandle.h"
#include "mozilla/dom/quota/ClientImpl.h"
#include "mozilla/dom/quota/ConditionalCompilation.h"
#include "mozilla/dom/quota/Date.h"
#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
#include "mozilla/dom/quota/DirectoryLock.h"
#include "mozilla/dom/quota/DirectoryLockInlines.h"
#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
#include "mozilla/dom/quota/ErrorHandling.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/dom/quota/OriginScope.h"
#include "mozilla/dom/quota/PersistenceScope.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/PrincipalUtils.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/ThreadUtils.h"
#include "mozilla/dom/quota/UniversalDirectoryLock.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/fallible.h"
#include "mozilla/glean/DomIndexedDBMetrics.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 "nsDebug.h"
#include "nsError.h"
#include "nsEscape.h"
#include "nsHashKeys.h"
#include "nsIAsyncInputStream.h"
#include "nsID.h"
#include "nsIDUtils.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 "nsTHashMap.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 {
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 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. Please keep in sync with the constants in
// test_connection_idle_maintenance*.js tests
const uint32_t kMaxConnectionThreadCount = 20;
static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");
// The maximum number of threads to keep when idle. Until we switch to the STS
// pool, we can reduce the number of idle threads kept around thanks to the
// grace timeout.
const uint32_t kMaxIdleConnectionThreadCount = 1;
static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
"Idle thread limit must be less than total thread limit!");
// The length of time that wanted idle threads will stay alive before being shut
// down.
const uint32_t kConnectionThreadMaxIdleMS = 30 * 1000; // 30 seconds
// The length of time that excess idle threads will stay alive before being shut
// down.
const uint32_t kConnectionThreadGraceIdleMS = 500; // 0.5 seconds
// The length of time that database connections will be held open after all
// transactions have completed before doing idle maintenance. Please keep in
// sync with the timeouts in test_connection_idle_maintenance*.js tests
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
#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;
// 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;
// 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;
// Set to a non-zero number to enable debugging of transaction event targets.
// It will cause sleeping after every transaction runnable!
//
// This can be useful for discovering race conditions related to switching to
// another thread. Such races are usually avoided by using MozPromise or
// RunAfterProcessingCurrentEvent. Chaos mode doesn't always help with
// uncovering these issues, and only a precisely targeted sleep call can
// simulate the problem.
const uint32_t kDEBUGTransactionThreadSleepMS = 0;
// Make sure that we notice if we ever accidentally check in a non-zero value.
# ifdef MOZILLA_OFFICIAL
static_assert(kDEBUGTransactionThreadSleepMS == 0);
# endif
#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_cvref_t<decltype(std::declval<const ObjectStoreGetParams&>()
.objectStoreId())>>);
static_assert(std::is_same_v<
IndexOrObjectStoreId,
std::remove_cvref_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);
});
}
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 QuotaVFS 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 nsCString directoryLockIdClause =
"&directoryLockId="_ns + IntToCString(aDirectoryLockId);
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;
}
nsLiteralCString GetDefaultSynchronousMode() {
return IndexedDatabaseManager::FullSynchronous() ? "FULL"_ns : "NORMAL"_ns;
}
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 + GetDefaultSynchronousMode() + ";"_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_INTERRUPTIBLE));
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();
// Use exponential backoff: start at 1ms, double up to 100ms max
uint32_t sleepMs = 1;
constexpr uint32_t kMaxSleepMs = 100;
do {
PR_Sleep(PR_MillisecondsToInterval(sleepMs));
// Exponential backoff with cap
sleepMs = std::min(sleepMs * 2, kMaxSleepMs);
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;
IDBTransaction::Durability mLastDurability;
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(const IDBTransaction::Durability aDurability);
nsresult CommitWriteTransaction();
void RollbackWriteTransaction();
void FinishWriteTransaction();
nsresult StartSavepoint();
nsresult ReleaseSavepoint();
nsresult RollbackSavepoint();
nsresult Checkpoint() {
AssertIsOnConnectionThread();
return CheckpointInternal(CheckpointMode::Full);
}
void DoIdleProcessing(bool aNeedsCheckpoint,
const Atomic<bool>& aInterrupted);
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,
const Atomic<bool>& aInterrupted);
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_ONEVENTTARGET
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; }
void ResetSavepointDelta() { mSavepointDelta = 0; }
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 DatabaseCompleteCallback;
class FinishCallbackWrapper;
class IdleConnectionRunnable;
#ifdef DEBUG
class TransactionRunnable;
#endif
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;
}
};
struct PerformingIdleMaintenanceDatabaseInfo {
const NotNull<DatabaseInfo*> mDatabaseInfo;
RefPtr<IdleConnectionRunnable> mIdleConnectionRunnable;
PerformingIdleMaintenanceDatabaseInfo(
DatabaseInfo& aDatabaseInfo,
RefPtr<IdleConnectionRunnable> aIdleConnectionRunnable);
PerformingIdleMaintenanceDatabaseInfo(
const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete;
PerformingIdleMaintenanceDatabaseInfo(
PerformingIdleMaintenanceDatabaseInfo&& aOther) noexcept
: mDatabaseInfo{aOther.mDatabaseInfo},
mIdleConnectionRunnable{std::move(aOther.mIdleConnectionRunnable)} {
MOZ_COUNT_CTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo);
}
PerformingIdleMaintenanceDatabaseInfo& operator=(
const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete;
PerformingIdleMaintenanceDatabaseInfo& operator=(
PerformingIdleMaintenanceDatabaseInfo&& aOther) = delete;
~PerformingIdleMaintenanceDatabaseInfo();
bool operator==(const DatabaseInfo* aDatabaseInfo) const {
return mDatabaseInfo == aDatabaseInfo;
}
};
// This mutex guards mDatabases, see below.
Mutex mDatabasesMutex MOZ_UNANNOTATED;
nsCOMPtr<nsIThreadPool> mIOTarget;
nsTArray<IdleDatabaseInfo> mIdleDatabases;
nsTArray<PerformingIdleMaintenanceDatabaseInfo>
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<DatabaseCompleteCallback>> mCompleteCallbacks;
uint64_t mNextTransactionId;
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);
/**
* Starts a new operation associated with the given transaction.
*
* This method initiates an operation by:
* 1. Dispatching the provided runnable to the task queue created on top of
* the I/O thread pool if the transaction is currently running.
* 2. Queuing the runnable for later execution if the transaction is not yet
* running.
*
* It is mandatory for all operations to call StartOp to ensure proper
* handling and sequencing within the transaction context.
*
* Note:
* - For more complex operations that involve work on other threads or require
* communication with content processes, StartOp should not be called again
* to dispatch to the task queue, as this could disrupt proper queuing and
* execution.
*/
void StartOp(uint64_t aTransactionId, nsCOMPtr<nsIRunnable> aRunnable);
/**
* Marks the completion of an operation associated with the given transaction.
*
* This method signals that the current operation has finished, allowing the
* next queued operation (if any) for the transaction to start.
*/
void FinishOp(uint64_t aTransactionId);
void Finish(uint64_t aTransactionId, FinishCallback* aCallback);
void CloseDatabaseWhenIdle(const nsACString& aDatabaseId) {
(void)CloseDatabaseWhenIdleInternal(aDatabaseId);
}
void WaitForDatabaseToComplete(const nsCString& aDatabaseId,
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 CloseIdleDatabases();
bool ScheduleTransaction(TransactionInfo& aTransactionInfo,
bool aFromQueuedTransactions);
void NoteFinishedTransaction(uint64_t aTransactionId);
void ScheduleQueuedTransactions();
void NoteIdleDatabase(DatabaseInfo& aDatabaseInfo);
void NoteClosedDatabase(DatabaseInfo& aDatabaseInfo);
bool MaybeFireCallback(DatabaseCompleteCallback* 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;
Atomic<bool> mInterrupted;
public:
IdleConnectionRunnable(DatabaseInfo& aDatabaseInfo, bool aNeedsCheckpoint)
: ConnectionRunnable(aDatabaseInfo), mNeedsCheckpoint(aNeedsCheckpoint) {}
NS_INLINE_DECL_REFCOUNTING_INHERITED(IdleConnectionRunnable,
ConnectionRunnable)
void Interrupt() { mInterrupted = true; }
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 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;
RefPtr<TaskQueue> mEventTarget;
uint32_t mReadTransactionCount;
uint32_t mWriteTransactionCount;
bool mNeedsCheckpoint;
bool mIdle;
FlippedOnce<false> mCloseOnIdle;
bool mClosing;
#ifdef DEBUG
nsISerialEventTarget* mDEBUGConnectionEventTarget;
#endif
DatabaseInfo(ConnectionPool* aConnectionPool, const nsACString& aDatabaseId);
void AssertIsOnConnectionThread() const {
MOZ_ASSERT(mDEBUGConnectionEventTarget);
MOZ_ASSERT(GetCurrentSerialEventTarget() == mDEBUGConnectionEventTarget);
}
uint64_t TotalTransactionCount() const {
return mReadTransactionCount + mWriteTransactionCount;
}
nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable);
DatabaseInfo(const DatabaseInfo&) = delete;
DatabaseInfo& operator=(const DatabaseInfo&) = delete;
private:
~DatabaseInfo();
};
struct ConnectionPool::DatabaseCompleteCallback final {
friend DefaultDelete<DatabaseCompleteCallback>;
nsCString mDatabaseId;
nsCOMPtr<nsIRunnable> mCallback;
DatabaseCompleteCallback(const nsCString& aDatabaseIds,
nsIRunnable* aCallback);
private:
~DatabaseCompleteCallback();
};
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
};
#ifdef DEBUG
class ConnectionPool::TransactionRunnable final : public Runnable {
public:
explicit TransactionRunnable(nsCOMPtr<nsIRunnable> aRunnable);
private:
NS_DECL_NSIRUNNABLE
nsCOMPtr<nsIRunnable> mRunnable;
};
#endif
class ConnectionPool::TransactionInfo final {
friend 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;
mozilla::Queue<nsCOMPtr<nsIRunnable>, 16> mQueuedOps;
const bool mIsWriteTransaction;
bool mRunning;
bool mRunningOp;
#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();
void SetRunning();
void StartOp(nsCOMPtr<nsIRunnable> aRunnable);
void FinishOp();
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(&current)) &&
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(GetCurrentSerialEventTarget()),
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;
// Unique request id within the context of the transaction, allocated by the
// transaction in the content process starting from 0. Values less than 0 are
// impossible and forbidden. Used to support the explicit commit() request.
const int64_t mRequestId;
InternalState mInternalState = InternalState::Initial;
bool mWaitingForContinue = false;
const bool mTransactionIsAborted;
bool mNotedActiveRequest = false;
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:
TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction,
int64_t aRequestId);
TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction,
const int64_t aRequestId,
uint64_t aLoggingSerialNumber);
~TransactionDatabaseOperationBase() override;
void NoteTransactionActiveRequest();
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 TransactionOpResult 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(const TransactionOpResult& aResult) = 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> {
nsCString mSystemLocale;
RefPtr<DatabaseLoggingInfo> mLoggingInfo;
#ifdef DEBUG
bool mActorDestroyed;
#endif
// Reference counted.
~Factory() override;
public:
[[nodiscard]] static SafeRefPtr<Factory> Create(
const LoggingInfo& aLoggingInfo, const nsACString& aSystemLocale);
DatabaseLoggingInfo* GetLoggingInfo() const {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mLoggingInfo);
return mLoggingInfo;
}
const nsCString& GetSystemLocale() const { return mSystemLocale; }
MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Factory)
MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(Factory, AtomicSafeRefCounted)
// Only constructed in Create().
Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo,
const nsACString& aSystemLocale);
// 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;
mozilla::ipc::IPCResult RecvGetDatabases(
const PersistenceType& aPersistenceType,
const PrincipalInfo& aPrincipalInfo,
GetDatabasesResolver&& aResolve) override;
private:
Maybe<ContentParentId> GetContentParentId() const;
};
class WaitForTransactionsHelper final : public Runnable {
const nsCString mDatabaseId;
nsCOMPtr<nsIRunnable> mCallback;
enum class State { Initial = 0, WaitingForTransactions, 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 CallCallback();
NS_DECL_NSIRUNNABLE
};
class Database final : public PBackgroundIDBDatabaseParent,
public LinkedListElement<Database>,
public AtomicSafeRefCounted<Database> {
friend class VersionChangeTransaction;
class StartTransactionOp;
class UnmapBlobCallback;
private:
SafeRefPtr<Factory> mFactory;
SafeRefPtr<FullDatabaseMetadata> mMetadata;
SafeRefPtr<DatabaseFileManager> mFileManager;
ClientDirectoryLockHandle mDirectoryLockHandle;
nsTHashSet<TransactionBase*> mTransactions;
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;
int64_t mDirectoryLockId;
const uint32_t mTelemetryId;
const PersistenceType mPersistenceType;
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,
ClientDirectoryLockHandle aDirectoryLockHandle,
bool aInPrivateBrowsing, const Maybe<const CipherKey>& aMaybeKey);
void AssertIsOnConnectionThread() const {
#ifdef DEBUG
// mConnection is used to cache the result from ConnectionPool's
// GetOrCreateConnection method (potentially avoiding a lock and a hash
// lookup). However, once the connection is closed, the task queue for the
// given database is also destroyed, so the connection, which caches the
// event target it was created on, is no longer reliable for asserting that
// the current thread is the connection thread (mConnection might be reset
// when EnsureConnection is called again, but in the meantime, we have to
// fallback to just checking the main thread and the PBackgroud thread).
//
// We cannot additionally assert mInvalidated here: mConnection is only ever
// assigned (never cleared), so it is also left pointing at a closed
// connection after a routine idle-connection close. ConnectionPool's idle
// timer (IdleTimerCallback -> CloseDatabase -> CloseConnectionRunnable)
// closes the connection, and NoteClosedDatabase then drops the
// DatabaseInfo without invalidating the owning Database -- so the Database
// survives with a Closed() mConnection and mInvalidated still false. A
// sibling transaction's CommitOp can then run on the connection thread in
// this closed-but-not-invalidated state -- a window widened whenever an
// in-profile database open is slow (e.g. SQLite at-rest encryption fetching
// a per-database key). The CommitOp path tolerates a closed/absent
// connection, so only the thread identity matters here.
if (mConnection && !mConnection->Closed()) {
mConnection->AssertIsOnConnectionThread();
} else {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
}
#endif
}
NS_IMETHOD_(MozExternalRefCountType) AddRef() override {
return AtomicSafeRefCounted<Database>::AddRef();
}
NS_IMETHOD_(MozExternalRefCountType) Release() override {
return AtomicSafeRefCounted<Database>::Release();
}
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<ClientDirectoryLock&> MaybeDirectoryLockRef() const {
AssertIsOnBackgroundThread();
return ToMaybeRef(mDirectoryLockHandle.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);
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);
MOZ_DIAGNOSTIC_ASSERT(!isInList());
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();
// IPDL methods are only called by IPDL.
void ActorDestroy(ActorDestroyReason aWhy) override;
PBackgroundIDBDatabaseFileParent* AllocPBackgroundIDBDatabaseFileParent(
const IPCBlob& aIPCBlob) override;
bool DeallocPBackgroundIDBDatabaseFileParent(
PBackgroundIDBDatabaseFileParent* aActor) override;
already_AddRefed<PBackgroundIDBTransactionParent>
AllocPBackgroundIDBTransactionParent(
const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode,
const Durability& aDurability) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBTransactionConstructor(
PBackgroundIDBTransactionParent* aActor,
nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode,
const Durability& aDurability) 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),
/* aRequestId */ 0,
/* aLoggingSerialNumber */ 0) {}
~StartTransactionOp() override = default;
void RunOnConnectionThread() override;
nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
TransactionOpResult SendSuccessResult() override;
bool SendFailureResult(const TransactionOpResult& aResult) 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;
using Durability = IDBTransaction::Durability;
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;
const Durability mDurability;
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; }
Durability GetDurability() const { return mDurability; }
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,
Durability aDurability);
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(const int64_t aRequestId,
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 int64_t aRequestId,
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 int64_t& aRequestId, const RequestParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor(
PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId,
const RequestParams& aParams) override;
bool DeallocPBackgroundIDBRequestParent(
PBackgroundIDBRequestParent* aActor) override;
already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent(
const int64_t& aRequestId, const OpenCursorParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor(
PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId,
const OpenCursorParams& aParams) override;
public:
// This constructor is only called by Database.
NormalTransaction(
SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode,
TransactionBase::Durability aDurability,
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 int64_t& aRequestId, const RequestParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor(
PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId,
const RequestParams& aParams) override;
bool DeallocPBackgroundIDBRequestParent(
PBackgroundIDBRequestParent* aActor) override;
already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent(
const int64_t& aRequestId, const OpenCursorParams& aParams) override;
mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor(
PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId,
const OpenCursorParams& aParams) override;
};
class FactoryOp : public DatabaseOperationBase,
public LinkedListElement<FactoryOp> {
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 current thread.
// Next step is either SendingResults if opening initialization failed, or
// DirectoryOpenPending if the opening initialization succeeded.
Initial,
// Waiting for directory open allowed on the PBackground thread. The next
// step is either SendingResults if directory lock failed to acquire, or
// DirectoryWorkOpen if the factory operation is not tied up to a specific
// database, or DatabaseOpenPending otherwise.
DirectoryOpenPending,
// Waiting to do/doing directory work on the QuotaManager IO thread. Its
// next step is DirectoryWorkDone if directory work was successful or
// SendingResults if directory work failed.
DirectoryWorkOpen,
// Checking if database work can be started. If the database is not blocked
// by other factory operations then the next step is DatabaseWorkOpen.
// Otherwise the next step is DatabaseOpenPending.
DirectoryWorkDone,
// 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. If the VersionChangeOp is
// OpenDatabaseOp and it succeeded then the next state is
// DatabaseWorkVersionUpdate. Otherwise the next step is SendingResults.
DatabaseWorkVersionChange,
// Waiting to do/doing finalization work on the QuotaManager IO thread.
// Eventually the state will transition to SendingResults.
DatabaseWorkVersionUpdate,
// 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;
Maybe<ContentParentId> mContentParentId;
// Must be released on the main thread!
ClientDirectoryLockHandle mDirectoryLockHandle;
nsTArray<NotNull<RefPtr<FactoryOp>>> mBlocking;
nsTHashSet<RefPtr<FactoryOp>> mBlockedOn;
nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases;
const PrincipalInfo mPrincipalInfo;
OriginMetadata mOriginMetadata;
Maybe<nsString> mDatabaseName;
Maybe<nsCString> mDatabaseId;
Maybe<nsString> mDatabaseFilePath;
int64_t mDirectoryLockId;
const PersistenceType mPersistenceType;
State mState;
bool mWaitingForPermissionRetry;
bool mEnforcingQuota;
const bool mDeleting;
FlippedOnce<false> mInPrivateBrowsing;
public:
const nsACString& Origin() const {
AssertIsOnOwningThread();
return mOriginMetadata.mOrigin;
}
const Maybe<nsString>& DatabaseNameRef() const {
AssertIsOnOwningThread();
return mDatabaseName;
}
bool DatabaseFilePathIsKnown() const {
AssertIsOnOwningThread();
return mDatabaseFilePath.isSome();
}
const nsAString& DatabaseFilePath() const {
AssertIsOnOwningThread();
MOZ_ASSERT(mDatabaseFilePath);
return mDatabaseFilePath.ref();
}
nsresult DispatchThisAfterProcessingCurrentEvent(
nsCOMPtr<nsIEventTarget> aEventTarget);
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,
const Maybe<ContentParentId>& aContentParentId,
const PersistenceType aPersistenceType,
const PrincipalInfo& aPrincipalInfo,
const Maybe<nsString>& aDatabaseName, 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);
MOZ_DIAGNOSTIC_ASSERT(!isInList());
}
nsresult Open();
nsresult DirectoryOpen();
nsresult DirectoryWorkDone();
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 DoDirectoryWork() = 0;
virtual nsresult DatabaseOpen() = 0;
virtual nsresult DoDatabaseWork() = 0;
virtual nsresult BeginVersionChange() = 0;
virtual bool AreActorsAlive() = 0;
virtual nsresult DispatchToWorkThread() = 0;
virtual nsresult DoVersionUpdate() = 0;
// Should only be called by Run().
virtual void SendResults() = 0;
// Common nsIRunnable implementation that subclasses may not override.
NS_IMETHOD
Run() final;
void DirectoryLockAcquired(ClientDirectoryLockHandle aLockHandle);
void DirectoryLockFailed();
virtual void SendBlockedNotification() = 0;
private:
// Test whether this FactoryOp needs to wait for the given op.
bool MustWaitFor(const FactoryOp& aExistingOp);
void AddBlockingOp(FactoryOp& aOp) {
AssertIsOnOwningThread();
mBlocking.AppendElement(WrapNotNull(&aOp));
}
void AddBlockedOnOp(FactoryOp& aOp) {
AssertIsOnOwningThread();
mBlockedOn.Insert(&aOp);
}
void MaybeUnblock(FactoryOp& aOp) {
AssertIsOnOwningThread();
mBlockedOn.Remove(&aOp);
if (mBlockedOn.IsEmpty()) {
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
}
}
};
class FactoryRequestOp : public FactoryOp,
public PBackgroundIDBFactoryRequestParent {
protected:
const CommonFactoryRequestParams mCommonParams;
FactoryRequestOp(SafeRefPtr<Factory> aFactory,
const Maybe<ContentParentId>& aContentParentId,
const CommonFactoryRequestParams& aCommonParams,
bool aDeleting)
: FactoryOp(std::move(aFactory), aContentParentId,
aCommonParams.metadata().persistenceType(),
aCommonParams.principalInfo(),
Some(aCommonParams.metadata().name()), aDeleting),
mCommonParams(aCommonParams) {}
nsresult DoDirectoryWork() override;
// IPDL methods.
void ActorDestroy(ActorDestroyReason aWhy) override;
};
class OpenDatabaseOp final : public FactoryRequestOp {
friend class Database;
friend class VersionChangeTransaction;
class VersionChangeOp;
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;
MoveOnlyFunction<void()> mCompleteCallback;
uint32_t mTelemetryId;
public:
OpenDatabaseOp(SafeRefPtr<Factory> aFactory,
const Maybe<ContentParentId>& aContentParentId,
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;
nsresult DoVersionUpdate() 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(),
/* aRequestId */ 0, 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;
TransactionOpResult SendSuccessResult() override;
bool SendFailureResult(const TransactionOpResult& aResult) override;
void Cleanup() override;
};
class DeleteDatabaseOp final : public FactoryRequestOp {
class VersionChangeOp;
nsString mDatabaseDirectoryPath;
nsString mDatabaseFilenameBase;
uint64_t mPreviousVersion;
public:
DeleteDatabaseOp(SafeRefPtr<Factory> aFactory,
const Maybe<ContentParentId>& aContentParentId,
const CommonFactoryRequestParams& aParams)
: FactoryRequestOp(std::move(aFactory), aContentParentId, 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;
nsresult DoVersionUpdate() 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 GetDatabasesOp final : public FactoryOp {
nsTHashMap<nsStringHashKey, DatabaseMetadata> mDatabaseMetadataTable;
nsTArray<DatabaseMetadata> mDatabaseMetadataArray;
Factory::GetDatabasesResolver mResolver;
public:
GetDatabasesOp(SafeRefPtr<Factory> aFactory,
const Maybe<ContentParentId>& aContentParentId,
const PersistenceType aPersistenceType,
const PrincipalInfo& aPrincipalInfo,
Factory::GetDatabasesResolver&& aResolver)
: FactoryOp(std::move(aFactory), aContentParentId, aPersistenceType,
aPrincipalInfo, Nothing(), /* aDeleting */ false),
mResolver(std::move(aResolver)) {}
private:
~GetDatabasesOp() override = default;
nsresult DatabasesNotAvailable();
nsresult DoDirectoryWork() override;
nsresult DatabaseOpen() override;
nsresult DoDatabaseWork() override;
nsresult BeginVersionChange() override;
bool AreActorsAlive() override;
void SendBlockedNotification() override;
nsresult DispatchToWorkThread() override;
nsresult DoVersionUpdate() override;
void SendResults() override;
};
class VersionChangeTransactionOp : public TransactionDatabaseOperationBase {
public:
void Cleanup() override;
protected:
explicit VersionChangeTransactionOp(
SafeRefPtr<VersionChangeTransaction> aTransaction)
: TransactionDatabaseOperationBase(std::move(aTransaction),
/* aRequestId */ 0) {}
~VersionChangeTransactionOp() override = default;
private:
TransactionOpResult SendSuccessResult() override;
bool SendFailureResult(const TransactionOpResult& aResult) override;
};
class CreateObjectStoreOp final : public