Bug 1816170 - Disable perftest-on-autoland cron. r=aglavic
[gecko.git] / dom / indexedDB / ActorsParent.cpp
blob87c08ab9147f887df3831cdc9e599ef4b4293c8b
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ActorsParent.h"
9 #include <inttypes.h>
10 #include <math.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <algorithm>
14 #include <cstdint>
15 #include <functional>
16 #include <iterator>
17 #include <new>
18 #include <numeric>
19 #include <tuple>
20 #include <type_traits>
21 #include <utility>
22 #include "ActorsParentCommon.h"
23 #include "CrashAnnotations.h"
24 #include "DatabaseFileInfo.h"
25 #include "DatabaseFileManager.h"
26 #include "DatabaseFileManagerImpl.h"
27 #include "DBSchema.h"
28 #include "ErrorList.h"
29 #include "IDBCursorType.h"
30 #include "IDBObjectStore.h"
31 #include "IDBTransaction.h"
32 #include "IndexedDBCommon.h"
33 #include "IndexedDatabaseInlines.h"
34 #include "IndexedDatabaseManager.h"
35 #include "IndexedDBCipherKeyManager.h"
36 #include "KeyPath.h"
37 #include "MainThreadUtils.h"
38 #include "ProfilerHelpers.h"
39 #include "ReportInternalError.h"
40 #include "SafeRefPtr.h"
41 #include "SchemaUpgrades.h"
42 #include "chrome/common/ipc_channel.h"
43 #include "ipc/IPCMessageUtils.h"
44 #include "js/RootingAPI.h"
45 #include "js/StructuredClone.h"
46 #include "js/Value.h"
47 #include "jsapi.h"
48 #include "mozIStorageAsyncConnection.h"
49 #include "mozIStorageConnection.h"
50 #include "mozIStorageFunction.h"
51 #include "mozIStorageProgressHandler.h"
52 #include "mozIStorageService.h"
53 #include "mozIStorageStatement.h"
54 #include "mozIStorageValueArray.h"
55 #include "mozStorageCID.h"
56 #include "mozStorageHelper.h"
57 #include "mozilla/Algorithm.h"
58 #include "mozilla/ArrayAlgorithm.h"
59 #include "mozilla/ArrayIterator.h"
60 #include "mozilla/Assertions.h"
61 #include "mozilla/Atomics.h"
62 #include "mozilla/Attributes.h"
63 #include "mozilla/Casting.h"
64 #include "mozilla/CondVar.h"
65 #include "mozilla/DebugOnly.h"
66 #include "mozilla/EndianUtils.h"
67 #include "mozilla/ErrorNames.h"
68 #include "mozilla/ErrorResult.h"
69 #include "mozilla/InitializedOnce.h"
70 #include "mozilla/Logging.h"
71 #include "mozilla/MacroForEach.h"
72 #include "mozilla/Maybe.h"
73 #include "mozilla/Monitor.h"
74 #include "mozilla/Mutex.h"
75 #include "mozilla/NotNull.h"
76 #include "mozilla/Preferences.h"
77 #include "mozilla/ProfilerLabels.h"
78 #include "mozilla/RefCountType.h"
79 #include "mozilla/RefCounted.h"
80 #include "mozilla/RemoteLazyInputStreamParent.h"
81 #include "mozilla/RemoteLazyInputStreamStorage.h"
82 #include "mozilla/Result.h"
83 #include "mozilla/ResultExtensions.h"
84 #include "mozilla/SchedulerGroup.h"
85 #include "mozilla/Scoped.h"
86 #include "mozilla/SnappyCompressOutputStream.h"
87 #include "mozilla/SpinEventLoopUntil.h"
88 #include "mozilla/StaticPtr.h"
89 #include "mozilla/TaskCategory.h"
90 #include "mozilla/TimeStamp.h"
91 #include "mozilla/UniquePtr.h"
92 #include "mozilla/Unused.h"
93 #include "mozilla/Variant.h"
94 #include "mozilla/dom/BlobImpl.h"
95 #include "mozilla/dom/ContentParent.h"
96 #include "mozilla/dom/FileBlobImpl.h"
97 #include "mozilla/dom/FlippedOnce.h"
98 #include "mozilla/dom/IDBCursorBinding.h"
99 #include "mozilla/dom/IPCBlob.h"
100 #include "mozilla/dom/IPCBlobUtils.h"
101 #include "mozilla/dom/IndexedDatabase.h"
102 #include "mozilla/dom/Nullable.h"
103 #include "mozilla/dom/PContentParent.h"
104 #include "mozilla/dom/ScriptSettings.h"
105 #include "mozilla/dom/indexedDB/IDBResult.h"
106 #include "mozilla/dom/indexedDB/Key.h"
107 #include "mozilla/dom/indexedDB/PBackgroundIDBCursor.h"
108 #include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
109 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabase.h"
110 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
111 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
112 #include "mozilla/dom/indexedDB/PBackgroundIDBFactory.h"
113 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
114 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
115 #include "mozilla/dom/indexedDB/PBackgroundIDBRequest.h"
116 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
117 #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
118 #include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
119 #include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
120 #include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
121 #include "mozilla/dom/ipc/IdType.h"
122 #include "mozilla/dom/quota/CachingDatabaseConnection.h"
123 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
124 #include "mozilla/dom/quota/Client.h"
125 #include "mozilla/dom/quota/ClientImpl.h"
126 #include "mozilla/dom/quota/DirectoryLock.h"
127 #include "mozilla/dom/quota/DecryptingInputStream_impl.h"
128 #include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
129 #include "mozilla/dom/quota/FileStreams.h"
130 #include "mozilla/dom/quota/OriginScope.h"
131 #include "mozilla/dom/quota/PersistenceType.h"
132 #include "mozilla/dom/quota/QuotaCommon.h"
133 #include "mozilla/dom/quota/QuotaManager.h"
134 #include "mozilla/dom/quota/QuotaObject.h"
135 #include "mozilla/dom/quota/ResultExtensions.h"
136 #include "mozilla/dom/quota/UsageInfo.h"
137 #include "mozilla/fallible.h"
138 #include "mozilla/ipc/BackgroundParent.h"
139 #include "mozilla/ipc/BackgroundUtils.h"
140 #include "mozilla/ipc/InputStreamParams.h"
141 #include "mozilla/ipc/PBackgroundParent.h"
142 #include "mozilla/ipc/PBackgroundSharedTypes.h"
143 #include "mozilla/ipc/ProtocolUtils.h"
144 #include "mozilla/mozalloc.h"
145 #include "mozilla/storage/Variant.h"
146 #include "nsBaseHashtable.h"
147 #include "nsCOMPtr.h"
148 #include "nsClassHashtable.h"
149 #include "nsContentUtils.h"
150 #include "nsTHashMap.h"
151 #include "nsDebug.h"
152 #include "nsError.h"
153 #include "nsEscape.h"
154 #include "nsHashKeys.h"
155 #include "nsIAsyncInputStream.h"
156 #include "nsID.h"
157 #include "nsIDirectoryEnumerator.h"
158 #include "nsIEventTarget.h"
159 #include "nsIFile.h"
160 #include "nsIFileProtocolHandler.h"
161 #include "nsIFileStreams.h"
162 #include "nsIFileURL.h"
163 #include "nsIInputStream.h"
164 #include "nsIOutputStream.h"
165 #include "nsIProtocolHandler.h"
166 #include "nsIRunnable.h"
167 #include "nsISupports.h"
168 #include "nsISupportsPriority.h"
169 #include "nsISupportsUtils.h"
170 #include "nsIThread.h"
171 #include "nsIThreadInternal.h"
172 #include "nsITimer.h"
173 #include "nsIURIMutator.h"
174 #include "nsIVariant.h"
175 #include "nsLiteralString.h"
176 #include "nsNetCID.h"
177 #include "nsPrintfCString.h"
178 #include "nsProxyRelease.h"
179 #include "nsServiceManagerUtils.h"
180 #include "nsStreamUtils.h"
181 #include "nsString.h"
182 #include "nsStringFlags.h"
183 #include "nsStringFwd.h"
184 #include "nsTArray.h"
185 #include "nsTHashSet.h"
186 #include "nsTHashtable.h"
187 #include "nsTLiteralString.h"
188 #include "nsTStringRepr.h"
189 #include "nsThreadPool.h"
190 #include "nsThreadUtils.h"
191 #include "nscore.h"
192 #include "prinrval.h"
193 #include "prio.h"
194 #include "prsystem.h"
195 #include "prthread.h"
196 #include "prtime.h"
197 #include "prtypes.h"
198 #include "snappy/snappy.h"
200 struct JSContext;
201 class JSObject;
202 template <class T>
203 class nsPtrHashKey;
205 #define IDB_DEBUG_LOG(_args) \
206 MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), LogLevel::Debug, _args)
208 #if defined(MOZ_WIDGET_ANDROID)
209 # define IDB_MOBILE
210 #endif
212 // Helper macros to reduce assertion verbosity
213 // AUUF == ASSERT_UNREACHABLE_UNLESS_FUZZING
214 #ifdef DEBUG
215 # ifdef FUZZING
216 # define NS_AUUF_OR_WARN(...) NS_WARNING(__VA_ARGS__)
217 # else
218 # define NS_AUUF_OR_WARN(...) MOZ_ASSERT(false, __VA_ARGS__)
219 # endif
220 # define NS_AUUF_OR_WARN_IF(cond) \
221 [](bool aCond) { \
222 if (MOZ_UNLIKELY(aCond)) { \
223 NS_AUUF_OR_WARN(#cond); \
225 return aCond; \
226 }((cond))
227 #else
228 # define NS_AUUF_OR_WARN(...) \
229 do { \
230 } while (false)
231 # define NS_AUUF_OR_WARN_IF(cond) static_cast<bool>(cond)
232 #endif
234 namespace mozilla {
236 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
237 PR_Close);
239 namespace dom::indexedDB {
241 using namespace mozilla::dom::quota;
242 using namespace mozilla::ipc;
243 using mozilla::dom::quota::Client;
245 namespace {
247 class ConnectionPool;
248 class Database;
249 struct DatabaseActorInfo;
250 class DatabaseFile;
251 class DatabaseLoggingInfo;
252 class DatabaseMaintenance;
253 class Factory;
254 class Maintenance;
255 class OpenDatabaseOp;
256 class TransactionBase;
257 class TransactionDatabaseOperationBase;
258 class VersionChangeTransaction;
259 template <bool StatementHasIndexKeyBindings>
260 struct ValuePopulateResponseHelper;
262 /*******************************************************************************
263 * Constants
264 ******************************************************************************/
266 const int32_t kStorageProgressGranularity = 1000;
268 // Changing the value here will override the page size of new databases only.
269 // A journal mode change and VACUUM are needed to change existing databases, so
270 // the best way to do that is to use the schema version upgrade mechanism.
271 const uint32_t kSQLitePageSizeOverride =
272 #ifdef IDB_MOBILE
273 2048;
274 #else
275 4096;
276 #endif
278 static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
279 (kSQLitePageSizeOverride % 2 == 0 &&
280 kSQLitePageSizeOverride >= 512 &&
281 kSQLitePageSizeOverride <= 65536),
282 "Must be 0 (disabled) or a power of 2 between 512 and 65536!");
284 // Set to -1 to use SQLite's default, 0 to disable, or some positive number to
285 // enforce a custom limit.
286 const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
288 // Set to some multiple of the page size to grow the database in larger chunks.
289 const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
291 static_assert(kSQLiteGrowthIncrement >= 0 &&
292 kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
293 kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
294 "Must be 0 (disabled) or a positive multiple of the page size!");
296 // The maximum number of threads that can be used for database activity at a
297 // single time.
298 const uint32_t kMaxConnectionThreadCount = 20;
300 static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");
302 // The maximum number of threads to keep when idle. Threads that become idle in
303 // excess of this number will be shut down immediately.
304 const uint32_t kMaxIdleConnectionThreadCount = 2;
306 static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
307 "Idle thread limit must be less than total thread limit!");
309 // The length of time that database connections will be held open after all
310 // transactions have completed before doing idle maintenance.
311 const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds
313 // The length of time that database connections will be held open after all
314 // transactions and maintenance have completed.
315 const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds
317 // The length of time that idle threads will stay alive before being shut down.
318 const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds
320 #define SAVEPOINT_CLAUSE "SAVEPOINT sp;"_ns
322 // For efficiency reasons, kEncryptedStreamBlockSize must be a multiple of large
323 // 4k disk sectors.
324 static_assert(kEncryptedStreamBlockSize % 4096 == 0);
325 // Similarly, the file copy buffer size must be a multiple of the encrypted
326 // block size.
327 static_assert(kFileCopyBufferSize % kEncryptedStreamBlockSize == 0);
329 constexpr auto kFileManagerDirectoryNameSuffix = u".files"_ns;
330 constexpr auto kSQLiteSuffix = u".sqlite"_ns;
331 constexpr auto kSQLiteJournalSuffix = u".sqlite-journal"_ns;
332 constexpr auto kSQLiteSHMSuffix = u".sqlite-shm"_ns;
333 constexpr auto kSQLiteWALSuffix = u".sqlite-wal"_ns;
335 constexpr auto kPermissionStringBase = "indexedDB-chrome-"_ns;
336 constexpr auto kPermissionReadSuffix = "-read"_ns;
337 constexpr auto kPermissionWriteSuffix = "-write"_ns;
339 // The following constants define all names of binding parameters in statements,
340 // where they are bound by name. This should include all parameter names which
341 // are bound by name. Binding may be done by index when the statement definition
342 // and binding are done in the same local scope, and no other reasons prevent
343 // using the indexes (e.g. multiple statement variants with differing number or
344 // order of parameters). Neither the styles of specifying parameter names
345 // (literally vs. via these constants) nor the binding styles (by index vs. by
346 // name) should not be mixed for the same statement. The decision must be made
347 // for each statement based on the proximity of statement and binding calls.
348 constexpr auto kStmtParamNameCurrentKey = "current_key"_ns;
349 constexpr auto kStmtParamNameRangeBound = "range_bound"_ns;
350 constexpr auto kStmtParamNameObjectStorePosition = "object_store_position"_ns;
351 constexpr auto kStmtParamNameLowerKey = "lower_key"_ns;
352 constexpr auto kStmtParamNameUpperKey = "upper_key"_ns;
353 constexpr auto kStmtParamNameKey = "key"_ns;
354 constexpr auto kStmtParamNameObjectStoreId = "object_store_id"_ns;
355 constexpr auto kStmtParamNameIndexId = "index_id"_ns;
356 // TODO: Maybe the uses of kStmtParamNameId should be replaced by more
357 // specific constants such as kStmtParamNameObjectStoreId.
358 constexpr auto kStmtParamNameId = "id"_ns;
359 constexpr auto kStmtParamNameValue = "value"_ns;
360 constexpr auto kStmtParamNameObjectDataKey = "object_data_key"_ns;
361 constexpr auto kStmtParamNameIndexDataValues = "index_data_values"_ns;
362 constexpr auto kStmtParamNameData = "data"_ns;
363 constexpr auto kStmtParamNameFileIds = "file_ids"_ns;
364 constexpr auto kStmtParamNameValueLocale = "value_locale"_ns;
365 constexpr auto kStmtParamNameLimit = "limit"_ns;
367 // The following constants define some names of columns in tables, which are
368 // referred to in remote locations, e.g. in calls to
369 // GetBindingClauseForKeyRange.
370 constexpr auto kColumnNameKey = "key"_ns;
371 constexpr auto kColumnNameValue = "value"_ns;
372 constexpr auto kColumnNameAliasSortKey = "sort_column"_ns;
374 // SQL fragments used at multiple locations.
375 constexpr auto kOpenLimit = " LIMIT "_ns;
377 // The deletion marker file is created before RemoveDatabaseFilesAndDirectory
378 // begins deleting a database. It is removed as the last step of deletion. If a
379 // deletion marker file is found when initializing the origin, the deletion
380 // routine is run again to ensure that the database and all of its related files
381 // are removed. The primary goal of this mechanism is to avoid situations where
382 // a database has been partially deleted, leading to inconsistent state for the
383 // origin.
384 constexpr auto kIdbDeletionMarkerFilePrefix = u"idb-deleting-"_ns;
386 const uint32_t kDeleteTimeoutMs = 1000;
388 #ifdef DEBUG
390 const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
391 const uint32_t kDEBUGThreadSleepMS = 0;
393 const int32_t kDEBUGTransactionThreadPriority =
394 nsISupportsPriority::PRIORITY_NORMAL;
395 const uint32_t kDEBUGTransactionThreadSleepMS = 0;
397 #endif
399 /*******************************************************************************
400 * Metadata classes
401 ******************************************************************************/
403 // Can be instantiated either on the QuotaManager IO thread or on a
404 // versionchange transaction thread. These threads can never race so this is
405 // totally safe.
406 struct FullIndexMetadata {
407 IndexMetadata mCommonMetadata = {0, nsString(), KeyPath(0), nsCString(),
408 false, false, false};
410 FlippedOnce<false> mDeleted;
412 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
414 private:
415 ~FullIndexMetadata() = default;
418 using IndexTable = nsTHashMap<nsUint64HashKey, SafeRefPtr<FullIndexMetadata>>;
420 // Can be instantiated either on the QuotaManager IO thread or on a
421 // versionchange transaction thread. These threads can never race so this is
422 // totally safe.
423 struct FullObjectStoreMetadata {
424 ObjectStoreMetadata mCommonMetadata;
425 IndexTable mIndexes;
427 // The auto increment ids are touched on both the background thread and the
428 // transaction I/O thread, and they must be kept in sync, so we need a mutex
429 // to protect them.
430 struct AutoIncrementIds {
431 int64_t next;
432 int64_t committed;
434 DataMutex<AutoIncrementIds> mAutoIncrementIds;
436 FlippedOnce<false> mDeleted;
438 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
440 bool HasLiveIndexes() const;
442 FullObjectStoreMetadata(ObjectStoreMetadata aCommonMetadata,
443 const AutoIncrementIds& aAutoIncrementIds)
444 : mCommonMetadata{std::move(aCommonMetadata)},
445 mAutoIncrementIds{AutoIncrementIds{aAutoIncrementIds},
446 "FullObjectStoreMetadata"} {}
448 private:
449 ~FullObjectStoreMetadata() = default;
452 using ObjectStoreTable =
453 nsTHashMap<nsUint64HashKey, SafeRefPtr<FullObjectStoreMetadata>>;
455 static_assert(
456 std::is_same_v<IndexOrObjectStoreId,
457 std::remove_cv_t<std::remove_reference_t<
458 decltype(std::declval<const ObjectStoreGetParams&>()
459 .objectStoreId())>>>);
460 static_assert(
461 std::is_same_v<
462 IndexOrObjectStoreId,
463 std::remove_cv_t<std::remove_reference_t<
464 decltype(std::declval<const IndexGetParams&>().objectStoreId())>>>);
466 struct FullDatabaseMetadata final : AtomicSafeRefCounted<FullDatabaseMetadata> {
467 DatabaseMetadata mCommonMetadata;
468 nsCString mDatabaseId;
469 nsString mFilePath;
470 ObjectStoreTable mObjectStores;
472 IndexOrObjectStoreId mNextObjectStoreId = 0;
473 IndexOrObjectStoreId mNextIndexId = 0;
475 public:
476 explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
477 : mCommonMetadata(aCommonMetadata) {
478 AssertIsOnBackgroundThread();
481 [[nodiscard]] SafeRefPtr<FullDatabaseMetadata> Duplicate() const;
483 MOZ_DECLARE_REFCOUNTED_TYPENAME(FullDatabaseMetadata)
486 template <class Enumerable>
487 auto MatchMetadataNameOrId(const Enumerable& aEnumerable,
488 IndexOrObjectStoreId aId,
489 Maybe<const nsAString&> aName = Nothing()) {
490 AssertIsOnBackgroundThread();
491 MOZ_ASSERT(aId);
493 const auto it = std::find_if(
494 aEnumerable.cbegin(), aEnumerable.cend(),
495 [aId, aName](const auto& entry) {
496 MOZ_ASSERT(entry.GetKey() != 0);
498 const auto& value = entry.GetData();
499 MOZ_ASSERT(value);
501 return !value->mDeleted &&
502 (aId == value->mCommonMetadata.id() ||
503 (aName && *aName == value->mCommonMetadata.name()));
506 return ToMaybeRef(it != aEnumerable.cend() ? it->GetData().unsafeGetRawPtr()
507 : nullptr);
510 /*******************************************************************************
511 * SQLite functions
512 ******************************************************************************/
514 // WARNING: the hash function used for the database name must not change.
515 // That's why this function exists separately from mozilla::HashString(), even
516 // though it is (at the time of writing) equivalent. See bug 780408 and bug
517 // 940315 for details.
518 uint32_t HashName(const nsAString& aName) {
519 struct Helper {
520 static uint32_t RotateBitsLeft32(uint32_t aValue, uint8_t aBits) {
521 MOZ_ASSERT(aBits < 32);
522 return (aValue << aBits) | (aValue >> (32 - aBits));
526 static const uint32_t kGoldenRatioU32 = 0x9e3779b9u;
528 return std::accumulate(aName.BeginReading(), aName.EndReading(), uint32_t(0),
529 [](uint32_t hash, char16_t ch) {
530 return kGoldenRatioU32 *
531 (Helper::RotateBitsLeft32(hash, 5) ^ ch);
535 nsresult ClampResultCode(nsresult aResultCode) {
536 if (NS_SUCCEEDED(aResultCode) ||
537 NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) {
538 return aResultCode;
541 switch (aResultCode) {
542 case NS_ERROR_FILE_NO_DEVICE_SPACE:
543 return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
544 case NS_ERROR_STORAGE_CONSTRAINT:
545 return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
546 default:
547 #ifdef DEBUG
548 nsPrintfCString message("Converting non-IndexedDB error code (0x%" PRIX32
549 ") to "
550 "NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
551 static_cast<uint32_t>(aResultCode));
552 NS_WARNING(message.get());
553 #else
555 #endif
558 IDB_REPORT_INTERNAL_ERR();
559 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
562 nsAutoString GetDatabaseFilenameBase(const nsAString& aDatabaseName) {
563 nsAutoString databaseFilenameBase;
565 // WARNING: do not change this hash function. See the comment in HashName()
566 // for details.
567 databaseFilenameBase.AppendInt(HashName(aDatabaseName));
569 nsAutoCString escapedName;
570 if (!NS_Escape(NS_ConvertUTF16toUTF8(aDatabaseName), escapedName,
571 url_XPAlphas)) {
572 MOZ_CRASH("Can't escape database name!");
575 const char* forwardIter = escapedName.BeginReading();
576 const char* backwardIter = escapedName.EndReading() - 1;
578 nsAutoCString substring;
579 while (forwardIter <= backwardIter && substring.Length() < 21) {
580 if (substring.Length() % 2) {
581 substring.Append(*backwardIter--);
582 } else {
583 substring.Append(*forwardIter++);
587 databaseFilenameBase.AppendASCII(substring.get(), substring.Length());
589 return databaseFilenameBase;
592 Result<nsCOMPtr<nsIFileURL>, nsresult> GetDatabaseFileURL(
593 nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
594 const Maybe<CipherKey>& aMaybeKey) {
595 MOZ_ASSERT(aDirectoryLockId >= -1);
597 QM_TRY_INSPECT(
598 const auto& protocolHandler,
599 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIProtocolHandler>,
600 MOZ_SELECT_OVERLOAD(do_GetService),
601 NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"));
603 QM_TRY_INSPECT(const auto& fileHandler,
604 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFileProtocolHandler>,
605 MOZ_SELECT_OVERLOAD(do_QueryInterface),
606 protocolHandler));
608 QM_TRY_INSPECT(const auto& mutator, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
609 nsCOMPtr<nsIURIMutator>, fileHandler,
610 NewFileURIMutator, &aDatabaseFile));
612 // aDirectoryLockId should only be -1 when we are called
613 // - from DatabaseFileManager::InitDirectory when the temporary storage
614 // hasn't been initialized yet. At that time, the in-memory objects (e.g.
615 // OriginInfo) are only being created so it doesn't make sense to tunnel
616 // quota information to QuotaVFS to get corresponding QuotaObject instances
617 // for SQLite files.
618 // - from DeleteDatabaseOp::LoadPreviousVersion, since this might require
619 // temporarily exceeding the quota limit before the database can be
620 // deleted.
621 const auto directoryLockIdClause =
622 aDirectoryLockId >= 0
623 ? "&directoryLockId="_ns + IntToCString(aDirectoryLockId)
624 : EmptyCString();
626 const auto keyClause = [&aMaybeKey] {
627 nsAutoCString keyClause;
628 if (aMaybeKey) {
629 keyClause.AssignLiteral("&key=");
630 for (uint8_t byte : IndexedDBCipherStrategy::SerializeKey(*aMaybeKey)) {
631 keyClause.AppendPrintf("%02x", byte);
634 return keyClause;
635 }();
637 QM_TRY_UNWRAP(auto result, ([&mutator, &directoryLockIdClause, &keyClause] {
638 nsCOMPtr<nsIFileURL> result;
639 nsresult rv = NS_MutateURI(mutator)
640 .SetQuery("cache=private"_ns +
641 directoryLockIdClause + keyClause)
642 .Finalize(result);
643 return NS_SUCCEEDED(rv)
644 ? Result<nsCOMPtr<nsIFileURL>, nsresult>{result}
645 : Err(rv);
646 }()));
648 return result;
651 nsresult SetDefaultPragmas(mozIStorageConnection& aConnection) {
652 MOZ_ASSERT(!NS_IsMainThread());
654 static constexpr auto kBuiltInPragmas =
655 // We use foreign keys in DEBUG builds only because there is a performance
656 // cost to using them.
657 "PRAGMA foreign_keys = "
658 #ifdef DEBUG
659 "ON"
660 #else
661 "OFF"
662 #endif
665 // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
666 // instead it fires only the insert trigger. This confuses the update
667 // refcount function. This behavior changes with enabled recursive
668 // triggers, so the statement fires the delete trigger first and then the
669 // insert trigger.
670 "PRAGMA recursive_triggers = ON;"
672 // We aggressively truncate the database file when idle so don't bother
673 // overwriting the WAL with 0 during active periods.
674 "PRAGMA secure_delete = OFF;"_ns;
676 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(kBuiltInPragmas)));
678 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsAutoCString{
679 "PRAGMA synchronous = "_ns +
680 (IndexedDatabaseManager::FullSynchronous() ? "FULL"_ns : "NORMAL"_ns) +
681 ";"_ns})));
683 #ifndef IDB_MOBILE
684 if (kSQLiteGrowthIncrement) {
685 // This is just an optimization so ignore the failure if the disk is
686 // currently too full.
687 QM_TRY(QM_OR_ELSE_WARN_IF(
688 // Expression.
689 MOZ_TO_RESULT(
690 aConnection.SetGrowthIncrement(kSQLiteGrowthIncrement, ""_ns)),
691 // Predicate.
692 IsSpecificError<NS_ERROR_FILE_TOO_BIG>,
693 // Fallback.
694 ErrToDefaultOk<>));
696 #endif // IDB_MOBILE
698 return NS_OK;
701 nsresult SetJournalMode(mozIStorageConnection& aConnection) {
702 MOZ_ASSERT(!NS_IsMainThread());
704 // Try enabling WAL mode. This can fail in various circumstances so we have to
705 // check the results here.
706 constexpr auto journalModeQueryStart = "PRAGMA journal_mode = "_ns;
707 constexpr auto journalModeWAL = "wal"_ns;
709 QM_TRY_INSPECT(const auto& stmt,
710 CreateAndExecuteSingleStepStatement(
711 aConnection, journalModeQueryStart + journalModeWAL));
713 QM_TRY_INSPECT(
714 const auto& journalMode,
715 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt, GetUTF8String, 0));
717 if (journalMode.Equals(journalModeWAL)) {
718 // WAL mode successfully enabled. Maybe set limits on its size here.
719 if (kMaxWALPages >= 0) {
720 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
721 "PRAGMA wal_autocheckpoint = "_ns + IntToCString(kMaxWALPages))));
723 } else {
724 NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
725 #ifdef IDB_MOBILE
726 QM_TRY(MOZ_TO_RESULT(
727 aConnection.ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns)));
728 #endif
731 return NS_OK;
734 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> OpenDatabase(
735 mozIStorageService& aStorageService, nsIFileURL& aFileURL,
736 const uint32_t aTelemetryId = 0) {
737 const nsAutoCString telemetryFilename =
738 aTelemetryId ? "indexedDB-"_ns + IntToCString(aTelemetryId) +
739 NS_ConvertUTF16toUTF8(kSQLiteSuffix)
740 : nsAutoCString();
742 QM_TRY_UNWRAP(auto connection,
743 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
744 nsCOMPtr<mozIStorageConnection>, aStorageService,
745 OpenDatabaseWithFileURL, &aFileURL, telemetryFilename,
746 mozIStorageService::CONNECTION_DEFAULT));
748 return WrapMovingNotNull(std::move(connection));
751 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
752 OpenDatabaseAndHandleBusy(mozIStorageService& aStorageService,
753 nsIFileURL& aFileURL,
754 const uint32_t aTelemetryId = 0) {
755 MOZ_ASSERT(!NS_IsMainThread());
756 MOZ_ASSERT(!IsOnBackgroundThread());
758 using ConnectionType = Maybe<MovingNotNull<nsCOMPtr<mozIStorageConnection>>>;
760 QM_TRY_UNWRAP(auto connection,
761 QM_OR_ELSE_WARN_IF(
762 // Expression
763 OpenDatabase(aStorageService, aFileURL, aTelemetryId)
764 .map([](auto connection) -> ConnectionType {
765 return Some(std::move(connection));
767 // Predicate.
768 IsSpecificError<NS_ERROR_STORAGE_BUSY>,
769 // Fallback.
770 ErrToDefaultOk<ConnectionType>));
772 if (connection.isNothing()) {
773 #ifdef DEBUG
775 nsCString path;
776 MOZ_ALWAYS_SUCCEEDS(aFileURL.GetFileName(path));
778 nsPrintfCString message(
779 "Received NS_ERROR_STORAGE_BUSY when attempting to open database "
780 "'%s', retrying for up to 10 seconds",
781 path.get());
782 NS_WARNING(message.get());
784 #endif
786 // Another thread must be checkpointing the WAL. Wait up to 10 seconds for
787 // that to complete.
788 const TimeStamp start = TimeStamp::NowLoRes();
790 do {
791 PR_Sleep(PR_MillisecondsToInterval(100));
793 QM_TRY_UNWRAP(connection,
794 QM_OR_ELSE_WARN_IF(
795 // Expression.
796 OpenDatabase(aStorageService, aFileURL, aTelemetryId)
797 .map([](auto connection) -> ConnectionType {
798 return Some(std::move(connection));
800 // Predicate.
801 ([&start](nsresult aValue) {
802 return aValue == NS_ERROR_STORAGE_BUSY &&
803 TimeStamp::NowLoRes() - start <=
804 TimeDuration::FromSeconds(10);
806 // Fallback.
807 ErrToDefaultOk<ConnectionType>));
808 } while (connection.isNothing());
811 return connection.extract();
814 // Returns true if a given nsIFile exists and is a directory. Returns false if
815 // it doesn't exist. Returns an error if it exists, but is not a directory, or
816 // any other error occurs.
817 Result<bool, nsresult> ExistsAsDirectory(nsIFile& aDirectory) {
818 QM_TRY_INSPECT(const bool& exists,
819 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
821 if (exists) {
822 QM_TRY_INSPECT(const bool& isDirectory,
823 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
825 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE));
828 return exists;
831 constexpr nsresult mapNoDeviceSpaceError(nsresult aRv) {
832 if (aRv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
833 // mozstorage translates SQLITE_FULL to
834 // NS_ERROR_FILE_NO_DEVICE_SPACE, which we know better as
835 // NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
836 return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
838 return aRv;
841 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
842 CreateStorageConnection(nsIFile& aDBFile, nsIFile& aFMDirectory,
843 const nsAString& aName, const nsACString& aOrigin,
844 const int64_t aDirectoryLockId,
845 const uint32_t aTelemetryId,
846 const Maybe<CipherKey>& aMaybeKey) {
847 AssertIsOnIOThread();
848 MOZ_ASSERT(aDirectoryLockId >= -1);
850 AUTO_PROFILER_LABEL("CreateStorageConnection", DOM);
852 QM_TRY_INSPECT(const auto& dbFileUrl,
853 GetDatabaseFileURL(aDBFile, aDirectoryLockId, aMaybeKey));
855 QM_TRY_INSPECT(const auto& storageService,
856 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
857 MOZ_SELECT_OVERLOAD(do_GetService),
858 MOZ_STORAGE_SERVICE_CONTRACTID));
860 QM_TRY_UNWRAP(
861 auto connection,
862 QM_OR_ELSE_WARN_IF(
863 // Expression.
864 OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId)
865 .map([](auto connection) -> nsCOMPtr<mozIStorageConnection> {
866 return std::move(connection).unwrapBasePtr();
868 // Predicate.
869 ([&aName](nsresult aValue) {
870 // If we're just opening the database during origin initialization,
871 // then we don't want to erase any files. The failure here will fail
872 // origin initialization too.
873 return IsDatabaseCorruptionError(aValue) && !aName.IsVoid();
875 // Fallback.
876 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
878 if (!connection) {
879 // XXX Shouldn't we also update quota usage?
881 // Nuke the database file.
882 QM_TRY(MOZ_TO_RESULT(aDBFile.Remove(false)));
883 QM_TRY_INSPECT(const bool& existsAsDirectory,
884 ExistsAsDirectory(aFMDirectory));
886 if (existsAsDirectory) {
887 QM_TRY(MOZ_TO_RESULT(aFMDirectory.Remove(true)));
890 QM_TRY_UNWRAP(connection, OpenDatabaseAndHandleBusy(
891 *storageService, *dbFileUrl, aTelemetryId));
894 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection)));
895 QM_TRY(MOZ_TO_RESULT(connection->EnableModule("filesystem"_ns)));
897 // Check to make sure that the database schema is correct.
898 QM_TRY_INSPECT(const int32_t& schemaVersion,
899 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion));
901 // Unknown schema will fail origin initialization too.
902 QM_TRY(OkIf(schemaVersion || !aName.IsVoid()),
903 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
904 IDB_WARNING("Unable to open IndexedDB database, schema is not set!");
907 QM_TRY(
908 OkIf(schemaVersion <= kSQLiteSchemaVersion),
909 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
910 IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
913 bool journalModeSet = false;
915 if (schemaVersion != kSQLiteSchemaVersion) {
916 const bool newDatabase = !schemaVersion;
918 if (newDatabase) {
919 // Set the page size first.
920 const auto sqlitePageSizeOverride =
921 aMaybeKey ? 8192 : kSQLitePageSizeOverride;
922 if (sqlitePageSizeOverride) {
923 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
924 "PRAGMA page_size = %" PRIu32 ";", sqlitePageSizeOverride))));
927 // We have to set the auto_vacuum mode before opening a transaction.
928 QM_TRY((MOZ_TO_RESULT_INVOKE_MEMBER(
929 connection, ExecuteSimpleSQL,
930 #ifdef IDB_MOBILE
931 // Turn on full auto_vacuum mode to reclaim disk space on
932 // mobile devices (at the cost of some COMMIT speed).
933 "PRAGMA auto_vacuum = FULL;"_ns
934 #else
935 // Turn on incremental auto_vacuum mode on desktop builds.
936 "PRAGMA auto_vacuum = INCREMENTAL;"_ns
937 #endif
939 .mapErr(mapNoDeviceSpaceError)));
941 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
943 journalModeSet = true;
944 } else {
945 #ifdef DEBUG
946 // Disable foreign key support while upgrading. This has to be done before
947 // starting a transaction.
948 MOZ_ALWAYS_SUCCEEDS(
949 connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
950 #endif
953 bool vacuumNeeded = false;
955 mozStorageTransaction transaction(
956 connection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
958 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
960 if (newDatabase) {
961 QM_TRY(MOZ_TO_RESULT(CreateTables(*connection)));
963 #ifdef DEBUG
965 QM_TRY_INSPECT(
966 const int32_t& schemaVersion,
967 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion),
968 QM_ASSERT_UNREACHABLE);
969 MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
971 #endif
973 // The parameter names are not used, parameters are bound by index only
974 // locally in the same function.
975 QM_TRY_INSPECT(
976 const auto& stmt,
977 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
978 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
979 "INSERT INTO database (name, origin) "
980 "VALUES (:name, :origin)"_ns));
982 QM_TRY(MOZ_TO_RESULT(stmt->BindStringByIndex(0, aName)));
983 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByIndex(1, aOrigin)));
984 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
985 } else {
986 QM_TRY_UNWRAP(vacuumNeeded, MaybeUpgradeSchema(*connection, schemaVersion,
987 aFMDirectory, aOrigin));
990 QM_TRY(MOZ_TO_RESULT_INVOKE_MEMBER(transaction, Commit)
991 .mapErr(mapNoDeviceSpaceError));
993 #ifdef DEBUG
994 if (!newDatabase) {
995 // Re-enable foreign key support after doing a foreign key check.
996 QM_TRY_INSPECT(const bool& foreignKeyError,
997 CreateAndExecuteSingleStepStatement<
998 SingleStepResult::ReturnNullIfNoResult>(
999 *connection, "PRAGMA foreign_key_check;"_ns),
1000 QM_ASSERT_UNREACHABLE);
1002 MOZ_ASSERT(!foreignKeyError, "Database has inconsisistent foreign keys!");
1004 MOZ_ALWAYS_SUCCEEDS(
1005 connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
1007 #endif
1009 if (kSQLitePageSizeOverride && !newDatabase) {
1010 QM_TRY_INSPECT(const auto& stmt,
1011 CreateAndExecuteSingleStepStatement(
1012 *connection, "PRAGMA page_size;"_ns));
1014 QM_TRY_INSPECT(const int32_t& pageSize,
1015 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
1016 MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
1018 if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
1019 // We must not be in WAL journal mode to change the page size.
1020 QM_TRY(MOZ_TO_RESULT(
1021 connection->ExecuteSimpleSQL("PRAGMA journal_mode = DELETE;"_ns)));
1023 QM_TRY_INSPECT(const auto& stmt,
1024 CreateAndExecuteSingleStepStatement(
1025 *connection, "PRAGMA journal_mode;"_ns));
1027 QM_TRY_INSPECT(const auto& journalMode,
1028 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt,
1029 GetUTF8String, 0));
1031 if (journalMode.EqualsLiteral("delete")) {
1032 // Successfully set to rollback journal mode so changing the page size
1033 // is possible with a VACUUM.
1034 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
1035 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
1037 // We will need to VACUUM in order to change the page size.
1038 vacuumNeeded = true;
1039 } else {
1040 NS_WARNING(
1041 "Failed to set journal_mode for database, unable to "
1042 "change the page size!");
1047 if (vacuumNeeded) {
1048 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("VACUUM;"_ns)));
1051 if (newDatabase || vacuumNeeded) {
1052 if (journalModeSet) {
1053 // Make sure we checkpoint to get an accurate file size.
1054 QM_TRY(MOZ_TO_RESULT(
1055 connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(FULL);"_ns)));
1058 QM_TRY_INSPECT(const int64_t& fileSize,
1059 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, GetFileSize));
1060 MOZ_ASSERT(fileSize > 0);
1062 PRTime vacuumTime = PR_Now();
1063 MOZ_ASSERT(vacuumTime);
1065 // The parameter names are not used, parameters are bound by index only
1066 // locally in the same function.
1067 QM_TRY_INSPECT(
1068 const auto& vacuumTimeStmt,
1069 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<mozIStorageStatement>,
1070 connection, CreateStatement,
1071 "UPDATE database "
1072 "SET last_vacuum_time = :time"
1073 ", last_vacuum_size = :size;"_ns));
1075 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(0, vacuumTime)));
1076 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(1, fileSize)));
1077 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->Execute()));
1081 if (!journalModeSet) {
1082 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
1085 return WrapMovingNotNullUnchecked(std::move(connection));
1088 nsCOMPtr<nsIFile> GetFileForPath(const nsAString& aPath) {
1089 MOZ_ASSERT(!aPath.IsEmpty());
1091 QM_TRY_RETURN(QM_NewLocalFile(aPath), nullptr);
1094 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
1095 GetStorageConnection(nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
1096 const uint32_t aTelemetryId,
1097 const Maybe<CipherKey>& aMaybeKey) {
1098 MOZ_ASSERT(!NS_IsMainThread());
1099 MOZ_ASSERT(!IsOnBackgroundThread());
1100 MOZ_ASSERT(aDirectoryLockId >= 0);
1102 AUTO_PROFILER_LABEL("GetStorageConnection", DOM);
1104 QM_TRY_INSPECT(const bool& exists,
1105 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, Exists));
1107 QM_TRY(OkIf(exists), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
1108 IDB_REPORT_INTERNAL_ERR_LAMBDA);
1110 QM_TRY_INSPECT(
1111 const auto& dbFileUrl,
1112 GetDatabaseFileURL(aDatabaseFile, aDirectoryLockId, aMaybeKey));
1114 QM_TRY_INSPECT(const auto& storageService,
1115 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
1116 MOZ_SELECT_OVERLOAD(do_GetService),
1117 MOZ_STORAGE_SERVICE_CONTRACTID));
1119 QM_TRY_UNWRAP(
1120 nsCOMPtr<mozIStorageConnection> connection,
1121 OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId));
1123 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection)));
1125 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
1127 return WrapMovingNotNullUnchecked(std::move(connection));
1130 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
1131 GetStorageConnection(const nsAString& aDatabaseFilePath,
1132 const int64_t aDirectoryLockId,
1133 const uint32_t aTelemetryId,
1134 const Maybe<CipherKey>& aMaybeKey) {
1135 MOZ_ASSERT(!NS_IsMainThread());
1136 MOZ_ASSERT(!IsOnBackgroundThread());
1137 MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
1138 MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, kSQLiteSuffix));
1139 MOZ_ASSERT(aDirectoryLockId >= 0);
1141 nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
1143 QM_TRY(OkIf(dbFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
1144 IDB_REPORT_INTERNAL_ERR_LAMBDA);
1146 return GetStorageConnection(*dbFile, aDirectoryLockId, aTelemetryId,
1147 aMaybeKey);
1150 /*******************************************************************************
1151 * ConnectionPool declarations
1152 ******************************************************************************/
1154 class DatabaseConnection final : public CachingDatabaseConnection {
1155 friend class ConnectionPool;
1157 enum class CheckpointMode { Full, Restart, Truncate };
1159 public:
1160 class AutoSavepoint;
1161 class UpdateRefcountFunction;
1163 private:
1164 InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileManager>>> mFileManager;
1165 RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
1166 RefPtr<QuotaObject> mQuotaObject;
1167 RefPtr<QuotaObject> mJournalQuotaObject;
1168 bool mInReadTransaction;
1169 bool mInWriteTransaction;
1171 #ifdef DEBUG
1172 uint32_t mDEBUGSavepointCount;
1173 #endif
1175 public:
1176 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection)
1178 UpdateRefcountFunction* GetUpdateRefcountFunction() const {
1179 AssertIsOnConnectionThread();
1181 return mUpdateRefcountFunction;
1184 nsresult BeginWriteTransaction();
1186 nsresult CommitWriteTransaction();
1188 void RollbackWriteTransaction();
1190 void FinishWriteTransaction();
1192 nsresult StartSavepoint();
1194 nsresult ReleaseSavepoint();
1196 nsresult RollbackSavepoint();
1198 nsresult Checkpoint() {
1199 AssertIsOnConnectionThread();
1201 return CheckpointInternal(CheckpointMode::Full);
1204 void DoIdleProcessing(bool aNeedsCheckpoint);
1206 void Close();
1208 nsresult DisableQuotaChecks();
1210 void EnableQuotaChecks();
1212 private:
1213 DatabaseConnection(
1214 MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection,
1215 MovingNotNull<SafeRefPtr<DatabaseFileManager>> aFileManager);
1217 ~DatabaseConnection();
1219 nsresult Init();
1221 nsresult CheckpointInternal(CheckpointMode aMode);
1223 Result<uint32_t, nsresult> GetFreelistCount(
1224 CachedStatement& aCachedStatement);
1227 * On success, returns whether some pages were freed.
1229 Result<bool, nsresult> ReclaimFreePagesWhileIdle(
1230 CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement,
1231 uint32_t aFreelistCount, bool aNeedsCheckpoint);
1233 Result<int64_t, nsresult> GetFileSize(const nsAString& aPath);
1236 class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final {
1237 DatabaseConnection* mConnection;
1238 #ifdef DEBUG
1239 const TransactionBase* mDEBUGTransaction;
1240 #endif
1242 public:
1243 AutoSavepoint();
1244 ~AutoSavepoint();
1246 nsresult Start(const TransactionBase& aTransaction);
1248 nsresult Commit();
1251 class DatabaseConnection::UpdateRefcountFunction final
1252 : public mozIStorageFunction {
1253 class FileInfoEntry;
1255 enum class UpdateType { Increment, Decrement };
1257 DatabaseConnection* const mConnection;
1258 DatabaseFileManager& mFileManager;
1259 nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
1260 nsTHashMap<nsUint64HashKey, NotNull<FileInfoEntry*>> mSavepointEntriesIndex;
1262 nsTArray<int64_t> mJournalsToCreateBeforeCommit;
1263 nsTArray<int64_t> mJournalsToRemoveAfterCommit;
1264 nsTArray<int64_t> mJournalsToRemoveAfterAbort;
1266 bool mInSavepoint;
1268 public:
1269 NS_DECL_ISUPPORTS
1270 NS_DECL_MOZISTORAGEFUNCTION
1272 UpdateRefcountFunction(DatabaseConnection* aConnection,
1273 DatabaseFileManager& aFileManager);
1275 nsresult WillCommit();
1277 void DidCommit();
1279 void DidAbort();
1281 void StartSavepoint();
1283 void ReleaseSavepoint();
1285 void RollbackSavepoint();
1287 void Reset();
1289 private:
1290 ~UpdateRefcountFunction() = default;
1292 nsresult ProcessValue(mozIStorageValueArray* aValues, int32_t aIndex,
1293 UpdateType aUpdateType);
1295 nsresult CreateJournals();
1297 nsresult RemoveJournals(const nsTArray<int64_t>& aJournals);
1300 class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final {
1301 SafeRefPtr<DatabaseFileInfo> mFileInfo;
1302 int32_t mDelta;
1303 int32_t mSavepointDelta;
1305 public:
1306 explicit FileInfoEntry(SafeRefPtr<DatabaseFileInfo> aFileInfo)
1307 : mFileInfo(std::move(aFileInfo)), mDelta(0), mSavepointDelta(0) {
1308 MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
1311 void IncDeltas(bool aUpdateSavepointDelta) {
1312 ++mDelta;
1313 if (aUpdateSavepointDelta) {
1314 ++mSavepointDelta;
1317 void DecDeltas(bool aUpdateSavepointDelta) {
1318 --mDelta;
1319 if (aUpdateSavepointDelta) {
1320 --mSavepointDelta;
1323 void DecBySavepointDelta() { mDelta -= mSavepointDelta; }
1324 SafeRefPtr<DatabaseFileInfo> ReleaseFileInfo() {
1325 return std::move(mFileInfo);
1327 void MaybeUpdateDBRefs() {
1328 if (mDelta) {
1329 mFileInfo->UpdateDBRefs(mDelta);
1333 int32_t Delta() const { return mDelta; }
1334 int32_t SavepointDelta() const { return mSavepointDelta; }
1336 ~FileInfoEntry() {
1337 MOZ_COUNT_DTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
1341 class ConnectionPool final {
1342 public:
1343 class FinishCallback;
1345 private:
1346 class ConnectionRunnable;
1347 class CloseConnectionRunnable;
1348 struct DatabaseInfo;
1349 struct DatabasesCompleteCallback;
1350 class FinishCallbackWrapper;
1351 class IdleConnectionRunnable;
1353 class ThreadRunnable;
1354 class TransactionInfo;
1355 struct TransactionInfoPair;
1357 struct IdleResource {
1358 TimeStamp mIdleTime;
1360 IdleResource(const IdleResource& aOther) = delete;
1361 IdleResource(IdleResource&& aOther) noexcept
1362 : IdleResource(aOther.mIdleTime) {}
1363 IdleResource& operator=(const IdleResource& aOther) = delete;
1364 IdleResource& operator=(IdleResource&& aOther) = delete;
1366 protected:
1367 explicit IdleResource(const TimeStamp& aIdleTime);
1369 ~IdleResource();
1372 struct IdleDatabaseInfo final : public IdleResource {
1373 InitializedOnce<const NotNull<DatabaseInfo*>> mDatabaseInfo;
1375 public:
1376 explicit IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo);
1378 IdleDatabaseInfo(const IdleDatabaseInfo& aOther) = delete;
1379 IdleDatabaseInfo(IdleDatabaseInfo&& aOther) noexcept
1380 : IdleResource(std::move(aOther)),
1381 mDatabaseInfo{std::move(aOther.mDatabaseInfo)} {
1382 MOZ_ASSERT(mDatabaseInfo);
1384 MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
1386 IdleDatabaseInfo& operator=(const IdleDatabaseInfo& aOther) = delete;
1387 IdleDatabaseInfo& operator=(IdleDatabaseInfo&& aOther) = delete;
1389 ~IdleDatabaseInfo();
1391 bool operator==(const IdleDatabaseInfo& aOther) const {
1392 return *mDatabaseInfo == *aOther.mDatabaseInfo;
1395 bool operator==(const DatabaseInfo* aDatabaseInfo) const {
1396 return *mDatabaseInfo == aDatabaseInfo;
1399 bool operator<(const IdleDatabaseInfo& aOther) const {
1400 return mIdleTime < aOther.mIdleTime;
1404 class ThreadInfo {
1405 public:
1406 ThreadInfo();
1408 ThreadInfo(nsCOMPtr<nsIThread> aThread, RefPtr<ThreadRunnable> aRunnable)
1409 : mThread{std::move(aThread)}, mRunnable{std::move(aRunnable)} {
1410 AssertIsOnBackgroundThread();
1411 AssertValid();
1413 MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
1416 ThreadInfo(const ThreadInfo& aOther) = delete;
1417 ThreadInfo& operator=(const ThreadInfo& aOther) = delete;
1419 ThreadInfo(ThreadInfo&& aOther) noexcept;
1420 ThreadInfo& operator=(ThreadInfo&& aOther) = default;
1422 bool IsValid() const {
1423 const bool res = mThread;
1424 if (res) {
1425 AssertValid();
1426 } else {
1427 AssertEmpty();
1429 return res;
1432 void AssertValid() const {
1433 MOZ_ASSERT(mThread);
1434 MOZ_ASSERT(mRunnable);
1437 void AssertEmpty() const {
1438 MOZ_ASSERT(!mThread);
1439 MOZ_ASSERT(!mRunnable);
1442 nsIThread& ThreadRef() {
1443 AssertValid();
1444 return *mThread;
1447 std::tuple<nsCOMPtr<nsIThread>, RefPtr<ThreadRunnable>> Forget() {
1448 AssertValid();
1450 return {std::move(mThread), std::move(mRunnable)};
1453 ~ThreadInfo();
1455 bool operator==(const ThreadInfo& aOther) const {
1456 return mThread == aOther.mThread && mRunnable == aOther.mRunnable;
1459 private:
1460 nsCOMPtr<nsIThread> mThread;
1461 RefPtr<ThreadRunnable> mRunnable;
1464 struct IdleThreadInfo final : public IdleResource {
1465 ThreadInfo mThreadInfo;
1467 explicit IdleThreadInfo(ThreadInfo aThreadInfo);
1469 IdleThreadInfo(const IdleThreadInfo& aOther) = delete;
1470 IdleThreadInfo(IdleThreadInfo&& aOther) noexcept
1471 : IdleResource(std::move(aOther)),
1472 mThreadInfo(std::move(aOther.mThreadInfo)) {
1473 AssertIsOnBackgroundThread();
1474 mThreadInfo.AssertValid();
1476 MOZ_COUNT_CTOR(ConnectionPool::IdleThreadInfo);
1478 IdleThreadInfo& operator=(const IdleThreadInfo& aOther) = delete;
1479 IdleThreadInfo& operator=(IdleThreadInfo&& aOther) = delete;
1481 ~IdleThreadInfo();
1483 bool operator==(const IdleThreadInfo& aOther) const {
1484 return mThreadInfo == aOther.mThreadInfo;
1487 bool operator<(const IdleThreadInfo& aOther) const {
1488 return mIdleTime < aOther.mIdleTime;
1492 // This mutex guards mDatabases, see below.
1493 Mutex mDatabasesMutex MOZ_UNANNOTATED;
1495 nsTArray<IdleThreadInfo> mIdleThreads;
1496 nsTArray<IdleDatabaseInfo> mIdleDatabases;
1497 nsTArray<NotNull<DatabaseInfo*>> mDatabasesPerformingIdleMaintenance;
1498 nsCOMPtr<nsITimer> mIdleTimer;
1499 TimeStamp mTargetIdleTime;
1501 // Only modifed on the owning thread, but read on multiple threads. Therefore
1502 // all modifications and all reads off the owning thread must be protected by
1503 // mDatabasesMutex.
1504 nsClassHashtable<nsCStringHashKey, DatabaseInfo> mDatabases;
1506 nsClassHashtable<nsUint64HashKey, TransactionInfo> mTransactions;
1507 nsTArray<NotNull<TransactionInfo*>> mQueuedTransactions;
1509 nsTArray<UniquePtr<DatabasesCompleteCallback>> mCompleteCallbacks;
1511 uint64_t mNextTransactionId;
1512 uint32_t mTotalThreadCount;
1513 FlippedOnce<false> mShutdownRequested;
1514 FlippedOnce<false> mShutdownComplete;
1516 public:
1517 ConnectionPool();
1519 void AssertIsOnOwningThread() const {
1520 NS_ASSERT_OWNINGTHREAD(ConnectionPool);
1523 Result<RefPtr<DatabaseConnection>, nsresult> GetOrCreateConnection(
1524 const Database& aDatabase);
1526 uint64_t Start(const nsID& aBackgroundChildLoggingId,
1527 const nsACString& aDatabaseId, int64_t aLoggingSerialNumber,
1528 const nsTArray<nsString>& aObjectStoreNames,
1529 bool aIsWriteTransaction,
1530 TransactionDatabaseOperationBase* aTransactionOp);
1532 void Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable);
1534 void Finish(uint64_t aTransactionId, FinishCallback* aCallback);
1536 void CloseDatabaseWhenIdle(const nsACString& aDatabaseId) {
1537 Unused << CloseDatabaseWhenIdleInternal(aDatabaseId);
1540 void WaitForDatabasesToComplete(nsTArray<nsCString>&& aDatabaseIds,
1541 nsIRunnable* aCallback);
1543 void Shutdown();
1545 NS_INLINE_DECL_REFCOUNTING(ConnectionPool)
1547 private:
1548 ~ConnectionPool();
1550 static void IdleTimerCallback(nsITimer* aTimer, void* aClosure);
1552 void Cleanup();
1554 void AdjustIdleTimer();
1556 void CancelIdleTimer();
1558 void ShutdownThread(ThreadInfo aThreadInfo);
1560 void CloseIdleDatabases();
1562 void ShutdownIdleThreads();
1564 bool ScheduleTransaction(TransactionInfo& aTransactionInfo,
1565 bool aFromQueuedTransactions);
1567 void NoteFinishedTransaction(uint64_t aTransactionId);
1569 void ScheduleQueuedTransactions(ThreadInfo aThreadInfo);
1571 void NoteIdleDatabase(DatabaseInfo& aDatabaseInfo);
1573 void NoteClosedDatabase(DatabaseInfo& aDatabaseInfo);
1575 bool MaybeFireCallback(DatabasesCompleteCallback* aCallback);
1577 void PerformIdleDatabaseMaintenance(DatabaseInfo& aDatabaseInfo);
1579 void CloseDatabase(DatabaseInfo& aDatabaseInfo) const;
1581 bool CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId);
1584 class ConnectionPool::ConnectionRunnable : public Runnable {
1585 protected:
1586 DatabaseInfo& mDatabaseInfo;
1587 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
1589 explicit ConnectionRunnable(DatabaseInfo& aDatabaseInfo);
1591 ~ConnectionRunnable() override = default;
1594 class ConnectionPool::IdleConnectionRunnable final : public ConnectionRunnable {
1595 const bool mNeedsCheckpoint;
1597 public:
1598 IdleConnectionRunnable(DatabaseInfo& aDatabaseInfo, bool aNeedsCheckpoint)
1599 : ConnectionRunnable(aDatabaseInfo), mNeedsCheckpoint(aNeedsCheckpoint) {}
1601 NS_INLINE_DECL_REFCOUNTING_INHERITED(IdleConnectionRunnable,
1602 ConnectionRunnable)
1604 private:
1605 ~IdleConnectionRunnable() override = default;
1607 NS_DECL_NSIRUNNABLE
1610 class ConnectionPool::CloseConnectionRunnable final
1611 : public ConnectionRunnable {
1612 public:
1613 explicit CloseConnectionRunnable(DatabaseInfo& aDatabaseInfo)
1614 : ConnectionRunnable(aDatabaseInfo) {}
1616 NS_INLINE_DECL_REFCOUNTING_INHERITED(CloseConnectionRunnable,
1617 ConnectionRunnable)
1619 private:
1620 ~CloseConnectionRunnable() override = default;
1622 NS_DECL_NSIRUNNABLE
1625 struct ConnectionPool::DatabaseInfo final {
1626 friend class mozilla::DefaultDelete<DatabaseInfo>;
1628 RefPtr<ConnectionPool> mConnectionPool;
1629 const nsCString mDatabaseId;
1630 RefPtr<DatabaseConnection> mConnection;
1631 nsClassHashtable<nsStringHashKey, TransactionInfoPair> mBlockingTransactions;
1632 nsTArray<NotNull<TransactionInfo*>> mTransactionsScheduledDuringClose;
1633 nsTArray<NotNull<TransactionInfo*>> mScheduledWriteTransactions;
1634 Maybe<TransactionInfo&> mRunningWriteTransaction;
1635 ThreadInfo mThreadInfo;
1636 uint32_t mReadTransactionCount;
1637 uint32_t mWriteTransactionCount;
1638 bool mNeedsCheckpoint;
1639 bool mIdle;
1640 FlippedOnce<false> mCloseOnIdle;
1641 bool mClosing;
1643 #ifdef DEBUG
1644 PRThread* mDEBUGConnectionThread;
1645 #endif
1647 DatabaseInfo(ConnectionPool* aConnectionPool, const nsACString& aDatabaseId);
1649 void AssertIsOnConnectionThread() const {
1650 MOZ_ASSERT(mDEBUGConnectionThread);
1651 MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGConnectionThread);
1654 uint64_t TotalTransactionCount() const {
1655 return mReadTransactionCount + mWriteTransactionCount;
1658 private:
1659 ~DatabaseInfo();
1661 DatabaseInfo(const DatabaseInfo&) = delete;
1662 DatabaseInfo& operator=(const DatabaseInfo&) = delete;
1665 struct ConnectionPool::DatabasesCompleteCallback final {
1666 friend class DefaultDelete<DatabasesCompleteCallback>;
1668 nsTArray<nsCString> mDatabaseIds;
1669 nsCOMPtr<nsIRunnable> mCallback;
1671 DatabasesCompleteCallback(nsTArray<nsCString>&& aDatabaseIds,
1672 nsIRunnable* aCallback);
1674 private:
1675 ~DatabasesCompleteCallback();
1678 class NS_NO_VTABLE ConnectionPool::FinishCallback : public nsIRunnable {
1679 public:
1680 // Called on the owning thread before any additional transactions are
1681 // unblocked.
1682 virtual void TransactionFinishedBeforeUnblock() = 0;
1684 // Called on the owning thread after additional transactions may have been
1685 // unblocked.
1686 virtual void TransactionFinishedAfterUnblock() = 0;
1688 protected:
1689 FinishCallback() = default;
1691 virtual ~FinishCallback() = default;
1694 class ConnectionPool::FinishCallbackWrapper final : public Runnable {
1695 RefPtr<ConnectionPool> mConnectionPool;
1696 RefPtr<FinishCallback> mCallback;
1697 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
1698 uint64_t mTransactionId;
1699 bool mHasRunOnce;
1701 public:
1702 FinishCallbackWrapper(ConnectionPool* aConnectionPool,
1703 uint64_t aTransactionId, FinishCallback* aCallback);
1705 NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishCallbackWrapper, Runnable)
1707 private:
1708 ~FinishCallbackWrapper() override;
1710 NS_DECL_NSIRUNNABLE
1713 class ConnectionPool::ThreadRunnable final : public Runnable {
1714 // Only touched on the background thread.
1715 static uint32_t sNextSerialNumber;
1717 // Set at construction for logging.
1718 const uint32_t mSerialNumber;
1720 // These two values are only modified on the connection thread.
1721 FlippedOnce<true> mFirstRun;
1722 FlippedOnce<true> mContinueRunning;
1724 public:
1725 ThreadRunnable();
1727 NS_INLINE_DECL_REFCOUNTING_INHERITED(ThreadRunnable, Runnable)
1729 uint32_t SerialNumber() const { return mSerialNumber; }
1731 nsCString GetThreadName() const {
1732 return nsPrintfCString("IndexedDB #%" PRIu32, mSerialNumber);
1735 private:
1736 ~ThreadRunnable() override;
1738 NS_DECL_NSIRUNNABLE
1741 class ConnectionPool::TransactionInfo final {
1742 friend class mozilla::DefaultDelete<TransactionInfo>;
1744 nsTHashSet<TransactionInfo*> mBlocking;
1745 nsTArray<NotNull<TransactionInfo*>> mBlockingOrdered;
1747 public:
1748 DatabaseInfo& mDatabaseInfo;
1749 const nsID mBackgroundChildLoggingId;
1750 const nsCString mDatabaseId;
1751 const uint64_t mTransactionId;
1752 const int64_t mLoggingSerialNumber;
1753 const nsTArray<nsString> mObjectStoreNames;
1754 nsTHashSet<TransactionInfo*> mBlockedOn;
1755 nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
1756 const bool mIsWriteTransaction;
1757 bool mRunning;
1759 #ifdef DEBUG
1760 FlippedOnce<false> mFinished;
1761 #endif
1763 TransactionInfo(DatabaseInfo& aDatabaseInfo,
1764 const nsID& aBackgroundChildLoggingId,
1765 const nsACString& aDatabaseId, uint64_t aTransactionId,
1766 int64_t aLoggingSerialNumber,
1767 const nsTArray<nsString>& aObjectStoreNames,
1768 bool aIsWriteTransaction,
1769 TransactionDatabaseOperationBase* aTransactionOp);
1771 void AddBlockingTransaction(TransactionInfo& aTransactionInfo);
1773 void RemoveBlockingTransactions();
1775 private:
1776 ~TransactionInfo();
1778 void MaybeUnblock(TransactionInfo& aTransactionInfo);
1781 struct ConnectionPool::TransactionInfoPair final {
1782 // Multiple reading transactions can block future writes.
1783 nsTArray<NotNull<TransactionInfo*>> mLastBlockingWrites;
1784 // But only a single writing transaction can block future reads.
1785 Maybe<TransactionInfo&> mLastBlockingReads;
1787 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
1788 TransactionInfoPair();
1789 ~TransactionInfoPair();
1790 #endif
1793 /*******************************************************************************
1794 * Actor class declarations
1795 ******************************************************************************/
1797 template <IDBCursorType CursorType>
1798 class CommonOpenOpHelper;
1799 template <IDBCursorType CursorType>
1800 class IndexOpenOpHelper;
1801 template <IDBCursorType CursorType>
1802 class ObjectStoreOpenOpHelper;
1803 template <IDBCursorType CursorType>
1804 class OpenOpHelper;
1806 class DatabaseOperationBase : public Runnable,
1807 public mozIStorageProgressHandler {
1808 template <IDBCursorType CursorType>
1809 friend class OpenOpHelper;
1811 protected:
1812 class AutoSetProgressHandler;
1814 using UniqueIndexTable = nsTHashMap<nsUint64HashKey, bool>;
1816 const nsCOMPtr<nsIEventTarget> mOwningEventTarget;
1817 const nsID mBackgroundChildLoggingId;
1818 const uint64_t mLoggingSerialNumber;
1820 private:
1821 nsresult mResultCode = NS_OK;
1822 Atomic<bool> mOperationMayProceed;
1823 FlippedOnce<false> mActorDestroyed;
1825 public:
1826 NS_DECL_ISUPPORTS_INHERITED
1828 bool IsOnOwningThread() const {
1829 MOZ_ASSERT(mOwningEventTarget);
1831 bool current;
1832 return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
1833 current;
1836 void AssertIsOnOwningThread() const {
1837 MOZ_ASSERT(IsOnBackgroundThread());
1838 MOZ_ASSERT(IsOnOwningThread());
1841 void NoteActorDestroyed() {
1842 AssertIsOnOwningThread();
1844 mActorDestroyed.EnsureFlipped();
1845 mOperationMayProceed = false;
1848 bool IsActorDestroyed() const {
1849 AssertIsOnOwningThread();
1851 return mActorDestroyed;
1854 // May be called on any thread, but you should call IsActorDestroyed() if
1855 // you know you're on the background thread because it is slightly faster.
1856 bool OperationMayProceed() const { return mOperationMayProceed; }
1858 const nsID& BackgroundChildLoggingId() const {
1859 return mBackgroundChildLoggingId;
1862 uint64_t LoggingSerialNumber() const { return mLoggingSerialNumber; }
1864 nsresult ResultCode() const { return mResultCode; }
1866 void SetFailureCode(nsresult aFailureCode) {
1867 MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
1868 OverrideFailureCode(aFailureCode);
1871 void SetFailureCodeIfUnset(nsresult aFailureCode) {
1872 if (NS_SUCCEEDED(mResultCode)) {
1873 OverrideFailureCode(aFailureCode);
1877 bool HasFailed() const { return NS_FAILED(mResultCode); }
1879 protected:
1880 DatabaseOperationBase(const nsID& aBackgroundChildLoggingId,
1881 uint64_t aLoggingSerialNumber)
1882 : Runnable("dom::indexedDB::DatabaseOperationBase"),
1883 mOwningEventTarget(GetCurrentSerialEventTarget()),
1884 mBackgroundChildLoggingId(aBackgroundChildLoggingId),
1885 mLoggingSerialNumber(aLoggingSerialNumber),
1886 mOperationMayProceed(true) {
1887 AssertIsOnOwningThread();
1890 ~DatabaseOperationBase() override { MOZ_ASSERT(mActorDestroyed); }
1892 void OverrideFailureCode(nsresult aFailureCode) {
1893 MOZ_ASSERT(NS_FAILED(aFailureCode));
1895 mResultCode = aFailureCode;
1898 static nsAutoCString MaybeGetBindingClauseForKeyRange(
1899 const Maybe<SerializedKeyRange>& aOptionalKeyRange,
1900 const nsACString& aKeyColumnName);
1902 static nsAutoCString GetBindingClauseForKeyRange(
1903 const SerializedKeyRange& aKeyRange, const nsACString& aKeyColumnName);
1905 static uint64_t ReinterpretDoubleAsUInt64(double aDouble);
1907 static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
1908 mozIStorageStatement* aStatement);
1910 static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
1911 mozIStorageStatement* aStatement,
1912 const nsCString& aLocale);
1914 static Result<IndexDataValuesAutoArray, nsresult>
1915 IndexDataValuesFromUpdateInfos(const nsTArray<IndexUpdateInfo>& aUpdateInfos,
1916 const UniqueIndexTable& aUniqueIndexTable);
1918 static nsresult InsertIndexTableRows(
1919 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
1920 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues);
1922 static nsresult DeleteIndexDataTableRows(
1923 DatabaseConnection* aConnection, const Key& aObjectStoreKey,
1924 const nsTArray<IndexDataValue>& aIndexValues);
1926 static nsresult DeleteObjectStoreDataTableRowsWithIndexes(
1927 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
1928 const Maybe<SerializedKeyRange>& aKeyRange);
1930 static nsresult UpdateIndexValues(
1931 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
1932 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues);
1934 static Result<bool, nsresult> ObjectStoreHasIndexes(
1935 DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId);
1937 private:
1938 template <typename KeyTransformation>
1939 static nsresult MaybeBindKeyToStatement(
1940 const Key& aKey, mozIStorageStatement* aStatement,
1941 const nsACString& aParameterName,
1942 const KeyTransformation& aKeyTransformation);
1944 template <typename KeyTransformation>
1945 static nsresult BindTransformedKeyRangeToStatement(
1946 const SerializedKeyRange& aKeyRange, mozIStorageStatement* aStatement,
1947 const KeyTransformation& aKeyTransformation);
1949 // Not to be overridden by subclasses.
1950 NS_DECL_MOZISTORAGEPROGRESSHANDLER
1953 class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final {
1954 Maybe<mozIStorageConnection&> mConnection;
1955 #ifdef DEBUG
1956 DatabaseOperationBase* mDEBUGDatabaseOp;
1957 #endif
1959 public:
1960 AutoSetProgressHandler();
1962 ~AutoSetProgressHandler();
1964 nsresult Register(mozIStorageConnection& aConnection,
1965 DatabaseOperationBase* aDatabaseOp);
1967 void Unregister();
1970 class TransactionDatabaseOperationBase : public DatabaseOperationBase {
1971 enum class InternalState {
1972 Initial,
1973 DatabaseWork,
1974 SendingPreprocess,
1975 WaitingForContinue,
1976 SendingResults,
1977 Completed
1980 InitializedOnce<const NotNull<SafeRefPtr<TransactionBase>>> mTransaction;
1981 InternalState mInternalState = InternalState::Initial;
1982 bool mWaitingForContinue = false;
1983 const bool mTransactionIsAborted;
1985 protected:
1986 const int64_t mTransactionLoggingSerialNumber;
1988 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1989 protected:
1990 // A check only enables when the diagnostic assert turns on. It assumes the
1991 // mUpdateRefcountFunction is a nullptr because the previous
1992 // StartTransactionOp failed on the connection thread and the next write
1993 // operation (e.g. ObjectstoreAddOrPutRequestOp) doesn't have enough time to
1994 // catch up the failure information.
1995 bool mAssumingPreviousOperationFail = false;
1996 #endif
1998 public:
1999 void AssertIsOnConnectionThread() const
2000 #ifdef DEBUG
2002 #else
2005 #endif
2007 uint64_t StartOnConnectionPool(const nsID& aBackgroundChildLoggingId,
2008 const nsACString& aDatabaseId,
2009 int64_t aLoggingSerialNumber,
2010 const nsTArray<nsString>& aObjectStoreNames,
2011 bool aIsWriteTransaction);
2013 void DispatchToConnectionPool();
2015 TransactionBase& Transaction() { return **mTransaction; }
2017 const TransactionBase& Transaction() const { return **mTransaction; }
2019 bool IsWaitingForContinue() const {
2020 AssertIsOnOwningThread();
2022 return mWaitingForContinue;
2025 void NoteContinueReceived();
2027 int64_t TransactionLoggingSerialNumber() const {
2028 return mTransactionLoggingSerialNumber;
2031 // May be overridden by subclasses if they need to perform work on the
2032 // background thread before being dispatched. Returning false will kill the
2033 // child actors and prevent dispatch.
2034 virtual bool Init(TransactionBase& aTransaction);
2036 // This callback will be called on the background thread before releasing the
2037 // final reference to this request object. Subclasses may perform any
2038 // additional cleanup here but must always call the base class implementation.
2039 virtual void Cleanup();
2041 protected:
2042 explicit TransactionDatabaseOperationBase(
2043 SafeRefPtr<TransactionBase> aTransaction);
2045 TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction,
2046 uint64_t aLoggingSerialNumber);
2048 ~TransactionDatabaseOperationBase() override;
2050 virtual void RunOnConnectionThread();
2052 // Must be overridden in subclasses. Called on the target thread to allow the
2053 // subclass to perform necessary database or file operations. A successful
2054 // return value will trigger a SendSuccessResult callback on the background
2055 // thread while a failure value will trigger a SendFailureResult callback.
2056 virtual nsresult DoDatabaseWork(DatabaseConnection* aConnection) = 0;
2058 // May be overriden in subclasses. Called on the background thread to decide
2059 // if the subclass needs to send any preprocess info to the child actor.
2060 virtual bool HasPreprocessInfo();
2062 // May be overriden in subclasses. Called on the background thread to allow
2063 // the subclass to serialize its preprocess info and send it to the child
2064 // actor. A successful return value will trigger a wait for a
2065 // NoteContinueReceived callback on the background thread while a failure
2066 // value will trigger a SendFailureResult callback.
2067 virtual nsresult SendPreprocessInfo();
2069 // Must be overridden in subclasses. Called on the background thread to allow
2070 // the subclass to serialize its results and send them to the child actor. A
2071 // failed return value will trigger a SendFailureResult callback.
2072 virtual nsresult SendSuccessResult() = 0;
2074 // Must be overridden in subclasses. Called on the background thread to allow
2075 // the subclass to send its failure code. Returning false will cause the
2076 // transaction to be aborted with aResultCode. Returning true will not cause
2077 // the transaction to be aborted.
2078 virtual bool SendFailureResult(nsresult aResultCode) = 0;
2080 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2081 auto MakeAutoSavepointCleanupHandler(DatabaseConnection& aConnection) {
2082 return [this, &aConnection](const auto) {
2083 if (!aConnection.GetUpdateRefcountFunction()) {
2084 mAssumingPreviousOperationFail = true;
2088 #endif
2090 private:
2091 void SendToConnectionPool();
2093 void SendPreprocess();
2095 void SendResults();
2097 void SendPreprocessInfoOrResults(bool aSendPreprocessInfo);
2099 // Not to be overridden by subclasses.
2100 NS_DECL_NSIRUNNABLE
2103 class Factory final : public PBackgroundIDBFactoryParent,
2104 public AtomicSafeRefCounted<Factory> {
2105 RefPtr<DatabaseLoggingInfo> mLoggingInfo;
2107 #ifdef DEBUG
2108 bool mActorDestroyed;
2109 #endif
2111 // Reference counted.
2112 ~Factory() override;
2114 public:
2115 [[nodiscard]] static SafeRefPtr<Factory> Create(
2116 const LoggingInfo& aLoggingInfo);
2118 DatabaseLoggingInfo* GetLoggingInfo() const {
2119 AssertIsOnBackgroundThread();
2120 MOZ_ASSERT(mLoggingInfo);
2122 return mLoggingInfo;
2125 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Factory)
2126 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(Factory, AtomicSafeRefCounted)
2128 // Only constructed in Create().
2129 explicit Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo);
2131 // IPDL methods are only called by IPDL.
2132 void ActorDestroy(ActorDestroyReason aWhy) override;
2134 mozilla::ipc::IPCResult RecvDeleteMe() override;
2136 PBackgroundIDBFactoryRequestParent* AllocPBackgroundIDBFactoryRequestParent(
2137 const FactoryRequestParams& aParams) override;
2139 mozilla::ipc::IPCResult RecvPBackgroundIDBFactoryRequestConstructor(
2140 PBackgroundIDBFactoryRequestParent* aActor,
2141 const FactoryRequestParams& aParams) override;
2143 bool DeallocPBackgroundIDBFactoryRequestParent(
2144 PBackgroundIDBFactoryRequestParent* aActor) override;
2146 PBackgroundIDBDatabaseParent* AllocPBackgroundIDBDatabaseParent(
2147 const DatabaseSpec& aSpec,
2148 PBackgroundIDBFactoryRequestParent* aRequest) override;
2150 bool DeallocPBackgroundIDBDatabaseParent(
2151 PBackgroundIDBDatabaseParent* aActor) override;
2154 class WaitForTransactionsHelper final : public Runnable {
2155 const nsCString mDatabaseId;
2156 nsCOMPtr<nsIRunnable> mCallback;
2158 enum class State { Initial = 0, WaitingForTransactions, Complete } mState;
2160 public:
2161 WaitForTransactionsHelper(const nsACString& aDatabaseId,
2162 nsIRunnable* aCallback)
2163 : Runnable("dom::indexedDB::WaitForTransactionsHelper"),
2164 mDatabaseId(aDatabaseId),
2165 mCallback(aCallback),
2166 mState(State::Initial) {
2167 AssertIsOnBackgroundThread();
2168 MOZ_ASSERT(!aDatabaseId.IsEmpty());
2169 MOZ_ASSERT(aCallback);
2172 void WaitForTransactions();
2174 NS_INLINE_DECL_REFCOUNTING_INHERITED(WaitForTransactionsHelper, Runnable)
2176 private:
2177 ~WaitForTransactionsHelper() override {
2178 MOZ_ASSERT(!mCallback);
2179 MOZ_ASSERT(mState == State::Complete);
2182 void MaybeWaitForTransactions();
2184 void CallCallback();
2186 NS_DECL_NSIRUNNABLE
2189 class Database final
2190 : public PBackgroundIDBDatabaseParent,
2191 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>>,
2192 public AtomicSafeRefCounted<Database> {
2193 friend class VersionChangeTransaction;
2195 class StartTransactionOp;
2196 class UnmapBlobCallback;
2198 private:
2199 SafeRefPtr<Factory> mFactory;
2200 SafeRefPtr<FullDatabaseMetadata> mMetadata;
2201 SafeRefPtr<DatabaseFileManager> mFileManager;
2202 RefPtr<DirectoryLock> mDirectoryLock;
2203 nsTHashSet<TransactionBase*> mTransactions;
2204 nsTHashMap<nsIDHashKey, SafeRefPtr<DatabaseFileInfo>> mMappedBlobs;
2205 RefPtr<DatabaseConnection> mConnection;
2206 const PrincipalInfo mPrincipalInfo;
2207 const Maybe<ContentParentId> mOptionalContentParentId;
2208 // XXX Consider changing this to ClientMetadata.
2209 const quota::OriginMetadata mOriginMetadata;
2210 const nsCString mId;
2211 const nsString mFilePath;
2212 const Maybe<const CipherKey> mKey;
2213 int64_t mDirectoryLockId;
2214 const uint32_t mTelemetryId;
2215 const PersistenceType mPersistenceType;
2216 const bool mChromeWriteAccessAllowed;
2217 const bool mInPrivateBrowsing;
2218 FlippedOnce<false> mClosed;
2219 FlippedOnce<false> mInvalidated;
2220 FlippedOnce<false> mActorWasAlive;
2221 FlippedOnce<false> mActorDestroyed;
2222 nsCOMPtr<nsIEventTarget> mBackgroundThread;
2223 #ifdef DEBUG
2224 bool mAllBlobsUnmapped;
2225 #endif
2227 public:
2228 // Created by OpenDatabaseOp.
2229 Database(SafeRefPtr<Factory> aFactory, const PrincipalInfo& aPrincipalInfo,
2230 const Maybe<ContentParentId>& aOptionalContentParentId,
2231 const quota::OriginMetadata& aOriginMetadata, uint32_t aTelemetryId,
2232 SafeRefPtr<FullDatabaseMetadata> aMetadata,
2233 SafeRefPtr<DatabaseFileManager> aFileManager,
2234 RefPtr<DirectoryLock> aDirectoryLock, bool aChromeWriteAccessAllowed,
2235 bool aInPrivateBrowsing, const Maybe<const CipherKey>& aMaybeKey);
2237 void AssertIsOnConnectionThread() const {
2238 #ifdef DEBUG
2239 if (mConnection) {
2240 MOZ_ASSERT(mConnection);
2241 mConnection->AssertIsOnConnectionThread();
2242 } else {
2243 MOZ_ASSERT(!NS_IsMainThread());
2244 MOZ_ASSERT(!IsOnBackgroundThread());
2245 MOZ_ASSERT(mInvalidated);
2247 #endif
2250 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Database)
2252 void Invalidate();
2254 bool IsOwnedByProcess(ContentParentId aContentParentId) const {
2255 return mOptionalContentParentId &&
2256 mOptionalContentParentId.value() == aContentParentId;
2259 const quota::OriginMetadata& OriginMetadata() const {
2260 return mOriginMetadata;
2263 const nsCString& Id() const { return mId; }
2265 Maybe<DirectoryLock&> MaybeDirectoryLockRef() const {
2266 AssertIsOnBackgroundThread();
2268 return ToMaybeRef(mDirectoryLock.get());
2271 int64_t DirectoryLockId() const { return mDirectoryLockId; }
2273 uint32_t TelemetryId() const { return mTelemetryId; }
2275 PersistenceType Type() const { return mPersistenceType; }
2277 const nsString& FilePath() const { return mFilePath; }
2279 DatabaseFileManager& GetFileManager() const { return *mFileManager; }
2281 MovingNotNull<SafeRefPtr<DatabaseFileManager>> GetFileManagerPtr() const {
2282 return WrapMovingNotNull(mFileManager.clonePtr());
2285 const FullDatabaseMetadata& Metadata() const {
2286 MOZ_ASSERT(mMetadata);
2287 return *mMetadata;
2290 SafeRefPtr<FullDatabaseMetadata> MetadataPtr() const {
2291 MOZ_ASSERT(mMetadata);
2292 return mMetadata.clonePtr();
2295 PBackgroundParent* GetBackgroundParent() const {
2296 AssertIsOnBackgroundThread();
2297 MOZ_ASSERT(!IsActorDestroyed());
2299 return Manager()->Manager();
2302 DatabaseLoggingInfo* GetLoggingInfo() const {
2303 AssertIsOnBackgroundThread();
2304 MOZ_ASSERT(mFactory);
2306 return mFactory->GetLoggingInfo();
2309 bool RegisterTransaction(TransactionBase& aTransaction);
2311 void UnregisterTransaction(TransactionBase& aTransaction);
2313 void SetActorAlive();
2315 void MapBlob(const IPCBlob& aIPCBlob, SafeRefPtr<DatabaseFileInfo> aFileInfo);
2317 bool IsActorAlive() const {
2318 AssertIsOnBackgroundThread();
2320 return mActorWasAlive && !mActorDestroyed;
2323 bool IsActorDestroyed() const {
2324 AssertIsOnBackgroundThread();
2326 return mActorWasAlive && mActorDestroyed;
2329 bool IsClosed() const {
2330 AssertIsOnBackgroundThread();
2332 return mClosed;
2335 bool IsInvalidated() const {
2336 AssertIsOnBackgroundThread();
2338 return mInvalidated;
2341 nsresult EnsureConnection();
2343 DatabaseConnection* GetConnection() const {
2344 #ifdef DEBUG
2345 if (mConnection) {
2346 mConnection->AssertIsOnConnectionThread();
2348 #endif
2350 return mConnection;
2353 void Stringify(nsACString& aResult) const;
2355 bool IsInPrivateBrowsing() const {
2356 AssertIsOnBackgroundThread();
2357 return mInPrivateBrowsing;
2360 const Maybe<const CipherKey>& MaybeKeyRef() const {
2361 // This can be called on any thread, as it is const.
2362 MOZ_ASSERT(mKey.isSome() == mInPrivateBrowsing);
2363 return mKey;
2366 ~Database() override {
2367 MOZ_ASSERT(mClosed);
2368 MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
2370 NS_ProxyRelease("ReleaseIDBFactory", mBackgroundThread.get(),
2371 mFactory.forget());
2374 private:
2375 [[nodiscard]] SafeRefPtr<DatabaseFileInfo> GetBlob(const IPCBlob& aIPCBlob);
2377 void UnmapBlob(const nsID& aID);
2379 void UnmapAllBlobs();
2381 bool CloseInternal();
2383 void MaybeCloseConnection();
2385 void ConnectionClosedCallback();
2387 void CleanupMetadata();
2389 // IPDL methods are only called by IPDL.
2390 void ActorDestroy(ActorDestroyReason aWhy) override;
2392 PBackgroundIDBDatabaseFileParent* AllocPBackgroundIDBDatabaseFileParent(
2393 const IPCBlob& aIPCBlob) override;
2395 bool DeallocPBackgroundIDBDatabaseFileParent(
2396 PBackgroundIDBDatabaseFileParent* aActor) override;
2398 already_AddRefed<PBackgroundIDBTransactionParent>
2399 AllocPBackgroundIDBTransactionParent(
2400 const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode) override;
2402 mozilla::ipc::IPCResult RecvPBackgroundIDBTransactionConstructor(
2403 PBackgroundIDBTransactionParent* aActor,
2404 nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode) override;
2406 mozilla::ipc::IPCResult RecvDeleteMe() override;
2408 mozilla::ipc::IPCResult RecvBlocked() override;
2410 mozilla::ipc::IPCResult RecvClose() override;
2412 template <typename T>
2413 static bool InvalidateAll(const nsTBaseHashSet<nsPtrHashKey<T>>& aTable);
2416 class Database::StartTransactionOp final
2417 : public TransactionDatabaseOperationBase {
2418 friend class Database;
2420 private:
2421 explicit StartTransactionOp(SafeRefPtr<TransactionBase> aTransaction)
2422 : TransactionDatabaseOperationBase(std::move(aTransaction),
2423 /* aLoggingSerialNumber */ 0) {}
2425 ~StartTransactionOp() override = default;
2427 void RunOnConnectionThread() override;
2429 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
2431 nsresult SendSuccessResult() override;
2433 bool SendFailureResult(nsresult aResultCode) override;
2435 void Cleanup() override;
2438 class Database::UnmapBlobCallback final
2439 : public RemoteLazyInputStreamParentCallback {
2440 SafeRefPtr<Database> mDatabase;
2441 nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
2443 public:
2444 explicit UnmapBlobCallback(SafeRefPtr<Database> aDatabase)
2445 : mDatabase(std::move(aDatabase)),
2446 mBackgroundThread(GetCurrentSerialEventTarget()) {
2447 AssertIsOnBackgroundThread();
2450 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Database::UnmapBlobCallback, override)
2452 void ActorDestroyed(const nsID& aID) override {
2453 MOZ_ASSERT(mDatabase);
2454 mBackgroundThread->Dispatch(NS_NewRunnableFunction(
2455 "UnmapBlobCallback", [aID, database = std::move(mDatabase)] {
2456 AssertIsOnBackgroundThread();
2457 database->UnmapBlob(aID);
2458 }));
2461 private:
2462 ~UnmapBlobCallback() = default;
2466 * In coordination with IDBDatabase's mFileActors weak-map on the child side, a
2467 * long-lived mapping from a child process's live Blobs to their corresponding
2468 * DatabaseFileInfo in our owning database. Assists in avoiding redundant IPC
2469 * traffic and disk storage. This includes both:
2470 * - Blobs retrieved from this database and sent to the child that do not need
2471 * to be written to disk because they already exist on disk in this database's
2472 * files directory.
2473 * - Blobs retrieved from other databases or from anywhere else that will need
2474 * to be written to this database's files directory. In this case we will
2475 * hold a reference to its BlobImpl in mBlobImpl until we have successfully
2476 * written the Blob to disk.
2478 * Relevant Blob context: Blobs sent from the parent process to child processes
2479 * are automatically linked back to their source BlobImpl when the child process
2480 * references the Blob via IPC. This is done using the internal IPCBlob
2481 * inputStream actor ID to DatabaseFileInfo mapping. However, when getting an
2482 * actor in the child process for sending an in-child-created Blob to the
2483 * parent process, there is (currently) no Blob machinery to automatically
2484 * establish and reuse a long-lived Actor. As a result, without IDB's weak-map
2485 * cleverness, a memory-backed Blob repeatedly sent from the child to the parent
2486 * would appear as a different Blob each time, requiring the Blob data to be
2487 * sent over IPC each time as well as potentially needing to be written to disk
2488 * each time.
2490 * This object remains alive as long as there is an active child actor or an
2491 * ObjectStoreAddOrPutRequestOp::StoredFileInfo for a queued or active add/put
2492 * op is holding a reference to us.
2494 class DatabaseFile final : public PBackgroundIDBDatabaseFileParent {
2495 // mBlobImpl's ownership lifecycle:
2496 // - Initialized on the background thread at creation time. Then
2497 // responsibility is handed off to the connection thread.
2498 // - Checked and used by the connection thread to generate a stream to write
2499 // the blob to disk by an add/put operation.
2500 // - Cleared on the connection thread once the file has successfully been
2501 // written to disk.
2502 InitializedOnce<const RefPtr<BlobImpl>> mBlobImpl;
2503 const SafeRefPtr<DatabaseFileInfo> mFileInfo;
2505 public:
2506 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::DatabaseFile);
2508 const DatabaseFileInfo& GetFileInfo() const {
2509 AssertIsOnBackgroundThread();
2511 return *mFileInfo;
2514 SafeRefPtr<DatabaseFileInfo> GetFileInfoPtr() const {
2515 AssertIsOnBackgroundThread();
2517 return mFileInfo.clonePtr();
2521 * If mBlobImpl is non-null (implying the contents of this file have not yet
2522 * been written to disk), then return an input stream. Otherwise, if mBlobImpl
2523 * is null (because the contents have been written to disk), returns null.
2525 [[nodiscard]] nsCOMPtr<nsIInputStream> GetInputStream(ErrorResult& rv) const;
2528 * To be called upon successful copying of the stream GetInputStream()
2529 * returned so that we won't try and redundantly write the file to disk in the
2530 * future. This is a separate step from GetInputStream() because
2531 * the write could fail due to quota errors that happen now but that might
2532 * not happen in a future attempt.
2534 void WriteSucceededClearBlobImpl() {
2535 MOZ_ASSERT(!IsOnBackgroundThread());
2537 MOZ_ASSERT(*mBlobImpl);
2538 mBlobImpl.destroy();
2541 public:
2542 // Called when sending to the child.
2543 explicit DatabaseFile(SafeRefPtr<DatabaseFileInfo> aFileInfo)
2544 : mBlobImpl{nullptr}, mFileInfo(std::move(aFileInfo)) {
2545 AssertIsOnBackgroundThread();
2546 MOZ_ASSERT(mFileInfo);
2549 // Called when receiving from the child.
2550 DatabaseFile(RefPtr<BlobImpl> aBlobImpl,
2551 SafeRefPtr<DatabaseFileInfo> aFileInfo)
2552 : mBlobImpl(std::move(aBlobImpl)), mFileInfo(std::move(aFileInfo)) {
2553 AssertIsOnBackgroundThread();
2554 MOZ_ASSERT(*mBlobImpl);
2555 MOZ_ASSERT(mFileInfo);
2558 private:
2559 ~DatabaseFile() override = default;
2561 void ActorDestroy(ActorDestroyReason aWhy) override {
2562 AssertIsOnBackgroundThread();
2566 nsCOMPtr<nsIInputStream> DatabaseFile::GetInputStream(ErrorResult& rv) const {
2567 // We should only be called from our DB connection thread, not the background
2568 // thread.
2569 MOZ_ASSERT(!IsOnBackgroundThread());
2571 // If we were constructed without a BlobImpl, or WriteSucceededClearBlobImpl
2572 // was already called, return nullptr.
2573 if (!mBlobImpl || !*mBlobImpl) {
2574 return nullptr;
2577 nsCOMPtr<nsIInputStream> inputStream;
2578 (*mBlobImpl)->CreateInputStream(getter_AddRefs(inputStream), rv);
2579 if (rv.Failed()) {
2580 return nullptr;
2583 return inputStream;
2586 class TransactionBase : public AtomicSafeRefCounted<TransactionBase> {
2587 friend class CursorBase;
2589 template <IDBCursorType CursorType>
2590 friend class Cursor;
2592 class CommitOp;
2594 protected:
2595 using Mode = IDBTransaction::Mode;
2597 private:
2598 const SafeRefPtr<Database> mDatabase;
2599 nsTArray<SafeRefPtr<FullObjectStoreMetadata>>
2600 mModifiedAutoIncrementObjectStoreMetadataArray;
2601 LazyInitializedOnceNotNull<const uint64_t> mTransactionId;
2602 const nsCString mDatabaseId;
2603 const int64_t mLoggingSerialNumber;
2604 uint64_t mActiveRequestCount;
2605 Atomic<bool> mInvalidatedOnAnyThread;
2606 const Mode mMode;
2607 FlippedOnce<false> mInitialized;
2608 FlippedOnce<false> mHasBeenActiveOnConnectionThread;
2609 FlippedOnce<false> mActorDestroyed;
2610 FlippedOnce<false> mInvalidated;
2612 protected:
2613 nsresult mResultCode;
2614 FlippedOnce<false> mCommitOrAbortReceived;
2615 FlippedOnce<false> mCommittedOrAborted;
2616 FlippedOnce<false> mForceAborted;
2617 LazyInitializedOnce<const Maybe<int64_t>> mLastRequestBeforeCommit;
2618 Maybe<int64_t> mLastFailedRequest;
2620 public:
2621 void AssertIsOnConnectionThread() const {
2622 MOZ_ASSERT(mDatabase);
2623 mDatabase->AssertIsOnConnectionThread();
2626 bool IsActorDestroyed() const {
2627 AssertIsOnBackgroundThread();
2629 return mActorDestroyed;
2632 // Must be called on the background thread.
2633 bool IsInvalidated() const {
2634 MOZ_ASSERT(IsOnBackgroundThread(), "Use IsInvalidatedOnAnyThread()");
2635 MOZ_ASSERT_IF(mInvalidated, NS_FAILED(mResultCode));
2637 return mInvalidated;
2640 // May be called on any thread, but is more expensive than IsInvalidated().
2641 bool IsInvalidatedOnAnyThread() const { return mInvalidatedOnAnyThread; }
2643 void Init(const uint64_t aTransactionId) {
2644 AssertIsOnBackgroundThread();
2645 MOZ_ASSERT(aTransactionId);
2647 mTransactionId.init(aTransactionId);
2648 mInitialized.Flip();
2651 void SetActiveOnConnectionThread() {
2652 AssertIsOnConnectionThread();
2653 mHasBeenActiveOnConnectionThread.Flip();
2656 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::TransactionBase)
2658 void Abort(nsresult aResultCode, bool aForce);
2660 uint64_t TransactionId() const { return *mTransactionId; }
2662 const nsACString& DatabaseId() const { return mDatabaseId; }
2664 Mode GetMode() const { return mMode; }
2666 const Database& GetDatabase() const {
2667 MOZ_ASSERT(mDatabase);
2669 return *mDatabase;
2672 Database& GetMutableDatabase() const {
2673 MOZ_ASSERT(mDatabase);
2675 return *mDatabase;
2678 SafeRefPtr<Database> GetDatabasePtr() const {
2679 MOZ_ASSERT(mDatabase);
2681 return mDatabase.clonePtr();
2684 DatabaseLoggingInfo* GetLoggingInfo() const {
2685 AssertIsOnBackgroundThread();
2686 MOZ_ASSERT(mDatabase);
2688 return mDatabase->GetLoggingInfo();
2691 int64_t LoggingSerialNumber() const { return mLoggingSerialNumber; }
2693 bool IsAborted() const {
2694 AssertIsOnBackgroundThread();
2696 return NS_FAILED(mResultCode);
2699 [[nodiscard]] SafeRefPtr<FullObjectStoreMetadata> GetMetadataForObjectStoreId(
2700 IndexOrObjectStoreId aObjectStoreId) const;
2702 [[nodiscard]] SafeRefPtr<FullIndexMetadata> GetMetadataForIndexId(
2703 FullObjectStoreMetadata& aObjectStoreMetadata,
2704 IndexOrObjectStoreId aIndexId) const;
2706 PBackgroundParent* GetBackgroundParent() const {
2707 AssertIsOnBackgroundThread();
2708 MOZ_ASSERT(!IsActorDestroyed());
2710 return GetDatabase().GetBackgroundParent();
2713 void NoteModifiedAutoIncrementObjectStore(
2714 const SafeRefPtr<FullObjectStoreMetadata>& aMetadata);
2716 void ForgetModifiedAutoIncrementObjectStore(
2717 FullObjectStoreMetadata& aMetadata);
2719 void NoteActiveRequest();
2721 void NoteFinishedRequest(int64_t aRequestId, nsresult aResultCode);
2723 void Invalidate();
2725 virtual ~TransactionBase();
2727 protected:
2728 TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode);
2730 void NoteActorDestroyed() {
2731 AssertIsOnBackgroundThread();
2733 mActorDestroyed.Flip();
2736 #ifdef DEBUG
2737 // Only called by VersionChangeTransaction.
2738 void FakeActorDestroyed() { mActorDestroyed.EnsureFlipped(); }
2739 #endif
2741 mozilla::ipc::IPCResult RecvCommit(IProtocol* aActor,
2742 const Maybe<int64_t> aLastRequest);
2744 mozilla::ipc::IPCResult RecvAbort(IProtocol* aActor, nsresult aResultCode);
2746 void MaybeCommitOrAbort() {
2747 AssertIsOnBackgroundThread();
2749 // If we've already committed or aborted then there's nothing else to do.
2750 if (mCommittedOrAborted) {
2751 return;
2754 // If there are active requests then we have to wait for those requests to
2755 // complete (see NoteFinishedRequest).
2756 if (mActiveRequestCount) {
2757 return;
2760 // If we haven't yet received a commit or abort message then there could be
2761 // additional requests coming so we should wait unless we're being forced to
2762 // abort.
2763 if (!mCommitOrAbortReceived && !mForceAborted) {
2764 return;
2767 CommitOrAbort();
2770 PBackgroundIDBRequestParent* AllocRequest(RequestParams&& aParams,
2771 bool aTrustParams);
2773 bool StartRequest(PBackgroundIDBRequestParent* aActor);
2775 bool DeallocRequest(PBackgroundIDBRequestParent* aActor);
2777 already_AddRefed<PBackgroundIDBCursorParent> AllocCursor(
2778 const OpenCursorParams& aParams, bool aTrustParams);
2780 bool StartCursor(PBackgroundIDBCursorParent* aActor,
2781 const OpenCursorParams& aParams);
2783 virtual void UpdateMetadata(nsresult aResult) {}
2785 virtual void SendCompleteNotification(nsresult aResult) = 0;
2787 private:
2788 bool VerifyRequestParams(const RequestParams& aParams) const;
2790 bool VerifyRequestParams(const SerializedKeyRange& aParams) const;
2792 bool VerifyRequestParams(const ObjectStoreAddPutParams& aParams) const;
2794 bool VerifyRequestParams(const Maybe<SerializedKeyRange>& aParams) const;
2796 void CommitOrAbort();
2799 class TransactionBase::CommitOp final : public DatabaseOperationBase,
2800 public ConnectionPool::FinishCallback {
2801 friend class TransactionBase;
2803 SafeRefPtr<TransactionBase> mTransaction;
2804 nsresult mResultCode; ///< TODO: There is also a mResultCode in
2805 ///< DatabaseOperationBase. Is there a reason not to
2806 ///< use that? At least a more specific name should be
2807 ///< given to this one.
2809 private:
2810 CommitOp(SafeRefPtr<TransactionBase> aTransaction, nsresult aResultCode);
2812 ~CommitOp() override = default;
2814 // Writes new autoIncrement counts to database.
2815 nsresult WriteAutoIncrementCounts();
2817 // Updates counts after a database activity has finished.
2818 void CommitOrRollbackAutoIncrementCounts();
2820 void AssertForeignKeyConsistency(DatabaseConnection* aConnection)
2821 #ifdef DEBUG
2823 #else
2826 #endif
2828 NS_DECL_NSIRUNNABLE
2830 void TransactionFinishedBeforeUnblock() override;
2832 void TransactionFinishedAfterUnblock() override;
2834 public:
2835 // We need to declare all of nsISupports, because FinishCallback has
2836 // a pure-virtual nsISupports declaration.
2837 NS_DECL_ISUPPORTS_INHERITED
2840 class NormalTransaction final : public TransactionBase,
2841 public PBackgroundIDBTransactionParent {
2842 nsTArray<SafeRefPtr<FullObjectStoreMetadata>> mObjectStores;
2844 // Reference counted.
2845 ~NormalTransaction() override = default;
2847 bool IsSameProcessActor();
2849 // Only called by TransactionBase.
2850 void SendCompleteNotification(nsresult aResult) override;
2852 // IPDL methods are only called by IPDL.
2853 void ActorDestroy(ActorDestroyReason aWhy) override;
2855 mozilla::ipc::IPCResult RecvDeleteMe() override;
2857 mozilla::ipc::IPCResult RecvCommit(
2858 const Maybe<int64_t>& aLastRequest) override;
2860 mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override;
2862 PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent(
2863 const RequestParams& aParams) override;
2865 mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor(
2866 PBackgroundIDBRequestParent* aActor,
2867 const RequestParams& aParams) override;
2869 bool DeallocPBackgroundIDBRequestParent(
2870 PBackgroundIDBRequestParent* aActor) override;
2872 already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent(
2873 const OpenCursorParams& aParams) override;
2875 mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor(
2876 PBackgroundIDBCursorParent* aActor,
2877 const OpenCursorParams& aParams) override;
2879 public:
2880 // This constructor is only called by Database.
2881 NormalTransaction(
2882 SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode,
2883 nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores);
2885 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(NormalTransaction, TransactionBase)
2888 class VersionChangeTransaction final
2889 : public TransactionBase,
2890 public PBackgroundIDBVersionChangeTransactionParent {
2891 friend class OpenDatabaseOp;
2893 RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
2894 SafeRefPtr<FullDatabaseMetadata> mOldMetadata;
2896 FlippedOnce<false> mActorWasAlive;
2898 public:
2899 // Only called by OpenDatabaseOp.
2900 explicit VersionChangeTransaction(OpenDatabaseOp* aOpenDatabaseOp);
2902 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(VersionChangeTransaction,
2903 TransactionBase)
2905 private:
2906 // Reference counted.
2907 ~VersionChangeTransaction() override;
2909 bool IsSameProcessActor();
2911 // Only called by OpenDatabaseOp.
2912 bool CopyDatabaseMetadata();
2914 void SetActorAlive();
2916 // Only called by TransactionBase.
2917 void UpdateMetadata(nsresult aResult) override;
2919 // Only called by TransactionBase.
2920 void SendCompleteNotification(nsresult aResult) override;
2922 // IPDL methods are only called by IPDL.
2923 void ActorDestroy(ActorDestroyReason aWhy) override;
2925 mozilla::ipc::IPCResult RecvDeleteMe() override;
2927 mozilla::ipc::IPCResult RecvCommit(
2928 const Maybe<int64_t>& aLastRequest) override;
2930 mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override;
2932 mozilla::ipc::IPCResult RecvCreateObjectStore(
2933 const ObjectStoreMetadata& aMetadata) override;
2935 mozilla::ipc::IPCResult RecvDeleteObjectStore(
2936 const IndexOrObjectStoreId& aObjectStoreId) override;
2938 mozilla::ipc::IPCResult RecvRenameObjectStore(
2939 const IndexOrObjectStoreId& aObjectStoreId,
2940 const nsAString& aName) override;
2942 mozilla::ipc::IPCResult RecvCreateIndex(
2943 const IndexOrObjectStoreId& aObjectStoreId,
2944 const IndexMetadata& aMetadata) override;
2946 mozilla::ipc::IPCResult RecvDeleteIndex(
2947 const IndexOrObjectStoreId& aObjectStoreId,
2948 const IndexOrObjectStoreId& aIndexId) override;
2950 mozilla::ipc::IPCResult RecvRenameIndex(
2951 const IndexOrObjectStoreId& aObjectStoreId,
2952 const IndexOrObjectStoreId& aIndexId, const nsAString& aName) override;
2954 PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent(
2955 const RequestParams& aParams) override;
2957 mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor(
2958 PBackgroundIDBRequestParent* aActor,
2959 const RequestParams& aParams) override;
2961 bool DeallocPBackgroundIDBRequestParent(
2962 PBackgroundIDBRequestParent* aActor) override;
2964 already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent(
2965 const OpenCursorParams& aParams) override;
2967 mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor(
2968 PBackgroundIDBCursorParent* aActor,
2969 const OpenCursorParams& aParams) override;
2972 class FactoryOp
2973 : public DatabaseOperationBase,
2974 public OpenDirectoryListener,
2975 public PBackgroundIDBFactoryRequestParent,
2976 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
2977 public:
2978 struct MaybeBlockedDatabaseInfo final {
2979 SafeRefPtr<Database> mDatabase;
2980 bool mBlocked;
2982 MaybeBlockedDatabaseInfo(MaybeBlockedDatabaseInfo&&) = default;
2983 MaybeBlockedDatabaseInfo& operator=(MaybeBlockedDatabaseInfo&&) = default;
2985 MOZ_IMPLICIT MaybeBlockedDatabaseInfo(SafeRefPtr<Database> aDatabase)
2986 : mDatabase(std::move(aDatabase)), mBlocked(false) {
2987 MOZ_ASSERT(mDatabase);
2989 MOZ_COUNT_CTOR(FactoryOp::MaybeBlockedDatabaseInfo);
2992 ~MaybeBlockedDatabaseInfo() {
2993 MOZ_COUNT_DTOR(FactoryOp::MaybeBlockedDatabaseInfo);
2996 bool operator==(const Database* aOther) const {
2997 return mDatabase == aOther;
3000 Database* operator->() const& MOZ_NO_ADDREF_RELEASE_ON_RETURN {
3001 return mDatabase.unsafeGetRawPtr();
3005 protected:
3006 enum class State {
3007 // Just created on the PBackground thread, dispatched to the main thread.
3008 // Next step is either SendingResults if permission is denied,
3009 // PermissionChallenge if the permission is unknown, or FinishOpen
3010 // if permission is granted.
3011 Initial,
3013 // Ensuring quota manager is created and opening directory on the
3014 // PBackground thread. Next step is either SendingResults if quota manager
3015 // is not available or DirectoryOpenPending if quota manager is available.
3016 FinishOpen,
3018 // Waiting for directory open allowed on the PBackground thread. The next
3019 // step is either SendingResults if directory lock failed to acquire, or
3020 // DatabaseOpenPending if directory lock is acquired.
3021 DirectoryOpenPending,
3023 // Waiting for database open allowed on the PBackground thread. The next
3024 // step is DatabaseWorkOpen.
3025 DatabaseOpenPending,
3027 // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
3028 // either BeginVersionChange if the requested version doesn't match the
3029 // existing database version or SendingResults if the versions match.
3030 DatabaseWorkOpen,
3032 // Starting a version change transaction or deleting a database on the
3033 // PBackground thread. We need to notify other databases that a version
3034 // change is about to happen, and maybe tell the request that a version
3035 // change has been blocked. If databases are notified then the next step is
3036 // WaitingForOtherDatabasesToClose. Otherwise the next step is
3037 // WaitingForTransactionsToComplete.
3038 BeginVersionChange,
3040 // Waiting for other databases to close on the PBackground thread. This
3041 // state may persist until all databases are closed. The next state is
3042 // WaitingForTransactionsToComplete.
3043 WaitingForOtherDatabasesToClose,
3045 // Waiting for all transactions that could interfere with this operation to
3046 // complete on the PBackground thread. Next state is
3047 // DatabaseWorkVersionChange.
3048 WaitingForTransactionsToComplete,
3050 // Waiting to do/doing work on the "work thread". This involves waiting for
3051 // the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a
3052 // different implementation) to do its work. Eventually the state will
3053 // transition to SendingResults.
3054 DatabaseWorkVersionChange,
3056 // Waiting to send/sending results on the PBackground thread. Next step is
3057 // Completed.
3058 SendingResults,
3060 // All done.
3061 Completed
3064 // Must be released on the background thread!
3065 SafeRefPtr<Factory> mFactory;
3067 RefPtr<ThreadsafeContentParentHandle> mContentHandle;
3069 // Must be released on the main thread!
3070 RefPtr<DirectoryLock> mDirectoryLock;
3072 RefPtr<FactoryOp> mDelayedOp;
3073 nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases;
3075 const CommonFactoryRequestParams mCommonParams;
3076 OriginMetadata mOriginMetadata;
3077 nsCString mDatabaseId;
3078 nsString mDatabaseFilePath;
3079 int64_t mDirectoryLockId;
3080 State mState;
3081 bool mWaitingForPermissionRetry;
3082 bool mEnforcingQuota;
3083 const bool mDeleting;
3084 bool mChromeWriteAccessAllowed;
3085 FlippedOnce<false> mInPrivateBrowsing;
3087 public:
3088 const nsACString& Origin() const {
3089 AssertIsOnOwningThread();
3091 return mOriginMetadata.mOrigin;
3094 bool DatabaseFilePathIsKnown() const {
3095 AssertIsOnOwningThread();
3097 return !mDatabaseFilePath.IsEmpty();
3100 const nsAString& DatabaseFilePath() const {
3101 AssertIsOnOwningThread();
3102 MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
3104 return mDatabaseFilePath;
3107 void NoteDatabaseBlocked(Database* aDatabase);
3109 void NoteDatabaseClosed(Database* aDatabase);
3111 #ifdef DEBUG
3112 bool HasBlockedDatabases() const { return !mMaybeBlockedDatabases.IsEmpty(); }
3113 #endif
3115 void StringifyState(nsACString& aResult) const;
3117 void Stringify(nsACString& aResult) const;
3119 protected:
3120 FactoryOp(SafeRefPtr<Factory> aFactory,
3121 RefPtr<ThreadsafeContentParentHandle> aContentHandle,
3122 const CommonFactoryRequestParams& aCommonParams, bool aDeleting);
3124 ~FactoryOp() override {
3125 // Normally this would be out-of-line since it is a virtual function but
3126 // MSVC 2010 fails to link for some reason if it is not inlined here...
3127 MOZ_ASSERT_IF(OperationMayProceed(),
3128 mState == State::Initial || mState == State::Completed);
3131 nsresult Open();
3133 nsresult DirectoryOpen();
3135 nsresult SendToIOThread();
3137 void WaitForTransactions();
3139 void CleanupMetadata();
3141 void FinishSendResults();
3143 nsresult SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
3144 Maybe<Database&> aOpeningDatabase,
3145 uint64_t aOldVersion,
3146 const Maybe<uint64_t>& aNewVersion);
3148 // Methods that subclasses must implement.
3149 virtual nsresult DatabaseOpen() = 0;
3151 virtual nsresult DoDatabaseWork() = 0;
3153 virtual nsresult BeginVersionChange() = 0;
3155 virtual bool AreActorsAlive() = 0;
3157 virtual nsresult DispatchToWorkThread() = 0;
3159 // Should only be called by Run().
3160 virtual void SendResults() = 0;
3162 // We need to declare refcounting unconditionally, because
3163 // OpenDirectoryListener has pure-virtual refcounting.
3164 NS_DECL_ISUPPORTS_INHERITED
3166 // Common nsIRunnable implementation that subclasses may not override.
3167 NS_IMETHOD
3168 Run() final;
3170 // OpenDirectoryListener overrides.
3171 void DirectoryLockAcquired(DirectoryLock* aLock) override;
3173 void DirectoryLockFailed() override;
3175 // IPDL methods.
3176 void ActorDestroy(ActorDestroyReason aWhy) override;
3178 virtual void SendBlockedNotification() = 0;
3180 private:
3181 mozilla::Result<PermissionValue, nsresult> CheckPermission(
3182 ContentParent* aContentParent);
3184 static bool CheckAtLeastOneAppHasPermission(
3185 ContentParent* aContentParent, const nsACString& aPermissionString);
3187 nsresult FinishOpen();
3189 // Test whether this FactoryOp needs to wait for the given op.
3190 bool MustWaitFor(const FactoryOp& aExistingOp);
3193 class OpenDatabaseOp final : public FactoryOp {
3194 friend class Database;
3195 friend class VersionChangeTransaction;
3197 class VersionChangeOp;
3199 SafeRefPtr<FullDatabaseMetadata> mMetadata;
3201 uint64_t mRequestedVersion;
3202 SafeRefPtr<DatabaseFileManager> mFileManager;
3204 SafeRefPtr<Database> mDatabase;
3205 SafeRefPtr<VersionChangeTransaction> mVersionChangeTransaction;
3207 // This is only set while a VersionChangeOp is live. It holds a strong
3208 // reference to its OpenDatabaseOp object so this is a weak pointer to avoid
3209 // cycles.
3210 VersionChangeOp* mVersionChangeOp;
3212 uint32_t mTelemetryId;
3214 public:
3215 OpenDatabaseOp(SafeRefPtr<Factory> aFactory,
3216 RefPtr<ThreadsafeContentParentHandle> aContentHandle,
3217 const CommonFactoryRequestParams& aParams);
3219 private:
3220 ~OpenDatabaseOp() override { MOZ_ASSERT(!mVersionChangeOp); }
3222 nsresult LoadDatabaseInformation(mozIStorageConnection& aConnection);
3224 nsresult SendUpgradeNeeded();
3226 void EnsureDatabaseActor();
3228 nsresult EnsureDatabaseActorIsAlive();
3230 mozilla::Result<DatabaseSpec, nsresult> MetadataToSpec() const;
3232 void AssertMetadataConsistency(const FullDatabaseMetadata& aMetadata)
3233 #ifdef DEBUG
3235 #else
3238 #endif
3240 void ConnectionClosedCallback();
3242 void ActorDestroy(ActorDestroyReason aWhy) override;
3244 nsresult DatabaseOpen() override;
3246 nsresult DoDatabaseWork() override;
3248 nsresult BeginVersionChange() override;
3250 bool AreActorsAlive() override;
3252 void SendBlockedNotification() override;
3254 nsresult DispatchToWorkThread() override;
3256 void SendResults() override;
3258 static nsresult UpdateLocaleAwareIndex(mozIStorageConnection& aConnection,
3259 const IndexMetadata& aIndexMetadata,
3260 const nsCString& aLocale);
3263 class OpenDatabaseOp::VersionChangeOp final
3264 : public TransactionDatabaseOperationBase {
3265 friend class OpenDatabaseOp;
3267 RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
3268 const uint64_t mRequestedVersion;
3269 uint64_t mPreviousVersion;
3271 private:
3272 explicit VersionChangeOp(OpenDatabaseOp* aOpenDatabaseOp)
3273 : TransactionDatabaseOperationBase(
3274 aOpenDatabaseOp->mVersionChangeTransaction.clonePtr(),
3275 aOpenDatabaseOp->LoggingSerialNumber()),
3276 mOpenDatabaseOp(aOpenDatabaseOp),
3277 mRequestedVersion(aOpenDatabaseOp->mRequestedVersion),
3278 mPreviousVersion(
3279 aOpenDatabaseOp->mMetadata->mCommonMetadata.version()) {
3280 MOZ_ASSERT(aOpenDatabaseOp);
3281 MOZ_ASSERT(mRequestedVersion);
3284 ~VersionChangeOp() override { MOZ_ASSERT(!mOpenDatabaseOp); }
3286 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3288 nsresult SendSuccessResult() override;
3290 bool SendFailureResult(nsresult aResultCode) override;
3292 void Cleanup() override;
3295 class DeleteDatabaseOp final : public FactoryOp {
3296 class VersionChangeOp;
3298 nsString mDatabaseDirectoryPath;
3299 nsString mDatabaseFilenameBase;
3300 uint64_t mPreviousVersion;
3302 public:
3303 DeleteDatabaseOp(SafeRefPtr<Factory> aFactory,
3304 RefPtr<ThreadsafeContentParentHandle> aContentHandle,
3305 const CommonFactoryRequestParams& aParams)
3306 : FactoryOp(std::move(aFactory), std::move(aContentHandle), aParams,
3307 /* aDeleting */ true),
3308 mPreviousVersion(0) {}
3310 private:
3311 ~DeleteDatabaseOp() override = default;
3313 void LoadPreviousVersion(nsIFile& aDatabaseFile);
3315 nsresult DatabaseOpen() override;
3317 nsresult DoDatabaseWork() override;
3319 nsresult BeginVersionChange() override;
3321 bool AreActorsAlive() override;
3323 void SendBlockedNotification() override;
3325 nsresult DispatchToWorkThread() override;
3327 void SendResults() override;
3330 class DeleteDatabaseOp::VersionChangeOp final : public DatabaseOperationBase {
3331 friend class DeleteDatabaseOp;
3333 RefPtr<DeleteDatabaseOp> mDeleteDatabaseOp;
3335 private:
3336 explicit VersionChangeOp(DeleteDatabaseOp* aDeleteDatabaseOp)
3337 : DatabaseOperationBase(aDeleteDatabaseOp->BackgroundChildLoggingId(),
3338 aDeleteDatabaseOp->LoggingSerialNumber()),
3339 mDeleteDatabaseOp(aDeleteDatabaseOp) {
3340 MOZ_ASSERT(aDeleteDatabaseOp);
3341 MOZ_ASSERT(!aDeleteDatabaseOp->mDatabaseDirectoryPath.IsEmpty());
3344 ~VersionChangeOp() override = default;
3346 nsresult RunOnIOThread();
3348 void RunOnOwningThread();
3350 NS_DECL_NSIRUNNABLE
3353 class VersionChangeTransactionOp : public TransactionDatabaseOperationBase {
3354 public:
3355 void Cleanup() override;
3357 protected:
3358 explicit VersionChangeTransactionOp(
3359 SafeRefPtr<VersionChangeTransaction> aTransaction)
3360 : TransactionDatabaseOperationBase(std::move(aTransaction)) {}
3362 ~VersionChangeTransactionOp() override = default;
3364 private:
3365 nsresult SendSuccessResult() override;
3367 bool SendFailureResult(nsresult aResultCode) override;
3370 class CreateObjectStoreOp final : public VersionChangeTransactionOp {
3371 friend class VersionChangeTransaction;
3373 const ObjectStoreMetadata mMetadata;
3375 private:
3376 // Only created by VersionChangeTransaction.
3377 CreateObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3378 const ObjectStoreMetadata& aMetadata)
3379 : VersionChangeTransactionOp(std::move(aTransaction)),
3380 mMetadata(aMetadata) {
3381 MOZ_ASSERT(aMetadata.id());
3384 ~CreateObjectStoreOp() override = default;
3386 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3389 class DeleteObjectStoreOp final : public VersionChangeTransactionOp {
3390 friend class VersionChangeTransaction;
3392 const SafeRefPtr<FullObjectStoreMetadata> mMetadata;
3393 const bool mIsLastObjectStore;
3395 private:
3396 // Only created by VersionChangeTransaction.
3397 DeleteObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3398 SafeRefPtr<FullObjectStoreMetadata> aMetadata,
3399 const bool aIsLastObjectStore)
3400 : VersionChangeTransactionOp(std::move(aTransaction)),
3401 mMetadata(std::move(aMetadata)),
3402 mIsLastObjectStore(aIsLastObjectStore) {
3403 MOZ_ASSERT(mMetadata->mCommonMetadata.id());
3406 ~DeleteObjectStoreOp() override = default;
3408 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3411 class RenameObjectStoreOp final : public VersionChangeTransactionOp {
3412 friend class VersionChangeTransaction;
3414 const int64_t mId;
3415 const nsString mNewName;
3417 private:
3418 // Only created by VersionChangeTransaction.
3419 RenameObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3420 FullObjectStoreMetadata& aMetadata)
3421 : VersionChangeTransactionOp(std::move(aTransaction)),
3422 mId(aMetadata.mCommonMetadata.id()),
3423 mNewName(aMetadata.mCommonMetadata.name()) {
3424 MOZ_ASSERT(mId);
3427 ~RenameObjectStoreOp() override = default;
3429 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3432 class CreateIndexOp final : public VersionChangeTransactionOp {
3433 friend class VersionChangeTransaction;
3435 class UpdateIndexDataValuesFunction;
3437 const IndexMetadata mMetadata;
3438 Maybe<UniqueIndexTable> mMaybeUniqueIndexTable;
3439 const SafeRefPtr<DatabaseFileManager> mFileManager;
3440 const nsCString mDatabaseId;
3441 const IndexOrObjectStoreId mObjectStoreId;
3443 private:
3444 // Only created by VersionChangeTransaction.
3445 CreateIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3446 IndexOrObjectStoreId aObjectStoreId,
3447 const IndexMetadata& aMetadata);
3449 ~CreateIndexOp() override = default;
3451 nsresult InsertDataFromObjectStore(DatabaseConnection* aConnection);
3453 nsresult InsertDataFromObjectStoreInternal(
3454 DatabaseConnection* aConnection) const;
3456 bool Init(TransactionBase& aTransaction) override;
3458 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3461 class CreateIndexOp::UpdateIndexDataValuesFunction final
3462 : public mozIStorageFunction {
3463 RefPtr<CreateIndexOp> mOp;
3464 RefPtr<DatabaseConnection> mConnection;
3465 const NotNull<SafeRefPtr<Database>> mDatabase;
3467 public:
3468 UpdateIndexDataValuesFunction(CreateIndexOp* aOp,
3469 DatabaseConnection* aConnection,
3470 SafeRefPtr<Database> aDatabase)
3471 : mOp(aOp),
3472 mConnection(aConnection),
3473 mDatabase(WrapNotNull(std::move(aDatabase))) {
3474 MOZ_ASSERT(aOp);
3475 MOZ_ASSERT(aConnection);
3476 aConnection->AssertIsOnConnectionThread();
3479 NS_DECL_ISUPPORTS
3481 private:
3482 ~UpdateIndexDataValuesFunction() = default;
3484 NS_DECL_MOZISTORAGEFUNCTION
3487 class DeleteIndexOp final : public VersionChangeTransactionOp {
3488 friend class VersionChangeTransaction;
3490 const IndexOrObjectStoreId mObjectStoreId;
3491 const IndexOrObjectStoreId mIndexId;
3492 const bool mUnique;
3493 const bool mIsLastIndex;
3495 private:
3496 // Only created by VersionChangeTransaction.
3497 DeleteIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3498 IndexOrObjectStoreId aObjectStoreId,
3499 IndexOrObjectStoreId aIndexId, const bool aUnique,
3500 const bool aIsLastIndex);
3502 ~DeleteIndexOp() override = default;
3504 nsresult RemoveReferencesToIndex(
3505 DatabaseConnection* aConnection, const Key& aObjectDataKey,
3506 nsTArray<IndexDataValue>& aIndexValues) const;
3508 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3511 class RenameIndexOp final : public VersionChangeTransactionOp {
3512 friend class VersionChangeTransaction;
3514 const IndexOrObjectStoreId mObjectStoreId;
3515 const IndexOrObjectStoreId mIndexId;
3516 const nsString mNewName;
3518 private:
3519 // Only created by VersionChangeTransaction.
3520 RenameIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3521 FullIndexMetadata& aMetadata,
3522 IndexOrObjectStoreId aObjectStoreId)
3523 : VersionChangeTransactionOp(std::move(aTransaction)),
3524 mObjectStoreId(aObjectStoreId),
3525 mIndexId(aMetadata.mCommonMetadata.id()),
3526 mNewName(aMetadata.mCommonMetadata.name()) {
3527 MOZ_ASSERT(mIndexId);
3530 ~RenameIndexOp() override = default;
3532 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3535 class NormalTransactionOp : public TransactionDatabaseOperationBase,
3536 public PBackgroundIDBRequestParent {
3537 #ifdef DEBUG
3538 bool mResponseSent;
3539 #endif
3541 public:
3542 void Cleanup() override;
3544 protected:
3545 explicit NormalTransactionOp(SafeRefPtr<TransactionBase> aTransaction)
3546 : TransactionDatabaseOperationBase(std::move(aTransaction))
3547 #ifdef DEBUG
3549 mResponseSent(false)
3550 #endif
3554 ~NormalTransactionOp() override = default;
3556 // An overload of DatabaseOperationBase's function that can avoid doing extra
3557 // work on non-versionchange transactions.
3558 mozilla::Result<bool, nsresult> ObjectStoreHasIndexes(
3559 DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId,
3560 bool aMayHaveIndexes);
3562 virtual mozilla::Result<PreprocessParams, nsresult> GetPreprocessParams();
3564 // Subclasses use this override to set the IPDL response value.
3565 virtual void GetResponse(RequestResponse& aResponse,
3566 size_t* aResponseSize) = 0;
3568 private:
3569 nsresult SendPreprocessInfo() override;
3571 nsresult SendSuccessResult() override;
3573 bool SendFailureResult(nsresult aResultCode) override;
3575 // IPDL methods.
3576 void ActorDestroy(ActorDestroyReason aWhy) override;
3578 mozilla::ipc::IPCResult RecvContinue(
3579 const PreprocessResponse& aResponse) final;
3582 // XXX Maybe we can avoid a mutex here by moving all accesses to the background
3583 // thread.
3584 StaticAutoPtr<IndexedDBCipherKeyManager> gIndexedDBCipherKeyManager;
3586 } // namespace
3588 Maybe<CipherKey> IndexedDBCipherKeyManager::Get(const nsCString& aDatabaseID,
3589 const nsCString& keyStoreID) {
3590 auto lockedPrivateBrowsingInfoHashTable =
3591 mPrivateBrowsingInfoHashTable.Lock();
3593 auto dbKeyStore = lockedPrivateBrowsingInfoHashTable->Lookup(aDatabaseID);
3594 if (!dbKeyStore) {
3595 return Nothing();
3598 return dbKeyStore->MaybeGet(keyStoreID);
3601 CipherKey IndexedDBCipherKeyManager::Ensure(const nsCString& aDatabaseID,
3602 const nsCString& keyStoreID) {
3603 auto lockedPrivateBrowsingInfoHashTable =
3604 mPrivateBrowsingInfoHashTable.Lock();
3606 auto& dbKeyStore =
3607 lockedPrivateBrowsingInfoHashTable->LookupOrInsert(aDatabaseID);
3609 return dbKeyStore.LookupOrInsertWith(keyStoreID, [] {
3610 // Generate a new key if one corresponding to keyStoreID
3611 // does not exists already.
3612 auto keyOrErr = IndexedDBCipherStrategy::GenerateKey();
3614 // Bug1800110 Propagate the error to the caller rather than asserting.
3615 MOZ_RELEASE_ASSERT(keyOrErr.isOk());
3616 return keyOrErr.unwrap();
3620 bool IndexedDBCipherKeyManager::Remove(const nsCString& aDatabaseID) {
3621 auto lockedPrivateBrowsingInfoHashTable =
3622 mPrivateBrowsingInfoHashTable.Lock();
3623 return lockedPrivateBrowsingInfoHashTable->Remove(aDatabaseID);
3626 IndexedDBCipherKeyManager* GetIndexedDBCipherKeyManager() {
3627 return gIndexedDBCipherKeyManager;
3630 namespace {
3632 class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp {
3633 friend class TransactionBase;
3635 using PersistenceType = mozilla::dom::quota::PersistenceType;
3637 class StoredFileInfo final {
3638 InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileInfo>>> mFileInfo;
3639 // Either nothing, a file actor or a non-Blob-backed inputstream to write to
3640 // disk.
3641 using FileActorOrInputStream =
3642 Variant<Nothing, RefPtr<DatabaseFile>, nsCOMPtr<nsIInputStream>>;
3643 InitializedOnce<const FileActorOrInputStream> mFileActorOrInputStream;
3644 #ifdef DEBUG
3645 const StructuredCloneFileBase::FileType mType;
3646 #endif
3647 void EnsureCipherKey();
3648 void AssertInvariants() const;
3650 StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo,
3651 RefPtr<DatabaseFile> aFileActor);
3653 StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo,
3654 nsCOMPtr<nsIInputStream> aInputStream);
3656 public:
3657 #if defined(NS_BUILD_REFCNT_LOGGING)
3658 // Only for MOZ_COUNT_CTOR.
3659 StoredFileInfo(StoredFileInfo&& aOther)
3660 : mFileInfo{std::move(aOther.mFileInfo)},
3661 mFileActorOrInputStream{std::move(aOther.mFileActorOrInputStream)}
3662 # ifdef DEBUG
3664 mType{aOther.mType}
3665 # endif
3667 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
3669 #else
3670 StoredFileInfo(StoredFileInfo&&) = default;
3671 #endif
3673 static StoredFileInfo CreateForBlob(SafeRefPtr<DatabaseFileInfo> aFileInfo,
3674 RefPtr<DatabaseFile> aFileActor);
3675 static StoredFileInfo CreateForStructuredClone(
3676 SafeRefPtr<DatabaseFileInfo> aFileInfo,
3677 nsCOMPtr<nsIInputStream> aInputStream);
3679 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
3680 ~StoredFileInfo() {
3681 AssertIsOnBackgroundThread();
3682 AssertInvariants();
3684 MOZ_COUNT_DTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
3686 #endif
3688 bool IsValid() const { return static_cast<bool>(mFileInfo); }
3690 const DatabaseFileInfo& GetFileInfo() const { return **mFileInfo; }
3692 bool ShouldCompress() const;
3694 void NotifyWriteSucceeded() const;
3696 using InputStreamResult =
3697 mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult>;
3698 InputStreamResult GetInputStream();
3700 void Serialize(nsString& aText) const;
3702 class SCInputStream;
3704 const ObjectStoreAddPutParams mParams;
3705 Maybe<UniqueIndexTable> mUniqueIndexTable;
3707 // This must be non-const so that we can update the mNextAutoIncrementId field
3708 // if we are modifying an autoIncrement objectStore.
3709 SafeRefPtr<FullObjectStoreMetadata> mMetadata;
3711 nsTArray<StoredFileInfo> mStoredFileInfos;
3713 Key mResponse;
3714 const OriginMetadata mOriginMetadata;
3715 const PersistenceType mPersistenceType;
3716 const bool mOverwrite;
3717 bool mObjectStoreMayHaveIndexes;
3718 bool mDataOverThreshold;
3720 private:
3721 // Only created by TransactionBase.
3722 ObjectStoreAddOrPutRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3723 RequestParams&& aParams);
3725 ~ObjectStoreAddOrPutRequestOp() override = default;
3727 nsresult RemoveOldIndexDataValues(DatabaseConnection* aConnection);
3729 bool Init(TransactionBase& aTransaction) override;
3731 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3733 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
3735 void Cleanup() override;
3738 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::AssertInvariants() const {
3739 // The only allowed types are eStructuredClone, eBlob and eMutableFile.
3740 MOZ_ASSERT(StructuredCloneFileBase::eStructuredClone == mType ||
3741 StructuredCloneFileBase::eBlob == mType ||
3742 StructuredCloneFileBase::eMutableFile == mType);
3744 // mFileInfo and a file actor in mFileActorOrInputStream are present until
3745 // the object is moved away, but an inputStream in mFileActorOrInputStream
3746 // can be released early.
3747 MOZ_ASSERT_IF(static_cast<bool>(mFileActorOrInputStream) &&
3748 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>(),
3749 static_cast<bool>(mFileInfo));
3751 if (mFileInfo) {
3752 // In a non-moved StoredFileInfo, one of the following is true:
3753 // - This was an overflow structured clone (eStructuredClone) and
3754 // storedFileInfo.mFileActorOrInputStream CAN be a non-nullptr input
3755 // stream (but that might have been release by ReleaseInputStream).
3756 MOZ_ASSERT_IF(
3757 StructuredCloneFileBase::eStructuredClone == mType,
3758 !mFileActorOrInputStream ||
3759 (mFileActorOrInputStream->is<nsCOMPtr<nsIInputStream>>() &&
3760 mFileActorOrInputStream->as<nsCOMPtr<nsIInputStream>>()));
3762 // - This is a reference to a Blob (eBlob) that may or may not have
3763 // already been written to disk. storedFileInfo.mFileActorOrInputStream
3764 // MUST be a non-null file actor, but its GetInputStream may return
3765 // nullptr (so don't assert on that).
3766 MOZ_ASSERT_IF(StructuredCloneFileBase::eBlob == mType,
3767 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>() &&
3768 mFileActorOrInputStream->as<RefPtr<DatabaseFile>>());
3770 // - It's a mutable file (eMutableFile). No writing will be performed,
3771 // and storedFileInfo.mFileActorOrInputStream is Nothing.
3772 MOZ_ASSERT_IF(StructuredCloneFileBase::eMutableFile == mType,
3773 mFileActorOrInputStream->is<Nothing>());
3777 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::EnsureCipherKey() {
3778 const auto& fileInfo = GetFileInfo();
3779 const auto& fileMgr = fileInfo.Manager();
3781 // no need to generate cipher keys if we are not in PBM
3782 if (!fileMgr.IsInPrivateBrowsingMode()) return;
3784 nsCString keyId;
3785 keyId.AppendInt(fileInfo.Id());
3786 gIndexedDBCipherKeyManager->Ensure(fileMgr.DatabaseID(), keyId);
3789 ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
3790 SafeRefPtr<DatabaseFileInfo> aFileInfo, RefPtr<DatabaseFile> aFileActor)
3791 : mFileInfo{WrapNotNull(std::move(aFileInfo))},
3792 mFileActorOrInputStream{std::move(aFileActor)}
3793 #ifdef DEBUG
3795 mType{StructuredCloneFileBase::eBlob}
3796 #endif
3798 AssertIsOnBackgroundThread();
3799 AssertInvariants();
3801 EnsureCipherKey();
3802 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
3805 ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
3806 SafeRefPtr<DatabaseFileInfo> aFileInfo,
3807 nsCOMPtr<nsIInputStream> aInputStream)
3808 : mFileInfo{WrapNotNull(std::move(aFileInfo))},
3809 mFileActorOrInputStream{std::move(aInputStream)}
3810 #ifdef DEBUG
3812 mType{StructuredCloneFileBase::eStructuredClone}
3813 #endif
3815 AssertIsOnBackgroundThread();
3816 AssertInvariants();
3818 EnsureCipherKey();
3819 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
3822 ObjectStoreAddOrPutRequestOp::StoredFileInfo
3823 ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForBlob(
3824 SafeRefPtr<DatabaseFileInfo> aFileInfo, RefPtr<DatabaseFile> aFileActor) {
3825 return {std::move(aFileInfo), std::move(aFileActor)};
3828 ObjectStoreAddOrPutRequestOp::StoredFileInfo
3829 ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForStructuredClone(
3830 SafeRefPtr<DatabaseFileInfo> aFileInfo,
3831 nsCOMPtr<nsIInputStream> aInputStream) {
3832 return {std::move(aFileInfo), std::move(aInputStream)};
3835 bool ObjectStoreAddOrPutRequestOp::StoredFileInfo::ShouldCompress() const {
3836 // Must not be called after moving.
3837 MOZ_ASSERT(IsValid());
3839 // Compression is only necessary for eStructuredClone, i.e. when
3840 // mFileActorOrInputStream stored an input stream. However, this is only
3841 // called after GetInputStream, when mFileActorOrInputStream has been
3842 // cleared, which is only possible for this type.
3843 const bool res = !mFileActorOrInputStream;
3844 MOZ_ASSERT(res == (StructuredCloneFileBase::eStructuredClone == mType));
3845 return res;
3848 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::NotifyWriteSucceeded()
3849 const {
3850 MOZ_ASSERT(IsValid());
3852 // For eBlob, clear the blob implementation.
3853 if (mFileActorOrInputStream &&
3854 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>()) {
3855 mFileActorOrInputStream->as<RefPtr<DatabaseFile>>()
3856 ->WriteSucceededClearBlobImpl();
3859 // For the other types, no action is necessary.
3862 ObjectStoreAddOrPutRequestOp::StoredFileInfo::InputStreamResult
3863 ObjectStoreAddOrPutRequestOp::StoredFileInfo::GetInputStream() {
3864 if (!mFileActorOrInputStream) {
3865 MOZ_ASSERT(StructuredCloneFileBase::eStructuredClone == mType);
3866 return nsCOMPtr<nsIInputStream>{};
3869 // For the different cases, see also the comments in AssertInvariants.
3870 return mFileActorOrInputStream->match(
3871 [](const Nothing&) -> InputStreamResult {
3872 return nsCOMPtr<nsIInputStream>{};
3874 [](const RefPtr<DatabaseFile>& databaseActor) -> InputStreamResult {
3875 ErrorResult rv;
3876 auto inputStream = databaseActor->GetInputStream(rv);
3877 if (NS_WARN_IF(rv.Failed())) {
3878 return Err(rv.StealNSResult());
3881 return inputStream;
3883 [this](const nsCOMPtr<nsIInputStream>& inputStream) -> InputStreamResult {
3884 auto res = inputStream;
3885 // destroy() clears the inputStream parameter, so we needed to make a
3886 // copy before
3887 mFileActorOrInputStream.destroy();
3888 AssertInvariants();
3889 return res;
3893 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::Serialize(
3894 nsString& aText) const {
3895 AssertInvariants();
3896 MOZ_ASSERT(IsValid());
3898 const int64_t id = (*mFileInfo)->Id();
3900 auto structuredCloneHandler = [&aText, id](const nsCOMPtr<nsIInputStream>&) {
3901 // eStructuredClone
3902 aText.Append('.');
3903 aText.AppendInt(id);
3906 // If mFileActorOrInputStream was moved, we had an inputStream before.
3907 if (!mFileActorOrInputStream) {
3908 structuredCloneHandler(nullptr);
3909 return;
3912 // This encoding is parsed in DeserializeStructuredCloneFile.
3913 mFileActorOrInputStream->match(
3914 [&aText, id](const Nothing&) {
3915 // eMutableFile
3916 aText.AppendInt(-id);
3918 [&aText, id](const RefPtr<DatabaseFile>&) {
3919 // eBlob
3920 aText.AppendInt(id);
3922 structuredCloneHandler);
3925 class ObjectStoreAddOrPutRequestOp::SCInputStream final
3926 : public nsIInputStream {
3927 const JSStructuredCloneData& mData;
3928 JSStructuredCloneData::Iterator mIter;
3930 public:
3931 explicit SCInputStream(const JSStructuredCloneData& aData)
3932 : mData(aData), mIter(aData.Start()) {}
3934 private:
3935 virtual ~SCInputStream() = default;
3937 NS_DECL_THREADSAFE_ISUPPORTS
3938 NS_DECL_NSIINPUTSTREAM
3941 class ObjectStoreGetRequestOp final : public NormalTransactionOp {
3942 friend class TransactionBase;
3944 const IndexOrObjectStoreId mObjectStoreId;
3945 SafeRefPtr<Database> mDatabase;
3946 const Maybe<SerializedKeyRange> mOptionalKeyRange;
3947 AutoTArray<StructuredCloneReadInfoParent, 1> mResponse;
3948 PBackgroundParent* mBackgroundParent;
3949 uint32_t mPreprocessInfoCount;
3950 const uint32_t mLimit;
3951 const bool mGetAll;
3953 private:
3954 // Only created by TransactionBase.
3955 ObjectStoreGetRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3956 const RequestParams& aParams, bool aGetAll);
3958 ~ObjectStoreGetRequestOp() override = default;
3960 template <typename T>
3961 mozilla::Result<T, nsresult> ConvertResponse(
3962 StructuredCloneReadInfoParent&& aInfo);
3964 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3966 bool HasPreprocessInfo() override;
3968 mozilla::Result<PreprocessParams, nsresult> GetPreprocessParams() override;
3970 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
3973 class ObjectStoreGetKeyRequestOp final : public NormalTransactionOp {
3974 friend class TransactionBase;
3976 const IndexOrObjectStoreId mObjectStoreId;
3977 const Maybe<SerializedKeyRange> mOptionalKeyRange;
3978 const uint32_t mLimit;
3979 const bool mGetAll;
3980 nsTArray<Key> mResponse;
3982 private:
3983 // Only created by TransactionBase.
3984 ObjectStoreGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3985 const RequestParams& aParams, bool aGetAll);
3987 ~ObjectStoreGetKeyRequestOp() override = default;
3989 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3991 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
3994 class ObjectStoreDeleteRequestOp final : public NormalTransactionOp {
3995 friend class TransactionBase;
3997 const ObjectStoreDeleteParams mParams;
3998 ObjectStoreDeleteResponse mResponse;
3999 bool mObjectStoreMayHaveIndexes;
4001 private:
4002 ObjectStoreDeleteRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4003 const ObjectStoreDeleteParams& aParams);
4005 ~ObjectStoreDeleteRequestOp() override = default;
4007 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4009 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
4010 aResponse = std::move(mResponse);
4011 *aResponseSize = 0;
4015 class ObjectStoreClearRequestOp final : public NormalTransactionOp {
4016 friend class TransactionBase;
4018 const ObjectStoreClearParams mParams;
4019 ObjectStoreClearResponse mResponse;
4020 bool mObjectStoreMayHaveIndexes;
4022 private:
4023 ObjectStoreClearRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4024 const ObjectStoreClearParams& aParams);
4026 ~ObjectStoreClearRequestOp() override = default;
4028 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4030 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
4031 aResponse = std::move(mResponse);
4032 *aResponseSize = 0;
4036 class ObjectStoreCountRequestOp final : public NormalTransactionOp {
4037 friend class TransactionBase;
4039 const ObjectStoreCountParams mParams;
4040 ObjectStoreCountResponse mResponse;
4042 private:
4043 ObjectStoreCountRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4044 const ObjectStoreCountParams& aParams)
4045 : NormalTransactionOp(std::move(aTransaction)), mParams(aParams) {}
4047 ~ObjectStoreCountRequestOp() override = default;
4049 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4051 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
4052 aResponse = std::move(mResponse);
4053 *aResponseSize = sizeof(uint64_t);
4057 class IndexRequestOpBase : public NormalTransactionOp {
4058 protected:
4059 const SafeRefPtr<FullIndexMetadata> mMetadata;
4061 protected:
4062 IndexRequestOpBase(SafeRefPtr<TransactionBase> aTransaction,
4063 const RequestParams& aParams)
4064 : NormalTransactionOp(std::move(aTransaction)),
4065 mMetadata(IndexMetadataForParams(Transaction(), aParams)) {}
4067 ~IndexRequestOpBase() override = default;
4069 private:
4070 static SafeRefPtr<FullIndexMetadata> IndexMetadataForParams(
4071 const TransactionBase& aTransaction, const RequestParams& aParams);
4074 class IndexGetRequestOp final : public IndexRequestOpBase {
4075 friend class TransactionBase;
4077 SafeRefPtr<Database> mDatabase;
4078 const Maybe<SerializedKeyRange> mOptionalKeyRange;
4079 AutoTArray<StructuredCloneReadInfoParent, 1> mResponse;
4080 PBackgroundParent* mBackgroundParent;
4081 const uint32_t mLimit;
4082 const bool mGetAll;
4084 private:
4085 // Only created by TransactionBase.
4086 IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4087 const RequestParams& aParams, bool aGetAll);
4089 ~IndexGetRequestOp() override = default;
4091 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4093 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
4096 class IndexGetKeyRequestOp final : public IndexRequestOpBase {
4097 friend class TransactionBase;
4099 const Maybe<SerializedKeyRange> mOptionalKeyRange;
4100 AutoTArray<Key, 1> mResponse;
4101 const uint32_t mLimit;
4102 const bool mGetAll;
4104 private:
4105 // Only created by TransactionBase.
4106 IndexGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4107 const RequestParams& aParams, bool aGetAll);
4109 ~IndexGetKeyRequestOp() override = default;
4111 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4113 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
4116 class IndexCountRequestOp final : public IndexRequestOpBase {
4117 friend class TransactionBase;
4119 const IndexCountParams mParams;
4120 IndexCountResponse mResponse;
4122 private:
4123 // Only created by TransactionBase.
4124 IndexCountRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4125 const RequestParams& aParams)
4126 : IndexRequestOpBase(std::move(aTransaction), aParams),
4127 mParams(aParams.get_IndexCountParams()) {}
4129 ~IndexCountRequestOp() override = default;
4131 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4133 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
4134 aResponse = std::move(mResponse);
4135 *aResponseSize = sizeof(uint64_t);
4139 template <IDBCursorType CursorType>
4140 class Cursor;
4142 constexpr IDBCursorType ToKeyOnlyType(const IDBCursorType aType) {
4143 MOZ_ASSERT(aType == IDBCursorType::ObjectStore ||
4144 aType == IDBCursorType::ObjectStoreKey ||
4145 aType == IDBCursorType::Index || aType == IDBCursorType::IndexKey);
4146 switch (aType) {
4147 case IDBCursorType::ObjectStore:
4148 case IDBCursorType::ObjectStoreKey:
4149 return IDBCursorType::ObjectStoreKey;
4150 case IDBCursorType::Index:
4151 case IDBCursorType::IndexKey:
4152 return IDBCursorType::IndexKey;
4156 template <IDBCursorType CursorType>
4157 using CursorPosition = CursorData<ToKeyOnlyType(CursorType)>;
4159 #ifdef DEBUG
4160 constexpr indexedDB::OpenCursorParams::Type ToOpenCursorParamsType(
4161 const IDBCursorType aType) {
4162 MOZ_ASSERT(aType == IDBCursorType::ObjectStore ||
4163 aType == IDBCursorType::ObjectStoreKey ||
4164 aType == IDBCursorType::Index || aType == IDBCursorType::IndexKey);
4165 switch (aType) {
4166 case IDBCursorType::ObjectStore:
4167 return indexedDB::OpenCursorParams::TObjectStoreOpenCursorParams;
4168 case IDBCursorType::ObjectStoreKey:
4169 return indexedDB::OpenCursorParams::TObjectStoreOpenKeyCursorParams;
4170 case IDBCursorType::Index:
4171 return indexedDB::OpenCursorParams::TIndexOpenCursorParams;
4172 case IDBCursorType::IndexKey:
4173 return indexedDB::OpenCursorParams::TIndexOpenKeyCursorParams;
4176 #endif
4178 class CursorBase : public PBackgroundIDBCursorParent {
4179 friend class TransactionBase;
4180 template <IDBCursorType CursorType>
4181 friend class CommonOpenOpHelper;
4183 protected:
4184 const SafeRefPtr<TransactionBase> mTransaction;
4186 // This should only be touched on the PBackground thread to check whether
4187 // the objectStore has been deleted. Holding these saves a hash lookup for
4188 // every call to continue()/advance().
4189 InitializedOnce<const NotNull<SafeRefPtr<FullObjectStoreMetadata>>>
4190 mObjectStoreMetadata;
4192 const IndexOrObjectStoreId mObjectStoreId;
4194 LazyInitializedOnce<const Key>
4195 mLocaleAwareRangeBound; ///< If the cursor is based on a key range, the
4196 ///< bound in the direction of iteration (e.g.
4197 ///< the upper bound in case of mDirection ==
4198 ///< NEXT). If the cursor is based on a key, it
4199 ///< is unset. If mLocale is set, this was
4200 ///< converted to mLocale.
4202 const Direction mDirection;
4204 const int32_t mMaxExtraCount;
4206 const bool mIsSameProcessActor;
4208 struct ConstructFromTransactionBase {};
4210 public:
4211 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::CursorBase,
4212 final)
4214 CursorBase(SafeRefPtr<TransactionBase> aTransaction,
4215 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
4216 Direction aDirection,
4217 ConstructFromTransactionBase aConstructionTag);
4219 protected:
4220 // Reference counted.
4221 ~CursorBase() override { MOZ_ASSERT(!mObjectStoreMetadata); }
4223 private:
4224 virtual bool Start(const OpenCursorParams& aParams) = 0;
4227 class IndexCursorBase : public CursorBase {
4228 public:
4229 bool IsLocaleAware() const { return !mLocale.IsEmpty(); }
4231 IndexCursorBase(SafeRefPtr<TransactionBase> aTransaction,
4232 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
4233 SafeRefPtr<FullIndexMetadata> aIndexMetadata,
4234 Direction aDirection,
4235 ConstructFromTransactionBase aConstructionTag)
4236 : CursorBase{std::move(aTransaction), std::move(aObjectStoreMetadata),
4237 aDirection, aConstructionTag},
4238 mIndexMetadata(WrapNotNull(std::move(aIndexMetadata))),
4239 mIndexId((*mIndexMetadata)->mCommonMetadata.id()),
4240 mUniqueIndex((*mIndexMetadata)->mCommonMetadata.unique()),
4241 mLocale((*mIndexMetadata)->mCommonMetadata.locale()) {}
4243 protected:
4244 IndexOrObjectStoreId Id() const { return mIndexId; }
4246 // This should only be touched on the PBackground thread to check whether
4247 // the index has been deleted. Holding these saves a hash lookup for every
4248 // call to continue()/advance().
4249 InitializedOnce<const NotNull<SafeRefPtr<FullIndexMetadata>>> mIndexMetadata;
4250 const IndexOrObjectStoreId mIndexId;
4251 const bool mUniqueIndex;
4252 const nsCString
4253 mLocale; ///< The locale if the cursor is locale-aware, otherwise empty.
4255 struct ContinueQueries {
4256 nsCString mContinueQuery;
4257 nsCString mContinueToQuery;
4258 nsCString mContinuePrimaryKeyQuery;
4260 const nsACString& GetContinueQuery(const bool hasContinueKey,
4261 const bool hasContinuePrimaryKey) const {
4262 return hasContinuePrimaryKey ? mContinuePrimaryKeyQuery
4263 : hasContinueKey ? mContinueToQuery
4264 : mContinueQuery;
4269 class ObjectStoreCursorBase : public CursorBase {
4270 public:
4271 using CursorBase::CursorBase;
4273 static constexpr bool IsLocaleAware() { return false; }
4275 protected:
4276 IndexOrObjectStoreId Id() const { return mObjectStoreId; }
4278 struct ContinueQueries {
4279 nsCString mContinueQuery;
4280 nsCString mContinueToQuery;
4282 const nsACString& GetContinueQuery(const bool hasContinueKey,
4283 const bool hasContinuePrimaryKey) const {
4284 MOZ_ASSERT(!hasContinuePrimaryKey);
4285 return hasContinueKey ? mContinueToQuery : mContinueQuery;
4290 using FilesArray = nsTArray<nsTArray<StructuredCloneFileParent>>;
4292 struct PseudoFilesArray {
4293 static constexpr bool IsEmpty() { return true; }
4295 static constexpr void Clear() {}
4298 template <IDBCursorType CursorType>
4299 using FilesArrayT =
4300 std::conditional_t<!CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
4301 FilesArray, PseudoFilesArray>;
4303 class ValueCursorBase {
4304 friend struct ValuePopulateResponseHelper<true>;
4305 friend struct ValuePopulateResponseHelper<false>;
4307 protected:
4308 explicit ValueCursorBase(TransactionBase* const aTransaction)
4309 : mDatabase(aTransaction->GetDatabasePtr()),
4310 mFileManager(mDatabase->GetFileManagerPtr()),
4311 mBackgroundParent(WrapNotNull(aTransaction->GetBackgroundParent())) {
4312 MOZ_ASSERT(mDatabase);
4315 void ProcessFiles(CursorResponse& aResponse, const FilesArray& aFiles);
4317 ~ValueCursorBase() { MOZ_ASSERT(!mBackgroundParent); }
4319 const SafeRefPtr<Database> mDatabase;
4320 const NotNull<SafeRefPtr<DatabaseFileManager>> mFileManager;
4322 InitializedOnce<const NotNull<PBackgroundParent*>> mBackgroundParent;
4325 class KeyCursorBase {
4326 protected:
4327 explicit KeyCursorBase(TransactionBase* const /*aTransaction*/) {}
4329 static constexpr void ProcessFiles(CursorResponse& aResponse,
4330 const PseudoFilesArray& aFiles) {}
4333 template <IDBCursorType CursorType>
4334 class CursorOpBaseHelperBase;
4336 template <IDBCursorType CursorType>
4337 class Cursor final
4338 : public std::conditional_t<
4339 CursorTypeTraits<CursorType>::IsObjectStoreCursor,
4340 ObjectStoreCursorBase, IndexCursorBase>,
4341 public std::conditional_t<CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
4342 KeyCursorBase, ValueCursorBase> {
4343 using Base =
4344 std::conditional_t<CursorTypeTraits<CursorType>::IsObjectStoreCursor,
4345 ObjectStoreCursorBase, IndexCursorBase>;
4347 using KeyValueBase =
4348 std::conditional_t<CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
4349 KeyCursorBase, ValueCursorBase>;
4351 static constexpr bool IsIndexCursor =
4352 !CursorTypeTraits<CursorType>::IsObjectStoreCursor;
4354 static constexpr bool IsValueCursor =
4355 !CursorTypeTraits<CursorType>::IsKeyOnlyCursor;
4357 class CursorOpBase;
4358 class OpenOp;
4359 class ContinueOp;
4361 using Base::Id;
4362 using CursorBase::Manager;
4363 using CursorBase::mDirection;
4364 using CursorBase::mObjectStoreId;
4365 using CursorBase::mTransaction;
4366 using typename CursorBase::ActorDestroyReason;
4368 using TypedOpenOpHelper =
4369 std::conditional_t<IsIndexCursor, IndexOpenOpHelper<CursorType>,
4370 ObjectStoreOpenOpHelper<CursorType>>;
4372 friend class CursorOpBaseHelperBase<CursorType>;
4373 friend class CommonOpenOpHelper<CursorType>;
4374 friend TypedOpenOpHelper;
4375 friend class OpenOpHelper<CursorType>;
4377 CursorOpBase* mCurrentlyRunningOp = nullptr;
4379 LazyInitializedOnce<const typename Base::ContinueQueries> mContinueQueries;
4381 // Only called by TransactionBase.
4382 bool Start(const OpenCursorParams& aParams) final;
4384 void SendResponseInternal(CursorResponse& aResponse,
4385 const FilesArrayT<CursorType>& aFiles);
4387 // Must call SendResponseInternal!
4388 bool SendResponse(const CursorResponse& aResponse) = delete;
4390 // IPDL methods.
4391 void ActorDestroy(ActorDestroyReason aWhy) override;
4393 mozilla::ipc::IPCResult RecvDeleteMe() override;
4395 mozilla::ipc::IPCResult RecvContinue(
4396 const CursorRequestParams& aParams, const Key& aCurrentKey,
4397 const Key& aCurrentObjectStoreKey) override;
4399 public:
4400 Cursor(SafeRefPtr<TransactionBase> aTransaction,
4401 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
4402 SafeRefPtr<FullIndexMetadata> aIndexMetadata,
4403 typename Base::Direction aDirection,
4404 typename Base::ConstructFromTransactionBase aConstructionTag)
4405 : Base{std::move(aTransaction), std::move(aObjectStoreMetadata),
4406 std::move(aIndexMetadata), aDirection, aConstructionTag},
4407 KeyValueBase{this->mTransaction.unsafeGetRawPtr()} {}
4409 Cursor(SafeRefPtr<TransactionBase> aTransaction,
4410 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
4411 typename Base::Direction aDirection,
4412 typename Base::ConstructFromTransactionBase aConstructionTag)
4413 : Base{std::move(aTransaction), std::move(aObjectStoreMetadata),
4414 aDirection, aConstructionTag},
4415 KeyValueBase{this->mTransaction.unsafeGetRawPtr()} {}
4417 private:
4418 void SetOptionalKeyRange(const Maybe<SerializedKeyRange>& aOptionalKeyRange,
4419 bool* aOpen);
4421 bool VerifyRequestParams(const CursorRequestParams& aParams,
4422 const CursorPosition<CursorType>& aPosition) const;
4424 ~Cursor() final = default;
4427 template <IDBCursorType CursorType>
4428 class Cursor<CursorType>::CursorOpBase
4429 : public TransactionDatabaseOperationBase {
4430 friend class CursorOpBaseHelperBase<CursorType>;
4432 protected:
4433 RefPtr<Cursor> mCursor;
4434 FilesArrayT<CursorType> mFiles; // TODO: Consider removing this member
4435 // entirely if we are no value cursor.
4437 CursorResponse mResponse;
4439 #ifdef DEBUG
4440 bool mResponseSent;
4441 #endif
4443 protected:
4444 explicit CursorOpBase(Cursor* aCursor)
4445 : TransactionDatabaseOperationBase(aCursor->mTransaction.clonePtr()),
4446 mCursor(aCursor)
4447 #ifdef DEBUG
4449 mResponseSent(false)
4450 #endif
4452 AssertIsOnBackgroundThread();
4453 MOZ_ASSERT(aCursor);
4456 ~CursorOpBase() override = default;
4458 bool SendFailureResult(nsresult aResultCode) final;
4459 nsresult SendSuccessResult() final;
4461 void Cleanup() override;
4464 template <IDBCursorType CursorType>
4465 class OpenOpHelper;
4467 using ResponseSizeOrError = Result<size_t, nsresult>;
4469 template <IDBCursorType CursorType>
4470 class CursorOpBaseHelperBase {
4471 public:
4472 explicit CursorOpBaseHelperBase(
4473 typename Cursor<CursorType>::CursorOpBase& aOp)
4474 : mOp{aOp} {}
4476 ResponseSizeOrError PopulateResponseFromStatement(mozIStorageStatement* aStmt,
4477 bool aInitializeResponse,
4478 Key* const aOptOutSortKey);
4480 void PopulateExtraResponses(mozIStorageStatement* aStmt,
4481 uint32_t aMaxExtraCount,
4482 const size_t aInitialResponseSize,
4483 const nsACString& aOperation,
4484 Key* const aOptPreviousSortKey);
4486 protected:
4487 Cursor<CursorType>& GetCursor() {
4488 MOZ_ASSERT(mOp.mCursor);
4489 return *mOp.mCursor;
4492 void SetResponse(CursorResponse aResponse) {
4493 mOp.mResponse = std::move(aResponse);
4496 protected:
4497 typename Cursor<CursorType>::CursorOpBase& mOp;
4500 class CommonOpenOpHelperBase {
4501 protected:
4502 static void AppendConditionClause(const nsACString& aColumnName,
4503 const nsACString& aStatementParameterName,
4504 bool aLessThan, bool aEquals,
4505 nsCString& aResult);
4508 template <IDBCursorType CursorType>
4509 class CommonOpenOpHelper : public CursorOpBaseHelperBase<CursorType>,
4510 protected CommonOpenOpHelperBase {
4511 public:
4512 explicit CommonOpenOpHelper(typename Cursor<CursorType>::OpenOp& aOp)
4513 : CursorOpBaseHelperBase<CursorType>{aOp} {}
4515 protected:
4516 using CursorOpBaseHelperBase<CursorType>::GetCursor;
4517 using CursorOpBaseHelperBase<CursorType>::PopulateExtraResponses;
4518 using CursorOpBaseHelperBase<CursorType>::PopulateResponseFromStatement;
4519 using CursorOpBaseHelperBase<CursorType>::SetResponse;
4521 const Maybe<SerializedKeyRange>& GetOptionalKeyRange() const {
4522 // This downcast is safe, since we initialized mOp from an OpenOp in the
4523 // ctor.
4524 return static_cast<typename Cursor<CursorType>::OpenOp&>(this->mOp)
4525 .mOptionalKeyRange;
4528 nsresult ProcessStatementSteps(mozIStorageStatement* aStmt);
4531 template <IDBCursorType CursorType>
4532 class ObjectStoreOpenOpHelper : protected CommonOpenOpHelper<CursorType> {
4533 public:
4534 using CommonOpenOpHelper<CursorType>::CommonOpenOpHelper;
4536 protected:
4537 using CommonOpenOpHelper<CursorType>::GetCursor;
4538 using CommonOpenOpHelper<CursorType>::GetOptionalKeyRange;
4539 using CommonOpenOpHelper<CursorType>::AppendConditionClause;
4541 void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
4542 const nsACString& aQueryStart);
4545 template <IDBCursorType CursorType>
4546 class IndexOpenOpHelper : protected CommonOpenOpHelper<CursorType> {
4547 public:
4548 using CommonOpenOpHelper<CursorType>::CommonOpenOpHelper;
4550 protected:
4551 using CommonOpenOpHelper<CursorType>::GetCursor;
4552 using CommonOpenOpHelper<CursorType>::GetOptionalKeyRange;
4553 using CommonOpenOpHelper<CursorType>::AppendConditionClause;
4555 void PrepareIndexKeyConditionClause(
4556 const nsACString& aDirectionClause,
4557 const nsLiteralCString& aObjectDataKeyPrefix, nsAutoCString aQueryStart);
4560 template <>
4561 class OpenOpHelper<IDBCursorType::ObjectStore>
4562 : public ObjectStoreOpenOpHelper<IDBCursorType::ObjectStore> {
4563 public:
4564 using ObjectStoreOpenOpHelper<
4565 IDBCursorType::ObjectStore>::ObjectStoreOpenOpHelper;
4567 nsresult DoDatabaseWork(DatabaseConnection* aConnection);
4570 template <>
4571 class OpenOpHelper<IDBCursorType::ObjectStoreKey>
4572 : public ObjectStoreOpenOpHelper<IDBCursorType::ObjectStoreKey> {
4573 public:
4574 using ObjectStoreOpenOpHelper<
4575 IDBCursorType::ObjectStoreKey>::ObjectStoreOpenOpHelper;
4577 nsresult DoDatabaseWork(DatabaseConnection* aConnection);
4580 template <>
4581 class OpenOpHelper<IDBCursorType::Index>
4582 : IndexOpenOpHelper<IDBCursorType::Index> {
4583 private:
4584 void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
4585 nsAutoCString aQueryStart) {
4586 PrepareIndexKeyConditionClause(aDirectionClause, "index_table."_ns,
4587 std::move(aQueryStart));
4590 public:
4591 using IndexOpenOpHelper<IDBCursorType::Index>::IndexOpenOpHelper;
4593 nsresult DoDatabaseWork(DatabaseConnection* aConnection);
4596 template <>
4597 class OpenOpHelper<IDBCursorType::IndexKey>
4598 : IndexOpenOpHelper<IDBCursorType::IndexKey> {
4599 private:
4600 void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
4601 nsAutoCString aQueryStart) {
4602 PrepareIndexKeyConditionClause(aDirectionClause, ""_ns,
4603 std::move(aQueryStart));
4606 public:
4607 using IndexOpenOpHelper<IDBCursorType::IndexKey>::IndexOpenOpHelper;
4609 nsresult DoDatabaseWork(DatabaseConnection* aConnection);
4612 template <IDBCursorType CursorType>
4613 class Cursor<CursorType>::OpenOp final : public CursorOpBase {
4614 friend class Cursor<CursorType>;
4615 friend class CommonOpenOpHelper<CursorType>;
4617 const Maybe<SerializedKeyRange> mOptionalKeyRange;
4619 using CursorOpBase::mCursor;
4620 using CursorOpBase::mResponse;
4622 // Only created by Cursor.
4623 OpenOp(Cursor* const aCursor,
4624 const Maybe<SerializedKeyRange>& aOptionalKeyRange)
4625 : CursorOpBase(aCursor), mOptionalKeyRange(aOptionalKeyRange) {}
4627 // Reference counted.
4628 ~OpenOp() override = default;
4630 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4633 template <IDBCursorType CursorType>
4634 class Cursor<CursorType>::ContinueOp final
4635 : public Cursor<CursorType>::CursorOpBase {
4636 friend class Cursor<CursorType>;
4638 using CursorOpBase::mCursor;
4639 using CursorOpBase::mResponse;
4640 const CursorRequestParams mParams;
4642 // Only created by Cursor.
4643 ContinueOp(Cursor* const aCursor, CursorRequestParams aParams,
4644 CursorPosition<CursorType> aPosition)
4645 : CursorOpBase(aCursor),
4646 mParams(std::move(aParams)),
4647 mCurrentPosition{std::move(aPosition)} {
4648 MOZ_ASSERT(mParams.type() != CursorRequestParams::T__None);
4651 // Reference counted.
4652 ~ContinueOp() override = default;
4654 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4656 const CursorPosition<CursorType> mCurrentPosition;
4659 class Utils final : public PBackgroundIndexedDBUtilsParent {
4660 #ifdef DEBUG
4661 bool mActorDestroyed;
4662 #endif
4664 public:
4665 Utils();
4667 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Utils)
4669 private:
4670 // Reference counted.
4671 ~Utils() override;
4673 // IPDL methods are only called by IPDL.
4674 void ActorDestroy(ActorDestroyReason aWhy) override;
4676 mozilla::ipc::IPCResult RecvDeleteMe() override;
4678 mozilla::ipc::IPCResult RecvGetFileReferences(
4679 const PersistenceType& aPersistenceType, const nsACString& aOrigin,
4680 const nsAString& aDatabaseName, const int64_t& aFileId, int32_t* aRefCnt,
4681 int32_t* aDBRefCnt, bool* aResult) override;
4684 /*******************************************************************************
4685 * Other class declarations
4686 ******************************************************************************/
4688 struct DatabaseActorInfo final {
4689 friend class mozilla::DefaultDelete<DatabaseActorInfo>;
4691 SafeRefPtr<FullDatabaseMetadata> mMetadata;
4692 nsTArray<NotNull<CheckedUnsafePtr<Database>>> mLiveDatabases;
4693 RefPtr<FactoryOp> mWaitingFactoryOp;
4695 DatabaseActorInfo(SafeRefPtr<FullDatabaseMetadata> aMetadata,
4696 NotNull<Database*> aDatabase)
4697 : mMetadata(std::move(aMetadata)) {
4698 MOZ_COUNT_CTOR(DatabaseActorInfo);
4700 mLiveDatabases.AppendElement(aDatabase);
4703 private:
4704 ~DatabaseActorInfo() {
4705 MOZ_ASSERT(mLiveDatabases.IsEmpty());
4706 MOZ_ASSERT(!mWaitingFactoryOp || !mWaitingFactoryOp->HasBlockedDatabases());
4708 MOZ_COUNT_DTOR(DatabaseActorInfo);
4712 class DatabaseLoggingInfo final {
4713 #ifdef DEBUG
4714 // Just for potential warnings.
4715 friend class Factory;
4716 #endif
4718 LoggingInfo mLoggingInfo;
4720 public:
4721 explicit DatabaseLoggingInfo(const LoggingInfo& aLoggingInfo)
4722 : mLoggingInfo(aLoggingInfo) {
4723 AssertIsOnBackgroundThread();
4724 MOZ_ASSERT(aLoggingInfo.nextTransactionSerialNumber());
4725 MOZ_ASSERT(aLoggingInfo.nextVersionChangeTransactionSerialNumber());
4726 MOZ_ASSERT(aLoggingInfo.nextRequestSerialNumber());
4729 const nsID& Id() const {
4730 AssertIsOnBackgroundThread();
4732 return mLoggingInfo.backgroundChildLoggingId();
4735 int64_t NextTransactionSN(IDBTransaction::Mode aMode) {
4736 AssertIsOnBackgroundThread();
4737 MOZ_ASSERT(mLoggingInfo.nextTransactionSerialNumber() < INT64_MAX);
4738 MOZ_ASSERT(mLoggingInfo.nextVersionChangeTransactionSerialNumber() >
4739 INT64_MIN);
4741 if (aMode == IDBTransaction::Mode::VersionChange) {
4742 return mLoggingInfo.nextVersionChangeTransactionSerialNumber()--;
4745 return mLoggingInfo.nextTransactionSerialNumber()++;
4748 uint64_t NextRequestSN() {
4749 AssertIsOnBackgroundThread();
4750 MOZ_ASSERT(mLoggingInfo.nextRequestSerialNumber() < UINT64_MAX);
4752 return mLoggingInfo.nextRequestSerialNumber()++;
4755 NS_INLINE_DECL_REFCOUNTING(DatabaseLoggingInfo)
4757 private:
4758 ~DatabaseLoggingInfo();
4761 class QuotaClient final : public mozilla::dom::quota::Client {
4762 static QuotaClient* sInstance;
4764 nsCOMPtr<nsIEventTarget> mBackgroundThread;
4765 nsCOMPtr<nsITimer> mDeleteTimer;
4766 nsTArray<RefPtr<Maintenance>> mMaintenanceQueue;
4767 RefPtr<Maintenance> mCurrentMaintenance;
4768 RefPtr<nsThreadPool> mMaintenanceThreadPool;
4769 nsClassHashtable<nsRefPtrHashKey<DatabaseFileManager>, nsTArray<int64_t>>
4770 mPendingDeleteInfos;
4772 public:
4773 QuotaClient();
4775 static QuotaClient* GetInstance() {
4776 AssertIsOnBackgroundThread();
4778 return sInstance;
4781 nsIEventTarget* BackgroundThread() const {
4782 MOZ_ASSERT(mBackgroundThread);
4783 return mBackgroundThread;
4786 nsresult AsyncDeleteFile(DatabaseFileManager* aFileManager, int64_t aFileId);
4788 nsresult FlushPendingFileDeletions();
4790 RefPtr<Maintenance> GetCurrentMaintenance() const {
4791 return mCurrentMaintenance;
4794 void NoteFinishedMaintenance(Maintenance* aMaintenance) {
4795 AssertIsOnBackgroundThread();
4796 MOZ_ASSERT(aMaintenance);
4797 MOZ_ASSERT(mCurrentMaintenance == aMaintenance);
4799 mCurrentMaintenance = nullptr;
4801 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::IDB,
4802 "Maintenance finished"_ns);
4804 ProcessMaintenanceQueue();
4807 nsThreadPool* GetOrCreateThreadPool();
4809 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::QuotaClient,
4810 override)
4812 mozilla::dom::quota::Client::Type GetType() override;
4814 nsresult UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) override;
4816 nsresult UpgradeStorageFrom2_1To2_2(nsIFile* aDirectory) override;
4818 Result<UsageInfo, nsresult> InitOrigin(PersistenceType aPersistenceType,
4819 const OriginMetadata& aOriginMetadata,
4820 const AtomicBool& aCanceled) override;
4822 nsresult InitOriginWithoutTracking(PersistenceType aPersistenceType,
4823 const OriginMetadata& aOriginMetadata,
4824 const AtomicBool& aCanceled) override;
4826 Result<UsageInfo, nsresult> GetUsageForOrigin(
4827 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
4828 const AtomicBool& aCanceled) override;
4830 void OnOriginClearCompleted(PersistenceType aPersistenceType,
4831 const nsACString& aOrigin) override;
4833 void ReleaseIOThreadObjects() override;
4835 void AbortOperationsForLocks(
4836 const DirectoryLockIdTable& aDirectoryLockIds) override;
4838 void AbortOperationsForProcess(ContentParentId aContentParentId) override;
4840 void AbortAllOperations() override;
4842 void StartIdleMaintenance() override;
4844 void StopIdleMaintenance() override;
4846 private:
4847 ~QuotaClient() override;
4849 void InitiateShutdown() override;
4850 bool IsShutdownCompleted() const override;
4851 nsCString GetShutdownStatus() const override;
4852 void ForceKillActors() override;
4853 void FinalizeShutdown() override;
4855 static void DeleteTimerCallback(nsITimer* aTimer, void* aClosure);
4857 Result<nsCOMPtr<nsIFile>, nsresult> GetDirectory(
4858 PersistenceType aPersistenceType, const nsACString& aOrigin);
4860 struct SubdirectoriesToProcessAndDatabaseFilenames {
4861 AutoTArray<nsString, 20> subdirsToProcess;
4862 nsTHashSet<nsString> databaseFilenames{20};
4865 struct SubdirectoriesToProcessAndDatabaseFilenamesAndObsoleteFilenames {
4866 AutoTArray<nsString, 20> subdirsToProcess;
4867 nsTHashSet<nsString> databaseFilenames{20};
4868 nsTHashSet<nsString> obsoleteFilenames{20};
4871 enum class ObsoleteFilenamesHandling { Include, Omit };
4873 template <ObsoleteFilenamesHandling ObsoleteFilenames>
4874 using GetDatabaseFilenamesResult = std::conditional_t<
4875 ObsoleteFilenames == ObsoleteFilenamesHandling::Include,
4876 SubdirectoriesToProcessAndDatabaseFilenamesAndObsoleteFilenames,
4877 SubdirectoriesToProcessAndDatabaseFilenames>;
4879 // Returns a two-part or three-part structure:
4881 // The first part is an array of subdirectories to process.
4883 // The second part is a hashtable of database filenames.
4885 // When ObsoleteFilenames is ObsoleteFilenamesHandling::Include, will also
4886 // collect files based on the marker files. For now,
4887 // GetUsageForOriginInternal() is the only consumer of this result because it
4888 // checks those unfinished deletion and clean them up after that.
4889 template <ObsoleteFilenamesHandling ObsoleteFilenames =
4890 ObsoleteFilenamesHandling::Omit>
4891 Result<GetDatabaseFilenamesResult<ObsoleteFilenames>, nsresult>
4892 GetDatabaseFilenames(nsIFile& aDirectory, const AtomicBool& aCanceled);
4894 nsresult GetUsageForOriginInternal(PersistenceType aPersistenceType,
4895 const OriginMetadata& aOriginMetadata,
4896 const AtomicBool& aCanceled,
4897 bool aInitializing, UsageInfo* aUsageInfo);
4899 // Runs on the PBackground thread. Checks to see if there's a queued
4900 // Maintenance to run.
4901 void ProcessMaintenanceQueue();
4904 class DeleteFilesRunnable final : public Runnable,
4905 public OpenDirectoryListener {
4906 using DirectoryLock = mozilla::dom::quota::DirectoryLock;
4908 enum State {
4909 // Just created on the PBackground thread. Next step is
4910 // State_DirectoryOpenPending.
4911 State_Initial,
4913 // Waiting for directory open allowed on the main thread. The next step is
4914 // State_DatabaseWorkOpen.
4915 State_DirectoryOpenPending,
4917 // Waiting to do/doing work on the QuotaManager IO thread. The next step is
4918 // State_UnblockingOpen.
4919 State_DatabaseWorkOpen,
4921 // Notifying the QuotaManager that it can proceed to the next operation on
4922 // the main thread. Next step is State_Completed.
4923 State_UnblockingOpen,
4925 // All done.
4926 State_Completed
4929 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
4930 SafeRefPtr<DatabaseFileManager> mFileManager;
4931 RefPtr<DirectoryLock> mDirectoryLock;
4932 nsTArray<int64_t> mFileIds;
4933 State mState;
4935 public:
4936 DeleteFilesRunnable(SafeRefPtr<DatabaseFileManager> aFileManager,
4937 nsTArray<int64_t>&& aFileIds);
4939 void RunImmediately();
4941 private:
4942 ~DeleteFilesRunnable() = default;
4944 void Open();
4946 void DoDatabaseWork();
4948 void Finish();
4950 void UnblockOpen();
4952 NS_DECL_ISUPPORTS_INHERITED
4953 NS_DECL_NSIRUNNABLE
4955 // OpenDirectoryListener overrides.
4956 virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
4958 virtual void DirectoryLockFailed() override;
4961 class Maintenance final : public Runnable, public OpenDirectoryListener {
4962 struct DirectoryInfo final {
4963 InitializedOnce<const FullOriginMetadata> mFullOriginMetadata;
4964 InitializedOnce<const nsTArray<nsString>> mDatabasePaths;
4965 const PersistenceType mPersistenceType;
4967 DirectoryInfo(PersistenceType aPersistenceType,
4968 FullOriginMetadata aFullOriginMetadata,
4969 nsTArray<nsString>&& aDatabasePaths);
4971 DirectoryInfo(const DirectoryInfo& aOther) = delete;
4972 DirectoryInfo(DirectoryInfo&& aOther) = delete;
4974 ~DirectoryInfo() { MOZ_COUNT_DTOR(Maintenance::DirectoryInfo); }
4977 enum class State {
4978 // Newly created on the PBackground thread. Will proceed immediately or be
4979 // added to the maintenance queue. The next step is either
4980 // DirectoryOpenPending if IndexedDatabaseManager is running, or
4981 // CreateIndexedDatabaseManager if not.
4982 Initial = 0,
4984 // Create IndexedDatabaseManager on the main thread. The next step is either
4985 // Finishing if IndexedDatabaseManager initialization fails, or
4986 // IndexedDatabaseManagerOpen if initialization succeeds.
4987 CreateIndexedDatabaseManager,
4989 // Call OpenDirectory() on the PBackground thread. The next step is
4990 // DirectoryOpenPending.
4991 IndexedDatabaseManagerOpen,
4993 // Waiting for directory open allowed on the PBackground thread. The next
4994 // step is either Finishing if directory lock failed to acquire, or
4995 // DirectoryWorkOpen if directory lock is acquired.
4996 DirectoryOpenPending,
4998 // Waiting to do/doing work on the QuotaManager IO thread. The next step is
4999 // BeginDatabaseMaintenance.
5000 DirectoryWorkOpen,
5002 // Dispatching a runnable for each database on the PBackground thread. The
5003 // next state is either WaitingForDatabaseMaintenancesToComplete if at least
5004 // one runnable has been dispatched, or Finishing otherwise.
5005 BeginDatabaseMaintenance,
5007 // Waiting for DatabaseMaintenance to finish on maintenance thread pool.
5008 // The next state is Finishing if the last runnable has finished.
5009 WaitingForDatabaseMaintenancesToComplete,
5011 // Waiting to finish/finishing on the PBackground thread. The next step is
5012 // Completed.
5013 Finishing,
5015 // All done.
5016 Complete
5019 RefPtr<QuotaClient> mQuotaClient;
5020 PRTime mStartTime;
5021 RefPtr<UniversalDirectoryLock> mPendingDirectoryLock;
5022 RefPtr<UniversalDirectoryLock> mDirectoryLock;
5023 nsTArray<DirectoryInfo> mDirectoryInfos;
5024 nsTHashMap<nsStringHashKey, DatabaseMaintenance*> mDatabaseMaintenances;
5025 nsresult mResultCode;
5026 Atomic<bool> mAborted;
5027 State mState;
5029 public:
5030 explicit Maintenance(QuotaClient* aQuotaClient)
5031 : Runnable("dom::indexedDB::Maintenance"),
5032 mQuotaClient(aQuotaClient),
5033 mStartTime(PR_Now()),
5034 mResultCode(NS_OK),
5035 mAborted(false),
5036 mState(State::Initial) {
5037 AssertIsOnBackgroundThread();
5038 MOZ_ASSERT(aQuotaClient);
5039 MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
5040 MOZ_ASSERT(mStartTime);
5043 nsIEventTarget* BackgroundThread() const {
5044 MOZ_ASSERT(mQuotaClient);
5045 return mQuotaClient->BackgroundThread();
5048 PRTime StartTime() const { return mStartTime; }
5050 bool IsAborted() const { return mAborted; }
5052 void RunImmediately() {
5053 MOZ_ASSERT(mState == State::Initial);
5055 Unused << this->Run();
5058 void Abort() {
5059 AssertIsOnBackgroundThread();
5061 mAborted = true;
5064 void RegisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
5066 void UnregisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
5068 RefPtr<DatabaseMaintenance> GetDatabaseMaintenance(
5069 const nsAString& aDatabasePath) const {
5070 AssertIsOnBackgroundThread();
5072 return mDatabaseMaintenances.Get(aDatabasePath);
5075 void Stringify(nsACString& aResult) const;
5077 private:
5078 ~Maintenance() override {
5079 MOZ_ASSERT(mState == State::Complete);
5080 MOZ_ASSERT(!mDatabaseMaintenances.Count());
5083 // Runs on the PBackground thread. Checks if IndexedDatabaseManager is
5084 // running. Calls OpenDirectory() or dispatches to the main thread on which
5085 // CreateIndexedDatabaseManager() is called.
5086 nsresult Start();
5088 // Runs on the main thread. Once IndexedDatabaseManager is created it will
5089 // dispatch to the PBackground thread on which OpenDirectory() is called.
5090 nsresult CreateIndexedDatabaseManager();
5092 // Runs on the PBackground thread. Once QuotaManager has given a lock it will
5093 // call DirectoryOpen().
5094 nsresult OpenDirectory();
5096 // Runs on the PBackground thread. Dispatches to the QuotaManager I/O thread.
5097 nsresult DirectoryOpen();
5099 // Runs on the QuotaManager I/O thread. Once it finds databases it will
5100 // dispatch to the PBackground thread on which BeginDatabaseMaintenance()
5101 // is called.
5102 nsresult DirectoryWork();
5104 // Runs on the PBackground thread. It dispatches a runnable for each database.
5105 nsresult BeginDatabaseMaintenance();
5107 // Runs on the PBackground thread. Called when the maintenance is finished or
5108 // if any of above methods fails.
5109 void Finish();
5111 // We need to declare refcounting unconditionally, because
5112 // OpenDirectoryListener has pure-virtual refcounting.
5113 NS_DECL_ISUPPORTS_INHERITED
5115 NS_DECL_NSIRUNNABLE
5117 // OpenDirectoryListener overrides.
5118 void DirectoryLockAcquired(DirectoryLock* aLock) override;
5120 void DirectoryLockFailed() override;
5123 Maintenance::DirectoryInfo::DirectoryInfo(
5124 PersistenceType aPersistenceType, FullOriginMetadata aFullOriginMetadata,
5125 nsTArray<nsString>&& aDatabasePaths)
5126 : mFullOriginMetadata(std::move(aFullOriginMetadata)),
5127 mDatabasePaths(std::move(aDatabasePaths)),
5128 mPersistenceType(aPersistenceType) {
5129 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
5130 MOZ_ASSERT(!mFullOriginMetadata->mGroup.IsEmpty());
5131 MOZ_ASSERT(!mFullOriginMetadata->mOrigin.IsEmpty());
5132 #ifdef DEBUG
5133 MOZ_ASSERT(!mDatabasePaths->IsEmpty());
5134 for (const nsAString& databasePath : *mDatabasePaths) {
5135 MOZ_ASSERT(!databasePath.IsEmpty());
5137 #endif
5139 MOZ_COUNT_CTOR(Maintenance::DirectoryInfo);
5142 class DatabaseMaintenance final : public Runnable {
5143 // The minimum amount of time that has passed since the last vacuum before we
5144 // will attempt to analyze the database for fragmentation.
5145 static const PRTime kMinVacuumAge =
5146 PRTime(PR_USEC_PER_SEC) * 60 * 60 * 24 * 7;
5148 // If the percent of database pages that are not in contiguous order is higher
5149 // than this percentage we will attempt a vacuum.
5150 static const int32_t kPercentUnorderedThreshold = 30;
5152 // If the percent of file size growth since the last vacuum is higher than
5153 // this percentage we will attempt a vacuum.
5154 static const int32_t kPercentFileSizeGrowthThreshold = 10;
5156 // The number of freelist pages beyond which we will favor an incremental
5157 // vacuum over a full vacuum.
5158 static const int32_t kMaxFreelistThreshold = 5;
5160 // If the percent of unused file bytes in the database exceeds this percentage
5161 // then we will attempt a full vacuum.
5162 static const int32_t kPercentUnusedThreshold = 20;
5164 class AutoProgressHandler;
5166 enum class MaintenanceAction { Nothing = 0, IncrementalVacuum, FullVacuum };
5168 RefPtr<Maintenance> mMaintenance;
5169 RefPtr<DirectoryLock> mDirectoryLock;
5170 const OriginMetadata mOriginMetadata;
5171 const nsString mDatabasePath;
5172 int64_t mDirectoryLockId;
5173 nsCOMPtr<nsIRunnable> mCompleteCallback;
5174 const PersistenceType mPersistenceType;
5175 const Maybe<CipherKey> mMaybeKey;
5177 public:
5178 DatabaseMaintenance(Maintenance* aMaintenance, DirectoryLock* aDirectoryLock,
5179 PersistenceType aPersistenceType,
5180 const OriginMetadata& aOriginMetadata,
5181 const nsAString& aDatabasePath,
5182 const Maybe<CipherKey>& aMaybeKey)
5183 : Runnable("dom::indexedDB::DatabaseMaintenance"),
5184 mMaintenance(aMaintenance),
5185 mDirectoryLock(aDirectoryLock),
5186 mOriginMetadata(aOriginMetadata),
5187 mDatabasePath(aDatabasePath),
5188 mPersistenceType(aPersistenceType),
5189 mMaybeKey{aMaybeKey} {
5190 MOZ_ASSERT(aDirectoryLock);
5192 MOZ_ASSERT(mDirectoryLock->Id() >= 0);
5193 mDirectoryLockId = mDirectoryLock->Id();
5196 const nsAString& DatabasePath() const { return mDatabasePath; }
5198 void WaitForCompletion(nsIRunnable* aCallback) {
5199 AssertIsOnBackgroundThread();
5200 MOZ_ASSERT(!mCompleteCallback);
5202 mCompleteCallback = aCallback;
5205 void Stringify(nsACString& aResult) const;
5207 private:
5208 ~DatabaseMaintenance() override = default;
5210 // Runs on maintenance thread pool. Does maintenance on the database.
5211 void PerformMaintenanceOnDatabase();
5213 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
5214 nsresult CheckIntegrity(mozIStorageConnection& aConnection, bool* aOk);
5216 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
5217 nsresult DetermineMaintenanceAction(mozIStorageConnection& aConnection,
5218 nsIFile* aDatabaseFile,
5219 MaintenanceAction* aMaintenanceAction);
5221 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
5222 void IncrementalVacuum(mozIStorageConnection& aConnection);
5224 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
5225 void FullVacuum(mozIStorageConnection& aConnection, nsIFile* aDatabaseFile);
5227 // Runs on the PBackground thread. It dispatches a complete callback and
5228 // unregisters from Maintenance.
5229 void RunOnOwningThread();
5231 // Runs on maintenance thread pool. Once it performs database maintenance
5232 // it will dispatch to the PBackground thread on which RunOnOwningThread()
5233 // is called.
5234 void RunOnConnectionThread();
5236 NS_DECL_NSIRUNNABLE
5239 class MOZ_STACK_CLASS DatabaseMaintenance::AutoProgressHandler final
5240 : public mozIStorageProgressHandler {
5241 Maintenance* mMaintenance;
5242 LazyInitializedOnce<const NotNull<mozIStorageConnection*>> mConnection;
5244 NS_DECL_OWNINGTHREAD
5246 #ifdef DEBUG
5247 // This class is stack-based so we never actually allow AddRef/Release to do
5248 // anything. But we need to know if any consumer *thinks* that they have a
5249 // reference to this object so we track the reference countin DEBUG builds.
5250 nsrefcnt mDEBUGRefCnt;
5251 #endif
5253 public:
5254 explicit AutoProgressHandler(Maintenance* aMaintenance)
5255 : mMaintenance(aMaintenance),
5256 mConnection()
5257 #ifdef DEBUG
5259 mDEBUGRefCnt(0)
5260 #endif
5262 MOZ_ASSERT(!NS_IsMainThread());
5263 MOZ_ASSERT(!IsOnBackgroundThread());
5264 NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
5265 MOZ_ASSERT(aMaintenance);
5268 ~AutoProgressHandler() {
5269 NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
5271 if (mConnection) {
5272 Unregister();
5275 MOZ_ASSERT(!mDEBUGRefCnt);
5278 nsresult Register(NotNull<mozIStorageConnection*> aConnection);
5280 // We don't want the mRefCnt member but this class does not "inherit"
5281 // nsISupports.
5282 NS_DECL_ISUPPORTS_INHERITED
5284 private:
5285 void Unregister();
5287 NS_DECL_MOZISTORAGEPROGRESSHANDLER
5289 // Not available for the heap!
5290 void* operator new(size_t) = delete;
5291 void* operator new[](size_t) = delete;
5292 void operator delete(void*) = delete;
5293 void operator delete[](void*) = delete;
5296 #ifdef DEBUG
5298 class DEBUGThreadSlower final : public nsIThreadObserver {
5299 public:
5300 DEBUGThreadSlower() {
5301 AssertIsOnBackgroundThread();
5302 MOZ_ASSERT(kDEBUGThreadSleepMS);
5305 NS_DECL_ISUPPORTS
5307 private:
5308 ~DEBUGThreadSlower() { AssertIsOnBackgroundThread(); }
5310 NS_DECL_NSITHREADOBSERVER
5313 #endif // DEBUG
5315 /*******************************************************************************
5316 * Helper classes
5317 ******************************************************************************/
5319 // XXX Get rid of FileHelper and move the functions into DatabaseFileManager.
5320 // Then, DatabaseFileManager::Get(Journal)Directory and
5321 // DatabaseFileManager::GetFileForId might eventually be made private.
5322 class MOZ_STACK_CLASS FileHelper final {
5323 const SafeRefPtr<DatabaseFileManager> mFileManager;
5325 LazyInitializedOnce<const NotNull<nsCOMPtr<nsIFile>>> mFileDirectory;
5326 LazyInitializedOnce<const NotNull<nsCOMPtr<nsIFile>>> mJournalDirectory;
5328 class ReadCallback;
5329 LazyInitializedOnce<const NotNull<RefPtr<ReadCallback>>> mReadCallback;
5331 public:
5332 explicit FileHelper(SafeRefPtr<DatabaseFileManager>&& aFileManager)
5333 : mFileManager(std::move(aFileManager)) {
5334 MOZ_ASSERT(mFileManager);
5337 nsresult Init();
5339 [[nodiscard]] nsCOMPtr<nsIFile> GetFile(const DatabaseFileInfo& aFileInfo);
5341 [[nodiscard]] nsCOMPtr<nsIFile> GetJournalFile(
5342 const DatabaseFileInfo& aFileInfo);
5344 nsresult CreateFileFromStream(nsIFile& aFile, nsIFile& aJournalFile,
5345 nsIInputStream& aInputStream, bool aCompress,
5346 const Maybe<CipherKey>& aMaybeKey);
5348 private:
5349 nsresult SyncCopy(nsIInputStream& aInputStream,
5350 nsIOutputStream& aOutputStream, char* aBuffer,
5351 uint32_t aBufferSize);
5353 nsresult SyncRead(nsIInputStream& aInputStream, char* aBuffer,
5354 uint32_t aBufferSize, uint32_t* aRead);
5357 /*******************************************************************************
5358 * Helper Functions
5359 ******************************************************************************/
5361 bool GetFilenameBase(const nsAString& aFilename, const nsAString& aSuffix,
5362 nsDependentSubstring& aFilenameBase) {
5363 MOZ_ASSERT(!aFilename.IsEmpty());
5364 MOZ_ASSERT(aFilenameBase.IsEmpty());
5366 if (!StringEndsWith(aFilename, aSuffix) ||
5367 aFilename.Length() == aSuffix.Length()) {
5368 return false;
5371 MOZ_ASSERT(aFilename.Length() > aSuffix.Length());
5373 aFilenameBase.Rebind(aFilename, 0, aFilename.Length() - aSuffix.Length());
5374 return true;
5377 class EncryptedFileBlobImpl final : public FileBlobImpl {
5378 public:
5379 EncryptedFileBlobImpl(const nsCOMPtr<nsIFile>& aNativeFile,
5380 const DatabaseFileInfo::IdType aId,
5381 const CipherKey& aKey)
5382 : FileBlobImpl{aNativeFile}, mKey{aKey} {
5383 SetFileId(aId);
5386 uint64_t GetSize(ErrorResult& aRv) override {
5387 nsCOMPtr<nsIInputStream> inputStream;
5388 CreateInputStream(getter_AddRefs(inputStream), aRv);
5390 if (aRv.Failed()) {
5391 return 0;
5394 MOZ_ASSERT(inputStream);
5396 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(inputStream, Available), 0,
5397 [&aRv](const nsresult rv) { aRv = rv; });
5400 void CreateInputStream(nsIInputStream** aInputStream,
5401 ErrorResult& aRv) const override {
5402 nsCOMPtr<nsIInputStream> baseInputStream;
5403 FileBlobImpl::CreateInputStream(getter_AddRefs(baseInputStream), aRv);
5404 if (NS_WARN_IF(aRv.Failed())) {
5405 return;
5408 *aInputStream =
5409 MakeAndAddRef<DecryptingInputStream<IndexedDBCipherStrategy>>(
5410 WrapNotNull(std::move(baseInputStream)), kEncryptedStreamBlockSize,
5411 mKey)
5412 .take();
5415 void GetBlobImplType(nsAString& aBlobImplType) const override {
5416 aBlobImplType = u"EncryptedFileBlobImpl"_ns;
5419 already_AddRefed<BlobImpl> CreateSlice(uint64_t aStart, uint64_t aLength,
5420 const nsAString& aContentType,
5421 ErrorResult& aRv) const override {
5422 MOZ_CRASH("Not implemented because this should be unreachable.");
5425 private:
5426 const CipherKey mKey;
5429 RefPtr<BlobImpl> CreateFileBlobImpl(const Database& aDatabase,
5430 const nsCOMPtr<nsIFile>& aNativeFile,
5431 const DatabaseFileInfo::IdType aId) {
5432 if (aDatabase.IsInPrivateBrowsing()) {
5433 nsCString cipherKeyId;
5434 cipherKeyId.AppendInt(aId);
5436 const auto& key =
5437 gIndexedDBCipherKeyManager->Get(aDatabase.Id(), cipherKeyId);
5439 MOZ_RELEASE_ASSERT(key.isSome());
5440 return MakeRefPtr<EncryptedFileBlobImpl>(aNativeFile, aId, *key);
5443 auto impl = MakeRefPtr<FileBlobImpl>(aNativeFile);
5444 impl->SetFileId(aId);
5446 return impl;
5449 Result<nsTArray<SerializedStructuredCloneFile>, nsresult>
5450 SerializeStructuredCloneFiles(const SafeRefPtr<Database>& aDatabase,
5451 const nsTArray<StructuredCloneFileParent>& aFiles,
5452 bool aForPreprocess) {
5453 AssertIsOnBackgroundThread();
5454 MOZ_ASSERT(aDatabase);
5456 if (aFiles.IsEmpty()) {
5457 return nsTArray<SerializedStructuredCloneFile>{};
5460 const nsCOMPtr<nsIFile> directory =
5461 aDatabase->GetFileManager().GetCheckedDirectory();
5462 QM_TRY(OkIf(directory), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
5463 IDB_REPORT_INTERNAL_ERR_LAMBDA);
5465 nsTArray<SerializedStructuredCloneFile> serializedStructuredCloneFiles;
5466 QM_TRY(OkIf(serializedStructuredCloneFiles.SetCapacity(aFiles.Length(),
5467 fallible)),
5468 Err(NS_ERROR_OUT_OF_MEMORY));
5470 QM_TRY(TransformIfAbortOnErr(
5471 aFiles, MakeBackInserter(serializedStructuredCloneFiles),
5472 [aForPreprocess](const auto& file) {
5473 return !aForPreprocess ||
5474 file.Type() == StructuredCloneFileBase::eStructuredClone;
5476 [&directory, &aDatabase, aForPreprocess](
5477 const auto& file) -> Result<SerializedStructuredCloneFile, nsresult> {
5478 const int64_t fileId = file.FileInfo().Id();
5479 MOZ_ASSERT(fileId > 0);
5481 const nsCOMPtr<nsIFile> nativeFile =
5482 mozilla::dom::indexedDB::DatabaseFileManager::GetCheckedFileForId(
5483 directory, fileId);
5484 QM_TRY(OkIf(nativeFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
5485 IDB_REPORT_INTERNAL_ERR_LAMBDA);
5487 switch (file.Type()) {
5488 case StructuredCloneFileBase::eStructuredClone:
5489 if (!aForPreprocess) {
5490 return SerializedStructuredCloneFile{
5491 null_t(), StructuredCloneFileBase::eStructuredClone};
5494 [[fallthrough]];
5496 case StructuredCloneFileBase::eBlob: {
5497 const auto impl = CreateFileBlobImpl(*aDatabase, nativeFile,
5498 file.FileInfo().Id());
5500 IPCBlob ipcBlob;
5502 // This can only fail if the child has crashed.
5503 QM_TRY(MOZ_TO_RESULT(IPCBlobUtils::Serialize(impl, ipcBlob)),
5504 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
5505 IDB_REPORT_INTERNAL_ERR_LAMBDA);
5507 aDatabase->MapBlob(ipcBlob, file.FileInfoPtr());
5509 return SerializedStructuredCloneFile{ipcBlob, file.Type()};
5512 case StructuredCloneFileBase::eMutableFile:
5513 case StructuredCloneFileBase::eWasmBytecode:
5514 case StructuredCloneFileBase::eWasmCompiled: {
5515 // Set file() to null, support for storing WebAssembly.Modules has
5516 // been removed in bug 1469395. Support for de-serialization of
5517 // WebAssembly.Modules modules has been removed in bug 1561876.
5518 // Support for MutableFile has been removed in bug 1500343. Full
5519 // removal is tracked in bug 1487479.
5521 return SerializedStructuredCloneFile{null_t(), file.Type()};
5524 default:
5525 MOZ_CRASH("Should never get here!");
5527 }));
5529 return std::move(serializedStructuredCloneFiles);
5532 bool IsFileNotFoundError(const nsresult aRv) {
5533 return aRv == NS_ERROR_FILE_NOT_FOUND;
5536 enum struct Idempotency { Yes, No };
5538 // Delete a file, decreasing the quota usage as appropriate. If the file no
5539 // longer exists but aIdempotency is Idempotency::Yes, success is returned,
5540 // although quota usage can't be decreased. (With the assumption being that the
5541 // file was already deleted prior to this logic running, and the non-existent
5542 // file was no longer tracked by quota because it didn't exist at
5543 // initialization time or a previous deletion call updated the usage.)
5544 nsresult DeleteFile(nsIFile& aFile, QuotaManager* const aQuotaManager,
5545 const PersistenceType aPersistenceType,
5546 const OriginMetadata& aOriginMetadata,
5547 const Idempotency aIdempotency) {
5548 MOZ_ASSERT(!NS_IsMainThread());
5549 MOZ_ASSERT(!IsOnBackgroundThread());
5551 // Callers which pass Idempotency::Yes call this function without checking if
5552 // the file already exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used
5553 // here since we just want to log NS_ERROR_FILE_NOT_FOUND results and not spam
5554 // the reports.
5555 // Theoretically, there should be no QM_OR_ELSE_(WARN|LOG_VERBOSE)_IF when a
5556 // caller passes Idempotency::No, but it's simpler when the predicate just
5557 // always returns false in that case.
5559 const auto isIgnorableError = [&aIdempotency]() -> bool (*)(nsresult) {
5560 if (aIdempotency == Idempotency::Yes) {
5561 return IsFileNotFoundError;
5564 return [](const nsresult rv) { return false; };
5565 }();
5567 QM_TRY_INSPECT(
5568 const auto& fileSize,
5569 ([aQuotaManager, &aFile,
5570 isIgnorableError]() -> Result<Maybe<int64_t>, nsresult> {
5571 if (aQuotaManager) {
5572 QM_TRY_INSPECT(
5573 const Maybe<int64_t>& fileSize,
5574 QM_OR_ELSE_LOG_VERBOSE_IF(
5575 // Expression.
5576 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize)
5577 .map([](const int64_t val) { return Some(val); }),
5578 // Predicate.
5579 isIgnorableError,
5580 // Fallback.
5581 ErrToDefaultOk<Maybe<int64_t>>));
5583 // XXX Can we really assert that the file size is not 0 if
5584 // it existed? This might be violated by external
5585 // influences.
5586 MOZ_ASSERT(!fileSize || fileSize.value() >= 0);
5588 return fileSize;
5591 return Some(int64_t(0));
5592 }()));
5594 if (!fileSize) {
5595 return NS_OK;
5598 QM_TRY_INSPECT(const auto& didExist,
5599 QM_OR_ELSE_LOG_VERBOSE_IF(
5600 // Expression.
5601 MOZ_TO_RESULT(aFile.Remove(false)).map(Some<Ok>),
5602 // Predicate.
5603 isIgnorableError,
5604 // Fallback.
5605 ErrToDefaultOk<Maybe<Ok>>));
5607 if (!didExist) {
5608 // XXX If we get here, this means that the file still existed when we
5609 // queried its size, but no longer when we tried to remove it. Not sure if
5610 // this should really be silently accepted in idempotent mode.
5611 return NS_OK;
5614 if (fileSize.value() > 0) {
5615 MOZ_ASSERT(aQuotaManager);
5617 aQuotaManager->DecreaseUsageForClient(
5618 ClientMetadata{aOriginMetadata, Client::IDB}, fileSize.value());
5621 return NS_OK;
5624 nsresult DeleteFile(nsIFile& aDirectory, const nsAString& aFilename,
5625 QuotaManager* const aQuotaManager,
5626 const PersistenceType aPersistenceType,
5627 const OriginMetadata& aOriginMetadata,
5628 const Idempotency aIdempotent) {
5629 AssertIsOnIOThread();
5630 MOZ_ASSERT(!aFilename.IsEmpty());
5632 QM_TRY_INSPECT(const auto& file, CloneFileAndAppend(aDirectory, aFilename));
5634 return DeleteFile(*file, aQuotaManager, aPersistenceType, aOriginMetadata,
5635 aIdempotent);
5638 // Delete files in a directory that you think exists. If the directory doesn't
5639 // exist, an error will not be returned, but warning telemetry will be
5640 // generated! So only call this on directories that you know exist (idempotent
5641 // usage, but it's not recommended).
5642 nsresult DeleteFilesNoQuota(nsIFile& aFile) {
5643 AssertIsOnIOThread();
5645 QM_TRY_INSPECT(const auto& didExist,
5646 QM_OR_ELSE_WARN_IF(
5647 // Expression.
5648 MOZ_TO_RESULT(aFile.Remove(true)).map(Some<Ok>),
5649 // Predicate.
5650 IsFileNotFoundError,
5651 // Fallback.
5652 ErrToDefaultOk<Maybe<Ok>>));
5654 Unused << didExist;
5656 return NS_OK;
5659 nsresult DeleteFilesNoQuota(nsIFile* aDirectory, const nsAString& aFilename) {
5660 AssertIsOnIOThread();
5661 MOZ_ASSERT(aDirectory);
5662 MOZ_ASSERT(!aFilename.IsEmpty());
5664 // The current using function hasn't initialized the origin, so in here we
5665 // don't update the size of origin. Adding this assertion for preventing from
5666 // misusing.
5667 DebugOnly<QuotaManager*> quotaManager = QuotaManager::Get();
5668 MOZ_ASSERT(!quotaManager->IsTemporaryStorageInitialized());
5670 QM_TRY_INSPECT(const auto& file, CloneFileAndAppend(*aDirectory, aFilename));
5672 QM_TRY(MOZ_TO_RESULT(DeleteFilesNoQuota(*file)));
5674 return NS_OK;
5677 // CreateMarkerFile and RemoveMarkerFile are a pair of functions to indicate
5678 // whether having removed all the files successfully. The marker file should
5679 // be checked before executing the next operation or initialization.
5680 Result<nsCOMPtr<nsIFile>, nsresult> CreateMarkerFile(
5681 nsIFile& aBaseDirectory, const nsAString& aDatabaseNameBase) {
5682 AssertIsOnIOThread();
5683 MOZ_ASSERT(!aDatabaseNameBase.IsEmpty());
5685 QM_TRY_INSPECT(
5686 const auto& markerFile,
5687 CloneFileAndAppend(aBaseDirectory,
5688 kIdbDeletionMarkerFilePrefix + aDatabaseNameBase));
5690 // Callers call this function without checking if the file already exists
5691 // (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we just want
5692 // to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the reports.
5694 // TODO: In theory if this file exists, then RemoveDatabaseFilesAndDirectory
5695 // should have cleaned it up, but obviously we can crash and not clean it up,
5696 // which is the whole point of the marker file. In that case, we'll realize
5697 // the marker file exists in OpenDatabaseOp::DoDatabaseWork or
5698 // GetUsageForOriginInternal and resume the removal by calling
5699 // RemoveDatabaseFilesAndDirectory again, but we will also try to create the
5700 // marker file again, so if we see this marker file, it is part
5701 // of our standard operating procedure to redundantly try and create the
5702 // marker here. We currently treat this as idempotent usage, but we could
5703 // add an additional argument to RemoveDatabaseFilesAndDirectory which would
5704 // indicate that we are resuming an unfinished removal, so the marker already
5705 // exists and doesn't have to be created, and change
5706 // QM_OR_ELSE_LOG_VERBOSE_IF to QM_OR_ELSE_WARN_IF in the end.
5707 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
5708 // Expression.
5709 MOZ_TO_RESULT(markerFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)),
5710 // Predicate.
5711 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
5712 // Fallback.
5713 ErrToDefaultOk<>));
5715 return markerFile;
5718 nsresult RemoveMarkerFile(nsIFile* aMarkerFile) {
5719 AssertIsOnIOThread();
5720 MOZ_ASSERT(aMarkerFile);
5722 DebugOnly<bool> exists;
5723 MOZ_ASSERT(NS_SUCCEEDED(aMarkerFile->Exists(&exists)));
5724 MOZ_ASSERT(exists);
5726 QM_TRY(MOZ_TO_RESULT(aMarkerFile->Remove(false)));
5728 return NS_OK;
5731 Result<Ok, nsresult> DeleteFileManagerDirectory(
5732 nsIFile& aFileManagerDirectory, QuotaManager* aQuotaManager,
5733 const PersistenceType aPersistenceType,
5734 const OriginMetadata& aOriginMetadata) {
5735 // XXX In theory, deleting can continue for other files in case of a failure,
5736 // leaving only those files behind that cause the problem actually. However,
5737 // the current architecture doesn't allow having more databases (for the same
5738 // name) on disk, so trying to delete as much as possible won't help much
5739 // because we need to delete entire .files directory in the end anyway.
5740 QM_TRY(DatabaseFileManager::TraverseFiles(
5741 aFileManagerDirectory,
5742 // KnownDirEntryOp
5743 [&aQuotaManager, aPersistenceType, &aOriginMetadata](
5744 nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
5745 if (isDirectory) {
5746 // The journal directory doesn't count towards quota.
5747 QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file)));
5750 // Stored files do count towards quota.
5751 QM_TRY_RETURN(
5752 MOZ_TO_RESULT(DeleteFile(file, aQuotaManager, aPersistenceType,
5753 aOriginMetadata, Idempotency::Yes)));
5755 // UnknownDirEntryOp
5756 [aPersistenceType, &aOriginMetadata](
5757 nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
5758 // Unknown files and directories don't count towards quota.
5760 if (isDirectory) {
5761 QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file)));
5764 QM_TRY_RETURN(MOZ_TO_RESULT(
5765 DeleteFile(file, /* doesn't count */ nullptr, aPersistenceType,
5766 aOriginMetadata, Idempotency::Yes)));
5767 }));
5769 QM_TRY_RETURN(MOZ_TO_RESULT(aFileManagerDirectory.Remove(false)));
5772 // Idempotently delete all the parts of an IndexedDB database including its
5773 // SQLite database file, its WAL journal, it's shared-memory file, and its
5774 // Blob/Files sub-directory. A marker file is created prior to performing the
5775 // deletion so that in the event we crash or fail to successfully delete the
5776 // database and its files, we will re-attempt the deletion the next time the
5777 // origin is initialized using this method. Because this means the method may be
5778 // called on a partially deleted database, this method uses DeleteFile which
5779 // succeeds when the file we ask it to delete does not actually exist. The
5780 // marker file is removed once deletion has successfully completed.
5781 nsresult RemoveDatabaseFilesAndDirectory(nsIFile& aBaseDirectory,
5782 const nsAString& aDatabaseFilenameBase,
5783 QuotaManager* aQuotaManager,
5784 const PersistenceType aPersistenceType,
5785 const OriginMetadata& aOriginMetadata,
5786 const nsAString& aDatabaseName) {
5787 AssertIsOnIOThread();
5788 MOZ_ASSERT(!aDatabaseFilenameBase.IsEmpty());
5790 AUTO_PROFILER_LABEL("RemoveDatabaseFilesAndDirectory", DOM);
5792 QM_TRY_UNWRAP(auto markerFile,
5793 CreateMarkerFile(aBaseDirectory, aDatabaseFilenameBase));
5795 // The database file counts towards quota.
5796 QM_TRY(MOZ_TO_RESULT(DeleteFile(
5797 aBaseDirectory, aDatabaseFilenameBase + kSQLiteSuffix, aQuotaManager,
5798 aPersistenceType, aOriginMetadata, Idempotency::Yes)));
5800 // .sqlite-journal files don't count towards quota.
5801 QM_TRY(MOZ_TO_RESULT(DeleteFile(aBaseDirectory,
5802 aDatabaseFilenameBase + kSQLiteJournalSuffix,
5803 /* doesn't count */ nullptr, aPersistenceType,
5804 aOriginMetadata, Idempotency::Yes)));
5806 // .sqlite-shm files don't count towards quota.
5807 QM_TRY(MOZ_TO_RESULT(DeleteFile(aBaseDirectory,
5808 aDatabaseFilenameBase + kSQLiteSHMSuffix,
5809 /* doesn't count */ nullptr, aPersistenceType,
5810 aOriginMetadata, Idempotency::Yes)));
5812 // .sqlite-wal files do count towards quota.
5813 QM_TRY(MOZ_TO_RESULT(DeleteFile(
5814 aBaseDirectory, aDatabaseFilenameBase + kSQLiteWALSuffix, aQuotaManager,
5815 aPersistenceType, aOriginMetadata, Idempotency::Yes)));
5817 // The files directory counts towards quota.
5818 QM_TRY_INSPECT(
5819 const auto& fmDirectory,
5820 CloneFileAndAppend(aBaseDirectory, aDatabaseFilenameBase +
5821 kFileManagerDirectoryNameSuffix));
5823 QM_TRY_INSPECT(const bool& exists,
5824 MOZ_TO_RESULT_INVOKE_MEMBER(fmDirectory, Exists));
5826 if (exists) {
5827 QM_TRY_INSPECT(const bool& isDirectory,
5828 MOZ_TO_RESULT_INVOKE_MEMBER(fmDirectory, IsDirectory));
5830 QM_TRY(OkIf(isDirectory), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
5832 QM_TRY(DeleteFileManagerDirectory(*fmDirectory, aQuotaManager,
5833 aPersistenceType, aOriginMetadata));
5836 IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
5837 MOZ_ASSERT_IF(aQuotaManager, mgr);
5839 if (mgr) {
5840 mgr->InvalidateFileManager(aPersistenceType, aOriginMetadata.mOrigin,
5841 aDatabaseName);
5844 QM_TRY(MOZ_TO_RESULT(RemoveMarkerFile(markerFile)));
5846 return NS_OK;
5849 /*******************************************************************************
5850 * Globals
5851 ******************************************************************************/
5853 // Counts the number of "live" Factory, FactoryOp and Database instances.
5854 uint64_t gBusyCount = 0;
5856 using FactoryOpArray = nsTArray<CheckedUnsafePtr<FactoryOp>>;
5858 StaticAutoPtr<FactoryOpArray> gFactoryOps;
5860 // Maps a database id to information about live database actors.
5861 using DatabaseActorHashtable =
5862 nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>;
5864 StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;
5866 StaticRefPtr<ConnectionPool> gConnectionPool;
5868 using DatabaseLoggingInfoHashtable =
5869 nsTHashMap<nsIDHashKey, DatabaseLoggingInfo*>;
5871 StaticAutoPtr<DatabaseLoggingInfoHashtable> gLoggingInfoHashtable;
5873 using TelemetryIdHashtable = nsTHashMap<nsUint32HashKey, uint32_t>;
5875 StaticAutoPtr<TelemetryIdHashtable> gTelemetryIdHashtable;
5877 // Protects all reads and writes to gTelemetryIdHashtable.
5878 StaticAutoPtr<Mutex> gTelemetryIdMutex;
5880 #ifdef DEBUG
5882 StaticRefPtr<DEBUGThreadSlower> gDEBUGThreadSlower;
5884 #endif // DEBUG
5886 void IncreaseBusyCount() {
5887 AssertIsOnBackgroundThread();
5889 // If this is the first instance then we need to do some initialization.
5890 if (!gBusyCount) {
5891 MOZ_ASSERT(!gFactoryOps);
5892 gFactoryOps = new FactoryOpArray();
5894 MOZ_ASSERT(!gLiveDatabaseHashtable);
5895 gLiveDatabaseHashtable = new DatabaseActorHashtable();
5897 MOZ_ASSERT(!gIndexedDBCipherKeyManager);
5898 gIndexedDBCipherKeyManager = new IndexedDBCipherKeyManager();
5900 MOZ_ASSERT(!gLoggingInfoHashtable);
5901 gLoggingInfoHashtable = new DatabaseLoggingInfoHashtable();
5903 #ifdef DEBUG
5904 if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
5905 NS_WARNING(
5906 "PBackground thread debugging enabled, priority has been "
5907 "modified!");
5908 nsCOMPtr<nsISupportsPriority> thread =
5909 do_QueryInterface(NS_GetCurrentThread());
5910 MOZ_ASSERT(thread);
5912 MOZ_ALWAYS_SUCCEEDS(thread->SetPriority(kDEBUGThreadPriority));
5915 if (kDEBUGThreadSleepMS) {
5916 NS_WARNING(
5917 "PBackground thread debugging enabled, sleeping after every "
5918 "event!");
5919 nsCOMPtr<nsIThreadInternal> thread =
5920 do_QueryInterface(NS_GetCurrentThread());
5921 MOZ_ASSERT(thread);
5923 gDEBUGThreadSlower = new DEBUGThreadSlower();
5925 MOZ_ALWAYS_SUCCEEDS(thread->AddObserver(gDEBUGThreadSlower));
5927 #endif // DEBUG
5930 gBusyCount++;
5933 void DecreaseBusyCount() {
5934 AssertIsOnBackgroundThread();
5935 MOZ_ASSERT(gBusyCount);
5937 // Clean up if there are no more instances.
5938 if (--gBusyCount == 0) {
5939 MOZ_ASSERT(gLoggingInfoHashtable);
5940 gLoggingInfoHashtable = nullptr;
5942 MOZ_ASSERT(gLiveDatabaseHashtable);
5943 MOZ_ASSERT(!gLiveDatabaseHashtable->Count());
5944 gLiveDatabaseHashtable = nullptr;
5946 MOZ_ASSERT(gIndexedDBCipherKeyManager);
5947 // XXX After we add the private browsing session end listener, we can assert
5948 // this.
5949 gIndexedDBCipherKeyManager = nullptr;
5951 MOZ_ASSERT(gFactoryOps);
5952 MOZ_ASSERT(gFactoryOps->IsEmpty());
5953 gFactoryOps = nullptr;
5955 #ifdef DEBUG
5956 if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
5957 nsCOMPtr<nsISupportsPriority> thread =
5958 do_QueryInterface(NS_GetCurrentThread());
5959 MOZ_ASSERT(thread);
5961 MOZ_ALWAYS_SUCCEEDS(
5962 thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL));
5965 if (kDEBUGThreadSleepMS) {
5966 MOZ_ASSERT(gDEBUGThreadSlower);
5968 nsCOMPtr<nsIThreadInternal> thread =
5969 do_QueryInterface(NS_GetCurrentThread());
5970 MOZ_ASSERT(thread);
5972 MOZ_ALWAYS_SUCCEEDS(thread->RemoveObserver(gDEBUGThreadSlower));
5974 gDEBUGThreadSlower = nullptr;
5976 #endif // DEBUG
5980 template <typename Condition>
5981 void InvalidateLiveDatabasesMatching(const Condition& aCondition) {
5982 AssertIsOnBackgroundThread();
5984 if (!gLiveDatabaseHashtable) {
5985 return;
5988 // Invalidating a Database will cause it to be removed from the
5989 // gLiveDatabaseHashtable entries' mLiveDatabases, and, if it was the last
5990 // element in mLiveDatabases, to remove the whole hashtable entry. Therefore,
5991 // we need to make a temporary list of the databases to invalidate to avoid
5992 // iterator invalidation.
5994 nsTArray<SafeRefPtr<Database>> databases;
5996 for (const auto& liveDatabasesEntry : gLiveDatabaseHashtable->Values()) {
5997 for (const auto& database : liveDatabasesEntry->mLiveDatabases) {
5998 if (aCondition(*database)) {
5999 databases.AppendElement(
6000 SafeRefPtr{database.get(), AcquireStrongRefFromRawPtr{}});
6005 for (const auto& database : databases) {
6006 database->Invalidate();
6010 uint32_t TelemetryIdForFile(nsIFile* aFile) {
6011 // May be called on any thread!
6013 MOZ_ASSERT(aFile);
6014 MOZ_ASSERT(gTelemetryIdMutex);
6016 // The storage directory is structured like this:
6018 // <profile>/storage/<persistence>/<origin>/idb/<filename>.sqlite
6020 // For the purposes of this function we're only concerned with the
6021 // <persistence>, <origin>, and <filename> pieces.
6023 nsString filename;
6024 MOZ_ALWAYS_SUCCEEDS(aFile->GetLeafName(filename));
6026 // Make sure we were given a database file.
6027 MOZ_ASSERT(StringEndsWith(filename, kSQLiteSuffix));
6029 filename.Truncate(filename.Length() - kSQLiteSuffix.Length());
6031 // Get the "idb" directory.
6032 nsCOMPtr<nsIFile> idbDirectory;
6033 MOZ_ALWAYS_SUCCEEDS(aFile->GetParent(getter_AddRefs(idbDirectory)));
6035 DebugOnly<nsString> idbLeafName;
6036 MOZ_ASSERT(NS_SUCCEEDED(idbDirectory->GetLeafName(idbLeafName)));
6037 MOZ_ASSERT(static_cast<nsString&>(idbLeafName).EqualsLiteral("idb"));
6039 // Get the <origin> directory.
6040 nsCOMPtr<nsIFile> originDirectory;
6041 MOZ_ALWAYS_SUCCEEDS(idbDirectory->GetParent(getter_AddRefs(originDirectory)));
6043 nsString origin;
6044 MOZ_ALWAYS_SUCCEEDS(originDirectory->GetLeafName(origin));
6046 // Any databases in these directories are owned by the application and should
6047 // not have their filenames masked. Hopefully they also appear in the
6048 // Telemetry.cpp whitelist.
6049 if (origin.EqualsLiteral("chrome") ||
6050 origin.EqualsLiteral("moz-safe-about+home")) {
6051 return 0;
6054 // Get the <persistence> directory.
6055 nsCOMPtr<nsIFile> persistenceDirectory;
6056 MOZ_ALWAYS_SUCCEEDS(
6057 originDirectory->GetParent(getter_AddRefs(persistenceDirectory)));
6059 nsString persistence;
6060 MOZ_ALWAYS_SUCCEEDS(persistenceDirectory->GetLeafName(persistence));
6062 constexpr auto separator = u"*"_ns;
6064 uint32_t hashValue =
6065 HashString(persistence + separator + origin + separator + filename);
6067 MutexAutoLock lock(*gTelemetryIdMutex);
6069 if (!gTelemetryIdHashtable) {
6070 gTelemetryIdHashtable = new TelemetryIdHashtable();
6073 return gTelemetryIdHashtable->LookupOrInsertWith(hashValue, [] {
6074 static uint32_t sNextId = 1;
6076 // We're locked, no need for atomics.
6077 return sNextId++;
6081 const CommonIndexOpenCursorParams& GetCommonIndexOpenCursorParams(
6082 const OpenCursorParams& aParams) {
6083 switch (aParams.type()) {
6084 case OpenCursorParams::TIndexOpenCursorParams:
6085 return aParams.get_IndexOpenCursorParams().commonIndexParams();
6086 case OpenCursorParams::TIndexOpenKeyCursorParams:
6087 return aParams.get_IndexOpenKeyCursorParams().commonIndexParams();
6088 default:
6089 MOZ_CRASH("Should never get here!");
6093 const CommonOpenCursorParams& GetCommonOpenCursorParams(
6094 const OpenCursorParams& aParams) {
6095 switch (aParams.type()) {
6096 case OpenCursorParams::TObjectStoreOpenCursorParams:
6097 return aParams.get_ObjectStoreOpenCursorParams().commonParams();
6098 case OpenCursorParams::TObjectStoreOpenKeyCursorParams:
6099 return aParams.get_ObjectStoreOpenKeyCursorParams().commonParams();
6100 case OpenCursorParams::TIndexOpenCursorParams:
6101 case OpenCursorParams::TIndexOpenKeyCursorParams:
6102 return GetCommonIndexOpenCursorParams(aParams).commonParams();
6103 default:
6104 MOZ_CRASH("Should never get here!");
6108 // TODO: Using nsCString as a return type here seems to lead to a dependency on
6109 // some temporaries, which I did not expect. Is it a good idea that the default
6110 // operator+ behaviour constructs such strings? It is certainly useful as an
6111 // optimization, but this should be better done via an appropriately named
6112 // function rather than an operator.
6113 nsAutoCString MakeColumnPairSelectionList(
6114 const nsLiteralCString& aPlainColumnName,
6115 const nsLiteralCString& aLocaleAwareColumnName,
6116 const nsLiteralCString& aSortColumnAlias, const bool aIsLocaleAware) {
6117 return aPlainColumnName +
6118 (aIsLocaleAware ? EmptyCString() : " as "_ns + aSortColumnAlias) +
6119 ", "_ns + aLocaleAwareColumnName +
6120 (aIsLocaleAware ? " as "_ns + aSortColumnAlias : EmptyCString());
6123 constexpr bool IsIncreasingOrder(const IDBCursorDirection aDirection) {
6124 MOZ_ASSERT(aDirection == IDBCursorDirection::Next ||
6125 aDirection == IDBCursorDirection::Nextunique ||
6126 aDirection == IDBCursorDirection::Prev ||
6127 aDirection == IDBCursorDirection::Prevunique);
6129 return aDirection == IDBCursorDirection::Next ||
6130 aDirection == IDBCursorDirection::Nextunique;
6133 constexpr bool IsUnique(const IDBCursorDirection aDirection) {
6134 MOZ_ASSERT(aDirection == IDBCursorDirection::Next ||
6135 aDirection == IDBCursorDirection::Nextunique ||
6136 aDirection == IDBCursorDirection::Prev ||
6137 aDirection == IDBCursorDirection::Prevunique);
6139 return aDirection == IDBCursorDirection::Nextunique ||
6140 aDirection == IDBCursorDirection::Prevunique;
6143 // TODO: In principle, this could be constexpr, if operator+(nsLiteralCString,
6144 // nsLiteralCString) were constexpr and returned a literal type.
6145 nsAutoCString MakeDirectionClause(const IDBCursorDirection aDirection) {
6146 return " ORDER BY "_ns + kColumnNameKey +
6147 (IsIncreasingOrder(aDirection) ? " ASC"_ns : " DESC"_ns);
6150 enum struct ComparisonOperator {
6151 LessThan,
6152 LessOrEquals,
6153 Equals,
6154 GreaterThan,
6155 GreaterOrEquals,
6158 constexpr nsLiteralCString GetComparisonOperatorString(
6159 const ComparisonOperator aComparisonOperator) {
6160 switch (aComparisonOperator) {
6161 case ComparisonOperator::LessThan:
6162 return "<"_ns;
6163 case ComparisonOperator::LessOrEquals:
6164 return "<="_ns;
6165 case ComparisonOperator::Equals:
6166 return "=="_ns;
6167 case ComparisonOperator::GreaterThan:
6168 return ">"_ns;
6169 case ComparisonOperator::GreaterOrEquals:
6170 return ">="_ns;
6173 // TODO: This is just to silence the "control reaches end of non-void
6174 // function" warning. Cannot use MOZ_CRASH in a constexpr function,
6175 // unfortunately.
6176 return ""_ns;
6179 nsAutoCString GetKeyClause(const nsACString& aColumnName,
6180 const ComparisonOperator aComparisonOperator,
6181 const nsLiteralCString& aStmtParamName) {
6182 return aColumnName + " "_ns +
6183 GetComparisonOperatorString(aComparisonOperator) + " :"_ns +
6184 aStmtParamName;
6187 nsAutoCString GetSortKeyClause(const ComparisonOperator aComparisonOperator,
6188 const nsLiteralCString& aStmtParamName) {
6189 return GetKeyClause(kColumnNameAliasSortKey, aComparisonOperator,
6190 aStmtParamName);
6193 template <IDBCursorType CursorType>
6194 struct PopulateResponseHelper;
6196 struct CommonPopulateResponseHelper {
6197 explicit CommonPopulateResponseHelper(
6198 const TransactionDatabaseOperationBase& aOp)
6199 : mOp{aOp} {}
6201 nsresult GetKeys(mozIStorageStatement* const aStmt,
6202 Key* const aOptOutSortKey) {
6203 QM_TRY(MOZ_TO_RESULT(GetCommonKeys(aStmt)));
6205 if (aOptOutSortKey) {
6206 *aOptOutSortKey = mPosition;
6209 return NS_OK;
6212 nsresult GetCommonKeys(mozIStorageStatement* const aStmt) {
6213 MOZ_ASSERT(mPosition.IsUnset());
6215 QM_TRY(MOZ_TO_RESULT(mPosition.SetFromStatement(aStmt, 0)));
6217 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
6218 "PRELOAD: Populating response with key %s", "Populating%.0s",
6219 IDB_LOG_ID_STRING(mOp.BackgroundChildLoggingId()),
6220 mOp.TransactionLoggingSerialNumber(), mOp.LoggingSerialNumber(),
6221 mPosition.GetBuffer().get());
6223 return NS_OK;
6226 template <typename Response>
6227 void FillKeys(Response& aResponse) {
6228 MOZ_ASSERT(!mPosition.IsUnset());
6229 aResponse.key() = std::move(mPosition);
6232 template <typename Response>
6233 static size_t GetKeySize(const Response& aResponse) {
6234 return aResponse.key().GetBuffer().Length();
6237 protected:
6238 const Key& GetPosition() const { return mPosition; }
6240 private:
6241 const TransactionDatabaseOperationBase& mOp;
6242 Key mPosition;
6245 struct IndexPopulateResponseHelper : CommonPopulateResponseHelper {
6246 using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
6248 nsresult GetKeys(mozIStorageStatement* const aStmt,
6249 Key* const aOptOutSortKey) {
6250 MOZ_ASSERT(mLocaleAwarePosition.IsUnset());
6251 MOZ_ASSERT(mObjectStorePosition.IsUnset());
6253 QM_TRY(MOZ_TO_RESULT(CommonPopulateResponseHelper::GetCommonKeys(aStmt)));
6255 QM_TRY(MOZ_TO_RESULT(mLocaleAwarePosition.SetFromStatement(aStmt, 1)));
6257 QM_TRY(MOZ_TO_RESULT(mObjectStorePosition.SetFromStatement(aStmt, 2)));
6259 if (aOptOutSortKey) {
6260 *aOptOutSortKey =
6261 mLocaleAwarePosition.IsUnset() ? GetPosition() : mLocaleAwarePosition;
6264 return NS_OK;
6267 template <typename Response>
6268 void FillKeys(Response& aResponse) {
6269 MOZ_ASSERT(!mLocaleAwarePosition.IsUnset());
6270 MOZ_ASSERT(!mObjectStorePosition.IsUnset());
6272 CommonPopulateResponseHelper::FillKeys(aResponse);
6273 aResponse.sortKey() = std::move(mLocaleAwarePosition);
6274 aResponse.objectKey() = std::move(mObjectStorePosition);
6277 template <typename Response>
6278 static size_t GetKeySize(Response& aResponse) {
6279 return CommonPopulateResponseHelper::GetKeySize(aResponse) +
6280 aResponse.sortKey().GetBuffer().Length() +
6281 aResponse.objectKey().GetBuffer().Length();
6284 private:
6285 Key mLocaleAwarePosition, mObjectStorePosition;
6288 struct KeyPopulateResponseHelper {
6289 static constexpr nsresult MaybeGetCloneInfo(
6290 mozIStorageStatement* const /*aStmt*/, const CursorBase& /*aCursor*/) {
6291 return NS_OK;
6294 template <typename Response>
6295 static constexpr void MaybeFillCloneInfo(Response& /*aResponse*/,
6296 FilesArray* const /*aFiles*/) {}
6298 template <typename Response>
6299 static constexpr size_t MaybeGetCloneInfoSize(const Response& /*aResponse*/) {
6300 return 0;
6304 template <bool StatementHasIndexKeyBindings>
6305 struct ValuePopulateResponseHelper {
6306 nsresult MaybeGetCloneInfo(mozIStorageStatement* const aStmt,
6307 const ValueCursorBase& aCursor) {
6308 constexpr auto offset = StatementHasIndexKeyBindings ? 2 : 0;
6310 QM_TRY_UNWRAP(auto cloneInfo,
6311 GetStructuredCloneReadInfoFromStatement(
6312 aStmt, 2 + offset, 1 + offset, *aCursor.mFileManager));
6314 mCloneInfo.init(std::move(cloneInfo));
6316 if (mCloneInfo->HasPreprocessInfo()) {
6317 IDB_WARNING("Preprocessing for cursors not yet implemented!");
6318 return NS_ERROR_NOT_IMPLEMENTED;
6321 return NS_OK;
6324 template <typename Response>
6325 void MaybeFillCloneInfo(Response& aResponse, FilesArray* const aFiles) {
6326 auto cloneInfo = mCloneInfo.release();
6327 aResponse.cloneInfo().data().data = cloneInfo.ReleaseData();
6328 aFiles->AppendElement(cloneInfo.ReleaseFiles());
6331 template <typename Response>
6332 static size_t MaybeGetCloneInfoSize(const Response& aResponse) {
6333 return aResponse.cloneInfo().data().data.Size();
6336 private:
6337 LazyInitializedOnceEarlyDestructible<const StructuredCloneReadInfoParent>
6338 mCloneInfo;
6341 template <>
6342 struct PopulateResponseHelper<IDBCursorType::ObjectStore>
6343 : ValuePopulateResponseHelper<false>, CommonPopulateResponseHelper {
6344 using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
6346 static auto& GetTypedResponse(CursorResponse* const aResponse) {
6347 return aResponse->get_ArrayOfObjectStoreCursorResponse();
6351 template <>
6352 struct PopulateResponseHelper<IDBCursorType::ObjectStoreKey>
6353 : KeyPopulateResponseHelper, CommonPopulateResponseHelper {
6354 using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
6356 static auto& GetTypedResponse(CursorResponse* const aResponse) {
6357 return aResponse->get_ArrayOfObjectStoreKeyCursorResponse();
6361 template <>
6362 struct PopulateResponseHelper<IDBCursorType::Index>
6363 : ValuePopulateResponseHelper<true>, IndexPopulateResponseHelper {
6364 using IndexPopulateResponseHelper::IndexPopulateResponseHelper;
6366 static auto& GetTypedResponse(CursorResponse* const aResponse) {
6367 return aResponse->get_ArrayOfIndexCursorResponse();
6371 template <>
6372 struct PopulateResponseHelper<IDBCursorType::IndexKey>
6373 : KeyPopulateResponseHelper, IndexPopulateResponseHelper {
6374 using IndexPopulateResponseHelper::IndexPopulateResponseHelper;
6376 static auto& GetTypedResponse(CursorResponse* const aResponse) {
6377 return aResponse->get_ArrayOfIndexKeyCursorResponse();
6381 nsresult DispatchAndReturnFileReferences(
6382 PersistenceType aPersistenceType, const nsACString& aOrigin,
6383 const nsAString& aDatabaseName, const int64_t aFileId,
6384 int32_t* const aMemRefCnt, int32_t* const aDBRefCnt, bool* const aResult) {
6385 AssertIsOnBackgroundThread();
6386 MOZ_ASSERT(aMemRefCnt);
6387 MOZ_ASSERT(aDBRefCnt);
6388 MOZ_ASSERT(aResult);
6390 *aResult = false;
6391 *aMemRefCnt = -1;
6392 *aDBRefCnt = -1;
6394 mozilla::Monitor monitor MOZ_ANNOTATED(__func__);
6395 bool waiting = true;
6397 auto lambda = [&] {
6398 AssertIsOnIOThread();
6401 IndexedDatabaseManager* const mgr = IndexedDatabaseManager::Get();
6402 MOZ_ASSERT(mgr);
6404 const SafeRefPtr<DatabaseFileManager> fileManager =
6405 mgr->GetFileManager(aPersistenceType, aOrigin, aDatabaseName);
6407 if (fileManager) {
6408 const SafeRefPtr<DatabaseFileInfo> fileInfo =
6409 fileManager->GetFileInfo(aFileId);
6411 if (fileInfo) {
6412 fileInfo->GetReferences(aMemRefCnt, aDBRefCnt);
6414 if (*aMemRefCnt != -1) {
6415 // We added an extra temp ref, so account for that accordingly.
6416 (*aMemRefCnt)--;
6419 *aResult = true;
6424 mozilla::MonitorAutoLock lock(monitor);
6425 MOZ_ASSERT(waiting);
6427 waiting = false;
6428 lock.Notify();
6431 QuotaManager* const quotaManager = QuotaManager::Get();
6432 MOZ_ASSERT(quotaManager);
6434 // XXX can't we simply use NS_DISPATCH_SYNC instead of using a monitor?
6435 QM_TRY(MOZ_TO_RESULT(quotaManager->IOThread()->Dispatch(
6436 NS_NewRunnableFunction("GetFileReferences", std::move(lambda)),
6437 NS_DISPATCH_NORMAL)));
6439 mozilla::MonitorAutoLock autolock(monitor);
6440 while (waiting) {
6441 autolock.Wait();
6444 return NS_OK;
6447 class DeserializeIndexValueHelper final : public Runnable {
6448 public:
6449 DeserializeIndexValueHelper(int64_t aIndexID, const KeyPath& aKeyPath,
6450 bool aMultiEntry, const nsACString& aLocale,
6451 StructuredCloneReadInfoParent& aCloneReadInfo,
6452 nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
6453 : Runnable("DeserializeIndexValueHelper"),
6454 mMonitor("DeserializeIndexValueHelper::mMonitor"),
6455 mIndexID(aIndexID),
6456 mKeyPath(aKeyPath),
6457 mMultiEntry(aMultiEntry),
6458 mLocale(aLocale),
6459 mCloneReadInfo(aCloneReadInfo),
6460 mUpdateInfoArray(aUpdateInfoArray),
6461 mStatus(NS_ERROR_FAILURE) {}
6463 nsresult DispatchAndWait() {
6464 // FIXME(Bug 1637530) Re-enable optimization using a non-system-principaled
6465 // JS context
6466 #if 0
6467 // We don't need to go to the main-thread and use the sandbox. Let's create
6468 // the updateInfo data here.
6469 if (!mCloneReadInfo.Data().Size()) {
6470 AutoJSAPI jsapi;
6471 jsapi.Init();
6473 JS::Rooted<JS::Value> value(jsapi.cx());
6474 value.setUndefined();
6476 ErrorResult rv;
6477 IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mMultiEntry,
6478 mLocale, jsapi.cx(), value,
6479 &mUpdateInfoArray, &rv);
6480 return rv.Failed() ? rv.StealNSResult() : NS_OK;
6482 #endif
6484 // The operation will continue on the main-thread.
6486 MOZ_ASSERT(!(mCloneReadInfo.Data().Size() % sizeof(uint64_t)));
6488 MonitorAutoLock lock(mMonitor);
6490 RefPtr<Runnable> self = this;
6491 QM_TRY(MOZ_TO_RESULT(
6492 SchedulerGroup::Dispatch(TaskCategory::Other, self.forget())));
6494 lock.Wait();
6495 return mStatus;
6498 NS_IMETHOD
6499 Run() override {
6500 MOZ_ASSERT(NS_IsMainThread());
6502 AutoJSAPI jsapi;
6503 jsapi.Init();
6504 JSContext* const cx = jsapi.cx();
6506 JS::Rooted<JSObject*> global(cx, GetSandbox(cx));
6508 QM_TRY(OkIf(global), NS_OK,
6509 [this](const NotOk) { OperationCompleted(NS_ERROR_FAILURE); });
6511 const JSAutoRealm ar(cx, global);
6513 JS::Rooted<JS::Value> value(cx);
6514 QM_TRY(MOZ_TO_RESULT(DeserializeIndexValue(cx, &value)), NS_OK,
6515 [this](const nsresult rv) { OperationCompleted(rv); });
6517 ErrorResult errorResult;
6518 IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mMultiEntry,
6519 mLocale, cx, value, &mUpdateInfoArray,
6520 &errorResult);
6521 QM_TRY(OkIf(!errorResult.Failed()), NS_OK,
6522 ([this, &errorResult](const NotOk) {
6523 OperationCompleted(errorResult.StealNSResult());
6524 }));
6526 OperationCompleted(NS_OK);
6527 return NS_OK;
6530 private:
6531 nsresult DeserializeIndexValue(JSContext* aCx,
6532 JS::MutableHandle<JS::Value> aValue) {
6533 static const JSStructuredCloneCallbacks callbacks = {
6534 StructuredCloneReadCallback<StructuredCloneReadInfoParent>,
6535 nullptr,
6536 nullptr,
6537 nullptr,
6538 nullptr,
6539 nullptr,
6540 nullptr,
6541 nullptr};
6543 if (!JS_ReadStructuredClone(
6544 aCx, mCloneReadInfo.Data(), JS_STRUCTURED_CLONE_VERSION,
6545 JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
6546 JS::CloneDataPolicy(), &callbacks, &mCloneReadInfo)) {
6547 return NS_ERROR_DOM_DATA_CLONE_ERR;
6550 return NS_OK;
6553 void OperationCompleted(nsresult aStatus) {
6554 mStatus = aStatus;
6556 MonitorAutoLock lock(mMonitor);
6557 lock.Notify();
6560 Monitor mMonitor MOZ_UNANNOTATED;
6562 const int64_t mIndexID;
6563 const KeyPath& mKeyPath;
6564 const bool mMultiEntry;
6565 const nsCString mLocale;
6566 StructuredCloneReadInfoParent& mCloneReadInfo;
6567 nsTArray<IndexUpdateInfo>& mUpdateInfoArray;
6568 nsresult mStatus;
6571 auto DeserializeIndexValueToUpdateInfos(
6572 int64_t aIndexID, const KeyPath& aKeyPath, bool aMultiEntry,
6573 const nsACString& aLocale, StructuredCloneReadInfoParent& aCloneReadInfo) {
6574 MOZ_ASSERT(!NS_IsMainThread());
6576 using ArrayType = AutoTArray<IndexUpdateInfo, 32>;
6577 using ResultType = Result<ArrayType, nsresult>;
6579 ArrayType updateInfoArray;
6580 const auto helper = MakeRefPtr<DeserializeIndexValueHelper>(
6581 aIndexID, aKeyPath, aMultiEntry, aLocale, aCloneReadInfo,
6582 updateInfoArray);
6583 const nsresult rv = helper->DispatchAndWait();
6584 return NS_FAILED(rv) ? Err(rv) : ResultType{std::move(updateInfoArray)};
6587 bool IsSome(
6588 const Maybe<CachingDatabaseConnection::BorrowedStatement>& aMaybeStmt) {
6589 return aMaybeStmt.isSome();
6592 } // namespace
6594 /*******************************************************************************
6595 * Exported functions
6596 ******************************************************************************/
6598 already_AddRefed<PBackgroundIDBFactoryParent> AllocPBackgroundIDBFactoryParent(
6599 const LoggingInfo& aLoggingInfo) {
6600 AssertIsOnBackgroundThread();
6602 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
6603 return nullptr;
6606 if (NS_AUUF_OR_WARN_IF(!aLoggingInfo.nextTransactionSerialNumber()) ||
6607 NS_AUUF_OR_WARN_IF(
6608 !aLoggingInfo.nextVersionChangeTransactionSerialNumber()) ||
6609 NS_AUUF_OR_WARN_IF(!aLoggingInfo.nextRequestSerialNumber())) {
6610 return nullptr;
6613 SafeRefPtr<Factory> actor = Factory::Create(aLoggingInfo);
6614 MOZ_ASSERT(actor);
6616 return actor.forget();
6619 bool RecvPBackgroundIDBFactoryConstructor(
6620 PBackgroundIDBFactoryParent* aActor,
6621 const LoggingInfo& /* aLoggingInfo */) {
6622 AssertIsOnBackgroundThread();
6623 MOZ_ASSERT(aActor);
6624 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6626 return true;
6629 PBackgroundIndexedDBUtilsParent* AllocPBackgroundIndexedDBUtilsParent() {
6630 AssertIsOnBackgroundThread();
6632 RefPtr<Utils> actor = new Utils();
6634 return actor.forget().take();
6637 bool DeallocPBackgroundIndexedDBUtilsParent(
6638 PBackgroundIndexedDBUtilsParent* aActor) {
6639 AssertIsOnBackgroundThread();
6640 MOZ_ASSERT(aActor);
6642 RefPtr<Utils> actor = dont_AddRef(static_cast<Utils*>(aActor));
6643 return true;
6646 bool RecvFlushPendingFileDeletions() {
6647 AssertIsOnBackgroundThread();
6649 if (QuotaClient* quotaClient = QuotaClient::GetInstance()) {
6650 QM_WARNONLY_TRY(QM_TO_RESULT(quotaClient->FlushPendingFileDeletions()));
6653 return true;
6656 RefPtr<mozilla::dom::quota::Client> CreateQuotaClient() {
6657 AssertIsOnBackgroundThread();
6659 return MakeRefPtr<QuotaClient>();
6662 nsresult DatabaseFileManager::AsyncDeleteFile(int64_t aFileId) {
6663 AssertIsOnBackgroundThread();
6664 MOZ_ASSERT(!mFileInfos.Contains(aFileId));
6666 QuotaClient* quotaClient = QuotaClient::GetInstance();
6667 if (quotaClient) {
6668 QM_TRY(MOZ_TO_RESULT(quotaClient->AsyncDeleteFile(this, aFileId)));
6671 return NS_OK;
6674 /*******************************************************************************
6675 * DatabaseConnection implementation
6676 ******************************************************************************/
6678 DatabaseConnection::DatabaseConnection(
6679 MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection,
6680 MovingNotNull<SafeRefPtr<DatabaseFileManager>> aFileManager)
6681 : CachingDatabaseConnection(std::move(aStorageConnection)),
6682 mFileManager(std::move(aFileManager)),
6683 mInReadTransaction(false),
6684 mInWriteTransaction(false)
6685 #ifdef DEBUG
6687 mDEBUGSavepointCount(0)
6688 #endif
6690 AssertIsOnConnectionThread();
6691 MOZ_ASSERT(mFileManager);
6694 DatabaseConnection::~DatabaseConnection() {
6695 MOZ_ASSERT(!mFileManager);
6696 MOZ_ASSERT(!mUpdateRefcountFunction);
6697 MOZ_DIAGNOSTIC_ASSERT(!mInWriteTransaction);
6698 MOZ_ASSERT(!mDEBUGSavepointCount);
6701 nsresult DatabaseConnection::Init() {
6702 AssertIsOnConnectionThread();
6703 MOZ_ASSERT(!mInReadTransaction);
6704 MOZ_ASSERT(!mInWriteTransaction);
6706 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN;"_ns)));
6708 mInReadTransaction = true;
6710 return NS_OK;
6713 nsresult DatabaseConnection::BeginWriteTransaction() {
6714 AssertIsOnConnectionThread();
6715 MOZ_ASSERT(HasStorageConnection());
6716 MOZ_ASSERT(mInReadTransaction);
6717 MOZ_ASSERT(!mInWriteTransaction);
6719 AUTO_PROFILER_LABEL("DatabaseConnection::BeginWriteTransaction", DOM);
6721 // Release our read locks.
6722 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("ROLLBACK;"_ns)));
6724 mInReadTransaction = false;
6726 if (!mUpdateRefcountFunction) {
6727 MOZ_ASSERT(mFileManager);
6729 RefPtr<UpdateRefcountFunction> function =
6730 new UpdateRefcountFunction(this, **mFileManager);
6732 QM_TRY(MOZ_TO_RESULT(MutableStorageConnection().CreateFunction(
6733 "update_refcount"_ns,
6734 /* aNumArguments */ 2, function)));
6736 mUpdateRefcountFunction = std::move(function);
6739 // This one cannot obviously use ExecuteCachedStatement because of the custom
6740 // error handling for Execute only. If only Execute can produce
6741 // NS_ERROR_STORAGE_BUSY, we could actually use ExecuteCachedStatement and
6742 // simplify this.
6743 QM_TRY_INSPECT(const auto& beginStmt,
6744 BorrowCachedStatement("BEGIN IMMEDIATE;"_ns));
6746 QM_TRY(QM_OR_ELSE_WARN_IF(
6747 // Expression.
6748 MOZ_TO_RESULT(beginStmt->Execute()),
6749 // Predicate.
6750 IsSpecificError<NS_ERROR_STORAGE_BUSY>,
6751 // Fallback.
6752 ([&beginStmt](nsresult rv) {
6753 NS_WARNING(
6754 "Received NS_ERROR_STORAGE_BUSY when attempting to start write "
6755 "transaction, retrying for up to 10 seconds");
6757 // Another thread must be using the database. Wait up to 10 seconds
6758 // for that to complete.
6759 const TimeStamp start = TimeStamp::NowLoRes();
6761 while (true) {
6762 PR_Sleep(PR_MillisecondsToInterval(100));
6764 rv = beginStmt->Execute();
6765 if (rv != NS_ERROR_STORAGE_BUSY ||
6766 TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
6767 break;
6771 return MOZ_TO_RESULT(rv);
6772 })));
6774 mInWriteTransaction = true;
6776 return NS_OK;
6779 nsresult DatabaseConnection::CommitWriteTransaction() {
6780 AssertIsOnConnectionThread();
6781 MOZ_ASSERT(HasStorageConnection());
6782 MOZ_ASSERT(!mInReadTransaction);
6783 MOZ_ASSERT(mInWriteTransaction);
6785 AUTO_PROFILER_LABEL("DatabaseConnection::CommitWriteTransaction", DOM);
6787 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("COMMIT;"_ns)));
6789 mInWriteTransaction = false;
6790 return NS_OK;
6793 void DatabaseConnection::RollbackWriteTransaction() {
6794 AssertIsOnConnectionThread();
6795 MOZ_ASSERT(!mInReadTransaction);
6796 MOZ_DIAGNOSTIC_ASSERT(HasStorageConnection());
6798 AUTO_PROFILER_LABEL("DatabaseConnection::RollbackWriteTransaction", DOM);
6800 if (!mInWriteTransaction) {
6801 return;
6804 QM_WARNONLY_TRY(
6805 BorrowCachedStatement("ROLLBACK;"_ns)
6806 .andThen([&self = *this](const auto& stmt) -> Result<Ok, nsresult> {
6807 // This may fail if SQLite already rolled back the transaction
6808 // so ignore any errors.
6810 // XXX ROLLBACK can fail quite normmally if a previous statement
6811 // failed to execute successfully so SQLite rolled back the
6812 // transaction already. However, if it failed because of some other
6813 // reason, we could try to close the connection.
6814 Unused << stmt->Execute();
6816 self.mInWriteTransaction = false;
6817 return Ok{};
6818 }));
6821 void DatabaseConnection::FinishWriteTransaction() {
6822 AssertIsOnConnectionThread();
6823 MOZ_ASSERT(HasStorageConnection());
6824 MOZ_ASSERT(!mInReadTransaction);
6825 MOZ_ASSERT(!mInWriteTransaction);
6827 AUTO_PROFILER_LABEL("DatabaseConnection::FinishWriteTransaction", DOM);
6829 if (mUpdateRefcountFunction) {
6830 mUpdateRefcountFunction->Reset();
6833 QM_WARNONLY_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN;"_ns))
6834 .andThen([&](const auto) -> Result<Ok, nsresult> {
6835 mInReadTransaction = true;
6836 return Ok{};
6837 }));
6840 nsresult DatabaseConnection::StartSavepoint() {
6841 AssertIsOnConnectionThread();
6842 MOZ_ASSERT(HasStorageConnection());
6843 MOZ_ASSERT(mUpdateRefcountFunction);
6844 MOZ_ASSERT(mInWriteTransaction);
6846 AUTO_PROFILER_LABEL("DatabaseConnection::StartSavepoint", DOM);
6848 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement(SAVEPOINT_CLAUSE)));
6850 mUpdateRefcountFunction->StartSavepoint();
6852 #ifdef DEBUG
6853 MOZ_ASSERT(mDEBUGSavepointCount < UINT32_MAX);
6854 mDEBUGSavepointCount++;
6855 #endif
6857 return NS_OK;
6860 nsresult DatabaseConnection::ReleaseSavepoint() {
6861 AssertIsOnConnectionThread();
6862 MOZ_ASSERT(HasStorageConnection());
6863 MOZ_ASSERT(mUpdateRefcountFunction);
6864 MOZ_ASSERT(mInWriteTransaction);
6866 AUTO_PROFILER_LABEL("DatabaseConnection::ReleaseSavepoint", DOM);
6868 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("RELEASE "_ns SAVEPOINT_CLAUSE)));
6870 mUpdateRefcountFunction->ReleaseSavepoint();
6872 #ifdef DEBUG
6873 MOZ_ASSERT(mDEBUGSavepointCount);
6874 mDEBUGSavepointCount--;
6875 #endif
6877 return NS_OK;
6880 nsresult DatabaseConnection::RollbackSavepoint() {
6881 AssertIsOnConnectionThread();
6882 MOZ_ASSERT(HasStorageConnection());
6883 MOZ_ASSERT(mUpdateRefcountFunction);
6884 MOZ_ASSERT(mInWriteTransaction);
6886 AUTO_PROFILER_LABEL("DatabaseConnection::RollbackSavepoint", DOM);
6888 #ifdef DEBUG
6889 MOZ_ASSERT(mDEBUGSavepointCount);
6890 mDEBUGSavepointCount--;
6891 #endif
6893 mUpdateRefcountFunction->RollbackSavepoint();
6895 QM_TRY_INSPECT(const auto& stmt,
6896 BorrowCachedStatement("ROLLBACK TO "_ns SAVEPOINT_CLAUSE));
6898 // This may fail if SQLite already rolled back the savepoint so ignore any
6899 // errors.
6900 Unused << stmt->Execute();
6902 return NS_OK;
6905 nsresult DatabaseConnection::CheckpointInternal(CheckpointMode aMode) {
6906 AssertIsOnConnectionThread();
6907 MOZ_ASSERT(!mInReadTransaction);
6908 MOZ_ASSERT(!mInWriteTransaction);
6910 AUTO_PROFILER_LABEL("DatabaseConnection::CheckpointInternal", DOM);
6912 nsAutoCString stmtString;
6913 stmtString.AssignLiteral("PRAGMA wal_checkpoint(");
6915 switch (aMode) {
6916 case CheckpointMode::Full:
6917 // Ensures that the database is completely checkpointed and flushed to
6918 // disk.
6919 stmtString.AppendLiteral("FULL");
6920 break;
6922 case CheckpointMode::Restart:
6923 // Like Full, but also ensures that the next write will start overwriting
6924 // the existing WAL file rather than letting the WAL file grow.
6925 stmtString.AppendLiteral("RESTART");
6926 break;
6928 case CheckpointMode::Truncate:
6929 // Like Restart but also truncates the existing WAL file.
6930 stmtString.AppendLiteral("TRUNCATE");
6931 break;
6933 default:
6934 MOZ_CRASH("Unknown CheckpointMode!");
6937 stmtString.AppendLiteral(");");
6939 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement(stmtString)));
6941 return NS_OK;
6944 void DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint) {
6945 AssertIsOnConnectionThread();
6946 MOZ_ASSERT(mInReadTransaction);
6947 MOZ_ASSERT(!mInWriteTransaction);
6949 AUTO_PROFILER_LABEL("DatabaseConnection::DoIdleProcessing", DOM);
6951 CachingDatabaseConnection::CachedStatement freelistStmt;
6952 const uint32_t freelistCount = [this, &freelistStmt] {
6953 QM_TRY_RETURN(GetFreelistCount(freelistStmt), 0u);
6954 }();
6956 CachedStatement rollbackStmt;
6957 CachedStatement beginStmt;
6958 if (aNeedsCheckpoint || freelistCount) {
6959 QM_TRY_UNWRAP(rollbackStmt, GetCachedStatement("ROLLBACK;"_ns), QM_VOID);
6960 QM_TRY_UNWRAP(beginStmt, GetCachedStatement("BEGIN;"_ns), QM_VOID);
6962 // Release the connection's normal transaction. It's possible that it could
6963 // fail, but that isn't a problem here.
6964 Unused << rollbackStmt.Borrow()->Execute();
6966 mInReadTransaction = false;
6969 const bool freedSomePages = freelistCount && [this, &freelistStmt,
6970 &rollbackStmt, freelistCount,
6971 aNeedsCheckpoint] {
6972 // Warn in case of an error, but do not propagate it. Just indicate we
6973 // didn't free any pages.
6974 QM_TRY_INSPECT(const bool& res,
6975 ReclaimFreePagesWhileIdle(freelistStmt, rollbackStmt,
6976 freelistCount, aNeedsCheckpoint),
6977 false);
6979 // Make sure we didn't leave a transaction running.
6980 MOZ_ASSERT(!mInReadTransaction);
6981 MOZ_ASSERT(!mInWriteTransaction);
6983 return res;
6984 }();
6986 // Truncate the WAL if we were asked to or if we managed to free some space.
6987 if (aNeedsCheckpoint || freedSomePages) {
6988 QM_WARNONLY_TRY(QM_TO_RESULT(CheckpointInternal(CheckpointMode::Truncate)));
6991 // Finally try to restart the read transaction if we rolled it back earlier.
6992 if (beginStmt) {
6993 QM_WARNONLY_TRY(
6994 MOZ_TO_RESULT(beginStmt.Borrow()->Execute())
6995 .andThen([&self = *this](const Ok) -> Result<Ok, nsresult> {
6996 self.mInReadTransaction = true;
6997 return Ok{};
6998 }));
7002 Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle(
7003 CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement,
7004 uint32_t aFreelistCount, bool aNeedsCheckpoint) {
7005 AssertIsOnConnectionThread();
7006 MOZ_ASSERT(aFreelistStatement);
7007 MOZ_ASSERT(aRollbackStatement);
7008 MOZ_ASSERT(aFreelistCount);
7009 MOZ_ASSERT(!mInReadTransaction);
7010 MOZ_ASSERT(!mInWriteTransaction);
7012 AUTO_PROFILER_LABEL("DatabaseConnection::ReclaimFreePagesWhileIdle", DOM);
7014 // Make sure we don't keep working if anything else needs this thread.
7015 nsIThread* currentThread = NS_GetCurrentThread();
7016 MOZ_ASSERT(currentThread);
7018 if (NS_HasPendingEvents(currentThread)) {
7019 return false;
7022 // Make all the statements we'll need up front.
7024 // Only try to free 10% at a time so that we can bail out if this connection
7025 // suddenly becomes active or if the thread is needed otherwise.
7026 QM_TRY_INSPECT(
7027 const auto& incrementalVacuumStmt,
7028 GetCachedStatement(
7029 "PRAGMA incremental_vacuum("_ns +
7030 IntToCString(std::max(uint64_t(1), uint64_t(aFreelistCount / 10))) +
7031 ");"_ns));
7033 QM_TRY_INSPECT(const auto& beginImmediateStmt,
7034 GetCachedStatement("BEGIN IMMEDIATE;"_ns));
7036 QM_TRY_INSPECT(const auto& commitStmt, GetCachedStatement("COMMIT;"_ns));
7038 if (aNeedsCheckpoint) {
7039 // Freeing pages is a journaled operation, so it will require additional WAL
7040 // space. However, we're idle and are about to checkpoint anyway, so doing a
7041 // RESTART checkpoint here should allow us to reuse any existing space.
7042 QM_TRY(MOZ_TO_RESULT(CheckpointInternal(CheckpointMode::Restart)));
7045 // Start the write transaction.
7046 QM_TRY(MOZ_TO_RESULT(beginImmediateStmt.Borrow()->Execute()));
7048 mInWriteTransaction = true;
7050 bool freedSomePages = false, interrupted = false;
7052 const auto rollback = [&aRollbackStatement, this](const auto&) {
7053 MOZ_ASSERT(mInWriteTransaction);
7055 // Something failed, make sure we roll everything back.
7056 Unused << aRollbackStatement.Borrow()->Execute();
7058 // XXX Is rollback infallible? Shouldn't we check the result?
7060 mInWriteTransaction = false;
7063 QM_TRY(CollectWhile(
7064 [&aFreelistCount, &interrupted,
7065 currentThread]() -> Result<bool, nsresult> {
7066 if (NS_HasPendingEvents(currentThread)) {
7067 // Abort if something else wants to use the thread, and
7068 // roll back this transaction. It's ok if we never make
7069 // progress here because the idle service should
7070 // eventually reclaim this space.
7071 interrupted = true;
7072 return false;
7074 return aFreelistCount != 0;
7076 [&aFreelistStatement, &aFreelistCount, &incrementalVacuumStmt,
7077 &freedSomePages, this]() -> mozilla::Result<Ok, nsresult> {
7078 QM_TRY(MOZ_TO_RESULT(incrementalVacuumStmt.Borrow()->Execute()));
7080 freedSomePages = true;
7082 QM_TRY_UNWRAP(aFreelistCount,
7083 GetFreelistCount(aFreelistStatement));
7085 return Ok{};
7087 .andThen([&commitStmt, &freedSomePages, &interrupted, &rollback,
7088 this](Ok) -> Result<Ok, nsresult> {
7089 if (interrupted) {
7090 rollback(Ok{});
7091 freedSomePages = false;
7094 if (freedSomePages) {
7095 // Commit the write transaction.
7096 QM_TRY(MOZ_TO_RESULT(commitStmt.Borrow()->Execute()),
7097 QM_PROPAGATE,
7098 [](const auto&) { NS_WARNING("Failed to commit!"); });
7100 mInWriteTransaction = false;
7103 return Ok{};
7105 QM_PROPAGATE, rollback);
7107 return freedSomePages;
7110 Result<uint32_t, nsresult> DatabaseConnection::GetFreelistCount(
7111 CachedStatement& aCachedStatement) {
7112 AssertIsOnConnectionThread();
7114 AUTO_PROFILER_LABEL("DatabaseConnection::GetFreelistCount", DOM);
7116 if (!aCachedStatement) {
7117 QM_TRY_UNWRAP(aCachedStatement,
7118 GetCachedStatement("PRAGMA freelist_count;"_ns));
7121 const auto borrowedStatement = aCachedStatement.Borrow();
7123 QM_TRY_UNWRAP(const DebugOnly<bool> hasResult,
7124 MOZ_TO_RESULT_INVOKE_MEMBER(&*borrowedStatement, ExecuteStep));
7126 MOZ_ASSERT(hasResult);
7128 QM_TRY_INSPECT(const int32_t& freelistCount,
7129 MOZ_TO_RESULT_INVOKE_MEMBER(*borrowedStatement, GetInt32, 0));
7131 MOZ_ASSERT(freelistCount >= 0);
7133 return uint32_t(freelistCount);
7136 void DatabaseConnection::Close() {
7137 AssertIsOnConnectionThread();
7138 MOZ_ASSERT(!mDEBUGSavepointCount);
7139 MOZ_DIAGNOSTIC_ASSERT(!mInWriteTransaction);
7141 AUTO_PROFILER_LABEL("DatabaseConnection::Close", DOM);
7143 if (mUpdateRefcountFunction) {
7144 MOZ_ALWAYS_SUCCEEDS(
7145 MutableStorageConnection().RemoveFunction("update_refcount"_ns));
7146 mUpdateRefcountFunction = nullptr;
7149 CachingDatabaseConnection::Close();
7151 mFileManager.destroy();
7154 nsresult DatabaseConnection::DisableQuotaChecks() {
7155 AssertIsOnConnectionThread();
7156 MOZ_ASSERT(HasStorageConnection());
7158 if (!mQuotaObject) {
7159 MOZ_ASSERT(!mJournalQuotaObject);
7161 QM_TRY(MOZ_TO_RESULT(MutableStorageConnection().GetQuotaObjects(
7162 getter_AddRefs(mQuotaObject), getter_AddRefs(mJournalQuotaObject))));
7164 MOZ_ASSERT(mQuotaObject);
7165 MOZ_ASSERT(mJournalQuotaObject);
7168 mQuotaObject->DisableQuotaCheck();
7169 mJournalQuotaObject->DisableQuotaCheck();
7171 return NS_OK;
7174 void DatabaseConnection::EnableQuotaChecks() {
7175 AssertIsOnConnectionThread();
7176 if (!mQuotaObject) {
7177 MOZ_ASSERT(!mJournalQuotaObject);
7179 // DisableQuotaChecks failed earlier, so we don't need to enable quota
7180 // checks again.
7181 return;
7184 MOZ_ASSERT(mJournalQuotaObject);
7186 const RefPtr<QuotaObject> quotaObject = std::move(mQuotaObject);
7187 const RefPtr<QuotaObject> journalQuotaObject = std::move(mJournalQuotaObject);
7189 quotaObject->EnableQuotaCheck();
7190 journalQuotaObject->EnableQuotaCheck();
7192 QM_TRY_INSPECT(const int64_t& fileSize, GetFileSize(quotaObject->Path()),
7193 QM_VOID);
7194 QM_TRY_INSPECT(const int64_t& journalFileSize,
7195 GetFileSize(journalQuotaObject->Path()), QM_VOID);
7197 DebugOnly<bool> result = journalQuotaObject->MaybeUpdateSize(
7198 journalFileSize, /* aTruncate */ true);
7199 MOZ_ASSERT(result);
7201 result = quotaObject->MaybeUpdateSize(fileSize, /* aTruncate */ true);
7202 MOZ_ASSERT(result);
7205 Result<int64_t, nsresult> DatabaseConnection::GetFileSize(
7206 const nsAString& aPath) {
7207 MOZ_ASSERT(!aPath.IsEmpty());
7209 QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath));
7210 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists));
7212 if (exists) {
7213 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(file, GetFileSize));
7216 return 0;
7219 DatabaseConnection::AutoSavepoint::AutoSavepoint()
7220 : mConnection(nullptr)
7221 #ifdef DEBUG
7223 mDEBUGTransaction(nullptr)
7224 #endif
7226 MOZ_COUNT_CTOR(DatabaseConnection::AutoSavepoint);
7229 DatabaseConnection::AutoSavepoint::~AutoSavepoint() {
7230 MOZ_COUNT_DTOR(DatabaseConnection::AutoSavepoint);
7232 if (mConnection) {
7233 mConnection->AssertIsOnConnectionThread();
7234 MOZ_ASSERT(mDEBUGTransaction);
7235 MOZ_ASSERT(
7236 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::ReadWrite ||
7237 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
7238 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
7239 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::VersionChange);
7241 QM_WARNONLY_TRY(QM_TO_RESULT(mConnection->RollbackSavepoint()));
7245 nsresult DatabaseConnection::AutoSavepoint::Start(
7246 const TransactionBase& aTransaction) {
7247 MOZ_ASSERT(aTransaction.GetMode() == IDBTransaction::Mode::ReadWrite ||
7248 aTransaction.GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
7249 aTransaction.GetMode() == IDBTransaction::Mode::Cleanup ||
7250 aTransaction.GetMode() == IDBTransaction::Mode::VersionChange);
7252 DatabaseConnection* connection = aTransaction.GetDatabase().GetConnection();
7253 MOZ_ASSERT(connection);
7254 connection->AssertIsOnConnectionThread();
7256 // The previous operation failed to begin a write transaction and the
7257 // following opertion jumped to the connection thread before the previous
7258 // operation has updated its failure to the transaction.
7259 if (!connection->GetUpdateRefcountFunction()) {
7260 NS_WARNING(
7261 "The connection was closed because the previous operation "
7262 "failed!");
7263 return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
7266 MOZ_ASSERT(!mConnection);
7267 MOZ_ASSERT(!mDEBUGTransaction);
7269 QM_TRY(MOZ_TO_RESULT(connection->StartSavepoint()));
7271 mConnection = connection;
7272 #ifdef DEBUG
7273 mDEBUGTransaction = &aTransaction;
7274 #endif
7276 return NS_OK;
7279 nsresult DatabaseConnection::AutoSavepoint::Commit() {
7280 MOZ_ASSERT(mConnection);
7281 mConnection->AssertIsOnConnectionThread();
7282 MOZ_ASSERT(mDEBUGTransaction);
7284 QM_TRY(MOZ_TO_RESULT(mConnection->ReleaseSavepoint()));
7286 mConnection = nullptr;
7287 #ifdef DEBUG
7288 mDEBUGTransaction = nullptr;
7289 #endif
7291 return NS_OK;
7294 DatabaseConnection::UpdateRefcountFunction::UpdateRefcountFunction(
7295 DatabaseConnection* const aConnection, DatabaseFileManager& aFileManager)
7296 : mConnection(aConnection),
7297 mFileManager(aFileManager),
7298 mInSavepoint(false) {
7299 MOZ_ASSERT(aConnection);
7300 aConnection->AssertIsOnConnectionThread();
7303 nsresult DatabaseConnection::UpdateRefcountFunction::WillCommit() {
7304 MOZ_ASSERT(mConnection);
7305 mConnection->AssertIsOnConnectionThread();
7306 MOZ_ASSERT(mConnection->HasStorageConnection());
7308 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::WillCommit",
7309 DOM);
7311 // The parameter names are not used, parameters are bound by index
7312 // only locally in the same function.
7313 auto update =
7314 [updateStatement = LazyStatement{*mConnection,
7315 "UPDATE file "
7316 "SET refcount = refcount + :delta "
7317 "WHERE id = :id"_ns},
7318 selectStatement = LazyStatement{*mConnection,
7319 "SELECT id "
7320 "FROM file "
7321 "WHERE id = :id"_ns},
7322 insertStatement =
7323 LazyStatement{
7324 *mConnection,
7325 "INSERT INTO file (id, refcount) VALUES(:id, :delta)"_ns},
7326 this](int64_t aId, int32_t aDelta) mutable -> Result<Ok, nsresult> {
7327 AUTO_PROFILER_LABEL(
7328 "DatabaseConnection::UpdateRefcountFunction::WillCommit::Update", DOM);
7330 QM_TRY_INSPECT(const auto& borrowedUpdateStatement,
7331 updateStatement.Borrow());
7333 QM_TRY(
7334 MOZ_TO_RESULT(borrowedUpdateStatement->BindInt32ByIndex(0, aDelta)));
7335 QM_TRY(MOZ_TO_RESULT(borrowedUpdateStatement->BindInt64ByIndex(1, aId)));
7336 QM_TRY(MOZ_TO_RESULT(borrowedUpdateStatement->Execute()));
7339 QM_TRY_INSPECT(
7340 const int32_t& rows,
7341 MOZ_TO_RESULT_INVOKE_MEMBER(mConnection->MutableStorageConnection(),
7342 GetAffectedRows));
7344 if (rows > 0) {
7345 QM_TRY_INSPECT(
7346 const bool& hasResult,
7347 selectStatement
7348 .BorrowAndExecuteSingleStep(
7349 [aId](auto& stmt) -> Result<Ok, nsresult> {
7350 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, aId)));
7351 return Ok{};
7353 .map(IsSome));
7355 if (!hasResult) {
7356 // Don't have to create the journal here, we can create all at once,
7357 // just before commit
7358 mJournalsToCreateBeforeCommit.AppendElement(aId);
7361 return Ok{};
7364 QM_TRY_INSPECT(const auto& borrowedInsertStatement,
7365 insertStatement.Borrow());
7367 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->BindInt64ByIndex(0, aId)));
7368 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->BindInt32ByIndex(1, aDelta)));
7369 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->Execute()));
7371 mJournalsToRemoveAfterCommit.AppendElement(aId);
7373 return Ok{};
7376 QM_TRY(CollectEachInRange(
7377 mFileInfoEntries, [&update](const auto& entry) -> Result<Ok, nsresult> {
7378 const auto delta = entry.GetData()->Delta();
7379 if (delta) {
7380 QM_TRY(update(entry.GetKey(), delta));
7383 return Ok{};
7384 }));
7386 QM_TRY(MOZ_TO_RESULT(CreateJournals()));
7388 return NS_OK;
7391 void DatabaseConnection::UpdateRefcountFunction::DidCommit() {
7392 MOZ_ASSERT(mConnection);
7393 mConnection->AssertIsOnConnectionThread();
7395 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::DidCommit",
7396 DOM);
7398 for (const auto& entry : mFileInfoEntries.Values()) {
7399 entry->MaybeUpdateDBRefs();
7402 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveJournals(mJournalsToRemoveAfterCommit)));
7405 void DatabaseConnection::UpdateRefcountFunction::DidAbort() {
7406 MOZ_ASSERT(mConnection);
7407 mConnection->AssertIsOnConnectionThread();
7409 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::DidAbort",
7410 DOM);
7412 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveJournals(mJournalsToRemoveAfterAbort)));
7415 void DatabaseConnection::UpdateRefcountFunction::StartSavepoint() {
7416 MOZ_ASSERT(mConnection);
7417 mConnection->AssertIsOnConnectionThread();
7418 MOZ_ASSERT(!mInSavepoint);
7419 MOZ_ASSERT(!mSavepointEntriesIndex.Count());
7421 mInSavepoint = true;
7424 void DatabaseConnection::UpdateRefcountFunction::ReleaseSavepoint() {
7425 MOZ_ASSERT(mConnection);
7426 mConnection->AssertIsOnConnectionThread();
7427 MOZ_ASSERT(mInSavepoint);
7429 mSavepointEntriesIndex.Clear();
7430 mInSavepoint = false;
7433 void DatabaseConnection::UpdateRefcountFunction::RollbackSavepoint() {
7434 MOZ_ASSERT(mConnection);
7435 mConnection->AssertIsOnConnectionThread();
7436 MOZ_ASSERT(!IsOnBackgroundThread());
7437 MOZ_ASSERT(mInSavepoint);
7439 for (const auto& entry : mSavepointEntriesIndex.Values()) {
7440 entry->DecBySavepointDelta();
7443 mInSavepoint = false;
7444 mSavepointEntriesIndex.Clear();
7447 void DatabaseConnection::UpdateRefcountFunction::Reset() {
7448 MOZ_ASSERT(mConnection);
7449 mConnection->AssertIsOnConnectionThread();
7450 MOZ_ASSERT(!mSavepointEntriesIndex.Count());
7451 MOZ_ASSERT(!mInSavepoint);
7453 mJournalsToCreateBeforeCommit.Clear();
7454 mJournalsToRemoveAfterCommit.Clear();
7455 mJournalsToRemoveAfterAbort.Clear();
7457 // DatabaseFileInfo implementation automatically removes unreferenced files,
7458 // but it's done asynchronously and with a delay. We want to remove them (and
7459 // decrease quota usage) before we fire the commit event.
7460 for (const auto& entry : mFileInfoEntries.Values()) {
7461 // We need to move mFileInfo into a raw pointer in order to release it
7462 // explicitly with aSyncDeleteFile == true.
7463 DatabaseFileInfo* const fileInfo = entry->ReleaseFileInfo().forget().take();
7464 MOZ_ASSERT(fileInfo);
7466 fileInfo->Release(/* aSyncDeleteFile */ true);
7469 mFileInfoEntries.Clear();
7472 nsresult DatabaseConnection::UpdateRefcountFunction::ProcessValue(
7473 mozIStorageValueArray* aValues, int32_t aIndex, UpdateType aUpdateType) {
7474 MOZ_ASSERT(mConnection);
7475 mConnection->AssertIsOnConnectionThread();
7476 MOZ_ASSERT(aValues);
7478 AUTO_PROFILER_LABEL(
7479 "DatabaseConnection::UpdateRefcountFunction::ProcessValue", DOM);
7481 QM_TRY_INSPECT(const int32_t& type,
7482 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, aIndex));
7484 if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
7485 return NS_OK;
7488 QM_TRY_INSPECT(const auto& ids, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7489 nsString, aValues, GetString, aIndex));
7491 QM_TRY_INSPECT(const auto& files,
7492 DeserializeStructuredCloneFiles(mFileManager, ids));
7494 for (const StructuredCloneFileParent& file : files) {
7495 const int64_t id = file.FileInfo().Id();
7496 MOZ_ASSERT(id > 0);
7498 const auto entry =
7499 WrapNotNull(mFileInfoEntries.GetOrInsertNew(id, file.FileInfoPtr()));
7501 if (mInSavepoint) {
7502 mSavepointEntriesIndex.InsertOrUpdate(id, entry);
7505 switch (aUpdateType) {
7506 case UpdateType::Increment:
7507 entry->IncDeltas(mInSavepoint);
7508 break;
7509 case UpdateType::Decrement:
7510 entry->DecDeltas(mInSavepoint);
7511 break;
7512 default:
7513 MOZ_CRASH("Unknown update type!");
7517 return NS_OK;
7520 nsresult DatabaseConnection::UpdateRefcountFunction::CreateJournals() {
7521 MOZ_ASSERT(mConnection);
7522 mConnection->AssertIsOnConnectionThread();
7524 AUTO_PROFILER_LABEL(
7525 "DatabaseConnection::UpdateRefcountFunction::CreateJournals", DOM);
7527 const nsCOMPtr<nsIFile> journalDirectory = mFileManager.GetJournalDirectory();
7528 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE);
7530 for (const int64_t id : mJournalsToCreateBeforeCommit) {
7531 const nsCOMPtr<nsIFile> file =
7532 DatabaseFileManager::GetFileForId(journalDirectory, id);
7533 QM_TRY(OkIf(file), NS_ERROR_FAILURE);
7535 QM_TRY(MOZ_TO_RESULT(file->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
7537 mJournalsToRemoveAfterAbort.AppendElement(id);
7540 return NS_OK;
7543 nsresult DatabaseConnection::UpdateRefcountFunction::RemoveJournals(
7544 const nsTArray<int64_t>& aJournals) {
7545 MOZ_ASSERT(mConnection);
7546 mConnection->AssertIsOnConnectionThread();
7548 AUTO_PROFILER_LABEL(
7549 "DatabaseConnection::UpdateRefcountFunction::RemoveJournals", DOM);
7551 nsCOMPtr<nsIFile> journalDirectory = mFileManager.GetJournalDirectory();
7552 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE);
7554 for (const auto& journal : aJournals) {
7555 nsCOMPtr<nsIFile> file =
7556 DatabaseFileManager::GetFileForId(journalDirectory, journal);
7557 QM_TRY(OkIf(file), NS_ERROR_FAILURE);
7559 QM_WARNONLY_TRY(QM_TO_RESULT(file->Remove(false)));
7562 return NS_OK;
7565 NS_IMPL_ISUPPORTS(DatabaseConnection::UpdateRefcountFunction,
7566 mozIStorageFunction)
7568 NS_IMETHODIMP
7569 DatabaseConnection::UpdateRefcountFunction::OnFunctionCall(
7570 mozIStorageValueArray* aValues, nsIVariant** _retval) {
7571 MOZ_ASSERT(aValues);
7572 MOZ_ASSERT(_retval);
7574 AUTO_PROFILER_LABEL(
7575 "DatabaseConnection::UpdateRefcountFunction::OnFunctionCall", DOM);
7577 #ifdef DEBUG
7579 QM_TRY_INSPECT(const uint32_t& numEntries,
7580 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetNumEntries),
7581 QM_ASSERT_UNREACHABLE);
7583 MOZ_ASSERT(numEntries == 2);
7585 QM_TRY_INSPECT(const int32_t& type1,
7586 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 0),
7587 QM_ASSERT_UNREACHABLE);
7589 QM_TRY_INSPECT(const int32_t& type2,
7590 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 1),
7591 QM_ASSERT_UNREACHABLE);
7593 MOZ_ASSERT(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL &&
7594 type2 == mozIStorageValueArray::VALUE_TYPE_NULL));
7596 #endif
7598 QM_TRY(MOZ_TO_RESULT(ProcessValue(aValues, 0, UpdateType::Decrement)));
7600 QM_TRY(MOZ_TO_RESULT(ProcessValue(aValues, 1, UpdateType::Increment)));
7602 return NS_OK;
7605 /*******************************************************************************
7606 * ConnectionPool implementation
7607 ******************************************************************************/
7609 ConnectionPool::ConnectionPool()
7610 : mDatabasesMutex("ConnectionPool::mDatabasesMutex"),
7611 mIdleTimer(NS_NewTimer()),
7612 mNextTransactionId(0),
7613 mTotalThreadCount(0) {
7614 AssertIsOnOwningThread();
7615 AssertIsOnBackgroundThread();
7616 MOZ_ASSERT(mIdleTimer);
7619 ConnectionPool::~ConnectionPool() {
7620 AssertIsOnOwningThread();
7621 MOZ_ASSERT(mIdleThreads.IsEmpty());
7622 MOZ_ASSERT(mIdleDatabases.IsEmpty());
7623 MOZ_ASSERT(!mIdleTimer);
7624 MOZ_ASSERT(mTargetIdleTime.IsNull());
7625 MOZ_ASSERT(!mDatabases.Count());
7626 MOZ_ASSERT(!mTransactions.Count());
7627 MOZ_ASSERT(mQueuedTransactions.IsEmpty());
7628 MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
7629 MOZ_ASSERT(!mTotalThreadCount);
7630 MOZ_ASSERT(mShutdownRequested);
7631 MOZ_ASSERT(mShutdownComplete);
7634 // static
7635 void ConnectionPool::IdleTimerCallback(nsITimer* aTimer, void* aClosure) {
7636 MOZ_ASSERT(aTimer);
7637 MOZ_ASSERT(aClosure);
7639 AUTO_PROFILER_LABEL("ConnectionPool::IdleTimerCallback", DOM);
7641 auto& self = *static_cast<ConnectionPool*>(aClosure);
7642 MOZ_ASSERT(self.mIdleTimer);
7643 MOZ_ASSERT(SameCOMIdentity(self.mIdleTimer, aTimer));
7644 MOZ_ASSERT(!self.mTargetIdleTime.IsNull());
7645 MOZ_ASSERT_IF(self.mIdleDatabases.IsEmpty(), !self.mIdleThreads.IsEmpty());
7646 MOZ_ASSERT_IF(self.mIdleThreads.IsEmpty(), !self.mIdleDatabases.IsEmpty());
7648 self.mTargetIdleTime = TimeStamp();
7650 // Cheat a little.
7651 const TimeStamp now =
7652 TimeStamp::NowLoRes() + TimeDuration::FromMilliseconds(500);
7654 // XXX Move this to ArrayAlgorithm.h?
7655 const auto removeUntil = [](auto& array, auto&& cond) {
7656 const auto begin = array.begin(), end = array.end();
7657 array.RemoveElementsRange(
7658 begin, std::find_if(begin, end, std::forward<decltype(cond)>(cond)));
7661 removeUntil(self.mIdleDatabases, [now, &self](const auto& info) {
7662 if (now >= info.mIdleTime) {
7663 if ((*info.mDatabaseInfo)->mIdle) {
7664 self.PerformIdleDatabaseMaintenance(*info.mDatabaseInfo.ref());
7665 } else {
7666 self.CloseDatabase(*info.mDatabaseInfo.ref());
7669 return false;
7672 return true;
7675 removeUntil(self.mIdleThreads, [now, &self](auto& info) {
7676 info.mThreadInfo.AssertValid();
7678 if (now >= info.mIdleTime) {
7679 self.ShutdownThread(std::move(info.mThreadInfo));
7681 return false;
7684 return true;
7687 self.AdjustIdleTimer();
7690 Result<RefPtr<DatabaseConnection>, nsresult>
7691 ConnectionPool::GetOrCreateConnection(const Database& aDatabase) {
7692 MOZ_ASSERT(!NS_IsMainThread());
7693 MOZ_ASSERT(!IsOnBackgroundThread());
7695 AUTO_PROFILER_LABEL("ConnectionPool::GetOrCreateConnection", DOM);
7697 DatabaseInfo* dbInfo;
7699 MutexAutoLock lock(mDatabasesMutex);
7701 dbInfo = mDatabases.Get(aDatabase.Id());
7704 MOZ_ASSERT(dbInfo);
7706 if (dbInfo->mConnection) {
7707 dbInfo->AssertIsOnConnectionThread();
7709 return dbInfo->mConnection;
7712 MOZ_ASSERT(!dbInfo->mDEBUGConnectionThread);
7714 QM_TRY_UNWRAP(
7715 MovingNotNull<nsCOMPtr<mozIStorageConnection>> storageConnection,
7716 GetStorageConnection(aDatabase.FilePath(), aDatabase.DirectoryLockId(),
7717 aDatabase.TelemetryId(), aDatabase.MaybeKeyRef()));
7719 RefPtr<DatabaseConnection> connection = new DatabaseConnection(
7720 std::move(storageConnection), aDatabase.GetFileManagerPtr());
7722 QM_TRY(MOZ_TO_RESULT(connection->Init()));
7724 dbInfo->mConnection = connection;
7726 IDB_DEBUG_LOG(("ConnectionPool created connection 0x%p for '%s'",
7727 dbInfo->mConnection.get(),
7728 NS_ConvertUTF16toUTF8(aDatabase.FilePath()).get()));
7730 #ifdef DEBUG
7731 dbInfo->mDEBUGConnectionThread = PR_GetCurrentThread();
7732 #endif
7734 return connection;
7737 uint64_t ConnectionPool::Start(
7738 const nsID& aBackgroundChildLoggingId, const nsACString& aDatabaseId,
7739 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames,
7740 bool aIsWriteTransaction,
7741 TransactionDatabaseOperationBase* aTransactionOp) {
7742 AssertIsOnOwningThread();
7743 MOZ_ASSERT(!aDatabaseId.IsEmpty());
7744 MOZ_ASSERT(mNextTransactionId < UINT64_MAX);
7745 MOZ_ASSERT(!mShutdownRequested);
7747 AUTO_PROFILER_LABEL("ConnectionPool::Start", DOM);
7749 const uint64_t transactionId = ++mNextTransactionId;
7751 // To avoid always acquiring a lock, we don't use WithEntryHandle here, which
7752 // would require a lock in any case.
7753 DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId);
7755 const bool databaseInfoIsNew = !dbInfo;
7757 if (databaseInfoIsNew) {
7758 MutexAutoLock lock(mDatabasesMutex);
7760 dbInfo = mDatabases
7761 .InsertOrUpdate(aDatabaseId,
7762 MakeUnique<DatabaseInfo>(this, aDatabaseId))
7763 .get();
7766 MOZ_ASSERT(!mTransactions.Contains(transactionId));
7767 auto& transactionInfo = *mTransactions.InsertOrUpdate(
7768 transactionId, MakeUnique<TransactionInfo>(
7769 *dbInfo, aBackgroundChildLoggingId, aDatabaseId,
7770 transactionId, aLoggingSerialNumber, aObjectStoreNames,
7771 aIsWriteTransaction, aTransactionOp));
7773 if (aIsWriteTransaction) {
7774 MOZ_ASSERT(dbInfo->mWriteTransactionCount < UINT32_MAX);
7775 dbInfo->mWriteTransactionCount++;
7776 } else {
7777 MOZ_ASSERT(dbInfo->mReadTransactionCount < UINT32_MAX);
7778 dbInfo->mReadTransactionCount++;
7781 auto& blockingTransactions = dbInfo->mBlockingTransactions;
7783 for (const nsAString& objectStoreName : aObjectStoreNames) {
7784 TransactionInfoPair* blockInfo =
7785 blockingTransactions.GetOrInsertNew(objectStoreName);
7787 // Mark what we are blocking on.
7788 if (const auto maybeBlockingRead = blockInfo->mLastBlockingReads) {
7789 transactionInfo.mBlockedOn.Insert(&maybeBlockingRead.ref());
7790 maybeBlockingRead->AddBlockingTransaction(transactionInfo);
7793 if (aIsWriteTransaction) {
7794 for (const auto blockingWrite : blockInfo->mLastBlockingWrites) {
7795 transactionInfo.mBlockedOn.Insert(blockingWrite);
7796 blockingWrite->AddBlockingTransaction(transactionInfo);
7799 blockInfo->mLastBlockingReads = SomeRef(transactionInfo);
7800 blockInfo->mLastBlockingWrites.Clear();
7801 } else {
7802 blockInfo->mLastBlockingWrites.AppendElement(
7803 WrapNotNullUnchecked(&transactionInfo));
7807 if (!transactionInfo.mBlockedOn.Count()) {
7808 Unused << ScheduleTransaction(transactionInfo,
7809 /* aFromQueuedTransactions */ false);
7812 if (!databaseInfoIsNew &&
7813 (mIdleDatabases.RemoveElement(dbInfo) ||
7814 mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo))) {
7815 AdjustIdleTimer();
7818 return transactionId;
7821 void ConnectionPool::Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable) {
7822 AssertIsOnOwningThread();
7823 MOZ_ASSERT(aRunnable);
7825 AUTO_PROFILER_LABEL("ConnectionPool::Dispatch", DOM);
7827 auto* const transactionInfo = mTransactions.Get(aTransactionId);
7828 MOZ_ASSERT(transactionInfo);
7829 MOZ_ASSERT(!transactionInfo->mFinished);
7831 if (transactionInfo->mRunning) {
7832 DatabaseInfo& dbInfo = transactionInfo->mDatabaseInfo;
7833 dbInfo.mThreadInfo.AssertValid();
7834 MOZ_ASSERT(!dbInfo.mClosing);
7835 MOZ_ASSERT_IF(
7836 transactionInfo->mIsWriteTransaction,
7837 dbInfo.mRunningWriteTransaction &&
7838 dbInfo.mRunningWriteTransaction.refEquals(*transactionInfo));
7840 MOZ_ALWAYS_SUCCEEDS(
7841 dbInfo.mThreadInfo.ThreadRef().Dispatch(aRunnable, NS_DISPATCH_NORMAL));
7842 } else {
7843 transactionInfo->mQueuedRunnables.AppendElement(aRunnable);
7847 void ConnectionPool::Finish(uint64_t aTransactionId,
7848 FinishCallback* aCallback) {
7849 AssertIsOnOwningThread();
7851 #ifdef DEBUG
7852 auto* const transactionInfo = mTransactions.Get(aTransactionId);
7853 MOZ_ASSERT(transactionInfo);
7854 MOZ_ASSERT(!transactionInfo->mFinished);
7855 #endif
7857 AUTO_PROFILER_LABEL("ConnectionPool::Finish", DOM);
7859 RefPtr<FinishCallbackWrapper> wrapper =
7860 new FinishCallbackWrapper(this, aTransactionId, aCallback);
7862 Dispatch(aTransactionId, wrapper);
7864 #ifdef DEBUG
7865 transactionInfo->mFinished.Flip();
7866 #endif
7869 void ConnectionPool::WaitForDatabasesToComplete(
7870 nsTArray<nsCString>&& aDatabaseIds, nsIRunnable* aCallback) {
7871 AssertIsOnOwningThread();
7872 MOZ_ASSERT(!aDatabaseIds.IsEmpty());
7873 MOZ_ASSERT(aCallback);
7875 AUTO_PROFILER_LABEL("ConnectionPool::WaitForDatabasesToComplete", DOM);
7877 bool mayRunCallbackImmediately = true;
7879 for (const nsACString& databaseId : aDatabaseIds) {
7880 MOZ_ASSERT(!databaseId.IsEmpty());
7882 if (CloseDatabaseWhenIdleInternal(databaseId)) {
7883 mayRunCallbackImmediately = false;
7887 if (mayRunCallbackImmediately) {
7888 Unused << aCallback->Run();
7889 return;
7892 mCompleteCallbacks.EmplaceBack(MakeUnique<DatabasesCompleteCallback>(
7893 std::move(aDatabaseIds), aCallback));
7896 void ConnectionPool::Shutdown() {
7897 AssertIsOnOwningThread();
7898 MOZ_ASSERT(!mShutdownComplete);
7900 AUTO_PROFILER_LABEL("ConnectionPool::Shutdown", DOM);
7902 mShutdownRequested.Flip();
7904 CancelIdleTimer();
7905 MOZ_ASSERT(mTargetIdleTime.IsNull());
7907 mIdleTimer = nullptr;
7909 CloseIdleDatabases();
7911 ShutdownIdleThreads();
7913 if (!mDatabases.Count()) {
7914 MOZ_ASSERT(!mTransactions.Count());
7916 Cleanup();
7918 MOZ_ASSERT(mShutdownComplete);
7919 return;
7922 MOZ_ALWAYS_TRUE(SpinEventLoopUntil("ConnectionPool::Shutdown"_ns, [&]() {
7923 return static_cast<bool>(mShutdownComplete);
7924 }));
7927 void ConnectionPool::Cleanup() {
7928 AssertIsOnOwningThread();
7929 MOZ_ASSERT(mShutdownRequested);
7930 MOZ_ASSERT(!mShutdownComplete);
7931 MOZ_ASSERT(!mDatabases.Count());
7932 MOZ_ASSERT(!mTransactions.Count());
7933 MOZ_ASSERT(mIdleThreads.IsEmpty());
7935 AUTO_PROFILER_LABEL("ConnectionPool::Cleanup", DOM);
7937 if (!mCompleteCallbacks.IsEmpty()) {
7938 // Run all callbacks manually now.
7941 auto completeCallbacks = std::move(mCompleteCallbacks);
7942 for (const auto& completeCallback : completeCallbacks) {
7943 MOZ_ASSERT(completeCallback);
7944 MOZ_ASSERT(completeCallback->mCallback);
7946 Unused << completeCallback->mCallback->Run();
7949 // We expect no new callbacks being completed by running the existing
7950 // ones.
7951 MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
7954 // And make sure they get processed.
7955 nsIThread* currentThread = NS_GetCurrentThread();
7956 MOZ_ASSERT(currentThread);
7958 MOZ_ALWAYS_SUCCEEDS(NS_ProcessPendingEvents(currentThread));
7961 mShutdownComplete.Flip();
7964 void ConnectionPool::AdjustIdleTimer() {
7965 AssertIsOnOwningThread();
7966 MOZ_ASSERT(mIdleTimer);
7968 AUTO_PROFILER_LABEL("ConnectionPool::AdjustIdleTimer", DOM);
7970 // Figure out the next time at which we should release idle resources. This
7971 // includes both databases and threads.
7972 TimeStamp newTargetIdleTime;
7973 MOZ_ASSERT(newTargetIdleTime.IsNull());
7975 if (!mIdleDatabases.IsEmpty()) {
7976 newTargetIdleTime = mIdleDatabases[0].mIdleTime;
7979 if (!mIdleThreads.IsEmpty()) {
7980 const TimeStamp& idleTime = mIdleThreads[0].mIdleTime;
7982 if (newTargetIdleTime.IsNull() || idleTime < newTargetIdleTime) {
7983 newTargetIdleTime = idleTime;
7987 MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleDatabases.IsEmpty());
7988 MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleThreads.IsEmpty());
7990 // Cancel the timer if it was running and the new target time is different.
7991 if (!mTargetIdleTime.IsNull() &&
7992 (newTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
7993 CancelIdleTimer();
7995 MOZ_ASSERT(mTargetIdleTime.IsNull());
7998 // Schedule the timer if we have a target time different than before.
7999 if (!newTargetIdleTime.IsNull() &&
8000 (mTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
8001 double delta = (newTargetIdleTime - TimeStamp::NowLoRes()).ToMilliseconds();
8003 uint32_t delay;
8004 if (delta > 0) {
8005 delay = uint32_t(std::min(delta, double(UINT32_MAX)));
8006 } else {
8007 delay = 0;
8010 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->InitWithNamedFuncCallback(
8011 IdleTimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT,
8012 "ConnectionPool::IdleTimerCallback"));
8014 mTargetIdleTime = newTargetIdleTime;
8018 void ConnectionPool::CancelIdleTimer() {
8019 AssertIsOnOwningThread();
8020 MOZ_ASSERT(mIdleTimer);
8022 if (!mTargetIdleTime.IsNull()) {
8023 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel());
8025 mTargetIdleTime = TimeStamp();
8026 MOZ_ASSERT(mTargetIdleTime.IsNull());
8030 void ConnectionPool::ShutdownThread(ThreadInfo aThreadInfo) {
8031 AssertIsOnOwningThread();
8032 MOZ_ASSERT(mTotalThreadCount);
8034 // We need to move thread and runnable separately.
8035 auto [thread, runnable] = aThreadInfo.Forget();
8037 IDB_DEBUG_LOG(("ConnectionPool shutting down thread %" PRIu32,
8038 runnable->SerialNumber()));
8040 // This should clean up the thread with the profiler.
8041 MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
8043 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(
8044 "nsIThread::AsyncShutdown", thread, &nsIThread::AsyncShutdown)));
8046 mTotalThreadCount--;
8049 void ConnectionPool::CloseIdleDatabases() {
8050 AssertIsOnOwningThread();
8051 MOZ_ASSERT(mShutdownRequested);
8053 AUTO_PROFILER_LABEL("ConnectionPool::CloseIdleDatabases", DOM);
8055 if (!mIdleDatabases.IsEmpty()) {
8056 for (IdleDatabaseInfo& idleInfo : mIdleDatabases) {
8057 CloseDatabase(*idleInfo.mDatabaseInfo.ref());
8059 mIdleDatabases.Clear();
8062 if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) {
8063 for (const auto dbInfo : mDatabasesPerformingIdleMaintenance) {
8064 CloseDatabase(*dbInfo);
8066 mDatabasesPerformingIdleMaintenance.Clear();
8070 void ConnectionPool::ShutdownIdleThreads() {
8071 AssertIsOnOwningThread();
8072 MOZ_ASSERT(mShutdownRequested);
8074 AUTO_PROFILER_LABEL("ConnectionPool::ShutdownIdleThreads", DOM);
8076 for (auto& idleThread : mIdleThreads) {
8077 ShutdownThread(std::move(idleThread.mThreadInfo));
8079 mIdleThreads.Clear();
8082 bool ConnectionPool::ScheduleTransaction(TransactionInfo& aTransactionInfo,
8083 bool aFromQueuedTransactions) {
8084 AssertIsOnOwningThread();
8086 AUTO_PROFILER_LABEL("ConnectionPool::ScheduleTransaction", DOM);
8088 DatabaseInfo& dbInfo = aTransactionInfo.mDatabaseInfo;
8090 dbInfo.mIdle = false;
8092 if (dbInfo.mClosing) {
8093 MOZ_ASSERT(!mIdleDatabases.Contains(&dbInfo));
8094 MOZ_ASSERT(
8095 !dbInfo.mTransactionsScheduledDuringClose.Contains(&aTransactionInfo));
8097 dbInfo.mTransactionsScheduledDuringClose.AppendElement(
8098 WrapNotNullUnchecked(&aTransactionInfo));
8099 return true;
8102 if (!dbInfo.mThreadInfo.IsValid()) {
8103 if (mIdleThreads.IsEmpty()) {
8104 bool created = false;
8106 if (mTotalThreadCount < kMaxConnectionThreadCount) {
8107 // This will set the thread up with the profiler.
8108 RefPtr<ThreadRunnable> runnable = new ThreadRunnable();
8110 nsCOMPtr<nsIThread> newThread;
8111 nsresult rv = NS_NewNamedThread(runnable->GetThreadName(),
8112 getter_AddRefs(newThread), runnable);
8113 if (NS_SUCCEEDED(rv)) {
8114 newThread->SetNameForWakeupTelemetry("IndexedDB (all)"_ns);
8115 MOZ_ASSERT(newThread);
8117 IDB_DEBUG_LOG(("ConnectionPool created thread %" PRIu32,
8118 runnable->SerialNumber()));
8120 dbInfo.mThreadInfo =
8121 ThreadInfo{std::move(newThread), std::move(runnable)};
8123 mTotalThreadCount++;
8124 created = true;
8125 } else {
8126 NS_WARNING("Failed to make new thread!");
8128 } else if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) {
8129 // We need a thread right now so force all idle processing to stop by
8130 // posting a dummy runnable to each thread that might be doing idle
8131 // maintenance.
8133 // This is copied for each database inside the loop below, it is
8134 // deliberately const to prevent the attempt to wrongly optimize the
8135 // refcounting by passing runnable.forget() to the Dispatch method, see
8136 // bug 1598559.
8137 const nsCOMPtr<nsIRunnable> runnable =
8138 new Runnable("IndexedDBDummyRunnable");
8140 for (uint32_t index = mDatabasesPerformingIdleMaintenance.Length();
8141 index > 0; index--) {
8142 const auto dbInfo = mDatabasesPerformingIdleMaintenance[index - 1];
8143 dbInfo->mThreadInfo.AssertValid();
8145 MOZ_ALWAYS_SUCCEEDS(dbInfo->mThreadInfo.ThreadRef().Dispatch(
8146 runnable, NS_DISPATCH_NORMAL));
8150 if (!created) {
8151 if (!aFromQueuedTransactions) {
8152 MOZ_ASSERT(!mQueuedTransactions.Contains(&aTransactionInfo));
8153 mQueuedTransactions.AppendElement(
8154 WrapNotNullUnchecked(&aTransactionInfo));
8156 return false;
8158 } else {
8159 dbInfo.mThreadInfo = std::move(mIdleThreads.PopLastElement().mThreadInfo);
8161 AdjustIdleTimer();
8165 dbInfo.mThreadInfo.AssertValid();
8167 if (aTransactionInfo.mIsWriteTransaction) {
8168 if (dbInfo.mRunningWriteTransaction) {
8169 // SQLite only allows one write transaction at a time so queue this
8170 // transaction for later.
8171 MOZ_ASSERT(
8172 !dbInfo.mScheduledWriteTransactions.Contains(&aTransactionInfo));
8174 dbInfo.mScheduledWriteTransactions.AppendElement(
8175 WrapNotNullUnchecked(&aTransactionInfo));
8176 return true;
8179 dbInfo.mRunningWriteTransaction = SomeRef(aTransactionInfo);
8180 dbInfo.mNeedsCheckpoint = true;
8183 MOZ_ASSERT(!aTransactionInfo.mRunning);
8184 aTransactionInfo.mRunning = true;
8186 nsTArray<nsCOMPtr<nsIRunnable>>& queuedRunnables =
8187 aTransactionInfo.mQueuedRunnables;
8189 if (!queuedRunnables.IsEmpty()) {
8190 for (auto& queuedRunnable : queuedRunnables) {
8191 MOZ_ALWAYS_SUCCEEDS(dbInfo.mThreadInfo.ThreadRef().Dispatch(
8192 queuedRunnable.forget(), NS_DISPATCH_NORMAL));
8195 queuedRunnables.Clear();
8198 return true;
8201 void ConnectionPool::NoteFinishedTransaction(uint64_t aTransactionId) {
8202 AssertIsOnOwningThread();
8204 AUTO_PROFILER_LABEL("ConnectionPool::NoteFinishedTransaction", DOM);
8206 auto* const transactionInfo = mTransactions.Get(aTransactionId);
8207 MOZ_ASSERT(transactionInfo);
8208 MOZ_ASSERT(transactionInfo->mRunning);
8209 MOZ_ASSERT(transactionInfo->mFinished);
8211 transactionInfo->mRunning = false;
8213 DatabaseInfo& dbInfo = transactionInfo->mDatabaseInfo;
8214 MOZ_ASSERT(mDatabases.Get(transactionInfo->mDatabaseId) == &dbInfo);
8215 dbInfo.mThreadInfo.AssertValid();
8217 // Schedule the next write transaction if there are any queued.
8218 if (dbInfo.mRunningWriteTransaction &&
8219 dbInfo.mRunningWriteTransaction.refEquals(*transactionInfo)) {
8220 MOZ_ASSERT(transactionInfo->mIsWriteTransaction);
8221 MOZ_ASSERT(dbInfo.mNeedsCheckpoint);
8223 dbInfo.mRunningWriteTransaction = Nothing();
8225 if (!dbInfo.mScheduledWriteTransactions.IsEmpty()) {
8226 const auto nextWriteTransaction = dbInfo.mScheduledWriteTransactions[0];
8228 dbInfo.mScheduledWriteTransactions.RemoveElementAt(0);
8230 MOZ_ALWAYS_TRUE(ScheduleTransaction(*nextWriteTransaction,
8231 /* aFromQueuedTransactions */ false));
8235 for (const auto& objectStoreName : transactionInfo->mObjectStoreNames) {
8236 TransactionInfoPair* blockInfo =
8237 dbInfo.mBlockingTransactions.Get(objectStoreName);
8238 MOZ_ASSERT(blockInfo);
8240 if (transactionInfo->mIsWriteTransaction && blockInfo->mLastBlockingReads &&
8241 blockInfo->mLastBlockingReads.refEquals(*transactionInfo)) {
8242 blockInfo->mLastBlockingReads = Nothing();
8245 blockInfo->mLastBlockingWrites.RemoveElement(transactionInfo);
8248 transactionInfo->RemoveBlockingTransactions();
8250 if (transactionInfo->mIsWriteTransaction) {
8251 MOZ_ASSERT(dbInfo.mWriteTransactionCount);
8252 dbInfo.mWriteTransactionCount--;
8253 } else {
8254 MOZ_ASSERT(dbInfo.mReadTransactionCount);
8255 dbInfo.mReadTransactionCount--;
8258 mTransactions.Remove(aTransactionId);
8260 if (!dbInfo.TotalTransactionCount()) {
8261 MOZ_ASSERT(!dbInfo.mIdle);
8262 dbInfo.mIdle = true;
8264 NoteIdleDatabase(dbInfo);
8268 void ConnectionPool::ScheduleQueuedTransactions(ThreadInfo aThreadInfo) {
8269 AssertIsOnOwningThread();
8270 aThreadInfo.AssertValid();
8271 MOZ_ASSERT(!mQueuedTransactions.IsEmpty());
8273 AUTO_PROFILER_LABEL("ConnectionPool::ScheduleQueuedTransactions", DOM);
8275 auto idleThreadInfo = IdleThreadInfo{std::move(aThreadInfo)};
8276 MOZ_ASSERT(!mIdleThreads.Contains(idleThreadInfo));
8277 mIdleThreads.InsertElementSorted(std::move(idleThreadInfo));
8279 const auto foundIt = std::find_if(
8280 mQueuedTransactions.begin(), mQueuedTransactions.end(),
8281 [&me = *this](const auto& queuedTransaction) {
8282 return !me.ScheduleTransaction(*queuedTransaction,
8283 /* aFromQueuedTransactions */ true);
8286 mQueuedTransactions.RemoveElementsRange(mQueuedTransactions.begin(), foundIt);
8288 AdjustIdleTimer();
8291 void ConnectionPool::NoteIdleDatabase(DatabaseInfo& aDatabaseInfo) {
8292 AssertIsOnOwningThread();
8293 MOZ_ASSERT(!aDatabaseInfo.TotalTransactionCount());
8294 aDatabaseInfo.mThreadInfo.AssertValid();
8295 MOZ_ASSERT(!mIdleDatabases.Contains(&aDatabaseInfo));
8297 AUTO_PROFILER_LABEL("ConnectionPool::NoteIdleDatabase", DOM);
8299 const bool otherDatabasesWaiting = !mQueuedTransactions.IsEmpty();
8301 if (mShutdownRequested || otherDatabasesWaiting ||
8302 aDatabaseInfo.mCloseOnIdle) {
8303 // Make sure we close the connection if we're shutting down or giving the
8304 // thread to another database.
8305 CloseDatabase(aDatabaseInfo);
8307 if (otherDatabasesWaiting) {
8308 // Let another database use this thread.
8309 ScheduleQueuedTransactions(std::move(aDatabaseInfo.mThreadInfo));
8310 } else if (mShutdownRequested) {
8311 // If there are no other databases that need to run then we can shut this
8312 // thread down immediately instead of going through the idle thread
8313 // mechanism.
8314 ShutdownThread(std::move(aDatabaseInfo.mThreadInfo));
8317 return;
8320 mIdleDatabases.InsertElementSorted(IdleDatabaseInfo{aDatabaseInfo});
8322 AdjustIdleTimer();
8325 void ConnectionPool::NoteClosedDatabase(DatabaseInfo& aDatabaseInfo) {
8326 AssertIsOnOwningThread();
8327 MOZ_ASSERT(aDatabaseInfo.mClosing);
8328 MOZ_ASSERT(!mIdleDatabases.Contains(&aDatabaseInfo));
8330 AUTO_PROFILER_LABEL("ConnectionPool::NoteClosedDatabase", DOM);
8332 aDatabaseInfo.mClosing = false;
8334 // Figure out what to do with this database's thread. It may have already been
8335 // given to another database, in which case there's nothing to do here.
8336 // Otherwise we prioritize the thread as follows:
8337 // 1. Databases that haven't had an opportunity to run at all are highest
8338 // priority. Those live in the |mQueuedTransactions| list.
8339 // 2. If this database has additional transactions that were started after
8340 // we began closing the connection then the thread can be reused for
8341 // those transactions.
8342 // 3. If we're shutting down then we can get rid of the thread.
8343 // 4. Finally, if nothing above took the thread then we can add it to our
8344 // list of idle threads. It may be reused or it may time out. If we have
8345 // too many idle threads then we will shut down the oldest.
8346 if (aDatabaseInfo.mThreadInfo.IsValid()) {
8347 if (!mQueuedTransactions.IsEmpty()) {
8348 // Give the thread to another database.
8349 ScheduleQueuedTransactions(std::move(aDatabaseInfo.mThreadInfo));
8350 } else if (!aDatabaseInfo.TotalTransactionCount()) {
8351 if (mShutdownRequested) {
8352 ShutdownThread(std::move(aDatabaseInfo.mThreadInfo));
8353 } else {
8354 auto idleThreadInfo =
8355 IdleThreadInfo{std::move(aDatabaseInfo.mThreadInfo)};
8356 MOZ_ASSERT(!mIdleThreads.Contains(idleThreadInfo));
8358 mIdleThreads.InsertElementSorted(std::move(idleThreadInfo));
8360 if (mIdleThreads.Length() > kMaxIdleConnectionThreadCount) {
8361 ShutdownThread(std::move(mIdleThreads[0].mThreadInfo));
8362 mIdleThreads.RemoveElementAt(0);
8365 AdjustIdleTimer();
8370 // Schedule any transactions that were started while we were closing the
8371 // connection.
8372 if (aDatabaseInfo.TotalTransactionCount()) {
8373 auto& scheduledTransactions =
8374 aDatabaseInfo.mTransactionsScheduledDuringClose;
8376 MOZ_ASSERT(!scheduledTransactions.IsEmpty());
8378 for (const auto& scheduledTransaction : scheduledTransactions) {
8379 Unused << ScheduleTransaction(*scheduledTransaction,
8380 /* aFromQueuedTransactions */ false);
8383 scheduledTransactions.Clear();
8385 return;
8388 // There are no more transactions and the connection has been closed. We're
8389 // done with this database.
8391 MutexAutoLock lock(mDatabasesMutex);
8393 mDatabases.Remove(aDatabaseInfo.mDatabaseId);
8396 // That just deleted |aDatabaseInfo|, we must not access that below.
8398 // See if we need to fire any complete callbacks now that the database is
8399 // finished.
8400 mCompleteCallbacks.RemoveLastElements(
8401 mCompleteCallbacks.end() -
8402 std::remove_if(mCompleteCallbacks.begin(), mCompleteCallbacks.end(),
8403 [&me = *this](const auto& completeCallback) {
8404 return me.MaybeFireCallback(completeCallback.get());
8405 }));
8407 // If that was the last database and we're supposed to be shutting down then
8408 // we are finished.
8409 if (mShutdownRequested && !mDatabases.Count()) {
8410 MOZ_ASSERT(!mTransactions.Count());
8411 Cleanup();
8415 bool ConnectionPool::MaybeFireCallback(DatabasesCompleteCallback* aCallback) {
8416 AssertIsOnOwningThread();
8417 MOZ_ASSERT(aCallback);
8418 MOZ_ASSERT(!aCallback->mDatabaseIds.IsEmpty());
8419 MOZ_ASSERT(aCallback->mCallback);
8421 AUTO_PROFILER_LABEL("ConnectionPool::MaybeFireCallback", DOM);
8423 if (std::any_of(aCallback->mDatabaseIds.begin(),
8424 aCallback->mDatabaseIds.end(),
8425 [&databases = mDatabases](const auto& databaseId) {
8426 MOZ_ASSERT(!databaseId.IsEmpty());
8428 return databases.Get(databaseId);
8429 })) {
8430 return false;
8433 Unused << aCallback->mCallback->Run();
8434 return true;
8437 void ConnectionPool::PerformIdleDatabaseMaintenance(
8438 DatabaseInfo& aDatabaseInfo) {
8439 AssertIsOnOwningThread();
8440 MOZ_ASSERT(!aDatabaseInfo.TotalTransactionCount());
8441 aDatabaseInfo.mThreadInfo.AssertValid();
8442 MOZ_ASSERT(aDatabaseInfo.mIdle);
8443 MOZ_ASSERT(!aDatabaseInfo.mCloseOnIdle);
8444 MOZ_ASSERT(!aDatabaseInfo.mClosing);
8445 MOZ_ASSERT(mIdleDatabases.Contains(&aDatabaseInfo));
8446 MOZ_ASSERT(!mDatabasesPerformingIdleMaintenance.Contains(&aDatabaseInfo));
8448 const bool neededCheckpoint = aDatabaseInfo.mNeedsCheckpoint;
8450 aDatabaseInfo.mNeedsCheckpoint = false;
8451 aDatabaseInfo.mIdle = false;
8453 mDatabasesPerformingIdleMaintenance.AppendElement(
8454 WrapNotNullUnchecked(&aDatabaseInfo));
8456 MOZ_ALWAYS_SUCCEEDS(aDatabaseInfo.mThreadInfo.ThreadRef().Dispatch(
8457 MakeAndAddRef<IdleConnectionRunnable>(aDatabaseInfo, neededCheckpoint),
8458 NS_DISPATCH_NORMAL));
8461 void ConnectionPool::CloseDatabase(DatabaseInfo& aDatabaseInfo) const {
8462 AssertIsOnOwningThread();
8463 MOZ_DIAGNOSTIC_ASSERT(!aDatabaseInfo.TotalTransactionCount());
8464 aDatabaseInfo.mThreadInfo.AssertValid();
8465 MOZ_ASSERT(!aDatabaseInfo.mClosing);
8467 aDatabaseInfo.mIdle = false;
8468 aDatabaseInfo.mNeedsCheckpoint = false;
8469 aDatabaseInfo.mClosing = true;
8471 MOZ_ALWAYS_SUCCEEDS(aDatabaseInfo.mThreadInfo.ThreadRef().Dispatch(
8472 MakeAndAddRef<CloseConnectionRunnable>(aDatabaseInfo),
8473 NS_DISPATCH_NORMAL));
8476 bool ConnectionPool::CloseDatabaseWhenIdleInternal(
8477 const nsACString& aDatabaseId) {
8478 AssertIsOnOwningThread();
8479 MOZ_ASSERT(!aDatabaseId.IsEmpty());
8481 AUTO_PROFILER_LABEL("ConnectionPool::CloseDatabaseWhenIdleInternal", DOM);
8483 if (DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId)) {
8484 if (mIdleDatabases.RemoveElement(dbInfo) ||
8485 mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo)) {
8486 CloseDatabase(*dbInfo);
8487 AdjustIdleTimer();
8488 } else {
8489 dbInfo->mCloseOnIdle.EnsureFlipped();
8492 return true;
8495 return false;
8498 ConnectionPool::ConnectionRunnable::ConnectionRunnable(
8499 DatabaseInfo& aDatabaseInfo)
8500 : Runnable("dom::indexedDB::ConnectionPool::ConnectionRunnable"),
8501 mDatabaseInfo(aDatabaseInfo),
8502 mOwningEventTarget(GetCurrentSerialEventTarget()) {
8503 AssertIsOnBackgroundThread();
8504 MOZ_ASSERT(aDatabaseInfo.mConnectionPool);
8505 aDatabaseInfo.mConnectionPool->AssertIsOnOwningThread();
8506 MOZ_ASSERT(mOwningEventTarget);
8509 NS_IMETHODIMP
8510 ConnectionPool::IdleConnectionRunnable::Run() {
8511 MOZ_ASSERT(!mDatabaseInfo.mIdle);
8513 const nsCOMPtr<nsIEventTarget> owningThread = std::move(mOwningEventTarget);
8515 if (owningThread) {
8516 mDatabaseInfo.AssertIsOnConnectionThread();
8518 // The connection could be null if EnsureConnection() didn't run or was not
8519 // successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
8520 if (mDatabaseInfo.mConnection) {
8521 mDatabaseInfo.mConnection->DoIdleProcessing(mNeedsCheckpoint);
8524 MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
8525 return NS_OK;
8528 AssertIsOnBackgroundThread();
8530 RefPtr<ConnectionPool> connectionPool = mDatabaseInfo.mConnectionPool;
8531 MOZ_ASSERT(connectionPool);
8533 if (mDatabaseInfo.mClosing || mDatabaseInfo.TotalTransactionCount()) {
8534 MOZ_ASSERT(!connectionPool->mDatabasesPerformingIdleMaintenance.Contains(
8535 &mDatabaseInfo));
8536 } else {
8537 MOZ_ALWAYS_TRUE(
8538 connectionPool->mDatabasesPerformingIdleMaintenance.RemoveElement(
8539 &mDatabaseInfo));
8541 connectionPool->NoteIdleDatabase(mDatabaseInfo);
8544 return NS_OK;
8547 NS_IMETHODIMP
8548 ConnectionPool::CloseConnectionRunnable::Run() {
8549 AUTO_PROFILER_LABEL("ConnectionPool::CloseConnectionRunnable::Run", DOM);
8551 if (mOwningEventTarget) {
8552 MOZ_ASSERT(mDatabaseInfo.mClosing);
8554 const nsCOMPtr<nsIEventTarget> owningThread = std::move(mOwningEventTarget);
8556 // The connection could be null if EnsureConnection() didn't run or was not
8557 // successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
8558 if (mDatabaseInfo.mConnection) {
8559 mDatabaseInfo.AssertIsOnConnectionThread();
8561 mDatabaseInfo.mConnection->Close();
8563 IDB_DEBUG_LOG(("ConnectionPool closed connection 0x%p",
8564 mDatabaseInfo.mConnection.get()));
8566 mDatabaseInfo.mConnection = nullptr;
8568 #ifdef DEBUG
8569 mDatabaseInfo.mDEBUGConnectionThread = nullptr;
8570 #endif
8573 MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
8574 return NS_OK;
8577 RefPtr<ConnectionPool> connectionPool = mDatabaseInfo.mConnectionPool;
8578 MOZ_ASSERT(connectionPool);
8580 connectionPool->NoteClosedDatabase(mDatabaseInfo);
8581 return NS_OK;
8584 ConnectionPool::DatabaseInfo::DatabaseInfo(ConnectionPool* aConnectionPool,
8585 const nsACString& aDatabaseId)
8586 : mConnectionPool(aConnectionPool),
8587 mDatabaseId(aDatabaseId),
8588 mReadTransactionCount(0),
8589 mWriteTransactionCount(0),
8590 mNeedsCheckpoint(false),
8591 mIdle(false),
8592 mClosing(false)
8593 #ifdef DEBUG
8595 mDEBUGConnectionThread(nullptr)
8596 #endif
8598 AssertIsOnBackgroundThread();
8599 MOZ_ASSERT(aConnectionPool);
8600 aConnectionPool->AssertIsOnOwningThread();
8601 MOZ_ASSERT(!aDatabaseId.IsEmpty());
8603 MOZ_COUNT_CTOR(ConnectionPool::DatabaseInfo);
8606 ConnectionPool::DatabaseInfo::~DatabaseInfo() {
8607 AssertIsOnBackgroundThread();
8608 MOZ_ASSERT(!mConnection);
8609 MOZ_ASSERT(mScheduledWriteTransactions.IsEmpty());
8610 MOZ_ASSERT(!mRunningWriteTransaction);
8611 mThreadInfo.AssertEmpty();
8612 MOZ_ASSERT(!TotalTransactionCount());
8614 MOZ_COUNT_DTOR(ConnectionPool::DatabaseInfo);
8617 ConnectionPool::DatabasesCompleteCallback::DatabasesCompleteCallback(
8618 nsTArray<nsCString>&& aDatabaseIds, nsIRunnable* aCallback)
8619 : mDatabaseIds(std::move(aDatabaseIds)), mCallback(aCallback) {
8620 AssertIsOnBackgroundThread();
8621 MOZ_ASSERT(!mDatabaseIds.IsEmpty());
8622 MOZ_ASSERT(aCallback);
8624 MOZ_COUNT_CTOR(ConnectionPool::DatabasesCompleteCallback);
8627 ConnectionPool::DatabasesCompleteCallback::~DatabasesCompleteCallback() {
8628 AssertIsOnBackgroundThread();
8630 MOZ_COUNT_DTOR(ConnectionPool::DatabasesCompleteCallback);
8633 ConnectionPool::FinishCallbackWrapper::FinishCallbackWrapper(
8634 ConnectionPool* aConnectionPool, uint64_t aTransactionId,
8635 FinishCallback* aCallback)
8636 : Runnable("dom::indexedDB::ConnectionPool::FinishCallbackWrapper"),
8637 mConnectionPool(aConnectionPool),
8638 mCallback(aCallback),
8639 mOwningEventTarget(GetCurrentSerialEventTarget()),
8640 mTransactionId(aTransactionId),
8641 mHasRunOnce(false) {
8642 AssertIsOnBackgroundThread();
8643 MOZ_ASSERT(aConnectionPool);
8644 MOZ_ASSERT(aCallback);
8645 MOZ_ASSERT(mOwningEventTarget);
8648 ConnectionPool::FinishCallbackWrapper::~FinishCallbackWrapper() {
8649 MOZ_ASSERT(!mConnectionPool);
8650 MOZ_ASSERT(!mCallback);
8653 nsresult ConnectionPool::FinishCallbackWrapper::Run() {
8654 MOZ_ASSERT(mConnectionPool);
8655 MOZ_ASSERT(mCallback);
8656 MOZ_ASSERT(mOwningEventTarget);
8658 AUTO_PROFILER_LABEL("ConnectionPool::FinishCallbackWrapper::Run", DOM);
8660 if (!mHasRunOnce) {
8661 MOZ_ASSERT(!IsOnBackgroundThread());
8663 mHasRunOnce = true;
8665 Unused << mCallback->Run();
8667 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
8669 return NS_OK;
8672 mConnectionPool->AssertIsOnOwningThread();
8673 MOZ_ASSERT(mHasRunOnce);
8675 RefPtr<ConnectionPool> connectionPool = std::move(mConnectionPool);
8676 RefPtr<FinishCallback> callback = std::move(mCallback);
8678 callback->TransactionFinishedBeforeUnblock();
8680 connectionPool->NoteFinishedTransaction(mTransactionId);
8682 callback->TransactionFinishedAfterUnblock();
8684 return NS_OK;
8687 uint32_t ConnectionPool::ThreadRunnable::sNextSerialNumber = 0;
8689 ConnectionPool::ThreadRunnable::ThreadRunnable()
8690 : Runnable("dom::indexedDB::ConnectionPool::ThreadRunnable"),
8691 mSerialNumber(++sNextSerialNumber) {
8692 AssertIsOnBackgroundThread();
8695 ConnectionPool::ThreadRunnable::~ThreadRunnable() {
8696 MOZ_ASSERT(!mFirstRun);
8697 MOZ_ASSERT(!mContinueRunning);
8700 nsresult ConnectionPool::ThreadRunnable::Run() {
8701 MOZ_ASSERT(!IsOnBackgroundThread());
8702 MOZ_ASSERT(mContinueRunning);
8704 if (!mFirstRun) {
8705 mContinueRunning.Flip();
8706 return NS_OK;
8709 mFirstRun.Flip();
8712 // Scope for the profiler label.
8713 AUTO_PROFILER_LABEL("ConnectionPool::ThreadRunnable::Run", DOM);
8715 DebugOnly<nsIThread*> currentThread = NS_GetCurrentThread();
8716 MOZ_ASSERT(currentThread);
8718 #ifdef DEBUG
8719 if (kDEBUGTransactionThreadPriority !=
8720 nsISupportsPriority::PRIORITY_NORMAL) {
8721 NS_WARNING(
8722 "ConnectionPool thread debugging enabled, priority has been "
8723 "modified!");
8725 nsCOMPtr<nsISupportsPriority> thread = do_QueryInterface(currentThread);
8726 MOZ_ASSERT(thread);
8728 MOZ_ALWAYS_SUCCEEDS(thread->SetPriority(kDEBUGTransactionThreadPriority));
8731 if (kDEBUGTransactionThreadSleepMS) {
8732 NS_WARNING(
8733 "TransactionThreadPool thread debugging enabled, sleeping "
8734 "after every event!");
8736 #endif // DEBUG
8738 DebugOnly<bool> b =
8739 SpinEventLoopUntil("ConnectionPool::ThreadRunnable"_ns, [&]() -> bool {
8740 if (!mContinueRunning) {
8741 return true;
8744 #ifdef DEBUG
8745 if (kDEBUGTransactionThreadSleepMS) {
8746 MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(
8747 kDEBUGTransactionThreadSleepMS)) == PR_SUCCESS);
8749 #endif // DEBUG
8751 return false;
8753 // MSVC can't stringify lambdas, so we have to separate the expression
8754 // generating the value from the assert itself.
8755 #if DEBUG
8756 MOZ_ALWAYS_TRUE(b);
8757 #endif
8760 return NS_OK;
8763 ConnectionPool::ThreadInfo::ThreadInfo() {
8764 AssertIsOnBackgroundThread();
8766 MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
8769 ConnectionPool::ThreadInfo::ThreadInfo(ThreadInfo&& aOther) noexcept
8770 : mThread(std::move(aOther.mThread)),
8771 mRunnable(std::move(aOther.mRunnable)) {
8772 AssertIsOnBackgroundThread();
8773 MOZ_ASSERT(mThread);
8774 MOZ_ASSERT(mRunnable);
8776 MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
8779 ConnectionPool::ThreadInfo::~ThreadInfo() {
8780 AssertIsOnBackgroundThread();
8782 MOZ_COUNT_DTOR(ConnectionPool::ThreadInfo);
8785 ConnectionPool::IdleResource::IdleResource(const TimeStamp& aIdleTime)
8786 : mIdleTime(aIdleTime) {
8787 AssertIsOnBackgroundThread();
8788 MOZ_ASSERT(!aIdleTime.IsNull());
8790 MOZ_COUNT_CTOR(ConnectionPool::IdleResource);
8793 ConnectionPool::IdleResource::~IdleResource() {
8794 AssertIsOnBackgroundThread();
8796 MOZ_COUNT_DTOR(ConnectionPool::IdleResource);
8799 ConnectionPool::IdleDatabaseInfo::IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo)
8800 : IdleResource(
8801 TimeStamp::NowLoRes() +
8802 (aDatabaseInfo.mIdle
8803 ? TimeDuration::FromMilliseconds(kConnectionIdleMaintenanceMS)
8804 : TimeDuration::FromMilliseconds(kConnectionIdleCloseMS))),
8805 mDatabaseInfo(WrapNotNullUnchecked(&aDatabaseInfo)) {
8806 AssertIsOnBackgroundThread();
8808 MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
8811 ConnectionPool::IdleDatabaseInfo::~IdleDatabaseInfo() {
8812 AssertIsOnBackgroundThread();
8814 MOZ_COUNT_DTOR(ConnectionPool::IdleDatabaseInfo);
8817 ConnectionPool::IdleThreadInfo::IdleThreadInfo(ThreadInfo aThreadInfo)
8818 : IdleResource(TimeStamp::NowLoRes() +
8819 TimeDuration::FromMilliseconds(kConnectionThreadIdleMS)),
8820 mThreadInfo(std::move(aThreadInfo)) {
8821 AssertIsOnBackgroundThread();
8822 mThreadInfo.AssertValid();
8824 MOZ_COUNT_CTOR(ConnectionPool::IdleThreadInfo);
8827 ConnectionPool::IdleThreadInfo::~IdleThreadInfo() {
8828 AssertIsOnBackgroundThread();
8830 MOZ_COUNT_DTOR(ConnectionPool::IdleThreadInfo);
8833 ConnectionPool::TransactionInfo::TransactionInfo(
8834 DatabaseInfo& aDatabaseInfo, const nsID& aBackgroundChildLoggingId,
8835 const nsACString& aDatabaseId, uint64_t aTransactionId,
8836 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames,
8837 bool aIsWriteTransaction, TransactionDatabaseOperationBase* aTransactionOp)
8838 : mDatabaseInfo(aDatabaseInfo),
8839 mBackgroundChildLoggingId(aBackgroundChildLoggingId),
8840 mDatabaseId(aDatabaseId),
8841 mTransactionId(aTransactionId),
8842 mLoggingSerialNumber(aLoggingSerialNumber),
8843 mObjectStoreNames(aObjectStoreNames.Clone()),
8844 mIsWriteTransaction(aIsWriteTransaction),
8845 mRunning(false) {
8846 AssertIsOnBackgroundThread();
8847 aDatabaseInfo.mConnectionPool->AssertIsOnOwningThread();
8849 MOZ_COUNT_CTOR(ConnectionPool::TransactionInfo);
8851 if (aTransactionOp) {
8852 mQueuedRunnables.AppendElement(aTransactionOp);
8856 ConnectionPool::TransactionInfo::~TransactionInfo() {
8857 AssertIsOnBackgroundThread();
8858 MOZ_ASSERT(!mBlockedOn.Count());
8859 MOZ_ASSERT(mQueuedRunnables.IsEmpty());
8860 MOZ_ASSERT(!mRunning);
8861 MOZ_ASSERT(mFinished);
8863 MOZ_COUNT_DTOR(ConnectionPool::TransactionInfo);
8866 void ConnectionPool::TransactionInfo::AddBlockingTransaction(
8867 TransactionInfo& aTransactionInfo) {
8868 AssertIsOnBackgroundThread();
8870 // XXX Does it really make sense to have both mBlocking and mBlockingOrdered,
8871 // just to reduce the algorithmic complexity of this Contains check? This was
8872 // mentioned in the context of Bug 1290853, but no real justification was
8873 // given. There was the suggestion of encapsulating this in an
8874 // insertion-ordered hashtable implementation, which seems like a good idea.
8875 // If we had that, this would be the appropriate data structure to use here.
8876 if (mBlocking.EnsureInserted(&aTransactionInfo)) {
8877 mBlockingOrdered.AppendElement(WrapNotNullUnchecked(&aTransactionInfo));
8881 void ConnectionPool::TransactionInfo::RemoveBlockingTransactions() {
8882 AssertIsOnBackgroundThread();
8884 for (const auto blockedInfo : mBlockingOrdered) {
8885 blockedInfo->MaybeUnblock(*this);
8888 mBlocking.Clear();
8889 mBlockingOrdered.Clear();
8892 void ConnectionPool::TransactionInfo::MaybeUnblock(
8893 TransactionInfo& aTransactionInfo) {
8894 AssertIsOnBackgroundThread();
8895 MOZ_ASSERT(mBlockedOn.Contains(&aTransactionInfo));
8897 mBlockedOn.Remove(&aTransactionInfo);
8898 if (mBlockedOn.IsEmpty()) {
8899 ConnectionPool* connectionPool = mDatabaseInfo.mConnectionPool;
8900 MOZ_ASSERT(connectionPool);
8901 connectionPool->AssertIsOnOwningThread();
8903 Unused << connectionPool->ScheduleTransaction(
8904 *this,
8905 /* aFromQueuedTransactions */ false);
8909 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
8910 ConnectionPool::TransactionInfoPair::TransactionInfoPair() {
8911 AssertIsOnBackgroundThread();
8913 MOZ_COUNT_CTOR(ConnectionPool::TransactionInfoPair);
8916 ConnectionPool::TransactionInfoPair::~TransactionInfoPair() {
8917 AssertIsOnBackgroundThread();
8919 MOZ_COUNT_DTOR(ConnectionPool::TransactionInfoPair);
8921 #endif
8923 /*******************************************************************************
8924 * Metadata classes
8925 ******************************************************************************/
8927 bool FullObjectStoreMetadata::HasLiveIndexes() const {
8928 AssertIsOnBackgroundThread();
8930 return std::any_of(mIndexes.Values().cbegin(), mIndexes.Values().cend(),
8931 [](const auto& entry) { return !entry->mDeleted; });
8934 SafeRefPtr<FullDatabaseMetadata> FullDatabaseMetadata::Duplicate() const {
8935 AssertIsOnBackgroundThread();
8937 // FullDatabaseMetadata contains two hash tables of pointers that we need to
8938 // duplicate so we can't just use the copy constructor.
8939 auto newMetadata = MakeSafeRefPtr<FullDatabaseMetadata>(mCommonMetadata);
8941 newMetadata->mDatabaseId = mDatabaseId;
8942 newMetadata->mFilePath = mFilePath;
8943 newMetadata->mNextObjectStoreId = mNextObjectStoreId;
8944 newMetadata->mNextIndexId = mNextIndexId;
8946 for (const auto& objectStoreEntry : mObjectStores) {
8947 const auto& objectStoreValue = objectStoreEntry.GetData();
8949 auto newOSMetadata = MakeSafeRefPtr<FullObjectStoreMetadata>(
8950 objectStoreValue->mCommonMetadata, [&objectStoreValue] {
8951 const auto&& srcLocked = objectStoreValue->mAutoIncrementIds.Lock();
8952 return *srcLocked;
8953 }());
8955 for (const auto& indexEntry : objectStoreValue->mIndexes) {
8956 const auto& value = indexEntry.GetData();
8958 auto newIndexMetadata = MakeSafeRefPtr<FullIndexMetadata>();
8960 newIndexMetadata->mCommonMetadata = value->mCommonMetadata;
8962 if (NS_WARN_IF(!newOSMetadata->mIndexes.InsertOrUpdate(
8963 indexEntry.GetKey(), std::move(newIndexMetadata), fallible))) {
8964 return nullptr;
8968 MOZ_ASSERT(objectStoreValue->mIndexes.Count() ==
8969 newOSMetadata->mIndexes.Count());
8971 if (NS_WARN_IF(!newMetadata->mObjectStores.InsertOrUpdate(
8972 objectStoreEntry.GetKey(), std::move(newOSMetadata), fallible))) {
8973 return nullptr;
8977 MOZ_ASSERT(mObjectStores.Count() == newMetadata->mObjectStores.Count());
8979 return newMetadata;
8982 DatabaseLoggingInfo::~DatabaseLoggingInfo() {
8983 AssertIsOnBackgroundThread();
8985 if (gLoggingInfoHashtable) {
8986 const nsID& backgroundChildLoggingId =
8987 mLoggingInfo.backgroundChildLoggingId();
8989 MOZ_ASSERT(gLoggingInfoHashtable->Get(backgroundChildLoggingId) == this);
8991 gLoggingInfoHashtable->Remove(backgroundChildLoggingId);
8995 /*******************************************************************************
8996 * Factory
8997 ******************************************************************************/
8999 Factory::Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo)
9000 : mLoggingInfo(std::move(aLoggingInfo))
9001 #ifdef DEBUG
9003 mActorDestroyed(false)
9004 #endif
9006 AssertIsOnBackgroundThread();
9007 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
9010 Factory::~Factory() { MOZ_ASSERT(mActorDestroyed); }
9012 // static
9013 SafeRefPtr<Factory> Factory::Create(const LoggingInfo& aLoggingInfo) {
9014 AssertIsOnBackgroundThread();
9015 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
9017 // Balanced in ActoryDestroy().
9018 IncreaseBusyCount();
9020 MOZ_ASSERT(gLoggingInfoHashtable);
9021 RefPtr<DatabaseLoggingInfo> loggingInfo =
9022 gLoggingInfoHashtable->WithEntryHandle(
9023 aLoggingInfo.backgroundChildLoggingId(), [&](auto&& entry) {
9024 if (entry) {
9025 [[maybe_unused]] const auto& loggingInfo = entry.Data();
9026 MOZ_ASSERT(aLoggingInfo.backgroundChildLoggingId() ==
9027 loggingInfo->Id());
9028 #if !FUZZING
9029 NS_WARNING_ASSERTION(
9030 aLoggingInfo.nextTransactionSerialNumber() ==
9031 loggingInfo->mLoggingInfo.nextTransactionSerialNumber(),
9032 "NextTransactionSerialNumber doesn't match!");
9033 NS_WARNING_ASSERTION(
9034 aLoggingInfo.nextVersionChangeTransactionSerialNumber() ==
9035 loggingInfo->mLoggingInfo
9036 .nextVersionChangeTransactionSerialNumber(),
9037 "NextVersionChangeTransactionSerialNumber doesn't match!");
9038 NS_WARNING_ASSERTION(
9039 aLoggingInfo.nextRequestSerialNumber() ==
9040 loggingInfo->mLoggingInfo.nextRequestSerialNumber(),
9041 "NextRequestSerialNumber doesn't match!");
9042 #endif // !FUZZING
9043 } else {
9044 entry.Insert(new DatabaseLoggingInfo(aLoggingInfo));
9047 return do_AddRef(entry.Data());
9050 return MakeSafeRefPtr<Factory>(std::move(loggingInfo));
9053 void Factory::ActorDestroy(ActorDestroyReason aWhy) {
9054 AssertIsOnBackgroundThread();
9055 MOZ_ASSERT(!mActorDestroyed);
9057 #ifdef DEBUG
9058 mActorDestroyed = true;
9059 #endif
9061 // Match the IncreaseBusyCount in Create().
9062 DecreaseBusyCount();
9065 mozilla::ipc::IPCResult Factory::RecvDeleteMe() {
9066 AssertIsOnBackgroundThread();
9067 MOZ_ASSERT(!mActorDestroyed);
9069 QM_WARNONLY_TRY(OkIf(PBackgroundIDBFactoryParent::Send__delete__(this)));
9071 return IPC_OK();
9074 PBackgroundIDBFactoryRequestParent*
9075 Factory::AllocPBackgroundIDBFactoryRequestParent(
9076 const FactoryRequestParams& aParams) {
9077 AssertIsOnBackgroundThread();
9078 MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
9080 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
9081 return nullptr;
9084 const CommonFactoryRequestParams* commonParams;
9086 switch (aParams.type()) {
9087 case FactoryRequestParams::TOpenDatabaseRequestParams: {
9088 const OpenDatabaseRequestParams& params =
9089 aParams.get_OpenDatabaseRequestParams();
9090 commonParams = &params.commonParams();
9091 break;
9094 case FactoryRequestParams::TDeleteDatabaseRequestParams: {
9095 const DeleteDatabaseRequestParams& params =
9096 aParams.get_DeleteDatabaseRequestParams();
9097 commonParams = &params.commonParams();
9098 break;
9101 default:
9102 MOZ_CRASH("Should never get here!");
9105 MOZ_ASSERT(commonParams);
9107 const DatabaseMetadata& metadata = commonParams->metadata();
9108 if (NS_AUUF_OR_WARN_IF(!IsValidPersistenceType(metadata.persistenceType()))) {
9109 return nullptr;
9112 const PrincipalInfo& principalInfo = commonParams->principalInfo();
9113 if (NS_AUUF_OR_WARN_IF(principalInfo.type() ==
9114 PrincipalInfo::TNullPrincipalInfo)) {
9115 return nullptr;
9118 if (NS_AUUF_OR_WARN_IF(
9119 principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
9120 metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) {
9121 return nullptr;
9124 if (NS_AUUF_OR_WARN_IF(!QuotaManager::IsPrincipalInfoValid(principalInfo))) {
9125 return nullptr;
9128 RefPtr<ThreadsafeContentParentHandle> contentHandle =
9129 BackgroundParent::GetContentParentHandle(Manager());
9131 auto actor = [&]() -> RefPtr<FactoryOp> {
9132 if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) {
9133 return MakeRefPtr<OpenDatabaseOp>(
9134 SafeRefPtrFromThis(), std::move(contentHandle), *commonParams);
9135 } else {
9136 return MakeRefPtr<DeleteDatabaseOp>(
9137 SafeRefPtrFromThis(), std::move(contentHandle), *commonParams);
9139 }();
9141 gFactoryOps->AppendElement(actor);
9143 // Balanced in CleanupMetadata() which is/must always called by SendResults().
9144 IncreaseBusyCount();
9146 // Transfer ownership to IPDL.
9147 return actor.forget().take();
9150 mozilla::ipc::IPCResult Factory::RecvPBackgroundIDBFactoryRequestConstructor(
9151 PBackgroundIDBFactoryRequestParent* aActor,
9152 const FactoryRequestParams& aParams) {
9153 AssertIsOnBackgroundThread();
9154 MOZ_ASSERT(aActor);
9155 MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
9156 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
9158 auto* op = static_cast<FactoryOp*>(aActor);
9160 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(op));
9161 return IPC_OK();
9164 bool Factory::DeallocPBackgroundIDBFactoryRequestParent(
9165 PBackgroundIDBFactoryRequestParent* aActor) {
9166 AssertIsOnBackgroundThread();
9167 MOZ_ASSERT(aActor);
9169 // Transfer ownership back from IPDL.
9170 RefPtr<FactoryOp> op = dont_AddRef(static_cast<FactoryOp*>(aActor));
9171 return true;
9174 PBackgroundIDBDatabaseParent* Factory::AllocPBackgroundIDBDatabaseParent(
9175 const DatabaseSpec& aSpec, PBackgroundIDBFactoryRequestParent* aRequest) {
9176 MOZ_CRASH(
9177 "PBackgroundIDBDatabaseParent actors should be constructed "
9178 "manually!");
9181 bool Factory::DeallocPBackgroundIDBDatabaseParent(
9182 PBackgroundIDBDatabaseParent* aActor) {
9183 AssertIsOnBackgroundThread();
9184 MOZ_ASSERT(aActor);
9186 RefPtr<Database> database = dont_AddRef(static_cast<Database*>(aActor));
9187 return true;
9190 /*******************************************************************************
9191 * WaitForTransactionsHelper
9192 ******************************************************************************/
9194 void WaitForTransactionsHelper::WaitForTransactions() {
9195 MOZ_ASSERT(mState == State::Initial);
9197 Unused << this->Run();
9200 void WaitForTransactionsHelper::MaybeWaitForTransactions() {
9201 AssertIsOnBackgroundThread();
9202 MOZ_ASSERT(mState == State::Initial);
9204 RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
9205 if (connectionPool) {
9206 mState = State::WaitingForTransactions;
9208 connectionPool->WaitForDatabasesToComplete(nsTArray<nsCString>{mDatabaseId},
9209 this);
9210 return;
9213 CallCallback();
9216 void WaitForTransactionsHelper::CallCallback() {
9217 AssertIsOnBackgroundThread();
9218 MOZ_ASSERT(mState == State::Initial ||
9219 mState == State::WaitingForTransactions);
9221 const nsCOMPtr<nsIRunnable> callback = std::move(mCallback);
9223 callback->Run();
9225 mState = State::Complete;
9228 NS_IMETHODIMP
9229 WaitForTransactionsHelper::Run() {
9230 MOZ_ASSERT(mState != State::Complete);
9231 MOZ_ASSERT(mCallback);
9233 switch (mState) {
9234 case State::Initial:
9235 MaybeWaitForTransactions();
9236 break;
9238 case State::WaitingForTransactions:
9239 CallCallback();
9240 break;
9242 default:
9243 MOZ_CRASH("Should never get here!");
9246 return NS_OK;
9249 /*******************************************************************************
9250 * Database
9251 ******************************************************************************/
9253 Database::Database(SafeRefPtr<Factory> aFactory,
9254 const PrincipalInfo& aPrincipalInfo,
9255 const Maybe<ContentParentId>& aOptionalContentParentId,
9256 const quota::OriginMetadata& aOriginMetadata,
9257 uint32_t aTelemetryId,
9258 SafeRefPtr<FullDatabaseMetadata> aMetadata,
9259 SafeRefPtr<DatabaseFileManager> aFileManager,
9260 RefPtr<DirectoryLock> aDirectoryLock,
9261 bool aChromeWriteAccessAllowed, bool aInPrivateBrowsing,
9262 const Maybe<const CipherKey>& aMaybeKey)
9263 : mFactory(std::move(aFactory)),
9264 mMetadata(std::move(aMetadata)),
9265 mFileManager(std::move(aFileManager)),
9266 mDirectoryLock(std::move(aDirectoryLock)),
9267 mPrincipalInfo(aPrincipalInfo),
9268 mOptionalContentParentId(aOptionalContentParentId),
9269 mOriginMetadata(aOriginMetadata),
9270 mId(mMetadata->mDatabaseId),
9271 mFilePath(mMetadata->mFilePath),
9272 mKey(aMaybeKey),
9273 mTelemetryId(aTelemetryId),
9274 mPersistenceType(mMetadata->mCommonMetadata.persistenceType()),
9275 mChromeWriteAccessAllowed(aChromeWriteAccessAllowed),
9276 mInPrivateBrowsing(aInPrivateBrowsing),
9277 mBackgroundThread(GetCurrentSerialEventTarget())
9278 #ifdef DEBUG
9280 mAllBlobsUnmapped(false)
9281 #endif
9283 AssertIsOnBackgroundThread();
9284 MOZ_ASSERT(mFactory);
9285 MOZ_ASSERT(mMetadata);
9286 MOZ_ASSERT(mFileManager);
9287 MOZ_ASSERT_IF(aChromeWriteAccessAllowed,
9288 aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo);
9290 MOZ_ASSERT(mDirectoryLock);
9291 MOZ_ASSERT(mDirectoryLock->Id() >= 0);
9292 mDirectoryLockId = mDirectoryLock->Id();
9295 template <typename T>
9296 bool Database::InvalidateAll(const nsTBaseHashSet<nsPtrHashKey<T>>& aTable) {
9297 AssertIsOnBackgroundThread();
9299 const uint32_t count = aTable.Count();
9300 if (!count) {
9301 return true;
9304 // XXX Does this really need to be fallible?
9305 QM_TRY_INSPECT(const auto& elementsToInvalidate,
9306 TransformIntoNewArray(
9307 aTable, [](const auto& entry) { return entry; }, fallible),
9308 false);
9310 IDB_REPORT_INTERNAL_ERR();
9312 for (const auto& elementToInvalidate : elementsToInvalidate) {
9313 MOZ_ASSERT(elementToInvalidate);
9315 elementToInvalidate->Invalidate();
9318 return true;
9321 void Database::Invalidate() {
9322 AssertIsOnBackgroundThread();
9324 if (mInvalidated) {
9325 return;
9328 mInvalidated.Flip();
9330 if (mActorWasAlive && !mActorDestroyed) {
9331 Unused << SendInvalidate();
9334 QM_WARNONLY_TRY(OkIf(InvalidateAll(mTransactions)));
9336 MOZ_ALWAYS_TRUE(CloseInternal());
9339 nsresult Database::EnsureConnection() {
9340 MOZ_ASSERT(!NS_IsMainThread());
9341 MOZ_ASSERT(!IsOnBackgroundThread());
9343 AUTO_PROFILER_LABEL("Database::EnsureConnection", DOM);
9345 if (!mConnection || !mConnection->HasStorageConnection()) {
9346 QM_TRY_UNWRAP(mConnection, gConnectionPool->GetOrCreateConnection(*this));
9349 AssertIsOnConnectionThread();
9351 return NS_OK;
9354 bool Database::RegisterTransaction(TransactionBase& aTransaction) {
9355 AssertIsOnBackgroundThread();
9356 MOZ_ASSERT(!mTransactions.Contains(&aTransaction));
9357 MOZ_ASSERT(mDirectoryLock);
9358 MOZ_ASSERT(!mInvalidated);
9359 MOZ_ASSERT(!mClosed);
9361 if (NS_WARN_IF(!mTransactions.Insert(&aTransaction, fallible))) {
9362 return false;
9365 return true;
9368 void Database::UnregisterTransaction(TransactionBase& aTransaction) {
9369 AssertIsOnBackgroundThread();
9370 MOZ_ASSERT(mTransactions.Contains(&aTransaction));
9372 mTransactions.Remove(&aTransaction);
9374 MaybeCloseConnection();
9377 void Database::SetActorAlive() {
9378 AssertIsOnBackgroundThread();
9379 MOZ_ASSERT(!mActorDestroyed);
9381 mActorWasAlive.Flip();
9383 // This reference will be absorbed by IPDL and released when the actor is
9384 // destroyed.
9385 AddRef();
9388 void Database::MapBlob(const IPCBlob& aIPCBlob,
9389 SafeRefPtr<DatabaseFileInfo> aFileInfo) {
9390 AssertIsOnBackgroundThread();
9392 const RemoteLazyStream& stream = aIPCBlob.inputStream();
9393 MOZ_ASSERT(stream.type() == RemoteLazyStream::TRemoteLazyInputStream);
9395 nsID id{};
9396 MOZ_ALWAYS_SUCCEEDS(
9397 stream.get_RemoteLazyInputStream()->GetInternalStreamID(id));
9399 MOZ_ASSERT(!mMappedBlobs.Contains(id));
9400 mMappedBlobs.InsertOrUpdate(id, std::move(aFileInfo));
9402 RefPtr<UnmapBlobCallback> callback =
9403 new UnmapBlobCallback(SafeRefPtrFromThis());
9405 auto storage = RemoteLazyInputStreamStorage::Get();
9406 MOZ_ASSERT(storage.isOk());
9407 storage.inspect()->StoreCallback(id, callback);
9410 void Database::Stringify(nsACString& aResult) const {
9411 AssertIsOnBackgroundThread();
9413 constexpr auto kQuotaGenericDelimiterString = "|"_ns;
9415 aResult.Append(
9416 "DirectoryLock:"_ns + IntToCString(!!mDirectoryLock) +
9417 kQuotaGenericDelimiterString +
9419 "Transactions:"_ns + IntToCString(mTransactions.Count()) +
9420 kQuotaGenericDelimiterString +
9422 "OtherProcessActor:"_ns +
9423 IntToCString(
9424 BackgroundParent::IsOtherProcessActor(GetBackgroundParent())) +
9425 kQuotaGenericDelimiterString +
9427 "Origin:"_ns + AnonymizedOriginString(mOriginMetadata.mOrigin) +
9428 kQuotaGenericDelimiterString +
9430 "PersistenceType:"_ns + PersistenceTypeToString(mPersistenceType) +
9431 kQuotaGenericDelimiterString +
9433 "Closed:"_ns + IntToCString(static_cast<bool>(mClosed)) +
9434 kQuotaGenericDelimiterString +
9436 "Invalidated:"_ns + IntToCString(static_cast<bool>(mInvalidated)) +
9437 kQuotaGenericDelimiterString +
9439 "ActorWasAlive:"_ns + IntToCString(static_cast<bool>(mActorWasAlive)) +
9440 kQuotaGenericDelimiterString +
9442 "ActorDestroyed:"_ns + IntToCString(static_cast<bool>(mActorDestroyed)));
9445 SafeRefPtr<DatabaseFileInfo> Database::GetBlob(const IPCBlob& aIPCBlob) {
9446 AssertIsOnBackgroundThread();
9448 RefPtr<RemoteLazyInputStream> lazyStream;
9449 switch (aIPCBlob.inputStream().type()) {
9450 case RemoteLazyStream::TIPCStream: {
9451 const InputStreamParams& inputStreamParams =
9452 aIPCBlob.inputStream().get_IPCStream().stream();
9453 if (inputStreamParams.type() !=
9454 InputStreamParams::TRemoteLazyInputStreamParams) {
9455 return nullptr;
9457 lazyStream = inputStreamParams.get_RemoteLazyInputStreamParams().stream();
9458 break;
9460 case RemoteLazyStream::TRemoteLazyInputStream:
9461 lazyStream = aIPCBlob.inputStream().get_RemoteLazyInputStream();
9462 break;
9463 default:
9464 MOZ_ASSERT_UNREACHABLE("Unknown RemoteLazyStream type");
9465 return nullptr;
9468 if (!lazyStream) {
9469 MOZ_ASSERT_UNREACHABLE("Unexpected null stream");
9470 return nullptr;
9473 nsID id{};
9474 nsresult rv = lazyStream->GetInternalStreamID(id);
9475 if (NS_FAILED(rv)) {
9476 MOZ_ASSERT_UNREACHABLE(
9477 "Received RemoteLazyInputStream doesn't have an actor connection");
9478 return nullptr;
9481 const auto fileInfo = mMappedBlobs.Lookup(id);
9482 return fileInfo ? fileInfo->clonePtr() : nullptr;
9485 void Database::UnmapBlob(const nsID& aID) {
9486 AssertIsOnBackgroundThread();
9488 MOZ_ASSERT_IF(!mAllBlobsUnmapped, mMappedBlobs.Contains(aID));
9489 mMappedBlobs.Remove(aID);
9492 void Database::UnmapAllBlobs() {
9493 AssertIsOnBackgroundThread();
9495 #ifdef DEBUG
9496 mAllBlobsUnmapped = true;
9497 #endif
9499 mMappedBlobs.Clear();
9502 bool Database::CloseInternal() {
9503 AssertIsOnBackgroundThread();
9505 if (mClosed) {
9506 if (NS_WARN_IF(!IsInvalidated())) {
9507 // Signal misbehaving child for sending the close message twice.
9508 return false;
9511 // Ignore harmless race when we just invalidated the database.
9512 return true;
9515 mClosed.Flip();
9517 if (gConnectionPool) {
9518 gConnectionPool->CloseDatabaseWhenIdle(Id());
9521 DatabaseActorInfo* info;
9522 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
9524 MOZ_ASSERT(info->mLiveDatabases.Contains(this));
9526 if (info->mWaitingFactoryOp) {
9527 info->mWaitingFactoryOp->NoteDatabaseClosed(this);
9530 MaybeCloseConnection();
9532 return true;
9535 void Database::MaybeCloseConnection() {
9536 AssertIsOnBackgroundThread();
9538 if (!mTransactions.Count() && IsClosed() && mDirectoryLock) {
9539 nsCOMPtr<nsIRunnable> callback =
9540 NewRunnableMethod("dom::indexedDB::Database::ConnectionClosedCallback",
9541 this, &Database::ConnectionClosedCallback);
9543 RefPtr<WaitForTransactionsHelper> helper =
9544 new WaitForTransactionsHelper(Id(), callback);
9545 helper->WaitForTransactions();
9549 void Database::ConnectionClosedCallback() {
9550 AssertIsOnBackgroundThread();
9551 MOZ_ASSERT(mClosed);
9552 MOZ_ASSERT(!mTransactions.Count());
9554 mDirectoryLock = nullptr;
9556 CleanupMetadata();
9558 UnmapAllBlobs();
9560 if (IsInvalidated() && IsActorAlive()) {
9561 // Step 3 and 4 of "5.2 Closing a Database":
9562 // 1. Wait for all transactions to complete.
9563 // 2. Fire a close event if forced flag is set, i.e., IsInvalidated() in our
9564 // implementation.
9565 Unused << SendCloseAfterInvalidationComplete();
9569 void Database::CleanupMetadata() {
9570 AssertIsOnBackgroundThread();
9572 DatabaseActorInfo* info;
9573 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
9574 MOZ_ALWAYS_TRUE(info->mLiveDatabases.RemoveElement(this));
9576 QuotaManager::MaybeRecordQuotaClientShutdownStep(
9577 quota::Client::IDB, "Live database entry removed"_ns);
9579 if (info->mLiveDatabases.IsEmpty()) {
9580 MOZ_ASSERT(!info->mWaitingFactoryOp ||
9581 !info->mWaitingFactoryOp->HasBlockedDatabases());
9582 gLiveDatabaseHashtable->Remove(Id());
9584 QuotaManager::MaybeRecordQuotaClientShutdownStep(
9585 quota::Client::IDB, "gLiveDatabaseHashtable entry removed"_ns);
9588 // Match the IncreaseBusyCount in OpenDatabaseOp::EnsureDatabaseActor().
9589 DecreaseBusyCount();
9592 void Database::ActorDestroy(ActorDestroyReason aWhy) {
9593 AssertIsOnBackgroundThread();
9595 mActorDestroyed.Flip();
9597 if (!IsInvalidated()) {
9598 Invalidate();
9602 PBackgroundIDBDatabaseFileParent*
9603 Database::AllocPBackgroundIDBDatabaseFileParent(const IPCBlob& aIPCBlob) {
9604 AssertIsOnBackgroundThread();
9606 SafeRefPtr<DatabaseFileInfo> fileInfo = GetBlob(aIPCBlob);
9607 RefPtr<DatabaseFile> actor;
9609 if (fileInfo) {
9610 actor = new DatabaseFile(std::move(fileInfo));
9611 } else {
9612 // This is a blob we haven't seen before.
9613 fileInfo = mFileManager->CreateFileInfo();
9614 if (NS_WARN_IF(!fileInfo)) {
9615 return nullptr;
9618 actor = new DatabaseFile(IPCBlobUtils::Deserialize(aIPCBlob),
9619 std::move(fileInfo));
9622 MOZ_ASSERT(actor);
9624 return actor.forget().take();
9627 bool Database::DeallocPBackgroundIDBDatabaseFileParent(
9628 PBackgroundIDBDatabaseFileParent* aActor) {
9629 AssertIsOnBackgroundThread();
9630 MOZ_ASSERT(aActor);
9632 RefPtr<DatabaseFile> actor = dont_AddRef(static_cast<DatabaseFile*>(aActor));
9633 return true;
9636 already_AddRefed<PBackgroundIDBTransactionParent>
9637 Database::AllocPBackgroundIDBTransactionParent(
9638 const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode) {
9639 AssertIsOnBackgroundThread();
9641 // Once a database is closed it must not try to open new transactions.
9642 if (NS_WARN_IF(mClosed)) {
9643 MOZ_ASSERT_UNLESS_FUZZING(mInvalidated);
9644 return nullptr;
9647 if (NS_AUUF_OR_WARN_IF(aObjectStoreNames.IsEmpty())) {
9648 return nullptr;
9651 if (NS_AUUF_OR_WARN_IF(aMode != IDBTransaction::Mode::ReadOnly &&
9652 aMode != IDBTransaction::Mode::ReadWrite &&
9653 aMode != IDBTransaction::Mode::ReadWriteFlush &&
9654 aMode != IDBTransaction::Mode::Cleanup)) {
9655 return nullptr;
9658 // If this is a readwrite transaction to a chrome database make sure the child
9659 // has write access.
9660 // XXX: Maybe add NS_AUUF_OR_WARN_IF also here, see bug 1758875
9661 if (NS_WARN_IF((aMode == IDBTransaction::Mode::ReadWrite ||
9662 aMode == IDBTransaction::Mode::ReadWriteFlush ||
9663 aMode == IDBTransaction::Mode::Cleanup) &&
9664 mPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
9665 !mChromeWriteAccessAllowed)) {
9666 return nullptr;
9669 const ObjectStoreTable& objectStores = mMetadata->mObjectStores;
9670 const uint32_t nameCount = aObjectStoreNames.Length();
9672 if (NS_AUUF_OR_WARN_IF(nameCount > objectStores.Count())) {
9673 return nullptr;
9676 QM_TRY_UNWRAP(
9677 auto objectStoreMetadatas,
9678 TransformIntoNewArrayAbortOnErr(
9679 aObjectStoreNames,
9680 [lastName = Maybe<const nsString&>{},
9681 &objectStores](const nsString& name) mutable
9682 -> mozilla::Result<SafeRefPtr<FullObjectStoreMetadata>, nsresult> {
9683 if (lastName) {
9684 // Make sure that this name is sorted properly and not a
9685 // duplicate.
9686 if (NS_AUUF_OR_WARN_IF(name <= lastName.ref())) {
9687 return Err(NS_ERROR_FAILURE);
9690 lastName = SomeRef(name);
9692 const auto foundIt =
9693 std::find_if(objectStores.cbegin(), objectStores.cend(),
9694 [&name](const auto& entry) {
9695 const auto& value = entry.GetData();
9696 MOZ_ASSERT(entry.GetKey());
9697 return name == value->mCommonMetadata.name() &&
9698 !value->mDeleted;
9700 if (foundIt == objectStores.cend()) {
9701 MOZ_ASSERT_UNLESS_FUZZING(false, "ObjectStore not found.");
9702 return Err(NS_ERROR_FAILURE);
9705 return foundIt->GetData().clonePtr();
9707 fallible),
9708 nullptr);
9710 return MakeSafeRefPtr<NormalTransaction>(SafeRefPtrFromThis(), aMode,
9711 std::move(objectStoreMetadatas))
9712 .forget();
9715 mozilla::ipc::IPCResult Database::RecvPBackgroundIDBTransactionConstructor(
9716 PBackgroundIDBTransactionParent* aActor,
9717 nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode) {
9718 AssertIsOnBackgroundThread();
9719 MOZ_ASSERT(aActor);
9720 MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
9721 MOZ_ASSERT(aMode == IDBTransaction::Mode::ReadOnly ||
9722 aMode == IDBTransaction::Mode::ReadWrite ||
9723 aMode == IDBTransaction::Mode::ReadWriteFlush ||
9724 aMode == IDBTransaction::Mode::Cleanup);
9725 MOZ_ASSERT(!mClosed);
9727 if (IsInvalidated()) {
9728 // This is an expected race. We don't want the child to die here, just don't
9729 // actually do any work.
9730 return IPC_OK();
9733 if (!gConnectionPool) {
9734 gConnectionPool = new ConnectionPool();
9737 auto* transaction = static_cast<NormalTransaction*>(aActor);
9739 RefPtr<StartTransactionOp> startOp = new StartTransactionOp(
9740 SafeRefPtr{transaction, AcquireStrongRefFromRawPtr{}});
9742 uint64_t transactionId = startOp->StartOnConnectionPool(
9743 GetLoggingInfo()->Id(), mMetadata->mDatabaseId,
9744 transaction->LoggingSerialNumber(), aObjectStoreNames,
9745 aMode != IDBTransaction::Mode::ReadOnly);
9747 transaction->Init(transactionId);
9749 if (NS_WARN_IF(!RegisterTransaction(*transaction))) {
9750 IDB_REPORT_INTERNAL_ERR();
9751 transaction->Abort(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, /* aForce */ false);
9752 return IPC_OK();
9755 return IPC_OK();
9758 mozilla::ipc::IPCResult Database::RecvDeleteMe() {
9759 AssertIsOnBackgroundThread();
9760 MOZ_ASSERT(!mActorDestroyed);
9762 QM_WARNONLY_TRY(OkIf(PBackgroundIDBDatabaseParent::Send__delete__(this)));
9764 return IPC_OK();
9767 mozilla::ipc::IPCResult Database::RecvBlocked() {
9768 AssertIsOnBackgroundThread();
9770 if (NS_WARN_IF(mClosed)) {
9771 return IPC_FAIL(this, "Database already closed!");
9774 DatabaseActorInfo* info;
9775 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
9776 MOZ_ASSERT(info->mLiveDatabases.Contains(this));
9778 if (NS_WARN_IF(!info->mWaitingFactoryOp)) {
9779 return IPC_FAIL(this, "Database info has no mWaitingFactoryOp!");
9782 info->mWaitingFactoryOp->NoteDatabaseBlocked(this);
9784 return IPC_OK();
9787 mozilla::ipc::IPCResult Database::RecvClose() {
9788 AssertIsOnBackgroundThread();
9790 if (NS_WARN_IF(!CloseInternal())) {
9791 return IPC_FAIL(this, "CloseInternal failed!");
9794 return IPC_OK();
9797 void Database::StartTransactionOp::RunOnConnectionThread() {
9798 MOZ_ASSERT(!IsOnBackgroundThread());
9799 MOZ_ASSERT(!HasFailed());
9801 IDB_LOG_MARK_PARENT_TRANSACTION("Beginning database work", "DB Start",
9802 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
9803 mTransactionLoggingSerialNumber);
9805 TransactionDatabaseOperationBase::RunOnConnectionThread();
9808 nsresult Database::StartTransactionOp::DoDatabaseWork(
9809 DatabaseConnection* aConnection) {
9810 MOZ_ASSERT(aConnection);
9811 aConnection->AssertIsOnConnectionThread();
9813 Transaction().SetActiveOnConnectionThread();
9815 if (Transaction().GetMode() == IDBTransaction::Mode::Cleanup) {
9816 DebugOnly<nsresult> rv = aConnection->DisableQuotaChecks();
9817 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
9818 "DisableQuotaChecks failed, trying to continue "
9819 "cleanup transaction with quota checks enabled");
9822 if (Transaction().GetMode() != IDBTransaction::Mode::ReadOnly) {
9823 QM_TRY(MOZ_TO_RESULT(aConnection->BeginWriteTransaction()));
9826 return NS_OK;
9829 nsresult Database::StartTransactionOp::SendSuccessResult() {
9830 // We don't need to do anything here.
9831 return NS_OK;
9834 bool Database::StartTransactionOp::SendFailureResult(
9835 nsresult /* aResultCode */) {
9836 IDB_REPORT_INTERNAL_ERR();
9838 // Abort the transaction.
9839 return false;
9842 void Database::StartTransactionOp::Cleanup() {
9843 #ifdef DEBUG
9844 // StartTransactionOp is not a normal database operation that is tied to an
9845 // actor. Do this to make our assertions happy.
9846 NoteActorDestroyed();
9847 #endif
9849 TransactionDatabaseOperationBase::Cleanup();
9852 /*******************************************************************************
9853 * TransactionBase
9854 ******************************************************************************/
9856 TransactionBase::TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode)
9857 : mDatabase(std::move(aDatabase)),
9858 mDatabaseId(mDatabase->Id()),
9859 mLoggingSerialNumber(
9860 mDatabase->GetLoggingInfo()->NextTransactionSN(aMode)),
9861 mActiveRequestCount(0),
9862 mInvalidatedOnAnyThread(false),
9863 mMode(aMode),
9864 mResultCode(NS_OK) {
9865 AssertIsOnBackgroundThread();
9866 MOZ_ASSERT(mDatabase);
9867 MOZ_ASSERT(mLoggingSerialNumber);
9870 TransactionBase::~TransactionBase() {
9871 MOZ_ASSERT(!mActiveRequestCount);
9872 MOZ_ASSERT(mActorDestroyed);
9873 MOZ_ASSERT_IF(mInitialized, mCommittedOrAborted);
9876 void TransactionBase::Abort(nsresult aResultCode, bool aForce) {
9877 AssertIsOnBackgroundThread();
9878 MOZ_ASSERT(NS_FAILED(aResultCode));
9880 if (NS_SUCCEEDED(mResultCode)) {
9881 mResultCode = aResultCode;
9884 if (aForce) {
9885 mForceAborted.EnsureFlipped();
9888 MaybeCommitOrAbort();
9891 mozilla::ipc::IPCResult TransactionBase::RecvCommit(
9892 IProtocol* aActor, const Maybe<int64_t> aLastRequest) {
9893 AssertIsOnBackgroundThread();
9895 if (NS_WARN_IF(mCommitOrAbortReceived)) {
9896 return IPC_FAIL(
9897 aActor, "Attempt to commit an already comitted/aborted transaction!");
9900 mCommitOrAbortReceived.Flip();
9901 mLastRequestBeforeCommit.init(aLastRequest);
9902 MaybeCommitOrAbort();
9904 return IPC_OK();
9907 mozilla::ipc::IPCResult TransactionBase::RecvAbort(IProtocol* aActor,
9908 nsresult aResultCode) {
9909 AssertIsOnBackgroundThread();
9911 if (NS_WARN_IF(NS_SUCCEEDED(aResultCode))) {
9912 return IPC_FAIL(aActor, "aResultCode must not be a success code!");
9915 if (NS_WARN_IF(NS_ERROR_GET_MODULE(aResultCode) !=
9916 NS_ERROR_MODULE_DOM_INDEXEDDB)) {
9917 return IPC_FAIL(aActor, "aResultCode does not refer to IndexedDB!");
9920 if (NS_WARN_IF(mCommitOrAbortReceived)) {
9921 return IPC_FAIL(
9922 aActor, "Attempt to abort an already comitted/aborted transaction!");
9925 mCommitOrAbortReceived.Flip();
9926 Abort(aResultCode, /* aForce */ false);
9928 return IPC_OK();
9931 void TransactionBase::CommitOrAbort() {
9932 AssertIsOnBackgroundThread();
9934 mCommittedOrAborted.Flip();
9936 if (!mInitialized) {
9937 return;
9940 // In case of a failed request that was started after committing was
9941 // initiated, abort (cf.
9942 // https://w3c.github.io/IndexedDB/#async-execute-request step 5.3 vs. 5.4).
9943 // Note this can only happen here when we are committing explicitly, otherwise
9944 // the decision is made by the child.
9945 if (NS_SUCCEEDED(mResultCode) && mLastFailedRequest &&
9946 *mLastRequestBeforeCommit &&
9947 *mLastFailedRequest >= **mLastRequestBeforeCommit) {
9948 mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
9951 RefPtr<CommitOp> commitOp =
9952 new CommitOp(SafeRefPtrFromThis(), ClampResultCode(mResultCode));
9954 gConnectionPool->Finish(TransactionId(), commitOp);
9957 SafeRefPtr<FullObjectStoreMetadata>
9958 TransactionBase::GetMetadataForObjectStoreId(
9959 IndexOrObjectStoreId aObjectStoreId) const {
9960 AssertIsOnBackgroundThread();
9961 MOZ_ASSERT(aObjectStoreId);
9963 if (!aObjectStoreId) {
9964 return nullptr;
9967 auto metadata = mDatabase->Metadata().mObjectStores.Lookup(aObjectStoreId);
9968 if (!metadata || (*metadata)->mDeleted) {
9969 return nullptr;
9972 MOZ_ASSERT((*metadata)->mCommonMetadata.id() == aObjectStoreId);
9974 return metadata->clonePtr();
9977 SafeRefPtr<FullIndexMetadata> TransactionBase::GetMetadataForIndexId(
9978 FullObjectStoreMetadata& aObjectStoreMetadata,
9979 IndexOrObjectStoreId aIndexId) const {
9980 AssertIsOnBackgroundThread();
9981 MOZ_ASSERT(aIndexId);
9983 if (!aIndexId) {
9984 return nullptr;
9987 auto metadata = aObjectStoreMetadata.mIndexes.Lookup(aIndexId);
9988 if (!metadata || (*metadata)->mDeleted) {
9989 return nullptr;
9992 MOZ_ASSERT((*metadata)->mCommonMetadata.id() == aIndexId);
9994 return metadata->clonePtr();
9997 void TransactionBase::NoteModifiedAutoIncrementObjectStore(
9998 const SafeRefPtr<FullObjectStoreMetadata>& aMetadata) {
9999 AssertIsOnConnectionThread();
10001 if (!mModifiedAutoIncrementObjectStoreMetadataArray.Contains(aMetadata)) {
10002 mModifiedAutoIncrementObjectStoreMetadataArray.AppendElement(
10003 aMetadata.clonePtr());
10007 void TransactionBase::ForgetModifiedAutoIncrementObjectStore(
10008 FullObjectStoreMetadata& aMetadata) {
10009 AssertIsOnConnectionThread();
10011 mModifiedAutoIncrementObjectStoreMetadataArray.RemoveElement(&aMetadata);
10014 bool TransactionBase::VerifyRequestParams(const RequestParams& aParams) const {
10015 AssertIsOnBackgroundThread();
10016 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
10018 switch (aParams.type()) {
10019 case RequestParams::TObjectStoreAddParams: {
10020 const ObjectStoreAddPutParams& params =
10021 aParams.get_ObjectStoreAddParams().commonParams();
10022 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params))) {
10023 return false;
10025 break;
10028 case RequestParams::TObjectStorePutParams: {
10029 const ObjectStoreAddPutParams& params =
10030 aParams.get_ObjectStorePutParams().commonParams();
10031 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params))) {
10032 return false;
10034 break;
10037 case RequestParams::TObjectStoreGetParams: {
10038 const ObjectStoreGetParams& params = aParams.get_ObjectStoreGetParams();
10039 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10040 GetMetadataForObjectStoreId(params.objectStoreId());
10041 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10042 return false;
10044 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
10045 return false;
10047 break;
10050 case RequestParams::TObjectStoreGetKeyParams: {
10051 const ObjectStoreGetKeyParams& params =
10052 aParams.get_ObjectStoreGetKeyParams();
10053 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10054 GetMetadataForObjectStoreId(params.objectStoreId());
10055 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10056 return false;
10058 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
10059 return false;
10061 break;
10064 case RequestParams::TObjectStoreGetAllParams: {
10065 const ObjectStoreGetAllParams& params =
10066 aParams.get_ObjectStoreGetAllParams();
10067 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10068 GetMetadataForObjectStoreId(params.objectStoreId());
10069 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10070 return false;
10072 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10073 return false;
10075 break;
10078 case RequestParams::TObjectStoreGetAllKeysParams: {
10079 const ObjectStoreGetAllKeysParams& params =
10080 aParams.get_ObjectStoreGetAllKeysParams();
10081 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10082 GetMetadataForObjectStoreId(params.objectStoreId());
10083 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10084 return false;
10086 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10087 return false;
10089 break;
10092 case RequestParams::TObjectStoreDeleteParams: {
10093 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite &&
10094 mMode != IDBTransaction::Mode::ReadWriteFlush &&
10095 mMode != IDBTransaction::Mode::Cleanup &&
10096 mMode != IDBTransaction::Mode::VersionChange)) {
10097 return false;
10100 const ObjectStoreDeleteParams& params =
10101 aParams.get_ObjectStoreDeleteParams();
10102 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10103 GetMetadataForObjectStoreId(params.objectStoreId());
10104 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10105 return false;
10107 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
10108 return false;
10110 break;
10113 case RequestParams::TObjectStoreClearParams: {
10114 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite &&
10115 mMode != IDBTransaction::Mode::ReadWriteFlush &&
10116 mMode != IDBTransaction::Mode::Cleanup &&
10117 mMode != IDBTransaction::Mode::VersionChange)) {
10118 return false;
10121 const ObjectStoreClearParams& params =
10122 aParams.get_ObjectStoreClearParams();
10123 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10124 GetMetadataForObjectStoreId(params.objectStoreId());
10125 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10126 return false;
10128 break;
10131 case RequestParams::TObjectStoreCountParams: {
10132 const ObjectStoreCountParams& params =
10133 aParams.get_ObjectStoreCountParams();
10134 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10135 GetMetadataForObjectStoreId(params.objectStoreId());
10136 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10137 return false;
10139 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10140 return false;
10142 break;
10145 case RequestParams::TIndexGetParams: {
10146 const IndexGetParams& params = aParams.get_IndexGetParams();
10147 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10148 GetMetadataForObjectStoreId(params.objectStoreId());
10149 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10150 return false;
10152 const SafeRefPtr<FullIndexMetadata> indexMetadata =
10153 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
10154 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10155 return false;
10157 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
10158 return false;
10160 break;
10163 case RequestParams::TIndexGetKeyParams: {
10164 const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams();
10165 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10166 GetMetadataForObjectStoreId(params.objectStoreId());
10167 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10168 return false;
10170 const SafeRefPtr<FullIndexMetadata> indexMetadata =
10171 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
10172 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10173 return false;
10175 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
10176 return false;
10178 break;
10181 case RequestParams::TIndexGetAllParams: {
10182 const IndexGetAllParams& params = aParams.get_IndexGetAllParams();
10183 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10184 GetMetadataForObjectStoreId(params.objectStoreId());
10185 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10186 return false;
10188 const SafeRefPtr<FullIndexMetadata> indexMetadata =
10189 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
10190 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10191 return false;
10193 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10194 return false;
10196 break;
10199 case RequestParams::TIndexGetAllKeysParams: {
10200 const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams();
10201 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10202 GetMetadataForObjectStoreId(params.objectStoreId());
10203 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10204 return false;
10206 const SafeRefPtr<FullIndexMetadata> indexMetadata =
10207 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
10208 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10209 return false;
10211 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10212 return false;
10214 break;
10217 case RequestParams::TIndexCountParams: {
10218 const IndexCountParams& params = aParams.get_IndexCountParams();
10219 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10220 GetMetadataForObjectStoreId(params.objectStoreId());
10221 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10222 return false;
10224 const SafeRefPtr<FullIndexMetadata> indexMetadata =
10225 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
10226 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10227 return false;
10229 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10230 return false;
10232 break;
10235 default:
10236 MOZ_CRASH("Should never get here!");
10239 return true;
10242 bool TransactionBase::VerifyRequestParams(
10243 const SerializedKeyRange& aParams) const {
10244 AssertIsOnBackgroundThread();
10246 // XXX Check more here?
10248 if (aParams.isOnly()) {
10249 if (NS_AUUF_OR_WARN_IF(aParams.lower().IsUnset())) {
10250 return false;
10252 if (NS_AUUF_OR_WARN_IF(!aParams.upper().IsUnset())) {
10253 return false;
10255 if (NS_AUUF_OR_WARN_IF(aParams.lowerOpen())) {
10256 return false;
10258 if (NS_AUUF_OR_WARN_IF(aParams.upperOpen())) {
10259 return false;
10261 } else if (NS_AUUF_OR_WARN_IF(aParams.lower().IsUnset() &&
10262 aParams.upper().IsUnset())) {
10263 return false;
10266 return true;
10269 bool TransactionBase::VerifyRequestParams(
10270 const ObjectStoreAddPutParams& aParams) const {
10271 AssertIsOnBackgroundThread();
10273 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite &&
10274 mMode != IDBTransaction::Mode::ReadWriteFlush &&
10275 mMode != IDBTransaction::Mode::VersionChange)) {
10276 return false;
10279 SafeRefPtr<FullObjectStoreMetadata> objMetadata =
10280 GetMetadataForObjectStoreId(aParams.objectStoreId());
10281 if (NS_AUUF_OR_WARN_IF(!objMetadata)) {
10282 return false;
10285 if (NS_AUUF_OR_WARN_IF(!aParams.cloneInfo().data().data.Size())) {
10286 return false;
10289 if (objMetadata->mCommonMetadata.autoIncrement() &&
10290 objMetadata->mCommonMetadata.keyPath().IsValid() &&
10291 aParams.key().IsUnset()) {
10292 const SerializedStructuredCloneWriteInfo& cloneInfo = aParams.cloneInfo();
10294 if (NS_AUUF_OR_WARN_IF(!cloneInfo.offsetToKeyProp())) {
10295 return false;
10298 if (NS_AUUF_OR_WARN_IF(cloneInfo.data().data.Size() < sizeof(uint64_t))) {
10299 return false;
10302 if (NS_AUUF_OR_WARN_IF(cloneInfo.offsetToKeyProp() >
10303 (cloneInfo.data().data.Size() - sizeof(uint64_t)))) {
10304 return false;
10306 } else if (NS_AUUF_OR_WARN_IF(aParams.cloneInfo().offsetToKeyProp())) {
10307 return false;
10310 for (const auto& updateInfo : aParams.indexUpdateInfos()) {
10311 SafeRefPtr<FullIndexMetadata> indexMetadata =
10312 GetMetadataForIndexId(*objMetadata, updateInfo.indexId());
10313 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10314 return false;
10317 if (NS_AUUF_OR_WARN_IF(updateInfo.value().IsUnset())) {
10318 return false;
10321 MOZ_ASSERT(!updateInfo.value().GetBuffer().IsEmpty());
10324 for (const FileAddInfo& fileAddInfo : aParams.fileAddInfos()) {
10325 const PBackgroundIDBDatabaseFileParent* file = fileAddInfo.fileParent();
10327 switch (fileAddInfo.type()) {
10328 case StructuredCloneFileBase::eBlob:
10329 if (NS_AUUF_OR_WARN_IF(!file)) {
10330 return false;
10332 break;
10334 case StructuredCloneFileBase::eMutableFile: {
10335 return false;
10338 case StructuredCloneFileBase::eStructuredClone:
10339 case StructuredCloneFileBase::eWasmBytecode:
10340 case StructuredCloneFileBase::eWasmCompiled:
10341 case StructuredCloneFileBase::eEndGuard:
10342 MOZ_ASSERT_UNLESS_FUZZING(false, "Unsupported.");
10343 return false;
10345 default:
10346 MOZ_CRASH("Should never get here!");
10350 return true;
10353 bool TransactionBase::VerifyRequestParams(
10354 const Maybe<SerializedKeyRange>& aParams) const {
10355 AssertIsOnBackgroundThread();
10357 if (aParams.isSome()) {
10358 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(aParams.ref()))) {
10359 return false;
10363 return true;
10366 void TransactionBase::NoteActiveRequest() {
10367 AssertIsOnBackgroundThread();
10368 MOZ_ASSERT(mActiveRequestCount < UINT64_MAX);
10370 mActiveRequestCount++;
10373 void TransactionBase::NoteFinishedRequest(const int64_t aRequestId,
10374 const nsresult aResultCode) {
10375 AssertIsOnBackgroundThread();
10376 MOZ_ASSERT(mActiveRequestCount);
10378 mActiveRequestCount--;
10380 if (NS_FAILED(aResultCode)) {
10381 mLastFailedRequest = Some(aRequestId);
10384 MaybeCommitOrAbort();
10387 void TransactionBase::Invalidate() {
10388 AssertIsOnBackgroundThread();
10389 MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread);
10391 if (!mInvalidated) {
10392 mInvalidated.Flip();
10393 mInvalidatedOnAnyThread = true;
10395 Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, /* aForce */ false);
10399 PBackgroundIDBRequestParent* TransactionBase::AllocRequest(
10400 RequestParams&& aParams, bool aTrustParams) {
10401 AssertIsOnBackgroundThread();
10402 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
10404 #ifdef DEBUG
10405 // Always verify parameters in DEBUG builds!
10406 aTrustParams = false;
10407 #endif
10409 if (NS_AUUF_OR_WARN_IF(!aTrustParams && !VerifyRequestParams(aParams))) {
10410 return nullptr;
10413 if (NS_AUUF_OR_WARN_IF(mCommitOrAbortReceived)) {
10414 return nullptr;
10417 RefPtr<NormalTransactionOp> actor;
10419 switch (aParams.type()) {
10420 case RequestParams::TObjectStoreAddParams:
10421 case RequestParams::TObjectStorePutParams:
10422 actor = new ObjectStoreAddOrPutRequestOp(SafeRefPtrFromThis(),
10423 std::move(aParams));
10424 break;
10426 case RequestParams::TObjectStoreGetParams:
10427 actor = new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aParams,
10428 /* aGetAll */ false);
10429 break;
10431 case RequestParams::TObjectStoreGetAllParams:
10432 actor = new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aParams,
10433 /* aGetAll */ true);
10434 break;
10436 case RequestParams::TObjectStoreGetKeyParams:
10437 actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aParams,
10438 /* aGetAll */ false);
10439 break;
10441 case RequestParams::TObjectStoreGetAllKeysParams:
10442 actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aParams,
10443 /* aGetAll */ true);
10444 break;
10446 case RequestParams::TObjectStoreDeleteParams:
10447 actor = new ObjectStoreDeleteRequestOp(
10448 SafeRefPtrFromThis(), aParams.get_ObjectStoreDeleteParams());
10449 break;
10451 case RequestParams::TObjectStoreClearParams:
10452 actor = new ObjectStoreClearRequestOp(
10453 SafeRefPtrFromThis(), aParams.get_ObjectStoreClearParams());
10454 break;
10456 case RequestParams::TObjectStoreCountParams:
10457 actor = new ObjectStoreCountRequestOp(
10458 SafeRefPtrFromThis(), aParams.get_ObjectStoreCountParams());
10459 break;
10461 case RequestParams::TIndexGetParams:
10462 actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aParams,
10463 /* aGetAll */ false);
10464 break;
10466 case RequestParams::TIndexGetKeyParams:
10467 actor = new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aParams,
10468 /* aGetAll */ false);
10469 break;
10471 case RequestParams::TIndexGetAllParams:
10472 actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aParams,
10473 /* aGetAll */ true);
10474 break;
10476 case RequestParams::TIndexGetAllKeysParams:
10477 actor = new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aParams,
10478 /* aGetAll */ true);
10479 break;
10481 case RequestParams::TIndexCountParams:
10482 actor = new IndexCountRequestOp(SafeRefPtrFromThis(), aParams);
10483 break;
10485 default:
10486 MOZ_CRASH("Should never get here!");
10489 MOZ_ASSERT(actor);
10491 // Transfer ownership to IPDL.
10492 return actor.forget().take();
10495 bool TransactionBase::StartRequest(PBackgroundIDBRequestParent* aActor) {
10496 AssertIsOnBackgroundThread();
10497 MOZ_ASSERT(aActor);
10499 auto* op = static_cast<NormalTransactionOp*>(aActor);
10501 if (NS_WARN_IF(!op->Init(*this))) {
10502 op->Cleanup();
10503 return false;
10506 op->DispatchToConnectionPool();
10507 return true;
10510 bool TransactionBase::DeallocRequest(
10511 PBackgroundIDBRequestParent* const aActor) {
10512 AssertIsOnBackgroundThread();
10513 MOZ_ASSERT(aActor);
10515 // Transfer ownership back from IPDL.
10516 const RefPtr<NormalTransactionOp> actor =
10517 dont_AddRef(static_cast<NormalTransactionOp*>(aActor));
10518 return true;
10521 already_AddRefed<PBackgroundIDBCursorParent> TransactionBase::AllocCursor(
10522 const OpenCursorParams& aParams, bool aTrustParams) {
10523 AssertIsOnBackgroundThread();
10524 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
10526 #ifdef DEBUG
10527 // Always verify parameters in DEBUG builds!
10528 aTrustParams = false;
10529 #endif
10531 const OpenCursorParams::Type type = aParams.type();
10532 SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata;
10533 SafeRefPtr<FullIndexMetadata> indexMetadata;
10534 CursorBase::Direction direction;
10536 // First extract the parameters common to all open cursor variants.
10537 const auto& commonParams = GetCommonOpenCursorParams(aParams);
10538 objectStoreMetadata =
10539 GetMetadataForObjectStoreId(commonParams.objectStoreId());
10540 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10541 return nullptr;
10543 if (aTrustParams && NS_AUUF_OR_WARN_IF(!VerifyRequestParams(
10544 commonParams.optionalKeyRange()))) {
10545 return nullptr;
10547 direction = commonParams.direction();
10549 // Now, for the index open cursor variants, extract the additional parameter.
10550 if (type == OpenCursorParams::TIndexOpenCursorParams ||
10551 type == OpenCursorParams::TIndexOpenKeyCursorParams) {
10552 const auto& commonIndexParams = GetCommonIndexOpenCursorParams(aParams);
10553 indexMetadata = GetMetadataForIndexId(*objectStoreMetadata,
10554 commonIndexParams.indexId());
10555 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10556 return nullptr;
10560 if (NS_AUUF_OR_WARN_IF(mCommitOrAbortReceived)) {
10561 return nullptr;
10564 // Create Cursor and transfer ownership to IPDL.
10565 switch (type) {
10566 case OpenCursorParams::TObjectStoreOpenCursorParams:
10567 MOZ_ASSERT(!indexMetadata);
10568 return MakeAndAddRef<Cursor<IDBCursorType::ObjectStore>>(
10569 SafeRefPtrFromThis(), std::move(objectStoreMetadata), direction,
10570 CursorBase::ConstructFromTransactionBase{});
10571 case OpenCursorParams::TObjectStoreOpenKeyCursorParams:
10572 MOZ_ASSERT(!indexMetadata);
10573 return MakeAndAddRef<Cursor<IDBCursorType::ObjectStoreKey>>(
10574 SafeRefPtrFromThis(), std::move(objectStoreMetadata), direction,
10575 CursorBase::ConstructFromTransactionBase{});
10576 case OpenCursorParams::TIndexOpenCursorParams:
10577 return MakeAndAddRef<Cursor<IDBCursorType::Index>>(
10578 SafeRefPtrFromThis(), std::move(objectStoreMetadata),
10579 std::move(indexMetadata), direction,
10580 CursorBase::ConstructFromTransactionBase{});
10581 case OpenCursorParams::TIndexOpenKeyCursorParams:
10582 return MakeAndAddRef<Cursor<IDBCursorType::IndexKey>>(
10583 SafeRefPtrFromThis(), std::move(objectStoreMetadata),
10584 std::move(indexMetadata), direction,
10585 CursorBase::ConstructFromTransactionBase{});
10586 default:
10587 MOZ_CRASH("Cannot get here.");
10591 bool TransactionBase::StartCursor(PBackgroundIDBCursorParent* const aActor,
10592 const OpenCursorParams& aParams) {
10593 AssertIsOnBackgroundThread();
10594 MOZ_ASSERT(aActor);
10595 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
10597 auto* const op = static_cast<CursorBase*>(aActor);
10599 if (NS_WARN_IF(!op->Start(aParams))) {
10600 return false;
10603 return true;
10606 /*******************************************************************************
10607 * NormalTransaction
10608 ******************************************************************************/
10610 NormalTransaction::NormalTransaction(
10611 SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode,
10612 nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores)
10613 : TransactionBase(std::move(aDatabase), aMode),
10614 mObjectStores{std::move(aObjectStores)} {
10615 AssertIsOnBackgroundThread();
10616 MOZ_ASSERT(!mObjectStores.IsEmpty());
10619 bool NormalTransaction::IsSameProcessActor() {
10620 AssertIsOnBackgroundThread();
10622 PBackgroundParent* const actor = Manager()->Manager()->Manager();
10623 MOZ_ASSERT(actor);
10625 return !BackgroundParent::IsOtherProcessActor(actor);
10628 void NormalTransaction::SendCompleteNotification(nsresult aResult) {
10629 AssertIsOnBackgroundThread();
10631 if (!IsActorDestroyed()) {
10632 Unused << SendComplete(aResult);
10636 void NormalTransaction::ActorDestroy(ActorDestroyReason aWhy) {
10637 AssertIsOnBackgroundThread();
10639 NoteActorDestroyed();
10641 if (!mCommittedOrAborted) {
10642 if (NS_SUCCEEDED(mResultCode)) {
10643 IDB_REPORT_INTERNAL_ERR();
10644 mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
10647 mForceAborted.EnsureFlipped();
10649 MaybeCommitOrAbort();
10653 mozilla::ipc::IPCResult NormalTransaction::RecvDeleteMe() {
10654 AssertIsOnBackgroundThread();
10655 MOZ_ASSERT(!IsActorDestroyed());
10657 QM_WARNONLY_TRY(OkIf(PBackgroundIDBTransactionParent::Send__delete__(this)));
10659 return IPC_OK();
10662 mozilla::ipc::IPCResult NormalTransaction::RecvCommit(
10663 const Maybe<int64_t>& aLastRequest) {
10664 AssertIsOnBackgroundThread();
10666 return TransactionBase::RecvCommit(this, aLastRequest);
10669 mozilla::ipc::IPCResult NormalTransaction::RecvAbort(
10670 const nsresult& aResultCode) {
10671 AssertIsOnBackgroundThread();
10673 return TransactionBase::RecvAbort(this, aResultCode);
10676 PBackgroundIDBRequestParent*
10677 NormalTransaction::AllocPBackgroundIDBRequestParent(
10678 const RequestParams& aParams) {
10679 AssertIsOnBackgroundThread();
10680 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
10682 return AllocRequest(std::move(const_cast<RequestParams&>(aParams)),
10683 IsSameProcessActor());
10686 mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBRequestConstructor(
10687 PBackgroundIDBRequestParent* const aActor, const RequestParams& aParams) {
10688 AssertIsOnBackgroundThread();
10689 MOZ_ASSERT(aActor);
10690 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
10692 if (!StartRequest(aActor)) {
10693 return IPC_FAIL(this, "StartRequest failed!");
10695 return IPC_OK();
10698 bool NormalTransaction::DeallocPBackgroundIDBRequestParent(
10699 PBackgroundIDBRequestParent* const aActor) {
10700 AssertIsOnBackgroundThread();
10701 MOZ_ASSERT(aActor);
10703 return DeallocRequest(aActor);
10706 already_AddRefed<PBackgroundIDBCursorParent>
10707 NormalTransaction::AllocPBackgroundIDBCursorParent(
10708 const OpenCursorParams& aParams) {
10709 AssertIsOnBackgroundThread();
10711 return AllocCursor(aParams, IsSameProcessActor());
10714 mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBCursorConstructor(
10715 PBackgroundIDBCursorParent* const aActor, const OpenCursorParams& aParams) {
10716 AssertIsOnBackgroundThread();
10717 MOZ_ASSERT(aActor);
10718 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
10720 if (!StartCursor(aActor, aParams)) {
10721 return IPC_FAIL(this, "StartCursor failed!");
10723 return IPC_OK();
10726 /*******************************************************************************
10727 * VersionChangeTransaction
10728 ******************************************************************************/
10730 VersionChangeTransaction::VersionChangeTransaction(
10731 OpenDatabaseOp* aOpenDatabaseOp)
10732 : TransactionBase(aOpenDatabaseOp->mDatabase.clonePtr(),
10733 IDBTransaction::Mode::VersionChange),
10734 mOpenDatabaseOp(aOpenDatabaseOp) {
10735 AssertIsOnBackgroundThread();
10736 MOZ_ASSERT(aOpenDatabaseOp);
10739 VersionChangeTransaction::~VersionChangeTransaction() {
10740 #ifdef DEBUG
10741 // Silence the base class' destructor assertion if we never made this actor
10742 // live.
10743 FakeActorDestroyed();
10744 #endif
10747 bool VersionChangeTransaction::IsSameProcessActor() {
10748 AssertIsOnBackgroundThread();
10750 PBackgroundParent* actor = Manager()->Manager()->Manager();
10751 MOZ_ASSERT(actor);
10753 return !BackgroundParent::IsOtherProcessActor(actor);
10756 void VersionChangeTransaction::SetActorAlive() {
10757 AssertIsOnBackgroundThread();
10758 MOZ_ASSERT(!IsActorDestroyed());
10760 mActorWasAlive.Flip();
10763 bool VersionChangeTransaction::CopyDatabaseMetadata() {
10764 AssertIsOnBackgroundThread();
10765 MOZ_ASSERT(!mOldMetadata);
10767 const auto& origMetadata = GetDatabase().Metadata();
10769 SafeRefPtr<FullDatabaseMetadata> newMetadata = origMetadata.Duplicate();
10770 if (NS_WARN_IF(!newMetadata)) {
10771 return false;
10774 // Replace the live metadata with the new mutable copy.
10775 DatabaseActorInfo* info;
10776 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(origMetadata.mDatabaseId, &info));
10777 MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
10778 MOZ_ASSERT(info->mMetadata == &origMetadata);
10780 mOldMetadata = std::move(info->mMetadata);
10781 info->mMetadata = std::move(newMetadata);
10783 // Replace metadata pointers for all live databases.
10784 for (const auto& liveDatabase : info->mLiveDatabases) {
10785 liveDatabase->mMetadata = info->mMetadata.clonePtr();
10788 return true;
10791 void VersionChangeTransaction::UpdateMetadata(nsresult aResult) {
10792 AssertIsOnBackgroundThread();
10793 MOZ_ASSERT(mOpenDatabaseOp);
10794 MOZ_ASSERT(!!mActorWasAlive == !!mOpenDatabaseOp->mDatabase);
10795 MOZ_ASSERT_IF(mActorWasAlive, !mOpenDatabaseOp->mDatabaseId.IsEmpty());
10797 if (IsActorDestroyed() || !mActorWasAlive) {
10798 return;
10801 SafeRefPtr<FullDatabaseMetadata> oldMetadata = std::move(mOldMetadata);
10803 DatabaseActorInfo* info;
10804 if (!gLiveDatabaseHashtable->Get(oldMetadata->mDatabaseId, &info)) {
10805 return;
10808 MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
10810 if (NS_SUCCEEDED(aResult)) {
10811 // Remove all deleted objectStores and indexes, then mark immutable.
10812 info->mMetadata->mObjectStores.RemoveIf([](const auto& objectStoreIter) {
10813 MOZ_ASSERT(objectStoreIter.Key());
10814 const SafeRefPtr<FullObjectStoreMetadata>& metadata =
10815 objectStoreIter.Data();
10816 MOZ_ASSERT(metadata);
10818 if (metadata->mDeleted) {
10819 return true;
10822 metadata->mIndexes.RemoveIf([](const auto& indexIter) -> bool {
10823 MOZ_ASSERT(indexIter.Key());
10824 const SafeRefPtr<FullIndexMetadata>& index = indexIter.Data();
10825 MOZ_ASSERT(index);
10827 return index->mDeleted;
10829 metadata->mIndexes.MarkImmutable();
10831 return false;
10834 info->mMetadata->mObjectStores.MarkImmutable();
10835 } else {
10836 // Replace metadata pointers for all live databases.
10837 info->mMetadata = std::move(oldMetadata);
10839 for (auto& liveDatabase : info->mLiveDatabases) {
10840 liveDatabase->mMetadata = info->mMetadata.clonePtr();
10845 void VersionChangeTransaction::SendCompleteNotification(nsresult aResult) {
10846 AssertIsOnBackgroundThread();
10847 MOZ_ASSERT(mOpenDatabaseOp);
10848 MOZ_ASSERT_IF(!mActorWasAlive, mOpenDatabaseOp->HasFailed());
10849 MOZ_ASSERT_IF(!mActorWasAlive, mOpenDatabaseOp->mState >
10850 OpenDatabaseOp::State::SendingResults);
10852 const RefPtr<OpenDatabaseOp> openDatabaseOp = std::move(mOpenDatabaseOp);
10854 if (!mActorWasAlive) {
10855 return;
10858 if (NS_FAILED(aResult)) {
10859 // 3.3.1 Opening a database:
10860 // "If the upgrade transaction was aborted, run the steps for closing a
10861 // database connection with connection, create and return a new AbortError
10862 // exception and abort these steps."
10863 openDatabaseOp->SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
10866 openDatabaseOp->mState = OpenDatabaseOp::State::SendingResults;
10868 if (!IsActorDestroyed()) {
10869 Unused << SendComplete(aResult);
10872 MOZ_ALWAYS_SUCCEEDS(openDatabaseOp->Run());
10875 void VersionChangeTransaction::ActorDestroy(ActorDestroyReason aWhy) {
10876 AssertIsOnBackgroundThread();
10878 NoteActorDestroyed();
10880 if (!mCommittedOrAborted) {
10881 if (NS_SUCCEEDED(mResultCode)) {
10882 IDB_REPORT_INTERNAL_ERR();
10883 mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
10886 mForceAborted.EnsureFlipped();
10888 MaybeCommitOrAbort();
10892 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteMe() {
10893 AssertIsOnBackgroundThread();
10894 MOZ_ASSERT(!IsActorDestroyed());
10896 QM_WARNONLY_TRY(
10897 OkIf(PBackgroundIDBVersionChangeTransactionParent::Send__delete__(this)));
10899 return IPC_OK();
10902 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCommit(
10903 const Maybe<int64_t>& aLastRequest) {
10904 AssertIsOnBackgroundThread();
10906 return TransactionBase::RecvCommit(this, aLastRequest);
10909 mozilla::ipc::IPCResult VersionChangeTransaction::RecvAbort(
10910 const nsresult& aResultCode) {
10911 AssertIsOnBackgroundThread();
10913 return TransactionBase::RecvAbort(this, aResultCode);
10916 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCreateObjectStore(
10917 const ObjectStoreMetadata& aMetadata) {
10918 AssertIsOnBackgroundThread();
10920 if (NS_WARN_IF(!aMetadata.id())) {
10921 return IPC_FAIL(this, "No metadata ID!");
10924 const SafeRefPtr<FullDatabaseMetadata> dbMetadata =
10925 GetDatabase().MetadataPtr();
10927 if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextObjectStoreId)) {
10928 return IPC_FAIL(this, "Requested metadata ID does not match next ID!");
10931 if (NS_WARN_IF(
10932 MatchMetadataNameOrId(dbMetadata->mObjectStores, aMetadata.id(),
10933 SomeRef<const nsAString&>(aMetadata.name()))
10934 .isSome())) {
10935 return IPC_FAIL(this, "MatchMetadataNameOrId failed!");
10938 if (NS_WARN_IF(mCommitOrAbortReceived)) {
10939 return IPC_FAIL(this, "Transaction is already committed/aborted!");
10942 const int64_t initialAutoIncrementId = aMetadata.autoIncrement() ? 1 : 0;
10943 auto newMetadata = MakeSafeRefPtr<FullObjectStoreMetadata>(
10944 aMetadata, FullObjectStoreMetadata::AutoIncrementIds{
10945 initialAutoIncrementId, initialAutoIncrementId});
10947 if (NS_WARN_IF(!dbMetadata->mObjectStores.InsertOrUpdate(
10948 aMetadata.id(), std::move(newMetadata), fallible))) {
10949 return IPC_FAIL(this, "mObjectStores.InsertOrUpdate failed!");
10952 dbMetadata->mNextObjectStoreId++;
10954 RefPtr<CreateObjectStoreOp> op = new CreateObjectStoreOp(
10955 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aMetadata);
10957 if (NS_WARN_IF(!op->Init(*this))) {
10958 op->Cleanup();
10959 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
10962 op->DispatchToConnectionPool();
10964 return IPC_OK();
10967 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteObjectStore(
10968 const IndexOrObjectStoreId& aObjectStoreId) {
10969 AssertIsOnBackgroundThread();
10971 if (NS_WARN_IF(!aObjectStoreId)) {
10972 return IPC_FAIL(this, "No ObjectStoreId!");
10975 const auto& dbMetadata = GetDatabase().Metadata();
10976 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0);
10978 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) {
10979 return IPC_FAIL(this, "Invalid ObjectStoreId!");
10982 SafeRefPtr<FullObjectStoreMetadata> foundMetadata =
10983 GetMetadataForObjectStoreId(aObjectStoreId);
10985 if (NS_WARN_IF(!foundMetadata)) {
10986 return IPC_FAIL(this, "No metadata found for ObjectStoreId!");
10989 if (NS_WARN_IF(mCommitOrAbortReceived)) {
10990 return IPC_FAIL(this, "Transaction is already committed/aborted!");
10993 foundMetadata->mDeleted.Flip();
10995 DebugOnly<bool> foundTargetId = false;
10996 const bool isLastObjectStore = std::all_of(
10997 dbMetadata.mObjectStores.begin(), dbMetadata.mObjectStores.end(),
10998 [&foundTargetId, aObjectStoreId](const auto& objectStoreEntry) -> bool {
10999 if (uint64_t(aObjectStoreId) == objectStoreEntry.GetKey()) {
11000 foundTargetId = true;
11001 return true;
11004 return objectStoreEntry.GetData()->mDeleted;
11006 MOZ_ASSERT_IF(isLastObjectStore, foundTargetId);
11008 RefPtr<DeleteObjectStoreOp> op = new DeleteObjectStoreOp(
11009 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(),
11010 std::move(foundMetadata), isLastObjectStore);
11012 if (NS_WARN_IF(!op->Init(*this))) {
11013 op->Cleanup();
11014 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
11017 op->DispatchToConnectionPool();
11019 return IPC_OK();
11022 mozilla::ipc::IPCResult VersionChangeTransaction::RecvRenameObjectStore(
11023 const IndexOrObjectStoreId& aObjectStoreId, const nsAString& aName) {
11024 AssertIsOnBackgroundThread();
11026 if (NS_WARN_IF(!aObjectStoreId)) {
11027 return IPC_FAIL(this, "No ObjectStoreId!");
11031 const auto& dbMetadata = GetDatabase().Metadata();
11032 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0);
11034 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) {
11035 return IPC_FAIL(this, "Invalid ObjectStoreId!");
11039 SafeRefPtr<FullObjectStoreMetadata> foundMetadata =
11040 GetMetadataForObjectStoreId(aObjectStoreId);
11042 if (NS_WARN_IF(!foundMetadata)) {
11043 return IPC_FAIL(this, "No metadata found for ObjectStoreId!");
11046 if (NS_WARN_IF(mCommitOrAbortReceived)) {
11047 return IPC_FAIL(this, "Transaction is already committed/aborted!");
11050 foundMetadata->mCommonMetadata.name() = aName;
11052 RefPtr<RenameObjectStoreOp> renameOp = new RenameObjectStoreOp(
11053 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(),
11054 *foundMetadata);
11056 if (NS_WARN_IF(!renameOp->Init(*this))) {
11057 renameOp->Cleanup();
11058 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
11061 renameOp->DispatchToConnectionPool();
11063 return IPC_OK();
11066 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCreateIndex(
11067 const IndexOrObjectStoreId& aObjectStoreId,
11068 const IndexMetadata& aMetadata) {
11069 AssertIsOnBackgroundThread();
11071 if (NS_WARN_IF(!aObjectStoreId)) {
11072 return IPC_FAIL(this, "No ObjectStoreId!");
11075 if (NS_WARN_IF(!aMetadata.id())) {
11076 return IPC_FAIL(this, "No Metadata id!");
11079 const auto dbMetadata = GetDatabase().MetadataPtr();
11081 if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextIndexId)) {
11082 return IPC_FAIL(this, "Requested metadata ID does not match next ID!");
11085 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
11086 GetMetadataForObjectStoreId(aObjectStoreId);
11088 if (NS_WARN_IF(!foundObjectStoreMetadata)) {
11089 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!");
11092 if (NS_WARN_IF(MatchMetadataNameOrId(
11093 foundObjectStoreMetadata->mIndexes, aMetadata.id(),
11094 SomeRef<const nsAString&>(aMetadata.name()))
11095 .isSome())) {
11096 return IPC_FAIL(this, "MatchMetadataNameOrId failed!");
11099 if (NS_WARN_IF(mCommitOrAbortReceived)) {
11100 return IPC_FAIL(this, "Transaction is already committed/aborted!");
11103 auto newMetadata = MakeSafeRefPtr<FullIndexMetadata>();
11104 newMetadata->mCommonMetadata = aMetadata;
11106 if (NS_WARN_IF(!foundObjectStoreMetadata->mIndexes.InsertOrUpdate(
11107 aMetadata.id(), std::move(newMetadata), fallible))) {
11108 return IPC_FAIL(this, "mIndexes.InsertOrUpdate failed!");
11111 dbMetadata->mNextIndexId++;
11113 RefPtr<CreateIndexOp> op = new CreateIndexOp(
11114 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aObjectStoreId,
11115 aMetadata);
11117 if (NS_WARN_IF(!op->Init(*this))) {
11118 op->Cleanup();
11119 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
11122 op->DispatchToConnectionPool();
11124 return IPC_OK();
11127 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteIndex(
11128 const IndexOrObjectStoreId& aObjectStoreId,
11129 const IndexOrObjectStoreId& aIndexId) {
11130 AssertIsOnBackgroundThread();
11132 if (NS_WARN_IF(!aObjectStoreId)) {
11133 return IPC_FAIL(this, "No ObjectStoreId!");
11136 if (NS_WARN_IF(!aIndexId)) {
11137 return IPC_FAIL(this, "No Index id!");
11140 const auto& dbMetadata = GetDatabase().Metadata();
11141 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0);
11142 MOZ_ASSERT(dbMetadata.mNextIndexId > 0);
11144 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) {
11145 return IPC_FAIL(this, "Requested ObjectStoreId does not match next ID!");
11148 if (NS_WARN_IF(aIndexId >= dbMetadata.mNextIndexId)) {
11149 return IPC_FAIL(this, "Requested IndexId does not match next ID!");
11153 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
11154 GetMetadataForObjectStoreId(aObjectStoreId);
11156 if (NS_WARN_IF(!foundObjectStoreMetadata)) {
11157 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!");
11160 SafeRefPtr<FullIndexMetadata> foundIndexMetadata =
11161 GetMetadataForIndexId(*foundObjectStoreMetadata, aIndexId);
11163 if (NS_WARN_IF(!foundIndexMetadata)) {
11164 return IPC_FAIL(this, "GetMetadataForIndexId failed!");
11167 if (NS_WARN_IF(mCommitOrAbortReceived)) {
11168 return IPC_FAIL(this, "Transaction is already committed/aborted!");
11171 foundIndexMetadata->mDeleted.Flip();
11173 DebugOnly<bool> foundTargetId = false;
11174 const bool isLastIndex =
11175 std::all_of(foundObjectStoreMetadata->mIndexes.cbegin(),
11176 foundObjectStoreMetadata->mIndexes.cend(),
11177 [&foundTargetId, aIndexId](const auto& indexEntry) -> bool {
11178 if (uint64_t(aIndexId) == indexEntry.GetKey()) {
11179 foundTargetId = true;
11180 return true;
11183 return indexEntry.GetData()->mDeleted;
11185 MOZ_ASSERT_IF(isLastIndex, foundTargetId);
11187 RefPtr<DeleteIndexOp> op = new DeleteIndexOp(
11188 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aObjectStoreId,
11189 aIndexId, foundIndexMetadata->mCommonMetadata.unique(), isLastIndex);
11191 if (NS_WARN_IF(!op->Init(*this))) {
11192 op->Cleanup();
11193 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
11196 op->DispatchToConnectionPool();
11198 return IPC_OK();
11201 mozilla::ipc::IPCResult VersionChangeTransaction::RecvRenameIndex(
11202 const IndexOrObjectStoreId& aObjectStoreId,
11203 const IndexOrObjectStoreId& aIndexId, const nsAString& aName) {
11204 AssertIsOnBackgroundThread();
11206 if (NS_WARN_IF(!aObjectStoreId)) {
11207 return IPC_FAIL(this, "No ObjectStoreId!");
11210 if (NS_WARN_IF(!aIndexId)) {
11211 return IPC_FAIL(this, "No Index id!");
11214 const SafeRefPtr<FullDatabaseMetadata> dbMetadata =
11215 GetDatabase().MetadataPtr();
11216 MOZ_ASSERT(dbMetadata);
11217 MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
11218 MOZ_ASSERT(dbMetadata->mNextIndexId > 0);
11220 if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
11221 return IPC_FAIL(this, "Requested ObjectStoreId does not match next ID!");
11224 if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) {
11225 return IPC_FAIL(this, "Requested IndexId does not match next ID!");
11228 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
11229 GetMetadataForObjectStoreId(aObjectStoreId);
11231 if (NS_WARN_IF(!foundObjectStoreMetadata)) {
11232 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!");
11235 SafeRefPtr<FullIndexMetadata> foundIndexMetadata =
11236 GetMetadataForIndexId(*foundObjectStoreMetadata, aIndexId);
11238 if (NS_WARN_IF(!foundIndexMetadata)) {
11239 return IPC_FAIL(this, "GetMetadataForIndexId failed!");
11242 if (NS_WARN_IF(mCommitOrAbortReceived)) {
11243 return IPC_FAIL(this, "Transaction is already committed/aborted!");
11246 foundIndexMetadata->mCommonMetadata.name() = aName;
11248 RefPtr<RenameIndexOp> renameOp = new RenameIndexOp(
11249 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(),
11250 *foundIndexMetadata, aObjectStoreId);
11252 if (NS_WARN_IF(!renameOp->Init(*this))) {
11253 renameOp->Cleanup();
11254 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
11257 renameOp->DispatchToConnectionPool();
11259 return IPC_OK();
11262 PBackgroundIDBRequestParent*
11263 VersionChangeTransaction::AllocPBackgroundIDBRequestParent(
11264 const RequestParams& aParams) {
11265 AssertIsOnBackgroundThread();
11266 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
11268 return AllocRequest(std::move(const_cast<RequestParams&>(aParams)),
11269 IsSameProcessActor());
11272 mozilla::ipc::IPCResult
11273 VersionChangeTransaction::RecvPBackgroundIDBRequestConstructor(
11274 PBackgroundIDBRequestParent* aActor, const RequestParams& aParams) {
11275 AssertIsOnBackgroundThread();
11276 MOZ_ASSERT(aActor);
11277 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
11279 if (!StartRequest(aActor)) {
11280 return IPC_FAIL(this, "StartRequest failed!");
11282 return IPC_OK();
11285 bool VersionChangeTransaction::DeallocPBackgroundIDBRequestParent(
11286 PBackgroundIDBRequestParent* aActor) {
11287 AssertIsOnBackgroundThread();
11288 MOZ_ASSERT(aActor);
11290 return DeallocRequest(aActor);
11293 already_AddRefed<PBackgroundIDBCursorParent>
11294 VersionChangeTransaction::AllocPBackgroundIDBCursorParent(
11295 const OpenCursorParams& aParams) {
11296 AssertIsOnBackgroundThread();
11298 return AllocCursor(aParams, IsSameProcessActor());
11301 mozilla::ipc::IPCResult
11302 VersionChangeTransaction::RecvPBackgroundIDBCursorConstructor(
11303 PBackgroundIDBCursorParent* aActor, const OpenCursorParams& aParams) {
11304 AssertIsOnBackgroundThread();
11305 MOZ_ASSERT(aActor);
11306 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
11308 if (!StartCursor(aActor, aParams)) {
11309 return IPC_FAIL(this, "StartCursor failed!");
11311 return IPC_OK();
11314 /*******************************************************************************
11315 * CursorBase
11316 ******************************************************************************/
11318 CursorBase::CursorBase(SafeRefPtr<TransactionBase> aTransaction,
11319 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
11320 const Direction aDirection,
11321 const ConstructFromTransactionBase /*aConstructionTag*/)
11322 : mTransaction(std::move(aTransaction)),
11323 mObjectStoreMetadata(WrapNotNull(std::move(aObjectStoreMetadata))),
11324 mObjectStoreId((*mObjectStoreMetadata)->mCommonMetadata.id()),
11325 mDirection(aDirection),
11326 mMaxExtraCount(IndexedDatabaseManager::MaxPreloadExtraRecords()),
11327 mIsSameProcessActor(!BackgroundParent::IsOtherProcessActor(
11328 mTransaction->GetBackgroundParent())) {
11329 AssertIsOnBackgroundThread();
11330 MOZ_ASSERT(mTransaction);
11332 static_assert(
11333 OpenCursorParams::T__None == 0 && OpenCursorParams::T__Last == 4,
11334 "Lots of code here assumes only four types of cursors!");
11337 template <IDBCursorType CursorType>
11338 bool Cursor<CursorType>::VerifyRequestParams(
11339 const CursorRequestParams& aParams,
11340 const CursorPosition<CursorType>& aPosition) const {
11341 AssertIsOnBackgroundThread();
11342 MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
11343 MOZ_ASSERT(this->mObjectStoreMetadata);
11344 if constexpr (IsIndexCursor) {
11345 MOZ_ASSERT(this->mIndexMetadata);
11348 #ifdef DEBUG
11350 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
11351 mTransaction->GetMetadataForObjectStoreId(mObjectStoreId);
11352 if (objectStoreMetadata) {
11353 MOZ_ASSERT(objectStoreMetadata == (*this->mObjectStoreMetadata));
11354 } else {
11355 MOZ_ASSERT((*this->mObjectStoreMetadata)->mDeleted);
11358 if constexpr (IsIndexCursor) {
11359 if (objectStoreMetadata) {
11360 const SafeRefPtr<FullIndexMetadata> indexMetadata =
11361 mTransaction->GetMetadataForIndexId(*objectStoreMetadata,
11362 this->mIndexId);
11363 if (indexMetadata) {
11364 MOZ_ASSERT(indexMetadata == *this->mIndexMetadata);
11365 } else {
11366 MOZ_ASSERT((*this->mIndexMetadata)->mDeleted);
11371 #endif
11373 if (NS_AUUF_OR_WARN_IF((*this->mObjectStoreMetadata)->mDeleted)) {
11374 return false;
11377 if constexpr (IsIndexCursor) {
11378 if (NS_AUUF_OR_WARN_IF(this->mIndexMetadata &&
11379 (*this->mIndexMetadata)->mDeleted)) {
11380 return false;
11384 const Key& sortKey = aPosition.GetSortKey(this->IsLocaleAware());
11386 switch (aParams.type()) {
11387 case CursorRequestParams::TContinueParams: {
11388 const Key& key = aParams.get_ContinueParams().key();
11389 if (!key.IsUnset()) {
11390 switch (mDirection) {
11391 case IDBCursorDirection::Next:
11392 case IDBCursorDirection::Nextunique:
11393 if (NS_AUUF_OR_WARN_IF(key <= sortKey)) {
11394 return false;
11396 break;
11398 case IDBCursorDirection::Prev:
11399 case IDBCursorDirection::Prevunique:
11400 if (NS_AUUF_OR_WARN_IF(key >= sortKey)) {
11401 return false;
11403 break;
11405 default:
11406 MOZ_CRASH("Should never get here!");
11409 break;
11412 case CursorRequestParams::TContinuePrimaryKeyParams: {
11413 if constexpr (IsIndexCursor) {
11414 const Key& key = aParams.get_ContinuePrimaryKeyParams().key();
11415 const Key& primaryKey =
11416 aParams.get_ContinuePrimaryKeyParams().primaryKey();
11417 MOZ_ASSERT(!key.IsUnset());
11418 MOZ_ASSERT(!primaryKey.IsUnset());
11419 switch (mDirection) {
11420 case IDBCursorDirection::Next:
11421 if (NS_AUUF_OR_WARN_IF(key < sortKey ||
11422 (key == sortKey &&
11423 primaryKey <= aPosition.mObjectStoreKey))) {
11424 return false;
11426 break;
11428 case IDBCursorDirection::Prev:
11429 if (NS_AUUF_OR_WARN_IF(key > sortKey ||
11430 (key == sortKey &&
11431 primaryKey >= aPosition.mObjectStoreKey))) {
11432 return false;
11434 break;
11436 default:
11437 MOZ_CRASH("Should never get here!");
11440 break;
11443 case CursorRequestParams::TAdvanceParams:
11444 if (NS_AUUF_OR_WARN_IF(!aParams.get_AdvanceParams().count())) {
11445 return false;
11447 break;
11449 default:
11450 MOZ_CRASH("Should never get here!");
11453 return true;
11456 template <IDBCursorType CursorType>
11457 bool Cursor<CursorType>::Start(const OpenCursorParams& aParams) {
11458 AssertIsOnBackgroundThread();
11459 MOZ_ASSERT(aParams.type() == ToOpenCursorParamsType(CursorType));
11460 MOZ_ASSERT(this->mObjectStoreMetadata);
11462 if (NS_AUUF_OR_WARN_IF(mCurrentlyRunningOp)) {
11463 return false;
11466 const Maybe<SerializedKeyRange>& optionalKeyRange =
11467 GetCommonOpenCursorParams(aParams).optionalKeyRange();
11469 const RefPtr<OpenOp> openOp = new OpenOp(this, optionalKeyRange);
11471 if (NS_WARN_IF(!openOp->Init(*mTransaction))) {
11472 openOp->Cleanup();
11473 return false;
11476 openOp->DispatchToConnectionPool();
11477 mCurrentlyRunningOp = openOp;
11479 return true;
11482 void ValueCursorBase::ProcessFiles(CursorResponse& aResponse,
11483 const FilesArray& aFiles) {
11484 MOZ_ASSERT_IF(
11485 aResponse.type() == CursorResponse::Tnsresult ||
11486 aResponse.type() == CursorResponse::Tvoid_t ||
11487 aResponse.type() ==
11488 CursorResponse::TArrayOfObjectStoreKeyCursorResponse ||
11489 aResponse.type() == CursorResponse::TArrayOfIndexKeyCursorResponse,
11490 aFiles.IsEmpty());
11492 for (size_t i = 0; i < aFiles.Length(); ++i) {
11493 const auto& files = aFiles[i];
11494 if (!files.IsEmpty()) {
11495 // TODO: Replace this assertion by one that checks if the response type
11496 // matches the cursor type, at a more generic location.
11497 MOZ_ASSERT(aResponse.type() ==
11498 CursorResponse::TArrayOfObjectStoreCursorResponse ||
11499 aResponse.type() ==
11500 CursorResponse::TArrayOfIndexCursorResponse);
11502 SerializedStructuredCloneReadInfo* serializedInfo = nullptr;
11503 switch (aResponse.type()) {
11504 case CursorResponse::TArrayOfObjectStoreCursorResponse: {
11505 auto& responses = aResponse.get_ArrayOfObjectStoreCursorResponse();
11506 MOZ_ASSERT(i < responses.Length());
11507 serializedInfo = &responses[i].cloneInfo();
11508 break;
11511 case CursorResponse::TArrayOfIndexCursorResponse: {
11512 auto& responses = aResponse.get_ArrayOfIndexCursorResponse();
11513 MOZ_ASSERT(i < responses.Length());
11514 serializedInfo = &responses[i].cloneInfo();
11515 break;
11518 default:
11519 MOZ_CRASH("Should never get here!");
11522 MOZ_ASSERT(serializedInfo);
11523 MOZ_ASSERT(serializedInfo->files().IsEmpty());
11524 MOZ_ASSERT(this->mDatabase);
11526 QM_TRY_UNWRAP(serializedInfo->files(),
11527 SerializeStructuredCloneFiles(this->mDatabase, files,
11528 /* aForPreprocess */ false),
11529 QM_VOID, [&aResponse](const nsresult result) {
11530 aResponse = ClampResultCode(result);
11536 template <IDBCursorType CursorType>
11537 void Cursor<CursorType>::SendResponseInternal(
11538 CursorResponse& aResponse, const FilesArrayT<CursorType>& aFiles) {
11539 AssertIsOnBackgroundThread();
11540 MOZ_ASSERT(aResponse.type() != CursorResponse::T__None);
11541 MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult,
11542 NS_FAILED(aResponse.get_nsresult()));
11543 MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult,
11544 NS_ERROR_GET_MODULE(aResponse.get_nsresult()) ==
11545 NS_ERROR_MODULE_DOM_INDEXEDDB);
11546 MOZ_ASSERT(this->mObjectStoreMetadata);
11547 MOZ_ASSERT(mCurrentlyRunningOp);
11549 KeyValueBase::ProcessFiles(aResponse, aFiles);
11551 // Work around the deleted function by casting to the base class.
11552 QM_WARNONLY_TRY(OkIf(
11553 static_cast<PBackgroundIDBCursorParent*>(this)->SendResponse(aResponse)));
11555 mCurrentlyRunningOp = nullptr;
11558 template <IDBCursorType CursorType>
11559 void Cursor<CursorType>::ActorDestroy(ActorDestroyReason aWhy) {
11560 AssertIsOnBackgroundThread();
11562 if (mCurrentlyRunningOp) {
11563 mCurrentlyRunningOp->NoteActorDestroyed();
11566 if constexpr (IsValueCursor) {
11567 this->mBackgroundParent.destroy();
11569 this->mObjectStoreMetadata.destroy();
11570 if constexpr (IsIndexCursor) {
11571 this->mIndexMetadata.destroy();
11575 template <IDBCursorType CursorType>
11576 mozilla::ipc::IPCResult Cursor<CursorType>::RecvDeleteMe() {
11577 AssertIsOnBackgroundThread();
11578 MOZ_ASSERT(this->mObjectStoreMetadata);
11580 if (NS_WARN_IF(mCurrentlyRunningOp)) {
11581 return IPC_FAIL(
11582 this,
11583 "Attempt to delete a cursor with a non-null mCurrentlyRunningOp!");
11586 QM_WARNONLY_TRY(OkIf(PBackgroundIDBCursorParent::Send__delete__(this)));
11588 return IPC_OK();
11591 template <IDBCursorType CursorType>
11592 mozilla::ipc::IPCResult Cursor<CursorType>::RecvContinue(
11593 const CursorRequestParams& aParams, const Key& aCurrentKey,
11594 const Key& aCurrentObjectStoreKey) {
11595 AssertIsOnBackgroundThread();
11596 MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
11597 MOZ_ASSERT(this->mObjectStoreMetadata);
11598 if constexpr (IsIndexCursor) {
11599 MOZ_ASSERT(this->mIndexMetadata);
11602 const bool trustParams =
11603 #ifdef DEBUG
11604 // Always verify parameters in DEBUG builds!
11605 false
11606 #else
11607 this->mIsSameProcessActor
11608 #endif
11611 MOZ_ASSERT(!aCurrentKey.IsUnset());
11613 QM_TRY_UNWRAP(
11614 auto position,
11615 ([&]() -> Result<CursorPosition<CursorType>, mozilla::ipc::IPCResult> {
11616 if constexpr (IsIndexCursor) {
11617 auto localeAwarePosition = Key{};
11618 if (this->IsLocaleAware()) {
11619 QM_TRY_UNWRAP(
11620 localeAwarePosition,
11621 aCurrentKey.ToLocaleAwareKey(this->mLocale),
11622 Err(IPC_FAIL(this, "aCurrentKey.ToLocaleAwareKey failed!")));
11624 return CursorPosition<CursorType>{aCurrentKey, localeAwarePosition,
11625 aCurrentObjectStoreKey};
11626 } else {
11627 return CursorPosition<CursorType>{aCurrentKey};
11629 }()));
11631 if (!trustParams && !VerifyRequestParams(aParams, position)) {
11632 return IPC_FAIL(this, "VerifyRequestParams failed!");
11635 if (NS_WARN_IF(mCurrentlyRunningOp)) {
11636 return IPC_FAIL(this, "Cursor is CurrentlyRunningOp!");
11639 if (NS_WARN_IF(mTransaction->mCommitOrAbortReceived)) {
11640 return IPC_FAIL(this, "Transaction is already committed/aborted!");
11643 const RefPtr<ContinueOp> continueOp =
11644 new ContinueOp(this, aParams, std::move(position));
11645 if (NS_WARN_IF(!continueOp->Init(*mTransaction))) {
11646 continueOp->Cleanup();
11647 return IPC_FAIL(this, "ContinueOp initialization failed!");
11650 continueOp->DispatchToConnectionPool();
11651 mCurrentlyRunningOp = continueOp;
11653 return IPC_OK();
11656 /*******************************************************************************
11657 * DatabaseFileManager
11658 ******************************************************************************/
11660 DatabaseFileManager::MutexType DatabaseFileManager::sMutex;
11662 DatabaseFileManager::DatabaseFileManager(
11663 PersistenceType aPersistenceType,
11664 const quota::OriginMetadata& aOriginMetadata,
11665 const nsAString& aDatabaseName, const nsCString& aDatabaseID,
11666 bool aEnforcingQuota, bool aIsInPrivateBrowsingMode)
11667 : mPersistenceType(aPersistenceType),
11668 mOriginMetadata(aOriginMetadata),
11669 mDatabaseName(aDatabaseName),
11670 mDatabaseID(aDatabaseID),
11671 mEnforcingQuota(aEnforcingQuota),
11672 mIsInPrivateBrowsingMode(aIsInPrivateBrowsingMode) {}
11674 nsresult DatabaseFileManager::Init(nsIFile* aDirectory,
11675 mozIStorageConnection& aConnection) {
11676 AssertIsOnIOThread();
11677 MOZ_ASSERT(aDirectory);
11680 QM_TRY_INSPECT(const bool& existsAsDirectory,
11681 ExistsAsDirectory(*aDirectory));
11683 if (!existsAsDirectory) {
11684 QM_TRY(MOZ_TO_RESULT(aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)));
11687 QM_TRY_UNWRAP(auto path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11688 nsString, aDirectory, GetPath));
11690 mDirectoryPath.init(std::move(path));
11693 QM_TRY_INSPECT(const auto& journalDirectory,
11694 CloneFileAndAppend(*aDirectory, kJournalDirectoryName));
11696 // We don't care if it doesn't exist at all, but if it does exist, make sure
11697 // it's a directory.
11698 QM_TRY_INSPECT(const bool& existsAsDirectory,
11699 ExistsAsDirectory(*journalDirectory));
11700 Unused << existsAsDirectory;
11703 QM_TRY_UNWRAP(auto path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11704 nsString, journalDirectory, GetPath));
11706 mJournalDirectoryPath.init(std::move(path));
11709 QM_TRY_INSPECT(const auto& stmt,
11710 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11711 nsCOMPtr<mozIStorageStatement>, aConnection,
11712 CreateStatement, "SELECT id, refcount FROM file"_ns));
11714 QM_TRY(
11715 CollectWhileHasResult(*stmt, [this](auto& stmt) -> Result<Ok, nsresult> {
11716 QM_TRY_INSPECT(const int64_t& id,
11717 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
11718 QM_TRY_INSPECT(const int32_t& dbRefCnt,
11719 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 1));
11721 // We put a raw pointer into the hash table, so the memory refcount will
11722 // be 0, but the dbRefCnt is non-zero, which will keep the
11723 // DatabaseFileInfo object alive.
11724 MOZ_ASSERT(dbRefCnt > 0);
11725 mFileInfos.InsertOrUpdate(
11726 id, MakeNotNull<DatabaseFileInfo*>(
11727 FileInfoManagerGuard{}, SafeRefPtrFromThis(), id,
11728 static_cast<nsrefcnt>(dbRefCnt)));
11730 mLastFileId = std::max(id, mLastFileId);
11732 return Ok{};
11733 }));
11735 return NS_OK;
11738 nsCOMPtr<nsIFile> DatabaseFileManager::GetDirectory() {
11739 if (!this->AssertValid()) {
11740 return nullptr;
11743 return GetFileForPath(*mDirectoryPath);
11746 nsCOMPtr<nsIFile> DatabaseFileManager::GetCheckedDirectory() {
11747 auto directory = GetDirectory();
11748 if (NS_WARN_IF(!directory)) {
11749 return nullptr;
11752 DebugOnly<bool> exists;
11753 MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)));
11754 MOZ_ASSERT(exists);
11756 DebugOnly<bool> isDirectory;
11757 MOZ_ASSERT(NS_SUCCEEDED(directory->IsDirectory(&isDirectory)));
11758 MOZ_ASSERT(isDirectory);
11760 return directory;
11763 nsCOMPtr<nsIFile> DatabaseFileManager::GetJournalDirectory() {
11764 if (!this->AssertValid()) {
11765 return nullptr;
11768 return GetFileForPath(*mJournalDirectoryPath);
11771 nsCOMPtr<nsIFile> DatabaseFileManager::EnsureJournalDirectory() {
11772 // This can happen on the IO or on a transaction thread.
11773 MOZ_ASSERT(!NS_IsMainThread());
11775 auto journalDirectory = GetFileForPath(*mJournalDirectoryPath);
11776 QM_TRY(OkIf(journalDirectory), nullptr);
11778 QM_TRY_INSPECT(const bool& exists,
11779 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, Exists),
11780 nullptr);
11782 if (exists) {
11783 QM_TRY_INSPECT(const bool& isDirectory,
11784 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, IsDirectory),
11785 nullptr);
11787 QM_TRY(OkIf(isDirectory), nullptr);
11788 } else {
11789 QM_TRY(
11790 MOZ_TO_RESULT(journalDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
11791 nullptr);
11794 return journalDirectory;
11797 // static
11798 nsCOMPtr<nsIFile> DatabaseFileManager::GetFileForId(nsIFile* aDirectory,
11799 int64_t aId) {
11800 MOZ_ASSERT(aDirectory);
11801 MOZ_ASSERT(aId > 0);
11803 QM_TRY_RETURN(CloneFileAndAppend(*aDirectory, IntToString(aId)), nullptr);
11806 // static
11807 nsCOMPtr<nsIFile> DatabaseFileManager::GetCheckedFileForId(nsIFile* aDirectory,
11808 int64_t aId) {
11809 auto file = GetFileForId(aDirectory, aId);
11810 if (NS_WARN_IF(!file)) {
11811 return nullptr;
11814 DebugOnly<bool> exists;
11815 MOZ_ASSERT(NS_SUCCEEDED(file->Exists(&exists)));
11816 MOZ_ASSERT(exists);
11818 DebugOnly<bool> isFile;
11819 MOZ_ASSERT(NS_SUCCEEDED(file->IsFile(&isFile)));
11820 MOZ_ASSERT(isFile);
11822 return file;
11825 // static
11826 nsresult DatabaseFileManager::InitDirectory(nsIFile& aDirectory,
11827 nsIFile& aDatabaseFile,
11828 const nsACString& aOrigin,
11829 uint32_t aTelemetryId) {
11830 AssertIsOnIOThread();
11833 QM_TRY_INSPECT(const bool& exists,
11834 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
11836 if (!exists) {
11837 return NS_OK;
11840 QM_TRY_INSPECT(const bool& isDirectory,
11841 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
11842 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE);
11845 QM_TRY_INSPECT(const auto& journalDirectory,
11846 CloneFileAndAppend(aDirectory, kJournalDirectoryName));
11848 QM_TRY_INSPECT(const bool& exists,
11849 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, Exists));
11851 if (exists) {
11852 QM_TRY_INSPECT(const bool& isDirectory,
11853 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, IsDirectory));
11854 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE);
11856 bool hasJournals = false;
11858 QM_TRY(CollectEachFile(
11859 *journalDirectory,
11860 [&hasJournals](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
11861 QM_TRY_INSPECT(
11862 const auto& leafName,
11863 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName));
11865 nsresult rv;
11866 leafName.ToInteger64(&rv);
11867 if (NS_SUCCEEDED(rv)) {
11868 hasJournals = true;
11869 } else {
11870 UNKNOWN_FILE_WARNING(leafName);
11873 return Ok{};
11874 }));
11876 if (hasJournals) {
11877 QM_TRY_UNWRAP(const NotNull<nsCOMPtr<mozIStorageConnection>> connection,
11878 CreateStorageConnection(
11879 aDatabaseFile, aDirectory, VoidString(), aOrigin,
11880 /* aDirectoryLockId */ -1, aTelemetryId, Nothing{}));
11882 mozStorageTransaction transaction(connection.get(), false);
11884 QM_TRY(MOZ_TO_RESULT(transaction.Start()))
11886 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(
11887 "CREATE VIRTUAL TABLE fs USING filesystem;"_ns)));
11889 // The parameter names are not used, parameters are bound by index only
11890 // locally in the same function.
11891 QM_TRY_INSPECT(
11892 const auto& stmt,
11893 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11894 nsCOMPtr<mozIStorageStatement>, *connection, CreateStatement,
11895 "SELECT name, (name IN (SELECT id FROM file)) FROM fs WHERE path = :path"_ns));
11897 QM_TRY_INSPECT(const auto& path,
11898 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11899 nsString, journalDirectory, GetPath));
11901 QM_TRY(MOZ_TO_RESULT(stmt->BindStringByIndex(0, path)));
11903 QM_TRY(CollectWhileHasResult(
11904 *stmt,
11905 [&aDirectory, &journalDirectory](auto& stmt) -> Result<Ok, nsresult> {
11906 nsString name;
11907 QM_TRY(MOZ_TO_RESULT(stmt.GetString(0, name)));
11909 nsresult rv;
11910 name.ToInteger64(&rv);
11911 if (NS_FAILED(rv)) {
11912 return Ok{};
11915 int32_t flag = stmt.AsInt32(1);
11917 if (!flag) {
11918 QM_TRY_INSPECT(const auto& file,
11919 CloneFileAndAppend(aDirectory, name));
11921 if (NS_FAILED(file->Remove(false))) {
11922 NS_WARNING("Failed to remove orphaned file!");
11926 QM_TRY_INSPECT(const auto& journalFile,
11927 CloneFileAndAppend(*journalDirectory, name));
11929 if (NS_FAILED(journalFile->Remove(false))) {
11930 NS_WARNING("Failed to remove journal file!");
11933 return Ok{};
11934 }));
11936 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("DROP TABLE fs;"_ns)));
11937 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
11941 return NS_OK;
11944 // static
11945 Result<FileUsageType, nsresult> DatabaseFileManager::GetUsage(
11946 nsIFile* aDirectory) {
11947 AssertIsOnIOThread();
11948 MOZ_ASSERT(aDirectory);
11950 FileUsageType usage;
11952 QM_TRY(TraverseFiles(
11953 *aDirectory,
11954 // KnownDirEntryOp
11955 [&usage](nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
11956 if (isDirectory) {
11957 return Ok{};
11960 // Usually we only use QM_OR_ELSE_LOG_VERBOSE(_IF) with Remove and
11961 // NS_ERROR_FILE_NOT_FOUND check, but the file was found by a directory
11962 // traversal and ToInteger on the name succeeded, so it should be our
11963 // file and if the file disappears, the use of QM_OR_ELSE_WARN_IF is ok
11964 // here.
11965 QM_TRY_INSPECT(const auto& thisUsage,
11966 QM_OR_ELSE_WARN_IF(
11967 // Expression.
11968 MOZ_TO_RESULT_INVOKE_MEMBER(file, GetFileSize)
11969 .map([](const int64_t fileSize) {
11970 return FileUsageType(Some(uint64_t(fileSize)));
11972 // Predicate.
11973 ([](const nsresult rv) {
11974 return rv == NS_ERROR_FILE_NOT_FOUND;
11976 // Fallback. If the file does no longer exist, treat
11977 // it as 0-sized.
11978 ErrToDefaultOk<FileUsageType>));
11980 usage += thisUsage;
11982 return Ok{};
11984 // UnknownDirEntryOp
11985 [](nsIFile&, const bool) -> Result<Ok, nsresult> { return Ok{}; }));
11987 return usage;
11990 nsresult DatabaseFileManager::SyncDeleteFile(const int64_t aId) {
11991 MOZ_ASSERT(!mFileInfos.Contains(aId));
11993 if (!this->AssertValid()) {
11994 return NS_ERROR_UNEXPECTED;
11997 const auto directory = GetDirectory();
11998 QM_TRY(OkIf(directory), NS_ERROR_FAILURE);
12000 const auto journalDirectory = GetJournalDirectory();
12001 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE);
12003 const nsCOMPtr<nsIFile> file = GetFileForId(directory, aId);
12004 QM_TRY(OkIf(file), NS_ERROR_FAILURE);
12006 const nsCOMPtr<nsIFile> journalFile = GetFileForId(journalDirectory, aId);
12007 QM_TRY(OkIf(journalFile), NS_ERROR_FAILURE);
12009 return SyncDeleteFile(*file, *journalFile);
12012 nsresult DatabaseFileManager::SyncDeleteFile(nsIFile& aFile,
12013 nsIFile& aJournalFile) const {
12014 QuotaManager* const quotaManager =
12015 EnforcingQuota() ? QuotaManager::Get() : nullptr;
12016 MOZ_ASSERT_IF(EnforcingQuota(), quotaManager);
12018 QM_TRY(MOZ_TO_RESULT(DeleteFile(aFile, quotaManager, Type(), OriginMetadata(),
12019 Idempotency::No)));
12021 QM_TRY(MOZ_TO_RESULT(aJournalFile.Remove(false)));
12023 return NS_OK;
12026 /*******************************************************************************
12027 * QuotaClient
12028 ******************************************************************************/
12030 QuotaClient* QuotaClient::sInstance = nullptr;
12032 QuotaClient::QuotaClient() : mDeleteTimer(NS_NewTimer()) {
12033 AssertIsOnBackgroundThread();
12034 MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
12035 MOZ_ASSERT(!gTelemetryIdMutex);
12037 // Always create this so that later access to gTelemetryIdHashtable can be
12038 // properly synchronized.
12039 gTelemetryIdMutex = new Mutex("IndexedDB gTelemetryIdMutex");
12041 sInstance = this;
12044 QuotaClient::~QuotaClient() {
12045 AssertIsOnBackgroundThread();
12046 MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
12047 MOZ_ASSERT(gTelemetryIdMutex);
12048 MOZ_ASSERT(!mMaintenanceThreadPool);
12050 // No one else should be able to touch gTelemetryIdHashtable now that the
12051 // QuotaClient has gone away.
12052 gTelemetryIdHashtable = nullptr;
12053 gTelemetryIdMutex = nullptr;
12055 sInstance = nullptr;
12058 nsresult QuotaClient::AsyncDeleteFile(DatabaseFileManager* aFileManager,
12059 int64_t aFileId) {
12060 AssertIsOnBackgroundThread();
12062 if (IsShuttingDownOnBackgroundThread()) {
12063 // Whoops! We want to delete an IndexedDB disk-backed File but it's too late
12064 // to actually delete the file! This means we're going to "leak" the file
12065 // and leave it around when we shouldn't! (The file will stay around until
12066 // next storage initialization is triggered when the app is started again).
12067 // Fixing this is tracked by bug 1539377.
12069 return NS_OK;
12072 MOZ_ASSERT(mDeleteTimer);
12073 MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel());
12075 QM_TRY(MOZ_TO_RESULT(mDeleteTimer->InitWithNamedFuncCallback(
12076 DeleteTimerCallback, this, kDeleteTimeoutMs, nsITimer::TYPE_ONE_SHOT,
12077 "dom::indexeddb::QuotaClient::AsyncDeleteFile")));
12079 mPendingDeleteInfos.GetOrInsertNew(aFileManager)->AppendElement(aFileId);
12081 return NS_OK;
12084 nsresult QuotaClient::FlushPendingFileDeletions() {
12085 AssertIsOnBackgroundThread();
12087 QM_TRY(MOZ_TO_RESULT(mDeleteTimer->Cancel()));
12089 DeleteTimerCallback(mDeleteTimer, this);
12091 return NS_OK;
12094 nsThreadPool* QuotaClient::GetOrCreateThreadPool() {
12095 AssertIsOnBackgroundThread();
12096 MOZ_ASSERT(!IsShuttingDownOnBackgroundThread());
12098 if (!mMaintenanceThreadPool) {
12099 RefPtr<nsThreadPool> threadPool = new nsThreadPool();
12101 // PR_GetNumberOfProcessors() can return -1 on error, so make sure we
12102 // don't set some huge number here. We add 2 in case some threads block on
12103 // the disk I/O.
12104 const uint32_t threadCount =
12105 std::max(int32_t(PR_GetNumberOfProcessors()), int32_t(1)) + 2;
12107 MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(threadCount));
12109 // Don't keep more than one idle thread.
12110 MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadLimit(1));
12112 // Don't keep idle threads alive very long.
12113 MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadTimeout(5 * PR_MSEC_PER_SEC));
12115 MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("IndexedDB Mnt"_ns));
12117 mMaintenanceThreadPool = std::move(threadPool);
12120 return mMaintenanceThreadPool;
12123 mozilla::dom::quota::Client::Type QuotaClient::GetType() {
12124 return QuotaClient::IDB;
12127 nsresult QuotaClient::UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) {
12128 AssertIsOnIOThread();
12129 MOZ_ASSERT(aDirectory);
12131 QM_TRY_INSPECT((const auto& [subdirsToProcess, databaseFilenames]),
12132 GetDatabaseFilenames(*aDirectory,
12133 /* aCanceled */ AtomicBool{false}));
12135 QM_TRY(CollectEachInRange(
12136 subdirsToProcess,
12137 [&databaseFilenames = databaseFilenames,
12138 aDirectory](const nsAString& subdirName) -> Result<Ok, nsresult> {
12139 // If the directory has the correct suffix then it should exist in
12140 // databaseFilenames.
12141 nsDependentSubstring subdirNameBase;
12142 if (GetFilenameBase(subdirName, kFileManagerDirectoryNameSuffix,
12143 subdirNameBase)) {
12144 QM_WARNONLY_TRY(OkIf(databaseFilenames.Contains(subdirNameBase)));
12145 return Ok{};
12148 // The directory didn't have the right suffix but we might need to
12149 // rename it. Check to see if we have a database that references this
12150 // directory.
12151 QM_TRY_INSPECT(
12152 const auto& subdirNameWithSuffix,
12153 ([&databaseFilenames,
12154 &subdirName]() -> Result<nsAutoString, NotOk> {
12155 if (databaseFilenames.Contains(subdirName)) {
12156 return nsAutoString{subdirName +
12157 kFileManagerDirectoryNameSuffix};
12160 // Windows doesn't allow a directory to end with a dot ('.'), so
12161 // we have to check that possibility here too. We do this on all
12162 // platforms, because the origin directory may have been created
12163 // on Windows and now accessed on different OS.
12164 const nsAutoString subdirNameWithDot = subdirName + u"."_ns;
12165 QM_TRY(OkIf(databaseFilenames.Contains(subdirNameWithDot)),
12166 Err(NotOk{}));
12168 return nsAutoString{subdirNameWithDot +
12169 kFileManagerDirectoryNameSuffix};
12170 }()),
12171 Ok{});
12173 // We do have a database that uses this subdir so we should rename it
12174 // now.
12175 QM_TRY_INSPECT(const auto& subdir,
12176 CloneFileAndAppend(*aDirectory, subdirName));
12178 DebugOnly<bool> isDirectory;
12179 MOZ_ASSERT(NS_SUCCEEDED(subdir->IsDirectory(&isDirectory)));
12180 MOZ_ASSERT(isDirectory);
12182 // Check if the subdir with suffix already exists before renaming.
12183 QM_TRY_INSPECT(const auto& subdirWithSuffix,
12184 CloneFileAndAppend(*aDirectory, subdirNameWithSuffix));
12186 QM_TRY_INSPECT(const bool& exists,
12187 MOZ_TO_RESULT_INVOKE_MEMBER(subdirWithSuffix, Exists));
12189 if (exists) {
12190 IDB_WARNING("Deleting old %s files directory!",
12191 NS_ConvertUTF16toUTF8(subdirName).get());
12193 QM_TRY(MOZ_TO_RESULT(subdir->Remove(/* aRecursive */ true)));
12195 return Ok{};
12198 // Finally, rename the subdir.
12199 QM_TRY(MOZ_TO_RESULT(subdir->RenameTo(nullptr, subdirNameWithSuffix)));
12201 return Ok{};
12202 }));
12204 return NS_OK;
12207 nsresult QuotaClient::UpgradeStorageFrom2_1To2_2(nsIFile* aDirectory) {
12208 AssertIsOnIOThread();
12209 MOZ_ASSERT(aDirectory);
12211 QM_TRY(CollectEachFile(
12212 *aDirectory, [](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
12213 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
12215 switch (dirEntryKind) {
12216 case nsIFileKind::ExistsAsDirectory:
12217 break;
12219 case nsIFileKind::ExistsAsFile: {
12220 QM_TRY_INSPECT(
12221 const auto& leafName,
12222 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName));
12224 // It's reported that files ending with ".tmp" somehow live in the
12225 // indexedDB directories in Bug 1503883. Such files shouldn't exist
12226 // in the indexedDB directory so remove them in this upgrade.
12227 if (StringEndsWith(leafName, u".tmp"_ns)) {
12228 IDB_WARNING("Deleting unknown temporary file!");
12230 QM_TRY(MOZ_TO_RESULT(file->Remove(false)));
12233 break;
12236 case nsIFileKind::DoesNotExist:
12237 // Ignore files that got removed externally while iterating.
12238 break;
12241 return Ok{};
12242 }));
12244 return NS_OK;
12247 Result<UsageInfo, nsresult> QuotaClient::InitOrigin(
12248 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
12249 const AtomicBool& aCanceled) {
12250 AssertIsOnIOThread();
12252 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(this, GetUsageForOriginInternal,
12253 aPersistenceType, aOriginMetadata,
12254 aCanceled,
12255 /* aInitializing*/ true));
12258 nsresult QuotaClient::InitOriginWithoutTracking(
12259 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
12260 const AtomicBool& aCanceled) {
12261 AssertIsOnIOThread();
12263 return GetUsageForOriginInternal(aPersistenceType, aOriginMetadata, aCanceled,
12264 /* aInitializing*/ true, nullptr);
12267 Result<UsageInfo, nsresult> QuotaClient::GetUsageForOrigin(
12268 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
12269 const AtomicBool& aCanceled) {
12270 AssertIsOnIOThread();
12272 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(this, GetUsageForOriginInternal,
12273 aPersistenceType, aOriginMetadata,
12274 aCanceled,
12275 /* aInitializing*/ false));
12278 nsresult QuotaClient::GetUsageForOriginInternal(
12279 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
12280 const AtomicBool& aCanceled, const bool aInitializing,
12281 UsageInfo* aUsageInfo) {
12282 AssertIsOnIOThread();
12284 QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& directory,
12285 GetDirectory(aPersistenceType, aOriginMetadata.mOrigin));
12287 // We need to see if there are any files in the directory already. If they
12288 // are database files then we need to cleanup stored files (if it's needed)
12289 // and also get the usage.
12291 // XXX Can we avoid unwrapping into non-const variables here? (Only
12292 // databaseFilenames is currently modified below)
12293 QM_TRY_UNWRAP((auto [subdirsToProcess, databaseFilenames, obsoleteFilenames]),
12294 GetDatabaseFilenames<ObsoleteFilenamesHandling::Include>(
12295 *directory, aCanceled));
12297 if (aInitializing) {
12298 QM_TRY(CollectEachInRange(
12299 subdirsToProcess,
12300 [&directory, &obsoleteFilenames = obsoleteFilenames,
12301 &databaseFilenames = databaseFilenames, aPersistenceType,
12302 &aOriginMetadata](
12303 const nsAString& subdirName) -> Result<Ok, nsresult> {
12304 // The directory must have the correct suffix.
12305 nsDependentSubstring subdirNameBase;
12306 QM_TRY(QM_OR_ELSE_WARN(
12307 // Expression.
12308 ([&subdirName, &subdirNameBase] {
12309 QM_TRY_RETURN(OkIf(GetFilenameBase(
12310 subdirName, kFileManagerDirectoryNameSuffix,
12311 subdirNameBase)));
12312 }()),
12313 // Fallback.
12314 ([&directory,
12315 &subdirName](const NotOk) -> Result<Ok, nsresult> {
12316 // If there is an unexpected directory in the idb
12317 // directory, trying to delete at first instead of
12318 // breaking the whole initialization.
12319 QM_TRY(MOZ_TO_RESULT(
12320 DeleteFilesNoQuota(directory, subdirName)),
12321 Err(NS_ERROR_UNEXPECTED));
12323 return Ok{};
12324 })),
12325 Ok{});
12327 if (obsoleteFilenames.Contains(subdirNameBase)) {
12328 // If this fails, it probably means we are in a serious situation.
12329 // e.g. Filesystem corruption. Will handle this in bug 1521541.
12330 QM_TRY(MOZ_TO_RESULT(RemoveDatabaseFilesAndDirectory(
12331 *directory, subdirNameBase, nullptr, aPersistenceType,
12332 aOriginMetadata, u""_ns)),
12333 Err(NS_ERROR_UNEXPECTED));
12335 databaseFilenames.Remove(subdirNameBase);
12336 return Ok{};
12339 // The directory base must exist in databaseFilenames.
12340 // If there is an unexpected directory in the idb directory, trying to
12341 // delete at first instead of breaking the whole initialization.
12343 // XXX This is still somewhat quirky. It would be nice to make it
12344 // clear that the warning handler is infallible, which would also
12345 // remove the need for the error type conversion.
12346 QM_WARNONLY_TRY(QM_OR_ELSE_WARN(
12347 // Expression.
12348 OkIf(databaseFilenames.Contains(subdirNameBase))
12349 .mapErr([](const NotOk) { return NS_ERROR_FAILURE; }),
12350 // Fallback.
12351 ([&directory,
12352 &subdirName](const nsresult) -> Result<Ok, nsresult> {
12353 // XXX It seems if we really got here, we can fail the
12354 // MOZ_ASSERT(!quotaManager->IsTemporaryStorageInitialized());
12355 // assertion in DeleteFilesNoQuota.
12356 QM_TRY(MOZ_TO_RESULT(DeleteFilesNoQuota(directory, subdirName)),
12357 Err(NS_ERROR_UNEXPECTED));
12359 return Ok{};
12360 })));
12362 return Ok{};
12363 }));
12366 for (const auto& databaseFilename : databaseFilenames) {
12367 if (aCanceled) {
12368 break;
12371 QM_TRY_INSPECT(
12372 const auto& fmDirectory,
12373 CloneFileAndAppend(*directory,
12374 databaseFilename + kFileManagerDirectoryNameSuffix));
12376 QM_TRY_INSPECT(
12377 const auto& databaseFile,
12378 CloneFileAndAppend(*directory, databaseFilename + kSQLiteSuffix));
12380 if (aInitializing) {
12381 QM_TRY(MOZ_TO_RESULT(DatabaseFileManager::InitDirectory(
12382 *fmDirectory, *databaseFile, aOriginMetadata.mOrigin,
12383 TelemetryIdForFile(databaseFile))));
12386 if (aUsageInfo) {
12388 QM_TRY_INSPECT(const int64_t& fileSize,
12389 MOZ_TO_RESULT_INVOKE_MEMBER(databaseFile, GetFileSize));
12391 MOZ_ASSERT(fileSize >= 0);
12393 *aUsageInfo += DatabaseUsageType(Some(uint64_t(fileSize)));
12397 QM_TRY_INSPECT(const auto& walFile,
12398 CloneFileAndAppend(*directory,
12399 databaseFilename + kSQLiteWALSuffix));
12401 // QM_OR_ELSE_WARN_IF is not used here since we just want to log
12402 // NS_ERROR_FILE_NOT_FOUND result and not spam the reports (the -wal
12403 // file doesn't have to exist).
12404 QM_TRY_INSPECT(const int64_t& walFileSize,
12405 QM_OR_ELSE_LOG_VERBOSE_IF(
12406 // Expression.
12407 MOZ_TO_RESULT_INVOKE_MEMBER(walFile, GetFileSize),
12408 // Predicate.
12409 ([](const nsresult rv) {
12410 return rv == NS_ERROR_FILE_NOT_FOUND;
12412 // Fallback.
12413 (ErrToOk<0, int64_t>)));
12414 MOZ_ASSERT(walFileSize >= 0);
12415 *aUsageInfo += DatabaseUsageType(Some(uint64_t(walFileSize)));
12419 QM_TRY_INSPECT(const auto& fileUsage,
12420 DatabaseFileManager::GetUsage(fmDirectory));
12422 *aUsageInfo += fileUsage;
12427 return NS_OK;
12430 void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
12431 const nsACString& aOrigin) {
12432 AssertIsOnIOThread();
12434 if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
12435 mgr->InvalidateFileManagers(aPersistenceType, aOrigin);
12439 void QuotaClient::ReleaseIOThreadObjects() {
12440 AssertIsOnIOThread();
12442 if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
12443 mgr->InvalidateAllFileManagers();
12447 void QuotaClient::AbortOperationsForLocks(
12448 const DirectoryLockIdTable& aDirectoryLockIds) {
12449 AssertIsOnBackgroundThread();
12451 InvalidateLiveDatabasesMatching([&aDirectoryLockIds](const auto& database) {
12452 // If the database is registered in gLiveDatabaseHashtable then it must have
12453 // a directory lock.
12454 return IsLockForObjectContainedInLockTable(database, aDirectoryLockIds);
12458 void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) {
12459 AssertIsOnBackgroundThread();
12461 InvalidateLiveDatabasesMatching([&aContentParentId](const auto& database) {
12462 return database.IsOwnedByProcess(aContentParentId);
12466 void QuotaClient::AbortAllOperations() {
12467 AssertIsOnBackgroundThread();
12469 InvalidateLiveDatabasesMatching([](const auto&) { return true; });
12472 void QuotaClient::StartIdleMaintenance() {
12473 AssertIsOnBackgroundThread();
12474 if (IsShuttingDownOnBackgroundThread()) {
12475 MOZ_ASSERT(false, "!IsShuttingDownOnBackgroundThread()");
12476 return;
12479 mBackgroundThread = GetCurrentSerialEventTarget();
12481 mMaintenanceQueue.EmplaceBack(MakeRefPtr<Maintenance>(this));
12482 ProcessMaintenanceQueue();
12485 void QuotaClient::StopIdleMaintenance() {
12486 AssertIsOnBackgroundThread();
12488 if (mCurrentMaintenance) {
12489 mCurrentMaintenance->Abort();
12492 for (const auto& maintenance : mMaintenanceQueue) {
12493 maintenance->Abort();
12497 void QuotaClient::InitiateShutdown() {
12498 AssertIsOnBackgroundThread();
12500 AbortAllOperations();
12503 bool QuotaClient::IsShutdownCompleted() const {
12504 return (!gFactoryOps || gFactoryOps->IsEmpty()) &&
12505 (!gLiveDatabaseHashtable || !gLiveDatabaseHashtable->Count()) &&
12506 !mCurrentMaintenance;
12509 void QuotaClient::ForceKillActors() {
12510 // Currently we don't implement force killing actors.
12513 nsCString QuotaClient::GetShutdownStatus() const {
12514 AssertIsOnBackgroundThread();
12516 nsCString data;
12518 if (gFactoryOps && !gFactoryOps->IsEmpty()) {
12519 data.Append("FactoryOperations: "_ns +
12520 IntToCString(static_cast<uint32_t>(gFactoryOps->Length())) +
12521 " ("_ns);
12523 // XXX It might be confusing to remove duplicates here, as the actual list
12524 // won't match the count then.
12525 nsTHashSet<nsCString> ids;
12526 std::transform(gFactoryOps->cbegin(), gFactoryOps->cend(),
12527 MakeInserter(ids), [](const auto& factoryOp) {
12528 MOZ_ASSERT(factoryOp);
12530 nsCString id;
12531 factoryOp->Stringify(id);
12532 return id;
12535 StringJoinAppend(data, ", "_ns, ids);
12537 data.Append(")\n");
12540 if (gLiveDatabaseHashtable && gLiveDatabaseHashtable->Count()) {
12541 data.Append("LiveDatabases: "_ns +
12542 IntToCString(gLiveDatabaseHashtable->Count()) + " ("_ns);
12544 // XXX What's the purpose of adding these to a hashtable before joining them
12545 // to the string? (Maybe this used to be an ordered container before???)
12546 nsTHashSet<nsCString> ids;
12548 for (const auto& entry : gLiveDatabaseHashtable->Values()) {
12549 MOZ_ASSERT(entry);
12551 std::transform(entry->mLiveDatabases.cbegin(),
12552 entry->mLiveDatabases.cend(), MakeInserter(ids),
12553 [](const auto& database) {
12554 nsCString id;
12555 database->Stringify(id);
12556 return id;
12560 StringJoinAppend(data, ", "_ns, ids);
12562 data.Append(")\n");
12565 if (mCurrentMaintenance) {
12566 data.Append("IdleMaintenance: 1 (");
12567 mCurrentMaintenance->Stringify(data);
12568 data.Append(")\n");
12571 return data;
12574 void QuotaClient::FinalizeShutdown() {
12575 RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
12576 if (connectionPool) {
12577 connectionPool->Shutdown();
12579 gConnectionPool = nullptr;
12582 if (mMaintenanceThreadPool) {
12583 mMaintenanceThreadPool->Shutdown();
12584 mMaintenanceThreadPool = nullptr;
12587 if (mDeleteTimer) {
12588 MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel());
12589 mDeleteTimer = nullptr;
12593 void QuotaClient::DeleteTimerCallback(nsITimer* aTimer, void* aClosure) {
12594 AssertIsOnBackgroundThread();
12595 MOZ_ASSERT(aTimer);
12597 auto* const self = static_cast<QuotaClient*>(aClosure);
12598 MOZ_ASSERT(self);
12599 MOZ_ASSERT(self->mDeleteTimer);
12600 MOZ_ASSERT(SameCOMIdentity(self->mDeleteTimer, aTimer));
12602 for (const auto& pendingDeleteInfoEntry : self->mPendingDeleteInfos) {
12603 const auto& key = pendingDeleteInfoEntry.GetKey();
12604 const auto& value = pendingDeleteInfoEntry.GetData();
12605 MOZ_ASSERT(!value->IsEmpty());
12607 RefPtr<DeleteFilesRunnable> runnable = new DeleteFilesRunnable(
12608 SafeRefPtr{key, AcquireStrongRefFromRawPtr{}}, std::move(*value));
12610 MOZ_ASSERT(value->IsEmpty());
12612 runnable->RunImmediately();
12615 self->mPendingDeleteInfos.Clear();
12618 Result<nsCOMPtr<nsIFile>, nsresult> QuotaClient::GetDirectory(
12619 PersistenceType aPersistenceType, const nsACString& aOrigin) {
12620 QuotaManager* const quotaManager = QuotaManager::Get();
12621 NS_ASSERTION(quotaManager, "This should never fail!");
12623 QM_TRY_INSPECT(const auto& directory, quotaManager->GetDirectoryForOrigin(
12624 aPersistenceType, aOrigin));
12626 MOZ_ASSERT(directory);
12628 QM_TRY(MOZ_TO_RESULT(
12629 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME))));
12631 return directory;
12634 template <QuotaClient::ObsoleteFilenamesHandling ObsoleteFilenames>
12635 Result<QuotaClient::GetDatabaseFilenamesResult<ObsoleteFilenames>, nsresult>
12636 QuotaClient::GetDatabaseFilenames(nsIFile& aDirectory,
12637 const AtomicBool& aCanceled) {
12638 AssertIsOnIOThread();
12640 GetDatabaseFilenamesResult<ObsoleteFilenames> result;
12642 QM_TRY(CollectEachFileAtomicCancelable(
12643 aDirectory, aCanceled,
12644 [&result](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
12645 QM_TRY_INSPECT(const auto& leafName, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
12646 nsString, file, GetLeafName));
12648 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
12650 switch (dirEntryKind) {
12651 case nsIFileKind::ExistsAsDirectory:
12652 result.subdirsToProcess.AppendElement(leafName);
12653 break;
12655 case nsIFileKind::ExistsAsFile: {
12656 if constexpr (ObsoleteFilenames ==
12657 ObsoleteFilenamesHandling::Include) {
12658 if (StringBeginsWith(leafName, kIdbDeletionMarkerFilePrefix)) {
12659 result.obsoleteFilenames.Insert(
12660 Substring(leafName, kIdbDeletionMarkerFilePrefix.Length()));
12661 break;
12665 // Skip OS metadata files. These files are only used in different
12666 // platforms, but the profile can be shared across different
12667 // operating systems, so we check it on all platforms.
12668 if (QuotaManager::IsOSMetadata(leafName)) {
12669 break;
12672 // Skip files starting with ".".
12673 if (QuotaManager::IsDotFile(leafName)) {
12674 break;
12677 // Skip SQLite temporary files. These files take up space on disk
12678 // but will be deleted as soon as the database is opened, so we
12679 // don't count them towards quota.
12680 if (StringEndsWith(leafName, kSQLiteJournalSuffix) ||
12681 StringEndsWith(leafName, kSQLiteSHMSuffix)) {
12682 break;
12685 // The SQLite WAL file does count towards quota, but it is handled
12686 // below once we find the actual database file.
12687 if (StringEndsWith(leafName, kSQLiteWALSuffix)) {
12688 break;
12691 nsDependentSubstring leafNameBase;
12692 if (!GetFilenameBase(leafName, kSQLiteSuffix, leafNameBase)) {
12693 UNKNOWN_FILE_WARNING(leafName);
12694 break;
12697 result.databaseFilenames.Insert(leafNameBase);
12698 break;
12701 case nsIFileKind::DoesNotExist:
12702 // Ignore files that got removed externally while iterating.
12703 break;
12706 return Ok{};
12707 }));
12709 return result;
12712 void QuotaClient::ProcessMaintenanceQueue() {
12713 AssertIsOnBackgroundThread();
12715 if (mCurrentMaintenance || mMaintenanceQueue.IsEmpty()) {
12716 return;
12719 mCurrentMaintenance = mMaintenanceQueue[0];
12720 mMaintenanceQueue.RemoveElementAt(0);
12722 mCurrentMaintenance->RunImmediately();
12725 /*******************************************************************************
12726 * DeleteFilesRunnable
12727 ******************************************************************************/
12729 DeleteFilesRunnable::DeleteFilesRunnable(
12730 SafeRefPtr<DatabaseFileManager> aFileManager, nsTArray<int64_t>&& aFileIds)
12731 : Runnable("dom::indexeddb::DeleteFilesRunnable"),
12732 mOwningEventTarget(GetCurrentSerialEventTarget()),
12733 mFileManager(std::move(aFileManager)),
12734 mFileIds(std::move(aFileIds)),
12735 mState(State_Initial) {}
12737 void DeleteFilesRunnable::RunImmediately() {
12738 AssertIsOnBackgroundThread();
12739 MOZ_ASSERT(mState == State_Initial);
12741 Unused << this->Run();
12744 void DeleteFilesRunnable::Open() {
12745 AssertIsOnBackgroundThread();
12746 MOZ_ASSERT(mState == State_Initial);
12748 QuotaManager* const quotaManager = QuotaManager::Get();
12749 if (NS_WARN_IF(!quotaManager)) {
12750 Finish();
12751 return;
12754 RefPtr<DirectoryLock> directoryLock = quotaManager->CreateDirectoryLock(
12755 mFileManager->Type(), mFileManager->OriginMetadata(), quota::Client::IDB,
12756 /* aExclusive */ false);
12758 mState = State_DirectoryOpenPending;
12759 directoryLock->Acquire(this);
12762 void DeleteFilesRunnable::DoDatabaseWork() {
12763 AssertIsOnIOThread();
12764 MOZ_ASSERT(mState == State_DatabaseWorkOpen);
12766 if (!mFileManager->Invalidated()) {
12767 for (int64_t fileId : mFileIds) {
12768 if (NS_FAILED(mFileManager->SyncDeleteFile(fileId))) {
12769 NS_WARNING("Failed to delete file!");
12774 Finish();
12777 void DeleteFilesRunnable::Finish() {
12778 MOZ_ASSERT(mState != State_UnblockingOpen);
12780 // Must set mState before dispatching otherwise we will race with the main
12781 // thread.
12782 mState = State_UnblockingOpen;
12784 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
12787 void DeleteFilesRunnable::UnblockOpen() {
12788 AssertIsOnBackgroundThread();
12789 MOZ_ASSERT(mState == State_UnblockingOpen);
12791 mDirectoryLock = nullptr;
12793 mState = State_Completed;
12796 NS_IMPL_ISUPPORTS_INHERITED0(DeleteFilesRunnable, Runnable)
12798 NS_IMETHODIMP
12799 DeleteFilesRunnable::Run() {
12800 switch (mState) {
12801 case State_Initial:
12802 Open();
12803 break;
12805 case State_DatabaseWorkOpen:
12806 DoDatabaseWork();
12807 break;
12809 case State_UnblockingOpen:
12810 UnblockOpen();
12811 break;
12813 case State_DirectoryOpenPending:
12814 default:
12815 MOZ_CRASH("Should never get here!");
12818 return NS_OK;
12821 void DeleteFilesRunnable::DirectoryLockAcquired(DirectoryLock* aLock) {
12822 AssertIsOnBackgroundThread();
12823 MOZ_ASSERT(mState == State_DirectoryOpenPending);
12824 MOZ_ASSERT(!mDirectoryLock);
12826 mDirectoryLock = aLock;
12828 QuotaManager* const quotaManager = QuotaManager::Get();
12829 MOZ_ASSERT(quotaManager);
12831 // Must set this before dispatching otherwise we will race with the IO thread
12832 mState = State_DatabaseWorkOpen;
12834 QM_TRY(MOZ_TO_RESULT(
12835 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
12836 QM_VOID, [this](const nsresult) { Finish(); });
12839 void DeleteFilesRunnable::DirectoryLockFailed() {
12840 AssertIsOnBackgroundThread();
12841 MOZ_ASSERT(mState == State_DirectoryOpenPending);
12842 MOZ_ASSERT(!mDirectoryLock);
12844 Finish();
12847 void Maintenance::RegisterDatabaseMaintenance(
12848 DatabaseMaintenance* aDatabaseMaintenance) {
12849 AssertIsOnBackgroundThread();
12850 MOZ_ASSERT(aDatabaseMaintenance);
12851 MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
12852 MOZ_ASSERT(
12853 !mDatabaseMaintenances.Contains(aDatabaseMaintenance->DatabasePath()));
12855 mDatabaseMaintenances.InsertOrUpdate(aDatabaseMaintenance->DatabasePath(),
12856 aDatabaseMaintenance);
12859 void Maintenance::UnregisterDatabaseMaintenance(
12860 DatabaseMaintenance* aDatabaseMaintenance) {
12861 AssertIsOnBackgroundThread();
12862 MOZ_ASSERT(aDatabaseMaintenance);
12863 MOZ_ASSERT(mState == State::WaitingForDatabaseMaintenancesToComplete);
12864 MOZ_ASSERT(mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
12866 mDatabaseMaintenances.Remove(aDatabaseMaintenance->DatabasePath());
12868 if (mDatabaseMaintenances.Count()) {
12869 return;
12872 mState = State::Finishing;
12873 Finish();
12876 void Maintenance::Stringify(nsACString& aResult) const {
12877 AssertIsOnBackgroundThread();
12879 aResult.Append("DatabaseMaintenances: "_ns +
12880 IntToCString(mDatabaseMaintenances.Count()) + " ("_ns);
12882 // XXX It might be confusing to remove duplicates here, as the actual list
12883 // won't match the count then.
12884 nsTHashSet<nsCString> ids;
12885 std::transform(mDatabaseMaintenances.Values().cbegin(),
12886 mDatabaseMaintenances.Values().cend(), MakeInserter(ids),
12887 [](const auto& entry) {
12888 MOZ_ASSERT(entry);
12890 nsCString id;
12891 entry->Stringify(id);
12893 return id;
12896 StringJoinAppend(aResult, ", "_ns, ids);
12898 aResult.Append(")");
12901 nsresult Maintenance::Start() {
12902 AssertIsOnBackgroundThread();
12903 MOZ_ASSERT(mState == State::Initial);
12905 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
12906 IsAborted()) {
12907 return NS_ERROR_ABORT;
12910 // Make sure that the IndexedDatabaseManager is running so that we can check
12911 // for low disk space mode.
12913 if (IndexedDatabaseManager::Get()) {
12914 OpenDirectory();
12915 return NS_OK;
12918 mState = State::CreateIndexedDatabaseManager;
12919 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
12921 return NS_OK;
12924 nsresult Maintenance::CreateIndexedDatabaseManager() {
12925 MOZ_ASSERT(NS_IsMainThread());
12926 MOZ_ASSERT(mState == State::CreateIndexedDatabaseManager);
12928 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
12929 IsAborted()) {
12930 return NS_ERROR_ABORT;
12933 IndexedDatabaseManager* const mgr = IndexedDatabaseManager::GetOrCreate();
12934 if (NS_WARN_IF(!mgr)) {
12935 return NS_ERROR_FAILURE;
12938 mState = State::IndexedDatabaseManagerOpen;
12939 MOZ_ALWAYS_SUCCEEDS(
12940 mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
12942 return NS_OK;
12945 nsresult Maintenance::OpenDirectory() {
12946 AssertIsOnBackgroundThread();
12947 MOZ_ASSERT(mState == State::Initial ||
12948 mState == State::IndexedDatabaseManagerOpen);
12949 MOZ_ASSERT(!mDirectoryLock);
12950 MOZ_ASSERT(QuotaManager::Get());
12952 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
12953 IsAborted()) {
12954 return NS_ERROR_ABORT;
12957 // Get a shared lock for <profile>/storage/*/*/idb
12959 mPendingDirectoryLock = QuotaManager::Get()->CreateDirectoryLockInternal(
12960 Nullable<PersistenceType>(), OriginScope::FromNull(),
12961 Nullable<Client::Type>(Client::IDB),
12962 /* aExclusive */ false);
12964 mState = State::DirectoryOpenPending;
12967 // Pin the directory lock, because Acquire might clear mPendingDirectoryLock
12968 // during the Acquire call.
12969 RefPtr pinnedDirectoryLock = mPendingDirectoryLock;
12970 pinnedDirectoryLock->Acquire(this);
12973 return NS_OK;
12976 nsresult Maintenance::DirectoryOpen() {
12977 AssertIsOnBackgroundThread();
12978 MOZ_ASSERT(mState == State::DirectoryOpenPending);
12979 MOZ_ASSERT(mDirectoryLock);
12981 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
12982 IsAborted()) {
12983 return NS_ERROR_ABORT;
12986 QuotaManager* const quotaManager = QuotaManager::Get();
12987 MOZ_ASSERT(quotaManager);
12989 mState = State::DirectoryWorkOpen;
12991 QM_TRY(MOZ_TO_RESULT(
12992 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
12993 NS_ERROR_FAILURE);
12995 return NS_OK;
12998 nsresult Maintenance::DirectoryWork() {
12999 AssertIsOnIOThread();
13000 MOZ_ASSERT(mState == State::DirectoryWorkOpen);
13002 // The storage directory is structured like this:
13004 // <profile>/storage/<persistence>/<origin>/idb/*.sqlite
13006 // We have to find all database files that match any persistence type and any
13007 // origin. We ignore anything out of the ordinary for now.
13009 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13010 IsAborted()) {
13011 return NS_ERROR_ABORT;
13014 QuotaManager* const quotaManager = QuotaManager::Get();
13015 MOZ_ASSERT(quotaManager);
13017 QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized()));
13019 // Since idle maintenance may occur before temporary storage is initialized,
13020 // make sure it's initialized here (all non-persistent origins need to be
13021 // cleaned up and quota info needs to be loaded for them).
13023 // Don't fail whole idle maintenance in case of an error, the persistent
13024 // repository can still
13025 // be processed.
13026 const bool initTemporaryStorageFailed = [&quotaManager] {
13027 QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized()),
13028 true);
13029 return false;
13030 }();
13032 const nsCOMPtr<nsIFile> storageDir =
13033 GetFileForPath(quotaManager->GetStoragePath());
13034 QM_TRY(OkIf(storageDir), NS_ERROR_FAILURE);
13037 QM_TRY_INSPECT(const bool& exists,
13038 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists));
13040 // XXX No warning here?
13041 if (!exists) {
13042 return NS_ERROR_NOT_AVAILABLE;
13047 QM_TRY_INSPECT(const bool& isDirectory,
13048 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, IsDirectory));
13050 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE);
13053 // There are currently only 3 persistence types, and we want to iterate them
13054 // in this order:
13055 static const PersistenceType kPersistenceTypes[] = {
13056 PERSISTENCE_TYPE_PERSISTENT, PERSISTENCE_TYPE_DEFAULT,
13057 PERSISTENCE_TYPE_TEMPORARY};
13059 static_assert(
13060 ArrayLength(kPersistenceTypes) == size_t(PERSISTENCE_TYPE_INVALID),
13061 "Something changed with available persistence types!");
13063 constexpr auto idbDirName =
13064 NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME);
13066 for (const PersistenceType persistenceType : kPersistenceTypes) {
13067 // Loop over "<persistence>" directories.
13068 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13069 IsAborted()) {
13070 return NS_ERROR_ABORT;
13073 const bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT;
13075 if (!persistent && initTemporaryStorageFailed) {
13076 // Non-persistent (best effort) repositories can't be processed if
13077 // temporary storage initialization failed.
13078 continue;
13081 // XXX persistenceType == PERSISTENCE_TYPE_PERSISTENT shouldn't be a special
13082 // case...
13083 const auto persistenceTypeString =
13084 persistenceType == PERSISTENCE_TYPE_PERSISTENT
13085 ? "permanent"_ns
13086 : PersistenceTypeToString(persistenceType);
13088 QM_TRY_INSPECT(const auto& persistenceDir,
13089 CloneFileAndAppend(*storageDir, NS_ConvertASCIItoUTF16(
13090 persistenceTypeString)));
13093 QM_TRY_INSPECT(const bool& exists,
13094 MOZ_TO_RESULT_INVOKE_MEMBER(persistenceDir, Exists));
13096 if (!exists) {
13097 continue;
13100 QM_TRY_INSPECT(const bool& isDirectory,
13101 MOZ_TO_RESULT_INVOKE_MEMBER(persistenceDir, IsDirectory));
13103 if (NS_WARN_IF(!isDirectory)) {
13104 continue;
13108 // Loop over "<origin>/idb" directories.
13109 QM_TRY(CollectEachFile(
13110 *persistenceDir,
13111 [this, &quotaManager, persistent, persistenceType, &idbDirName](
13112 const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
13113 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13114 IsAborted()) {
13115 return Err(NS_ERROR_ABORT);
13118 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
13120 switch (dirEntryKind) {
13121 case nsIFileKind::ExistsAsFile:
13122 break;
13124 case nsIFileKind::ExistsAsDirectory: {
13125 // Get the necessary information about the origin
13126 // (LoadFullOriginMetadataWithRestore also checks if it's a valid
13127 // origin).
13129 QM_TRY_INSPECT(
13130 const auto& metadata,
13131 quotaManager->LoadFullOriginMetadataWithRestore(originDir),
13132 // Not much we can do here...
13133 Ok{});
13135 // Don't do any maintenance for private browsing databases, which
13136 // are only temporary.
13137 if (OriginAttributes::IsPrivateBrowsing(metadata.mOrigin)) {
13138 return Ok{};
13141 if (persistent) {
13142 // We have to check that all persistent origins are cleaned up,
13143 // but there's no way to do that by one call, we need to
13144 // initialize (and possibly clean up) them one by one
13145 // (EnsureTemporaryStorageIsInitialized cleans up only
13146 // non-persistent origins).
13148 QM_TRY_UNWRAP(
13149 const DebugOnly<bool> created,
13150 quotaManager->EnsurePersistentOriginIsInitialized(metadata)
13151 .map([](const auto& res) { return res.second; }),
13152 // Not much we can do here...
13153 Ok{});
13155 // We found this origin directory by traversing the repository,
13156 // so EnsurePersistentOriginIsInitialized shouldn't report that
13157 // a new directory has been created.
13158 MOZ_ASSERT(!created);
13161 QM_TRY_INSPECT(const auto& idbDir,
13162 CloneFileAndAppend(*originDir, idbDirName));
13164 QM_TRY_INSPECT(const bool& exists,
13165 MOZ_TO_RESULT_INVOKE_MEMBER(idbDir, Exists));
13167 if (!exists) {
13168 return Ok{};
13171 QM_TRY_INSPECT(const bool& isDirectory,
13172 MOZ_TO_RESULT_INVOKE_MEMBER(idbDir, IsDirectory));
13174 QM_TRY(OkIf(isDirectory), Ok{});
13176 nsTArray<nsString> databasePaths;
13178 // Loop over files in the "idb" directory.
13179 QM_TRY(CollectEachFile(
13180 *idbDir,
13181 [this, &databasePaths](const nsCOMPtr<nsIFile>& idbDirFile)
13182 -> Result<Ok, nsresult> {
13183 if (NS_WARN_IF(QuotaClient::
13184 IsShuttingDownOnNonBackgroundThread()) ||
13185 IsAborted()) {
13186 return Err(NS_ERROR_ABORT);
13189 QM_TRY_UNWRAP(auto idbFilePath,
13190 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
13191 nsString, idbDirFile, GetPath));
13193 if (!StringEndsWith(idbFilePath, kSQLiteSuffix)) {
13194 return Ok{};
13197 QM_TRY_INSPECT(const auto& dirEntryKind,
13198 GetDirEntryKind(*idbDirFile));
13200 switch (dirEntryKind) {
13201 case nsIFileKind::ExistsAsDirectory:
13202 break;
13204 case nsIFileKind::ExistsAsFile:
13205 // Found a database.
13207 MOZ_ASSERT(!databasePaths.Contains(idbFilePath));
13209 databasePaths.AppendElement(std::move(idbFilePath));
13210 break;
13212 case nsIFileKind::DoesNotExist:
13213 // Ignore files that got removed externally while
13214 // iterating.
13215 break;
13218 return Ok{};
13219 }));
13221 if (!databasePaths.IsEmpty()) {
13222 mDirectoryInfos.EmplaceBack(persistenceType, metadata,
13223 std::move(databasePaths));
13226 break;
13229 case nsIFileKind::DoesNotExist:
13230 // Ignore files that got removed externally while iterating.
13231 break;
13234 return Ok{};
13235 }));
13238 mState = State::BeginDatabaseMaintenance;
13240 MOZ_ALWAYS_SUCCEEDS(
13241 mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
13243 return NS_OK;
13246 nsresult Maintenance::BeginDatabaseMaintenance() {
13247 AssertIsOnBackgroundThread();
13248 MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
13250 class MOZ_STACK_CLASS Helper final {
13251 public:
13252 static bool IsSafeToRunMaintenance(const nsAString& aDatabasePath) {
13253 if (gFactoryOps) {
13254 for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
13255 CheckedUnsafePtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
13257 if (!existingOp->DatabaseFilePathIsKnown()) {
13258 continue;
13261 if (existingOp->DatabaseFilePath() == aDatabasePath) {
13262 return false;
13267 if (gLiveDatabaseHashtable) {
13268 return std::all_of(
13269 gLiveDatabaseHashtable->Values().cbegin(),
13270 gLiveDatabaseHashtable->Values().cend(),
13271 [&aDatabasePath](const auto& liveDatabasesEntry) {
13272 const auto& liveDatabases = liveDatabasesEntry->mLiveDatabases;
13273 return std::all_of(liveDatabases.cbegin(), liveDatabases.cend(),
13274 [&aDatabasePath](const auto& database) {
13275 return database->FilePath() != aDatabasePath;
13280 return true;
13284 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
13285 IsAborted()) {
13286 return NS_ERROR_ABORT;
13289 RefPtr<nsThreadPool> threadPool;
13291 for (DirectoryInfo& directoryInfo : mDirectoryInfos) {
13292 RefPtr<DirectoryLock> directoryLock;
13294 for (const nsAString& databasePath : *directoryInfo.mDatabasePaths) {
13295 if (Helper::IsSafeToRunMaintenance(databasePath)) {
13296 if (!directoryLock) {
13297 directoryLock = mDirectoryLock->SpecializeForClient(
13298 directoryInfo.mPersistenceType,
13299 *directoryInfo.mFullOriginMetadata, Client::IDB);
13300 MOZ_ASSERT(directoryLock);
13303 // No key needs to be passed here, because we skip encrypted databases
13304 // in DoDirectoryWork as long as they are only used in private browsing
13305 // mode.
13306 const auto databaseMaintenance = MakeRefPtr<DatabaseMaintenance>(
13307 this, directoryLock, directoryInfo.mPersistenceType,
13308 *directoryInfo.mFullOriginMetadata, databasePath, Nothing{});
13310 if (!threadPool) {
13311 threadPool = mQuotaClient->GetOrCreateThreadPool();
13312 MOZ_ASSERT(threadPool);
13315 // Perform database maintenance on a TaskQueue, as database connections
13316 // require a serial event target when being opened in order to allow
13317 // memory pressure notifications to clear caches (bug 1806751).
13318 const auto taskQueue = TaskQueue::Create(
13319 do_AddRef(threadPool), "IndexedDB Database Maintenance");
13321 MOZ_ALWAYS_SUCCEEDS(
13322 taskQueue->Dispatch(databaseMaintenance, NS_DISPATCH_NORMAL));
13324 RegisterDatabaseMaintenance(databaseMaintenance);
13329 mDirectoryInfos.Clear();
13331 mDirectoryLock = nullptr;
13333 if (mDatabaseMaintenances.Count()) {
13334 mState = State::WaitingForDatabaseMaintenancesToComplete;
13335 } else {
13336 mState = State::Finishing;
13337 Finish();
13340 return NS_OK;
13343 void Maintenance::Finish() {
13344 AssertIsOnBackgroundThread();
13345 MOZ_ASSERT(!mDirectoryLock);
13346 MOZ_ASSERT(mState == State::Finishing);
13348 if (NS_FAILED(mResultCode)) {
13349 nsCString errorName;
13350 GetErrorName(mResultCode, errorName);
13352 IDB_WARNING("Maintenance finished with error: %s", errorName.get());
13355 // It can happen that we are only referenced by mCurrentMaintenance which is
13356 // cleared in NoteFinishedMaintenance()
13357 const RefPtr<Maintenance> kungFuDeathGrip = this;
13359 mQuotaClient->NoteFinishedMaintenance(this);
13361 mState = State::Complete;
13364 NS_IMPL_ISUPPORTS_INHERITED0(Maintenance, Runnable)
13366 NS_IMETHODIMP
13367 Maintenance::Run() {
13368 MOZ_ASSERT(mState != State::Complete);
13370 const auto handleError = [this](const nsresult rv) {
13371 if (mState != State::Finishing) {
13372 if (NS_SUCCEEDED(mResultCode)) {
13373 mResultCode = rv;
13376 // Must set mState before dispatching otherwise we will race with the
13377 // owning thread.
13378 mState = State::Finishing;
13380 if (IsOnBackgroundThread()) {
13381 Finish();
13382 } else {
13383 MOZ_ALWAYS_SUCCEEDS(mQuotaClient->BackgroundThread()->Dispatch(
13384 this, NS_DISPATCH_NORMAL));
13389 switch (mState) {
13390 case State::Initial:
13391 QM_TRY(MOZ_TO_RESULT(Start()), NS_OK, handleError);
13392 break;
13394 case State::CreateIndexedDatabaseManager:
13395 QM_TRY(MOZ_TO_RESULT(CreateIndexedDatabaseManager()), NS_OK, handleError);
13396 break;
13398 case State::IndexedDatabaseManagerOpen:
13399 QM_TRY(MOZ_TO_RESULT(OpenDirectory()), NS_OK, handleError);
13400 break;
13402 case State::DirectoryWorkOpen:
13403 QM_TRY(MOZ_TO_RESULT(DirectoryWork()), NS_OK, handleError);
13404 break;
13406 case State::BeginDatabaseMaintenance:
13407 QM_TRY(MOZ_TO_RESULT(BeginDatabaseMaintenance()), NS_OK, handleError);
13408 break;
13410 case State::Finishing:
13411 Finish();
13412 break;
13414 default:
13415 MOZ_CRASH("Bad state!");
13418 return NS_OK;
13421 void Maintenance::DirectoryLockAcquired(DirectoryLock* aLock) {
13422 AssertIsOnBackgroundThread();
13423 MOZ_ASSERT(mState == State::DirectoryOpenPending);
13424 MOZ_ASSERT(!mDirectoryLock);
13426 mDirectoryLock = std::exchange(mPendingDirectoryLock, nullptr);
13428 nsresult rv = DirectoryOpen();
13429 if (NS_WARN_IF(NS_FAILED(rv))) {
13430 if (NS_SUCCEEDED(mResultCode)) {
13431 mResultCode = rv;
13434 mState = State::Finishing;
13435 Finish();
13437 return;
13441 void Maintenance::DirectoryLockFailed() {
13442 AssertIsOnBackgroundThread();
13443 MOZ_ASSERT(mState == State::DirectoryOpenPending);
13444 MOZ_ASSERT(!mDirectoryLock);
13446 mPendingDirectoryLock = nullptr;
13448 if (NS_SUCCEEDED(mResultCode)) {
13449 mResultCode = NS_ERROR_FAILURE;
13452 mState = State::Finishing;
13453 Finish();
13456 void DatabaseMaintenance::Stringify(nsACString& aResult) const {
13457 AssertIsOnBackgroundThread();
13459 aResult.AppendLiteral("Origin:");
13460 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin));
13461 aResult.Append(kQuotaGenericDelimiter);
13463 aResult.AppendLiteral("PersistenceType:");
13464 aResult.Append(PersistenceTypeToString(mPersistenceType));
13465 aResult.Append(kQuotaGenericDelimiter);
13467 aResult.AppendLiteral("Duration:");
13468 aResult.AppendInt((PR_Now() - mMaintenance->StartTime()) / PR_USEC_PER_MSEC);
13471 void DatabaseMaintenance::PerformMaintenanceOnDatabase() {
13472 MOZ_ASSERT(!NS_IsMainThread());
13473 MOZ_ASSERT(!IsOnBackgroundThread());
13474 MOZ_ASSERT(mMaintenance);
13475 MOZ_ASSERT(mMaintenance->StartTime());
13476 MOZ_ASSERT(mDirectoryLock);
13477 MOZ_ASSERT(!mDatabasePath.IsEmpty());
13478 MOZ_ASSERT(!mOriginMetadata.mGroup.IsEmpty());
13479 MOZ_ASSERT(!mOriginMetadata.mOrigin.IsEmpty());
13481 class MOZ_STACK_CLASS AutoClose final {
13482 NotNull<nsCOMPtr<mozIStorageConnection>> mConnection;
13484 public:
13485 explicit AutoClose(
13486 MovingNotNull<nsCOMPtr<mozIStorageConnection>> aConnection)
13487 : mConnection(std::move(aConnection)) {}
13489 ~AutoClose() { MOZ_ALWAYS_SUCCEEDS(mConnection->Close()); }
13492 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13493 mMaintenance->IsAborted()) {
13494 return;
13497 const nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath);
13498 MOZ_ASSERT(databaseFile);
13500 QM_TRY_UNWRAP(
13501 const NotNull<nsCOMPtr<mozIStorageConnection>> connection,
13502 GetStorageConnection(*databaseFile, mDirectoryLockId,
13503 TelemetryIdForFile(databaseFile), mMaybeKey),
13504 QM_VOID);
13506 AutoClose autoClose(connection);
13508 AutoProgressHandler progressHandler(mMaintenance);
13509 if (NS_WARN_IF(NS_FAILED(progressHandler.Register(connection)))) {
13510 return;
13513 bool databaseIsOk;
13514 nsresult rv = CheckIntegrity(*connection, &databaseIsOk);
13515 if (NS_WARN_IF(NS_FAILED(rv))) {
13516 return;
13519 if (NS_WARN_IF(!databaseIsOk)) {
13520 // XXX Handle this somehow! Probably need to clear all storage for the
13521 // origin. Needs followup.
13522 MOZ_ASSERT(false, "Database corruption detected!");
13523 return;
13526 MaintenanceAction maintenanceAction;
13527 rv =
13528 DetermineMaintenanceAction(*connection, databaseFile, &maintenanceAction);
13529 if (NS_WARN_IF(NS_FAILED(rv))) {
13530 return;
13533 switch (maintenanceAction) {
13534 case MaintenanceAction::Nothing:
13535 break;
13537 case MaintenanceAction::IncrementalVacuum:
13538 IncrementalVacuum(*connection);
13539 break;
13541 case MaintenanceAction::FullVacuum:
13542 FullVacuum(*connection, databaseFile);
13543 break;
13545 default:
13546 MOZ_CRASH("Unknown MaintenanceAction!");
13550 nsresult DatabaseMaintenance::CheckIntegrity(mozIStorageConnection& aConnection,
13551 bool* aOk) {
13552 MOZ_ASSERT(!NS_IsMainThread());
13553 MOZ_ASSERT(!IsOnBackgroundThread());
13554 MOZ_ASSERT(aOk);
13556 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13557 mMaintenance->IsAborted()) {
13558 return NS_ERROR_ABORT;
13561 // First do a full integrity_check. Scope statements tightly here because
13562 // later operations require zero live statements.
13564 QM_TRY_INSPECT(const auto& stmt,
13565 CreateAndExecuteSingleStepStatement(
13566 aConnection, "PRAGMA integrity_check(1);"_ns));
13568 QM_TRY_INSPECT(const auto& result, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
13569 nsString, *stmt, GetString, 0));
13571 QM_TRY(OkIf(result.EqualsLiteral("ok")), NS_OK,
13572 [&aOk](const auto) { *aOk = false; });
13575 // Now enable and check for foreign key constraints.
13577 QM_TRY_INSPECT(
13578 const int32_t& foreignKeysWereEnabled,
13579 ([&aConnection]() -> Result<int32_t, nsresult> {
13580 QM_TRY_INSPECT(const auto& stmt,
13581 CreateAndExecuteSingleStepStatement(
13582 aConnection, "PRAGMA foreign_keys;"_ns));
13584 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
13585 }()));
13587 if (!foreignKeysWereEnabled) {
13588 QM_TRY(MOZ_TO_RESULT(
13589 aConnection.ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
13592 QM_TRY_INSPECT(const bool& foreignKeyError,
13593 CreateAndExecuteSingleStepStatement<
13594 SingleStepResult::ReturnNullIfNoResult>(
13595 aConnection, "PRAGMA foreign_key_check;"_ns));
13597 if (!foreignKeysWereEnabled) {
13598 QM_TRY(MOZ_TO_RESULT(
13599 aConnection.ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns)));
13602 if (foreignKeyError) {
13603 *aOk = false;
13604 return NS_OK;
13608 *aOk = true;
13609 return NS_OK;
13612 nsresult DatabaseMaintenance::DetermineMaintenanceAction(
13613 mozIStorageConnection& aConnection, nsIFile* aDatabaseFile,
13614 MaintenanceAction* aMaintenanceAction) {
13615 MOZ_ASSERT(!NS_IsMainThread());
13616 MOZ_ASSERT(!IsOnBackgroundThread());
13617 MOZ_ASSERT(aDatabaseFile);
13618 MOZ_ASSERT(aMaintenanceAction);
13620 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13621 mMaintenance->IsAborted()) {
13622 return NS_ERROR_ABORT;
13625 QM_TRY_INSPECT(const int32_t& schemaVersion,
13626 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
13628 // Don't do anything if the schema version is less than 18; before that
13629 // version no databases had |auto_vacuum == INCREMENTAL| set and we didn't
13630 // track the values needed for the heuristics below.
13631 if (schemaVersion < MakeSchemaVersion(18, 0)) {
13632 *aMaintenanceAction = MaintenanceAction::Nothing;
13633 return NS_OK;
13636 // This method shouldn't make any permanent changes to the database, so make
13637 // sure everything gets rolled back when we leave.
13638 mozStorageTransaction transaction(&aConnection,
13639 /* aCommitOnComplete */ false);
13641 QM_TRY(MOZ_TO_RESULT(transaction.Start()))
13643 // Check to see when we last vacuumed this database.
13644 QM_TRY_INSPECT(const auto& stmt,
13645 CreateAndExecuteSingleStepStatement(
13646 aConnection,
13647 "SELECT last_vacuum_time, last_vacuum_size "
13648 "FROM database;"_ns));
13650 QM_TRY_INSPECT(const PRTime& lastVacuumTime,
13651 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 0));
13653 QM_TRY_INSPECT(const int64_t& lastVacuumSize,
13654 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 1));
13656 NS_ASSERTION(lastVacuumSize > 0,
13657 "Thy last vacuum size shall be greater than zero, less than "
13658 "zero shall thy last vacuum size not be. Zero is right out.");
13660 const PRTime startTime = mMaintenance->StartTime();
13662 // This shouldn't really be possible...
13663 if (NS_WARN_IF(startTime <= lastVacuumTime)) {
13664 *aMaintenanceAction = MaintenanceAction::Nothing;
13665 return NS_OK;
13668 if (startTime - lastVacuumTime < kMinVacuumAge) {
13669 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
13670 return NS_OK;
13673 // It has been more than a week since the database was vacuumed, so gather
13674 // statistics on its usage to see if vacuuming is worthwhile.
13676 // Create a temporary copy of the dbstat table to speed up the queries that
13677 // come later.
13678 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
13679 "CREATE VIRTUAL TABLE __stats__ USING dbstat;"
13680 "CREATE TEMP TABLE __temp_stats__ AS SELECT * FROM __stats__;"_ns)));
13682 { // Calculate the percentage of the database pages that are not in
13683 // contiguous order.
13684 QM_TRY_INSPECT(
13685 const auto& stmt,
13686 CreateAndExecuteSingleStepStatement(
13687 aConnection,
13688 "SELECT SUM(__ts1__.pageno != __ts2__.pageno + 1) * 100.0 / "
13689 "COUNT(*) "
13690 "FROM __temp_stats__ AS __ts1__, __temp_stats__ AS __ts2__ "
13691 "WHERE __ts1__.name = __ts2__.name "
13692 "AND __ts1__.rowid = __ts2__.rowid + 1;"_ns));
13694 QM_TRY_INSPECT(const int32_t& percentUnordered,
13695 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
13697 MOZ_ASSERT(percentUnordered >= 0);
13698 MOZ_ASSERT(percentUnordered <= 100);
13700 if (percentUnordered >= kPercentUnorderedThreshold) {
13701 *aMaintenanceAction = MaintenanceAction::FullVacuum;
13702 return NS_OK;
13706 // Don't try a full vacuum if the file hasn't grown by 10%.
13707 QM_TRY_INSPECT(const int64_t& currentFileSize,
13708 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, GetFileSize));
13710 if (currentFileSize <= lastVacuumSize ||
13711 (((currentFileSize - lastVacuumSize) * 100 / currentFileSize) <
13712 kPercentFileSizeGrowthThreshold)) {
13713 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
13714 return NS_OK;
13717 { // See if there are any free pages that we can reclaim.
13718 QM_TRY_INSPECT(const auto& stmt,
13719 CreateAndExecuteSingleStepStatement(
13720 aConnection, "PRAGMA freelist_count;"_ns));
13722 QM_TRY_INSPECT(const int32_t& freelistCount,
13723 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
13725 MOZ_ASSERT(freelistCount >= 0);
13727 // If we have too many free pages then we should try an incremental
13728 // vacuum. If that causes too much fragmentation then we'll try a full
13729 // vacuum later.
13730 if (freelistCount > kMaxFreelistThreshold) {
13731 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
13732 return NS_OK;
13736 { // Calculate the percentage of unused bytes on pages in the database.
13737 QM_TRY_INSPECT(
13738 const auto& stmt,
13739 CreateAndExecuteSingleStepStatement(
13740 aConnection,
13741 "SELECT SUM(unused) * 100.0 / SUM(pgsize) FROM __temp_stats__;"_ns));
13743 QM_TRY_INSPECT(const int32_t& percentUnused,
13744 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
13746 MOZ_ASSERT(percentUnused >= 0);
13747 MOZ_ASSERT(percentUnused <= 100);
13749 *aMaintenanceAction = percentUnused >= kPercentUnusedThreshold
13750 ? MaintenanceAction::FullVacuum
13751 : MaintenanceAction::IncrementalVacuum;
13754 return NS_OK;
13757 void DatabaseMaintenance::IncrementalVacuum(
13758 mozIStorageConnection& aConnection) {
13759 MOZ_ASSERT(!NS_IsMainThread());
13760 MOZ_ASSERT(!IsOnBackgroundThread());
13762 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13763 mMaintenance->IsAborted()) {
13764 return;
13767 nsresult rv = aConnection.ExecuteSimpleSQL("PRAGMA incremental_vacuum;"_ns);
13768 if (NS_WARN_IF(NS_FAILED(rv))) {
13769 return;
13773 void DatabaseMaintenance::FullVacuum(mozIStorageConnection& aConnection,
13774 nsIFile* aDatabaseFile) {
13775 MOZ_ASSERT(!NS_IsMainThread());
13776 MOZ_ASSERT(!IsOnBackgroundThread());
13777 MOZ_ASSERT(aDatabaseFile);
13779 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13780 mMaintenance->IsAborted()) {
13781 return;
13784 QM_WARNONLY_TRY(([&]() -> Result<Ok, nsresult> {
13785 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("VACUUM;"_ns)));
13787 const PRTime vacuumTime = PR_Now();
13788 MOZ_ASSERT(vacuumTime > 0);
13790 QM_TRY_INSPECT(const int64_t& fileSize,
13791 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, GetFileSize));
13793 MOZ_ASSERT(fileSize > 0);
13795 // The parameter names are not used, parameters are bound by index only
13796 // locally in the same function.
13797 QM_TRY_INSPECT(const auto& stmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
13798 nsCOMPtr<mozIStorageStatement>,
13799 aConnection, CreateStatement,
13800 "UPDATE database "
13801 "SET last_vacuum_time = :time"
13802 ", last_vacuum_size = :size;"_ns));
13804 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByIndex(0, vacuumTime)));
13806 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByIndex(1, fileSize)));
13808 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
13809 return Ok{};
13810 }()));
13813 void DatabaseMaintenance::RunOnOwningThread() {
13814 AssertIsOnBackgroundThread();
13816 mDirectoryLock = nullptr;
13818 if (mCompleteCallback) {
13819 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget()));
13822 mMaintenance->UnregisterDatabaseMaintenance(this);
13825 void DatabaseMaintenance::RunOnConnectionThread() {
13826 MOZ_ASSERT(!NS_IsMainThread());
13827 MOZ_ASSERT(!IsOnBackgroundThread());
13829 PerformMaintenanceOnDatabase();
13831 MOZ_ALWAYS_SUCCEEDS(
13832 mMaintenance->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
13835 NS_IMETHODIMP
13836 DatabaseMaintenance::Run() {
13837 if (IsOnBackgroundThread()) {
13838 RunOnOwningThread();
13839 } else {
13840 RunOnConnectionThread();
13843 return NS_OK;
13846 nsresult DatabaseMaintenance::AutoProgressHandler::Register(
13847 NotNull<mozIStorageConnection*> aConnection) {
13848 MOZ_ASSERT(!NS_IsMainThread());
13849 MOZ_ASSERT(!IsOnBackgroundThread());
13851 // We want to quickly bail out of any operation if the user becomes active, so
13852 // use a small granularity here since database performance isn't critical.
13853 static const int32_t kProgressGranularity = 50;
13855 nsCOMPtr<mozIStorageProgressHandler> oldHandler;
13856 nsresult rv = aConnection->SetProgressHandler(kProgressGranularity, this,
13857 getter_AddRefs(oldHandler));
13858 if (NS_WARN_IF(NS_FAILED(rv))) {
13859 return rv;
13862 MOZ_ASSERT(!oldHandler);
13863 mConnection.init(aConnection);
13865 return NS_OK;
13868 void DatabaseMaintenance::AutoProgressHandler::Unregister() {
13869 MOZ_ASSERT(!NS_IsMainThread());
13870 MOZ_ASSERT(!IsOnBackgroundThread());
13871 MOZ_ASSERT(mConnection);
13873 nsCOMPtr<mozIStorageProgressHandler> oldHandler;
13874 nsresult rv =
13875 mConnection->get()->RemoveProgressHandler(getter_AddRefs(oldHandler));
13876 Unused << NS_WARN_IF(NS_FAILED(rv));
13878 MOZ_ASSERT_IF(NS_SUCCEEDED(rv), oldHandler == this);
13881 NS_IMETHODIMP_(MozExternalRefCountType)
13882 DatabaseMaintenance::AutoProgressHandler::AddRef() {
13883 NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
13885 #ifdef DEBUG
13886 mDEBUGRefCnt++;
13887 #endif
13888 return 2;
13891 NS_IMETHODIMP_(MozExternalRefCountType)
13892 DatabaseMaintenance::AutoProgressHandler::Release() {
13893 NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
13895 #ifdef DEBUG
13896 mDEBUGRefCnt--;
13897 #endif
13898 return 1;
13901 NS_IMPL_QUERY_INTERFACE(DatabaseMaintenance::AutoProgressHandler,
13902 mozIStorageProgressHandler)
13904 NS_IMETHODIMP
13905 DatabaseMaintenance::AutoProgressHandler::OnProgress(
13906 mozIStorageConnection* aConnection, bool* _retval) {
13907 NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
13908 MOZ_ASSERT(*mConnection == aConnection);
13909 MOZ_ASSERT(_retval);
13911 *_retval = QuotaClient::IsShuttingDownOnNonBackgroundThread() ||
13912 mMaintenance->IsAborted();
13914 return NS_OK;
13917 /*******************************************************************************
13918 * Local class implementations
13919 ******************************************************************************/
13921 // static
13922 nsAutoCString DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
13923 const Maybe<SerializedKeyRange>& aOptionalKeyRange,
13924 const nsACString& aKeyColumnName) {
13925 return aOptionalKeyRange.isSome()
13926 ? GetBindingClauseForKeyRange(aOptionalKeyRange.ref(),
13927 aKeyColumnName)
13928 : nsAutoCString{};
13931 // static
13932 nsAutoCString DatabaseOperationBase::GetBindingClauseForKeyRange(
13933 const SerializedKeyRange& aKeyRange, const nsACString& aKeyColumnName) {
13934 MOZ_ASSERT(!IsOnBackgroundThread());
13935 MOZ_ASSERT(!aKeyColumnName.IsEmpty());
13937 constexpr auto andStr = " AND "_ns;
13938 constexpr auto spacecolon = " :"_ns;
13940 nsAutoCString result;
13941 if (aKeyRange.isOnly()) {
13942 // Both keys equal.
13943 result =
13944 andStr + aKeyColumnName + " ="_ns + spacecolon + kStmtParamNameLowerKey;
13945 } else {
13946 if (!aKeyRange.lower().IsUnset()) {
13947 // Lower key is set.
13948 result.Append(andStr + aKeyColumnName);
13949 result.AppendLiteral(" >");
13950 if (!aKeyRange.lowerOpen()) {
13951 result.AppendLiteral("=");
13953 result.Append(spacecolon + kStmtParamNameLowerKey);
13956 if (!aKeyRange.upper().IsUnset()) {
13957 // Upper key is set.
13958 result.Append(andStr + aKeyColumnName);
13959 result.AppendLiteral(" <");
13960 if (!aKeyRange.upperOpen()) {
13961 result.AppendLiteral("=");
13963 result.Append(spacecolon + kStmtParamNameUpperKey);
13967 MOZ_ASSERT(!result.IsEmpty());
13969 return result;
13972 // static
13973 uint64_t DatabaseOperationBase::ReinterpretDoubleAsUInt64(double aDouble) {
13974 // This is a duplicate of the js engine's byte munging in StructuredClone.cpp
13975 return BitwiseCast<uint64_t>(aDouble);
13978 // static
13979 template <typename KeyTransformation>
13980 nsresult DatabaseOperationBase::MaybeBindKeyToStatement(
13981 const Key& aKey, mozIStorageStatement* const aStatement,
13982 const nsACString& aParameterName,
13983 const KeyTransformation& aKeyTransformation) {
13984 MOZ_ASSERT(!IsOnBackgroundThread());
13985 MOZ_ASSERT(aStatement);
13987 if (!aKey.IsUnset()) {
13988 // XXX This case distinction could be avoided if QM_TRY_INSPECT would also
13989 // work with a function not returning a Result<V, E> but simply a V (which
13990 // is const Key& here) and then assuming it is always a success. Or the
13991 // transformation could be changed to return Result<const V&, void> but I
13992 // don't think that Result supports that at the moment.
13993 if constexpr (std::is_reference_v<
13994 std::invoke_result_t<KeyTransformation, Key>>) {
13995 QM_TRY(MOZ_TO_RESULT(aKeyTransformation(aKey).BindToStatement(
13996 aStatement, aParameterName)));
13997 } else {
13998 QM_TRY_INSPECT(const auto& transformedKey, aKeyTransformation(aKey));
13999 QM_TRY(MOZ_TO_RESULT(
14000 transformedKey.BindToStatement(aStatement, aParameterName)));
14004 return NS_OK;
14007 // static
14008 template <typename KeyTransformation>
14009 nsresult DatabaseOperationBase::BindTransformedKeyRangeToStatement(
14010 const SerializedKeyRange& aKeyRange, mozIStorageStatement* const aStatement,
14011 const KeyTransformation& aKeyTransformation) {
14012 MOZ_ASSERT(!IsOnBackgroundThread());
14013 MOZ_ASSERT(aStatement);
14015 QM_TRY(MOZ_TO_RESULT(MaybeBindKeyToStatement(aKeyRange.lower(), aStatement,
14016 kStmtParamNameLowerKey,
14017 aKeyTransformation)));
14019 if (aKeyRange.isOnly()) {
14020 return NS_OK;
14023 QM_TRY(MOZ_TO_RESULT(MaybeBindKeyToStatement(aKeyRange.upper(), aStatement,
14024 kStmtParamNameUpperKey,
14025 aKeyTransformation)));
14027 return NS_OK;
14030 // static
14031 nsresult DatabaseOperationBase::BindKeyRangeToStatement(
14032 const SerializedKeyRange& aKeyRange,
14033 mozIStorageStatement* const aStatement) {
14034 return BindTransformedKeyRangeToStatement(
14035 aKeyRange, aStatement, [](const Key& key) -> const auto& { return key; });
14038 // static
14039 nsresult DatabaseOperationBase::BindKeyRangeToStatement(
14040 const SerializedKeyRange& aKeyRange, mozIStorageStatement* const aStatement,
14041 const nsCString& aLocale) {
14042 MOZ_ASSERT(!aLocale.IsEmpty());
14044 return BindTransformedKeyRangeToStatement(
14045 aKeyRange, aStatement,
14046 [&aLocale](const Key& key) { return key.ToLocaleAwareKey(aLocale); });
14049 // static
14050 void CommonOpenOpHelperBase::AppendConditionClause(
14051 const nsACString& aColumnName, const nsACString& aStatementParameterName,
14052 bool aLessThan, bool aEquals, nsCString& aResult) {
14053 aResult += " AND "_ns + aColumnName + " "_ns;
14055 if (aLessThan) {
14056 aResult.Append('<');
14057 } else {
14058 aResult.Append('>');
14061 if (aEquals) {
14062 aResult.Append('=');
14065 aResult += " :"_ns + aStatementParameterName;
14068 // static
14069 Result<IndexDataValuesAutoArray, nsresult>
14070 DatabaseOperationBase::IndexDataValuesFromUpdateInfos(
14071 const nsTArray<IndexUpdateInfo>& aUpdateInfos,
14072 const UniqueIndexTable& aUniqueIndexTable) {
14073 MOZ_ASSERT_IF(!aUpdateInfos.IsEmpty(), aUniqueIndexTable.Count());
14075 AUTO_PROFILER_LABEL("DatabaseOperationBase::IndexDataValuesFromUpdateInfos",
14076 DOM);
14078 // XXX We could use TransformIntoNewArray here if it allowed to specify that
14079 // an AutoArray should be created.
14080 IndexDataValuesAutoArray indexValues;
14082 if (NS_WARN_IF(!indexValues.SetCapacity(aUpdateInfos.Length(), fallible))) {
14083 IDB_REPORT_INTERNAL_ERR();
14084 return Err(NS_ERROR_OUT_OF_MEMORY);
14087 std::transform(aUpdateInfos.cbegin(), aUpdateInfos.cend(),
14088 MakeBackInserter(indexValues),
14089 [&aUniqueIndexTable](const IndexUpdateInfo& updateInfo) {
14090 const IndexOrObjectStoreId& indexId = updateInfo.indexId();
14092 bool unique = false;
14093 MOZ_ALWAYS_TRUE(aUniqueIndexTable.Get(indexId, &unique));
14095 return IndexDataValue{indexId, unique, updateInfo.value(),
14096 updateInfo.localizedValue()};
14098 indexValues.Sort();
14100 return indexValues;
14103 // static
14104 nsresult DatabaseOperationBase::InsertIndexTableRows(
14105 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId,
14106 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues) {
14107 MOZ_ASSERT(aConnection);
14108 aConnection->AssertIsOnConnectionThread();
14109 MOZ_ASSERT(!aObjectStoreKey.IsUnset());
14111 AUTO_PROFILER_LABEL("DatabaseOperationBase::InsertIndexTableRows", DOM);
14113 const uint32_t count = aIndexValues.Length();
14114 if (!count) {
14115 return NS_OK;
14118 auto insertUniqueStmt = DatabaseConnection::LazyStatement{
14119 *aConnection,
14120 "INSERT INTO unique_index_data "
14121 "(index_id, value, object_store_id, "
14122 "object_data_key, value_locale) "
14123 "VALUES (:"_ns +
14124 kStmtParamNameIndexId + ", :"_ns + kStmtParamNameValue + ", :"_ns +
14125 kStmtParamNameObjectStoreId + ", :"_ns + kStmtParamNameObjectDataKey +
14126 ", :"_ns + kStmtParamNameValueLocale + ");"_ns};
14127 auto insertStmt = DatabaseConnection::LazyStatement{
14128 *aConnection,
14129 "INSERT OR IGNORE INTO index_data "
14130 "(index_id, value, object_data_key, "
14131 "object_store_id, value_locale) "
14132 "VALUES (:"_ns +
14133 kStmtParamNameIndexId + ", :"_ns + kStmtParamNameValue + ", :"_ns +
14134 kStmtParamNameObjectDataKey + ", :"_ns + kStmtParamNameObjectStoreId +
14135 ", :"_ns + kStmtParamNameValueLocale + ");"_ns};
14137 for (uint32_t index = 0; index < count; index++) {
14138 const IndexDataValue& info = aIndexValues[index];
14140 auto& stmt = info.mUnique ? insertUniqueStmt : insertStmt;
14142 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow());
14144 QM_TRY(MOZ_TO_RESULT(
14145 borrowedStmt->BindInt64ByName(kStmtParamNameIndexId, info.mIndexId)));
14146 QM_TRY(MOZ_TO_RESULT(
14147 info.mPosition.BindToStatement(&*borrowedStmt, kStmtParamNameValue)));
14148 QM_TRY(MOZ_TO_RESULT(info.mLocaleAwarePosition.BindToStatement(
14149 &*borrowedStmt, kStmtParamNameValueLocale)));
14150 QM_TRY(MOZ_TO_RESULT(borrowedStmt->BindInt64ByName(
14151 kStmtParamNameObjectStoreId, aObjectStoreId)));
14152 QM_TRY(MOZ_TO_RESULT(aObjectStoreKey.BindToStatement(
14153 &*borrowedStmt, kStmtParamNameObjectDataKey)));
14155 // QM_OR_ELSE_WARN_IF is not used here since we just want to log the
14156 // collision and not spam the reports.
14157 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
14158 // Expression.
14159 MOZ_TO_RESULT(borrowedStmt->Execute()),
14160 // Predicate.
14161 ([&info, index, &aIndexValues](nsresult rv) {
14162 if (rv == NS_ERROR_STORAGE_CONSTRAINT && info.mUnique) {
14163 // If we're inserting multiple entries for the same unique
14164 // index, then we might have failed to insert due to
14165 // colliding with another entry for the same index in which
14166 // case we should ignore it.
14167 for (int32_t index2 = int32_t(index) - 1;
14168 index2 >= 0 && aIndexValues[index2].mIndexId == info.mIndexId;
14169 --index2) {
14170 if (info.mPosition == aIndexValues[index2].mPosition) {
14171 // We found a key with the same value for the same
14172 // index. So we must have had a collision with a value
14173 // we just inserted.
14174 return true;
14179 return false;
14181 // Fallback.
14182 ErrToDefaultOk<>));
14185 return NS_OK;
14188 // static
14189 nsresult DatabaseOperationBase::DeleteIndexDataTableRows(
14190 DatabaseConnection* aConnection, const Key& aObjectStoreKey,
14191 const nsTArray<IndexDataValue>& aIndexValues) {
14192 MOZ_ASSERT(aConnection);
14193 aConnection->AssertIsOnConnectionThread();
14194 MOZ_ASSERT(!aObjectStoreKey.IsUnset());
14196 AUTO_PROFILER_LABEL("DatabaseOperationBase::DeleteIndexDataTableRows", DOM);
14198 const uint32_t count = aIndexValues.Length();
14199 if (!count) {
14200 return NS_OK;
14203 auto deleteUniqueStmt = DatabaseConnection::LazyStatement{
14204 *aConnection, "DELETE FROM unique_index_data WHERE index_id = :"_ns +
14205 kStmtParamNameIndexId + " AND value = :"_ns +
14206 kStmtParamNameValue + ";"_ns};
14207 auto deleteStmt = DatabaseConnection::LazyStatement{
14208 *aConnection, "DELETE FROM index_data WHERE index_id = :"_ns +
14209 kStmtParamNameIndexId + " AND value = :"_ns +
14210 kStmtParamNameValue + " AND object_data_key = :"_ns +
14211 kStmtParamNameObjectDataKey + ";"_ns};
14213 for (uint32_t index = 0; index < count; index++) {
14214 const IndexDataValue& indexValue = aIndexValues[index];
14216 auto& stmt = indexValue.mUnique ? deleteUniqueStmt : deleteStmt;
14218 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow());
14220 QM_TRY(MOZ_TO_RESULT(borrowedStmt->BindInt64ByName(kStmtParamNameIndexId,
14221 indexValue.mIndexId)));
14223 QM_TRY(MOZ_TO_RESULT(indexValue.mPosition.BindToStatement(
14224 &*borrowedStmt, kStmtParamNameValue)));
14226 if (!indexValue.mUnique) {
14227 QM_TRY(MOZ_TO_RESULT(aObjectStoreKey.BindToStatement(
14228 &*borrowedStmt, kStmtParamNameObjectDataKey)));
14231 QM_TRY(MOZ_TO_RESULT(borrowedStmt->Execute()));
14234 return NS_OK;
14237 // static
14238 nsresult DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes(
14239 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId,
14240 const Maybe<SerializedKeyRange>& aKeyRange) {
14241 MOZ_ASSERT(aConnection);
14242 aConnection->AssertIsOnConnectionThread();
14243 MOZ_ASSERT(aObjectStoreId);
14245 #ifdef DEBUG
14247 QM_TRY_INSPECT(const bool& hasIndexes,
14248 ObjectStoreHasIndexes(*aConnection, aObjectStoreId),
14249 QM_PROPAGATE, [](const auto&) { MOZ_ASSERT(false); });
14250 MOZ_ASSERT(hasIndexes,
14251 "Don't use this slow method if there are no indexes!");
14253 #endif
14255 AUTO_PROFILER_LABEL(
14256 "DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes", DOM);
14258 const bool singleRowOnly = aKeyRange.isSome() && aKeyRange.ref().isOnly();
14260 Key objectStoreKey;
14261 QM_TRY_INSPECT(
14262 const auto& selectStmt,
14263 ([singleRowOnly, &aConnection, &objectStoreKey, &aKeyRange]()
14264 -> Result<CachingDatabaseConnection::BorrowedStatement, nsresult> {
14265 if (singleRowOnly) {
14266 QM_TRY_UNWRAP(auto selectStmt,
14267 aConnection->BorrowCachedStatement(
14268 "SELECT index_data_values "
14269 "FROM object_data "
14270 "WHERE object_store_id = :"_ns +
14271 kStmtParamNameObjectStoreId + " AND key = :"_ns +
14272 kStmtParamNameKey + ";"_ns));
14274 objectStoreKey = aKeyRange.ref().lower();
14276 QM_TRY(MOZ_TO_RESULT(
14277 objectStoreKey.BindToStatement(&*selectStmt, kStmtParamNameKey)));
14279 return selectStmt;
14282 const auto keyRangeClause =
14283 MaybeGetBindingClauseForKeyRange(aKeyRange, kColumnNameKey);
14285 QM_TRY_UNWRAP(
14286 auto selectStmt,
14287 aConnection->BorrowCachedStatement(
14288 "SELECT index_data_values, "_ns + kColumnNameKey +
14289 " FROM object_data WHERE object_store_id = :"_ns +
14290 kStmtParamNameObjectStoreId + keyRangeClause + ";"_ns));
14292 if (aKeyRange.isSome()) {
14293 QM_TRY(MOZ_TO_RESULT(
14294 BindKeyRangeToStatement(aKeyRange.ref(), &*selectStmt)));
14297 return selectStmt;
14298 }()));
14300 QM_TRY(MOZ_TO_RESULT(selectStmt->BindInt64ByName(kStmtParamNameObjectStoreId,
14301 aObjectStoreId)));
14303 DebugOnly<uint32_t> resultCountDEBUG = 0;
14305 QM_TRY(CollectWhileHasResult(
14306 *selectStmt,
14307 [singleRowOnly, aObjectStoreId, &objectStoreKey, &aConnection,
14308 &resultCountDEBUG, indexValues = IndexDataValuesAutoArray{},
14309 deleteStmt = DatabaseConnection::LazyStatement{
14310 *aConnection,
14311 "DELETE FROM object_data "
14312 "WHERE object_store_id = :"_ns +
14313 kStmtParamNameObjectStoreId + " AND key = :"_ns +
14314 kStmtParamNameKey +
14315 ";"_ns}](auto& selectStmt) mutable -> Result<Ok, nsresult> {
14316 if (!singleRowOnly) {
14317 QM_TRY(
14318 MOZ_TO_RESULT(objectStoreKey.SetFromStatement(&selectStmt, 1)));
14320 indexValues.ClearAndRetainStorage();
14323 QM_TRY(MOZ_TO_RESULT(
14324 ReadCompressedIndexDataValues(selectStmt, 0, indexValues)));
14325 QM_TRY(MOZ_TO_RESULT(DeleteIndexDataTableRows(
14326 aConnection, objectStoreKey, indexValues)));
14328 QM_TRY_INSPECT(const auto& borrowedDeleteStmt, deleteStmt.Borrow());
14330 QM_TRY(MOZ_TO_RESULT(borrowedDeleteStmt->BindInt64ByName(
14331 kStmtParamNameObjectStoreId, aObjectStoreId)));
14332 QM_TRY(MOZ_TO_RESULT(objectStoreKey.BindToStatement(
14333 &*borrowedDeleteStmt, kStmtParamNameKey)));
14334 QM_TRY(MOZ_TO_RESULT(borrowedDeleteStmt->Execute()));
14336 resultCountDEBUG++;
14338 return Ok{};
14339 }));
14341 MOZ_ASSERT_IF(singleRowOnly, resultCountDEBUG <= 1);
14343 return NS_OK;
14346 // static
14347 nsresult DatabaseOperationBase::UpdateIndexValues(
14348 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId,
14349 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues) {
14350 MOZ_ASSERT(aConnection);
14351 aConnection->AssertIsOnConnectionThread();
14352 MOZ_ASSERT(!aObjectStoreKey.IsUnset());
14354 AUTO_PROFILER_LABEL("DatabaseOperationBase::UpdateIndexValues", DOM);
14356 QM_TRY_UNWRAP((auto [indexDataValues, indexDataValuesLength]),
14357 MakeCompressedIndexDataValues(aIndexValues));
14359 MOZ_ASSERT(!indexDataValuesLength == !(indexDataValues.get()));
14361 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
14362 "UPDATE object_data SET index_data_values = :"_ns +
14363 kStmtParamNameIndexDataValues + " WHERE object_store_id = :"_ns +
14364 kStmtParamNameObjectStoreId + " AND key = :"_ns + kStmtParamNameKey +
14365 ";"_ns,
14366 [&indexDataValues = indexDataValues,
14367 indexDataValuesLength = indexDataValuesLength, aObjectStoreId,
14368 &aObjectStoreKey](
14369 mozIStorageStatement& updateStmt) -> Result<Ok, nsresult> {
14370 QM_TRY(MOZ_TO_RESULT(
14371 indexDataValues
14372 ? updateStmt.BindAdoptedBlobByName(
14373 kStmtParamNameIndexDataValues, indexDataValues.release(),
14374 indexDataValuesLength)
14375 : updateStmt.BindNullByName(kStmtParamNameIndexDataValues)));
14377 QM_TRY(MOZ_TO_RESULT(updateStmt.BindInt64ByName(
14378 kStmtParamNameObjectStoreId, aObjectStoreId)));
14380 QM_TRY(MOZ_TO_RESULT(
14381 aObjectStoreKey.BindToStatement(&updateStmt, kStmtParamNameKey)));
14383 return Ok{};
14384 })));
14386 return NS_OK;
14389 // static
14390 Result<bool, nsresult> DatabaseOperationBase::ObjectStoreHasIndexes(
14391 DatabaseConnection& aConnection,
14392 const IndexOrObjectStoreId aObjectStoreId) {
14393 aConnection.AssertIsOnConnectionThread();
14394 MOZ_ASSERT(aObjectStoreId);
14396 QM_TRY_RETURN(aConnection
14397 .BorrowAndExecuteSingleStepStatement(
14398 "SELECT id "
14399 "FROM object_store_index "
14400 "WHERE object_store_id = :"_ns +
14401 kStmtParamNameObjectStoreId + kOpenLimit + "1;"_ns,
14402 [aObjectStoreId](auto& stmt) -> Result<Ok, nsresult> {
14403 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(
14404 kStmtParamNameObjectStoreId, aObjectStoreId)));
14405 return Ok{};
14407 .map(IsSome));
14410 NS_IMPL_ISUPPORTS_INHERITED(DatabaseOperationBase, Runnable,
14411 mozIStorageProgressHandler)
14413 NS_IMETHODIMP
14414 DatabaseOperationBase::OnProgress(mozIStorageConnection* aConnection,
14415 bool* _retval) {
14416 MOZ_ASSERT(!IsOnBackgroundThread());
14417 MOZ_ASSERT(_retval);
14419 // This is intentionally racy.
14420 *_retval = QuotaClient::IsShuttingDownOnNonBackgroundThread() ||
14421 !OperationMayProceed();
14422 return NS_OK;
14425 DatabaseOperationBase::AutoSetProgressHandler::AutoSetProgressHandler()
14426 : mConnection(Nothing())
14427 #ifdef DEBUG
14429 mDEBUGDatabaseOp(nullptr)
14430 #endif
14432 MOZ_ASSERT(!IsOnBackgroundThread());
14435 DatabaseOperationBase::AutoSetProgressHandler::~AutoSetProgressHandler() {
14436 MOZ_ASSERT(!IsOnBackgroundThread());
14438 if (mConnection) {
14439 Unregister();
14443 nsresult DatabaseOperationBase::AutoSetProgressHandler::Register(
14444 mozIStorageConnection& aConnection, DatabaseOperationBase* aDatabaseOp) {
14445 MOZ_ASSERT(!IsOnBackgroundThread());
14446 MOZ_ASSERT(aDatabaseOp);
14447 MOZ_ASSERT(!mConnection);
14449 QM_TRY_UNWRAP(
14450 const DebugOnly oldProgressHandler,
14451 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
14452 nsCOMPtr<mozIStorageProgressHandler>, aConnection, SetProgressHandler,
14453 kStorageProgressGranularity, aDatabaseOp));
14455 MOZ_ASSERT(!oldProgressHandler.inspect());
14457 mConnection = SomeRef(aConnection);
14458 #ifdef DEBUG
14459 mDEBUGDatabaseOp = aDatabaseOp;
14460 #endif
14462 return NS_OK;
14465 void DatabaseOperationBase::AutoSetProgressHandler::Unregister() {
14466 MOZ_ASSERT(!IsOnBackgroundThread());
14467 MOZ_ASSERT(mConnection);
14469 nsCOMPtr<mozIStorageProgressHandler> oldHandler;
14470 MOZ_ALWAYS_SUCCEEDS(
14471 mConnection->RemoveProgressHandler(getter_AddRefs(oldHandler)));
14472 MOZ_ASSERT(oldHandler == mDEBUGDatabaseOp);
14474 mConnection = Nothing();
14477 FactoryOp::FactoryOp(SafeRefPtr<Factory> aFactory,
14478 RefPtr<ThreadsafeContentParentHandle> aContentHandle,
14479 const CommonFactoryRequestParams& aCommonParams,
14480 bool aDeleting)
14481 : DatabaseOperationBase(aFactory->GetLoggingInfo()->Id(),
14482 aFactory->GetLoggingInfo()->NextRequestSN()),
14483 mFactory(std::move(aFactory)),
14484 mContentHandle(std::move(aContentHandle)),
14485 mCommonParams(aCommonParams),
14486 mDirectoryLockId(-1),
14487 mState(State::Initial),
14488 mWaitingForPermissionRetry(false),
14489 mEnforcingQuota(true),
14490 mDeleting(aDeleting),
14491 mChromeWriteAccessAllowed(false) {
14492 AssertIsOnBackgroundThread();
14493 MOZ_ASSERT(mFactory);
14494 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
14497 void FactoryOp::NoteDatabaseBlocked(Database* aDatabase) {
14498 AssertIsOnOwningThread();
14499 MOZ_ASSERT(aDatabase);
14500 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
14501 MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
14502 MOZ_ASSERT(mMaybeBlockedDatabases.Contains(aDatabase));
14504 // Only send the blocked event if all databases have reported back. If the
14505 // database was closed then it will have been removed from the array.
14506 // Otherwise if it was blocked its |mBlocked| flag will be true.
14507 bool sendBlockedEvent = true;
14509 for (auto& info : mMaybeBlockedDatabases) {
14510 if (info == aDatabase) {
14511 // This database was blocked, mark accordingly.
14512 info.mBlocked = true;
14513 } else if (!info.mBlocked) {
14514 // A database has not yet reported back yet, don't send the event yet.
14515 sendBlockedEvent = false;
14519 if (sendBlockedEvent) {
14520 SendBlockedNotification();
14524 void FactoryOp::NoteDatabaseClosed(Database* const aDatabase) {
14525 AssertIsOnOwningThread();
14526 MOZ_ASSERT(aDatabase);
14527 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
14528 MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
14529 MOZ_ASSERT(mMaybeBlockedDatabases.Contains(aDatabase));
14531 mMaybeBlockedDatabases.RemoveElement(aDatabase);
14533 if (!mMaybeBlockedDatabases.IsEmpty()) {
14534 return;
14537 DatabaseActorInfo* info;
14538 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
14539 MOZ_ASSERT(info->mWaitingFactoryOp == this);
14541 if (AreActorsAlive()) {
14542 // The IPDL strong reference has not yet been released, so we can clear
14543 // mWaitingFactoryOp immediately.
14544 info->mWaitingFactoryOp = nullptr;
14546 WaitForTransactions();
14547 return;
14550 // The IPDL strong reference has been released, mWaitingFactoryOp holds the
14551 // last strong reference to us, so we need to move it to a stack variable
14552 // instead of clearing it immediately (We could clear it immediately if only
14553 // the other actor is destroyed, but we don't need to optimize for that, and
14554 // move it anyway).
14555 const RefPtr<FactoryOp> waitingFactoryOp = std::move(info->mWaitingFactoryOp);
14557 IDB_REPORT_INTERNAL_ERR();
14558 SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
14560 // We hold a strong ref in waitingFactoryOp, so it's safe to call Run()
14561 // directly.
14563 mState = State::SendingResults;
14564 MOZ_ALWAYS_SUCCEEDS(Run());
14567 void FactoryOp::StringifyState(nsACString& aResult) const {
14568 AssertIsOnOwningThread();
14570 switch (mState) {
14571 case State::Initial:
14572 aResult.AppendLiteral("Initial");
14573 return;
14575 case State::FinishOpen:
14576 aResult.AppendLiteral("FinishOpen");
14577 return;
14579 case State::DirectoryOpenPending:
14580 aResult.AppendLiteral("DirectoryOpenPending");
14581 return;
14583 case State::DatabaseOpenPending:
14584 aResult.AppendLiteral("DatabaseOpenPending");
14585 return;
14587 case State::DatabaseWorkOpen:
14588 aResult.AppendLiteral("DatabaseWorkOpen");
14589 return;
14591 case State::BeginVersionChange:
14592 aResult.AppendLiteral("BeginVersionChange");
14593 return;
14595 case State::WaitingForOtherDatabasesToClose:
14596 aResult.AppendLiteral("WaitingForOtherDatabasesToClose");
14597 return;
14599 case State::WaitingForTransactionsToComplete:
14600 aResult.AppendLiteral("WaitingForTransactionsToComplete");
14601 return;
14603 case State::DatabaseWorkVersionChange:
14604 aResult.AppendLiteral("DatabaseWorkVersionChange");
14605 return;
14607 case State::SendingResults:
14608 aResult.AppendLiteral("SendingResults");
14609 return;
14611 case State::Completed:
14612 aResult.AppendLiteral("Completed");
14613 return;
14615 default:
14616 MOZ_CRASH("Bad state!");
14620 void FactoryOp::Stringify(nsACString& aResult) const {
14621 AssertIsOnOwningThread();
14623 aResult.AppendLiteral("PersistenceType:");
14624 aResult.Append(
14625 PersistenceTypeToString(mCommonParams.metadata().persistenceType()));
14626 aResult.Append(kQuotaGenericDelimiter);
14628 aResult.AppendLiteral("Origin:");
14629 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin));
14630 aResult.Append(kQuotaGenericDelimiter);
14632 aResult.AppendLiteral("State:");
14633 StringifyState(aResult);
14636 nsresult FactoryOp::Open() {
14637 AssertIsOnMainThread();
14638 MOZ_ASSERT(mState == State::Initial);
14640 RefPtr<ContentParent> contentParent =
14641 mContentHandle ? mContentHandle->GetContentParent() : nullptr;
14643 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
14644 !OperationMayProceed()) {
14645 IDB_REPORT_INTERNAL_ERR();
14646 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
14649 QM_TRY_INSPECT(const auto& permission, CheckPermission(contentParent));
14651 MOZ_ASSERT(permission == PermissionValue::kPermissionAllowed ||
14652 permission == PermissionValue::kPermissionDenied);
14654 if (permission == PermissionValue::kPermissionDenied) {
14655 return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
14659 // These services have to be started on the main thread currently.
14661 IndexedDatabaseManager* mgr;
14662 if (NS_WARN_IF(!(mgr = IndexedDatabaseManager::GetOrCreate()))) {
14663 IDB_REPORT_INTERNAL_ERR();
14664 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
14667 nsCOMPtr<mozIStorageService> ss;
14668 if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) {
14669 IDB_REPORT_INTERNAL_ERR();
14670 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
14674 const DatabaseMetadata& metadata = mCommonParams.metadata();
14676 QuotaManager::GetStorageId(metadata.persistenceType(),
14677 mOriginMetadata.mOrigin, Client::IDB, mDatabaseId);
14679 mDatabaseId.Append('*');
14680 mDatabaseId.Append(NS_ConvertUTF16toUTF8(metadata.name()));
14682 MOZ_ASSERT(permission == PermissionValue::kPermissionAllowed);
14684 if (mInPrivateBrowsing) {
14685 gIndexedDBCipherKeyManager->Ensure(mDatabaseId);
14688 mState = State::FinishOpen;
14689 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
14691 return NS_OK;
14694 nsresult FactoryOp::DirectoryOpen() {
14695 AssertIsOnOwningThread();
14696 MOZ_ASSERT(mState == State::DirectoryOpenPending);
14697 MOZ_ASSERT(mDirectoryLock);
14698 MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
14699 MOZ_ASSERT(gFactoryOps);
14701 // See if this FactoryOp needs to wait.
14702 const bool delayed =
14703 std::any_of(
14704 gFactoryOps->rbegin(), gFactoryOps->rend(),
14705 [foundThis = false, &self = *this](const auto& existingOp) mutable {
14706 if (existingOp == &self) {
14707 foundThis = true;
14708 return false;
14711 if (foundThis && self.MustWaitFor(*existingOp)) {
14712 // Only one op can be delayed.
14713 MOZ_ASSERT(!existingOp->mDelayedOp);
14714 existingOp->mDelayedOp = &self;
14715 return true;
14718 return false;
14719 }) ||
14720 [&self = *this] {
14721 QuotaClient* quotaClient = QuotaClient::GetInstance();
14722 MOZ_ASSERT(quotaClient);
14724 if (RefPtr<Maintenance> currentMaintenance =
14725 quotaClient->GetCurrentMaintenance()) {
14726 if (RefPtr<DatabaseMaintenance> databaseMaintenance =
14727 currentMaintenance->GetDatabaseMaintenance(
14728 self.mDatabaseFilePath)) {
14729 databaseMaintenance->WaitForCompletion(&self);
14730 return true;
14734 return false;
14735 }();
14737 mState = State::DatabaseOpenPending;
14738 if (!delayed) {
14739 QM_TRY(MOZ_TO_RESULT(DatabaseOpen()));
14742 return NS_OK;
14745 nsresult FactoryOp::SendToIOThread() {
14746 AssertIsOnOwningThread();
14747 MOZ_ASSERT(mState == State::DatabaseOpenPending);
14749 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
14750 !OperationMayProceed()) {
14751 IDB_REPORT_INTERNAL_ERR();
14752 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
14755 QuotaManager* const quotaManager = QuotaManager::Get();
14756 MOZ_ASSERT(quotaManager);
14758 // Must set this before dispatching otherwise we will race with the IO thread.
14759 mState = State::DatabaseWorkOpen;
14761 QM_TRY(MOZ_TO_RESULT(
14762 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
14763 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA);
14765 return NS_OK;
14768 void FactoryOp::WaitForTransactions() {
14769 AssertIsOnOwningThread();
14770 MOZ_ASSERT(mState == State::BeginVersionChange ||
14771 mState == State::WaitingForOtherDatabasesToClose);
14772 MOZ_ASSERT(!mDatabaseId.IsEmpty());
14773 MOZ_ASSERT(!IsActorDestroyed());
14775 mState = State::WaitingForTransactionsToComplete;
14777 RefPtr<WaitForTransactionsHelper> helper =
14778 new WaitForTransactionsHelper(mDatabaseId, this);
14779 helper->WaitForTransactions();
14782 void FactoryOp::CleanupMetadata() {
14783 AssertIsOnOwningThread();
14785 if (mDelayedOp) {
14786 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget()));
14789 MOZ_ASSERT(gFactoryOps);
14790 gFactoryOps->RemoveElement(this);
14792 // We might get here even after QuotaManagerOpen failed, so we need to check
14793 // if we have a quota manager.
14794 quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
14795 quota::Client::IDB, "An element was removed from gFactoryOps"_ns);
14797 // Match the IncreaseBusyCount in AllocPBackgroundIDBFactoryRequestParent().
14798 DecreaseBusyCount();
14801 void FactoryOp::FinishSendResults() {
14802 AssertIsOnOwningThread();
14803 MOZ_ASSERT(mState == State::SendingResults);
14804 MOZ_ASSERT(mFactory);
14806 mState = State::Completed;
14808 // Make sure to release the factory on this thread.
14809 mFactory = nullptr;
14812 Result<PermissionValue, nsresult> FactoryOp::CheckPermission(
14813 ContentParent* aContentParent) {
14814 MOZ_ASSERT(NS_IsMainThread());
14815 MOZ_ASSERT(mState == State::Initial);
14817 const PrincipalInfo& principalInfo = mCommonParams.principalInfo();
14818 if (principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
14819 if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo) {
14820 if (aContentParent) {
14821 // We just want ContentPrincipalInfo or SystemPrincipalInfo.
14822 aContentParent->KillHard("IndexedDB CheckPermission 0");
14825 return Err(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
14828 const ContentPrincipalInfo& contentPrincipalInfo =
14829 principalInfo.get_ContentPrincipalInfo();
14830 if (contentPrincipalInfo.attrs().mPrivateBrowsingId != 0) {
14831 if (StaticPrefs::dom_indexedDB_privateBrowsing_enabled()) {
14832 // XXX Not sure if this should be done from here, it goes beyond
14833 // checking the permissions.
14834 mInPrivateBrowsing.Flip();
14835 } else {
14836 return Err(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
14841 PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
14843 MOZ_ASSERT(principalInfo.type() != PrincipalInfo::TNullPrincipalInfo);
14845 if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
14846 MOZ_ASSERT(mState == State::Initial);
14847 MOZ_ASSERT(persistenceType == PERSISTENCE_TYPE_PERSISTENT);
14849 if (aContentParent) {
14850 // Check to make sure that the child process has access to the database it
14851 // is accessing.
14852 NS_ConvertUTF16toUTF8 databaseName(mCommonParams.metadata().name());
14854 const nsAutoCString permissionStringWrite =
14855 kPermissionStringBase + databaseName + kPermissionWriteSuffix;
14856 const nsAutoCString permissionStringRead =
14857 kPermissionStringBase + databaseName + kPermissionReadSuffix;
14859 bool canWrite = CheckAtLeastOneAppHasPermission(aContentParent,
14860 permissionStringWrite);
14862 bool canRead;
14863 if (canWrite) {
14864 MOZ_ASSERT(CheckAtLeastOneAppHasPermission(aContentParent,
14865 permissionStringRead));
14866 canRead = true;
14867 } else {
14868 canRead = CheckAtLeastOneAppHasPermission(aContentParent,
14869 permissionStringRead);
14872 // Deleting a database requires write permissions.
14873 if (mDeleting && !canWrite) {
14874 aContentParent->KillHard("IndexedDB CheckPermission 2");
14875 IDB_REPORT_INTERNAL_ERR();
14876 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
14879 // Opening or deleting requires read permissions.
14880 if (!canRead) {
14881 aContentParent->KillHard("IndexedDB CheckPermission 3");
14882 IDB_REPORT_INTERNAL_ERR();
14883 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
14886 mChromeWriteAccessAllowed = canWrite;
14887 } else {
14888 mChromeWriteAccessAllowed = true;
14891 if (State::Initial == mState) {
14892 mOriginMetadata = {QuotaManager::GetInfoForChrome(), persistenceType};
14894 MOZ_ASSERT(QuotaManager::IsOriginInternal(mOriginMetadata.mOrigin));
14896 mEnforcingQuota = false;
14899 return PermissionValue::kPermissionAllowed;
14902 MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
14904 QM_TRY_INSPECT(const auto& principal,
14905 PrincipalInfoToPrincipal(principalInfo));
14907 QM_TRY_UNWRAP(auto principalMetadata,
14908 QuotaManager::GetInfoFromPrincipal(principal));
14910 QM_TRY_INSPECT(
14911 const auto& permission,
14912 ([persistenceType, &origin = principalMetadata.mOrigin,
14913 &principal =
14914 *principal]() -> mozilla::Result<PermissionValue, nsresult> {
14915 if (persistenceType == PERSISTENCE_TYPE_PERSISTENT) {
14916 if (QuotaManager::IsOriginInternal(origin)) {
14917 return PermissionValue::kPermissionAllowed;
14919 return Err(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
14921 return PermissionValue::kPermissionAllowed;
14922 })());
14924 if (permission != PermissionValue::kPermissionDenied &&
14925 State::Initial == mState) {
14926 mOriginMetadata = {std::move(principalMetadata), persistenceType};
14928 mEnforcingQuota = persistenceType != PERSISTENCE_TYPE_PERSISTENT;
14931 return permission;
14934 nsresult FactoryOp::SendVersionChangeMessages(
14935 DatabaseActorInfo* aDatabaseActorInfo, Maybe<Database&> aOpeningDatabase,
14936 uint64_t aOldVersion, const Maybe<uint64_t>& aNewVersion) {
14937 AssertIsOnOwningThread();
14938 MOZ_ASSERT(aDatabaseActorInfo);
14939 MOZ_ASSERT(mState == State::BeginVersionChange);
14940 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
14941 MOZ_ASSERT(!IsActorDestroyed());
14943 const uint32_t expectedCount = mDeleting ? 0 : 1;
14944 const uint32_t liveCount = aDatabaseActorInfo->mLiveDatabases.Length();
14945 if (liveCount > expectedCount) {
14946 nsTArray<MaybeBlockedDatabaseInfo> maybeBlockedDatabases;
14947 for (const auto& database : aDatabaseActorInfo->mLiveDatabases) {
14948 if ((!aOpeningDatabase || database.get() != &aOpeningDatabase.ref()) &&
14949 !database->IsClosed() &&
14950 NS_WARN_IF(!maybeBlockedDatabases.AppendElement(
14951 SafeRefPtr{database.get(), AcquireStrongRefFromRawPtr{}},
14952 fallible))) {
14953 return NS_ERROR_OUT_OF_MEMORY;
14957 mMaybeBlockedDatabases = std::move(maybeBlockedDatabases);
14960 // We don't want to wait forever if we were not able to send the
14961 // message.
14962 mMaybeBlockedDatabases.RemoveLastElements(
14963 mMaybeBlockedDatabases.end() -
14964 std::remove_if(mMaybeBlockedDatabases.begin(),
14965 mMaybeBlockedDatabases.end(),
14966 [aOldVersion, &aNewVersion](auto& maybeBlockedDatabase) {
14967 return !maybeBlockedDatabase->SendVersionChange(
14968 aOldVersion, aNewVersion);
14969 }));
14971 return NS_OK;
14972 } // namespace indexedDB
14974 // static
14975 bool FactoryOp::CheckAtLeastOneAppHasPermission(
14976 ContentParent* aContentParent, const nsACString& aPermissionString) {
14977 MOZ_ASSERT(NS_IsMainThread());
14978 MOZ_ASSERT(aContentParent);
14979 MOZ_ASSERT(!aPermissionString.IsEmpty());
14981 return true;
14984 nsresult FactoryOp::FinishOpen() {
14985 AssertIsOnOwningThread();
14986 MOZ_ASSERT(mState == State::FinishOpen);
14987 MOZ_ASSERT(!mOriginMetadata.mOrigin.IsEmpty());
14988 MOZ_ASSERT(!mDirectoryLock);
14990 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
14991 IsActorDestroyed()) {
14992 IDB_REPORT_INTERNAL_ERR();
14993 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
14996 QM_TRY(QuotaManager::EnsureCreated());
14998 const PersistenceType persistenceType =
14999 mCommonParams.metadata().persistenceType();
15001 QuotaManager* const quotaManager = QuotaManager::Get();
15002 MOZ_ASSERT(quotaManager);
15004 // Need to get database file path before opening the directory.
15005 // XXX: For what reason?
15006 QM_TRY_UNWRAP(mDatabaseFilePath,
15007 ([this, quotaManager,
15008 persistenceType]() -> mozilla::Result<nsString, nsresult> {
15009 QM_TRY_INSPECT(const auto& dbFile,
15010 quotaManager->GetDirectoryForOrigin(
15011 persistenceType, mOriginMetadata.mOrigin));
15013 QM_TRY(MOZ_TO_RESULT(dbFile->Append(
15014 NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME))));
15016 QM_TRY(MOZ_TO_RESULT(dbFile->Append(
15017 GetDatabaseFilenameBase(mCommonParams.metadata().name()) +
15018 kSQLiteSuffix)));
15020 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15021 nsString, dbFile, GetPath));
15022 }()));
15024 // Open directory
15025 RefPtr<DirectoryLock> directoryLock = quotaManager->CreateDirectoryLock(
15026 persistenceType, mOriginMetadata, Client::IDB,
15027 /* aExclusive */ false);
15029 mState = State::DirectoryOpenPending;
15031 directoryLock->Acquire(this);
15033 return NS_OK;
15036 bool FactoryOp::MustWaitFor(const FactoryOp& aExistingOp) {
15037 AssertIsOnOwningThread();
15039 // Things for the same persistence type, the same origin and the same
15040 // database must wait.
15041 return aExistingOp.mCommonParams.metadata().persistenceType() ==
15042 mCommonParams.metadata().persistenceType() &&
15043 aExistingOp.mOriginMetadata.mOrigin == mOriginMetadata.mOrigin &&
15044 aExistingOp.mDatabaseId == mDatabaseId;
15047 NS_IMPL_ISUPPORTS_INHERITED0(FactoryOp, DatabaseOperationBase)
15049 // Run() assumes that the caller holds a strong reference to the object that
15050 // can't be cleared while Run() is being executed.
15051 // So if you call Run() directly (as opposed to dispatching to an event queue)
15052 // you need to make sure there's such a reference.
15053 // See bug 1356824 for more details.
15054 NS_IMETHODIMP
15055 FactoryOp::Run() {
15056 const auto handleError = [this](const nsresult rv) {
15057 if (mState != State::SendingResults) {
15058 SetFailureCodeIfUnset(rv);
15060 // Must set mState before dispatching otherwise we will race with the
15061 // owning thread.
15062 mState = State::SendingResults;
15064 if (IsOnOwningThread()) {
15065 SendResults();
15066 } else {
15067 MOZ_ALWAYS_SUCCEEDS(
15068 mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
15073 switch (mState) {
15074 case State::Initial:
15075 QM_WARNONLY_TRY(MOZ_TO_RESULT(Open()), handleError);
15076 break;
15078 case State::FinishOpen:
15079 QM_WARNONLY_TRY(MOZ_TO_RESULT(FinishOpen()), handleError);
15080 break;
15082 case State::DatabaseOpenPending:
15083 QM_WARNONLY_TRY(MOZ_TO_RESULT(DatabaseOpen()), handleError);
15084 break;
15086 case State::DatabaseWorkOpen:
15087 QM_WARNONLY_TRY(MOZ_TO_RESULT(DoDatabaseWork()), handleError);
15088 break;
15090 case State::BeginVersionChange:
15091 QM_WARNONLY_TRY(MOZ_TO_RESULT(BeginVersionChange()), handleError);
15092 break;
15094 case State::WaitingForTransactionsToComplete:
15095 QM_WARNONLY_TRY(MOZ_TO_RESULT(DispatchToWorkThread()), handleError);
15096 break;
15098 case State::SendingResults:
15099 SendResults();
15100 break;
15102 default:
15103 MOZ_CRASH("Bad state!");
15106 return NS_OK;
15109 void FactoryOp::DirectoryLockAcquired(DirectoryLock* aLock) {
15110 AssertIsOnOwningThread();
15111 MOZ_ASSERT(aLock);
15112 MOZ_ASSERT(mState == State::DirectoryOpenPending);
15113 MOZ_ASSERT(!mDirectoryLock);
15115 mDirectoryLock = aLock;
15117 MOZ_ASSERT(mDirectoryLock->Id() >= 0);
15118 mDirectoryLockId = mDirectoryLock->Id();
15120 QM_WARNONLY_TRY(MOZ_TO_RESULT(DirectoryOpen()), [this](const nsresult rv) {
15121 SetFailureCodeIfUnset(rv);
15123 // The caller holds a strong reference to us, no need for a self reference
15124 // before calling Run().
15126 mState = State::SendingResults;
15127 MOZ_ALWAYS_SUCCEEDS(Run());
15131 void FactoryOp::DirectoryLockFailed() {
15132 AssertIsOnOwningThread();
15133 MOZ_ASSERT(mState == State::DirectoryOpenPending);
15134 MOZ_ASSERT(!mDirectoryLock);
15136 if (!HasFailed()) {
15137 IDB_REPORT_INTERNAL_ERR();
15138 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
15141 // The caller holds a strong reference to us, no need for a self reference
15142 // before calling Run().
15144 mState = State::SendingResults;
15145 MOZ_ALWAYS_SUCCEEDS(Run());
15148 void FactoryOp::ActorDestroy(ActorDestroyReason aWhy) {
15149 AssertIsOnBackgroundThread();
15151 NoteActorDestroyed();
15154 OpenDatabaseOp::OpenDatabaseOp(
15155 SafeRefPtr<Factory> aFactory,
15156 RefPtr<ThreadsafeContentParentHandle> aContentHandle,
15157 const CommonFactoryRequestParams& aParams)
15158 : FactoryOp(std::move(aFactory), std::move(aContentHandle), aParams,
15159 /* aDeleting */ false),
15160 mMetadata(MakeSafeRefPtr<FullDatabaseMetadata>(aParams.metadata())),
15161 mRequestedVersion(aParams.metadata().version()),
15162 mVersionChangeOp(nullptr),
15163 mTelemetryId(0) {}
15165 void OpenDatabaseOp::ActorDestroy(ActorDestroyReason aWhy) {
15166 AssertIsOnOwningThread();
15168 FactoryOp::ActorDestroy(aWhy);
15170 if (mVersionChangeOp) {
15171 mVersionChangeOp->NoteActorDestroyed();
15175 nsresult OpenDatabaseOp::DatabaseOpen() {
15176 AssertIsOnOwningThread();
15177 MOZ_ASSERT(mState == State::DatabaseOpenPending);
15179 nsresult rv = SendToIOThread();
15180 if (NS_WARN_IF(NS_FAILED(rv))) {
15181 return rv;
15184 return NS_OK;
15187 nsresult OpenDatabaseOp::DoDatabaseWork() {
15188 AssertIsOnIOThread();
15189 MOZ_ASSERT(mState == State::DatabaseWorkOpen);
15191 AUTO_PROFILER_LABEL("OpenDatabaseOp::DoDatabaseWork", DOM);
15193 QM_TRY(OkIf(!QuotaClient::IsShuttingDownOnNonBackgroundThread()),
15194 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA);
15196 if (!OperationMayProceed()) {
15197 IDB_REPORT_INTERNAL_ERR();
15198 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15201 const nsAString& databaseName = mCommonParams.metadata().name();
15202 const PersistenceType persistenceType =
15203 mCommonParams.metadata().persistenceType();
15205 QuotaManager* const quotaManager = QuotaManager::Get();
15206 MOZ_ASSERT(quotaManager);
15208 QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized()));
15210 QM_TRY_INSPECT(
15211 const auto& dbDirectory,
15212 ([persistenceType, &quotaManager, this]()
15213 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
15214 if (persistenceType == PERSISTENCE_TYPE_PERSISTENT) {
15215 QM_TRY_RETURN(quotaManager->EnsurePersistentOriginIsInitialized(
15216 mOriginMetadata));
15219 QM_TRY(
15220 MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized()));
15221 QM_TRY_RETURN(quotaManager->EnsureTemporaryOriginIsInitialized(
15222 persistenceType, mOriginMetadata));
15224 .map([](const auto& res) { return res.first; })));
15226 QM_TRY(MOZ_TO_RESULT(
15227 dbDirectory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME))));
15230 QM_TRY_INSPECT(const bool& exists,
15231 MOZ_TO_RESULT_INVOKE_MEMBER(dbDirectory, Exists));
15233 if (!exists) {
15234 QM_TRY(MOZ_TO_RESULT(dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)));
15236 #ifdef DEBUG
15237 else {
15238 bool isDirectory;
15239 MOZ_ASSERT(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory)));
15240 MOZ_ASSERT(isDirectory);
15242 #endif
15245 const auto databaseFilenameBase = GetDatabaseFilenameBase(databaseName);
15247 QM_TRY_INSPECT(const auto& markerFile,
15248 CloneFileAndAppend(*dbDirectory, kIdbDeletionMarkerFilePrefix +
15249 databaseFilenameBase));
15251 QM_TRY_INSPECT(const bool& exists,
15252 MOZ_TO_RESULT_INVOKE_MEMBER(markerFile, Exists));
15254 if (exists) {
15255 // Delete the database and directroy since they should be deleted in
15256 // previous operation.
15257 // Note: only update usage to the QuotaManager when mEnforcingQuota == true
15258 QM_TRY(MOZ_TO_RESULT(RemoveDatabaseFilesAndDirectory(
15259 *dbDirectory, databaseFilenameBase,
15260 mEnforcingQuota ? quotaManager : nullptr, persistenceType,
15261 mOriginMetadata, databaseName)));
15264 QM_TRY_INSPECT(
15265 const auto& dbFile,
15266 CloneFileAndAppend(*dbDirectory, databaseFilenameBase + kSQLiteSuffix));
15268 mTelemetryId = TelemetryIdForFile(dbFile);
15270 #ifdef DEBUG
15272 QM_TRY_INSPECT(
15273 const auto& databaseFilePath,
15274 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath));
15276 MOZ_ASSERT(databaseFilePath == mDatabaseFilePath);
15278 #endif
15280 QM_TRY_INSPECT(
15281 const auto& fmDirectory,
15282 CloneFileAndAppend(*dbDirectory, databaseFilenameBase +
15283 kFileManagerDirectoryNameSuffix));
15285 Maybe<const CipherKey> maybeKey =
15286 mInPrivateBrowsing ? gIndexedDBCipherKeyManager->Get(mDatabaseId)
15287 : Nothing();
15289 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());
15291 QM_TRY_UNWRAP(
15292 NotNull<nsCOMPtr<mozIStorageConnection>> connection,
15293 CreateStorageConnection(*dbFile, *fmDirectory, databaseName,
15294 mOriginMetadata.mOrigin, mDirectoryLockId,
15295 mTelemetryId, maybeKey));
15297 AutoSetProgressHandler asph;
15298 QM_TRY(MOZ_TO_RESULT(asph.Register(*connection, this)));
15300 QM_TRY(MOZ_TO_RESULT(LoadDatabaseInformation(*connection)));
15302 MOZ_ASSERT(mMetadata->mNextObjectStoreId > mMetadata->mObjectStores.Count());
15303 MOZ_ASSERT(mMetadata->mNextIndexId > 0);
15305 // See if we need to do a versionchange transaction
15307 // Optional version semantics.
15308 if (!mRequestedVersion) {
15309 // If the requested version was not specified and the database was created,
15310 // treat it as if version 1 were requested.
15311 // Otherwise, treat it as if the current version were requested.
15312 mRequestedVersion = mMetadata->mCommonMetadata.version() == 0
15314 : mMetadata->mCommonMetadata.version();
15317 QM_TRY(OkIf(mMetadata->mCommonMetadata.version() <= mRequestedVersion),
15318 NS_ERROR_DOM_INDEXEDDB_VERSION_ERR);
15320 QM_TRY_UNWRAP(
15321 mFileManager,
15322 ([this, persistenceType, &databaseName, &fmDirectory, &connection]()
15323 -> mozilla::Result<SafeRefPtr<DatabaseFileManager>, nsresult> {
15324 IndexedDatabaseManager* const mgr = IndexedDatabaseManager::Get();
15325 MOZ_ASSERT(mgr);
15327 SafeRefPtr<DatabaseFileManager> fileManager = mgr->GetFileManager(
15328 persistenceType, mOriginMetadata.mOrigin, databaseName);
15330 if (!fileManager) {
15331 fileManager = MakeSafeRefPtr<DatabaseFileManager>(
15332 persistenceType, mOriginMetadata, databaseName, mDatabaseId,
15333 mEnforcingQuota, mInPrivateBrowsing);
15335 QM_TRY(MOZ_TO_RESULT(fileManager->Init(fmDirectory, *connection)));
15337 mgr->AddFileManager(fileManager.clonePtr());
15340 return fileManager;
15341 }()));
15343 // Must close connection before dispatching otherwise we might race with the
15344 // connection thread which needs to open the same database.
15345 asph.Unregister();
15347 MOZ_ALWAYS_SUCCEEDS(connection->Close());
15349 // Must set mState before dispatching otherwise we will race with the owning
15350 // thread.
15351 mState = (mMetadata->mCommonMetadata.version() == mRequestedVersion)
15352 ? State::SendingResults
15353 : State::BeginVersionChange;
15355 QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
15357 return NS_OK;
15360 nsresult OpenDatabaseOp::LoadDatabaseInformation(
15361 mozIStorageConnection& aConnection) {
15362 AssertIsOnIOThread();
15363 MOZ_ASSERT(mMetadata);
15366 // Load version information.
15367 QM_TRY_INSPECT(
15368 const auto& stmt,
15369 CreateAndExecuteSingleStepStatement<
15370 SingleStepResult::ReturnNullIfNoResult>(
15371 aConnection, "SELECT name, origin, version FROM database"_ns));
15373 QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED);
15375 QM_TRY_INSPECT(const auto& databaseName, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15376 nsString, stmt, GetString, 0));
15378 QM_TRY(OkIf(mCommonParams.metadata().name() == databaseName),
15379 NS_ERROR_FILE_CORRUPTED);
15381 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15382 nsCString, stmt, GetUTF8String, 1));
15384 // We can't just compare these strings directly. See bug 1339081 comment 69.
15385 QM_TRY(OkIf(QuotaManager::AreOriginsEqualOnDisk(mOriginMetadata.mOrigin,
15386 origin)),
15387 NS_ERROR_FILE_CORRUPTED);
15389 QM_TRY_INSPECT(const int64_t& version,
15390 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 2));
15392 mMetadata->mCommonMetadata.version() = uint64_t(version);
15395 ObjectStoreTable& objectStores = mMetadata->mObjectStores;
15397 QM_TRY_INSPECT(
15398 const auto& lastObjectStoreId,
15399 ([&aConnection,
15400 &objectStores]() -> mozilla::Result<IndexOrObjectStoreId, nsresult> {
15401 // Load object store names and ids.
15402 QM_TRY_INSPECT(
15403 const auto& stmt,
15404 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15405 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
15406 "SELECT id, auto_increment, name, key_path "
15407 "FROM object_store"_ns));
15409 IndexOrObjectStoreId lastObjectStoreId = 0;
15411 QM_TRY(CollectWhileHasResult(
15412 *stmt,
15413 [&lastObjectStoreId, &objectStores,
15414 usedIds = Maybe<nsTHashSet<uint64_t>>{},
15415 usedNames = Maybe<nsTHashSet<nsString>>{}](
15416 auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
15417 QM_TRY_INSPECT(const IndexOrObjectStoreId& objectStoreId,
15418 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
15420 if (!usedIds) {
15421 usedIds.emplace();
15424 QM_TRY(OkIf(objectStoreId > 0), Err(NS_ERROR_FILE_CORRUPTED));
15425 QM_TRY(OkIf(!usedIds.ref().Contains(objectStoreId)),
15426 Err(NS_ERROR_FILE_CORRUPTED));
15428 QM_TRY(OkIf(usedIds.ref().Insert(objectStoreId, fallible)),
15429 Err(NS_ERROR_OUT_OF_MEMORY));
15431 nsString name;
15432 QM_TRY(MOZ_TO_RESULT(stmt.GetString(2, name)));
15434 if (!usedNames) {
15435 usedNames.emplace();
15438 QM_TRY(OkIf(!usedNames.ref().Contains(name)),
15439 Err(NS_ERROR_FILE_CORRUPTED));
15441 QM_TRY(OkIf(usedNames.ref().Insert(name, fallible)),
15442 Err(NS_ERROR_OUT_OF_MEMORY));
15444 ObjectStoreMetadata commonMetadata;
15445 commonMetadata.id() = objectStoreId;
15446 commonMetadata.name() = std::move(name);
15448 QM_TRY_INSPECT(
15449 const int32_t& columnType,
15450 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetTypeOfIndex, 3));
15452 if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
15453 commonMetadata.keyPath() = KeyPath(0);
15454 } else {
15455 MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_TEXT);
15457 nsString keyPathSerialization;
15458 QM_TRY(MOZ_TO_RESULT(stmt.GetString(3, keyPathSerialization)));
15460 commonMetadata.keyPath() =
15461 KeyPath::DeserializeFromString(keyPathSerialization);
15462 QM_TRY(OkIf(commonMetadata.keyPath().IsValid()),
15463 Err(NS_ERROR_FILE_CORRUPTED));
15466 QM_TRY_INSPECT(const int64_t& nextAutoIncrementId,
15467 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1));
15469 commonMetadata.autoIncrement() = !!nextAutoIncrementId;
15471 QM_TRY(OkIf(objectStores.InsertOrUpdate(
15472 objectStoreId,
15473 MakeSafeRefPtr<FullObjectStoreMetadata>(
15474 std::move(commonMetadata),
15475 FullObjectStoreMetadata::AutoIncrementIds{
15476 nextAutoIncrementId, nextAutoIncrementId}),
15477 fallible)),
15478 Err(NS_ERROR_OUT_OF_MEMORY));
15480 lastObjectStoreId = std::max(lastObjectStoreId, objectStoreId);
15482 return Ok{};
15483 }));
15485 return lastObjectStoreId;
15486 }()));
15488 QM_TRY_INSPECT(
15489 const auto& lastIndexId,
15490 ([&aConnection,
15491 &objectStores]() -> mozilla::Result<IndexOrObjectStoreId, nsresult> {
15492 // Load index information
15493 QM_TRY_INSPECT(
15494 const auto& stmt,
15495 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15496 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
15497 "SELECT "
15498 "id, object_store_id, name, key_path, "
15499 "unique_index, multientry, "
15500 "locale, is_auto_locale "
15501 "FROM object_store_index"_ns));
15503 IndexOrObjectStoreId lastIndexId = 0;
15505 QM_TRY(CollectWhileHasResult(
15506 *stmt,
15507 [&lastIndexId, &objectStores, &aConnection,
15508 usedIds = Maybe<nsTHashSet<uint64_t>>{},
15509 usedNames = Maybe<nsTHashSet<nsString>>{}](
15510 auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
15511 QM_TRY_INSPECT(const IndexOrObjectStoreId& objectStoreId,
15512 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1));
15514 // XXX Why does this return NS_ERROR_OUT_OF_MEMORY if we don't
15515 // know the object store id?
15517 auto objectStoreMetadata = objectStores.Lookup(objectStoreId);
15518 QM_TRY(OkIf(static_cast<bool>(objectStoreMetadata)),
15519 Err(NS_ERROR_OUT_OF_MEMORY));
15521 MOZ_ASSERT((*objectStoreMetadata)->mCommonMetadata.id() ==
15522 objectStoreId);
15524 IndexOrObjectStoreId indexId;
15525 QM_TRY(MOZ_TO_RESULT(stmt.GetInt64(0, &indexId)));
15527 if (!usedIds) {
15528 usedIds.emplace();
15531 QM_TRY(OkIf(indexId > 0), Err(NS_ERROR_FILE_CORRUPTED));
15532 QM_TRY(OkIf(!usedIds.ref().Contains(indexId)),
15533 Err(NS_ERROR_FILE_CORRUPTED));
15535 QM_TRY(OkIf(usedIds.ref().Insert(indexId, fallible)),
15536 Err(NS_ERROR_OUT_OF_MEMORY));
15538 nsString name;
15539 QM_TRY(MOZ_TO_RESULT(stmt.GetString(2, name)));
15541 const nsAutoString hashName =
15542 IntToString(indexId) + u":"_ns + name;
15544 if (!usedNames) {
15545 usedNames.emplace();
15548 QM_TRY(OkIf(!usedNames.ref().Contains(hashName)),
15549 Err(NS_ERROR_FILE_CORRUPTED));
15551 QM_TRY(OkIf(usedNames.ref().Insert(hashName, fallible)),
15552 Err(NS_ERROR_OUT_OF_MEMORY));
15554 auto indexMetadata = MakeSafeRefPtr<FullIndexMetadata>();
15555 indexMetadata->mCommonMetadata.id() = indexId;
15556 indexMetadata->mCommonMetadata.name() = name;
15558 #ifdef DEBUG
15560 int32_t columnType;
15561 nsresult rv = stmt.GetTypeOfIndex(3, &columnType);
15562 MOZ_ASSERT(NS_SUCCEEDED(rv));
15563 MOZ_ASSERT(columnType != mozIStorageStatement::VALUE_TYPE_NULL);
15565 #endif
15567 nsString keyPathSerialization;
15568 QM_TRY(MOZ_TO_RESULT(stmt.GetString(3, keyPathSerialization)));
15570 indexMetadata->mCommonMetadata.keyPath() =
15571 KeyPath::DeserializeFromString(keyPathSerialization);
15572 QM_TRY(OkIf(indexMetadata->mCommonMetadata.keyPath().IsValid()),
15573 Err(NS_ERROR_FILE_CORRUPTED));
15575 int32_t scratch;
15576 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(4, &scratch)));
15578 indexMetadata->mCommonMetadata.unique() = !!scratch;
15580 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(5, &scratch)));
15582 indexMetadata->mCommonMetadata.multiEntry() = !!scratch;
15584 const bool localeAware = !stmt.IsNull(6);
15585 if (localeAware) {
15586 QM_TRY(MOZ_TO_RESULT(stmt.GetUTF8String(
15587 6, indexMetadata->mCommonMetadata.locale())));
15589 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(7, &scratch)));
15591 indexMetadata->mCommonMetadata.autoLocale() = !!scratch;
15593 // Update locale-aware indexes if necessary
15594 const nsCString& indexedLocale =
15595 indexMetadata->mCommonMetadata.locale();
15596 const bool& isAutoLocale =
15597 indexMetadata->mCommonMetadata.autoLocale();
15598 const nsCString& systemLocale =
15599 IndexedDatabaseManager::GetLocale();
15600 if (!systemLocale.IsEmpty() && isAutoLocale &&
15601 !indexedLocale.Equals(systemLocale)) {
15602 QM_TRY(MOZ_TO_RESULT(UpdateLocaleAwareIndex(
15603 aConnection, indexMetadata->mCommonMetadata,
15604 systemLocale)));
15608 QM_TRY(OkIf((*objectStoreMetadata)
15609 ->mIndexes.InsertOrUpdate(
15610 indexId, std::move(indexMetadata), fallible)),
15611 Err(NS_ERROR_OUT_OF_MEMORY));
15613 lastIndexId = std::max(lastIndexId, indexId);
15615 return Ok{};
15616 }));
15618 return lastIndexId;
15619 }()));
15621 QM_TRY(OkIf(lastObjectStoreId != INT64_MAX),
15622 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA);
15623 QM_TRY(OkIf(lastIndexId != INT64_MAX), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
15624 IDB_REPORT_INTERNAL_ERR_LAMBDA);
15626 mMetadata->mNextObjectStoreId = lastObjectStoreId + 1;
15627 mMetadata->mNextIndexId = lastIndexId + 1;
15629 return NS_OK;
15632 /* static */
15633 nsresult OpenDatabaseOp::UpdateLocaleAwareIndex(
15634 mozIStorageConnection& aConnection, const IndexMetadata& aIndexMetadata,
15635 const nsCString& aLocale) {
15636 const auto indexTable =
15637 aIndexMetadata.unique() ? "unique_index_data"_ns : "index_data"_ns;
15639 // The parameter names are not used, parameters are bound by index only
15640 // locally in the same function.
15641 const nsCString readQuery = "SELECT value, object_data_key FROM "_ns +
15642 indexTable + " WHERE index_id = :index_id"_ns;
15644 QM_TRY_INSPECT(const auto& readStmt,
15645 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15646 nsCOMPtr<mozIStorageStatement>, aConnection,
15647 CreateStatement, readQuery));
15649 QM_TRY(MOZ_TO_RESULT(readStmt->BindInt64ByIndex(0, aIndexMetadata.id())));
15651 QM_TRY(CollectWhileHasResult(
15652 *readStmt,
15653 [&aConnection, &indexTable, &aIndexMetadata, &aLocale,
15654 writeStmt = nsCOMPtr<mozIStorageStatement>{}](
15655 auto& readStmt) mutable -> mozilla::Result<Ok, nsresult> {
15656 if (!writeStmt) {
15657 QM_TRY_UNWRAP(
15658 writeStmt,
15659 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15660 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
15661 "UPDATE "_ns + indexTable + "SET value_locale = :"_ns +
15662 kStmtParamNameValueLocale + " WHERE index_id = :"_ns +
15663 kStmtParamNameIndexId + " AND value = :"_ns +
15664 kStmtParamNameValue + " AND object_data_key = :"_ns +
15665 kStmtParamNameObjectDataKey));
15668 mozStorageStatementScoper scoper(writeStmt);
15669 QM_TRY(MOZ_TO_RESULT(writeStmt->BindInt64ByName(kStmtParamNameIndexId,
15670 aIndexMetadata.id())));
15672 Key oldKey, objectStorePosition;
15673 QM_TRY(MOZ_TO_RESULT(oldKey.SetFromStatement(&readStmt, 0)));
15674 QM_TRY(MOZ_TO_RESULT(
15675 oldKey.BindToStatement(writeStmt, kStmtParamNameValue)));
15677 QM_TRY_INSPECT(const auto& newSortKey,
15678 oldKey.ToLocaleAwareKey(aLocale));
15680 QM_TRY(MOZ_TO_RESULT(
15681 newSortKey.BindToStatement(writeStmt, kStmtParamNameValueLocale)));
15682 QM_TRY(
15683 MOZ_TO_RESULT(objectStorePosition.SetFromStatement(&readStmt, 1)));
15684 QM_TRY(MOZ_TO_RESULT(objectStorePosition.BindToStatement(
15685 writeStmt, kStmtParamNameObjectDataKey)));
15687 QM_TRY(MOZ_TO_RESULT(writeStmt->Execute()));
15689 return Ok{};
15690 }));
15692 // The parameter names are not used, parameters are bound by index only
15693 // locally in the same function.
15694 static constexpr auto metaQuery =
15695 "UPDATE object_store_index SET "
15696 "locale = :locale WHERE id = :id"_ns;
15698 QM_TRY_INSPECT(const auto& metaStmt,
15699 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15700 nsCOMPtr<mozIStorageStatement>, aConnection,
15701 CreateStatement, metaQuery));
15703 QM_TRY(MOZ_TO_RESULT(
15704 metaStmt->BindStringByIndex(0, NS_ConvertASCIItoUTF16(aLocale))));
15706 QM_TRY(MOZ_TO_RESULT(metaStmt->BindInt64ByIndex(1, aIndexMetadata.id())));
15708 QM_TRY(MOZ_TO_RESULT(metaStmt->Execute()));
15710 return NS_OK;
15713 nsresult OpenDatabaseOp::BeginVersionChange() {
15714 AssertIsOnOwningThread();
15715 MOZ_ASSERT(mState == State::BeginVersionChange);
15716 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
15717 MOZ_ASSERT(mMetadata->mCommonMetadata.version() <= mRequestedVersion);
15718 MOZ_ASSERT(!mDatabase);
15719 MOZ_ASSERT(!mVersionChangeTransaction);
15721 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
15722 IsActorDestroyed()) {
15723 IDB_REPORT_INTERNAL_ERR();
15724 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR));
15727 EnsureDatabaseActor();
15729 if (mDatabase->IsInvalidated()) {
15730 IDB_REPORT_INTERNAL_ERR();
15731 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR));
15734 MOZ_ASSERT(!mDatabase->IsClosed());
15736 DatabaseActorInfo* info;
15737 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
15739 MOZ_ASSERT(info->mLiveDatabases.Contains(mDatabase.unsafeGetRawPtr()));
15740 MOZ_ASSERT(!info->mWaitingFactoryOp);
15741 MOZ_ASSERT(info->mMetadata == mMetadata);
15743 auto transaction = MakeSafeRefPtr<VersionChangeTransaction>(this);
15745 if (NS_WARN_IF(!transaction->CopyDatabaseMetadata())) {
15746 return NS_ERROR_OUT_OF_MEMORY;
15749 MOZ_ASSERT(info->mMetadata != mMetadata);
15750 mMetadata = info->mMetadata.clonePtr();
15752 const Maybe<uint64_t> newVersion = Some(mRequestedVersion);
15754 QM_TRY(MOZ_TO_RESULT(SendVersionChangeMessages(
15755 info, mDatabase.maybeDeref(), mMetadata->mCommonMetadata.version(),
15756 newVersion)));
15758 mVersionChangeTransaction = std::move(transaction);
15760 if (mMaybeBlockedDatabases.IsEmpty()) {
15761 // We don't need to wait on any databases, just jump to the transaction
15762 // pool.
15763 WaitForTransactions();
15764 return NS_OK;
15767 // If the actor gets destroyed, mWaitingFactoryOp will hold the last strong
15768 // reference to us.
15769 info->mWaitingFactoryOp = this;
15771 mState = State::WaitingForOtherDatabasesToClose;
15772 return NS_OK;
15775 bool OpenDatabaseOp::AreActorsAlive() {
15776 AssertIsOnOwningThread();
15777 MOZ_ASSERT(mDatabase);
15779 return !(IsActorDestroyed() || mDatabase->IsActorDestroyed());
15782 void OpenDatabaseOp::SendBlockedNotification() {
15783 AssertIsOnOwningThread();
15784 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
15786 if (!IsActorDestroyed()) {
15787 Unused << SendBlocked(mMetadata->mCommonMetadata.version());
15791 nsresult OpenDatabaseOp::DispatchToWorkThread() {
15792 AssertIsOnOwningThread();
15793 MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
15794 MOZ_ASSERT(mVersionChangeTransaction);
15795 MOZ_ASSERT(mVersionChangeTransaction->GetMode() ==
15796 IDBTransaction::Mode::VersionChange);
15797 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
15799 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
15800 IsActorDestroyed() || mDatabase->IsInvalidated()) {
15801 IDB_REPORT_INTERNAL_ERR();
15802 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15805 mState = State::DatabaseWorkVersionChange;
15807 // Intentionally empty.
15808 nsTArray<nsString> objectStoreNames;
15810 const int64_t loggingSerialNumber =
15811 mVersionChangeTransaction->LoggingSerialNumber();
15812 const nsID& backgroundChildLoggingId =
15813 mVersionChangeTransaction->GetLoggingInfo()->Id();
15815 if (NS_WARN_IF(!mDatabase->RegisterTransaction(*mVersionChangeTransaction))) {
15816 return NS_ERROR_OUT_OF_MEMORY;
15819 if (!gConnectionPool) {
15820 gConnectionPool = new ConnectionPool();
15823 RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
15825 uint64_t transactionId = versionChangeOp->StartOnConnectionPool(
15826 backgroundChildLoggingId, mVersionChangeTransaction->DatabaseId(),
15827 loggingSerialNumber, objectStoreNames,
15828 /* aIsWriteTransaction */ true);
15830 mVersionChangeOp = versionChangeOp;
15832 mVersionChangeTransaction->NoteActiveRequest();
15833 mVersionChangeTransaction->Init(transactionId);
15835 return NS_OK;
15838 nsresult OpenDatabaseOp::SendUpgradeNeeded() {
15839 AssertIsOnOwningThread();
15840 MOZ_ASSERT(mState == State::DatabaseWorkVersionChange);
15841 MOZ_ASSERT(mVersionChangeTransaction);
15842 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
15843 MOZ_ASSERT(!HasFailed());
15844 MOZ_ASSERT_IF(!IsActorDestroyed(), mDatabase);
15846 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
15847 IsActorDestroyed()) {
15848 IDB_REPORT_INTERNAL_ERR();
15849 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15852 const SafeRefPtr<VersionChangeTransaction> transaction =
15853 std::move(mVersionChangeTransaction);
15855 nsresult rv = EnsureDatabaseActorIsAlive();
15856 if (NS_WARN_IF(NS_FAILED(rv))) {
15857 return rv;
15860 // Transfer ownership to IPDL.
15861 transaction->SetActorAlive();
15863 if (!mDatabase->SendPBackgroundIDBVersionChangeTransactionConstructor(
15864 transaction.unsafeGetRawPtr(), mMetadata->mCommonMetadata.version(),
15865 mRequestedVersion, mMetadata->mNextObjectStoreId,
15866 mMetadata->mNextIndexId)) {
15867 IDB_REPORT_INTERNAL_ERR();
15868 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15871 return NS_OK;
15874 void OpenDatabaseOp::SendResults() {
15875 AssertIsOnOwningThread();
15876 MOZ_ASSERT(mState == State::SendingResults);
15877 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
15878 MOZ_ASSERT_IF(!HasFailed(), !mVersionChangeTransaction);
15880 DebugOnly<DatabaseActorInfo*> info = nullptr;
15881 MOZ_ASSERT_IF(
15882 gLiveDatabaseHashtable && gLiveDatabaseHashtable->Get(mDatabaseId, &info),
15883 !info->mWaitingFactoryOp);
15885 if (mVersionChangeTransaction) {
15886 MOZ_ASSERT(HasFailed());
15888 mVersionChangeTransaction->Abort(ResultCode(), /* aForce */ true);
15889 mVersionChangeTransaction = nullptr;
15892 if (IsActorDestroyed()) {
15893 SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
15894 } else {
15895 FactoryRequestResponse response;
15897 if (!HasFailed()) {
15898 // If we just successfully completed a versionchange operation then we
15899 // need to update the version in our metadata.
15900 mMetadata->mCommonMetadata.version() = mRequestedVersion;
15902 nsresult rv = EnsureDatabaseActorIsAlive();
15903 if (NS_SUCCEEDED(rv)) {
15904 // We successfully opened a database so use its actor as the success
15905 // result for this request.
15907 // XXX OpenDatabaseRequestResponse stores a raw pointer, can this be
15908 // avoided?
15909 response =
15910 OpenDatabaseRequestResponse{mDatabase.unsafeGetRawPtr(), nullptr};
15911 } else {
15912 response = ClampResultCode(rv);
15913 #ifdef DEBUG
15914 SetFailureCode(response.get_nsresult());
15915 #endif
15917 } else {
15918 #ifdef DEBUG
15919 // If something failed then our metadata pointer is now bad. No one should
15920 // ever touch it again though so just null it out in DEBUG builds to make
15921 // sure we find such cases.
15922 mMetadata = nullptr;
15923 #endif
15924 response = ClampResultCode(ResultCode());
15927 Unused << PBackgroundIDBFactoryRequestParent::Send__delete__(this,
15928 response);
15931 if (mDatabase) {
15932 MOZ_ASSERT(!mDirectoryLock);
15934 if (HasFailed()) {
15935 mDatabase->Invalidate();
15938 // Make sure to release the database on this thread.
15939 mDatabase = nullptr;
15941 CleanupMetadata();
15942 } else if (mDirectoryLock) {
15943 // ConnectionClosedCallback will call CleanupMetadata().
15944 nsCOMPtr<nsIRunnable> callback = NewRunnableMethod(
15945 "dom::indexedDB::OpenDatabaseOp::ConnectionClosedCallback", this,
15946 &OpenDatabaseOp::ConnectionClosedCallback);
15948 RefPtr<WaitForTransactionsHelper> helper =
15949 new WaitForTransactionsHelper(mDatabaseId, callback);
15950 helper->WaitForTransactions();
15951 } else {
15952 CleanupMetadata();
15955 FinishSendResults();
15958 void OpenDatabaseOp::ConnectionClosedCallback() {
15959 AssertIsOnOwningThread();
15960 MOZ_ASSERT(HasFailed());
15961 MOZ_ASSERT(mDirectoryLock);
15963 mDirectoryLock = nullptr;
15965 CleanupMetadata();
15968 void OpenDatabaseOp::EnsureDatabaseActor() {
15969 AssertIsOnOwningThread();
15970 MOZ_ASSERT(mState == State::BeginVersionChange ||
15971 mState == State::DatabaseWorkVersionChange ||
15972 mState == State::SendingResults);
15973 MOZ_ASSERT(!HasFailed());
15974 MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
15975 MOZ_ASSERT(!IsActorDestroyed());
15977 if (mDatabase) {
15978 return;
15981 MOZ_ASSERT(mMetadata->mDatabaseId.IsEmpty());
15982 mMetadata->mDatabaseId = mDatabaseId;
15984 MOZ_ASSERT(mMetadata->mFilePath.IsEmpty());
15985 mMetadata->mFilePath = mDatabaseFilePath;
15987 DatabaseActorInfo* info;
15988 if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) {
15989 AssertMetadataConsistency(*info->mMetadata);
15990 mMetadata = info->mMetadata.clonePtr();
15993 Maybe<const CipherKey> maybeKey =
15994 mInPrivateBrowsing ? gIndexedDBCipherKeyManager->Get(mDatabaseId)
15995 : Nothing();
15997 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());
15999 // XXX Shouldn't Manager() return already_AddRefed when
16000 // PBackgroundIDBFactoryParent is declared refcounted?
16001 mDatabase = MakeSafeRefPtr<Database>(
16002 SafeRefPtr{static_cast<Factory*>(Manager()),
16003 AcquireStrongRefFromRawPtr{}},
16004 mCommonParams.principalInfo(),
16005 mContentHandle ? Some(mContentHandle->ChildID()) : Nothing(),
16006 mOriginMetadata, mTelemetryId, mMetadata.clonePtr(),
16007 mFileManager.clonePtr(), std::move(mDirectoryLock),
16008 mChromeWriteAccessAllowed, mInPrivateBrowsing, maybeKey);
16010 if (info) {
16011 info->mLiveDatabases.AppendElement(
16012 WrapNotNullUnchecked(mDatabase.unsafeGetRawPtr()));
16013 } else {
16014 // XXX Maybe use LookupOrInsertWith above, to avoid a second lookup here?
16015 info = gLiveDatabaseHashtable
16016 ->InsertOrUpdate(
16017 mDatabaseId,
16018 MakeUnique<DatabaseActorInfo>(
16019 mMetadata.clonePtr(),
16020 WrapNotNullUnchecked(mDatabase.unsafeGetRawPtr())))
16021 .get();
16024 // Balanced in Database::CleanupMetadata().
16025 IncreaseBusyCount();
16028 nsresult OpenDatabaseOp::EnsureDatabaseActorIsAlive() {
16029 AssertIsOnOwningThread();
16030 MOZ_ASSERT(mState == State::DatabaseWorkVersionChange ||
16031 mState == State::SendingResults);
16032 MOZ_ASSERT(!HasFailed());
16033 MOZ_ASSERT(!IsActorDestroyed());
16035 EnsureDatabaseActor();
16037 if (mDatabase->IsActorAlive()) {
16038 return NS_OK;
16041 auto* const factory = static_cast<Factory*>(Manager());
16043 QM_TRY_INSPECT(const auto& spec, MetadataToSpec());
16045 // Transfer ownership to IPDL.
16046 mDatabase->SetActorAlive();
16048 if (!factory->SendPBackgroundIDBDatabaseConstructor(
16049 mDatabase.unsafeGetRawPtr(), spec, this)) {
16050 IDB_REPORT_INTERNAL_ERR();
16051 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16054 return NS_OK;
16057 Result<DatabaseSpec, nsresult> OpenDatabaseOp::MetadataToSpec() const {
16058 AssertIsOnOwningThread();
16059 MOZ_ASSERT(mMetadata);
16061 DatabaseSpec spec;
16062 spec.metadata() = mMetadata->mCommonMetadata;
16064 QM_TRY_UNWRAP(spec.objectStores(),
16065 TransformIntoNewArrayAbortOnErr(
16066 mMetadata->mObjectStores,
16067 [](const auto& objectStoreEntry)
16068 -> mozilla::Result<ObjectStoreSpec, nsresult> {
16069 FullObjectStoreMetadata* metadata =
16070 objectStoreEntry.GetWeak();
16071 MOZ_ASSERT(objectStoreEntry.GetKey());
16072 MOZ_ASSERT(metadata);
16074 ObjectStoreSpec objectStoreSpec;
16075 objectStoreSpec.metadata() = metadata->mCommonMetadata;
16077 QM_TRY_UNWRAP(auto indexes,
16078 TransformIntoNewArray(
16079 metadata->mIndexes,
16080 [](const auto& indexEntry) {
16081 FullIndexMetadata* indexMetadata =
16082 indexEntry.GetWeak();
16083 MOZ_ASSERT(indexEntry.GetKey());
16084 MOZ_ASSERT(indexMetadata);
16086 return indexMetadata->mCommonMetadata;
16088 fallible));
16090 objectStoreSpec.indexes() = std::move(indexes);
16092 return objectStoreSpec;
16094 fallible));
16096 return spec;
16099 #ifdef DEBUG
16101 void OpenDatabaseOp::AssertMetadataConsistency(
16102 const FullDatabaseMetadata& aMetadata) {
16103 AssertIsOnBackgroundThread();
16105 const FullDatabaseMetadata& thisDB = *mMetadata;
16106 const FullDatabaseMetadata& otherDB = aMetadata;
16108 MOZ_ASSERT(&thisDB != &otherDB);
16110 MOZ_ASSERT(thisDB.mCommonMetadata.name() == otherDB.mCommonMetadata.name());
16111 MOZ_ASSERT(thisDB.mCommonMetadata.version() ==
16112 otherDB.mCommonMetadata.version());
16113 MOZ_ASSERT(thisDB.mCommonMetadata.persistenceType() ==
16114 otherDB.mCommonMetadata.persistenceType());
16115 MOZ_ASSERT(thisDB.mDatabaseId == otherDB.mDatabaseId);
16116 MOZ_ASSERT(thisDB.mFilePath == otherDB.mFilePath);
16118 // |thisDB| reflects the latest objectStore and index ids that have committed
16119 // to disk. The in-memory metadata |otherDB| keeps track of objectStores and
16120 // indexes that were created and then removed as well, so the next ids for
16121 // |otherDB| may be higher than for |thisDB|.
16122 MOZ_ASSERT(thisDB.mNextObjectStoreId <= otherDB.mNextObjectStoreId);
16123 MOZ_ASSERT(thisDB.mNextIndexId <= otherDB.mNextIndexId);
16125 MOZ_ASSERT(thisDB.mObjectStores.Count() == otherDB.mObjectStores.Count());
16127 for (const auto& thisObjectStore : thisDB.mObjectStores.Values()) {
16128 MOZ_ASSERT(thisObjectStore);
16129 MOZ_ASSERT(!thisObjectStore->mDeleted);
16131 auto otherObjectStore = MatchMetadataNameOrId(
16132 otherDB.mObjectStores, thisObjectStore->mCommonMetadata.id());
16133 MOZ_ASSERT(otherObjectStore);
16135 MOZ_ASSERT(thisObjectStore != &otherObjectStore.ref());
16137 MOZ_ASSERT(thisObjectStore->mCommonMetadata.id() ==
16138 otherObjectStore->mCommonMetadata.id());
16139 MOZ_ASSERT(thisObjectStore->mCommonMetadata.name() ==
16140 otherObjectStore->mCommonMetadata.name());
16141 MOZ_ASSERT(thisObjectStore->mCommonMetadata.autoIncrement() ==
16142 otherObjectStore->mCommonMetadata.autoIncrement());
16143 MOZ_ASSERT(thisObjectStore->mCommonMetadata.keyPath() ==
16144 otherObjectStore->mCommonMetadata.keyPath());
16145 // mNextAutoIncrementId and mCommittedAutoIncrementId may be modified
16146 // concurrently with this OpenOp, so it is not possible to assert equality
16147 // here. It's also possible that we've written the new ids to disk but not
16148 // yet updated the in-memory count.
16149 // TODO The first part of the comment should probably be rephrased. I think
16150 // it still applies but it sounds as if this were thread-unsafe like it was
16151 // before, which isn't true anymore.
16153 const auto&& thisAutoIncrementIds =
16154 thisObjectStore->mAutoIncrementIds.Lock();
16155 const auto&& otherAutoIncrementIds =
16156 otherObjectStore->mAutoIncrementIds.Lock();
16158 MOZ_ASSERT(thisAutoIncrementIds->next <= otherAutoIncrementIds->next);
16159 MOZ_ASSERT(
16160 thisAutoIncrementIds->committed <= otherAutoIncrementIds->committed ||
16161 thisAutoIncrementIds->committed == otherAutoIncrementIds->next);
16163 MOZ_ASSERT(!otherObjectStore->mDeleted);
16165 MOZ_ASSERT(thisObjectStore->mIndexes.Count() ==
16166 otherObjectStore->mIndexes.Count());
16168 for (const auto& thisIndex : thisObjectStore->mIndexes.Values()) {
16169 MOZ_ASSERT(thisIndex);
16170 MOZ_ASSERT(!thisIndex->mDeleted);
16172 auto otherIndex = MatchMetadataNameOrId(otherObjectStore->mIndexes,
16173 thisIndex->mCommonMetadata.id());
16174 MOZ_ASSERT(otherIndex);
16176 MOZ_ASSERT(thisIndex != &otherIndex.ref());
16178 MOZ_ASSERT(thisIndex->mCommonMetadata.id() ==
16179 otherIndex->mCommonMetadata.id());
16180 MOZ_ASSERT(thisIndex->mCommonMetadata.name() ==
16181 otherIndex->mCommonMetadata.name());
16182 MOZ_ASSERT(thisIndex->mCommonMetadata.keyPath() ==
16183 otherIndex->mCommonMetadata.keyPath());
16184 MOZ_ASSERT(thisIndex->mCommonMetadata.unique() ==
16185 otherIndex->mCommonMetadata.unique());
16186 MOZ_ASSERT(thisIndex->mCommonMetadata.multiEntry() ==
16187 otherIndex->mCommonMetadata.multiEntry());
16188 MOZ_ASSERT(!otherIndex->mDeleted);
16193 #endif // DEBUG
16195 nsresult OpenDatabaseOp::VersionChangeOp::DoDatabaseWork(
16196 DatabaseConnection* aConnection) {
16197 MOZ_ASSERT(aConnection);
16198 aConnection->AssertIsOnConnectionThread();
16199 MOZ_ASSERT(mOpenDatabaseOp);
16200 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
16202 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
16203 !OperationMayProceed()) {
16204 IDB_REPORT_INTERNAL_ERR();
16205 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16208 AUTO_PROFILER_LABEL("OpenDatabaseOp::VersionChangeOp::DoDatabaseWork", DOM);
16210 IDB_LOG_MARK_PARENT_TRANSACTION("Beginning database work", "DB Start",
16211 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
16212 mTransactionLoggingSerialNumber);
16214 Transaction().SetActiveOnConnectionThread();
16216 QM_TRY(MOZ_TO_RESULT(aConnection->BeginWriteTransaction()));
16218 // The parameter names are not used, parameters are bound by index only
16219 // locally in the same function.
16220 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
16221 "UPDATE database SET version = :version;"_ns,
16222 ([&self = *this](
16223 mozIStorageStatement& updateStmt) -> mozilla::Result<Ok, nsresult> {
16224 QM_TRY(MOZ_TO_RESULT(
16225 updateStmt.BindInt64ByIndex(0, int64_t(self.mRequestedVersion))));
16227 return Ok{};
16228 }))));
16230 return NS_OK;
16233 nsresult OpenDatabaseOp::VersionChangeOp::SendSuccessResult() {
16234 AssertIsOnOwningThread();
16235 MOZ_ASSERT(mOpenDatabaseOp);
16236 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
16237 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
16239 nsresult rv = mOpenDatabaseOp->SendUpgradeNeeded();
16240 if (NS_WARN_IF(NS_FAILED(rv))) {
16241 return rv;
16244 return NS_OK;
16247 bool OpenDatabaseOp::VersionChangeOp::SendFailureResult(nsresult aResultCode) {
16248 AssertIsOnOwningThread();
16249 MOZ_ASSERT(mOpenDatabaseOp);
16250 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
16251 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
16253 mOpenDatabaseOp->SetFailureCode(aResultCode);
16254 mOpenDatabaseOp->mState = State::SendingResults;
16256 MOZ_ALWAYS_SUCCEEDS(mOpenDatabaseOp->Run());
16258 return false;
16261 void OpenDatabaseOp::VersionChangeOp::Cleanup() {
16262 AssertIsOnOwningThread();
16263 MOZ_ASSERT(mOpenDatabaseOp);
16264 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
16266 mOpenDatabaseOp->mVersionChangeOp = nullptr;
16267 mOpenDatabaseOp = nullptr;
16269 #ifdef DEBUG
16270 // A bit hacky but the VersionChangeOp is not generated in response to a
16271 // child request like most other database operations. Do this to make our
16272 // assertions happy.
16274 // XXX: Depending on timing, in most cases, NoteActorDestroyed will not have
16275 // been destroyed before, but in some cases it has. This should be reworked in
16276 // a way this hack is not necessary. There are also several similar cases in
16277 // other *Op classes.
16278 if (!IsActorDestroyed()) {
16279 NoteActorDestroyed();
16281 #endif
16283 TransactionDatabaseOperationBase::Cleanup();
16286 void DeleteDatabaseOp::LoadPreviousVersion(nsIFile& aDatabaseFile) {
16287 AssertIsOnIOThread();
16288 MOZ_ASSERT(mState == State::DatabaseWorkOpen);
16289 MOZ_ASSERT(!mPreviousVersion);
16291 AUTO_PROFILER_LABEL("DeleteDatabaseOp::LoadPreviousVersion", DOM);
16293 nsresult rv;
16295 nsCOMPtr<mozIStorageService> ss =
16296 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
16297 if (NS_WARN_IF(NS_FAILED(rv))) {
16298 return;
16301 const auto maybeKey = mInPrivateBrowsing
16302 ? gIndexedDBCipherKeyManager->Get(mDatabaseId)
16303 : Nothing();
16305 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());
16307 // Pass -1 as the directoryLockId to disable quota checking, since we might
16308 // temporarily exceed quota before deleting the database.
16309 QM_TRY_INSPECT(const auto& dbFileUrl,
16310 GetDatabaseFileURL(aDatabaseFile, -1, maybeKey), QM_VOID);
16312 QM_TRY_UNWRAP(const NotNull<nsCOMPtr<mozIStorageConnection>> connection,
16313 OpenDatabaseAndHandleBusy(*ss, *dbFileUrl), QM_VOID);
16315 #ifdef DEBUG
16317 QM_TRY_INSPECT(const auto& stmt,
16318 CreateAndExecuteSingleStepStatement<
16319 SingleStepResult::ReturnNullIfNoResult>(
16320 *connection, "SELECT name FROM database"_ns),
16321 QM_VOID);
16323 QM_TRY(OkIf(stmt), QM_VOID);
16325 nsString databaseName;
16326 rv = stmt->GetString(0, databaseName);
16327 if (NS_WARN_IF(NS_FAILED(rv))) {
16328 return;
16331 MOZ_ASSERT(mCommonParams.metadata().name() == databaseName);
16333 #endif
16335 QM_TRY_INSPECT(const auto& stmt,
16336 CreateAndExecuteSingleStepStatement<
16337 SingleStepResult::ReturnNullIfNoResult>(
16338 *connection, "SELECT version FROM database"_ns),
16339 QM_VOID);
16341 QM_TRY(OkIf(stmt), QM_VOID);
16343 int64_t version;
16344 rv = stmt->GetInt64(0, &version);
16345 if (NS_WARN_IF(NS_FAILED(rv))) {
16346 return;
16349 mPreviousVersion = uint64_t(version);
16352 nsresult DeleteDatabaseOp::DatabaseOpen() {
16353 AssertIsOnOwningThread();
16354 MOZ_ASSERT(mState == State::DatabaseOpenPending);
16356 nsresult rv = SendToIOThread();
16357 if (NS_WARN_IF(NS_FAILED(rv))) {
16358 return rv;
16361 return NS_OK;
16364 nsresult DeleteDatabaseOp::DoDatabaseWork() {
16365 AssertIsOnIOThread();
16366 MOZ_ASSERT(mState == State::DatabaseWorkOpen);
16368 AUTO_PROFILER_LABEL("DeleteDatabaseOp::DoDatabaseWork", DOM);
16370 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
16371 !OperationMayProceed()) {
16372 IDB_REPORT_INTERNAL_ERR();
16373 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16376 const nsAString& databaseName = mCommonParams.metadata().name();
16377 const PersistenceType persistenceType =
16378 mCommonParams.metadata().persistenceType();
16380 QuotaManager* const quotaManager = QuotaManager::Get();
16381 MOZ_ASSERT(quotaManager);
16383 QM_TRY_UNWRAP(auto directory, quotaManager->GetDirectoryForOrigin(
16384 persistenceType, mOriginMetadata.mOrigin));
16386 QM_TRY(MOZ_TO_RESULT(
16387 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME))));
16389 QM_TRY_UNWRAP(mDatabaseDirectoryPath, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
16390 nsString, directory, GetPath));
16392 mDatabaseFilenameBase = GetDatabaseFilenameBase(databaseName);
16394 QM_TRY_INSPECT(
16395 const auto& dbFile,
16396 CloneFileAndAppend(*directory, mDatabaseFilenameBase + kSQLiteSuffix));
16398 #ifdef DEBUG
16400 QM_TRY_INSPECT(
16401 const auto& databaseFilePath,
16402 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath));
16404 MOZ_ASSERT(databaseFilePath == mDatabaseFilePath);
16406 #endif
16408 QM_TRY_INSPECT(const bool& exists,
16409 MOZ_TO_RESULT_INVOKE_MEMBER(dbFile, Exists));
16411 if (exists) {
16412 // Parts of this function may fail but that shouldn't prevent us from
16413 // deleting the file eventually.
16414 LoadPreviousVersion(*dbFile);
16416 mState = State::BeginVersionChange;
16417 } else {
16418 mState = State::SendingResults;
16421 QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
16423 return NS_OK;
16426 nsresult DeleteDatabaseOp::BeginVersionChange() {
16427 AssertIsOnOwningThread();
16428 MOZ_ASSERT(mState == State::BeginVersionChange);
16429 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
16431 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
16432 IsActorDestroyed()) {
16433 IDB_REPORT_INTERNAL_ERR();
16434 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR));
16437 DatabaseActorInfo* info;
16438 if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) {
16439 MOZ_ASSERT(!info->mWaitingFactoryOp);
16441 nsresult rv =
16442 SendVersionChangeMessages(info, Nothing(), mPreviousVersion, Nothing());
16443 if (NS_WARN_IF(NS_FAILED(rv))) {
16444 return rv;
16447 if (!mMaybeBlockedDatabases.IsEmpty()) {
16448 // If the actor gets destroyed, mWaitingFactoryOp will hold the last
16449 // strong reference to us.
16450 info->mWaitingFactoryOp = this;
16452 mState = State::WaitingForOtherDatabasesToClose;
16453 return NS_OK;
16457 // No other databases need to be notified, just make sure that all
16458 // transactions are complete.
16459 WaitForTransactions();
16460 return NS_OK;
16463 bool DeleteDatabaseOp::AreActorsAlive() {
16464 AssertIsOnOwningThread();
16466 return !IsActorDestroyed();
16469 nsresult DeleteDatabaseOp::DispatchToWorkThread() {
16470 AssertIsOnOwningThread();
16471 MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
16472 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
16474 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
16475 IsActorDestroyed()) {
16476 IDB_REPORT_INTERNAL_ERR();
16477 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16480 mState = State::DatabaseWorkVersionChange;
16482 RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
16484 QuotaManager* const quotaManager = QuotaManager::Get();
16485 MOZ_ASSERT(quotaManager);
16487 nsresult rv = quotaManager->IOThread()->Dispatch(versionChangeOp.forget(),
16488 NS_DISPATCH_NORMAL);
16489 if (NS_WARN_IF(NS_FAILED(rv))) {
16490 IDB_REPORT_INTERNAL_ERR();
16491 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16494 return NS_OK;
16497 void DeleteDatabaseOp::SendBlockedNotification() {
16498 AssertIsOnOwningThread();
16499 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
16501 if (!IsActorDestroyed()) {
16502 Unused << SendBlocked(mPreviousVersion);
16506 void DeleteDatabaseOp::SendResults() {
16507 AssertIsOnOwningThread();
16508 MOZ_ASSERT(mState == State::SendingResults);
16509 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
16511 DebugOnly<DatabaseActorInfo*> info = nullptr;
16512 MOZ_ASSERT_IF(
16513 gLiveDatabaseHashtable && gLiveDatabaseHashtable->Get(mDatabaseId, &info),
16514 !info->mWaitingFactoryOp);
16516 if (!IsActorDestroyed()) {
16517 FactoryRequestResponse response;
16519 if (!HasFailed()) {
16520 response = DeleteDatabaseRequestResponse(mPreviousVersion);
16521 } else {
16522 response = ClampResultCode(ResultCode());
16525 Unused << PBackgroundIDBFactoryRequestParent::Send__delete__(this,
16526 response);
16529 mDirectoryLock = nullptr;
16531 CleanupMetadata();
16533 FinishSendResults();
16536 nsresult DeleteDatabaseOp::VersionChangeOp::RunOnIOThread() {
16537 AssertIsOnIOThread();
16538 MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
16540 AUTO_PROFILER_LABEL("DeleteDatabaseOp::VersionChangeOp::RunOnIOThread", DOM);
16542 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
16543 !OperationMayProceed()) {
16544 IDB_REPORT_INTERNAL_ERR();
16545 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16548 const PersistenceType& persistenceType =
16549 mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
16551 QuotaManager* quotaManager =
16552 mDeleteDatabaseOp->mEnforcingQuota ? QuotaManager::Get() : nullptr;
16554 MOZ_ASSERT_IF(mDeleteDatabaseOp->mEnforcingQuota, quotaManager);
16556 nsCOMPtr<nsIFile> directory =
16557 GetFileForPath(mDeleteDatabaseOp->mDatabaseDirectoryPath);
16558 if (NS_WARN_IF(!directory)) {
16559 IDB_REPORT_INTERNAL_ERR();
16560 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16563 nsresult rv = RemoveDatabaseFilesAndDirectory(
16564 *directory, mDeleteDatabaseOp->mDatabaseFilenameBase, quotaManager,
16565 persistenceType, mDeleteDatabaseOp->mOriginMetadata,
16566 mDeleteDatabaseOp->mCommonParams.metadata().name());
16567 if (NS_WARN_IF(NS_FAILED(rv))) {
16568 return rv;
16571 if (mDeleteDatabaseOp->mInPrivateBrowsing) {
16572 DebugOnly<bool> ok =
16573 gIndexedDBCipherKeyManager->Remove(mDeleteDatabaseOp->mDatabaseId);
16574 MOZ_ASSERT(ok);
16577 rv = mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
16578 if (NS_WARN_IF(NS_FAILED(rv))) {
16579 return rv;
16582 return NS_OK;
16585 void DeleteDatabaseOp::VersionChangeOp::RunOnOwningThread() {
16586 AssertIsOnOwningThread();
16587 MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
16589 const RefPtr<DeleteDatabaseOp> deleteOp = std::move(mDeleteDatabaseOp);
16591 if (deleteOp->IsActorDestroyed()) {
16592 IDB_REPORT_INTERNAL_ERR();
16593 deleteOp->SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
16594 } else if (HasFailed()) {
16595 deleteOp->SetFailureCodeIfUnset(ResultCode());
16596 } else {
16597 DatabaseActorInfo* info;
16599 // Inform all the other databases that they are now invalidated. That
16600 // should remove the previous metadata from our table.
16601 if (gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId, &info)) {
16602 MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
16603 MOZ_ASSERT(!info->mWaitingFactoryOp);
16605 nsTArray<SafeRefPtr<Database>> liveDatabases;
16606 if (NS_WARN_IF(!liveDatabases.SetCapacity(info->mLiveDatabases.Length(),
16607 fallible))) {
16608 deleteOp->SetFailureCode(NS_ERROR_OUT_OF_MEMORY);
16609 } else {
16610 std::transform(info->mLiveDatabases.cbegin(),
16611 info->mLiveDatabases.cend(),
16612 MakeBackInserter(liveDatabases),
16613 [](const auto& aDatabase) -> SafeRefPtr<Database> {
16614 return {aDatabase.get(), AcquireStrongRefFromRawPtr{}};
16617 #ifdef DEBUG
16618 // The code below should result in the deletion of |info|. Set to null
16619 // here to make sure we find invalid uses later.
16620 info = nullptr;
16621 #endif
16623 for (const auto& database : liveDatabases) {
16624 database->Invalidate();
16627 MOZ_ASSERT(!gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId));
16632 // We hold a strong ref to the deleteOp, so it's safe to call Run() directly.
16634 deleteOp->mState = State::SendingResults;
16635 MOZ_ALWAYS_SUCCEEDS(deleteOp->Run());
16637 #ifdef DEBUG
16638 // A bit hacky but the DeleteDatabaseOp::VersionChangeOp is not really a
16639 // normal database operation that is tied to an actor. Do this to make our
16640 // assertions happy.
16641 NoteActorDestroyed();
16642 #endif
16645 nsresult DeleteDatabaseOp::VersionChangeOp::Run() {
16646 nsresult rv;
16648 if (IsOnIOThread()) {
16649 rv = RunOnIOThread();
16650 } else {
16651 RunOnOwningThread();
16652 rv = NS_OK;
16655 if (NS_WARN_IF(NS_FAILED(rv))) {
16656 SetFailureCodeIfUnset(rv);
16658 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
16661 return NS_OK;
16664 TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
16665 SafeRefPtr<TransactionBase> aTransaction)
16666 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
16667 aTransaction->GetLoggingInfo()->NextRequestSN()),
16668 mTransaction(WrapNotNull(std::move(aTransaction))),
16669 mTransactionIsAborted((*mTransaction)->IsAborted()),
16670 mTransactionLoggingSerialNumber((*mTransaction)->LoggingSerialNumber()) {
16671 MOZ_ASSERT(LoggingSerialNumber());
16674 TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
16675 SafeRefPtr<TransactionBase> aTransaction, uint64_t aLoggingSerialNumber)
16676 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
16677 aLoggingSerialNumber),
16678 mTransaction(WrapNotNull(std::move(aTransaction))),
16679 mTransactionIsAborted((*mTransaction)->IsAborted()),
16680 mTransactionLoggingSerialNumber((*mTransaction)->LoggingSerialNumber()) {}
16682 TransactionDatabaseOperationBase::~TransactionDatabaseOperationBase() {
16683 MOZ_ASSERT(mInternalState == InternalState::Completed);
16684 MOZ_ASSERT(!mTransaction,
16685 "TransactionDatabaseOperationBase::Cleanup() was not called by a "
16686 "subclass!");
16689 #ifdef DEBUG
16691 void TransactionDatabaseOperationBase::AssertIsOnConnectionThread() const {
16692 (*mTransaction)->AssertIsOnConnectionThread();
16695 #endif // DEBUG
16697 uint64_t TransactionDatabaseOperationBase::StartOnConnectionPool(
16698 const nsID& aBackgroundChildLoggingId, const nsACString& aDatabaseId,
16699 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames,
16700 bool aIsWriteTransaction) {
16701 AssertIsOnOwningThread();
16702 MOZ_ASSERT(mInternalState == InternalState::Initial);
16704 // Must set mInternalState before dispatching otherwise we will race with the
16705 // connection thread.
16706 mInternalState = InternalState::DatabaseWork;
16708 return gConnectionPool->Start(aBackgroundChildLoggingId, aDatabaseId,
16709 aLoggingSerialNumber, aObjectStoreNames,
16710 aIsWriteTransaction, this);
16713 void TransactionDatabaseOperationBase::DispatchToConnectionPool() {
16714 AssertIsOnOwningThread();
16715 MOZ_ASSERT(mInternalState == InternalState::Initial);
16717 Unused << this->Run();
16720 void TransactionDatabaseOperationBase::RunOnConnectionThread() {
16721 MOZ_ASSERT(!IsOnBackgroundThread());
16722 MOZ_ASSERT(mInternalState == InternalState::DatabaseWork);
16723 MOZ_ASSERT(!HasFailed());
16725 AUTO_PROFILER_LABEL("TransactionDatabaseOperationBase::RunOnConnectionThread",
16726 DOM);
16728 // There are several cases where we don't actually have to to any work here.
16730 if (mTransactionIsAborted || (*mTransaction)->IsInvalidatedOnAnyThread()) {
16731 // This transaction is already set to be aborted or invalidated.
16732 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
16733 } else if (!OperationMayProceed()) {
16734 // The operation was canceled in some way, likely because the child process
16735 // has crashed.
16736 IDB_REPORT_INTERNAL_ERR();
16737 OverrideFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
16738 } else {
16739 Database& database = (*mTransaction)->GetMutableDatabase();
16741 // Here we're actually going to perform the database operation.
16742 nsresult rv = database.EnsureConnection();
16743 if (NS_WARN_IF(NS_FAILED(rv))) {
16744 SetFailureCode(rv);
16745 } else {
16746 DatabaseConnection* connection = database.GetConnection();
16747 MOZ_ASSERT(connection);
16749 auto& storageConnection = connection->MutableStorageConnection();
16751 AutoSetProgressHandler autoProgress;
16752 if (mLoggingSerialNumber) {
16753 rv = autoProgress.Register(storageConnection, this);
16754 if (NS_WARN_IF(NS_FAILED(rv))) {
16755 SetFailureCode(rv);
16759 if (NS_SUCCEEDED(rv)) {
16760 if (mLoggingSerialNumber) {
16761 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
16762 "Beginning database work", "DB Start",
16763 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
16764 mTransactionLoggingSerialNumber, mLoggingSerialNumber);
16767 rv = DoDatabaseWork(connection);
16769 if (mLoggingSerialNumber) {
16770 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
16771 "Finished database work", "DB End",
16772 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
16773 mTransactionLoggingSerialNumber, mLoggingSerialNumber);
16776 if (NS_FAILED(rv)) {
16777 SetFailureCode(rv);
16783 // Must set mInternalState before dispatching otherwise we will race with the
16784 // owning thread.
16785 if (HasPreprocessInfo()) {
16786 mInternalState = InternalState::SendingPreprocess;
16787 } else {
16788 mInternalState = InternalState::SendingResults;
16791 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
16794 bool TransactionDatabaseOperationBase::HasPreprocessInfo() { return false; }
16796 nsresult TransactionDatabaseOperationBase::SendPreprocessInfo() {
16797 return NS_OK;
16800 void TransactionDatabaseOperationBase::NoteContinueReceived() {
16801 AssertIsOnOwningThread();
16802 MOZ_ASSERT(mInternalState == InternalState::WaitingForContinue);
16804 mWaitingForContinue = false;
16806 mInternalState = InternalState::SendingResults;
16808 // This TransactionDatabaseOperationBase can only be held alive by the IPDL.
16809 // Run() can end up with clearing that last reference. So we need to add
16810 // a self reference here.
16811 RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip = this;
16813 Unused << this->Run();
16816 void TransactionDatabaseOperationBase::SendToConnectionPool() {
16817 AssertIsOnOwningThread();
16818 MOZ_ASSERT(mInternalState == InternalState::Initial);
16820 // Must set mInternalState before dispatching otherwise we will race with the
16821 // connection thread.
16822 mInternalState = InternalState::DatabaseWork;
16824 gConnectionPool->Dispatch((*mTransaction)->TransactionId(), this);
16826 (*mTransaction)->NoteActiveRequest();
16829 void TransactionDatabaseOperationBase::SendPreprocess() {
16830 AssertIsOnOwningThread();
16831 MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess);
16833 SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ true);
16836 void TransactionDatabaseOperationBase::SendResults() {
16837 AssertIsOnOwningThread();
16838 MOZ_ASSERT(mInternalState == InternalState::SendingResults);
16840 SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ false);
16843 void TransactionDatabaseOperationBase::SendPreprocessInfoOrResults(
16844 bool aSendPreprocessInfo) {
16845 AssertIsOnOwningThread();
16846 MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess ||
16847 mInternalState == InternalState::SendingResults);
16849 // The flag is raised only when there is no mUpdateRefcountFunction for the
16850 // executing operation. It assume that is because the previous
16851 // StartTransactionOp was failed to begin a write transaction and it reported
16852 // when this operation has already jumped to the Connection thread.
16853 MOZ_DIAGNOSTIC_ASSERT_IF(mAssumingPreviousOperationFail,
16854 (*mTransaction)->IsAborted());
16856 if (NS_WARN_IF(IsActorDestroyed())) {
16857 // Normally we wouldn't need to send any notifications if the actor was
16858 // already destroyed, but this can be a VersionChangeOp which needs to
16859 // notify its parent operation (OpenDatabaseOp) about the failure.
16860 // So SendFailureResult needs to be called even when the actor was
16861 // destroyed. Normal operations redundantly check if the actor was
16862 // destroyed in SendSuccessResult and SendFailureResult, therefore it's
16863 // ok to call it in all cases here.
16864 if (!HasFailed()) {
16865 IDB_REPORT_INTERNAL_ERR();
16866 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
16868 } else if ((*mTransaction)->IsInvalidated() || (*mTransaction)->IsAborted()) {
16869 // Aborted transactions always see their requests fail with ABORT_ERR,
16870 // even if the request succeeded or failed with another error.
16871 OverrideFailureCode(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
16874 const nsresult rv = [aSendPreprocessInfo, this] {
16875 if (HasFailed()) {
16876 return ResultCode();
16878 if (aSendPreprocessInfo) {
16879 // This should not release the IPDL reference.
16880 return SendPreprocessInfo();
16882 // This may release the IPDL reference.
16883 return SendSuccessResult();
16884 }();
16886 if (NS_FAILED(rv)) {
16887 SetFailureCodeIfUnset(rv);
16889 // This should definitely release the IPDL reference.
16890 if (!SendFailureResult(rv)) {
16891 // Abort the transaction.
16892 (*mTransaction)->Abort(rv, /* aForce */ false);
16896 if (aSendPreprocessInfo && !HasFailed()) {
16897 mInternalState = InternalState::WaitingForContinue;
16899 mWaitingForContinue = true;
16900 } else {
16901 if (mLoggingSerialNumber) {
16902 (*mTransaction)->NoteFinishedRequest(mLoggingSerialNumber, ResultCode());
16905 Cleanup();
16907 mInternalState = InternalState::Completed;
16911 bool TransactionDatabaseOperationBase::Init(TransactionBase& aTransaction) {
16912 AssertIsOnBackgroundThread();
16913 MOZ_ASSERT(mInternalState == InternalState::Initial);
16915 return true;
16918 void TransactionDatabaseOperationBase::Cleanup() {
16919 AssertIsOnOwningThread();
16920 MOZ_ASSERT(mInternalState == InternalState::SendingResults);
16922 mTransaction.destroy();
16925 NS_IMETHODIMP
16926 TransactionDatabaseOperationBase::Run() {
16927 switch (mInternalState) {
16928 case InternalState::Initial:
16929 SendToConnectionPool();
16930 return NS_OK;
16932 case InternalState::DatabaseWork:
16933 RunOnConnectionThread();
16934 return NS_OK;
16936 case InternalState::SendingPreprocess:
16937 SendPreprocess();
16938 return NS_OK;
16940 case InternalState::SendingResults:
16941 SendResults();
16942 return NS_OK;
16944 default:
16945 MOZ_CRASH("Bad state!");
16949 TransactionBase::CommitOp::CommitOp(SafeRefPtr<TransactionBase> aTransaction,
16950 nsresult aResultCode)
16951 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
16952 aTransaction->GetLoggingInfo()->NextRequestSN()),
16953 mTransaction(std::move(aTransaction)),
16954 mResultCode(aResultCode) {
16955 MOZ_ASSERT(mTransaction);
16956 MOZ_ASSERT(LoggingSerialNumber());
16959 nsresult TransactionBase::CommitOp::WriteAutoIncrementCounts() {
16960 MOZ_ASSERT(mTransaction);
16961 mTransaction->AssertIsOnConnectionThread();
16962 MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::Mode::ReadWrite ||
16963 mTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
16964 mTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
16965 mTransaction->GetMode() == IDBTransaction::Mode::VersionChange);
16967 const nsTArray<SafeRefPtr<FullObjectStoreMetadata>>& metadataArray =
16968 mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray;
16970 if (!metadataArray.IsEmpty()) {
16971 DatabaseConnection* connection =
16972 mTransaction->GetDatabase().GetConnection();
16973 MOZ_ASSERT(connection);
16975 // The parameter names are not used, parameters are bound by index only
16976 // locally in the same function.
16977 auto stmt = DatabaseConnection::LazyStatement(
16978 *connection,
16979 "UPDATE object_store "
16980 "SET auto_increment = :auto_increment WHERE id "
16981 "= :object_store_id;"_ns);
16983 for (const auto& metadata : metadataArray) {
16984 MOZ_ASSERT(!metadata->mDeleted);
16986 const int64_t nextAutoIncrementId = [&metadata] {
16987 const auto&& lockedAutoIncrementIds =
16988 metadata->mAutoIncrementIds.Lock();
16989 return lockedAutoIncrementIds->next;
16990 }();
16992 MOZ_ASSERT(nextAutoIncrementId > 1);
16994 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow());
16996 QM_TRY(MOZ_TO_RESULT(
16997 borrowedStmt->BindInt64ByIndex(1, metadata->mCommonMetadata.id())));
16999 QM_TRY(MOZ_TO_RESULT(
17000 borrowedStmt->BindInt64ByIndex(0, nextAutoIncrementId)));
17002 QM_TRY(MOZ_TO_RESULT(borrowedStmt->Execute()));
17006 return NS_OK;
17009 void TransactionBase::CommitOp::CommitOrRollbackAutoIncrementCounts() {
17010 MOZ_ASSERT(mTransaction);
17011 mTransaction->AssertIsOnConnectionThread();
17012 MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::Mode::ReadWrite ||
17013 mTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
17014 mTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
17015 mTransaction->GetMode() == IDBTransaction::Mode::VersionChange);
17017 const auto& metadataArray =
17018 mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray;
17020 if (!metadataArray.IsEmpty()) {
17021 bool committed = NS_SUCCEEDED(mResultCode);
17023 for (const auto& metadata : metadataArray) {
17024 auto&& lockedAutoIncrementIds = metadata->mAutoIncrementIds.Lock();
17026 if (committed) {
17027 lockedAutoIncrementIds->committed = lockedAutoIncrementIds->next;
17028 } else {
17029 lockedAutoIncrementIds->next = lockedAutoIncrementIds->committed;
17035 #ifdef DEBUG
17037 void TransactionBase::CommitOp::AssertForeignKeyConsistency(
17038 DatabaseConnection* aConnection) {
17039 MOZ_ASSERT(aConnection);
17040 MOZ_ASSERT(mTransaction);
17041 mTransaction->AssertIsOnConnectionThread();
17042 MOZ_ASSERT(mTransaction->GetMode() != IDBTransaction::Mode::ReadOnly);
17045 QM_TRY_INSPECT(
17046 const auto& pragmaStmt,
17047 CreateAndExecuteSingleStepStatement(
17048 aConnection->MutableStorageConnection(), "PRAGMA foreign_keys;"_ns),
17049 QM_ASSERT_UNREACHABLE_VOID);
17051 int32_t foreignKeysEnabled;
17052 MOZ_ALWAYS_SUCCEEDS(pragmaStmt->GetInt32(0, &foreignKeysEnabled));
17054 MOZ_ASSERT(foreignKeysEnabled,
17055 "Database doesn't have foreign keys enabled!");
17059 QM_TRY_INSPECT(const bool& foreignKeyError,
17060 CreateAndExecuteSingleStepStatement<
17061 SingleStepResult::ReturnNullIfNoResult>(
17062 aConnection->MutableStorageConnection(),
17063 "PRAGMA foreign_key_check;"_ns),
17064 QM_ASSERT_UNREACHABLE_VOID);
17066 MOZ_ASSERT(!foreignKeyError, "Database has inconsisistent foreign keys!");
17070 #endif // DEBUG
17072 NS_IMPL_ISUPPORTS_INHERITED0(TransactionBase::CommitOp, DatabaseOperationBase)
17074 NS_IMETHODIMP
17075 TransactionBase::CommitOp::Run() {
17076 MOZ_ASSERT(mTransaction);
17077 mTransaction->AssertIsOnConnectionThread();
17079 AUTO_PROFILER_LABEL("TransactionBase::CommitOp::Run", DOM);
17081 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
17082 "Beginning database work", "DB Start",
17083 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
17084 mTransaction->LoggingSerialNumber(), mLoggingSerialNumber);
17086 if (mTransaction->GetMode() != IDBTransaction::Mode::ReadOnly &&
17087 mTransaction->mHasBeenActiveOnConnectionThread) {
17088 if (DatabaseConnection* connection =
17089 mTransaction->GetDatabase().GetConnection()) {
17090 // May be null if the VersionChangeOp was canceled.
17091 DatabaseConnection::UpdateRefcountFunction* fileRefcountFunction =
17092 connection->GetUpdateRefcountFunction();
17094 if (NS_SUCCEEDED(mResultCode)) {
17095 if (fileRefcountFunction) {
17096 mResultCode = fileRefcountFunction->WillCommit();
17097 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode),
17098 "WillCommit() failed!");
17101 if (NS_SUCCEEDED(mResultCode)) {
17102 mResultCode = WriteAutoIncrementCounts();
17103 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode),
17104 "WriteAutoIncrementCounts() failed!");
17106 if (NS_SUCCEEDED(mResultCode)) {
17107 AssertForeignKeyConsistency(connection);
17109 mResultCode = connection->CommitWriteTransaction();
17110 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode), "Commit failed!");
17112 if (NS_SUCCEEDED(mResultCode) &&
17113 mTransaction->GetMode() ==
17114 IDBTransaction::Mode::ReadWriteFlush) {
17115 mResultCode = connection->Checkpoint();
17118 if (NS_SUCCEEDED(mResultCode) && fileRefcountFunction) {
17119 fileRefcountFunction->DidCommit();
17125 if (NS_FAILED(mResultCode)) {
17126 if (fileRefcountFunction) {
17127 fileRefcountFunction->DidAbort();
17130 connection->RollbackWriteTransaction();
17133 CommitOrRollbackAutoIncrementCounts();
17135 connection->FinishWriteTransaction();
17137 if (mTransaction->GetMode() == IDBTransaction::Mode::Cleanup) {
17138 connection->DoIdleProcessing(/* aNeedsCheckpoint */ true);
17140 connection->EnableQuotaChecks();
17145 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
17146 "Finished database work", "DB End",
17147 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
17148 mTransaction->LoggingSerialNumber(), mLoggingSerialNumber);
17150 IDB_LOG_MARK_PARENT_TRANSACTION("Finished database work", "DB End",
17151 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
17152 mTransaction->LoggingSerialNumber());
17154 return NS_OK;
17157 void TransactionBase::CommitOp::TransactionFinishedBeforeUnblock() {
17158 AssertIsOnBackgroundThread();
17159 MOZ_ASSERT(mTransaction);
17161 AUTO_PROFILER_LABEL("CommitOp::TransactionFinishedBeforeUnblock", DOM);
17163 if (!IsActorDestroyed()) {
17164 mTransaction->UpdateMetadata(mResultCode);
17168 void TransactionBase::CommitOp::TransactionFinishedAfterUnblock() {
17169 AssertIsOnBackgroundThread();
17170 MOZ_ASSERT(mTransaction);
17172 IDB_LOG_MARK_PARENT_TRANSACTION(
17173 "Finished with result 0x%" PRIx32, "Transaction finished (0x%" PRIx32 ")",
17174 IDB_LOG_ID_STRING(mTransaction->GetLoggingInfo()->Id()),
17175 mTransaction->LoggingSerialNumber(), static_cast<uint32_t>(mResultCode));
17177 mTransaction->SendCompleteNotification(ClampResultCode(mResultCode));
17179 mTransaction->GetMutableDatabase().UnregisterTransaction(*mTransaction);
17181 mTransaction = nullptr;
17183 #ifdef DEBUG
17184 // A bit hacky but the CommitOp is not really a normal database operation
17185 // that is tied to an actor. Do this to make our assertions happy.
17186 NoteActorDestroyed();
17187 #endif
17190 nsresult VersionChangeTransactionOp::SendSuccessResult() {
17191 AssertIsOnOwningThread();
17193 // Nothing to send here, the API assumes that this request always succeeds.
17194 return NS_OK;
17197 bool VersionChangeTransactionOp::SendFailureResult(nsresult aResultCode) {
17198 AssertIsOnOwningThread();
17200 // The only option here is to cause the transaction to abort.
17201 return false;
17204 void VersionChangeTransactionOp::Cleanup() {
17205 AssertIsOnOwningThread();
17207 #ifdef DEBUG
17208 // A bit hacky but the VersionChangeTransactionOp is not generated in response
17209 // to a child request like most other database operations. Do this to make our
17210 // assertions happy.
17211 NoteActorDestroyed();
17212 #endif
17214 TransactionDatabaseOperationBase::Cleanup();
17217 nsresult CreateObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17218 MOZ_ASSERT(aConnection);
17219 aConnection->AssertIsOnConnectionThread();
17221 AUTO_PROFILER_LABEL("CreateObjectStoreOp::DoDatabaseWork", DOM);
17223 #ifdef DEBUG
17225 // Make sure that we're not creating an object store with the same name as
17226 // another that already exists. This should be impossible because we should
17227 // have thrown an error long before now...
17228 // The parameter names are not used, parameters are bound by index only
17229 // locally in the same function.
17230 QM_TRY_INSPECT(const bool& hasResult,
17231 aConnection
17232 ->BorrowAndExecuteSingleStepStatement(
17233 "SELECT name "
17234 "FROM object_store "
17235 "WHERE name = :name;"_ns,
17236 [&self = *this](auto& stmt) -> Result<Ok, nsresult> {
17237 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(
17238 0, self.mMetadata.name())));
17239 return Ok{};
17241 .map(IsSome),
17242 QM_ASSERT_UNREACHABLE);
17244 MOZ_ASSERT(!hasResult);
17246 #endif
17248 DatabaseConnection::AutoSavepoint autoSave;
17249 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17250 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17252 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17253 #endif
17256 // The parameter names are not used, parameters are bound by index only
17257 // locally in the same function.
17258 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17259 "INSERT INTO object_store (id, auto_increment, name, key_path) "
17260 "VALUES (:id, :auto_increment, :name, :key_path);"_ns,
17261 [&metadata =
17262 mMetadata](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17263 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, metadata.id())));
17265 QM_TRY(MOZ_TO_RESULT(
17266 stmt.BindInt32ByIndex(1, metadata.autoIncrement() ? 1 : 0)));
17268 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(2, metadata.name())));
17270 if (metadata.keyPath().IsValid()) {
17271 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(
17272 3, metadata.keyPath().SerializeToString())));
17273 } else {
17274 QM_TRY(MOZ_TO_RESULT(stmt.BindNullByIndex(3)));
17277 return Ok{};
17278 })));
17280 #ifdef DEBUG
17282 int64_t id;
17283 MOZ_ALWAYS_SUCCEEDS(
17284 aConnection->MutableStorageConnection().GetLastInsertRowID(&id));
17285 MOZ_ASSERT(mMetadata.id() == id);
17287 #endif
17289 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
17291 return NS_OK;
17294 nsresult DeleteObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17295 MOZ_ASSERT(aConnection);
17296 aConnection->AssertIsOnConnectionThread();
17298 AUTO_PROFILER_LABEL("DeleteObjectStoreOp::DoDatabaseWork", DOM);
17300 #ifdef DEBUG
17302 // Make sure |mIsLastObjectStore| is telling the truth.
17303 QM_TRY_INSPECT(
17304 const auto& stmt,
17305 aConnection->BorrowCachedStatement("SELECT id FROM object_store;"_ns),
17306 QM_ASSERT_UNREACHABLE);
17308 bool foundThisObjectStore = false;
17309 bool foundOtherObjectStore = false;
17311 while (true) {
17312 bool hasResult;
17313 MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
17315 if (!hasResult) {
17316 break;
17319 int64_t id;
17320 MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id));
17322 if (id == mMetadata->mCommonMetadata.id()) {
17323 foundThisObjectStore = true;
17324 } else {
17325 foundOtherObjectStore = true;
17329 MOZ_ASSERT_IF(mIsLastObjectStore,
17330 foundThisObjectStore && !foundOtherObjectStore);
17331 MOZ_ASSERT_IF(!mIsLastObjectStore,
17332 foundThisObjectStore && foundOtherObjectStore);
17334 #endif
17336 DatabaseConnection::AutoSavepoint autoSave;
17337 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17338 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17340 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17341 #endif
17344 if (mIsLastObjectStore) {
17345 // We can just delete everything if this is the last object store.
17346 QM_TRY(MOZ_TO_RESULT(
17347 aConnection->ExecuteCachedStatement("DELETE FROM index_data;"_ns)));
17349 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17350 "DELETE FROM unique_index_data;"_ns)));
17352 QM_TRY(MOZ_TO_RESULT(
17353 aConnection->ExecuteCachedStatement("DELETE FROM object_data;"_ns)));
17355 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17356 "DELETE FROM object_store_index;"_ns)));
17358 QM_TRY(MOZ_TO_RESULT(
17359 aConnection->ExecuteCachedStatement("DELETE FROM object_store;"_ns)));
17360 } else {
17361 QM_TRY_INSPECT(
17362 const bool& hasIndexes,
17363 ObjectStoreHasIndexes(*aConnection, mMetadata->mCommonMetadata.id()));
17365 const auto bindObjectStoreIdToFirstParameter =
17366 [this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17367 QM_TRY(MOZ_TO_RESULT(
17368 stmt.BindInt64ByIndex(0, mMetadata->mCommonMetadata.id())));
17370 return Ok{};
17373 // The parameter name :object_store_id in the SQL statements below is not
17374 // used for binding, parameters are bound by index only locally by
17375 // bindObjectStoreIdToFirstParameter.
17376 if (hasIndexes) {
17377 QM_TRY(MOZ_TO_RESULT(DeleteObjectStoreDataTableRowsWithIndexes(
17378 aConnection, mMetadata->mCommonMetadata.id(), Nothing())));
17380 // Now clean up the object store index table.
17381 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17382 "DELETE FROM object_store_index "
17383 "WHERE object_store_id = :object_store_id;"_ns,
17384 bindObjectStoreIdToFirstParameter)));
17385 } else {
17386 // We only have to worry about object data if this object store has no
17387 // indexes.
17388 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17389 "DELETE FROM object_data "
17390 "WHERE object_store_id = :object_store_id;"_ns,
17391 bindObjectStoreIdToFirstParameter)));
17394 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17395 "DELETE FROM object_store "
17396 "WHERE id = :object_store_id;"_ns,
17397 bindObjectStoreIdToFirstParameter)));
17399 #ifdef DEBUG
17401 int32_t deletedRowCount;
17402 MOZ_ALWAYS_SUCCEEDS(
17403 aConnection->MutableStorageConnection().GetAffectedRows(
17404 &deletedRowCount));
17405 MOZ_ASSERT(deletedRowCount == 1);
17407 #endif
17410 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
17412 if (mMetadata->mCommonMetadata.autoIncrement()) {
17413 Transaction().ForgetModifiedAutoIncrementObjectStore(*mMetadata);
17416 return NS_OK;
17419 nsresult RenameObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17420 MOZ_ASSERT(aConnection);
17421 aConnection->AssertIsOnConnectionThread();
17423 AUTO_PROFILER_LABEL("RenameObjectStoreOp::DoDatabaseWork", DOM);
17425 #ifdef DEBUG
17427 // Make sure that we're not renaming an object store with the same name as
17428 // another that already exists. This should be impossible because we should
17429 // have thrown an error long before now...
17430 // The parameter names are not used, parameters are bound by index only
17431 // locally in the same function.
17432 QM_TRY_INSPECT(
17433 const bool& hasResult,
17434 aConnection
17435 ->BorrowAndExecuteSingleStepStatement(
17436 "SELECT name "
17437 "FROM object_store "
17438 "WHERE name = :name AND id != :id;"_ns,
17439 [&self = *this](auto& stmt) -> Result<Ok, nsresult> {
17440 QM_TRY(
17441 MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName)));
17443 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mId)));
17444 return Ok{};
17446 .map(IsSome),
17447 QM_ASSERT_UNREACHABLE);
17449 MOZ_ASSERT(!hasResult);
17451 #endif
17453 DatabaseConnection::AutoSavepoint autoSave;
17454 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17455 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17457 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17458 #endif
17461 // The parameter names are not used, parameters are bound by index only
17462 // locally in the same function.
17463 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17464 "UPDATE object_store "
17465 "SET name = :name "
17466 "WHERE id = :id;"_ns,
17467 [&self = *this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17468 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName)));
17470 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mId)));
17472 return Ok{};
17473 })));
17475 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
17477 return NS_OK;
17480 CreateIndexOp::CreateIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
17481 const IndexOrObjectStoreId aObjectStoreId,
17482 const IndexMetadata& aMetadata)
17483 : VersionChangeTransactionOp(std::move(aTransaction)),
17484 mMetadata(aMetadata),
17485 mFileManager(Transaction().GetDatabase().GetFileManagerPtr()),
17486 mDatabaseId(Transaction().DatabaseId()),
17487 mObjectStoreId(aObjectStoreId) {
17488 MOZ_ASSERT(aObjectStoreId);
17489 MOZ_ASSERT(aMetadata.id());
17490 MOZ_ASSERT(mFileManager);
17491 MOZ_ASSERT(!mDatabaseId.IsEmpty());
17494 nsresult CreateIndexOp::InsertDataFromObjectStore(
17495 DatabaseConnection* aConnection) {
17496 MOZ_ASSERT(aConnection);
17497 aConnection->AssertIsOnConnectionThread();
17498 MOZ_ASSERT(mMaybeUniqueIndexTable);
17500 AUTO_PROFILER_LABEL("CreateIndexOp::InsertDataFromObjectStore", DOM);
17502 auto& storageConnection = aConnection->MutableStorageConnection();
17504 RefPtr<UpdateIndexDataValuesFunction> updateFunction =
17505 new UpdateIndexDataValuesFunction(this, aConnection,
17506 Transaction().GetDatabasePtr());
17508 constexpr auto updateFunctionName = "update_index_data_values"_ns;
17510 nsresult rv =
17511 storageConnection.CreateFunction(updateFunctionName, 4, updateFunction);
17512 if (NS_WARN_IF(NS_FAILED(rv))) {
17513 return rv;
17516 rv = InsertDataFromObjectStoreInternal(aConnection);
17518 MOZ_ALWAYS_SUCCEEDS(storageConnection.RemoveFunction(updateFunctionName));
17520 if (NS_WARN_IF(NS_FAILED(rv))) {
17521 return rv;
17524 return NS_OK;
17527 nsresult CreateIndexOp::InsertDataFromObjectStoreInternal(
17528 DatabaseConnection* aConnection) const {
17529 MOZ_ASSERT(aConnection);
17530 aConnection->AssertIsOnConnectionThread();
17531 MOZ_ASSERT(mMaybeUniqueIndexTable);
17533 MOZ_ASSERT(aConnection->HasStorageConnection());
17535 // The parameter names are not used, parameters are bound by index only
17536 // locally in the same function.
17537 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17538 "UPDATE object_data "
17539 "SET index_data_values = update_index_data_values "
17540 "(key, index_data_values, file_ids, data) "
17541 "WHERE object_store_id = :object_store_id;"_ns,
17542 [objectStoredId =
17543 mObjectStoreId](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17544 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, objectStoredId)));
17546 return Ok{};
17547 })));
17549 return NS_OK;
17552 bool CreateIndexOp::Init(TransactionBase& aTransaction) {
17553 AssertIsOnBackgroundThread();
17554 MOZ_ASSERT(mObjectStoreId);
17555 MOZ_ASSERT(mMaybeUniqueIndexTable.isNothing());
17557 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
17558 aTransaction.GetMetadataForObjectStoreId(mObjectStoreId);
17559 MOZ_ASSERT(objectStoreMetadata);
17561 const uint32_t indexCount = objectStoreMetadata->mIndexes.Count();
17562 if (!indexCount) {
17563 return true;
17566 auto uniqueIndexTable = UniqueIndexTable{indexCount};
17568 for (const auto& value : objectStoreMetadata->mIndexes.Values()) {
17569 MOZ_ASSERT(!uniqueIndexTable.Contains(value->mCommonMetadata.id()));
17571 if (NS_WARN_IF(!uniqueIndexTable.InsertOrUpdate(
17572 value->mCommonMetadata.id(), value->mCommonMetadata.unique(),
17573 fallible))) {
17574 IDB_REPORT_INTERNAL_ERR();
17575 NS_WARNING("out of memory");
17576 return false;
17580 uniqueIndexTable.MarkImmutable();
17582 mMaybeUniqueIndexTable.emplace(std::move(uniqueIndexTable));
17584 return true;
17587 nsresult CreateIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17588 MOZ_ASSERT(aConnection);
17589 aConnection->AssertIsOnConnectionThread();
17591 AUTO_PROFILER_LABEL("CreateIndexOp::DoDatabaseWork", DOM);
17593 #ifdef DEBUG
17595 // Make sure that we're not creating an index with the same name and object
17596 // store as another that already exists. This should be impossible because
17597 // we should have thrown an error long before now...
17598 // The parameter names are not used, parameters are bound by index only
17599 // locally in the same function.
17600 QM_TRY_INSPECT(
17601 const bool& hasResult,
17602 aConnection
17603 ->BorrowAndExecuteSingleStepStatement(
17604 "SELECT name "
17605 "FROM object_store_index "
17606 "WHERE object_store_id = :object_store_id AND name = :name;"_ns,
17607 [&self = *this](auto& stmt) -> Result<Ok, nsresult> {
17608 QM_TRY(MOZ_TO_RESULT(
17609 stmt.BindInt64ByIndex(0, self.mObjectStoreId)));
17610 QM_TRY(MOZ_TO_RESULT(
17611 stmt.BindStringByIndex(1, self.mMetadata.name())));
17612 return Ok{};
17614 .map(IsSome),
17615 QM_ASSERT_UNREACHABLE);
17617 MOZ_ASSERT(!hasResult);
17619 #endif
17621 DatabaseConnection::AutoSavepoint autoSave;
17622 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17623 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17625 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17626 #endif
17629 // The parameter names are not used, parameters are bound by index only
17630 // locally in the same function.
17631 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17632 "INSERT INTO object_store_index (id, name, key_path, unique_index, "
17633 "multientry, object_store_id, locale, "
17634 "is_auto_locale) "
17635 "VALUES (:id, :name, :key_path, :unique, :multientry, "
17636 ":object_store_id, :locale, :is_auto_locale)"_ns,
17637 [&metadata = mMetadata, objectStoreId = mObjectStoreId](
17638 mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17639 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, metadata.id())));
17641 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(1, metadata.name())));
17643 QM_TRY(MOZ_TO_RESULT(
17644 stmt.BindStringByIndex(2, metadata.keyPath().SerializeToString())));
17646 QM_TRY(
17647 MOZ_TO_RESULT(stmt.BindInt32ByIndex(3, metadata.unique() ? 1 : 0)));
17649 QM_TRY(MOZ_TO_RESULT(
17650 stmt.BindInt32ByIndex(4, metadata.multiEntry() ? 1 : 0)));
17651 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(5, objectStoreId)));
17653 QM_TRY(MOZ_TO_RESULT(
17654 metadata.locale().IsEmpty()
17655 ? stmt.BindNullByIndex(6)
17656 : stmt.BindUTF8StringByIndex(6, metadata.locale())));
17658 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByIndex(7, metadata.autoLocale())));
17660 return Ok{};
17661 })));
17663 #ifdef DEBUG
17665 int64_t id;
17666 MOZ_ALWAYS_SUCCEEDS(
17667 aConnection->MutableStorageConnection().GetLastInsertRowID(&id));
17668 MOZ_ASSERT(mMetadata.id() == id);
17670 #endif
17672 QM_TRY(MOZ_TO_RESULT(InsertDataFromObjectStore(aConnection)));
17674 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
17676 return NS_OK;
17679 NS_IMPL_ISUPPORTS(CreateIndexOp::UpdateIndexDataValuesFunction,
17680 mozIStorageFunction);
17682 NS_IMETHODIMP
17683 CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall(
17684 mozIStorageValueArray* aValues, nsIVariant** _retval) {
17685 MOZ_ASSERT(aValues);
17686 MOZ_ASSERT(_retval);
17687 MOZ_ASSERT(mConnection);
17688 mConnection->AssertIsOnConnectionThread();
17689 MOZ_ASSERT(mOp);
17690 MOZ_ASSERT(mOp->mFileManager);
17692 AUTO_PROFILER_LABEL(
17693 "CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall", DOM);
17695 #ifdef DEBUG
17697 uint32_t argCount;
17698 MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
17699 MOZ_ASSERT(argCount == 4); // key, index_data_values, file_ids, data
17701 int32_t valueType;
17702 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
17703 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
17705 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType));
17706 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
17707 valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
17709 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType));
17710 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
17711 valueType == mozIStorageValueArray::VALUE_TYPE_TEXT);
17713 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType));
17714 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB ||
17715 valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
17717 #endif
17719 QM_TRY_UNWRAP(auto cloneInfo, GetStructuredCloneReadInfoFromValueArray(
17720 aValues,
17721 /* aDataIndex */ 3,
17722 /* aFileIdsIndex */ 2, *mOp->mFileManager));
17724 const IndexMetadata& metadata = mOp->mMetadata;
17725 const IndexOrObjectStoreId& objectStoreId = mOp->mObjectStoreId;
17727 // XXX does this really need a non-const cloneInfo?
17728 QM_TRY_INSPECT(const auto& updateInfos,
17729 DeserializeIndexValueToUpdateInfos(
17730 metadata.id(), metadata.keyPath(), metadata.multiEntry(),
17731 metadata.locale(), cloneInfo));
17733 if (updateInfos.IsEmpty()) {
17734 // XXX See if we can do this without copying...
17736 nsCOMPtr<nsIVariant> unmodifiedValue;
17738 // No changes needed, just return the original value.
17739 QM_TRY_INSPECT(const int32_t& valueType,
17740 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 1));
17742 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
17743 valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
17745 if (valueType == mozIStorageValueArray::VALUE_TYPE_NULL) {
17746 unmodifiedValue = new storage::NullVariant();
17747 unmodifiedValue.forget(_retval);
17748 return NS_OK;
17751 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
17753 const uint8_t* blobData;
17754 uint32_t blobDataLength;
17755 QM_TRY(
17756 MOZ_TO_RESULT(aValues->GetSharedBlob(1, &blobDataLength, &blobData)));
17758 const std::pair<uint8_t*, int> copiedBlobDataPair(
17759 static_cast<uint8_t*>(malloc(blobDataLength)), blobDataLength);
17761 if (!copiedBlobDataPair.first) {
17762 IDB_REPORT_INTERNAL_ERR();
17763 return NS_ERROR_OUT_OF_MEMORY;
17766 memcpy(copiedBlobDataPair.first, blobData, blobDataLength);
17768 unmodifiedValue = new storage::AdoptedBlobVariant(copiedBlobDataPair);
17769 unmodifiedValue.forget(_retval);
17771 return NS_OK;
17774 Key key;
17775 QM_TRY(MOZ_TO_RESULT(key.SetFromValueArray(aValues, 0)));
17777 QM_TRY_UNWRAP(auto indexValues, ReadCompressedIndexDataValues(*aValues, 1));
17779 const bool hadPreviousIndexValues = !indexValues.IsEmpty();
17781 const uint32_t updateInfoCount = updateInfos.Length();
17783 QM_TRY(OkIf(indexValues.SetCapacity(indexValues.Length() + updateInfoCount,
17784 fallible)),
17785 NS_ERROR_OUT_OF_MEMORY, IDB_REPORT_INTERNAL_ERR_LAMBDA);
17787 // First construct the full list to update the index_data_values row.
17788 for (const IndexUpdateInfo& info : updateInfos) {
17789 MOZ_ALWAYS_TRUE(indexValues.InsertElementSorted(
17790 IndexDataValue(metadata.id(), metadata.unique(), info.value(),
17791 info.localizedValue()),
17792 fallible));
17795 QM_TRY_UNWRAP((auto [indexValuesBlob, indexValuesBlobLength]),
17796 MakeCompressedIndexDataValues(indexValues));
17798 MOZ_ASSERT(!indexValuesBlobLength == !(indexValuesBlob.get()));
17800 nsCOMPtr<nsIVariant> value;
17802 if (!indexValuesBlob) {
17803 value = new storage::NullVariant();
17805 value.forget(_retval);
17806 return NS_OK;
17809 // Now insert the new table rows. We only need to construct a new list if
17810 // the full list is different.
17811 if (hadPreviousIndexValues) {
17812 indexValues.ClearAndRetainStorage();
17814 MOZ_ASSERT(indexValues.Capacity() >= updateInfoCount);
17816 for (const IndexUpdateInfo& info : updateInfos) {
17817 MOZ_ALWAYS_TRUE(indexValues.InsertElementSorted(
17818 IndexDataValue(metadata.id(), metadata.unique(), info.value(),
17819 info.localizedValue()),
17820 fallible));
17824 QM_TRY(MOZ_TO_RESULT(
17825 InsertIndexTableRows(mConnection, objectStoreId, key, indexValues)));
17827 value = new storage::AdoptedBlobVariant(
17828 std::pair(indexValuesBlob.release(), indexValuesBlobLength));
17830 value.forget(_retval);
17831 return NS_OK;
17834 DeleteIndexOp::DeleteIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
17835 const IndexOrObjectStoreId aObjectStoreId,
17836 const IndexOrObjectStoreId aIndexId,
17837 const bool aUnique, const bool aIsLastIndex)
17838 : VersionChangeTransactionOp(std::move(aTransaction)),
17839 mObjectStoreId(aObjectStoreId),
17840 mIndexId(aIndexId),
17841 mUnique(aUnique),
17842 mIsLastIndex(aIsLastIndex) {
17843 MOZ_ASSERT(aObjectStoreId);
17844 MOZ_ASSERT(aIndexId);
17847 nsresult DeleteIndexOp::RemoveReferencesToIndex(
17848 DatabaseConnection* aConnection, const Key& aObjectStoreKey,
17849 nsTArray<IndexDataValue>& aIndexValues) const {
17850 MOZ_ASSERT(!NS_IsMainThread());
17851 MOZ_ASSERT(!IsOnBackgroundThread());
17852 MOZ_ASSERT(aConnection);
17853 MOZ_ASSERT(!aObjectStoreKey.IsUnset());
17854 MOZ_ASSERT_IF(!mIsLastIndex, !aIndexValues.IsEmpty());
17856 AUTO_PROFILER_LABEL("DeleteIndexOp::RemoveReferencesToIndex", DOM);
17858 if (mIsLastIndex) {
17859 // There is no need to parse the previous entry in the index_data_values
17860 // column if this is the last index. Simply set it to NULL.
17861 QM_TRY_INSPECT(const auto& stmt,
17862 aConnection->BorrowCachedStatement(
17863 "UPDATE object_data "
17864 "SET index_data_values = NULL "
17865 "WHERE object_store_id = :"_ns +
17866 kStmtParamNameObjectStoreId + " AND key = :"_ns +
17867 kStmtParamNameKey + ";"_ns));
17869 QM_TRY(MOZ_TO_RESULT(
17870 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId)));
17872 QM_TRY(MOZ_TO_RESULT(
17873 aObjectStoreKey.BindToStatement(&*stmt, kStmtParamNameKey)));
17875 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
17877 return NS_OK;
17881 IndexDataValue search;
17882 search.mIndexId = mIndexId;
17884 // Use raw pointers for search to avoid redundant index validity checks.
17885 // Maybe this should better be encapsulated in nsTArray.
17886 const auto* const begin = aIndexValues.Elements();
17887 const auto* const end = aIndexValues.Elements() + aIndexValues.Length();
17889 const auto indexIdComparator = [](const IndexDataValue& aA,
17890 const IndexDataValue& aB) {
17891 return aA.mIndexId < aB.mIndexId;
17894 MOZ_ASSERT(std::is_sorted(begin, end, indexIdComparator));
17896 const auto [beginRange, endRange] =
17897 std::equal_range(begin, end, search, indexIdComparator);
17898 if (beginRange == end) {
17899 IDB_REPORT_INTERNAL_ERR();
17900 return NS_ERROR_FILE_CORRUPTED;
17903 aIndexValues.RemoveElementsAt(beginRange - begin, endRange - beginRange);
17906 QM_TRY(MOZ_TO_RESULT(UpdateIndexValues(aConnection, mObjectStoreId,
17907 aObjectStoreKey, aIndexValues)));
17909 return NS_OK;
17912 nsresult DeleteIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17913 MOZ_ASSERT(aConnection);
17914 aConnection->AssertIsOnConnectionThread();
17916 #ifdef DEBUG
17918 // Make sure |mIsLastIndex| is telling the truth.
17919 // The parameter names are not used, parameters are bound by index only
17920 // locally in the same function.
17921 QM_TRY_INSPECT(const auto& stmt,
17922 aConnection->BorrowCachedStatement(
17923 "SELECT id "
17924 "FROM object_store_index "
17925 "WHERE object_store_id = :object_store_id;"_ns),
17926 QM_ASSERT_UNREACHABLE);
17928 MOZ_ALWAYS_SUCCEEDS(stmt->BindInt64ByIndex(0, mObjectStoreId));
17930 bool foundThisIndex = false;
17931 bool foundOtherIndex = false;
17933 while (true) {
17934 bool hasResult;
17935 MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
17937 if (!hasResult) {
17938 break;
17941 int64_t id;
17942 MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id));
17944 if (id == mIndexId) {
17945 foundThisIndex = true;
17946 } else {
17947 foundOtherIndex = true;
17951 MOZ_ASSERT_IF(mIsLastIndex, foundThisIndex && !foundOtherIndex);
17952 MOZ_ASSERT_IF(!mIsLastIndex, foundThisIndex && foundOtherIndex);
17954 #endif
17956 AUTO_PROFILER_LABEL("DeleteIndexOp::DoDatabaseWork", DOM);
17958 DatabaseConnection::AutoSavepoint autoSave;
17959 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17960 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17962 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17963 #endif
17966 // mozStorage warns that these statements trigger a sort operation but we
17967 // don't care because this is a very rare call and we expect it to be slow.
17968 // The cost of having an index on this field is too high.
17969 QM_TRY_INSPECT(
17970 const auto& selectStmt,
17971 aConnection->BorrowCachedStatement(
17972 mUnique
17973 ? (mIsLastIndex
17974 ? "/* do not warn (bug someone else) */ "
17975 "SELECT value, object_data_key "
17976 "FROM unique_index_data "
17977 "WHERE index_id = :"_ns +
17978 kStmtParamNameIndexId +
17979 " ORDER BY object_data_key ASC;"_ns
17980 : "/* do not warn (bug out) */ "
17981 "SELECT unique_index_data.value, "
17982 "unique_index_data.object_data_key, "
17983 "object_data.index_data_values "
17984 "FROM unique_index_data "
17985 "JOIN object_data "
17986 "ON unique_index_data.object_data_key = object_data.key "
17987 "WHERE unique_index_data.index_id = :"_ns +
17988 kStmtParamNameIndexId +
17989 " AND object_data.object_store_id = :"_ns +
17990 kStmtParamNameObjectStoreId +
17991 " ORDER BY unique_index_data.object_data_key ASC;"_ns)
17992 : (mIsLastIndex
17993 ? "/* do not warn (bug me not) */ "
17994 "SELECT value, object_data_key "
17995 "FROM index_data "
17996 "WHERE index_id = :"_ns +
17997 kStmtParamNameIndexId +
17998 " AND object_store_id = :"_ns +
17999 kStmtParamNameObjectStoreId +
18000 " ORDER BY object_data_key ASC;"_ns
18001 : "/* do not warn (bug off) */ "
18002 "SELECT index_data.value, "
18003 "index_data.object_data_key, "
18004 "object_data.index_data_values "
18005 "FROM index_data "
18006 "JOIN object_data "
18007 "ON index_data.object_data_key = object_data.key "
18008 "WHERE index_data.index_id = :"_ns +
18009 kStmtParamNameIndexId +
18010 " AND object_data.object_store_id = :"_ns +
18011 kStmtParamNameObjectStoreId +
18012 " ORDER BY index_data.object_data_key ASC;"_ns)));
18014 QM_TRY(MOZ_TO_RESULT(
18015 selectStmt->BindInt64ByName(kStmtParamNameIndexId, mIndexId)));
18017 if (!mUnique || !mIsLastIndex) {
18018 QM_TRY(MOZ_TO_RESULT(selectStmt->BindInt64ByName(
18019 kStmtParamNameObjectStoreId, mObjectStoreId)));
18022 Key lastObjectStoreKey;
18023 IndexDataValuesAutoArray lastIndexValues;
18025 QM_TRY(CollectWhileHasResult(
18026 *selectStmt,
18027 [this, &aConnection, &lastObjectStoreKey, &lastIndexValues,
18028 deleteIndexRowStmt =
18029 DatabaseConnection::LazyStatement{
18030 *aConnection,
18031 mUnique
18032 ? "DELETE FROM unique_index_data "
18033 "WHERE index_id = :"_ns +
18034 kStmtParamNameIndexId + " AND value = :"_ns +
18035 kStmtParamNameValue + ";"_ns
18036 : "DELETE FROM index_data "
18037 "WHERE index_id = :"_ns +
18038 kStmtParamNameIndexId + " AND value = :"_ns +
18039 kStmtParamNameValue + " AND object_data_key = :"_ns +
18040 kStmtParamNameObjectDataKey + ";"_ns}](
18041 auto& selectStmt) mutable -> Result<Ok, nsresult> {
18042 // We always need the index key to delete the index row.
18043 Key indexKey;
18044 QM_TRY(MOZ_TO_RESULT(indexKey.SetFromStatement(&selectStmt, 0)));
18046 QM_TRY(OkIf(!indexKey.IsUnset()), Err(NS_ERROR_FILE_CORRUPTED),
18047 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18049 // Don't call |lastObjectStoreKey.BindToStatement()| directly because we
18050 // don't want to copy the same key multiple times.
18051 const uint8_t* objectStoreKeyData;
18052 uint32_t objectStoreKeyDataLength;
18053 QM_TRY(MOZ_TO_RESULT(selectStmt.GetSharedBlob(
18054 1, &objectStoreKeyDataLength, &objectStoreKeyData)));
18056 QM_TRY(OkIf(objectStoreKeyDataLength), Err(NS_ERROR_FILE_CORRUPTED),
18057 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18059 const nsDependentCString currentObjectStoreKeyBuffer(
18060 reinterpret_cast<const char*>(objectStoreKeyData),
18061 objectStoreKeyDataLength);
18062 if (currentObjectStoreKeyBuffer != lastObjectStoreKey.GetBuffer()) {
18063 // We just walked to the next object store key.
18064 if (!lastObjectStoreKey.IsUnset()) {
18065 // Before we move on to the next key we need to update the previous
18066 // key's index_data_values column.
18067 QM_TRY(MOZ_TO_RESULT(RemoveReferencesToIndex(
18068 aConnection, lastObjectStoreKey, lastIndexValues)));
18071 // Save the object store key.
18072 lastObjectStoreKey = Key(currentObjectStoreKeyBuffer);
18074 // And the |index_data_values| row if this isn't the only index.
18075 if (!mIsLastIndex) {
18076 lastIndexValues.ClearAndRetainStorage();
18077 QM_TRY(MOZ_TO_RESULT(
18078 ReadCompressedIndexDataValues(selectStmt, 2, lastIndexValues)));
18080 QM_TRY(OkIf(!lastIndexValues.IsEmpty()),
18081 Err(NS_ERROR_FILE_CORRUPTED),
18082 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18086 // Now delete the index row.
18088 QM_TRY_INSPECT(const auto& borrowedDeleteIndexRowStmt,
18089 deleteIndexRowStmt.Borrow());
18091 QM_TRY(MOZ_TO_RESULT(borrowedDeleteIndexRowStmt->BindInt64ByName(
18092 kStmtParamNameIndexId, mIndexId)));
18094 QM_TRY(MOZ_TO_RESULT(indexKey.BindToStatement(
18095 &*borrowedDeleteIndexRowStmt, kStmtParamNameValue)));
18097 if (!mUnique) {
18098 QM_TRY(MOZ_TO_RESULT(lastObjectStoreKey.BindToStatement(
18099 &*borrowedDeleteIndexRowStmt, kStmtParamNameObjectDataKey)));
18102 QM_TRY(MOZ_TO_RESULT(borrowedDeleteIndexRowStmt->Execute()));
18105 return Ok{};
18106 }));
18108 // Take care of the last key.
18109 if (!lastObjectStoreKey.IsUnset()) {
18110 MOZ_ASSERT_IF(!mIsLastIndex, !lastIndexValues.IsEmpty());
18112 QM_TRY(MOZ_TO_RESULT(RemoveReferencesToIndex(
18113 aConnection, lastObjectStoreKey, lastIndexValues)));
18116 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
18117 "DELETE FROM object_store_index "
18118 "WHERE id = :index_id;"_ns,
18119 [indexId =
18120 mIndexId](mozIStorageStatement& deleteStmt) -> Result<Ok, nsresult> {
18121 QM_TRY(MOZ_TO_RESULT(deleteStmt.BindInt64ByIndex(0, indexId)));
18123 return Ok{};
18124 })));
18126 #ifdef DEBUG
18128 int32_t deletedRowCount;
18129 MOZ_ALWAYS_SUCCEEDS(aConnection->MutableStorageConnection().GetAffectedRows(
18130 &deletedRowCount));
18131 MOZ_ASSERT(deletedRowCount == 1);
18133 #endif
18135 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
18137 return NS_OK;
18140 nsresult RenameIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) {
18141 MOZ_ASSERT(aConnection);
18142 aConnection->AssertIsOnConnectionThread();
18144 AUTO_PROFILER_LABEL("RenameIndexOp::DoDatabaseWork", DOM);
18146 #ifdef DEBUG
18148 // Make sure that we're not renaming an index with the same name as another
18149 // that already exists. This should be impossible because we should have
18150 // thrown an error long before now...
18151 // The parameter names are not used, parameters are bound by index only
18152 // locally in the same function.
18153 QM_TRY_INSPECT(const bool& hasResult,
18154 aConnection
18155 ->BorrowAndExecuteSingleStepStatement(
18156 "SELECT name "
18157 "FROM object_store_index "
18158 "WHERE object_store_id = :object_store_id "
18159 "AND name = :name "
18160 "AND id != :id;"_ns,
18161 [&self = *this](auto& stmt) -> Result<Ok, nsresult> {
18162 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(
18163 0, self.mObjectStoreId)));
18164 QM_TRY(MOZ_TO_RESULT(
18165 stmt.BindStringByIndex(1, self.mNewName)));
18166 QM_TRY(MOZ_TO_RESULT(
18167 stmt.BindInt64ByIndex(2, self.mIndexId)));
18169 return Ok{};
18171 .map(IsSome),
18172 QM_ASSERT_UNREACHABLE);
18174 MOZ_ASSERT(!hasResult);
18176 #else
18177 Unused << mObjectStoreId;
18178 #endif
18180 DatabaseConnection::AutoSavepoint autoSave;
18181 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
18182 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
18184 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
18185 #endif
18188 // The parameter names are not used, parameters are bound by index only
18189 // locally in the same function.
18190 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
18191 "UPDATE object_store_index "
18192 "SET name = :name "
18193 "WHERE id = :id;"_ns,
18194 [&self = *this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
18195 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName)));
18197 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mIndexId)));
18199 return Ok{};
18200 })));
18202 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
18204 return NS_OK;
18207 Result<bool, nsresult> NormalTransactionOp::ObjectStoreHasIndexes(
18208 DatabaseConnection& aConnection, const IndexOrObjectStoreId aObjectStoreId,
18209 const bool aMayHaveIndexes) {
18210 aConnection.AssertIsOnConnectionThread();
18211 MOZ_ASSERT(aObjectStoreId);
18213 if (Transaction().GetMode() == IDBTransaction::Mode::VersionChange &&
18214 aMayHaveIndexes) {
18215 // If this is a version change transaction then mObjectStoreMayHaveIndexes
18216 // could be wrong (e.g. if a unique index failed to be created due to a
18217 // constraint error). We have to check on this thread by asking the database
18218 // directly.
18219 QM_TRY_RETURN(DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
18220 aObjectStoreId));
18223 #ifdef DEBUG
18224 QM_TRY_INSPECT(
18225 const bool& hasIndexes,
18226 DatabaseOperationBase::ObjectStoreHasIndexes(aConnection, aObjectStoreId),
18227 QM_ASSERT_UNREACHABLE);
18228 MOZ_ASSERT(aMayHaveIndexes == hasIndexes);
18229 #endif
18231 return aMayHaveIndexes;
18234 Result<PreprocessParams, nsresult> NormalTransactionOp::GetPreprocessParams() {
18235 return PreprocessParams{};
18238 nsresult NormalTransactionOp::SendPreprocessInfo() {
18239 AssertIsOnOwningThread();
18240 MOZ_ASSERT(!IsActorDestroyed());
18242 QM_TRY_INSPECT(const auto& params, GetPreprocessParams());
18244 MOZ_ASSERT(params.type() != PreprocessParams::T__None);
18246 if (NS_WARN_IF(!PBackgroundIDBRequestParent::SendPreprocess(params))) {
18247 IDB_REPORT_INTERNAL_ERR();
18248 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
18251 return NS_OK;
18254 nsresult NormalTransactionOp::SendSuccessResult() {
18255 AssertIsOnOwningThread();
18257 if (!IsActorDestroyed()) {
18258 static const size_t kMaxIDBMsgOverhead = 1024 * 1024 * 10; // 10MB
18259 const uint32_t maximalSizeFromPref =
18260 IndexedDatabaseManager::MaxSerializedMsgSize();
18261 MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead);
18262 const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead;
18264 RequestResponse response;
18265 size_t responseSize = kMaxMessageSize;
18266 GetResponse(response, &responseSize);
18268 if (responseSize >= kMaxMessageSize) {
18269 nsPrintfCString warning(
18270 "The serialized value is too large"
18271 " (size=%zu bytes, max=%zu bytes).",
18272 responseSize, kMaxMessageSize);
18273 NS_WARNING(warning.get());
18274 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
18277 MOZ_ASSERT(response.type() != RequestResponse::T__None);
18279 if (response.type() == RequestResponse::Tnsresult) {
18280 MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
18282 return response.get_nsresult();
18285 if (NS_WARN_IF(
18286 !PBackgroundIDBRequestParent::Send__delete__(this, response))) {
18287 IDB_REPORT_INTERNAL_ERR();
18288 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
18292 #ifdef DEBUG
18293 mResponseSent = true;
18294 #endif
18296 return NS_OK;
18299 bool NormalTransactionOp::SendFailureResult(nsresult aResultCode) {
18300 AssertIsOnOwningThread();
18301 MOZ_ASSERT(NS_FAILED(aResultCode));
18303 bool result = false;
18305 if (!IsActorDestroyed()) {
18306 result = PBackgroundIDBRequestParent::Send__delete__(
18307 this, ClampResultCode(aResultCode));
18310 #ifdef DEBUG
18311 mResponseSent = true;
18312 #endif
18314 return result;
18317 void NormalTransactionOp::Cleanup() {
18318 AssertIsOnOwningThread();
18319 MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
18321 TransactionDatabaseOperationBase::Cleanup();
18324 void NormalTransactionOp::ActorDestroy(ActorDestroyReason aWhy) {
18325 AssertIsOnOwningThread();
18327 NoteActorDestroyed();
18329 // Assume ActorDestroy can happen at any time, so we can't probe the current
18330 // state since mInternalState can be modified on any thread (only one thread
18331 // at a time based on the state machine).
18332 // However we can use mWaitingForContinue which is only touched on the owning
18333 // thread. If mWaitingForContinue is true, we can also modify mInternalState
18334 // since we are guaranteed that there are no pending runnables which would
18335 // probe mInternalState to decide what code needs to run (there shouldn't be
18336 // any running runnables on other threads either).
18338 if (IsWaitingForContinue()) {
18339 NoteContinueReceived();
18342 // We don't have to handle the case when mWaitingForContinue is not true since
18343 // it means that either nothing has been initialized yet, so nothing to
18344 // cleanup or there are pending runnables that will detect that the actor has
18345 // been destroyed and cleanup accordingly.
18348 mozilla::ipc::IPCResult NormalTransactionOp::RecvContinue(
18349 const PreprocessResponse& aResponse) {
18350 AssertIsOnOwningThread();
18352 switch (aResponse.type()) {
18353 case PreprocessResponse::Tnsresult:
18354 SetFailureCode(aResponse.get_nsresult());
18355 break;
18357 case PreprocessResponse::TObjectStoreGetPreprocessResponse:
18358 case PreprocessResponse::TObjectStoreGetAllPreprocessResponse:
18359 break;
18361 default:
18362 MOZ_CRASH("Should never get here!");
18365 NoteContinueReceived();
18367 return IPC_OK();
18370 ObjectStoreAddOrPutRequestOp::ObjectStoreAddOrPutRequestOp(
18371 SafeRefPtr<TransactionBase> aTransaction, RequestParams&& aParams)
18372 : NormalTransactionOp(std::move(aTransaction)),
18373 mParams(
18374 std::move(aParams.type() == RequestParams::TObjectStoreAddParams
18375 ? aParams.get_ObjectStoreAddParams().commonParams()
18376 : aParams.get_ObjectStorePutParams().commonParams())),
18377 mOriginMetadata(Transaction().GetDatabase().OriginMetadata()),
18378 mPersistenceType(Transaction().GetDatabase().Type()),
18379 mOverwrite(aParams.type() == RequestParams::TObjectStorePutParams),
18380 mObjectStoreMayHaveIndexes(false) {
18381 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreAddParams ||
18382 aParams.type() == RequestParams::TObjectStorePutParams);
18384 mMetadata =
18385 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId());
18386 MOZ_ASSERT(mMetadata);
18388 mObjectStoreMayHaveIndexes = mMetadata->HasLiveIndexes();
18390 mDataOverThreshold =
18391 snappy::MaxCompressedLength(mParams.cloneInfo().data().data.Size()) >
18392 IndexedDatabaseManager::DataThreshold();
18395 nsresult ObjectStoreAddOrPutRequestOp::RemoveOldIndexDataValues(
18396 DatabaseConnection* aConnection) {
18397 AssertIsOnConnectionThread();
18398 MOZ_ASSERT(aConnection);
18399 MOZ_ASSERT(mOverwrite);
18400 MOZ_ASSERT(!mResponse.IsUnset());
18402 #ifdef DEBUG
18404 QM_TRY_INSPECT(const bool& hasIndexes,
18405 DatabaseOperationBase::ObjectStoreHasIndexes(
18406 *aConnection, mParams.objectStoreId()),
18407 QM_ASSERT_UNREACHABLE);
18409 MOZ_ASSERT(hasIndexes,
18410 "Don't use this slow method if there are no indexes!");
18412 #endif
18414 QM_TRY_INSPECT(
18415 const auto& indexValuesStmt,
18416 aConnection->BorrowAndExecuteSingleStepStatement(
18417 "SELECT index_data_values "
18418 "FROM object_data "
18419 "WHERE object_store_id = :"_ns +
18420 kStmtParamNameObjectStoreId + " AND key = :"_ns +
18421 kStmtParamNameKey + ";"_ns,
18422 [&self = *this](auto& stmt) -> mozilla::Result<Ok, nsresult> {
18423 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(
18424 kStmtParamNameObjectStoreId, self.mParams.objectStoreId())));
18426 QM_TRY(MOZ_TO_RESULT(
18427 self.mResponse.BindToStatement(&stmt, kStmtParamNameKey)));
18429 return Ok{};
18430 }));
18432 if (indexValuesStmt) {
18433 QM_TRY_INSPECT(const auto& existingIndexValues,
18434 ReadCompressedIndexDataValues(**indexValuesStmt, 0));
18436 QM_TRY(MOZ_TO_RESULT(
18437 DeleteIndexDataTableRows(aConnection, mResponse, existingIndexValues)));
18440 return NS_OK;
18443 bool ObjectStoreAddOrPutRequestOp::Init(TransactionBase& aTransaction) {
18444 AssertIsOnOwningThread();
18446 const nsTArray<IndexUpdateInfo>& indexUpdateInfos =
18447 mParams.indexUpdateInfos();
18449 if (!indexUpdateInfos.IsEmpty()) {
18450 mUniqueIndexTable.emplace();
18452 for (const auto& updateInfo : indexUpdateInfos) {
18453 auto indexMetadata = mMetadata->mIndexes.Lookup(updateInfo.indexId());
18454 MOZ_ALWAYS_TRUE(indexMetadata);
18456 MOZ_ASSERT(!(*indexMetadata)->mDeleted);
18458 const IndexOrObjectStoreId& indexId =
18459 (*indexMetadata)->mCommonMetadata.id();
18460 const bool& unique = (*indexMetadata)->mCommonMetadata.unique();
18462 MOZ_ASSERT(indexId == updateInfo.indexId());
18463 MOZ_ASSERT_IF(!(*indexMetadata)->mCommonMetadata.multiEntry(),
18464 !mUniqueIndexTable.ref().Contains(indexId));
18466 if (NS_WARN_IF(!mUniqueIndexTable.ref().InsertOrUpdate(indexId, unique,
18467 fallible))) {
18468 return false;
18471 } else if (mOverwrite) {
18472 mUniqueIndexTable.emplace();
18475 if (mUniqueIndexTable.isSome()) {
18476 mUniqueIndexTable.ref().MarkImmutable();
18479 QM_TRY_UNWRAP(
18480 mStoredFileInfos,
18481 TransformIntoNewArray(
18482 mParams.fileAddInfos(),
18483 [](const auto& fileAddInfo) {
18484 MOZ_ASSERT(fileAddInfo.type() == StructuredCloneFileBase::eBlob ||
18485 fileAddInfo.type() ==
18486 StructuredCloneFileBase::eMutableFile);
18488 switch (fileAddInfo.type()) {
18489 case StructuredCloneFileBase::eBlob: {
18490 PBackgroundIDBDatabaseFileParent* file =
18491 fileAddInfo.fileParent();
18492 MOZ_ASSERT(file);
18494 auto* const fileActor = static_cast<DatabaseFile*>(file);
18495 MOZ_ASSERT(fileActor);
18497 return StoredFileInfo::CreateForBlob(
18498 fileActor->GetFileInfoPtr(), fileActor);
18501 default:
18502 MOZ_CRASH("Should never get here!");
18505 fallible),
18506 false);
18508 if (mDataOverThreshold) {
18509 auto fileInfo =
18510 aTransaction.GetDatabase().GetFileManager().CreateFileInfo();
18511 if (NS_WARN_IF(!fileInfo)) {
18512 return false;
18515 mStoredFileInfos.EmplaceBack(StoredFileInfo::CreateForStructuredClone(
18516 std::move(fileInfo),
18517 MakeRefPtr<SCInputStream>(mParams.cloneInfo().data().data)));
18520 return true;
18523 nsresult ObjectStoreAddOrPutRequestOp::DoDatabaseWork(
18524 DatabaseConnection* aConnection) {
18525 MOZ_ASSERT(aConnection);
18526 aConnection->AssertIsOnConnectionThread();
18527 MOZ_ASSERT(aConnection->HasStorageConnection());
18529 AUTO_PROFILER_LABEL("ObjectStoreAddOrPutRequestOp::DoDatabaseWork", DOM);
18531 DatabaseConnection::AutoSavepoint autoSave;
18532 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
18533 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
18535 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
18536 #endif
18539 QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
18540 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
18541 mObjectStoreMayHaveIndexes));
18543 // This will be the final key we use.
18544 Key& key = mResponse;
18545 key = mParams.key();
18547 const bool keyUnset = key.IsUnset();
18548 const IndexOrObjectStoreId osid = mParams.objectStoreId();
18550 // First delete old index_data_values if we're overwriting something and we
18551 // have indexes.
18552 if (mOverwrite && !keyUnset && objectStoreHasIndexes) {
18553 QM_TRY(MOZ_TO_RESULT(RemoveOldIndexDataValues(aConnection)));
18556 int64_t autoIncrementNum = 0;
18559 // The "|| keyUnset" here is mostly a debugging tool. If a key isn't
18560 // specified we should never have a collision and so it shouldn't matter
18561 // if we allow overwrite or not. By not allowing overwrite we raise
18562 // detectable errors rather than corrupting data.
18563 const auto optReplaceDirective =
18564 (!mOverwrite || keyUnset) ? ""_ns : "OR REPLACE "_ns;
18565 QM_TRY_INSPECT(const auto& stmt,
18566 aConnection->BorrowCachedStatement(
18567 "INSERT "_ns + optReplaceDirective +
18568 "INTO object_data "
18569 "(object_store_id, key, file_ids, data) "
18570 "VALUES (:"_ns +
18571 kStmtParamNameObjectStoreId + ", :"_ns +
18572 kStmtParamNameKey + ", :"_ns + kStmtParamNameFileIds +
18573 ", :"_ns + kStmtParamNameData + ");"_ns));
18575 QM_TRY(MOZ_TO_RESULT(
18576 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, osid)));
18578 const SerializedStructuredCloneWriteInfo& cloneInfo = mParams.cloneInfo();
18579 const JSStructuredCloneData& cloneData = cloneInfo.data().data;
18580 const size_t cloneDataSize = cloneData.Size();
18582 MOZ_ASSERT(!keyUnset || mMetadata->mCommonMetadata.autoIncrement(),
18583 "Should have key unless autoIncrement");
18585 if (mMetadata->mCommonMetadata.autoIncrement()) {
18586 if (keyUnset) {
18588 const auto&& lockedAutoIncrementIds =
18589 mMetadata->mAutoIncrementIds.Lock();
18591 autoIncrementNum = lockedAutoIncrementIds->next;
18594 MOZ_ASSERT(autoIncrementNum > 0);
18596 if (autoIncrementNum > (1LL << 53)) {
18597 return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
18600 QM_TRY(key.SetFromInteger(autoIncrementNum));
18601 } else if (key.IsFloat()) {
18602 double numericKey = key.ToFloat();
18603 numericKey = std::min(numericKey, double(1LL << 53));
18604 numericKey = floor(numericKey);
18606 const auto&& lockedAutoIncrementIds =
18607 mMetadata->mAutoIncrementIds.Lock();
18608 if (numericKey >= lockedAutoIncrementIds->next) {
18609 autoIncrementNum = numericKey;
18613 if (keyUnset && mMetadata->mCommonMetadata.keyPath().IsValid()) {
18614 const SerializedStructuredCloneWriteInfo& cloneInfo =
18615 mParams.cloneInfo();
18616 MOZ_ASSERT(cloneInfo.offsetToKeyProp());
18617 MOZ_ASSERT(cloneDataSize > sizeof(uint64_t));
18618 MOZ_ASSERT(cloneInfo.offsetToKeyProp() <=
18619 (cloneDataSize - sizeof(uint64_t)));
18621 // Special case where someone put an object into an autoIncrement'ing
18622 // objectStore with no key in its keyPath set. We needed to figure out
18623 // which row id we would get above before we could set that properly.
18624 uint64_t keyPropValue =
18625 ReinterpretDoubleAsUInt64(static_cast<double>(autoIncrementNum));
18627 static const size_t keyPropSize = sizeof(uint64_t);
18629 char keyPropBuffer[keyPropSize];
18630 LittleEndian::writeUint64(keyPropBuffer, keyPropValue);
18632 auto iter = cloneData.Start();
18633 MOZ_ALWAYS_TRUE(cloneData.Advance(iter, cloneInfo.offsetToKeyProp()));
18634 MOZ_ALWAYS_TRUE(
18635 cloneData.UpdateBytes(iter, keyPropBuffer, keyPropSize));
18639 key.BindToStatement(&*stmt, kStmtParamNameKey);
18641 if (mDataOverThreshold) {
18642 // The data we store in the SQLite database is a (signed) 64-bit integer.
18643 // The flags are left-shifted 32 bits so the max value is 0xFFFFFFFF.
18644 // The file_ids index occupies the lower 32 bits and its max is
18645 // 0xFFFFFFFF.
18646 static const uint32_t kCompressedFlag = (1 << 0);
18648 uint32_t flags = 0;
18649 flags |= kCompressedFlag;
18651 const uint32_t index = mStoredFileInfos.Length() - 1;
18653 const int64_t data = (uint64_t(flags) << 32) | index;
18655 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameData, data)));
18656 } else {
18657 AutoTArray<char, 4096> flatCloneData; // 4096 from JSStructuredCloneData
18658 QM_TRY(OkIf(flatCloneData.SetLength(cloneDataSize, fallible)),
18659 Err(NS_ERROR_OUT_OF_MEMORY));
18662 auto iter = cloneData.Start();
18663 MOZ_ALWAYS_TRUE(
18664 cloneData.ReadBytes(iter, flatCloneData.Elements(), cloneDataSize));
18667 // Compress the bytes before adding into the database.
18668 const char* const uncompressed = flatCloneData.Elements();
18669 const size_t uncompressedLength = cloneDataSize;
18671 size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
18673 UniqueFreePtr<char> compressed(
18674 static_cast<char*>(malloc(compressedLength)));
18675 if (NS_WARN_IF(!compressed)) {
18676 return NS_ERROR_OUT_OF_MEMORY;
18679 snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(),
18680 &compressedLength);
18682 uint8_t* const dataBuffer =
18683 reinterpret_cast<uint8_t*>(compressed.release());
18684 const size_t dataBufferLength = compressedLength;
18686 QM_TRY(MOZ_TO_RESULT(stmt->BindAdoptedBlobByName(
18687 kStmtParamNameData, dataBuffer, dataBufferLength)));
18690 if (!mStoredFileInfos.IsEmpty()) {
18691 // Moved outside the loop to allow it to be cached when demanded by the
18692 // first write. (We may have mStoredFileInfos without any required
18693 // writes.)
18694 Maybe<FileHelper> fileHelper;
18695 nsAutoString fileIds;
18697 for (auto& storedFileInfo : mStoredFileInfos) {
18698 MOZ_ASSERT(storedFileInfo.IsValid());
18700 QM_TRY_INSPECT(const auto& inputStream,
18701 storedFileInfo.GetInputStream());
18703 if (inputStream) {
18704 if (fileHelper.isNothing()) {
18705 fileHelper.emplace(Transaction().GetDatabase().GetFileManagerPtr());
18706 QM_TRY(MOZ_TO_RESULT(fileHelper->Init()),
18707 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
18708 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18711 const DatabaseFileInfo& fileInfo = storedFileInfo.GetFileInfo();
18713 const auto file = fileHelper->GetFile(fileInfo);
18714 QM_TRY(OkIf(file), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
18715 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18717 const auto journalFile = fileHelper->GetJournalFile(fileInfo);
18718 QM_TRY(OkIf(journalFile), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
18719 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18721 nsCString fileKeyId;
18722 fileKeyId.AppendInt(fileInfo.Id());
18724 const auto maybeKey =
18725 Transaction()
18726 .GetDatabase()
18727 .GetFileManager()
18728 .IsInPrivateBrowsingMode()
18729 ? gIndexedDBCipherKeyManager->Get(
18730 Transaction().GetDatabase().Id(), fileKeyId)
18731 : Nothing();
18733 QM_TRY(
18734 MOZ_TO_RESULT(fileHelper->CreateFileFromStream(
18735 *file, *journalFile, *inputStream,
18736 storedFileInfo.ShouldCompress(), maybeKey))
18737 .mapErr([](const nsresult rv) {
18738 if (NS_ERROR_GET_MODULE(rv) !=
18739 NS_ERROR_MODULE_DOM_INDEXEDDB) {
18740 IDB_REPORT_INTERNAL_ERR();
18741 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
18743 return rv;
18745 QM_PROPAGATE,
18746 ([this, &file = *file, &journalFile = *journalFile](const auto) {
18747 // Try to remove the file if the copy failed.
18748 QM_TRY(MOZ_TO_RESULT(Transaction()
18749 .GetDatabase()
18750 .GetFileManager()
18751 .SyncDeleteFile(file, journalFile)),
18752 QM_VOID);
18753 }));
18755 storedFileInfo.NotifyWriteSucceeded();
18758 if (!fileIds.IsEmpty()) {
18759 fileIds.Append(' ');
18761 storedFileInfo.Serialize(fileIds);
18764 QM_TRY(MOZ_TO_RESULT(
18765 stmt->BindStringByName(kStmtParamNameFileIds, fileIds)));
18766 } else {
18767 QM_TRY(MOZ_TO_RESULT(stmt->BindNullByName(kStmtParamNameFileIds)));
18770 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_PROPAGATE,
18771 [keyUnset = DebugOnly{keyUnset}](const nsresult rv) {
18772 if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
18773 MOZ_ASSERT(!keyUnset, "Generated key had a collision!");
18778 // Update our indexes if needed.
18779 if (!mParams.indexUpdateInfos().IsEmpty()) {
18780 MOZ_ASSERT(mUniqueIndexTable.isSome());
18782 // Write the index_data_values column.
18783 QM_TRY_INSPECT(const auto& indexValues,
18784 IndexDataValuesFromUpdateInfos(mParams.indexUpdateInfos(),
18785 mUniqueIndexTable.ref()));
18787 QM_TRY(
18788 MOZ_TO_RESULT(UpdateIndexValues(aConnection, osid, key, indexValues)));
18790 QM_TRY(MOZ_TO_RESULT(
18791 InsertIndexTableRows(aConnection, osid, key, indexValues)));
18794 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
18796 if (autoIncrementNum) {
18798 auto&& lockedAutoIncrementIds = mMetadata->mAutoIncrementIds.Lock();
18800 lockedAutoIncrementIds->next = autoIncrementNum + 1;
18803 Transaction().NoteModifiedAutoIncrementObjectStore(mMetadata);
18806 return NS_OK;
18809 void ObjectStoreAddOrPutRequestOp::GetResponse(RequestResponse& aResponse,
18810 size_t* aResponseSize) {
18811 AssertIsOnOwningThread();
18813 if (mOverwrite) {
18814 aResponse = ObjectStorePutResponse(mResponse);
18815 *aResponseSize = mResponse.GetBuffer().Length();
18816 } else {
18817 aResponse = ObjectStoreAddResponse(mResponse);
18818 *aResponseSize = mResponse.GetBuffer().Length();
18822 void ObjectStoreAddOrPutRequestOp::Cleanup() {
18823 AssertIsOnOwningThread();
18825 mStoredFileInfos.Clear();
18827 NormalTransactionOp::Cleanup();
18830 NS_IMPL_ISUPPORTS(ObjectStoreAddOrPutRequestOp::SCInputStream, nsIInputStream)
18832 NS_IMETHODIMP
18833 ObjectStoreAddOrPutRequestOp::SCInputStream::Close() { return NS_OK; }
18835 NS_IMETHODIMP
18836 ObjectStoreAddOrPutRequestOp::SCInputStream::Available(uint64_t* _retval) {
18837 return NS_ERROR_NOT_IMPLEMENTED;
18840 NS_IMETHODIMP
18841 ObjectStoreAddOrPutRequestOp::SCInputStream::Read(char* aBuf, uint32_t aCount,
18842 uint32_t* _retval) {
18843 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
18846 NS_IMETHODIMP
18847 ObjectStoreAddOrPutRequestOp::SCInputStream::ReadSegments(
18848 nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
18849 uint32_t* _retval) {
18850 *_retval = 0;
18852 while (aCount) {
18853 uint32_t count = std::min(uint32_t(mIter.RemainingInSegment()), aCount);
18854 if (!count) {
18855 // We've run out of data in the last segment.
18856 break;
18859 uint32_t written;
18860 nsresult rv =
18861 aWriter(this, aClosure, mIter.Data(), *_retval, count, &written);
18862 if (NS_WARN_IF(NS_FAILED(rv))) {
18863 // InputStreams do not propagate errors to caller.
18864 return NS_OK;
18867 // Writer should write what we asked it to write.
18868 MOZ_ASSERT(written == count);
18870 *_retval += count;
18871 aCount -= count;
18873 if (NS_WARN_IF(!mData.Advance(mIter, count))) {
18874 // InputStreams do not propagate errors to caller.
18875 return NS_OK;
18879 return NS_OK;
18882 NS_IMETHODIMP
18883 ObjectStoreAddOrPutRequestOp::SCInputStream::IsNonBlocking(bool* _retval) {
18884 *_retval = false;
18885 return NS_OK;
18888 ObjectStoreGetRequestOp::ObjectStoreGetRequestOp(
18889 SafeRefPtr<TransactionBase> aTransaction, const RequestParams& aParams,
18890 bool aGetAll)
18891 : NormalTransactionOp(std::move(aTransaction)),
18892 mObjectStoreId(aGetAll
18893 ? aParams.get_ObjectStoreGetAllParams().objectStoreId()
18894 : aParams.get_ObjectStoreGetParams().objectStoreId()),
18895 mDatabase(Transaction().GetDatabasePtr()),
18896 mOptionalKeyRange(
18897 aGetAll ? aParams.get_ObjectStoreGetAllParams().optionalKeyRange()
18898 : Some(aParams.get_ObjectStoreGetParams().keyRange())),
18899 mBackgroundParent(Transaction().GetBackgroundParent()),
18900 mPreprocessInfoCount(0),
18901 mLimit(aGetAll ? aParams.get_ObjectStoreGetAllParams().limit() : 1),
18902 mGetAll(aGetAll) {
18903 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetParams ||
18904 aParams.type() == RequestParams::TObjectStoreGetAllParams);
18905 MOZ_ASSERT(mObjectStoreId);
18906 MOZ_ASSERT(mDatabase);
18907 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome());
18908 MOZ_ASSERT(mBackgroundParent);
18911 template <typename T>
18912 Result<T, nsresult> ObjectStoreGetRequestOp::ConvertResponse(
18913 StructuredCloneReadInfoParent&& aInfo) {
18914 T result;
18916 static_assert(std::is_same_v<T, SerializedStructuredCloneReadInfo> ||
18917 std::is_same_v<T, PreprocessInfo>);
18919 if constexpr (std::is_same_v<T, SerializedStructuredCloneReadInfo>) {
18920 result.data().data = aInfo.ReleaseData();
18921 result.hasPreprocessInfo() = aInfo.HasPreprocessInfo();
18924 QM_TRY_UNWRAP(result.files(), SerializeStructuredCloneFiles(
18925 mDatabase, aInfo.Files(),
18926 std::is_same_v<T, PreprocessInfo>));
18928 return result;
18931 nsresult ObjectStoreGetRequestOp::DoDatabaseWork(
18932 DatabaseConnection* aConnection) {
18933 MOZ_ASSERT(aConnection);
18934 aConnection->AssertIsOnConnectionThread();
18935 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome());
18936 MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
18938 AUTO_PROFILER_LABEL("ObjectStoreGetRequestOp::DoDatabaseWork", DOM);
18940 const nsCString query =
18941 "SELECT file_ids, data "
18942 "FROM object_data "
18943 "WHERE object_store_id = :"_ns +
18944 kStmtParamNameObjectStoreId +
18945 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameKey) +
18946 " ORDER BY key ASC"_ns +
18947 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString());
18949 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query));
18951 QM_TRY(MOZ_TO_RESULT(
18952 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId)));
18954 if (mOptionalKeyRange.isSome()) {
18955 QM_TRY(MOZ_TO_RESULT(
18956 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt)));
18959 QM_TRY(CollectWhileHasResult(
18960 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
18961 QM_TRY_UNWRAP(auto cloneInfo,
18962 GetStructuredCloneReadInfoFromStatement(
18963 &stmt, 1, 0, mDatabase->GetFileManager()));
18965 if (cloneInfo.HasPreprocessInfo()) {
18966 mPreprocessInfoCount++;
18969 QM_TRY(OkIf(mResponse.EmplaceBack(fallible, std::move(cloneInfo))),
18970 Err(NS_ERROR_OUT_OF_MEMORY));
18972 return Ok{};
18973 }));
18975 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
18977 return NS_OK;
18980 bool ObjectStoreGetRequestOp::HasPreprocessInfo() {
18981 return mPreprocessInfoCount > 0;
18984 Result<PreprocessParams, nsresult>
18985 ObjectStoreGetRequestOp::GetPreprocessParams() {
18986 AssertIsOnOwningThread();
18987 MOZ_ASSERT(!mResponse.IsEmpty());
18989 if (mGetAll) {
18990 auto params = ObjectStoreGetAllPreprocessParams();
18992 auto& preprocessInfos = params.preprocessInfos();
18993 if (NS_WARN_IF(
18994 !preprocessInfos.SetCapacity(mPreprocessInfoCount, fallible))) {
18995 return Err(NS_ERROR_OUT_OF_MEMORY);
18998 QM_TRY(TransformIfAbortOnErr(
18999 std::make_move_iterator(mResponse.begin()),
19000 std::make_move_iterator(mResponse.end()),
19001 MakeBackInserter(preprocessInfos),
19002 [](const auto& info) { return info.HasPreprocessInfo(); },
19003 [&self = *this](StructuredCloneReadInfoParent&& info) {
19004 return self.ConvertResponse<PreprocessInfo>(std::move(info));
19005 }));
19007 return PreprocessParams{std::move(params)};
19010 auto params = ObjectStoreGetPreprocessParams();
19012 QM_TRY_UNWRAP(params.preprocessInfo(),
19013 ConvertResponse<PreprocessInfo>(std::move(mResponse[0])));
19015 return PreprocessParams{std::move(params)};
19018 void ObjectStoreGetRequestOp::GetResponse(RequestResponse& aResponse,
19019 size_t* aResponseSize) {
19020 MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
19022 if (mGetAll) {
19023 aResponse = ObjectStoreGetAllResponse();
19024 *aResponseSize = 0;
19026 if (!mResponse.IsEmpty()) {
19027 QM_TRY_UNWRAP(
19028 aResponse.get_ObjectStoreGetAllResponse().cloneInfos(),
19029 TransformIntoNewArrayAbortOnErr(
19030 std::make_move_iterator(mResponse.begin()),
19031 std::make_move_iterator(mResponse.end()),
19032 [this, &aResponseSize](StructuredCloneReadInfoParent&& info) {
19033 *aResponseSize += info.Size();
19034 return ConvertResponse<SerializedStructuredCloneReadInfo>(
19035 std::move(info));
19037 fallible),
19038 QM_VOID, [&aResponse](const nsresult result) { aResponse = result; });
19041 return;
19044 aResponse = ObjectStoreGetResponse();
19045 *aResponseSize = 0;
19047 if (!mResponse.IsEmpty()) {
19048 SerializedStructuredCloneReadInfo& serializedInfo =
19049 aResponse.get_ObjectStoreGetResponse().cloneInfo();
19051 *aResponseSize += mResponse[0].Size();
19052 QM_TRY_UNWRAP(serializedInfo,
19053 ConvertResponse<SerializedStructuredCloneReadInfo>(
19054 std::move(mResponse[0])),
19055 QM_VOID,
19056 [&aResponse](const nsresult result) { aResponse = result; });
19060 ObjectStoreGetKeyRequestOp::ObjectStoreGetKeyRequestOp(
19061 SafeRefPtr<TransactionBase> aTransaction, const RequestParams& aParams,
19062 bool aGetAll)
19063 : NormalTransactionOp(std::move(aTransaction)),
19064 mObjectStoreId(
19065 aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().objectStoreId()
19066 : aParams.get_ObjectStoreGetKeyParams().objectStoreId()),
19067 mOptionalKeyRange(
19068 aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().optionalKeyRange()
19069 : Some(aParams.get_ObjectStoreGetKeyParams().keyRange())),
19070 mLimit(aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().limit() : 1),
19071 mGetAll(aGetAll) {
19072 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetKeyParams ||
19073 aParams.type() == RequestParams::TObjectStoreGetAllKeysParams);
19074 MOZ_ASSERT(mObjectStoreId);
19075 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome());
19078 nsresult ObjectStoreGetKeyRequestOp::DoDatabaseWork(
19079 DatabaseConnection* aConnection) {
19080 MOZ_ASSERT(aConnection);
19081 aConnection->AssertIsOnConnectionThread();
19083 AUTO_PROFILER_LABEL("ObjectStoreGetKeyRequestOp::DoDatabaseWork", DOM);
19085 const nsCString query =
19086 "SELECT key "
19087 "FROM object_data "
19088 "WHERE object_store_id = :"_ns +
19089 kStmtParamNameObjectStoreId +
19090 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameKey) +
19091 " ORDER BY key ASC"_ns +
19092 (mLimit ? " LIMIT "_ns + IntToCString(mLimit) : EmptyCString());
19094 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query));
19096 nsresult rv =
19097 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId);
19098 if (NS_WARN_IF(NS_FAILED(rv))) {
19099 return rv;
19102 if (mOptionalKeyRange.isSome()) {
19103 rv = BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt);
19104 if (NS_WARN_IF(NS_FAILED(rv))) {
19105 return rv;
19109 QM_TRY(CollectWhileHasResult(
19110 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
19111 Key* const key = mResponse.AppendElement(fallible);
19112 QM_TRY(OkIf(key), Err(NS_ERROR_OUT_OF_MEMORY));
19113 QM_TRY(MOZ_TO_RESULT(key->SetFromStatement(&stmt, 0)));
19115 return Ok{};
19116 }));
19118 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19120 return NS_OK;
19123 void ObjectStoreGetKeyRequestOp::GetResponse(RequestResponse& aResponse,
19124 size_t* aResponseSize) {
19125 MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
19127 if (mGetAll) {
19128 aResponse = ObjectStoreGetAllKeysResponse();
19129 *aResponseSize = std::accumulate(mResponse.begin(), mResponse.end(), 0u,
19130 [](size_t old, const auto& entry) {
19131 return old + entry.GetBuffer().Length();
19134 aResponse.get_ObjectStoreGetAllKeysResponse().keys() = std::move(mResponse);
19136 return;
19139 aResponse = ObjectStoreGetKeyResponse();
19140 *aResponseSize = 0;
19142 if (!mResponse.IsEmpty()) {
19143 *aResponseSize = mResponse[0].GetBuffer().Length();
19144 aResponse.get_ObjectStoreGetKeyResponse().key() = std::move(mResponse[0]);
19148 ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp(
19149 SafeRefPtr<TransactionBase> aTransaction,
19150 const ObjectStoreDeleteParams& aParams)
19151 : NormalTransactionOp(std::move(aTransaction)),
19152 mParams(aParams),
19153 mObjectStoreMayHaveIndexes(false) {
19154 AssertIsOnBackgroundThread();
19156 SafeRefPtr<FullObjectStoreMetadata> metadata =
19157 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId());
19158 MOZ_ASSERT(metadata);
19160 mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes();
19163 nsresult ObjectStoreDeleteRequestOp::DoDatabaseWork(
19164 DatabaseConnection* aConnection) {
19165 MOZ_ASSERT(aConnection);
19166 aConnection->AssertIsOnConnectionThread();
19167 AUTO_PROFILER_LABEL("ObjectStoreDeleteRequestOp::DoDatabaseWork", DOM);
19169 DatabaseConnection::AutoSavepoint autoSave;
19170 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
19171 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
19173 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
19174 #endif
19177 QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
19178 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
19179 mObjectStoreMayHaveIndexes));
19181 if (objectStoreHasIndexes) {
19182 QM_TRY(MOZ_TO_RESULT(DeleteObjectStoreDataTableRowsWithIndexes(
19183 aConnection, mParams.objectStoreId(), Some(mParams.keyRange()))));
19184 } else {
19185 const auto keyRangeClause =
19186 GetBindingClauseForKeyRange(mParams.keyRange(), kColumnNameKey);
19188 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
19189 "DELETE FROM object_data "
19190 "WHERE object_store_id = :"_ns +
19191 kStmtParamNameObjectStoreId + keyRangeClause + ";"_ns,
19192 [&params = mParams](
19193 mozIStorageStatement& stmt) -> mozilla::Result<Ok, nsresult> {
19194 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(kStmtParamNameObjectStoreId,
19195 params.objectStoreId())));
19197 QM_TRY(
19198 MOZ_TO_RESULT(BindKeyRangeToStatement(params.keyRange(), &stmt)));
19200 return Ok{};
19201 })));
19204 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
19206 return NS_OK;
19209 ObjectStoreClearRequestOp::ObjectStoreClearRequestOp(
19210 SafeRefPtr<TransactionBase> aTransaction,
19211 const ObjectStoreClearParams& aParams)
19212 : NormalTransactionOp(std::move(aTransaction)),
19213 mParams(aParams),
19214 mObjectStoreMayHaveIndexes(false) {
19215 AssertIsOnBackgroundThread();
19217 SafeRefPtr<FullObjectStoreMetadata> metadata =
19218 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId());
19219 MOZ_ASSERT(metadata);
19221 mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes();
19224 nsresult ObjectStoreClearRequestOp::DoDatabaseWork(
19225 DatabaseConnection* aConnection) {
19226 MOZ_ASSERT(aConnection);
19227 aConnection->AssertIsOnConnectionThread();
19229 AUTO_PROFILER_LABEL("ObjectStoreClearRequestOp::DoDatabaseWork", DOM);
19231 DatabaseConnection::AutoSavepoint autoSave;
19232 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
19233 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
19235 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
19236 #endif
19239 QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
19240 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
19241 mObjectStoreMayHaveIndexes));
19243 // The parameter names are not used, parameters are bound by index only
19244 // locally in the same function.
19245 QM_TRY(MOZ_TO_RESULT(
19246 objectStoreHasIndexes
19247 ? DeleteObjectStoreDataTableRowsWithIndexes(
19248 aConnection, mParams.objectStoreId(), Nothing())
19249 : aConnection->ExecuteCachedStatement(
19250 "DELETE FROM object_data "
19251 "WHERE object_store_id = :object_store_id;"_ns,
19252 [objectStoreId =
19253 mParams.objectStoreId()](mozIStorageStatement& stmt)
19254 -> mozilla::Result<Ok, nsresult> {
19255 QM_TRY(
19256 MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, objectStoreId)));
19258 return Ok{};
19259 })));
19261 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
19263 return NS_OK;
19266 nsresult ObjectStoreCountRequestOp::DoDatabaseWork(
19267 DatabaseConnection* aConnection) {
19268 MOZ_ASSERT(aConnection);
19269 aConnection->AssertIsOnConnectionThread();
19271 AUTO_PROFILER_LABEL("ObjectStoreCountRequestOp::DoDatabaseWork", DOM);
19273 const auto keyRangeClause = MaybeGetBindingClauseForKeyRange(
19274 mParams.optionalKeyRange(), kColumnNameKey);
19276 QM_TRY_INSPECT(
19277 const auto& maybeStmt,
19278 aConnection->BorrowAndExecuteSingleStepStatement(
19279 "SELECT count(*) "
19280 "FROM object_data "
19281 "WHERE object_store_id = :"_ns +
19282 kStmtParamNameObjectStoreId + keyRangeClause,
19283 [&params = mParams](auto& stmt) -> mozilla::Result<Ok, nsresult> {
19284 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(
19285 kStmtParamNameObjectStoreId, params.objectStoreId())));
19287 if (params.optionalKeyRange().isSome()) {
19288 QM_TRY(MOZ_TO_RESULT(BindKeyRangeToStatement(
19289 params.optionalKeyRange().ref(), &stmt)));
19292 return Ok{};
19293 }));
19295 QM_TRY(OkIf(maybeStmt.isSome()), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
19296 [](const auto) {
19297 // XXX Why do we have an assertion here, but not at most other
19298 // places using IDB_REPORT_INTERNAL_ERR(_LAMBDA)?
19299 MOZ_ASSERT(false, "This should never be possible!");
19300 IDB_REPORT_INTERNAL_ERR();
19303 const auto& stmt = *maybeStmt;
19305 const int64_t count = stmt->AsInt64(0);
19306 QM_TRY(OkIf(count >= 0), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, [](const auto) {
19307 // XXX Why do we have an assertion here, but not at most other places using
19308 // IDB_REPORT_INTERNAL_ERR(_LAMBDA)?
19309 MOZ_ASSERT(false, "This should never be possible!");
19310 IDB_REPORT_INTERNAL_ERR();
19313 mResponse.count() = count;
19315 return NS_OK;
19318 // static
19319 SafeRefPtr<FullIndexMetadata> IndexRequestOpBase::IndexMetadataForParams(
19320 const TransactionBase& aTransaction, const RequestParams& aParams) {
19321 AssertIsOnBackgroundThread();
19322 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams ||
19323 aParams.type() == RequestParams::TIndexGetKeyParams ||
19324 aParams.type() == RequestParams::TIndexGetAllParams ||
19325 aParams.type() == RequestParams::TIndexGetAllKeysParams ||
19326 aParams.type() == RequestParams::TIndexCountParams);
19328 IndexOrObjectStoreId objectStoreId;
19329 IndexOrObjectStoreId indexId;
19331 switch (aParams.type()) {
19332 case RequestParams::TIndexGetParams: {
19333 const IndexGetParams& params = aParams.get_IndexGetParams();
19334 objectStoreId = params.objectStoreId();
19335 indexId = params.indexId();
19336 break;
19339 case RequestParams::TIndexGetKeyParams: {
19340 const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams();
19341 objectStoreId = params.objectStoreId();
19342 indexId = params.indexId();
19343 break;
19346 case RequestParams::TIndexGetAllParams: {
19347 const IndexGetAllParams& params = aParams.get_IndexGetAllParams();
19348 objectStoreId = params.objectStoreId();
19349 indexId = params.indexId();
19350 break;
19353 case RequestParams::TIndexGetAllKeysParams: {
19354 const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams();
19355 objectStoreId = params.objectStoreId();
19356 indexId = params.indexId();
19357 break;
19360 case RequestParams::TIndexCountParams: {
19361 const IndexCountParams& params = aParams.get_IndexCountParams();
19362 objectStoreId = params.objectStoreId();
19363 indexId = params.indexId();
19364 break;
19367 default:
19368 MOZ_CRASH("Should never get here!");
19371 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
19372 aTransaction.GetMetadataForObjectStoreId(objectStoreId);
19373 MOZ_ASSERT(objectStoreMetadata);
19375 SafeRefPtr<FullIndexMetadata> indexMetadata =
19376 aTransaction.GetMetadataForIndexId(*objectStoreMetadata, indexId);
19377 MOZ_ASSERT(indexMetadata);
19379 return indexMetadata;
19382 IndexGetRequestOp::IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction,
19383 const RequestParams& aParams, bool aGetAll)
19384 : IndexRequestOpBase(std::move(aTransaction), aParams),
19385 mDatabase(Transaction().GetDatabasePtr()),
19386 mOptionalKeyRange(aGetAll
19387 ? aParams.get_IndexGetAllParams().optionalKeyRange()
19388 : Some(aParams.get_IndexGetParams().keyRange())),
19389 mBackgroundParent(Transaction().GetBackgroundParent()),
19390 mLimit(aGetAll ? aParams.get_IndexGetAllParams().limit() : 1),
19391 mGetAll(aGetAll) {
19392 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams ||
19393 aParams.type() == RequestParams::TIndexGetAllParams);
19394 MOZ_ASSERT(mDatabase);
19395 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome());
19396 MOZ_ASSERT(mBackgroundParent);
19399 nsresult IndexGetRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) {
19400 MOZ_ASSERT(aConnection);
19401 aConnection->AssertIsOnConnectionThread();
19402 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome());
19403 MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
19405 AUTO_PROFILER_LABEL("IndexGetRequestOp::DoDatabaseWork", DOM);
19407 const auto indexTable = mMetadata->mCommonMetadata.unique()
19408 ? "unique_index_data "_ns
19409 : "index_data "_ns;
19411 QM_TRY_INSPECT(
19412 const auto& stmt,
19413 aConnection->BorrowCachedStatement(
19414 "SELECT file_ids, data "
19415 "FROM object_data "
19416 "INNER JOIN "_ns +
19417 indexTable +
19418 "AS index_table "
19419 "ON object_data.object_store_id = "
19420 "index_table.object_store_id "
19421 "AND object_data.key = "
19422 "index_table.object_data_key "
19423 "WHERE index_id = :"_ns +
19424 kStmtParamNameIndexId +
19425 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange,
19426 kColumnNameValue) +
19427 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString())));
19429 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameIndexId,
19430 mMetadata->mCommonMetadata.id())));
19432 if (mOptionalKeyRange.isSome()) {
19433 QM_TRY(MOZ_TO_RESULT(
19434 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt)));
19437 QM_TRY(CollectWhileHasResult(
19438 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
19439 QM_TRY_UNWRAP(auto cloneInfo,
19440 GetStructuredCloneReadInfoFromStatement(
19441 &stmt, 1, 0, mDatabase->GetFileManager()));
19443 if (cloneInfo.HasPreprocessInfo()) {
19444 IDB_WARNING("Preprocessing for indexes not yet implemented!");
19445 return Err(NS_ERROR_NOT_IMPLEMENTED);
19448 QM_TRY(OkIf(mResponse.EmplaceBack(fallible, std::move(cloneInfo))),
19449 Err(NS_ERROR_OUT_OF_MEMORY));
19451 return Ok{};
19452 }));
19454 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19456 return NS_OK;
19459 // XXX This is more or less a duplicate of ObjectStoreGetRequestOp::GetResponse
19460 void IndexGetRequestOp::GetResponse(RequestResponse& aResponse,
19461 size_t* aResponseSize) {
19462 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19464 auto convertResponse = [this](StructuredCloneReadInfoParent&& info)
19465 -> mozilla::Result<SerializedStructuredCloneReadInfo, nsresult> {
19466 SerializedStructuredCloneReadInfo result;
19468 result.data().data = info.ReleaseData();
19470 QM_TRY_UNWRAP(result.files(), SerializeStructuredCloneFiles(
19471 mDatabase, info.Files(), false));
19473 return result;
19476 if (mGetAll) {
19477 aResponse = IndexGetAllResponse();
19478 *aResponseSize = 0;
19480 if (!mResponse.IsEmpty()) {
19481 QM_TRY_UNWRAP(
19482 aResponse.get_IndexGetAllResponse().cloneInfos(),
19483 TransformIntoNewArrayAbortOnErr(
19484 std::make_move_iterator(mResponse.begin()),
19485 std::make_move_iterator(mResponse.end()),
19486 [convertResponse,
19487 &aResponseSize](StructuredCloneReadInfoParent&& info) {
19488 *aResponseSize += info.Size();
19489 return convertResponse(std::move(info));
19491 fallible),
19492 QM_VOID, [&aResponse](const nsresult result) { aResponse = result; });
19495 return;
19498 aResponse = IndexGetResponse();
19499 *aResponseSize = 0;
19501 if (!mResponse.IsEmpty()) {
19502 SerializedStructuredCloneReadInfo& serializedInfo =
19503 aResponse.get_IndexGetResponse().cloneInfo();
19505 *aResponseSize += mResponse[0].Size();
19506 QM_TRY_UNWRAP(serializedInfo, convertResponse(std::move(mResponse[0])),
19507 QM_VOID,
19508 [&aResponse](const nsresult result) { aResponse = result; });
19512 IndexGetKeyRequestOp::IndexGetKeyRequestOp(
19513 SafeRefPtr<TransactionBase> aTransaction, const RequestParams& aParams,
19514 bool aGetAll)
19515 : IndexRequestOpBase(std::move(aTransaction), aParams),
19516 mOptionalKeyRange(
19517 aGetAll ? aParams.get_IndexGetAllKeysParams().optionalKeyRange()
19518 : Some(aParams.get_IndexGetKeyParams().keyRange())),
19519 mLimit(aGetAll ? aParams.get_IndexGetAllKeysParams().limit() : 1),
19520 mGetAll(aGetAll) {
19521 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetKeyParams ||
19522 aParams.type() == RequestParams::TIndexGetAllKeysParams);
19523 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome());
19526 nsresult IndexGetKeyRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) {
19527 MOZ_ASSERT(aConnection);
19528 aConnection->AssertIsOnConnectionThread();
19529 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome());
19530 MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
19532 AUTO_PROFILER_LABEL("IndexGetKeyRequestOp::DoDatabaseWork", DOM);
19534 const bool hasKeyRange = mOptionalKeyRange.isSome();
19536 const auto indexTable = mMetadata->mCommonMetadata.unique()
19537 ? "unique_index_data "_ns
19538 : "index_data "_ns;
19540 const nsCString query =
19541 "SELECT object_data_key "
19542 "FROM "_ns +
19543 indexTable + "WHERE index_id = :"_ns + kStmtParamNameIndexId +
19544 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameValue) +
19545 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString());
19547 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query));
19549 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameIndexId,
19550 mMetadata->mCommonMetadata.id())));
19552 if (hasKeyRange) {
19553 QM_TRY(MOZ_TO_RESULT(
19554 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt)));
19557 QM_TRY(CollectWhileHasResult(
19558 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
19559 Key* const key = mResponse.AppendElement(fallible);
19560 QM_TRY(OkIf(key), Err(NS_ERROR_OUT_OF_MEMORY));
19561 QM_TRY(MOZ_TO_RESULT(key->SetFromStatement(&stmt, 0)));
19563 return Ok{};
19564 }));
19566 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19568 return NS_OK;
19571 void IndexGetKeyRequestOp::GetResponse(RequestResponse& aResponse,
19572 size_t* aResponseSize) {
19573 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19575 if (mGetAll) {
19576 aResponse = IndexGetAllKeysResponse();
19577 *aResponseSize = std::accumulate(mResponse.begin(), mResponse.end(), 0u,
19578 [](size_t old, const auto& entry) {
19579 return old + entry.GetBuffer().Length();
19582 aResponse.get_IndexGetAllKeysResponse().keys() = std::move(mResponse);
19584 return;
19587 aResponse = IndexGetKeyResponse();
19588 *aResponseSize = 0;
19590 if (!mResponse.IsEmpty()) {
19591 *aResponseSize = mResponse[0].GetBuffer().Length();
19592 aResponse.get_IndexGetKeyResponse().key() = std::move(mResponse[0]);
19596 nsresult IndexCountRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) {
19597 MOZ_ASSERT(aConnection);
19598 aConnection->AssertIsOnConnectionThread();
19600 AUTO_PROFILER_LABEL("IndexCountRequestOp::DoDatabaseWork", DOM);
19602 const auto indexTable = mMetadata->mCommonMetadata.unique()
19603 ? "unique_index_data "_ns
19604 : "index_data "_ns;
19606 const auto keyRangeClause = MaybeGetBindingClauseForKeyRange(
19607 mParams.optionalKeyRange(), kColumnNameValue);
19609 QM_TRY_INSPECT(
19610 const auto& maybeStmt,
19611 aConnection->BorrowAndExecuteSingleStepStatement(
19612 "SELECT count(*) "
19613 "FROM "_ns +
19614 indexTable + "WHERE index_id = :"_ns + kStmtParamNameIndexId +
19615 keyRangeClause,
19616 [&self = *this](auto& stmt) -> mozilla::Result<Ok, nsresult> {
19617 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(
19618 kStmtParamNameIndexId, self.mMetadata->mCommonMetadata.id())));
19620 if (self.mParams.optionalKeyRange().isSome()) {
19621 QM_TRY(MOZ_TO_RESULT(BindKeyRangeToStatement(
19622 self.mParams.optionalKeyRange().ref(), &stmt)));
19625 return Ok{};
19626 }));
19628 QM_TRY(OkIf(maybeStmt.isSome()), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
19629 [](const auto) {
19630 // XXX Why do we have an assertion here, but not at most other
19631 // places using IDB_REPORT_INTERNAL_ERR(_LAMBDA)?
19632 MOZ_ASSERT(false, "This should never be possible!");
19633 IDB_REPORT_INTERNAL_ERR();
19636 const auto& stmt = *maybeStmt;
19638 const int64_t count = stmt->AsInt64(0);
19639 QM_TRY(OkIf(count >= 0), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, [](const auto) {
19640 // XXX Why do we have an assertion here, but not at most other places using
19641 // IDB_REPORT_INTERNAL_ERR(_LAMBDA)?
19642 MOZ_ASSERT(false, "This should never be possible!");
19643 IDB_REPORT_INTERNAL_ERR();
19646 mResponse.count() = count;
19648 return NS_OK;
19651 template <IDBCursorType CursorType>
19652 bool Cursor<CursorType>::CursorOpBase::SendFailureResult(nsresult aResultCode) {
19653 AssertIsOnOwningThread();
19654 MOZ_ASSERT(NS_FAILED(aResultCode));
19655 MOZ_ASSERT(mCursor);
19656 MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
19657 MOZ_ASSERT(!mResponseSent);
19659 if (!IsActorDestroyed()) {
19660 mResponse = ClampResultCode(aResultCode);
19662 // This is an expected race when the transaction is invalidated after
19663 // data is retrieved from database.
19665 // TODO: There seem to be other cases when mFiles is non-empty here, which
19666 // have been present before adding cursor preloading, but with cursor
19667 // preloading they have become more frequent (also during startup). One
19668 // possible cause with cursor preloading is to be addressed by Bug 1597191.
19669 NS_WARNING_ASSERTION(
19670 !mFiles.IsEmpty() && !Transaction().IsInvalidated(),
19671 "Expected empty mFiles when transaction has not been invalidated");
19673 // SendResponseInternal will assert when mResponse.type() is
19674 // CursorResponse::Tnsresult and mFiles is non-empty, so we clear mFiles
19675 // here.
19676 mFiles.Clear();
19678 mCursor->SendResponseInternal(mResponse, mFiles);
19681 #ifdef DEBUG
19682 mResponseSent = true;
19683 #endif
19684 return false;
19687 template <IDBCursorType CursorType>
19688 void Cursor<CursorType>::CursorOpBase::Cleanup() {
19689 AssertIsOnOwningThread();
19690 MOZ_ASSERT(mCursor);
19691 MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
19693 mCursor = nullptr;
19695 #ifdef DEBUG
19696 // A bit hacky but the CursorOp request is not generated in response to a
19697 // child request like most other database operations. Do this to make our
19698 // assertions happy.
19699 NoteActorDestroyed();
19700 #endif
19702 TransactionDatabaseOperationBase::Cleanup();
19705 template <IDBCursorType CursorType>
19706 ResponseSizeOrError
19707 CursorOpBaseHelperBase<CursorType>::PopulateResponseFromStatement(
19708 mozIStorageStatement* const aStmt, const bool aInitializeResponse,
19709 Key* const aOptOutSortKey) {
19710 mOp.Transaction().AssertIsOnConnectionThread();
19711 MOZ_ASSERT_IF(aInitializeResponse,
19712 mOp.mResponse.type() == CursorResponse::T__None);
19713 MOZ_ASSERT_IF(!aInitializeResponse,
19714 mOp.mResponse.type() != CursorResponse::T__None);
19715 MOZ_ASSERT_IF(
19716 mOp.mFiles.IsEmpty() &&
19717 (mOp.mResponse.type() ==
19718 CursorResponse::TArrayOfObjectStoreCursorResponse ||
19719 mOp.mResponse.type() == CursorResponse::TArrayOfIndexCursorResponse),
19720 aInitializeResponse);
19722 auto populateResponseHelper = PopulateResponseHelper<CursorType>{mOp};
19723 auto previousKey = aOptOutSortKey ? std::move(*aOptOutSortKey) : Key{};
19725 QM_TRY(MOZ_TO_RESULT(populateResponseHelper.GetKeys(aStmt, aOptOutSortKey)));
19727 // aOptOutSortKey must be set iff the cursor is a unique cursor. For unique
19728 // cursors, we need to skip records with the same key. The SQL queries
19729 // currently do not filter these out.
19730 if (aOptOutSortKey && !previousKey.IsUnset() &&
19731 previousKey == *aOptOutSortKey) {
19732 return 0;
19735 QM_TRY(MOZ_TO_RESULT(
19736 populateResponseHelper.MaybeGetCloneInfo(aStmt, GetCursor())));
19738 // CAUTION: It is important that only the part of the function above this
19739 // comment may fail, and modifications to the data structure (in particular
19740 // mResponse and mFiles) may only be made below. This is necessary to allow to
19741 // discard entries that were attempted to be preloaded without causing an
19742 // inconsistent state.
19744 if (aInitializeResponse) {
19745 mOp.mResponse = std::remove_reference_t<
19746 decltype(populateResponseHelper.GetTypedResponse(&mOp.mResponse))>();
19749 auto& responses = populateResponseHelper.GetTypedResponse(&mOp.mResponse);
19750 auto& response = *responses.AppendElement();
19752 populateResponseHelper.FillKeys(response);
19753 if constexpr (!CursorTypeTraits<CursorType>::IsKeyOnlyCursor) {
19754 populateResponseHelper.MaybeFillCloneInfo(response, &mOp.mFiles);
19757 return populateResponseHelper.GetKeySize(response) +
19758 populateResponseHelper.MaybeGetCloneInfoSize(response);
19761 template <IDBCursorType CursorType>
19762 void CursorOpBaseHelperBase<CursorType>::PopulateExtraResponses(
19763 mozIStorageStatement* const aStmt, const uint32_t aMaxExtraCount,
19764 const size_t aInitialResponseSize, const nsACString& aOperation,
19765 Key* const aOptPreviousSortKey) {
19766 mOp.AssertIsOnConnectionThread();
19768 const auto extraCount = [&]() -> uint32_t {
19769 auto accumulatedResponseSize = aInitialResponseSize;
19770 uint32_t extraCount = 0;
19772 do {
19773 bool hasResult;
19774 nsresult rv = aStmt->ExecuteStep(&hasResult);
19775 if (NS_WARN_IF(NS_FAILED(rv))) {
19776 // In case of a failure on one step, do not attempt to execute further
19777 // steps, but use the results already populated.
19779 break;
19782 if (!hasResult) {
19783 break;
19786 // PopulateResponseFromStatement does not modify the data in case of
19787 // failure, so we can just use the results already populated, and discard
19788 // any remaining entries, and signal overall success. Probably, future
19789 // attempts to access the same entry will fail as well, but it might never
19790 // be accessed by the application.
19791 QM_TRY_INSPECT(
19792 const auto& responseSize,
19793 PopulateResponseFromStatement(aStmt, false, aOptPreviousSortKey),
19794 extraCount, [](const auto&) {
19795 // TODO: Maybe disable preloading for this cursor? The problem will
19796 // probably reoccur on the next attempt, and disabling preloading
19797 // will reduce latency. However, if some problematic entry will be
19798 // skipped over, after that it might be fine again. To judge this,
19799 // the causes for such failures would need to be analyzed more
19800 // thoroughly. Since this seems to be rare, maybe no further action
19801 // is necessary at all.
19804 // Check accumulated size of individual responses and maybe break early.
19805 accumulatedResponseSize += responseSize;
19806 if (accumulatedResponseSize > IPC::Channel::kMaximumMessageSize / 2) {
19807 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
19808 "PRELOAD: %s: Dropping entries because maximum message size is "
19809 "exceeded: %" PRIu32 "/%zu bytes",
19810 "%.0s Dropping too large (%" PRIu32 "/%zu)",
19811 IDB_LOG_ID_STRING(mOp.mBackgroundChildLoggingId),
19812 mOp.mTransactionLoggingSerialNumber, mOp.mLoggingSerialNumber,
19813 PromiseFlatCString(aOperation).get(), extraCount,
19814 accumulatedResponseSize);
19816 break;
19819 // TODO: Do not count entries skipped for unique cursors.
19820 ++extraCount;
19821 } while (true);
19823 return extraCount;
19824 }();
19826 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
19827 "PRELOAD: %s: Number of extra results populated: %" PRIu32 "/%" PRIu32,
19828 "%.0s Populated (%" PRIu32 "/%" PRIu32 ")",
19829 IDB_LOG_ID_STRING(mOp.mBackgroundChildLoggingId),
19830 mOp.mTransactionLoggingSerialNumber, mOp.mLoggingSerialNumber,
19831 PromiseFlatCString(aOperation).get(), extraCount, aMaxExtraCount);
19834 template <IDBCursorType CursorType>
19835 void Cursor<CursorType>::SetOptionalKeyRange(
19836 const Maybe<SerializedKeyRange>& aOptionalKeyRange, bool* const aOpen) {
19837 MOZ_ASSERT(aOpen);
19839 Key localeAwareRangeBound;
19841 if (aOptionalKeyRange.isSome()) {
19842 const SerializedKeyRange& range = aOptionalKeyRange.ref();
19844 const bool lowerBound = !IsIncreasingOrder(mDirection);
19845 *aOpen =
19846 !range.isOnly() && (lowerBound ? range.lowerOpen() : range.upperOpen());
19848 const auto& bound =
19849 (range.isOnly() || lowerBound) ? range.lower() : range.upper();
19850 if constexpr (IsIndexCursor) {
19851 if (this->IsLocaleAware()) {
19852 // XXX Don't we need to propagate the error?
19853 QM_TRY_UNWRAP(localeAwareRangeBound,
19854 bound.ToLocaleAwareKey(this->mLocale), QM_VOID);
19855 } else {
19856 localeAwareRangeBound = bound;
19858 } else {
19859 localeAwareRangeBound = bound;
19861 } else {
19862 *aOpen = false;
19865 this->mLocaleAwareRangeBound.init(std::move(localeAwareRangeBound));
19868 template <IDBCursorType CursorType>
19869 void ObjectStoreOpenOpHelper<CursorType>::PrepareKeyConditionClauses(
19870 const nsACString& aDirectionClause, const nsACString& aQueryStart) {
19871 const bool isIncreasingOrder = IsIncreasingOrder(GetCursor().mDirection);
19873 nsAutoCString keyRangeClause;
19874 nsAutoCString continueToKeyRangeClause;
19875 AppendConditionClause(kStmtParamNameKey, kStmtParamNameCurrentKey,
19876 !isIncreasingOrder, false, keyRangeClause);
19877 AppendConditionClause(kStmtParamNameKey, kStmtParamNameCurrentKey,
19878 !isIncreasingOrder, true, continueToKeyRangeClause);
19881 bool open;
19882 GetCursor().SetOptionalKeyRange(GetOptionalKeyRange(), &open);
19884 if (GetOptionalKeyRange().isSome() &&
19885 !GetCursor().mLocaleAwareRangeBound->IsUnset()) {
19886 AppendConditionClause(kStmtParamNameKey, kStmtParamNameRangeBound,
19887 isIncreasingOrder, !open, keyRangeClause);
19888 AppendConditionClause(kStmtParamNameKey, kStmtParamNameRangeBound,
19889 isIncreasingOrder, !open, continueToKeyRangeClause);
19893 const nsAutoCString suffix =
19894 aDirectionClause + kOpenLimit + ":"_ns + kStmtParamNameLimit;
19896 GetCursor().mContinueQueries.init(
19897 aQueryStart + keyRangeClause + suffix,
19898 aQueryStart + continueToKeyRangeClause + suffix);
19901 template <IDBCursorType CursorType>
19902 void IndexOpenOpHelper<CursorType>::PrepareIndexKeyConditionClause(
19903 const nsACString& aDirectionClause,
19904 const nsLiteralCString& aObjectDataKeyPrefix, nsAutoCString aQueryStart) {
19905 const bool isIncreasingOrder = IsIncreasingOrder(GetCursor().mDirection);
19908 bool open;
19909 GetCursor().SetOptionalKeyRange(GetOptionalKeyRange(), &open);
19910 if (GetOptionalKeyRange().isSome() &&
19911 !GetCursor().mLocaleAwareRangeBound->IsUnset()) {
19912 AppendConditionClause(kColumnNameAliasSortKey, kStmtParamNameRangeBound,
19913 isIncreasingOrder, !open, aQueryStart);
19917 nsCString continueQuery, continueToQuery, continuePrimaryKeyQuery;
19919 continueToQuery =
19920 aQueryStart + " AND "_ns +
19921 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterOrEquals
19922 : ComparisonOperator::LessOrEquals,
19923 kStmtParamNameCurrentKey);
19925 switch (GetCursor().mDirection) {
19926 case IDBCursorDirection::Next:
19927 case IDBCursorDirection::Prev:
19928 continueQuery =
19929 aQueryStart + " AND "_ns +
19930 GetSortKeyClause(isIncreasingOrder
19931 ? ComparisonOperator::GreaterOrEquals
19932 : ComparisonOperator::LessOrEquals,
19933 kStmtParamNameCurrentKey) +
19934 " AND ( "_ns +
19935 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan
19936 : ComparisonOperator::LessThan,
19937 kStmtParamNameCurrentKey) +
19938 " OR "_ns +
19939 GetKeyClause(aObjectDataKeyPrefix + "object_data_key"_ns,
19940 isIncreasingOrder ? ComparisonOperator::GreaterThan
19941 : ComparisonOperator::LessThan,
19942 kStmtParamNameObjectStorePosition) +
19943 " ) "_ns;
19945 continuePrimaryKeyQuery =
19946 aQueryStart +
19947 " AND ("
19948 "("_ns +
19949 GetSortKeyClause(ComparisonOperator::Equals,
19950 kStmtParamNameCurrentKey) +
19951 " AND "_ns +
19952 GetKeyClause(aObjectDataKeyPrefix + "object_data_key"_ns,
19953 isIncreasingOrder ? ComparisonOperator::GreaterOrEquals
19954 : ComparisonOperator::LessOrEquals,
19955 kStmtParamNameObjectStorePosition) +
19956 ") OR "_ns +
19957 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan
19958 : ComparisonOperator::LessThan,
19959 kStmtParamNameCurrentKey) +
19960 ")"_ns;
19961 break;
19963 case IDBCursorDirection::Nextunique:
19964 case IDBCursorDirection::Prevunique:
19965 continueQuery =
19966 aQueryStart + " AND "_ns +
19967 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan
19968 : ComparisonOperator::LessThan,
19969 kStmtParamNameCurrentKey);
19970 break;
19972 default:
19973 MOZ_CRASH("Should never get here!");
19976 const nsAutoCString suffix =
19977 aDirectionClause + kOpenLimit + ":"_ns + kStmtParamNameLimit;
19978 continueQuery += suffix;
19979 continueToQuery += suffix;
19980 if (!continuePrimaryKeyQuery.IsEmpty()) {
19981 continuePrimaryKeyQuery += suffix;
19984 GetCursor().mContinueQueries.init(std::move(continueQuery),
19985 std::move(continueToQuery),
19986 std::move(continuePrimaryKeyQuery));
19989 template <IDBCursorType CursorType>
19990 nsresult CommonOpenOpHelper<CursorType>::ProcessStatementSteps(
19991 mozIStorageStatement* const aStmt) {
19992 QM_TRY_INSPECT(const bool& hasResult,
19993 MOZ_TO_RESULT_INVOKE_MEMBER(aStmt, ExecuteStep));
19995 if (!hasResult) {
19996 SetResponse(void_t{});
19997 return NS_OK;
20000 Key previousKey;
20001 auto* optPreviousKey =
20002 IsUnique(GetCursor().mDirection) ? &previousKey : nullptr;
20004 QM_TRY_INSPECT(const auto& responseSize,
20005 PopulateResponseFromStatement(aStmt, true, optPreviousKey));
20007 // The degree to which extra responses on OpenOp can actually be used depends
20008 // on the parameters of subsequent ContinueOp operations, see also comment in
20009 // ContinueOp::DoDatabaseWork.
20011 // TODO: We should somehow evaluate the effects of this. Maybe use a smaller
20012 // extra count than for ContinueOp?
20013 PopulateExtraResponses(aStmt, GetCursor().mMaxExtraCount, responseSize,
20014 "OpenOp"_ns, optPreviousKey);
20016 return NS_OK;
20019 nsresult OpenOpHelper<IDBCursorType::ObjectStore>::DoDatabaseWork(
20020 DatabaseConnection* aConnection) {
20021 MOZ_ASSERT(aConnection);
20022 aConnection->AssertIsOnConnectionThread();
20023 MOZ_ASSERT(GetCursor().mObjectStoreId);
20025 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoObjectStoreDatabaseWork", DOM);
20027 const bool usingKeyRange = GetOptionalKeyRange().isSome();
20029 const nsCString queryStart = "SELECT "_ns + kColumnNameKey +
20030 ", file_ids, data "
20031 "FROM object_data "
20032 "WHERE object_store_id = :"_ns +
20033 kStmtParamNameId;
20035 const auto keyRangeClause =
20036 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
20037 GetOptionalKeyRange(), kColumnNameKey);
20039 const auto& directionClause = MakeDirectionClause(GetCursor().mDirection);
20041 // Note: Changing the number or order of SELECT columns in the query will
20042 // require changes to CursorOpBase::PopulateResponseFromStatement.
20043 const nsCString firstQuery = queryStart + keyRangeClause + directionClause +
20044 kOpenLimit +
20045 IntToCString(1 + GetCursor().mMaxExtraCount);
20047 QM_TRY_INSPECT(const auto& stmt,
20048 aConnection->BorrowCachedStatement(firstQuery));
20050 QM_TRY(MOZ_TO_RESULT(
20051 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mObjectStoreId)));
20053 if (usingKeyRange) {
20054 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20055 GetOptionalKeyRange().ref(), &*stmt)));
20058 // Now we need to make the query for ContinueOp.
20059 PrepareKeyConditionClauses(directionClause, queryStart);
20061 return ProcessStatementSteps(&*stmt);
20064 nsresult OpenOpHelper<IDBCursorType::ObjectStoreKey>::DoDatabaseWork(
20065 DatabaseConnection* aConnection) {
20066 MOZ_ASSERT(aConnection);
20067 aConnection->AssertIsOnConnectionThread();
20068 MOZ_ASSERT(GetCursor().mObjectStoreId);
20070 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoObjectStoreKeyDatabaseWork", DOM);
20072 const bool usingKeyRange = GetOptionalKeyRange().isSome();
20074 const nsCString queryStart = "SELECT "_ns + kColumnNameKey +
20075 " FROM object_data "
20076 "WHERE object_store_id = :"_ns +
20077 kStmtParamNameId;
20079 const auto keyRangeClause =
20080 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
20081 GetOptionalKeyRange(), kColumnNameKey);
20083 const auto& directionClause = MakeDirectionClause(GetCursor().mDirection);
20085 // Note: Changing the number or order of SELECT columns in the query will
20086 // require changes to CursorOpBase::PopulateResponseFromStatement.
20087 const nsCString firstQuery =
20088 queryStart + keyRangeClause + directionClause + kOpenLimit + "1"_ns;
20090 QM_TRY_INSPECT(const auto& stmt,
20091 aConnection->BorrowCachedStatement(firstQuery));
20093 QM_TRY(MOZ_TO_RESULT(
20094 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mObjectStoreId)));
20096 if (usingKeyRange) {
20097 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20098 GetOptionalKeyRange().ref(), &*stmt)));
20101 // Now we need to make the query to get the next match.
20102 PrepareKeyConditionClauses(directionClause, queryStart);
20104 return ProcessStatementSteps(&*stmt);
20107 nsresult OpenOpHelper<IDBCursorType::Index>::DoDatabaseWork(
20108 DatabaseConnection* aConnection) {
20109 MOZ_ASSERT(aConnection);
20110 aConnection->AssertIsOnConnectionThread();
20111 MOZ_ASSERT(GetCursor().mObjectStoreId);
20112 MOZ_ASSERT(GetCursor().mIndexId);
20114 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoIndexDatabaseWork", DOM);
20116 const bool usingKeyRange = GetOptionalKeyRange().isSome();
20118 const auto indexTable =
20119 GetCursor().mUniqueIndex ? "unique_index_data"_ns : "index_data"_ns;
20121 // The result of MakeColumnPairSelectionList is stored in a local variable,
20122 // since inlining it into the next statement causes a crash on some Mac OS X
20123 // builds (see https://bugzilla.mozilla.org/show_bug.cgi?id=1168606#c110).
20124 const auto columnPairSelectionList = MakeColumnPairSelectionList(
20125 "index_table.value"_ns, "index_table.value_locale"_ns,
20126 kColumnNameAliasSortKey, GetCursor().IsLocaleAware());
20127 const nsCString sortColumnAlias =
20128 "SELECT "_ns + columnPairSelectionList + ", "_ns;
20130 const nsAutoCString queryStart = sortColumnAlias +
20131 "index_table.object_data_key, "
20132 "object_data.file_ids, "
20133 "object_data.data "
20134 "FROM "_ns +
20135 indexTable +
20136 " AS index_table "
20137 "JOIN object_data "
20138 "ON index_table.object_store_id = "
20139 "object_data.object_store_id "
20140 "AND index_table.object_data_key = "
20141 "object_data.key "
20142 "WHERE index_table.index_id = :"_ns +
20143 kStmtParamNameId;
20145 const auto keyRangeClause =
20146 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
20147 GetOptionalKeyRange(), kColumnNameAliasSortKey);
20149 nsAutoCString directionClause = " ORDER BY "_ns + kColumnNameAliasSortKey;
20151 switch (GetCursor().mDirection) {
20152 case IDBCursorDirection::Next:
20153 case IDBCursorDirection::Nextunique:
20154 directionClause.AppendLiteral(" ASC, index_table.object_data_key ASC");
20155 break;
20157 case IDBCursorDirection::Prev:
20158 directionClause.AppendLiteral(" DESC, index_table.object_data_key DESC");
20159 break;
20161 case IDBCursorDirection::Prevunique:
20162 directionClause.AppendLiteral(" DESC, index_table.object_data_key ASC");
20163 break;
20165 default:
20166 MOZ_CRASH("Should never get here!");
20169 // Note: Changing the number or order of SELECT columns in the query will
20170 // require changes to CursorOpBase::PopulateResponseFromStatement.
20171 const nsCString firstQuery = queryStart + keyRangeClause + directionClause +
20172 kOpenLimit +
20173 IntToCString(1 + GetCursor().mMaxExtraCount);
20175 QM_TRY_INSPECT(const auto& stmt,
20176 aConnection->BorrowCachedStatement(firstQuery));
20178 QM_TRY(MOZ_TO_RESULT(
20179 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mIndexId)));
20181 if (usingKeyRange) {
20182 if (GetCursor().IsLocaleAware()) {
20183 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20184 GetOptionalKeyRange().ref(), &*stmt, GetCursor().mLocale)));
20185 } else {
20186 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20187 GetOptionalKeyRange().ref(), &*stmt)));
20191 // TODO: At least the last two statements are almost the same in all
20192 // DoDatabaseWork variants, consider removing this duplication.
20194 // Now we need to make the query to get the next match.
20195 PrepareKeyConditionClauses(directionClause, std::move(queryStart));
20197 return ProcessStatementSteps(&*stmt);
20200 nsresult OpenOpHelper<IDBCursorType::IndexKey>::DoDatabaseWork(
20201 DatabaseConnection* aConnection) {
20202 MOZ_ASSERT(aConnection);
20203 aConnection->AssertIsOnConnectionThread();
20204 MOZ_ASSERT(GetCursor().mObjectStoreId);
20205 MOZ_ASSERT(GetCursor().mIndexId);
20207 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoIndexKeyDatabaseWork", DOM);
20209 const bool usingKeyRange = GetOptionalKeyRange().isSome();
20211 const auto table =
20212 GetCursor().mUniqueIndex ? "unique_index_data"_ns : "index_data"_ns;
20214 // The result of MakeColumnPairSelectionList is stored in a local variable,
20215 // since inlining it into the next statement causes a crash on some Mac OS X
20216 // builds (see https://bugzilla.mozilla.org/show_bug.cgi?id=1168606#c110).
20217 const auto columnPairSelectionList = MakeColumnPairSelectionList(
20218 "value"_ns, "value_locale"_ns, kColumnNameAliasSortKey,
20219 GetCursor().IsLocaleAware());
20220 const nsCString sortColumnAlias =
20221 "SELECT "_ns + columnPairSelectionList + ", "_ns;
20223 const nsAutoCString queryStart = sortColumnAlias +
20224 "object_data_key "
20225 " FROM "_ns +
20226 table + " WHERE index_id = :"_ns +
20227 kStmtParamNameId;
20229 const auto keyRangeClause =
20230 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
20231 GetOptionalKeyRange(), kColumnNameAliasSortKey);
20233 nsAutoCString directionClause = " ORDER BY "_ns + kColumnNameAliasSortKey;
20235 switch (GetCursor().mDirection) {
20236 case IDBCursorDirection::Next:
20237 case IDBCursorDirection::Nextunique:
20238 directionClause.AppendLiteral(" ASC, object_data_key ASC");
20239 break;
20241 case IDBCursorDirection::Prev:
20242 directionClause.AppendLiteral(" DESC, object_data_key DESC");
20243 break;
20245 case IDBCursorDirection::Prevunique:
20246 directionClause.AppendLiteral(" DESC, object_data_key ASC");
20247 break;
20249 default:
20250 MOZ_CRASH("Should never get here!");
20253 // Note: Changing the number or order of SELECT columns in the query will
20254 // require changes to CursorOpBase::PopulateResponseFromStatement.
20255 const nsCString firstQuery =
20256 queryStart + keyRangeClause + directionClause + kOpenLimit + "1"_ns;
20258 QM_TRY_INSPECT(const auto& stmt,
20259 aConnection->BorrowCachedStatement(firstQuery));
20261 QM_TRY(MOZ_TO_RESULT(
20262 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mIndexId)));
20264 if (usingKeyRange) {
20265 if (GetCursor().IsLocaleAware()) {
20266 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20267 GetOptionalKeyRange().ref(), &*stmt, GetCursor().mLocale)));
20268 } else {
20269 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20270 GetOptionalKeyRange().ref(), &*stmt)));
20274 // Now we need to make the query to get the next match.
20275 PrepareKeyConditionClauses(directionClause, std::move(queryStart));
20277 return ProcessStatementSteps(&*stmt);
20280 template <IDBCursorType CursorType>
20281 nsresult Cursor<CursorType>::OpenOp::DoDatabaseWork(
20282 DatabaseConnection* aConnection) {
20283 MOZ_ASSERT(aConnection);
20284 aConnection->AssertIsOnConnectionThread();
20285 MOZ_ASSERT(mCursor);
20286 MOZ_ASSERT(!mCursor->mContinueQueries);
20288 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoDatabaseWork", DOM);
20290 auto helper = OpenOpHelper<CursorType>{*this};
20291 const auto rv = helper.DoDatabaseWork(aConnection);
20292 if (NS_WARN_IF(NS_FAILED(rv))) {
20293 return rv;
20296 return NS_OK;
20299 template <IDBCursorType CursorType>
20300 nsresult Cursor<CursorType>::CursorOpBase::SendSuccessResult() {
20301 AssertIsOnOwningThread();
20302 MOZ_ASSERT(mCursor);
20303 MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
20304 MOZ_ASSERT(mResponse.type() != CursorResponse::T__None);
20306 if (IsActorDestroyed()) {
20307 return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
20310 mCursor->SendResponseInternal(mResponse, mFiles);
20312 #ifdef DEBUG
20313 mResponseSent = true;
20314 #endif
20315 return NS_OK;
20318 template <IDBCursorType CursorType>
20319 nsresult Cursor<CursorType>::ContinueOp::DoDatabaseWork(
20320 DatabaseConnection* aConnection) {
20321 MOZ_ASSERT(aConnection);
20322 aConnection->AssertIsOnConnectionThread();
20323 MOZ_ASSERT(mCursor);
20324 MOZ_ASSERT(mCursor->mObjectStoreId);
20325 MOZ_ASSERT(!mCursor->mContinueQueries->mContinueQuery.IsEmpty());
20326 MOZ_ASSERT(!mCursor->mContinueQueries->mContinueToQuery.IsEmpty());
20327 MOZ_ASSERT(!mCurrentPosition.mKey.IsUnset());
20329 if constexpr (IsIndexCursor) {
20330 MOZ_ASSERT_IF(
20331 mCursor->mDirection == IDBCursorDirection::Next ||
20332 mCursor->mDirection == IDBCursorDirection::Prev,
20333 !mCursor->mContinueQueries->mContinuePrimaryKeyQuery.IsEmpty());
20334 MOZ_ASSERT(mCursor->mIndexId);
20335 MOZ_ASSERT(!mCurrentPosition.mObjectStoreKey.IsUnset());
20338 AUTO_PROFILER_LABEL("Cursor::ContinueOp::DoDatabaseWork", DOM);
20340 // We need to pick a query based on whether or not a key was passed to the
20341 // continue function. If not we'll grab the next item in the database that
20342 // is greater than (or less than, if we're running a PREV cursor) the current
20343 // key. If a key was passed we'll grab the next item in the database that is
20344 // greater than (or less than, if we're running a PREV cursor) or equal to the
20345 // key that was specified.
20347 // TODO: The description above is not complete, it does not take account of
20348 // ContinuePrimaryKey nor Advance.
20350 // Note: Changing the number or order of SELECT columns in the query will
20351 // require changes to CursorOpBase::PopulateResponseFromStatement.
20353 const uint32_t advanceCount =
20354 mParams.type() == CursorRequestParams::TAdvanceParams
20355 ? mParams.get_AdvanceParams().count()
20356 : 1;
20357 MOZ_ASSERT(advanceCount > 0);
20359 bool hasContinueKey = false;
20360 bool hasContinuePrimaryKey = false;
20362 auto explicitContinueKey = Key{};
20364 switch (mParams.type()) {
20365 case CursorRequestParams::TContinueParams:
20366 if (!mParams.get_ContinueParams().key().IsUnset()) {
20367 hasContinueKey = true;
20368 explicitContinueKey = mParams.get_ContinueParams().key();
20370 break;
20371 case CursorRequestParams::TContinuePrimaryKeyParams:
20372 MOZ_ASSERT(!mParams.get_ContinuePrimaryKeyParams().key().IsUnset());
20373 MOZ_ASSERT(
20374 !mParams.get_ContinuePrimaryKeyParams().primaryKey().IsUnset());
20375 MOZ_ASSERT(mCursor->mDirection == IDBCursorDirection::Next ||
20376 mCursor->mDirection == IDBCursorDirection::Prev);
20377 hasContinueKey = true;
20378 hasContinuePrimaryKey = true;
20379 explicitContinueKey = mParams.get_ContinuePrimaryKeyParams().key();
20380 break;
20381 case CursorRequestParams::TAdvanceParams:
20382 break;
20383 default:
20384 MOZ_CRASH("Should never get here!");
20387 // TODO: Whether it makes sense to preload depends on the kind of the
20388 // subsequent operations, not of the current operation. We could assume that
20389 // the subsequent operations are:
20390 // - the same as the current operation (with the same parameter values)
20391 // - as above, except for Advance, where we assume the count will be 1 on the
20392 // next call
20393 // - basic operations (Advance with count 1 or Continue-without-key)
20395 // For now, we implement the second option for now (which correspond to
20396 // !hasContinueKey).
20398 // Based on that, we could in both cases either preload for any assumed
20399 // subsequent operations, or only for the basic operations. For now, we
20400 // preload only for an assumed basic operation. Other operations would require
20401 // more work on the client side for invalidation, and may not make any sense
20402 // at all.
20403 const uint32_t maxExtraCount = hasContinueKey ? 0 : mCursor->mMaxExtraCount;
20405 QM_TRY_INSPECT(const auto& stmt,
20406 aConnection->BorrowCachedStatement(
20407 mCursor->mContinueQueries->GetContinueQuery(
20408 hasContinueKey, hasContinuePrimaryKey)));
20410 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName(
20411 kStmtParamNameLimit,
20412 IntToCString(advanceCount + mCursor->mMaxExtraCount))));
20414 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameId, mCursor->Id())));
20416 // Bind current key.
20417 const auto& continueKey =
20418 hasContinueKey ? explicitContinueKey
20419 : mCurrentPosition.GetSortKey(mCursor->IsLocaleAware());
20420 QM_TRY(MOZ_TO_RESULT(
20421 continueKey.BindToStatement(&*stmt, kStmtParamNameCurrentKey)));
20423 // Bind range bound if it is specified.
20424 if (!mCursor->mLocaleAwareRangeBound->IsUnset()) {
20425 QM_TRY(MOZ_TO_RESULT(mCursor->mLocaleAwareRangeBound->BindToStatement(
20426 &*stmt, kStmtParamNameRangeBound)));
20429 // Bind object store position if duplicates are allowed and we're not
20430 // continuing to a specific key.
20431 if constexpr (IsIndexCursor) {
20432 if (!hasContinueKey && (mCursor->mDirection == IDBCursorDirection::Next ||
20433 mCursor->mDirection == IDBCursorDirection::Prev)) {
20434 QM_TRY(MOZ_TO_RESULT(mCurrentPosition.mObjectStoreKey.BindToStatement(
20435 &*stmt, kStmtParamNameObjectStorePosition)));
20436 } else if (hasContinuePrimaryKey) {
20437 QM_TRY(MOZ_TO_RESULT(
20438 mParams.get_ContinuePrimaryKeyParams().primaryKey().BindToStatement(
20439 &*stmt, kStmtParamNameObjectStorePosition)));
20443 // TODO: Why do we query the records we don't need and skip them here, rather
20444 // than using a OFFSET clause in the query?
20445 for (uint32_t index = 0; index < advanceCount; index++) {
20446 QM_TRY_INSPECT(const bool& hasResult,
20447 MOZ_TO_RESULT_INVOKE_MEMBER(&*stmt, ExecuteStep));
20449 if (!hasResult) {
20450 mResponse = void_t();
20451 return NS_OK;
20455 Key previousKey;
20456 auto* const optPreviousKey =
20457 IsUnique(mCursor->mDirection) ? &previousKey : nullptr;
20459 auto helper = CursorOpBaseHelperBase<CursorType>{*this};
20460 QM_TRY_INSPECT(const auto& responseSize, helper.PopulateResponseFromStatement(
20461 &*stmt, true, optPreviousKey));
20463 helper.PopulateExtraResponses(&*stmt, maxExtraCount, responseSize,
20464 "ContinueOp"_ns, optPreviousKey);
20466 return NS_OK;
20469 Utils::Utils()
20470 #ifdef DEBUG
20471 : mActorDestroyed(false)
20472 #endif
20474 AssertIsOnBackgroundThread();
20477 Utils::~Utils() { MOZ_ASSERT(mActorDestroyed); }
20479 void Utils::ActorDestroy(ActorDestroyReason aWhy) {
20480 AssertIsOnBackgroundThread();
20481 MOZ_ASSERT(!mActorDestroyed);
20483 #ifdef DEBUG
20484 mActorDestroyed = true;
20485 #endif
20488 mozilla::ipc::IPCResult Utils::RecvDeleteMe() {
20489 AssertIsOnBackgroundThread();
20490 MOZ_ASSERT(!mActorDestroyed);
20492 QM_WARNONLY_TRY(OkIf(PBackgroundIndexedDBUtilsParent::Send__delete__(this)));
20494 return IPC_OK();
20497 mozilla::ipc::IPCResult Utils::RecvGetFileReferences(
20498 const PersistenceType& aPersistenceType, const nsACString& aOrigin,
20499 const nsAString& aDatabaseName, const int64_t& aFileId, int32_t* aRefCnt,
20500 int32_t* aDBRefCnt, bool* aResult) {
20501 AssertIsOnBackgroundThread();
20502 MOZ_ASSERT(aRefCnt);
20503 MOZ_ASSERT(aDBRefCnt);
20504 MOZ_ASSERT(aResult);
20505 MOZ_ASSERT(!mActorDestroyed);
20507 if (NS_WARN_IF(!IndexedDatabaseManager::Get())) {
20508 return IPC_FAIL(this, "No IndexedDatabaseManager active!");
20511 if (NS_WARN_IF(!QuotaManager::Get())) {
20512 return IPC_FAIL(this, "No QuotaManager active!");
20515 if (NS_WARN_IF(!StaticPrefs::dom_indexedDB_testing())) {
20516 return IPC_FAIL(this, "IndexedDB is not in testing mode!");
20519 if (NS_WARN_IF(!IsValidPersistenceType(aPersistenceType))) {
20520 return IPC_FAIL(this, "PersistenceType is not valid!");
20523 if (NS_WARN_IF(aOrigin.IsEmpty())) {
20524 return IPC_FAIL(this, "Origin is empty!");
20527 if (NS_WARN_IF(aDatabaseName.IsEmpty())) {
20528 return IPC_FAIL(this, "DatabaseName is empty!");
20531 if (NS_WARN_IF(aFileId == 0)) {
20532 return IPC_FAIL(this, "No FileId!");
20535 nsresult rv =
20536 DispatchAndReturnFileReferences(aPersistenceType, aOrigin, aDatabaseName,
20537 aFileId, aRefCnt, aDBRefCnt, aResult);
20538 if (NS_WARN_IF(NS_FAILED(rv))) {
20539 return IPC_FAIL(this, "DispatchAndReturnFileReferences failed!");
20542 return IPC_OK();
20545 #ifdef DEBUG
20547 NS_IMPL_ISUPPORTS(DEBUGThreadSlower, nsIThreadObserver)
20549 NS_IMETHODIMP
20550 DEBUGThreadSlower::OnDispatchedEvent() { MOZ_CRASH("Should never be called!"); }
20552 NS_IMETHODIMP
20553 DEBUGThreadSlower::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
20554 bool /* aMayWait */) {
20555 return NS_OK;
20558 NS_IMETHODIMP
20559 DEBUGThreadSlower::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
20560 bool /* aEventWasProcessed */) {
20561 MOZ_ASSERT(kDEBUGThreadSleepMS);
20563 MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(kDEBUGThreadSleepMS)) ==
20564 PR_SUCCESS);
20565 return NS_OK;
20568 #endif // DEBUG
20570 nsresult FileHelper::Init() {
20571 MOZ_ASSERT(!IsOnBackgroundThread());
20573 auto fileDirectory = mFileManager->GetCheckedDirectory();
20574 if (NS_WARN_IF(!fileDirectory)) {
20575 return NS_ERROR_FAILURE;
20578 auto journalDirectory = mFileManager->EnsureJournalDirectory();
20579 if (NS_WARN_IF(!journalDirectory)) {
20580 return NS_ERROR_FAILURE;
20583 DebugOnly<bool> exists;
20584 MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists)));
20585 MOZ_ASSERT(exists);
20587 DebugOnly<bool> isDirectory;
20588 MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory)));
20589 MOZ_ASSERT(isDirectory);
20591 mFileDirectory.init(WrapNotNullUnchecked(std::move(fileDirectory)));
20592 mJournalDirectory.init(WrapNotNullUnchecked(std::move(journalDirectory)));
20594 return NS_OK;
20597 nsCOMPtr<nsIFile> FileHelper::GetFile(const DatabaseFileInfo& aFileInfo) {
20598 MOZ_ASSERT(!IsOnBackgroundThread());
20600 return mFileManager->GetFileForId(mFileDirectory->get(), aFileInfo.Id());
20603 nsCOMPtr<nsIFile> FileHelper::GetJournalFile(
20604 const DatabaseFileInfo& aFileInfo) {
20605 MOZ_ASSERT(!IsOnBackgroundThread());
20607 return mFileManager->GetFileForId(mJournalDirectory->get(), aFileInfo.Id());
20610 nsresult FileHelper::CreateFileFromStream(nsIFile& aFile, nsIFile& aJournalFile,
20611 nsIInputStream& aInputStream,
20612 bool aCompress,
20613 const Maybe<CipherKey>& aMaybeKey) {
20614 MOZ_ASSERT(!IsOnBackgroundThread());
20616 QM_TRY_INSPECT(const auto& exists,
20617 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists));
20619 // DOM blobs that are being stored in IDB are cached by calling
20620 // IDBDatabase::GetOrCreateFileActorForBlob. So if the same DOM blob is stored
20621 // again under a different key or in a different object store, we just add
20622 // a new reference instead of creating a new copy (all such stored blobs share
20623 // the same id).
20624 // However, it can happen that CreateFileFromStream failed due to quota
20625 // exceeded error and for some reason the orphaned file couldn't be deleted
20626 // immediately. Now, if the operation is being repeated, the DOM blob is
20627 // already cached, so it has the same file id which clashes with the orphaned
20628 // file. We could do some tricks to restore previous copy loop, but it's safer
20629 // to just delete the orphaned file and start from scratch.
20630 // This corner case is partially simulated in test_file_copy_failure.js
20631 if (exists) {
20632 QM_TRY_INSPECT(const auto& isFile,
20633 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, IsFile));
20635 QM_TRY(OkIf(isFile), NS_ERROR_FAILURE);
20637 QM_TRY_INSPECT(const auto& journalExists,
20638 MOZ_TO_RESULT_INVOKE_MEMBER(aJournalFile, Exists));
20640 QM_TRY(OkIf(journalExists), NS_ERROR_FAILURE);
20642 QM_TRY_INSPECT(const auto& journalIsFile,
20643 MOZ_TO_RESULT_INVOKE_MEMBER(aJournalFile, IsFile));
20645 QM_TRY(OkIf(journalIsFile), NS_ERROR_FAILURE);
20647 IDB_WARNING("Deleting orphaned file!");
20649 QM_TRY(MOZ_TO_RESULT(mFileManager->SyncDeleteFile(aFile, aJournalFile)));
20652 // Create a journal file first.
20653 QM_TRY(MOZ_TO_RESULT(aJournalFile.Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
20655 // Now try to copy the stream.
20656 QM_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileOutputStream,
20657 CreateFileOutputStream(mFileManager->Type(),
20658 mFileManager->OriginMetadata(),
20659 Client::IDB, &aFile));
20661 AutoTArray<char, kFileCopyBufferSize> buffer;
20662 const auto actualOutputStream =
20663 [aCompress, &aMaybeKey, &buffer,
20664 baseOutputStream =
20665 std::move(fileOutputStream)]() mutable -> nsCOMPtr<nsIOutputStream> {
20666 if (aMaybeKey) {
20667 baseOutputStream =
20668 MakeRefPtr<EncryptingOutputStream<IndexedDBCipherStrategy>>(
20669 std::move(baseOutputStream), kEncryptedStreamBlockSize,
20670 *aMaybeKey);
20673 if (aCompress) {
20674 auto snappyOutputStream =
20675 MakeRefPtr<SnappyCompressOutputStream>(baseOutputStream);
20677 buffer.SetLength(snappyOutputStream->BlockSize());
20679 return snappyOutputStream;
20682 buffer.SetLength(kFileCopyBufferSize);
20683 return std::move(baseOutputStream);
20684 }();
20686 QM_TRY(MOZ_TO_RESULT(SyncCopy(aInputStream, *actualOutputStream,
20687 buffer.Elements(), buffer.Length())));
20689 return NS_OK;
20692 class FileHelper::ReadCallback final : public nsIInputStreamCallback {
20693 public:
20694 NS_DECL_THREADSAFE_ISUPPORTS
20696 ReadCallback()
20697 : mMutex("ReadCallback::mMutex"),
20698 mCondVar(mMutex, "ReadCallback::mCondVar"),
20699 mInputAvailable(false) {}
20701 NS_IMETHOD
20702 OnInputStreamReady(nsIAsyncInputStream* aStream) override {
20703 mozilla::MutexAutoLock autolock(mMutex);
20705 mInputAvailable = true;
20706 mCondVar.Notify();
20708 return NS_OK;
20711 nsresult AsyncWait(nsIAsyncInputStream* aStream, uint32_t aBufferSize,
20712 nsIEventTarget* aTarget) {
20713 MOZ_ASSERT(aStream);
20714 mozilla::MutexAutoLock autolock(mMutex);
20716 nsresult rv = aStream->AsyncWait(this, 0, aBufferSize, aTarget);
20717 if (NS_WARN_IF(NS_FAILED(rv))) {
20718 return rv;
20721 mInputAvailable = false;
20722 while (!mInputAvailable) {
20723 mCondVar.Wait();
20726 return NS_OK;
20729 private:
20730 ~ReadCallback() = default;
20732 mozilla::Mutex mMutex MOZ_UNANNOTATED;
20733 mozilla::CondVar mCondVar;
20734 bool mInputAvailable;
20737 NS_IMPL_ADDREF(FileHelper::ReadCallback);
20738 NS_IMPL_RELEASE(FileHelper::ReadCallback);
20740 NS_INTERFACE_MAP_BEGIN(FileHelper::ReadCallback)
20741 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
20742 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamCallback)
20743 NS_INTERFACE_MAP_END
20745 nsresult FileHelper::SyncRead(nsIInputStream& aInputStream, char* const aBuffer,
20746 const uint32_t aBufferSize,
20747 uint32_t* const aRead) {
20748 MOZ_ASSERT(!IsOnBackgroundThread());
20750 // Let's try to read, directly.
20751 nsresult rv = aInputStream.Read(aBuffer, aBufferSize, aRead);
20752 if (NS_SUCCEEDED(rv) || rv != NS_BASE_STREAM_WOULD_BLOCK) {
20753 return rv;
20756 // We need to proceed async.
20757 nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(&aInputStream);
20758 if (!asyncStream) {
20759 return rv;
20762 if (!mReadCallback) {
20763 mReadCallback.init(MakeNotNull<RefPtr<ReadCallback>>());
20766 // We just need any thread with an event loop for receiving the
20767 // OnInputStreamReady callback. Let's use the I/O thread.
20768 nsCOMPtr<nsIEventTarget> target =
20769 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
20770 MOZ_ASSERT(target);
20772 rv = (*mReadCallback)->AsyncWait(asyncStream, aBufferSize, target);
20773 if (NS_WARN_IF(NS_FAILED(rv))) {
20774 return rv;
20777 return SyncRead(aInputStream, aBuffer, aBufferSize, aRead);
20780 nsresult FileHelper::SyncCopy(nsIInputStream& aInputStream,
20781 nsIOutputStream& aOutputStream,
20782 char* const aBuffer, const uint32_t aBufferSize) {
20783 MOZ_ASSERT(!IsOnBackgroundThread());
20785 AUTO_PROFILER_LABEL("FileHelper::SyncCopy", DOM);
20787 nsresult rv;
20789 do {
20790 uint32_t numRead;
20791 rv = SyncRead(aInputStream, aBuffer, aBufferSize, &numRead);
20792 if (NS_WARN_IF(NS_FAILED(rv))) {
20793 break;
20796 if (!numRead) {
20797 break;
20800 uint32_t numWrite;
20801 rv = aOutputStream.Write(aBuffer, numRead, &numWrite);
20802 if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
20803 rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
20805 if (NS_WARN_IF(NS_FAILED(rv))) {
20806 break;
20809 if (NS_WARN_IF(numWrite != numRead)) {
20810 rv = NS_ERROR_FAILURE;
20811 break;
20813 } while (true);
20815 if (NS_SUCCEEDED(rv)) {
20816 rv = aOutputStream.Flush();
20817 if (NS_WARN_IF(NS_FAILED(rv))) {
20818 return rv;
20822 nsresult rv2 = aOutputStream.Close();
20823 if (NS_WARN_IF(NS_FAILED(rv2))) {
20824 return NS_SUCCEEDED(rv) ? rv2 : rv;
20827 return rv;
20830 } // namespace dom::indexedDB
20831 } // namespace mozilla
20833 #undef IDB_MOBILE
20834 #undef IDB_DEBUG_LOG