Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / indexedDB / ActorsParent.cpp
blob00a8528f559b06643645bbaf0234dff27a306697
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/SnappyCompressOutputStream.h"
86 #include "mozilla/SpinEventLoopUntil.h"
87 #include "mozilla/StaticPtr.h"
88 #include "mozilla/TimeStamp.h"
89 #include "mozilla/UniquePtr.h"
90 #include "mozilla/Unused.h"
91 #include "mozilla/Variant.h"
92 #include "mozilla/dom/BlobImpl.h"
93 #include "mozilla/dom/ContentParent.h"
94 #include "mozilla/dom/FileBlobImpl.h"
95 #include "mozilla/dom/FlippedOnce.h"
96 #include "mozilla/dom/IDBCursorBinding.h"
97 #include "mozilla/dom/IDBFactory.h"
98 #include "mozilla/dom/IPCBlob.h"
99 #include "mozilla/dom/IPCBlobUtils.h"
100 #include "mozilla/dom/IndexedDatabase.h"
101 #include "mozilla/dom/Nullable.h"
102 #include "mozilla/dom/PContentParent.h"
103 #include "mozilla/dom/ScriptSettings.h"
104 #include "mozilla/dom/indexedDB/IDBResult.h"
105 #include "mozilla/dom/indexedDB/Key.h"
106 #include "mozilla/dom/indexedDB/PBackgroundIDBCursor.h"
107 #include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
108 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabase.h"
109 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
110 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
111 #include "mozilla/dom/indexedDB/PBackgroundIDBFactory.h"
112 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
113 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
114 #include "mozilla/dom/indexedDB/PBackgroundIDBRequest.h"
115 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
116 #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
117 #include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
118 #include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
119 #include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
120 #include "mozilla/dom/ipc/IdType.h"
121 #include "mozilla/dom/quota/Assertions.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/DebugOnlyMacro.h"
127 #include "mozilla/dom/quota/DirectoryLock.h"
128 #include "mozilla/dom/quota/DecryptingInputStream_impl.h"
129 #include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
130 #include "mozilla/dom/quota/FileStreams.h"
131 #include "mozilla/dom/quota/OriginScope.h"
132 #include "mozilla/dom/quota/PersistenceType.h"
133 #include "mozilla/dom/quota/QuotaCommon.h"
134 #include "mozilla/dom/quota/QuotaManager.h"
135 #include "mozilla/dom/quota/QuotaObject.h"
136 #include "mozilla/dom/quota/ResultExtensions.h"
137 #include "mozilla/dom/quota/UsageInfo.h"
138 #include "mozilla/fallible.h"
139 #include "mozilla/ipc/BackgroundParent.h"
140 #include "mozilla/ipc/BackgroundUtils.h"
141 #include "mozilla/ipc/InputStreamParams.h"
142 #include "mozilla/ipc/PBackgroundParent.h"
143 #include "mozilla/ipc/PBackgroundSharedTypes.h"
144 #include "mozilla/ipc/ProtocolUtils.h"
145 #include "mozilla/mozalloc.h"
146 #include "mozilla/storage/Variant.h"
147 #include "nsBaseHashtable.h"
148 #include "nsCOMPtr.h"
149 #include "nsClassHashtable.h"
150 #include "nsContentUtils.h"
151 #include "nsTHashMap.h"
152 #include "nsDebug.h"
153 #include "nsError.h"
154 #include "nsEscape.h"
155 #include "nsHashKeys.h"
156 #include "nsIAsyncInputStream.h"
157 #include "nsID.h"
158 #include "nsIDUtils.h"
159 #include "nsIDirectoryEnumerator.h"
160 #include "nsIEventTarget.h"
161 #include "nsIFile.h"
162 #include "nsIFileProtocolHandler.h"
163 #include "nsIFileStreams.h"
164 #include "nsIFileURL.h"
165 #include "nsIInputStream.h"
166 #include "nsIOutputStream.h"
167 #include "nsIProtocolHandler.h"
168 #include "nsIRunnable.h"
169 #include "nsISupports.h"
170 #include "nsISupportsPriority.h"
171 #include "nsISupportsUtils.h"
172 #include "nsIThread.h"
173 #include "nsIThreadInternal.h"
174 #include "nsITimer.h"
175 #include "nsIURIMutator.h"
176 #include "nsIVariant.h"
177 #include "nsLiteralString.h"
178 #include "nsNetCID.h"
179 #include "nsPrintfCString.h"
180 #include "nsProxyRelease.h"
181 #include "nsServiceManagerUtils.h"
182 #include "nsStreamUtils.h"
183 #include "nsString.h"
184 #include "nsStringFlags.h"
185 #include "nsStringFwd.h"
186 #include "nsTArray.h"
187 #include "nsTHashSet.h"
188 #include "nsTHashtable.h"
189 #include "nsTLiteralString.h"
190 #include "nsTStringRepr.h"
191 #include "nsThreadPool.h"
192 #include "nsThreadUtils.h"
193 #include "nscore.h"
194 #include "prinrval.h"
195 #include "prio.h"
196 #include "prsystem.h"
197 #include "prthread.h"
198 #include "prtime.h"
199 #include "prtypes.h"
200 #include "snappy/snappy.h"
202 struct JSContext;
203 class JSObject;
204 template <class T>
205 class nsPtrHashKey;
207 #define IDB_DEBUG_LOG(_args) \
208 MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), LogLevel::Debug, _args)
210 #if defined(MOZ_WIDGET_ANDROID)
211 # define IDB_MOBILE
212 #endif
214 // Helper macros to reduce assertion verbosity
215 // AUUF == ASSERT_UNREACHABLE_UNLESS_FUZZING
216 #ifdef DEBUG
217 # ifdef FUZZING
218 # define NS_AUUF_OR_WARN(...) NS_WARNING(__VA_ARGS__)
219 # else
220 # define NS_AUUF_OR_WARN(...) MOZ_ASSERT(false, __VA_ARGS__)
221 # endif
222 # define NS_AUUF_OR_WARN_IF(cond) \
223 [](bool aCond) { \
224 if (MOZ_UNLIKELY(aCond)) { \
225 NS_AUUF_OR_WARN(#cond); \
227 return aCond; \
228 }((cond))
229 #else
230 # define NS_AUUF_OR_WARN(...) \
231 do { \
232 } while (false)
233 # define NS_AUUF_OR_WARN_IF(cond) static_cast<bool>(cond)
234 #endif
236 namespace mozilla {
238 namespace dom::indexedDB {
240 using namespace mozilla::dom::quota;
241 using namespace mozilla::ipc;
242 using mozilla::dom::quota::Client;
244 namespace {
246 class ConnectionPool;
247 class Database;
248 struct DatabaseActorInfo;
249 class DatabaseFile;
250 class DatabaseLoggingInfo;
251 class DatabaseMaintenance;
252 class Factory;
253 class Maintenance;
254 class OpenDatabaseOp;
255 class TransactionBase;
256 class TransactionDatabaseOperationBase;
257 class VersionChangeTransaction;
258 template <bool StatementHasIndexKeyBindings>
259 struct ValuePopulateResponseHelper;
261 /*******************************************************************************
262 * Constants
263 ******************************************************************************/
265 const int32_t kStorageProgressGranularity = 1000;
267 // Changing the value here will override the page size of new databases only.
268 // A journal mode change and VACUUM are needed to change existing databases, so
269 // the best way to do that is to use the schema version upgrade mechanism.
270 const uint32_t kSQLitePageSizeOverride =
271 #ifdef IDB_MOBILE
272 2048;
273 #else
274 4096;
275 #endif
277 static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
278 (kSQLitePageSizeOverride % 2 == 0 &&
279 kSQLitePageSizeOverride >= 512 &&
280 kSQLitePageSizeOverride <= 65536),
281 "Must be 0 (disabled) or a power of 2 between 512 and 65536!");
283 // Set to -1 to use SQLite's default, 0 to disable, or some positive number to
284 // enforce a custom limit.
285 const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
287 // Set to some multiple of the page size to grow the database in larger chunks.
288 const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
290 static_assert(kSQLiteGrowthIncrement >= 0 &&
291 kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
292 kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
293 "Must be 0 (disabled) or a positive multiple of the page size!");
295 // The maximum number of threads that can be used for database activity at a
296 // single time. Please keep in sync with the constants in
297 // test_connection_idle_maintenance*.js tests
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. Please keep in
311 // sync with the timeouts in test_connection_idle_maintenance*.js tests
312 const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds
314 // The length of time that database connections will be held open after all
315 // transactions and maintenance have completed.
316 const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds
318 // The length of time that idle threads will stay alive before being shut down.
319 const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds
321 #define SAVEPOINT_CLAUSE "SAVEPOINT sp;"_ns
323 // For efficiency reasons, kEncryptedStreamBlockSize must be a multiple of large
324 // 4k disk sectors.
325 static_assert(kEncryptedStreamBlockSize % 4096 == 0);
326 // Similarly, the file copy buffer size must be a multiple of the encrypted
327 // block size.
328 static_assert(kFileCopyBufferSize % kEncryptedStreamBlockSize == 0);
330 constexpr auto kFileManagerDirectoryNameSuffix = u".files"_ns;
331 constexpr auto kSQLiteSuffix = u".sqlite"_ns;
332 constexpr auto kSQLiteJournalSuffix = u".sqlite-journal"_ns;
333 constexpr auto kSQLiteSHMSuffix = u".sqlite-shm"_ns;
334 constexpr auto kSQLiteWALSuffix = u".sqlite-wal"_ns;
336 // The following constants define all names of binding parameters in statements,
337 // where they are bound by name. This should include all parameter names which
338 // are bound by name. Binding may be done by index when the statement definition
339 // and binding are done in the same local scope, and no other reasons prevent
340 // using the indexes (e.g. multiple statement variants with differing number or
341 // order of parameters). Neither the styles of specifying parameter names
342 // (literally vs. via these constants) nor the binding styles (by index vs. by
343 // name) should not be mixed for the same statement. The decision must be made
344 // for each statement based on the proximity of statement and binding calls.
345 constexpr auto kStmtParamNameCurrentKey = "current_key"_ns;
346 constexpr auto kStmtParamNameRangeBound = "range_bound"_ns;
347 constexpr auto kStmtParamNameObjectStorePosition = "object_store_position"_ns;
348 constexpr auto kStmtParamNameLowerKey = "lower_key"_ns;
349 constexpr auto kStmtParamNameUpperKey = "upper_key"_ns;
350 constexpr auto kStmtParamNameKey = "key"_ns;
351 constexpr auto kStmtParamNameObjectStoreId = "object_store_id"_ns;
352 constexpr auto kStmtParamNameIndexId = "index_id"_ns;
353 // TODO: Maybe the uses of kStmtParamNameId should be replaced by more
354 // specific constants such as kStmtParamNameObjectStoreId.
355 constexpr auto kStmtParamNameId = "id"_ns;
356 constexpr auto kStmtParamNameValue = "value"_ns;
357 constexpr auto kStmtParamNameObjectDataKey = "object_data_key"_ns;
358 constexpr auto kStmtParamNameIndexDataValues = "index_data_values"_ns;
359 constexpr auto kStmtParamNameData = "data"_ns;
360 constexpr auto kStmtParamNameFileIds = "file_ids"_ns;
361 constexpr auto kStmtParamNameValueLocale = "value_locale"_ns;
362 constexpr auto kStmtParamNameLimit = "limit"_ns;
364 // The following constants define some names of columns in tables, which are
365 // referred to in remote locations, e.g. in calls to
366 // GetBindingClauseForKeyRange.
367 constexpr auto kColumnNameKey = "key"_ns;
368 constexpr auto kColumnNameValue = "value"_ns;
369 constexpr auto kColumnNameAliasSortKey = "sort_column"_ns;
371 // SQL fragments used at multiple locations.
372 constexpr auto kOpenLimit = " LIMIT "_ns;
374 // The deletion marker file is created before RemoveDatabaseFilesAndDirectory
375 // begins deleting a database. It is removed as the last step of deletion. If a
376 // deletion marker file is found when initializing the origin, the deletion
377 // routine is run again to ensure that the database and all of its related files
378 // are removed. The primary goal of this mechanism is to avoid situations where
379 // a database has been partially deleted, leading to inconsistent state for the
380 // origin.
381 constexpr auto kIdbDeletionMarkerFilePrefix = u"idb-deleting-"_ns;
383 const uint32_t kDeleteTimeoutMs = 1000;
385 #ifdef DEBUG
387 const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
388 const uint32_t kDEBUGThreadSleepMS = 0;
390 #endif
392 /*******************************************************************************
393 * Metadata classes
394 ******************************************************************************/
396 // Can be instantiated either on the QuotaManager IO thread or on a
397 // versionchange transaction thread. These threads can never race so this is
398 // totally safe.
399 struct FullIndexMetadata {
400 IndexMetadata mCommonMetadata = {0, nsString(), KeyPath(0), nsCString(),
401 false, false, false};
403 FlippedOnce<false> mDeleted;
405 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
407 private:
408 ~FullIndexMetadata() = default;
411 using IndexTable = nsTHashMap<nsUint64HashKey, SafeRefPtr<FullIndexMetadata>>;
413 // Can be instantiated either on the QuotaManager IO thread or on a
414 // versionchange transaction thread. These threads can never race so this is
415 // totally safe.
416 struct FullObjectStoreMetadata {
417 ObjectStoreMetadata mCommonMetadata;
418 IndexTable mIndexes;
420 // The auto increment ids are touched on both the background thread and the
421 // transaction I/O thread, and they must be kept in sync, so we need a mutex
422 // to protect them.
423 struct AutoIncrementIds {
424 int64_t next;
425 int64_t committed;
427 DataMutex<AutoIncrementIds> mAutoIncrementIds;
429 FlippedOnce<false> mDeleted;
431 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
433 bool HasLiveIndexes() const;
435 FullObjectStoreMetadata(ObjectStoreMetadata aCommonMetadata,
436 const AutoIncrementIds& aAutoIncrementIds)
437 : mCommonMetadata{std::move(aCommonMetadata)},
438 mAutoIncrementIds{AutoIncrementIds{aAutoIncrementIds},
439 "FullObjectStoreMetadata"} {}
441 private:
442 ~FullObjectStoreMetadata() = default;
445 using ObjectStoreTable =
446 nsTHashMap<nsUint64HashKey, SafeRefPtr<FullObjectStoreMetadata>>;
448 static_assert(
449 std::is_same_v<IndexOrObjectStoreId,
450 std::remove_cv_t<std::remove_reference_t<
451 decltype(std::declval<const ObjectStoreGetParams&>()
452 .objectStoreId())>>>);
453 static_assert(
454 std::is_same_v<
455 IndexOrObjectStoreId,
456 std::remove_cv_t<std::remove_reference_t<
457 decltype(std::declval<const IndexGetParams&>().objectStoreId())>>>);
459 struct FullDatabaseMetadata final : AtomicSafeRefCounted<FullDatabaseMetadata> {
460 DatabaseMetadata mCommonMetadata;
461 nsCString mDatabaseId;
462 nsString mFilePath;
463 ObjectStoreTable mObjectStores;
465 IndexOrObjectStoreId mNextObjectStoreId = 0;
466 IndexOrObjectStoreId mNextIndexId = 0;
468 public:
469 explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
470 : mCommonMetadata(aCommonMetadata) {
471 AssertIsOnBackgroundThread();
474 [[nodiscard]] SafeRefPtr<FullDatabaseMetadata> Duplicate() const;
476 MOZ_DECLARE_REFCOUNTED_TYPENAME(FullDatabaseMetadata)
479 template <class Enumerable>
480 auto MatchMetadataNameOrId(const Enumerable& aEnumerable,
481 IndexOrObjectStoreId aId,
482 Maybe<const nsAString&> aName = Nothing()) {
483 AssertIsOnBackgroundThread();
484 MOZ_ASSERT(aId);
486 const auto it = std::find_if(
487 aEnumerable.cbegin(), aEnumerable.cend(),
488 [aId, aName](const auto& entry) {
489 MOZ_ASSERT(entry.GetKey() != 0);
491 const auto& value = entry.GetData();
492 MOZ_ASSERT(value);
494 return !value->mDeleted &&
495 (aId == value->mCommonMetadata.id() ||
496 (aName && *aName == value->mCommonMetadata.name()));
499 return ToMaybeRef(it != aEnumerable.cend() ? it->GetData().unsafeGetRawPtr()
500 : nullptr);
503 /*******************************************************************************
504 * SQLite functions
505 ******************************************************************************/
507 // WARNING: the hash function used for the database name must not change.
508 // That's why this function exists separately from mozilla::HashString(), even
509 // though it is (at the time of writing) equivalent. See bug 780408 and bug
510 // 940315 for details.
511 uint32_t HashName(const nsAString& aName) {
512 struct Helper {
513 static uint32_t RotateBitsLeft32(uint32_t aValue, uint8_t aBits) {
514 MOZ_ASSERT(aBits < 32);
515 return (aValue << aBits) | (aValue >> (32 - aBits));
519 static const uint32_t kGoldenRatioU32 = 0x9e3779b9u;
521 return std::accumulate(aName.BeginReading(), aName.EndReading(), uint32_t(0),
522 [](uint32_t hash, char16_t ch) {
523 return kGoldenRatioU32 *
524 (Helper::RotateBitsLeft32(hash, 5) ^ ch);
528 nsresult ClampResultCode(nsresult aResultCode) {
529 if (NS_SUCCEEDED(aResultCode) ||
530 NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) {
531 return aResultCode;
534 switch (aResultCode) {
535 case NS_ERROR_FILE_NO_DEVICE_SPACE:
536 return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
537 case NS_ERROR_STORAGE_CONSTRAINT:
538 return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
539 default:
540 #ifdef DEBUG
541 nsPrintfCString message("Converting non-IndexedDB error code (0x%" PRIX32
542 ") to "
543 "NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
544 static_cast<uint32_t>(aResultCode));
545 NS_WARNING(message.get());
546 #else
548 #endif
551 IDB_REPORT_INTERNAL_ERR();
552 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
555 Result<nsCOMPtr<nsIFileURL>, nsresult> GetDatabaseFileURL(
556 nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
557 const Maybe<CipherKey>& aMaybeKey) {
558 MOZ_ASSERT(aDirectoryLockId >= -1);
560 QM_TRY_INSPECT(
561 const auto& protocolHandler,
562 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIProtocolHandler>,
563 MOZ_SELECT_OVERLOAD(do_GetService),
564 NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"));
566 QM_TRY_INSPECT(const auto& fileHandler,
567 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFileProtocolHandler>,
568 MOZ_SELECT_OVERLOAD(do_QueryInterface),
569 protocolHandler));
571 QM_TRY_INSPECT(const auto& mutator, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
572 nsCOMPtr<nsIURIMutator>, fileHandler,
573 NewFileURIMutator, &aDatabaseFile));
575 // aDirectoryLockId should only be -1 when we are called
576 // - from DatabaseFileManager::InitDirectory when the temporary storage
577 // hasn't been initialized yet. At that time, the in-memory objects (e.g.
578 // OriginInfo) are only being created so it doesn't make sense to tunnel
579 // quota information to QuotaVFS to get corresponding QuotaObject instances
580 // for SQLite files.
581 // - from DeleteDatabaseOp::LoadPreviousVersion, since this might require
582 // temporarily exceeding the quota limit before the database can be
583 // deleted.
584 const nsCString directoryLockIdClause =
585 "&directoryLockId="_ns + IntToCString(aDirectoryLockId);
587 const auto keyClause = [&aMaybeKey] {
588 nsAutoCString keyClause;
589 if (aMaybeKey) {
590 keyClause.AssignLiteral("&key=");
591 for (uint8_t byte : IndexedDBCipherStrategy::SerializeKey(*aMaybeKey)) {
592 keyClause.AppendPrintf("%02x", byte);
595 return keyClause;
596 }();
598 QM_TRY_UNWRAP(auto result, ([&mutator, &directoryLockIdClause, &keyClause] {
599 nsCOMPtr<nsIFileURL> result;
600 nsresult rv = NS_MutateURI(mutator)
601 .SetQuery("cache=private"_ns +
602 directoryLockIdClause + keyClause)
603 .Finalize(result);
604 return NS_SUCCEEDED(rv)
605 ? Result<nsCOMPtr<nsIFileURL>, nsresult>{result}
606 : Err(rv);
607 }()));
609 return result;
612 nsresult SetDefaultPragmas(mozIStorageConnection& aConnection) {
613 MOZ_ASSERT(!NS_IsMainThread());
615 static constexpr auto kBuiltInPragmas =
616 // We use foreign keys in DEBUG builds only because there is a performance
617 // cost to using them.
618 "PRAGMA foreign_keys = "
619 #ifdef DEBUG
620 "ON"
621 #else
622 "OFF"
623 #endif
626 // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
627 // instead it fires only the insert trigger. This confuses the update
628 // refcount function. This behavior changes with enabled recursive
629 // triggers, so the statement fires the delete trigger first and then the
630 // insert trigger.
631 "PRAGMA recursive_triggers = ON;"
633 // We aggressively truncate the database file when idle so don't bother
634 // overwriting the WAL with 0 during active periods.
635 "PRAGMA secure_delete = OFF;"_ns;
637 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(kBuiltInPragmas)));
639 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsAutoCString{
640 "PRAGMA synchronous = "_ns +
641 (IndexedDatabaseManager::FullSynchronous() ? "FULL"_ns : "NORMAL"_ns) +
642 ";"_ns})));
644 #ifndef IDB_MOBILE
645 if (kSQLiteGrowthIncrement) {
646 // This is just an optimization so ignore the failure if the disk is
647 // currently too full.
648 QM_TRY(QM_OR_ELSE_WARN_IF(
649 // Expression.
650 MOZ_TO_RESULT(
651 aConnection.SetGrowthIncrement(kSQLiteGrowthIncrement, ""_ns)),
652 // Predicate.
653 IsSpecificError<NS_ERROR_FILE_TOO_BIG>,
654 // Fallback.
655 ErrToDefaultOk<>));
657 #endif // IDB_MOBILE
659 return NS_OK;
662 nsresult SetJournalMode(mozIStorageConnection& aConnection) {
663 MOZ_ASSERT(!NS_IsMainThread());
665 // Try enabling WAL mode. This can fail in various circumstances so we have to
666 // check the results here.
667 constexpr auto journalModeQueryStart = "PRAGMA journal_mode = "_ns;
668 constexpr auto journalModeWAL = "wal"_ns;
670 QM_TRY_INSPECT(const auto& stmt,
671 CreateAndExecuteSingleStepStatement(
672 aConnection, journalModeQueryStart + journalModeWAL));
674 QM_TRY_INSPECT(
675 const auto& journalMode,
676 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt, GetUTF8String, 0));
678 if (journalMode.Equals(journalModeWAL)) {
679 // WAL mode successfully enabled. Maybe set limits on its size here.
680 if (kMaxWALPages >= 0) {
681 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
682 "PRAGMA wal_autocheckpoint = "_ns + IntToCString(kMaxWALPages))));
684 } else {
685 NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
686 #ifdef IDB_MOBILE
687 QM_TRY(MOZ_TO_RESULT(
688 aConnection.ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns)));
689 #endif
692 return NS_OK;
695 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> OpenDatabase(
696 mozIStorageService& aStorageService, nsIFileURL& aFileURL,
697 const uint32_t aTelemetryId = 0) {
698 const nsAutoCString telemetryFilename =
699 aTelemetryId ? "indexedDB-"_ns + IntToCString(aTelemetryId) +
700 NS_ConvertUTF16toUTF8(kSQLiteSuffix)
701 : nsAutoCString();
703 QM_TRY_UNWRAP(auto connection,
704 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
705 nsCOMPtr<mozIStorageConnection>, aStorageService,
706 OpenDatabaseWithFileURL, &aFileURL, telemetryFilename,
707 mozIStorageService::CONNECTION_INTERRUPTIBLE));
709 return WrapMovingNotNull(std::move(connection));
712 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
713 OpenDatabaseAndHandleBusy(mozIStorageService& aStorageService,
714 nsIFileURL& aFileURL,
715 const uint32_t aTelemetryId = 0) {
716 MOZ_ASSERT(!NS_IsMainThread());
717 MOZ_ASSERT(!IsOnBackgroundThread());
719 using ConnectionType = Maybe<MovingNotNull<nsCOMPtr<mozIStorageConnection>>>;
721 QM_TRY_UNWRAP(auto connection,
722 QM_OR_ELSE_WARN_IF(
723 // Expression
724 OpenDatabase(aStorageService, aFileURL, aTelemetryId)
725 .map([](auto connection) -> ConnectionType {
726 return Some(std::move(connection));
728 // Predicate.
729 IsSpecificError<NS_ERROR_STORAGE_BUSY>,
730 // Fallback.
731 ErrToDefaultOk<ConnectionType>));
733 if (connection.isNothing()) {
734 #ifdef DEBUG
736 nsCString path;
737 MOZ_ALWAYS_SUCCEEDS(aFileURL.GetFileName(path));
739 nsPrintfCString message(
740 "Received NS_ERROR_STORAGE_BUSY when attempting to open database "
741 "'%s', retrying for up to 10 seconds",
742 path.get());
743 NS_WARNING(message.get());
745 #endif
747 // Another thread must be checkpointing the WAL. Wait up to 10 seconds for
748 // that to complete.
749 const TimeStamp start = TimeStamp::NowLoRes();
751 do {
752 PR_Sleep(PR_MillisecondsToInterval(100));
754 QM_TRY_UNWRAP(connection,
755 QM_OR_ELSE_WARN_IF(
756 // Expression.
757 OpenDatabase(aStorageService, aFileURL, aTelemetryId)
758 .map([](auto connection) -> ConnectionType {
759 return Some(std::move(connection));
761 // Predicate.
762 ([&start](nsresult aValue) {
763 return aValue == NS_ERROR_STORAGE_BUSY &&
764 TimeStamp::NowLoRes() - start <=
765 TimeDuration::FromSeconds(10);
767 // Fallback.
768 ErrToDefaultOk<ConnectionType>));
769 } while (connection.isNothing());
772 return connection.extract();
775 // Returns true if a given nsIFile exists and is a directory. Returns false if
776 // it doesn't exist. Returns an error if it exists, but is not a directory, or
777 // any other error occurs.
778 Result<bool, nsresult> ExistsAsDirectory(nsIFile& aDirectory) {
779 QM_TRY_INSPECT(const bool& exists,
780 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
782 if (exists) {
783 QM_TRY_INSPECT(const bool& isDirectory,
784 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
786 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE));
789 return exists;
792 constexpr nsresult mapNoDeviceSpaceError(nsresult aRv) {
793 if (aRv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
794 // mozstorage translates SQLITE_FULL to
795 // NS_ERROR_FILE_NO_DEVICE_SPACE, which we know better as
796 // NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
797 return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
799 return aRv;
802 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
803 CreateStorageConnection(nsIFile& aDBFile, nsIFile& aFMDirectory,
804 const nsAString& aName, const nsACString& aOrigin,
805 const int64_t aDirectoryLockId,
806 const uint32_t aTelemetryId,
807 const Maybe<CipherKey>& aMaybeKey) {
808 AssertIsOnIOThread();
809 MOZ_ASSERT(aDirectoryLockId >= -1);
811 AUTO_PROFILER_LABEL("CreateStorageConnection", DOM);
813 QM_TRY_INSPECT(const auto& dbFileUrl,
814 GetDatabaseFileURL(aDBFile, aDirectoryLockId, aMaybeKey));
816 QM_TRY_INSPECT(const auto& storageService,
817 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
818 MOZ_SELECT_OVERLOAD(do_GetService),
819 MOZ_STORAGE_SERVICE_CONTRACTID));
821 QM_TRY_UNWRAP(
822 auto connection,
823 QM_OR_ELSE_WARN_IF(
824 // Expression.
825 OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId)
826 .map([](auto connection) -> nsCOMPtr<mozIStorageConnection> {
827 return std::move(connection).unwrapBasePtr();
829 // Predicate.
830 ([&aName](nsresult aValue) {
831 // If we're just opening the database during origin initialization,
832 // then we don't want to erase any files. The failure here will fail
833 // origin initialization too.
834 return IsDatabaseCorruptionError(aValue) && !aName.IsVoid();
836 // Fallback.
837 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
839 if (!connection) {
840 // XXX Shouldn't we also update quota usage?
842 // Nuke the database file.
843 QM_TRY(MOZ_TO_RESULT(aDBFile.Remove(false)));
844 QM_TRY_INSPECT(const bool& existsAsDirectory,
845 ExistsAsDirectory(aFMDirectory));
847 if (existsAsDirectory) {
848 QM_TRY(MOZ_TO_RESULT(aFMDirectory.Remove(true)));
851 QM_TRY_UNWRAP(connection, OpenDatabaseAndHandleBusy(
852 *storageService, *dbFileUrl, aTelemetryId));
855 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection)));
856 QM_TRY(MOZ_TO_RESULT(connection->EnableModule("filesystem"_ns)));
858 // Check to make sure that the database schema is correct.
859 QM_TRY_INSPECT(const int32_t& schemaVersion,
860 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion));
862 // Unknown schema will fail origin initialization too.
863 QM_TRY(OkIf(schemaVersion || !aName.IsVoid()),
864 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
865 IDB_WARNING("Unable to open IndexedDB database, schema is not set!");
868 QM_TRY(
869 OkIf(schemaVersion <= kSQLiteSchemaVersion),
870 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) {
871 IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
874 bool journalModeSet = false;
876 if (schemaVersion != kSQLiteSchemaVersion) {
877 const bool newDatabase = !schemaVersion;
879 if (newDatabase) {
880 // Set the page size first.
881 const auto sqlitePageSizeOverride =
882 aMaybeKey ? 8192 : kSQLitePageSizeOverride;
883 if (sqlitePageSizeOverride) {
884 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
885 "PRAGMA page_size = %" PRIu32 ";", sqlitePageSizeOverride))));
888 // We have to set the auto_vacuum mode before opening a transaction.
889 QM_TRY((MOZ_TO_RESULT_INVOKE_MEMBER(
890 connection, ExecuteSimpleSQL,
891 #ifdef IDB_MOBILE
892 // Turn on full auto_vacuum mode to reclaim disk space on
893 // mobile devices (at the cost of some COMMIT speed).
894 "PRAGMA auto_vacuum = FULL;"_ns
895 #else
896 // Turn on incremental auto_vacuum mode on desktop builds.
897 "PRAGMA auto_vacuum = INCREMENTAL;"_ns
898 #endif
900 .mapErr(mapNoDeviceSpaceError)));
902 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
904 journalModeSet = true;
905 } else {
906 #ifdef DEBUG
907 // Disable foreign key support while upgrading. This has to be done before
908 // starting a transaction.
909 MOZ_ALWAYS_SUCCEEDS(
910 connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
911 #endif
914 bool vacuumNeeded = false;
916 mozStorageTransaction transaction(
917 connection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
919 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
921 if (newDatabase) {
922 QM_TRY(MOZ_TO_RESULT(CreateTables(*connection)));
924 #ifdef DEBUG
926 QM_TRY_INSPECT(
927 const int32_t& schemaVersion,
928 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion),
929 QM_ASSERT_UNREACHABLE);
930 MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
932 #endif
934 // The parameter names are not used, parameters are bound by index only
935 // locally in the same function.
936 QM_TRY_INSPECT(
937 const auto& stmt,
938 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
939 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
940 "INSERT INTO database (name, origin) "
941 "VALUES (:name, :origin)"_ns));
943 QM_TRY(MOZ_TO_RESULT(stmt->BindStringByIndex(0, aName)));
944 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByIndex(1, aOrigin)));
945 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
946 } else {
947 QM_TRY_UNWRAP(vacuumNeeded, MaybeUpgradeSchema(*connection, schemaVersion,
948 aFMDirectory, aOrigin));
951 QM_TRY(MOZ_TO_RESULT_INVOKE_MEMBER(transaction, Commit)
952 .mapErr(mapNoDeviceSpaceError));
954 #ifdef DEBUG
955 if (!newDatabase) {
956 // Re-enable foreign key support after doing a foreign key check.
957 QM_TRY_INSPECT(const bool& foreignKeyError,
958 CreateAndExecuteSingleStepStatement<
959 SingleStepResult::ReturnNullIfNoResult>(
960 *connection, "PRAGMA foreign_key_check;"_ns),
961 QM_ASSERT_UNREACHABLE);
963 MOZ_ASSERT(!foreignKeyError, "Database has inconsisistent foreign keys!");
965 MOZ_ALWAYS_SUCCEEDS(
966 connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns));
968 #endif
970 if (kSQLitePageSizeOverride && !newDatabase) {
971 QM_TRY_INSPECT(const auto& stmt,
972 CreateAndExecuteSingleStepStatement(
973 *connection, "PRAGMA page_size;"_ns));
975 QM_TRY_INSPECT(const int32_t& pageSize,
976 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
977 MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
979 if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
980 // We must not be in WAL journal mode to change the page size.
981 QM_TRY(MOZ_TO_RESULT(
982 connection->ExecuteSimpleSQL("PRAGMA journal_mode = DELETE;"_ns)));
984 QM_TRY_INSPECT(const auto& stmt,
985 CreateAndExecuteSingleStepStatement(
986 *connection, "PRAGMA journal_mode;"_ns));
988 QM_TRY_INSPECT(const auto& journalMode,
989 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt,
990 GetUTF8String, 0));
992 if (journalMode.EqualsLiteral("delete")) {
993 // Successfully set to rollback journal mode so changing the page size
994 // is possible with a VACUUM.
995 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
996 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
998 // We will need to VACUUM in order to change the page size.
999 vacuumNeeded = true;
1000 } else {
1001 NS_WARNING(
1002 "Failed to set journal_mode for database, unable to "
1003 "change the page size!");
1008 if (vacuumNeeded) {
1009 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("VACUUM;"_ns)));
1012 if (newDatabase || vacuumNeeded) {
1013 if (journalModeSet) {
1014 // Make sure we checkpoint to get an accurate file size.
1015 QM_TRY(MOZ_TO_RESULT(
1016 connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(FULL);"_ns)));
1019 QM_TRY_INSPECT(const int64_t& fileSize,
1020 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, GetFileSize));
1021 MOZ_ASSERT(fileSize > 0);
1023 PRTime vacuumTime = PR_Now();
1024 MOZ_ASSERT(vacuumTime);
1026 // The parameter names are not used, parameters are bound by index only
1027 // locally in the same function.
1028 QM_TRY_INSPECT(
1029 const auto& vacuumTimeStmt,
1030 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<mozIStorageStatement>,
1031 connection, CreateStatement,
1032 "UPDATE database "
1033 "SET last_vacuum_time = :time"
1034 ", last_vacuum_size = :size;"_ns));
1036 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(0, vacuumTime)));
1037 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(1, fileSize)));
1038 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->Execute()));
1042 if (!journalModeSet) {
1043 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
1046 return WrapMovingNotNullUnchecked(std::move(connection));
1049 nsCOMPtr<nsIFile> GetFileForPath(const nsAString& aPath) {
1050 MOZ_ASSERT(!aPath.IsEmpty());
1052 QM_TRY_RETURN(QM_NewLocalFile(aPath), nullptr);
1055 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
1056 GetStorageConnection(nsIFile& aDatabaseFile, const int64_t aDirectoryLockId,
1057 const uint32_t aTelemetryId,
1058 const Maybe<CipherKey>& aMaybeKey) {
1059 MOZ_ASSERT(!NS_IsMainThread());
1060 MOZ_ASSERT(!IsOnBackgroundThread());
1061 MOZ_ASSERT(aDirectoryLockId >= 0);
1063 AUTO_PROFILER_LABEL("GetStorageConnection", DOM);
1065 QM_TRY_INSPECT(const bool& exists,
1066 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, Exists));
1068 QM_TRY(OkIf(exists), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
1069 IDB_REPORT_INTERNAL_ERR_LAMBDA);
1071 QM_TRY_INSPECT(
1072 const auto& dbFileUrl,
1073 GetDatabaseFileURL(aDatabaseFile, aDirectoryLockId, aMaybeKey));
1075 QM_TRY_INSPECT(const auto& storageService,
1076 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
1077 MOZ_SELECT_OVERLOAD(do_GetService),
1078 MOZ_STORAGE_SERVICE_CONTRACTID));
1080 QM_TRY_UNWRAP(
1081 nsCOMPtr<mozIStorageConnection> connection,
1082 OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId));
1084 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection)));
1086 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection)));
1088 return WrapMovingNotNullUnchecked(std::move(connection));
1091 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult>
1092 GetStorageConnection(const nsAString& aDatabaseFilePath,
1093 const int64_t aDirectoryLockId,
1094 const uint32_t aTelemetryId,
1095 const Maybe<CipherKey>& aMaybeKey) {
1096 MOZ_ASSERT(!NS_IsMainThread());
1097 MOZ_ASSERT(!IsOnBackgroundThread());
1098 MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
1099 MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, kSQLiteSuffix));
1100 MOZ_ASSERT(aDirectoryLockId >= 0);
1102 nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
1104 QM_TRY(OkIf(dbFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
1105 IDB_REPORT_INTERNAL_ERR_LAMBDA);
1107 return GetStorageConnection(*dbFile, aDirectoryLockId, aTelemetryId,
1108 aMaybeKey);
1111 /*******************************************************************************
1112 * ConnectionPool declarations
1113 ******************************************************************************/
1115 class DatabaseConnection final : public CachingDatabaseConnection {
1116 friend class ConnectionPool;
1118 enum class CheckpointMode { Full, Restart, Truncate };
1120 public:
1121 class AutoSavepoint;
1122 class UpdateRefcountFunction;
1124 private:
1125 InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileManager>>> mFileManager;
1126 RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
1127 RefPtr<QuotaObject> mQuotaObject;
1128 RefPtr<QuotaObject> mJournalQuotaObject;
1129 bool mInReadTransaction;
1130 bool mInWriteTransaction;
1132 #ifdef DEBUG
1133 uint32_t mDEBUGSavepointCount;
1134 #endif
1136 public:
1137 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection)
1139 UpdateRefcountFunction* GetUpdateRefcountFunction() const {
1140 AssertIsOnConnectionThread();
1142 return mUpdateRefcountFunction;
1145 nsresult BeginWriteTransaction();
1147 nsresult CommitWriteTransaction();
1149 void RollbackWriteTransaction();
1151 void FinishWriteTransaction();
1153 nsresult StartSavepoint();
1155 nsresult ReleaseSavepoint();
1157 nsresult RollbackSavepoint();
1159 nsresult Checkpoint() {
1160 AssertIsOnConnectionThread();
1162 return CheckpointInternal(CheckpointMode::Full);
1165 void DoIdleProcessing(bool aNeedsCheckpoint,
1166 const Atomic<bool>& aInterrupted);
1168 void Close();
1170 nsresult DisableQuotaChecks();
1172 void EnableQuotaChecks();
1174 private:
1175 DatabaseConnection(
1176 MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection,
1177 MovingNotNull<SafeRefPtr<DatabaseFileManager>> aFileManager);
1179 ~DatabaseConnection();
1181 nsresult Init();
1183 nsresult CheckpointInternal(CheckpointMode aMode);
1185 Result<uint32_t, nsresult> GetFreelistCount(
1186 CachedStatement& aCachedStatement);
1189 * On success, returns whether some pages were freed.
1191 Result<bool, nsresult> ReclaimFreePagesWhileIdle(
1192 CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement,
1193 uint32_t aFreelistCount, bool aNeedsCheckpoint,
1194 const Atomic<bool>& aInterrupted);
1196 Result<int64_t, nsresult> GetFileSize(const nsAString& aPath);
1199 class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final {
1200 DatabaseConnection* mConnection;
1201 #ifdef DEBUG
1202 const TransactionBase* mDEBUGTransaction;
1203 #endif
1205 public:
1206 AutoSavepoint();
1207 ~AutoSavepoint();
1209 nsresult Start(const TransactionBase& aTransaction);
1211 nsresult Commit();
1214 class DatabaseConnection::UpdateRefcountFunction final
1215 : public mozIStorageFunction {
1216 class FileInfoEntry;
1218 enum class UpdateType { Increment, Decrement };
1220 DatabaseConnection* const mConnection;
1221 DatabaseFileManager& mFileManager;
1222 nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
1223 nsTHashMap<nsUint64HashKey, NotNull<FileInfoEntry*>> mSavepointEntriesIndex;
1225 nsTArray<int64_t> mJournalsToCreateBeforeCommit;
1226 nsTArray<int64_t> mJournalsToRemoveAfterCommit;
1227 nsTArray<int64_t> mJournalsToRemoveAfterAbort;
1229 bool mInSavepoint;
1231 public:
1232 NS_DECL_ISUPPORTS_ONEVENTTARGET
1233 NS_DECL_MOZISTORAGEFUNCTION
1235 UpdateRefcountFunction(DatabaseConnection* aConnection,
1236 DatabaseFileManager& aFileManager);
1238 nsresult WillCommit();
1240 void DidCommit();
1242 void DidAbort();
1244 void StartSavepoint();
1246 void ReleaseSavepoint();
1248 void RollbackSavepoint();
1250 void Reset();
1252 private:
1253 ~UpdateRefcountFunction() = default;
1255 nsresult ProcessValue(mozIStorageValueArray* aValues, int32_t aIndex,
1256 UpdateType aUpdateType);
1258 nsresult CreateJournals();
1260 nsresult RemoveJournals(const nsTArray<int64_t>& aJournals);
1263 class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final {
1264 SafeRefPtr<DatabaseFileInfo> mFileInfo;
1265 int32_t mDelta;
1266 int32_t mSavepointDelta;
1268 public:
1269 explicit FileInfoEntry(SafeRefPtr<DatabaseFileInfo> aFileInfo)
1270 : mFileInfo(std::move(aFileInfo)), mDelta(0), mSavepointDelta(0) {
1271 MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
1274 void IncDeltas(bool aUpdateSavepointDelta) {
1275 ++mDelta;
1276 if (aUpdateSavepointDelta) {
1277 ++mSavepointDelta;
1280 void DecDeltas(bool aUpdateSavepointDelta) {
1281 --mDelta;
1282 if (aUpdateSavepointDelta) {
1283 --mSavepointDelta;
1286 void DecBySavepointDelta() { mDelta -= mSavepointDelta; }
1287 SafeRefPtr<DatabaseFileInfo> ReleaseFileInfo() {
1288 return std::move(mFileInfo);
1290 void MaybeUpdateDBRefs() {
1291 if (mDelta) {
1292 mFileInfo->UpdateDBRefs(mDelta);
1296 int32_t Delta() const { return mDelta; }
1297 int32_t SavepointDelta() const { return mSavepointDelta; }
1299 ~FileInfoEntry() {
1300 MOZ_COUNT_DTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
1304 class ConnectionPool final {
1305 public:
1306 class FinishCallback;
1308 private:
1309 class ConnectionRunnable;
1310 class CloseConnectionRunnable;
1311 struct DatabaseInfo;
1312 struct DatabaseCompleteCallback;
1313 class FinishCallbackWrapper;
1314 class IdleConnectionRunnable;
1316 class TransactionInfo;
1317 struct TransactionInfoPair;
1319 struct IdleResource {
1320 TimeStamp mIdleTime;
1322 IdleResource(const IdleResource& aOther) = delete;
1323 IdleResource(IdleResource&& aOther) noexcept
1324 : IdleResource(aOther.mIdleTime) {}
1325 IdleResource& operator=(const IdleResource& aOther) = delete;
1326 IdleResource& operator=(IdleResource&& aOther) = delete;
1328 protected:
1329 explicit IdleResource(const TimeStamp& aIdleTime);
1331 ~IdleResource();
1334 struct IdleDatabaseInfo final : public IdleResource {
1335 InitializedOnce<const NotNull<DatabaseInfo*>> mDatabaseInfo;
1337 public:
1338 explicit IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo);
1340 IdleDatabaseInfo(const IdleDatabaseInfo& aOther) = delete;
1341 IdleDatabaseInfo(IdleDatabaseInfo&& aOther) noexcept
1342 : IdleResource(std::move(aOther)),
1343 mDatabaseInfo{std::move(aOther.mDatabaseInfo)} {
1344 MOZ_ASSERT(mDatabaseInfo);
1346 MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
1348 IdleDatabaseInfo& operator=(const IdleDatabaseInfo& aOther) = delete;
1349 IdleDatabaseInfo& operator=(IdleDatabaseInfo&& aOther) = delete;
1351 ~IdleDatabaseInfo();
1353 bool operator==(const IdleDatabaseInfo& aOther) const {
1354 return *mDatabaseInfo == *aOther.mDatabaseInfo;
1357 bool operator==(const DatabaseInfo* aDatabaseInfo) const {
1358 return *mDatabaseInfo == aDatabaseInfo;
1361 bool operator<(const IdleDatabaseInfo& aOther) const {
1362 return mIdleTime < aOther.mIdleTime;
1366 struct PerformingIdleMaintenanceDatabaseInfo {
1367 const NotNull<DatabaseInfo*> mDatabaseInfo;
1368 RefPtr<IdleConnectionRunnable> mIdleConnectionRunnable;
1370 PerformingIdleMaintenanceDatabaseInfo(
1371 DatabaseInfo& aDatabaseInfo,
1372 RefPtr<IdleConnectionRunnable> aIdleConnectionRunnable);
1374 PerformingIdleMaintenanceDatabaseInfo(
1375 const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete;
1376 PerformingIdleMaintenanceDatabaseInfo(
1377 PerformingIdleMaintenanceDatabaseInfo&& aOther) noexcept
1378 : mDatabaseInfo{aOther.mDatabaseInfo},
1379 mIdleConnectionRunnable{std::move(aOther.mIdleConnectionRunnable)} {
1380 MOZ_COUNT_CTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo);
1382 PerformingIdleMaintenanceDatabaseInfo& operator=(
1383 const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete;
1384 PerformingIdleMaintenanceDatabaseInfo& operator=(
1385 PerformingIdleMaintenanceDatabaseInfo&& aOther) = delete;
1387 ~PerformingIdleMaintenanceDatabaseInfo();
1389 bool operator==(const DatabaseInfo* aDatabaseInfo) const {
1390 return mDatabaseInfo == aDatabaseInfo;
1394 // This mutex guards mDatabases, see below.
1395 Mutex mDatabasesMutex MOZ_UNANNOTATED;
1397 nsCOMPtr<nsIThreadPool> mIOTarget;
1398 nsTArray<IdleDatabaseInfo> mIdleDatabases;
1399 nsTArray<PerformingIdleMaintenanceDatabaseInfo>
1400 mDatabasesPerformingIdleMaintenance;
1401 nsCOMPtr<nsITimer> mIdleTimer;
1402 TimeStamp mTargetIdleTime;
1404 // Only modifed on the owning thread, but read on multiple threads. Therefore
1405 // all modifications and all reads off the owning thread must be protected by
1406 // mDatabasesMutex.
1407 nsClassHashtable<nsCStringHashKey, DatabaseInfo> mDatabases;
1409 nsClassHashtable<nsUint64HashKey, TransactionInfo> mTransactions;
1410 nsTArray<NotNull<TransactionInfo*>> mQueuedTransactions;
1412 nsTArray<UniquePtr<DatabaseCompleteCallback>> mCompleteCallbacks;
1414 uint64_t mNextTransactionId;
1415 FlippedOnce<false> mShutdownRequested;
1416 FlippedOnce<false> mShutdownComplete;
1418 public:
1419 ConnectionPool();
1421 void AssertIsOnOwningThread() const {
1422 NS_ASSERT_OWNINGTHREAD(ConnectionPool);
1425 Result<RefPtr<DatabaseConnection>, nsresult> GetOrCreateConnection(
1426 const Database& aDatabase);
1428 uint64_t Start(const nsID& aBackgroundChildLoggingId,
1429 const nsACString& aDatabaseId, int64_t aLoggingSerialNumber,
1430 const nsTArray<nsString>& aObjectStoreNames,
1431 bool aIsWriteTransaction,
1432 TransactionDatabaseOperationBase* aTransactionOp);
1434 void Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable);
1436 void Finish(uint64_t aTransactionId, FinishCallback* aCallback);
1438 void CloseDatabaseWhenIdle(const nsACString& aDatabaseId) {
1439 Unused << CloseDatabaseWhenIdleInternal(aDatabaseId);
1442 void WaitForDatabaseToComplete(const nsCString& aDatabaseId,
1443 nsIRunnable* aCallback);
1445 void Shutdown();
1447 NS_INLINE_DECL_REFCOUNTING(ConnectionPool)
1449 private:
1450 ~ConnectionPool();
1452 static void IdleTimerCallback(nsITimer* aTimer, void* aClosure);
1454 static uint32_t SerialNumber() { return ++sSerialNumber; }
1456 static uint32_t sSerialNumber;
1458 void Cleanup();
1460 void AdjustIdleTimer();
1462 void CancelIdleTimer();
1464 void CloseIdleDatabases();
1466 bool ScheduleTransaction(TransactionInfo& aTransactionInfo,
1467 bool aFromQueuedTransactions);
1469 void NoteFinishedTransaction(uint64_t aTransactionId);
1471 void ScheduleQueuedTransactions();
1473 void NoteIdleDatabase(DatabaseInfo& aDatabaseInfo);
1475 void NoteClosedDatabase(DatabaseInfo& aDatabaseInfo);
1477 bool MaybeFireCallback(DatabaseCompleteCallback* aCallback);
1479 void PerformIdleDatabaseMaintenance(DatabaseInfo& aDatabaseInfo);
1481 void CloseDatabase(DatabaseInfo& aDatabaseInfo) const;
1483 bool CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId);
1486 class ConnectionPool::ConnectionRunnable : public Runnable {
1487 protected:
1488 DatabaseInfo& mDatabaseInfo;
1489 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
1491 explicit ConnectionRunnable(DatabaseInfo& aDatabaseInfo);
1493 ~ConnectionRunnable() override = default;
1496 class ConnectionPool::IdleConnectionRunnable final : public ConnectionRunnable {
1497 const bool mNeedsCheckpoint;
1498 Atomic<bool> mInterrupted;
1500 public:
1501 IdleConnectionRunnable(DatabaseInfo& aDatabaseInfo, bool aNeedsCheckpoint)
1502 : ConnectionRunnable(aDatabaseInfo), mNeedsCheckpoint(aNeedsCheckpoint) {}
1504 NS_INLINE_DECL_REFCOUNTING_INHERITED(IdleConnectionRunnable,
1505 ConnectionRunnable)
1507 void Interrupt() { mInterrupted = true; }
1509 private:
1510 ~IdleConnectionRunnable() override = default;
1512 NS_DECL_NSIRUNNABLE
1515 class ConnectionPool::CloseConnectionRunnable final
1516 : public ConnectionRunnable {
1517 public:
1518 explicit CloseConnectionRunnable(DatabaseInfo& aDatabaseInfo)
1519 : ConnectionRunnable(aDatabaseInfo) {}
1521 NS_INLINE_DECL_REFCOUNTING_INHERITED(CloseConnectionRunnable,
1522 ConnectionRunnable)
1524 private:
1525 ~CloseConnectionRunnable() override = default;
1527 NS_DECL_NSIRUNNABLE
1530 struct ConnectionPool::DatabaseInfo final {
1531 friend class mozilla::DefaultDelete<DatabaseInfo>;
1533 RefPtr<ConnectionPool> mConnectionPool;
1534 const nsCString mDatabaseId;
1535 RefPtr<DatabaseConnection> mConnection;
1536 nsClassHashtable<nsStringHashKey, TransactionInfoPair> mBlockingTransactions;
1537 nsTArray<NotNull<TransactionInfo*>> mTransactionsScheduledDuringClose;
1538 nsTArray<NotNull<TransactionInfo*>> mScheduledWriteTransactions;
1539 Maybe<TransactionInfo&> mRunningWriteTransaction;
1540 RefPtr<TaskQueue> mEventTarget;
1541 uint32_t mReadTransactionCount;
1542 uint32_t mWriteTransactionCount;
1543 bool mNeedsCheckpoint;
1544 bool mIdle;
1545 FlippedOnce<false> mCloseOnIdle;
1546 bool mClosing;
1548 #ifdef DEBUG
1549 nsISerialEventTarget* mDEBUGConnectionEventTarget;
1550 #endif
1552 DatabaseInfo(ConnectionPool* aConnectionPool, const nsACString& aDatabaseId);
1554 void AssertIsOnConnectionThread() const {
1555 MOZ_ASSERT(mDEBUGConnectionEventTarget);
1556 MOZ_ASSERT(GetCurrentSerialEventTarget() == mDEBUGConnectionEventTarget);
1559 uint64_t TotalTransactionCount() const {
1560 return mReadTransactionCount + mWriteTransactionCount;
1563 private:
1564 ~DatabaseInfo();
1566 DatabaseInfo(const DatabaseInfo&) = delete;
1567 DatabaseInfo& operator=(const DatabaseInfo&) = delete;
1570 struct ConnectionPool::DatabaseCompleteCallback final {
1571 friend class DefaultDelete<DatabaseCompleteCallback>;
1573 nsCString mDatabaseId;
1574 nsCOMPtr<nsIRunnable> mCallback;
1576 DatabaseCompleteCallback(const nsCString& aDatabaseIds,
1577 nsIRunnable* aCallback);
1579 private:
1580 ~DatabaseCompleteCallback();
1583 class NS_NO_VTABLE ConnectionPool::FinishCallback : public nsIRunnable {
1584 public:
1585 // Called on the owning thread before any additional transactions are
1586 // unblocked.
1587 virtual void TransactionFinishedBeforeUnblock() = 0;
1589 // Called on the owning thread after additional transactions may have been
1590 // unblocked.
1591 virtual void TransactionFinishedAfterUnblock() = 0;
1593 protected:
1594 FinishCallback() = default;
1596 virtual ~FinishCallback() = default;
1599 class ConnectionPool::FinishCallbackWrapper final : public Runnable {
1600 RefPtr<ConnectionPool> mConnectionPool;
1601 RefPtr<FinishCallback> mCallback;
1602 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
1603 uint64_t mTransactionId;
1604 bool mHasRunOnce;
1606 public:
1607 FinishCallbackWrapper(ConnectionPool* aConnectionPool,
1608 uint64_t aTransactionId, FinishCallback* aCallback);
1610 NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishCallbackWrapper, Runnable)
1612 private:
1613 ~FinishCallbackWrapper() override;
1615 NS_DECL_NSIRUNNABLE
1618 class ConnectionPool::TransactionInfo final {
1619 friend class mozilla::DefaultDelete<TransactionInfo>;
1621 nsTHashSet<TransactionInfo*> mBlocking;
1622 nsTArray<NotNull<TransactionInfo*>> mBlockingOrdered;
1624 public:
1625 DatabaseInfo& mDatabaseInfo;
1626 const nsID mBackgroundChildLoggingId;
1627 const nsCString mDatabaseId;
1628 const uint64_t mTransactionId;
1629 const int64_t mLoggingSerialNumber;
1630 const nsTArray<nsString> mObjectStoreNames;
1631 nsTHashSet<TransactionInfo*> mBlockedOn;
1632 nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
1633 const bool mIsWriteTransaction;
1634 bool mRunning;
1636 #ifdef DEBUG
1637 FlippedOnce<false> mFinished;
1638 #endif
1640 TransactionInfo(DatabaseInfo& aDatabaseInfo,
1641 const nsID& aBackgroundChildLoggingId,
1642 const nsACString& aDatabaseId, uint64_t aTransactionId,
1643 int64_t aLoggingSerialNumber,
1644 const nsTArray<nsString>& aObjectStoreNames,
1645 bool aIsWriteTransaction,
1646 TransactionDatabaseOperationBase* aTransactionOp);
1648 void AddBlockingTransaction(TransactionInfo& aTransactionInfo);
1650 void RemoveBlockingTransactions();
1652 private:
1653 ~TransactionInfo();
1655 void MaybeUnblock(TransactionInfo& aTransactionInfo);
1658 struct ConnectionPool::TransactionInfoPair final {
1659 // Multiple reading transactions can block future writes.
1660 nsTArray<NotNull<TransactionInfo*>> mLastBlockingWrites;
1661 // But only a single writing transaction can block future reads.
1662 Maybe<TransactionInfo&> mLastBlockingReads;
1664 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
1665 TransactionInfoPair();
1666 ~TransactionInfoPair();
1667 #endif
1670 /*******************************************************************************
1671 * Actor class declarations
1672 ******************************************************************************/
1674 template <IDBCursorType CursorType>
1675 class CommonOpenOpHelper;
1676 template <IDBCursorType CursorType>
1677 class IndexOpenOpHelper;
1678 template <IDBCursorType CursorType>
1679 class ObjectStoreOpenOpHelper;
1680 template <IDBCursorType CursorType>
1681 class OpenOpHelper;
1683 class DatabaseOperationBase : public Runnable,
1684 public mozIStorageProgressHandler {
1685 template <IDBCursorType CursorType>
1686 friend class OpenOpHelper;
1688 protected:
1689 class AutoSetProgressHandler;
1691 using UniqueIndexTable = nsTHashMap<nsUint64HashKey, bool>;
1693 const nsCOMPtr<nsIEventTarget> mOwningEventTarget;
1694 const nsID mBackgroundChildLoggingId;
1695 const uint64_t mLoggingSerialNumber;
1697 private:
1698 nsresult mResultCode = NS_OK;
1699 Atomic<bool> mOperationMayProceed;
1700 FlippedOnce<false> mActorDestroyed;
1702 public:
1703 NS_DECL_ISUPPORTS_INHERITED
1705 bool IsOnOwningThread() const {
1706 MOZ_ASSERT(mOwningEventTarget);
1708 bool current;
1709 return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
1710 current;
1713 void AssertIsOnOwningThread() const {
1714 MOZ_ASSERT(IsOnBackgroundThread());
1715 MOZ_ASSERT(IsOnOwningThread());
1718 void NoteActorDestroyed() {
1719 AssertIsOnOwningThread();
1721 mActorDestroyed.EnsureFlipped();
1722 mOperationMayProceed = false;
1725 bool IsActorDestroyed() const {
1726 AssertIsOnOwningThread();
1728 return mActorDestroyed;
1731 // May be called on any thread, but you should call IsActorDestroyed() if
1732 // you know you're on the background thread because it is slightly faster.
1733 bool OperationMayProceed() const { return mOperationMayProceed; }
1735 const nsID& BackgroundChildLoggingId() const {
1736 return mBackgroundChildLoggingId;
1739 uint64_t LoggingSerialNumber() const { return mLoggingSerialNumber; }
1741 nsresult ResultCode() const { return mResultCode; }
1743 void SetFailureCode(nsresult aFailureCode) {
1744 MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
1745 OverrideFailureCode(aFailureCode);
1748 void SetFailureCodeIfUnset(nsresult aFailureCode) {
1749 if (NS_SUCCEEDED(mResultCode)) {
1750 OverrideFailureCode(aFailureCode);
1754 bool HasFailed() const { return NS_FAILED(mResultCode); }
1756 protected:
1757 DatabaseOperationBase(const nsID& aBackgroundChildLoggingId,
1758 uint64_t aLoggingSerialNumber)
1759 : Runnable("dom::indexedDB::DatabaseOperationBase"),
1760 mOwningEventTarget(GetCurrentSerialEventTarget()),
1761 mBackgroundChildLoggingId(aBackgroundChildLoggingId),
1762 mLoggingSerialNumber(aLoggingSerialNumber),
1763 mOperationMayProceed(true) {
1764 AssertIsOnOwningThread();
1767 ~DatabaseOperationBase() override { MOZ_ASSERT(mActorDestroyed); }
1769 void OverrideFailureCode(nsresult aFailureCode) {
1770 MOZ_ASSERT(NS_FAILED(aFailureCode));
1772 mResultCode = aFailureCode;
1775 static nsAutoCString MaybeGetBindingClauseForKeyRange(
1776 const Maybe<SerializedKeyRange>& aOptionalKeyRange,
1777 const nsACString& aKeyColumnName);
1779 static nsAutoCString GetBindingClauseForKeyRange(
1780 const SerializedKeyRange& aKeyRange, const nsACString& aKeyColumnName);
1782 static uint64_t ReinterpretDoubleAsUInt64(double aDouble);
1784 static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
1785 mozIStorageStatement* aStatement);
1787 static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
1788 mozIStorageStatement* aStatement,
1789 const nsCString& aLocale);
1791 static Result<IndexDataValuesAutoArray, nsresult>
1792 IndexDataValuesFromUpdateInfos(const nsTArray<IndexUpdateInfo>& aUpdateInfos,
1793 const UniqueIndexTable& aUniqueIndexTable);
1795 static nsresult InsertIndexTableRows(
1796 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
1797 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues);
1799 static nsresult DeleteIndexDataTableRows(
1800 DatabaseConnection* aConnection, const Key& aObjectStoreKey,
1801 const nsTArray<IndexDataValue>& aIndexValues);
1803 static nsresult DeleteObjectStoreDataTableRowsWithIndexes(
1804 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
1805 const Maybe<SerializedKeyRange>& aKeyRange);
1807 static nsresult UpdateIndexValues(
1808 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId,
1809 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues);
1811 static Result<bool, nsresult> ObjectStoreHasIndexes(
1812 DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId);
1814 private:
1815 template <typename KeyTransformation>
1816 static nsresult MaybeBindKeyToStatement(
1817 const Key& aKey, mozIStorageStatement* aStatement,
1818 const nsACString& aParameterName,
1819 const KeyTransformation& aKeyTransformation);
1821 template <typename KeyTransformation>
1822 static nsresult BindTransformedKeyRangeToStatement(
1823 const SerializedKeyRange& aKeyRange, mozIStorageStatement* aStatement,
1824 const KeyTransformation& aKeyTransformation);
1826 // Not to be overridden by subclasses.
1827 NS_DECL_MOZISTORAGEPROGRESSHANDLER
1830 class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final {
1831 Maybe<mozIStorageConnection&> mConnection;
1832 #ifdef DEBUG
1833 DatabaseOperationBase* mDEBUGDatabaseOp;
1834 #endif
1836 public:
1837 AutoSetProgressHandler();
1839 ~AutoSetProgressHandler();
1841 nsresult Register(mozIStorageConnection& aConnection,
1842 DatabaseOperationBase* aDatabaseOp);
1844 void Unregister();
1847 class TransactionDatabaseOperationBase : public DatabaseOperationBase {
1848 enum class InternalState {
1849 Initial,
1850 DatabaseWork,
1851 SendingPreprocess,
1852 WaitingForContinue,
1853 SendingResults,
1854 Completed
1857 InitializedOnce<const NotNull<SafeRefPtr<TransactionBase>>> mTransaction;
1858 // Unique request id within the context of the transaction, allocated by the
1859 // transaction in the content process starting from 0. Values less than 0 are
1860 // impossible and forbidden. Used to support the explicit commit() request.
1861 const int64_t mRequestId;
1862 InternalState mInternalState = InternalState::Initial;
1863 bool mWaitingForContinue = false;
1864 const bool mTransactionIsAborted;
1866 protected:
1867 const int64_t mTransactionLoggingSerialNumber;
1869 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1870 protected:
1871 // A check only enables when the diagnostic assert turns on. It assumes the
1872 // mUpdateRefcountFunction is a nullptr because the previous
1873 // StartTransactionOp failed on the connection thread and the next write
1874 // operation (e.g. ObjectstoreAddOrPutRequestOp) doesn't have enough time to
1875 // catch up the failure information.
1876 bool mAssumingPreviousOperationFail = false;
1877 #endif
1879 public:
1880 void AssertIsOnConnectionThread() const
1881 #ifdef DEBUG
1883 #else
1886 #endif
1888 uint64_t StartOnConnectionPool(const nsID& aBackgroundChildLoggingId,
1889 const nsACString& aDatabaseId,
1890 int64_t aLoggingSerialNumber,
1891 const nsTArray<nsString>& aObjectStoreNames,
1892 bool aIsWriteTransaction);
1894 void DispatchToConnectionPool();
1896 TransactionBase& Transaction() { return **mTransaction; }
1898 const TransactionBase& Transaction() const { return **mTransaction; }
1900 bool IsWaitingForContinue() const {
1901 AssertIsOnOwningThread();
1903 return mWaitingForContinue;
1906 void NoteContinueReceived();
1908 int64_t TransactionLoggingSerialNumber() const {
1909 return mTransactionLoggingSerialNumber;
1912 // May be overridden by subclasses if they need to perform work on the
1913 // background thread before being dispatched. Returning false will kill the
1914 // child actors and prevent dispatch.
1915 virtual bool Init(TransactionBase& aTransaction);
1917 // This callback will be called on the background thread before releasing the
1918 // final reference to this request object. Subclasses may perform any
1919 // additional cleanup here but must always call the base class implementation.
1920 virtual void Cleanup();
1922 protected:
1923 TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction,
1924 int64_t aRequestId);
1926 TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction,
1927 const int64_t aRequestId,
1928 uint64_t aLoggingSerialNumber);
1930 ~TransactionDatabaseOperationBase() override;
1932 virtual void RunOnConnectionThread();
1934 // Must be overridden in subclasses. Called on the target thread to allow the
1935 // subclass to perform necessary database or file operations. A successful
1936 // return value will trigger a SendSuccessResult callback on the background
1937 // thread while a failure value will trigger a SendFailureResult callback.
1938 virtual nsresult DoDatabaseWork(DatabaseConnection* aConnection) = 0;
1940 // May be overriden in subclasses. Called on the background thread to decide
1941 // if the subclass needs to send any preprocess info to the child actor.
1942 virtual bool HasPreprocessInfo();
1944 // May be overriden in subclasses. Called on the background thread to allow
1945 // the subclass to serialize its preprocess info and send it to the child
1946 // actor. A successful return value will trigger a wait for a
1947 // NoteContinueReceived callback on the background thread while a failure
1948 // value will trigger a SendFailureResult callback.
1949 virtual nsresult SendPreprocessInfo();
1951 // Must be overridden in subclasses. Called on the background thread to allow
1952 // the subclass to serialize its results and send them to the child actor. A
1953 // failed return value will trigger a SendFailureResult callback.
1954 virtual nsresult SendSuccessResult() = 0;
1956 // Must be overridden in subclasses. Called on the background thread to allow
1957 // the subclass to send its failure code. Returning false will cause the
1958 // transaction to be aborted with aResultCode. Returning true will not cause
1959 // the transaction to be aborted.
1960 virtual bool SendFailureResult(nsresult aResultCode) = 0;
1962 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1963 auto MakeAutoSavepointCleanupHandler(DatabaseConnection& aConnection) {
1964 return [this, &aConnection](const auto) {
1965 if (!aConnection.GetUpdateRefcountFunction()) {
1966 mAssumingPreviousOperationFail = true;
1970 #endif
1972 private:
1973 void SendToConnectionPool();
1975 void SendPreprocess();
1977 void SendResults();
1979 void SendPreprocessInfoOrResults(bool aSendPreprocessInfo);
1981 // Not to be overridden by subclasses.
1982 NS_DECL_NSIRUNNABLE
1985 class Factory final : public PBackgroundIDBFactoryParent,
1986 public AtomicSafeRefCounted<Factory> {
1987 nsCString mSystemLocale;
1988 RefPtr<DatabaseLoggingInfo> mLoggingInfo;
1990 #ifdef DEBUG
1991 bool mActorDestroyed;
1992 #endif
1994 // Reference counted.
1995 ~Factory() override;
1997 public:
1998 [[nodiscard]] static SafeRefPtr<Factory> Create(
1999 const LoggingInfo& aLoggingInfo, const nsACString& aSystemLocale);
2001 DatabaseLoggingInfo* GetLoggingInfo() const {
2002 AssertIsOnBackgroundThread();
2003 MOZ_ASSERT(mLoggingInfo);
2005 return mLoggingInfo;
2008 const nsCString& GetSystemLocale() const { return mSystemLocale; }
2010 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Factory)
2011 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(Factory, AtomicSafeRefCounted)
2013 // Only constructed in Create().
2014 Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo,
2015 const nsACString& aSystemLocale);
2017 // IPDL methods are only called by IPDL.
2018 void ActorDestroy(ActorDestroyReason aWhy) override;
2020 mozilla::ipc::IPCResult RecvDeleteMe() override;
2022 PBackgroundIDBFactoryRequestParent* AllocPBackgroundIDBFactoryRequestParent(
2023 const FactoryRequestParams& aParams) override;
2025 mozilla::ipc::IPCResult RecvPBackgroundIDBFactoryRequestConstructor(
2026 PBackgroundIDBFactoryRequestParent* aActor,
2027 const FactoryRequestParams& aParams) override;
2029 bool DeallocPBackgroundIDBFactoryRequestParent(
2030 PBackgroundIDBFactoryRequestParent* aActor) override;
2032 mozilla::ipc::IPCResult RecvGetDatabases(
2033 const PersistenceType& aPersistenceType,
2034 const PrincipalInfo& aPrincipalInfo,
2035 GetDatabasesResolver&& aResolve) override;
2037 private:
2038 Maybe<ContentParentId> GetContentParentId() const;
2041 class WaitForTransactionsHelper final : public Runnable {
2042 const nsCString mDatabaseId;
2043 nsCOMPtr<nsIRunnable> mCallback;
2045 enum class State { Initial = 0, WaitingForTransactions, Complete } mState;
2047 public:
2048 WaitForTransactionsHelper(const nsACString& aDatabaseId,
2049 nsIRunnable* aCallback)
2050 : Runnable("dom::indexedDB::WaitForTransactionsHelper"),
2051 mDatabaseId(aDatabaseId),
2052 mCallback(aCallback),
2053 mState(State::Initial) {
2054 AssertIsOnBackgroundThread();
2055 MOZ_ASSERT(!aDatabaseId.IsEmpty());
2056 MOZ_ASSERT(aCallback);
2059 void WaitForTransactions();
2061 NS_INLINE_DECL_REFCOUNTING_INHERITED(WaitForTransactionsHelper, Runnable)
2063 private:
2064 ~WaitForTransactionsHelper() override {
2065 MOZ_ASSERT(!mCallback);
2066 MOZ_ASSERT(mState == State::Complete);
2069 void MaybeWaitForTransactions();
2071 void CallCallback();
2073 NS_DECL_NSIRUNNABLE
2076 class Database final
2077 : public PBackgroundIDBDatabaseParent,
2078 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>>,
2079 public AtomicSafeRefCounted<Database> {
2080 friend class VersionChangeTransaction;
2082 class StartTransactionOp;
2083 class UnmapBlobCallback;
2085 private:
2086 SafeRefPtr<Factory> mFactory;
2087 SafeRefPtr<FullDatabaseMetadata> mMetadata;
2088 SafeRefPtr<DatabaseFileManager> mFileManager;
2089 RefPtr<DirectoryLock> mDirectoryLock;
2090 nsTHashSet<TransactionBase*> mTransactions;
2091 nsTHashMap<nsIDHashKey, SafeRefPtr<DatabaseFileInfo>> mMappedBlobs;
2092 RefPtr<DatabaseConnection> mConnection;
2093 const PrincipalInfo mPrincipalInfo;
2094 const Maybe<ContentParentId> mOptionalContentParentId;
2095 // XXX Consider changing this to ClientMetadata.
2096 const quota::OriginMetadata mOriginMetadata;
2097 const nsCString mId;
2098 const nsString mFilePath;
2099 const Maybe<const CipherKey> mKey;
2100 int64_t mDirectoryLockId;
2101 const uint32_t mTelemetryId;
2102 const PersistenceType mPersistenceType;
2103 const bool mInPrivateBrowsing;
2104 FlippedOnce<false> mClosed;
2105 FlippedOnce<false> mInvalidated;
2106 FlippedOnce<false> mActorWasAlive;
2107 FlippedOnce<false> mActorDestroyed;
2108 nsCOMPtr<nsIEventTarget> mBackgroundThread;
2109 #ifdef DEBUG
2110 bool mAllBlobsUnmapped;
2111 #endif
2113 public:
2114 // Created by OpenDatabaseOp.
2115 Database(SafeRefPtr<Factory> aFactory, const PrincipalInfo& aPrincipalInfo,
2116 const Maybe<ContentParentId>& aOptionalContentParentId,
2117 const quota::OriginMetadata& aOriginMetadata, uint32_t aTelemetryId,
2118 SafeRefPtr<FullDatabaseMetadata> aMetadata,
2119 SafeRefPtr<DatabaseFileManager> aFileManager,
2120 RefPtr<DirectoryLock> aDirectoryLock, bool aInPrivateBrowsing,
2121 const Maybe<const CipherKey>& aMaybeKey);
2123 void AssertIsOnConnectionThread() const {
2124 #ifdef DEBUG
2125 if (mConnection) {
2126 MOZ_ASSERT(mConnection);
2127 mConnection->AssertIsOnConnectionThread();
2128 } else {
2129 MOZ_ASSERT(!NS_IsMainThread());
2130 MOZ_ASSERT(!IsOnBackgroundThread());
2131 MOZ_ASSERT(mInvalidated);
2133 #endif
2136 NS_IMETHOD_(MozExternalRefCountType) AddRef() override {
2137 return AtomicSafeRefCounted<Database>::AddRef();
2139 NS_IMETHOD_(MozExternalRefCountType) Release() override {
2140 return AtomicSafeRefCounted<Database>::Release();
2143 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Database)
2145 void Invalidate();
2147 bool IsOwnedByProcess(ContentParentId aContentParentId) const {
2148 return mOptionalContentParentId &&
2149 mOptionalContentParentId.value() == aContentParentId;
2152 const quota::OriginMetadata& OriginMetadata() const {
2153 return mOriginMetadata;
2156 const nsCString& Id() const { return mId; }
2158 Maybe<DirectoryLock&> MaybeDirectoryLockRef() const {
2159 AssertIsOnBackgroundThread();
2161 return ToMaybeRef(mDirectoryLock.get());
2164 int64_t DirectoryLockId() const { return mDirectoryLockId; }
2166 uint32_t TelemetryId() const { return mTelemetryId; }
2168 PersistenceType Type() const { return mPersistenceType; }
2170 const nsString& FilePath() const { return mFilePath; }
2172 DatabaseFileManager& GetFileManager() const { return *mFileManager; }
2174 MovingNotNull<SafeRefPtr<DatabaseFileManager>> GetFileManagerPtr() const {
2175 return WrapMovingNotNull(mFileManager.clonePtr());
2178 const FullDatabaseMetadata& Metadata() const {
2179 MOZ_ASSERT(mMetadata);
2180 return *mMetadata;
2183 SafeRefPtr<FullDatabaseMetadata> MetadataPtr() const {
2184 MOZ_ASSERT(mMetadata);
2185 return mMetadata.clonePtr();
2188 PBackgroundParent* GetBackgroundParent() const {
2189 AssertIsOnBackgroundThread();
2190 MOZ_ASSERT(!IsActorDestroyed());
2192 return Manager()->Manager();
2195 DatabaseLoggingInfo* GetLoggingInfo() const {
2196 AssertIsOnBackgroundThread();
2197 MOZ_ASSERT(mFactory);
2199 return mFactory->GetLoggingInfo();
2202 bool RegisterTransaction(TransactionBase& aTransaction);
2204 void UnregisterTransaction(TransactionBase& aTransaction);
2206 void SetActorAlive();
2208 void MapBlob(const IPCBlob& aIPCBlob, SafeRefPtr<DatabaseFileInfo> aFileInfo);
2210 bool IsActorAlive() const {
2211 AssertIsOnBackgroundThread();
2213 return mActorWasAlive && !mActorDestroyed;
2216 bool IsActorDestroyed() const {
2217 AssertIsOnBackgroundThread();
2219 return mActorWasAlive && mActorDestroyed;
2222 bool IsClosed() const {
2223 AssertIsOnBackgroundThread();
2225 return mClosed;
2228 bool IsInvalidated() const {
2229 AssertIsOnBackgroundThread();
2231 return mInvalidated;
2234 nsresult EnsureConnection();
2236 DatabaseConnection* GetConnection() const {
2237 #ifdef DEBUG
2238 if (mConnection) {
2239 mConnection->AssertIsOnConnectionThread();
2241 #endif
2243 return mConnection;
2246 void Stringify(nsACString& aResult) const;
2248 bool IsInPrivateBrowsing() const {
2249 AssertIsOnBackgroundThread();
2250 return mInPrivateBrowsing;
2253 const Maybe<const CipherKey>& MaybeKeyRef() const {
2254 // This can be called on any thread, as it is const.
2255 MOZ_ASSERT(mKey.isSome() == mInPrivateBrowsing);
2256 return mKey;
2259 ~Database() override {
2260 MOZ_ASSERT(mClosed);
2261 MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
2263 NS_ProxyRelease("ReleaseIDBFactory", mBackgroundThread.get(),
2264 mFactory.forget());
2267 private:
2268 [[nodiscard]] SafeRefPtr<DatabaseFileInfo> GetBlob(const IPCBlob& aIPCBlob);
2270 void UnmapBlob(const nsID& aID);
2272 void UnmapAllBlobs();
2274 bool CloseInternal();
2276 void MaybeCloseConnection();
2278 void ConnectionClosedCallback();
2280 void CleanupMetadata();
2282 // IPDL methods are only called by IPDL.
2283 void ActorDestroy(ActorDestroyReason aWhy) override;
2285 PBackgroundIDBDatabaseFileParent* AllocPBackgroundIDBDatabaseFileParent(
2286 const IPCBlob& aIPCBlob) override;
2288 bool DeallocPBackgroundIDBDatabaseFileParent(
2289 PBackgroundIDBDatabaseFileParent* aActor) override;
2291 already_AddRefed<PBackgroundIDBTransactionParent>
2292 AllocPBackgroundIDBTransactionParent(
2293 const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode,
2294 const Durability& aDurability) override;
2296 mozilla::ipc::IPCResult RecvPBackgroundIDBTransactionConstructor(
2297 PBackgroundIDBTransactionParent* aActor,
2298 nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode,
2299 const Durability& aDurability) override;
2301 mozilla::ipc::IPCResult RecvDeleteMe() override;
2303 mozilla::ipc::IPCResult RecvBlocked() override;
2305 mozilla::ipc::IPCResult RecvClose() override;
2307 template <typename T>
2308 static bool InvalidateAll(const nsTBaseHashSet<nsPtrHashKey<T>>& aTable);
2311 class Database::StartTransactionOp final
2312 : public TransactionDatabaseOperationBase {
2313 friend class Database;
2315 private:
2316 explicit StartTransactionOp(SafeRefPtr<TransactionBase> aTransaction)
2317 : TransactionDatabaseOperationBase(std::move(aTransaction),
2318 /* aRequestId */ 0,
2319 /* aLoggingSerialNumber */ 0) {}
2321 ~StartTransactionOp() override = default;
2323 void RunOnConnectionThread() override;
2325 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
2327 nsresult SendSuccessResult() override;
2329 bool SendFailureResult(nsresult aResultCode) override;
2331 void Cleanup() override;
2334 class Database::UnmapBlobCallback final
2335 : public RemoteLazyInputStreamParentCallback {
2336 SafeRefPtr<Database> mDatabase;
2337 nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
2339 public:
2340 explicit UnmapBlobCallback(SafeRefPtr<Database> aDatabase)
2341 : mDatabase(std::move(aDatabase)),
2342 mBackgroundThread(GetCurrentSerialEventTarget()) {
2343 AssertIsOnBackgroundThread();
2346 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Database::UnmapBlobCallback, override)
2348 void ActorDestroyed(const nsID& aID) override {
2349 MOZ_ASSERT(mDatabase);
2350 mBackgroundThread->Dispatch(NS_NewRunnableFunction(
2351 "UnmapBlobCallback", [aID, database = std::move(mDatabase)] {
2352 AssertIsOnBackgroundThread();
2353 database->UnmapBlob(aID);
2354 }));
2357 private:
2358 ~UnmapBlobCallback() = default;
2362 * In coordination with IDBDatabase's mFileActors weak-map on the child side, a
2363 * long-lived mapping from a child process's live Blobs to their corresponding
2364 * DatabaseFileInfo in our owning database. Assists in avoiding redundant IPC
2365 * traffic and disk storage. This includes both:
2366 * - Blobs retrieved from this database and sent to the child that do not need
2367 * to be written to disk because they already exist on disk in this database's
2368 * files directory.
2369 * - Blobs retrieved from other databases or from anywhere else that will need
2370 * to be written to this database's files directory. In this case we will
2371 * hold a reference to its BlobImpl in mBlobImpl until we have successfully
2372 * written the Blob to disk.
2374 * Relevant Blob context: Blobs sent from the parent process to child processes
2375 * are automatically linked back to their source BlobImpl when the child process
2376 * references the Blob via IPC. This is done using the internal IPCBlob
2377 * inputStream actor ID to DatabaseFileInfo mapping. However, when getting an
2378 * actor in the child process for sending an in-child-created Blob to the
2379 * parent process, there is (currently) no Blob machinery to automatically
2380 * establish and reuse a long-lived Actor. As a result, without IDB's weak-map
2381 * cleverness, a memory-backed Blob repeatedly sent from the child to the parent
2382 * would appear as a different Blob each time, requiring the Blob data to be
2383 * sent over IPC each time as well as potentially needing to be written to disk
2384 * each time.
2386 * This object remains alive as long as there is an active child actor or an
2387 * ObjectStoreAddOrPutRequestOp::StoredFileInfo for a queued or active add/put
2388 * op is holding a reference to us.
2390 class DatabaseFile final : public PBackgroundIDBDatabaseFileParent {
2391 // mBlobImpl's ownership lifecycle:
2392 // - Initialized on the background thread at creation time. Then
2393 // responsibility is handed off to the connection thread.
2394 // - Checked and used by the connection thread to generate a stream to write
2395 // the blob to disk by an add/put operation.
2396 // - Cleared on the connection thread once the file has successfully been
2397 // written to disk.
2398 InitializedOnce<const RefPtr<BlobImpl>> mBlobImpl;
2399 const SafeRefPtr<DatabaseFileInfo> mFileInfo;
2401 public:
2402 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::DatabaseFile);
2404 const DatabaseFileInfo& GetFileInfo() const {
2405 AssertIsOnBackgroundThread();
2407 return *mFileInfo;
2410 SafeRefPtr<DatabaseFileInfo> GetFileInfoPtr() const {
2411 AssertIsOnBackgroundThread();
2413 return mFileInfo.clonePtr();
2417 * If mBlobImpl is non-null (implying the contents of this file have not yet
2418 * been written to disk), then return an input stream. Otherwise, if mBlobImpl
2419 * is null (because the contents have been written to disk), returns null.
2421 [[nodiscard]] nsCOMPtr<nsIInputStream> GetInputStream(ErrorResult& rv) const;
2424 * To be called upon successful copying of the stream GetInputStream()
2425 * returned so that we won't try and redundantly write the file to disk in the
2426 * future. This is a separate step from GetInputStream() because
2427 * the write could fail due to quota errors that happen now but that might
2428 * not happen in a future attempt.
2430 void WriteSucceededClearBlobImpl() {
2431 MOZ_ASSERT(!IsOnBackgroundThread());
2433 MOZ_ASSERT(*mBlobImpl);
2434 mBlobImpl.destroy();
2437 public:
2438 // Called when sending to the child.
2439 explicit DatabaseFile(SafeRefPtr<DatabaseFileInfo> aFileInfo)
2440 : mBlobImpl{nullptr}, mFileInfo(std::move(aFileInfo)) {
2441 AssertIsOnBackgroundThread();
2442 MOZ_ASSERT(mFileInfo);
2445 // Called when receiving from the child.
2446 DatabaseFile(RefPtr<BlobImpl> aBlobImpl,
2447 SafeRefPtr<DatabaseFileInfo> aFileInfo)
2448 : mBlobImpl(std::move(aBlobImpl)), mFileInfo(std::move(aFileInfo)) {
2449 AssertIsOnBackgroundThread();
2450 MOZ_ASSERT(*mBlobImpl);
2451 MOZ_ASSERT(mFileInfo);
2454 private:
2455 ~DatabaseFile() override = default;
2457 void ActorDestroy(ActorDestroyReason aWhy) override {
2458 AssertIsOnBackgroundThread();
2462 nsCOMPtr<nsIInputStream> DatabaseFile::GetInputStream(ErrorResult& rv) const {
2463 // We should only be called from our DB connection thread, not the background
2464 // thread.
2465 MOZ_ASSERT(!IsOnBackgroundThread());
2467 // If we were constructed without a BlobImpl, or WriteSucceededClearBlobImpl
2468 // was already called, return nullptr.
2469 if (!mBlobImpl || !*mBlobImpl) {
2470 return nullptr;
2473 nsCOMPtr<nsIInputStream> inputStream;
2474 (*mBlobImpl)->CreateInputStream(getter_AddRefs(inputStream), rv);
2475 if (rv.Failed()) {
2476 return nullptr;
2479 return inputStream;
2482 class TransactionBase : public AtomicSafeRefCounted<TransactionBase> {
2483 friend class CursorBase;
2485 template <IDBCursorType CursorType>
2486 friend class Cursor;
2488 class CommitOp;
2490 protected:
2491 using Mode = IDBTransaction::Mode;
2492 using Durability = IDBTransaction::Durability;
2494 private:
2495 const SafeRefPtr<Database> mDatabase;
2496 nsTArray<SafeRefPtr<FullObjectStoreMetadata>>
2497 mModifiedAutoIncrementObjectStoreMetadataArray;
2498 LazyInitializedOnceNotNull<const uint64_t> mTransactionId;
2499 const nsCString mDatabaseId;
2500 const int64_t mLoggingSerialNumber;
2501 uint64_t mActiveRequestCount;
2502 Atomic<bool> mInvalidatedOnAnyThread;
2503 const Mode mMode;
2504 const Durability mDurability; // TODO: See bug 1883045
2505 FlippedOnce<false> mInitialized;
2506 FlippedOnce<false> mHasBeenActiveOnConnectionThread;
2507 FlippedOnce<false> mActorDestroyed;
2508 FlippedOnce<false> mInvalidated;
2510 protected:
2511 nsresult mResultCode;
2512 FlippedOnce<false> mCommitOrAbortReceived;
2513 FlippedOnce<false> mCommittedOrAborted;
2514 FlippedOnce<false> mForceAborted;
2515 LazyInitializedOnce<const Maybe<int64_t>> mLastRequestBeforeCommit;
2516 Maybe<int64_t> mLastFailedRequest;
2518 public:
2519 void AssertIsOnConnectionThread() const {
2520 MOZ_ASSERT(mDatabase);
2521 mDatabase->AssertIsOnConnectionThread();
2524 bool IsActorDestroyed() const {
2525 AssertIsOnBackgroundThread();
2527 return mActorDestroyed;
2530 // Must be called on the background thread.
2531 bool IsInvalidated() const {
2532 MOZ_ASSERT(IsOnBackgroundThread(), "Use IsInvalidatedOnAnyThread()");
2533 MOZ_ASSERT_IF(mInvalidated, NS_FAILED(mResultCode));
2535 return mInvalidated;
2538 // May be called on any thread, but is more expensive than IsInvalidated().
2539 bool IsInvalidatedOnAnyThread() const { return mInvalidatedOnAnyThread; }
2541 void Init(const uint64_t aTransactionId) {
2542 AssertIsOnBackgroundThread();
2543 MOZ_ASSERT(aTransactionId);
2545 mTransactionId.init(aTransactionId);
2546 mInitialized.Flip();
2549 void SetActiveOnConnectionThread() {
2550 AssertIsOnConnectionThread();
2551 mHasBeenActiveOnConnectionThread.Flip();
2554 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::TransactionBase)
2556 void Abort(nsresult aResultCode, bool aForce);
2558 uint64_t TransactionId() const { return *mTransactionId; }
2560 const nsACString& DatabaseId() const { return mDatabaseId; }
2562 Mode GetMode() const { return mMode; }
2564 Durability GetDurability() const { return mDurability; }
2566 const Database& GetDatabase() const {
2567 MOZ_ASSERT(mDatabase);
2569 return *mDatabase;
2572 Database& GetMutableDatabase() const {
2573 MOZ_ASSERT(mDatabase);
2575 return *mDatabase;
2578 SafeRefPtr<Database> GetDatabasePtr() const {
2579 MOZ_ASSERT(mDatabase);
2581 return mDatabase.clonePtr();
2584 DatabaseLoggingInfo* GetLoggingInfo() const {
2585 AssertIsOnBackgroundThread();
2586 MOZ_ASSERT(mDatabase);
2588 return mDatabase->GetLoggingInfo();
2591 int64_t LoggingSerialNumber() const { return mLoggingSerialNumber; }
2593 bool IsAborted() const {
2594 AssertIsOnBackgroundThread();
2596 return NS_FAILED(mResultCode);
2599 [[nodiscard]] SafeRefPtr<FullObjectStoreMetadata> GetMetadataForObjectStoreId(
2600 IndexOrObjectStoreId aObjectStoreId) const;
2602 [[nodiscard]] SafeRefPtr<FullIndexMetadata> GetMetadataForIndexId(
2603 FullObjectStoreMetadata& aObjectStoreMetadata,
2604 IndexOrObjectStoreId aIndexId) const;
2606 PBackgroundParent* GetBackgroundParent() const {
2607 AssertIsOnBackgroundThread();
2608 MOZ_ASSERT(!IsActorDestroyed());
2610 return GetDatabase().GetBackgroundParent();
2613 void NoteModifiedAutoIncrementObjectStore(
2614 const SafeRefPtr<FullObjectStoreMetadata>& aMetadata);
2616 void ForgetModifiedAutoIncrementObjectStore(
2617 FullObjectStoreMetadata& aMetadata);
2619 void NoteActiveRequest();
2621 void NoteFinishedRequest(int64_t aRequestId, nsresult aResultCode);
2623 void Invalidate();
2625 virtual ~TransactionBase();
2627 protected:
2628 TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode,
2629 Durability aDurability);
2631 void NoteActorDestroyed() {
2632 AssertIsOnBackgroundThread();
2634 mActorDestroyed.Flip();
2637 #ifdef DEBUG
2638 // Only called by VersionChangeTransaction.
2639 void FakeActorDestroyed() { mActorDestroyed.EnsureFlipped(); }
2640 #endif
2642 mozilla::ipc::IPCResult RecvCommit(IProtocol* aActor,
2643 const Maybe<int64_t> aLastRequest);
2645 mozilla::ipc::IPCResult RecvAbort(IProtocol* aActor, nsresult aResultCode);
2647 void MaybeCommitOrAbort() {
2648 AssertIsOnBackgroundThread();
2650 // If we've already committed or aborted then there's nothing else to do.
2651 if (mCommittedOrAborted) {
2652 return;
2655 // If there are active requests then we have to wait for those requests to
2656 // complete (see NoteFinishedRequest).
2657 if (mActiveRequestCount) {
2658 return;
2661 // If we haven't yet received a commit or abort message then there could be
2662 // additional requests coming so we should wait unless we're being forced to
2663 // abort.
2664 if (!mCommitOrAbortReceived && !mForceAborted) {
2665 return;
2668 CommitOrAbort();
2671 PBackgroundIDBRequestParent* AllocRequest(const int64_t aRequestId,
2672 RequestParams&& aParams,
2673 bool aTrustParams);
2675 bool StartRequest(PBackgroundIDBRequestParent* aActor);
2677 bool DeallocRequest(PBackgroundIDBRequestParent* aActor);
2679 already_AddRefed<PBackgroundIDBCursorParent> AllocCursor(
2680 const OpenCursorParams& aParams, bool aTrustParams);
2682 bool StartCursor(PBackgroundIDBCursorParent* aActor, const int64_t aRequestId,
2683 const OpenCursorParams& aParams);
2685 virtual void UpdateMetadata(nsresult aResult) {}
2687 virtual void SendCompleteNotification(nsresult aResult) = 0;
2689 private:
2690 bool VerifyRequestParams(const RequestParams& aParams) const;
2692 bool VerifyRequestParams(const SerializedKeyRange& aParams) const;
2694 bool VerifyRequestParams(const ObjectStoreAddPutParams& aParams) const;
2696 bool VerifyRequestParams(const Maybe<SerializedKeyRange>& aParams) const;
2698 void CommitOrAbort();
2701 class TransactionBase::CommitOp final : public DatabaseOperationBase,
2702 public ConnectionPool::FinishCallback {
2703 friend class TransactionBase;
2705 SafeRefPtr<TransactionBase> mTransaction;
2706 nsresult mResultCode; ///< TODO: There is also a mResultCode in
2707 ///< DatabaseOperationBase. Is there a reason not to
2708 ///< use that? At least a more specific name should be
2709 ///< given to this one.
2711 private:
2712 CommitOp(SafeRefPtr<TransactionBase> aTransaction, nsresult aResultCode);
2714 ~CommitOp() override = default;
2716 // Writes new autoIncrement counts to database.
2717 nsresult WriteAutoIncrementCounts();
2719 // Updates counts after a database activity has finished.
2720 void CommitOrRollbackAutoIncrementCounts();
2722 void AssertForeignKeyConsistency(DatabaseConnection* aConnection)
2723 #ifdef DEBUG
2725 #else
2728 #endif
2730 NS_DECL_NSIRUNNABLE
2732 void TransactionFinishedBeforeUnblock() override;
2734 void TransactionFinishedAfterUnblock() override;
2736 public:
2737 // We need to declare all of nsISupports, because FinishCallback has
2738 // a pure-virtual nsISupports declaration.
2739 NS_DECL_ISUPPORTS_INHERITED
2742 class NormalTransaction final : public TransactionBase,
2743 public PBackgroundIDBTransactionParent {
2744 nsTArray<SafeRefPtr<FullObjectStoreMetadata>> mObjectStores;
2746 // Reference counted.
2747 ~NormalTransaction() override = default;
2749 bool IsSameProcessActor();
2751 // Only called by TransactionBase.
2752 void SendCompleteNotification(nsresult aResult) override;
2754 // IPDL methods are only called by IPDL.
2755 void ActorDestroy(ActorDestroyReason aWhy) override;
2757 mozilla::ipc::IPCResult RecvDeleteMe() override;
2759 mozilla::ipc::IPCResult RecvCommit(
2760 const Maybe<int64_t>& aLastRequest) override;
2762 mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override;
2764 PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent(
2765 const int64_t& aRequestId, const RequestParams& aParams) override;
2767 mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor(
2768 PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId,
2769 const RequestParams& aParams) override;
2771 bool DeallocPBackgroundIDBRequestParent(
2772 PBackgroundIDBRequestParent* aActor) override;
2774 already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent(
2775 const int64_t& aRequestId, const OpenCursorParams& aParams) override;
2777 mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor(
2778 PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId,
2779 const OpenCursorParams& aParams) override;
2781 public:
2782 // This constructor is only called by Database.
2783 NormalTransaction(
2784 SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode,
2785 TransactionBase::Durability aDurability,
2786 nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores);
2788 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(NormalTransaction, TransactionBase)
2791 class VersionChangeTransaction final
2792 : public TransactionBase,
2793 public PBackgroundIDBVersionChangeTransactionParent {
2794 friend class OpenDatabaseOp;
2796 RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
2797 SafeRefPtr<FullDatabaseMetadata> mOldMetadata;
2799 FlippedOnce<false> mActorWasAlive;
2801 public:
2802 // Only called by OpenDatabaseOp.
2803 explicit VersionChangeTransaction(OpenDatabaseOp* aOpenDatabaseOp);
2805 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(VersionChangeTransaction,
2806 TransactionBase)
2808 private:
2809 // Reference counted.
2810 ~VersionChangeTransaction() override;
2812 bool IsSameProcessActor();
2814 // Only called by OpenDatabaseOp.
2815 bool CopyDatabaseMetadata();
2817 void SetActorAlive();
2819 // Only called by TransactionBase.
2820 void UpdateMetadata(nsresult aResult) override;
2822 // Only called by TransactionBase.
2823 void SendCompleteNotification(nsresult aResult) override;
2825 // IPDL methods are only called by IPDL.
2826 void ActorDestroy(ActorDestroyReason aWhy) override;
2828 mozilla::ipc::IPCResult RecvDeleteMe() override;
2830 mozilla::ipc::IPCResult RecvCommit(
2831 const Maybe<int64_t>& aLastRequest) override;
2833 mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override;
2835 mozilla::ipc::IPCResult RecvCreateObjectStore(
2836 const ObjectStoreMetadata& aMetadata) override;
2838 mozilla::ipc::IPCResult RecvDeleteObjectStore(
2839 const IndexOrObjectStoreId& aObjectStoreId) override;
2841 mozilla::ipc::IPCResult RecvRenameObjectStore(
2842 const IndexOrObjectStoreId& aObjectStoreId,
2843 const nsAString& aName) override;
2845 mozilla::ipc::IPCResult RecvCreateIndex(
2846 const IndexOrObjectStoreId& aObjectStoreId,
2847 const IndexMetadata& aMetadata) override;
2849 mozilla::ipc::IPCResult RecvDeleteIndex(
2850 const IndexOrObjectStoreId& aObjectStoreId,
2851 const IndexOrObjectStoreId& aIndexId) override;
2853 mozilla::ipc::IPCResult RecvRenameIndex(
2854 const IndexOrObjectStoreId& aObjectStoreId,
2855 const IndexOrObjectStoreId& aIndexId, const nsAString& aName) override;
2857 PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent(
2858 const int64_t& aRequestId, const RequestParams& aParams) override;
2860 mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor(
2861 PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId,
2862 const RequestParams& aParams) override;
2864 bool DeallocPBackgroundIDBRequestParent(
2865 PBackgroundIDBRequestParent* aActor) override;
2867 already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent(
2868 const int64_t& aRequestId, const OpenCursorParams& aParams) override;
2870 mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor(
2871 PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId,
2872 const OpenCursorParams& aParams) override;
2875 class FactoryOp
2876 : public DatabaseOperationBase,
2877 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
2878 public:
2879 struct MaybeBlockedDatabaseInfo final {
2880 SafeRefPtr<Database> mDatabase;
2881 bool mBlocked;
2883 MaybeBlockedDatabaseInfo(MaybeBlockedDatabaseInfo&&) = default;
2884 MaybeBlockedDatabaseInfo& operator=(MaybeBlockedDatabaseInfo&&) = default;
2886 MOZ_IMPLICIT MaybeBlockedDatabaseInfo(SafeRefPtr<Database> aDatabase)
2887 : mDatabase(std::move(aDatabase)), mBlocked(false) {
2888 MOZ_ASSERT(mDatabase);
2890 MOZ_COUNT_CTOR(FactoryOp::MaybeBlockedDatabaseInfo);
2893 ~MaybeBlockedDatabaseInfo() {
2894 MOZ_COUNT_DTOR(FactoryOp::MaybeBlockedDatabaseInfo);
2897 bool operator==(const Database* aOther) const {
2898 return mDatabase == aOther;
2901 Database* operator->() const& MOZ_NO_ADDREF_RELEASE_ON_RETURN {
2902 return mDatabase.unsafeGetRawPtr();
2906 protected:
2907 enum class State {
2908 // Just created on the PBackground thread, dispatched to the current thread.
2909 // Next step is either SendingResults if opening initialization failed, or
2910 // DirectoryOpenPending if the opening initialization succeeded.
2911 Initial,
2913 // Waiting for directory open allowed on the PBackground thread. The next
2914 // step is either SendingResults if directory lock failed to acquire, or
2915 // DirectoryWorkOpen if the factory operation is not tied up to a specific
2916 // database, or DatabaseOpenPending otherwise.
2917 DirectoryOpenPending,
2919 // Waiting to do/doing directory work on the QuotaManager IO thread. Its
2920 // next step is DirectoryWorkDone if directory work was successful or
2921 // SendingResults if directory work failed.
2922 DirectoryWorkOpen,
2924 // Checking if database work can be started. If the database is not blocked
2925 // by other factory operations then the next step is DatabaseWorkOpen.
2926 // Otherwise the next step is DatabaseOpenPending.
2927 DirectoryWorkDone,
2929 // Waiting for database open allowed on the PBackground thread. The next
2930 // step is DatabaseWorkOpen.
2931 DatabaseOpenPending,
2933 // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
2934 // either BeginVersionChange if the requested version doesn't match the
2935 // existing database version or SendingResults if the versions match.
2936 DatabaseWorkOpen,
2938 // Starting a version change transaction or deleting a database on the
2939 // PBackground thread. We need to notify other databases that a version
2940 // change is about to happen, and maybe tell the request that a version
2941 // change has been blocked. If databases are notified then the next step is
2942 // WaitingForOtherDatabasesToClose. Otherwise the next step is
2943 // WaitingForTransactionsToComplete.
2944 BeginVersionChange,
2946 // Waiting for other databases to close on the PBackground thread. This
2947 // state may persist until all databases are closed. The next state is
2948 // WaitingForTransactionsToComplete.
2949 WaitingForOtherDatabasesToClose,
2951 // Waiting for all transactions that could interfere with this operation to
2952 // complete on the PBackground thread. Next state is
2953 // DatabaseWorkVersionChange.
2954 WaitingForTransactionsToComplete,
2956 // Waiting to do/doing work on the "work thread". This involves waiting for
2957 // the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a
2958 // different implementation) to do its work. Eventually the state will
2959 // transition to SendingResults.
2960 DatabaseWorkVersionChange,
2962 // Waiting to send/sending results on the PBackground thread. Next step is
2963 // Completed.
2964 SendingResults,
2966 // All done.
2967 Completed
2970 // Must be released on the background thread!
2971 SafeRefPtr<Factory> mFactory;
2973 Maybe<ContentParentId> mContentParentId;
2975 // Must be released on the main thread!
2976 RefPtr<DirectoryLock> mDirectoryLock;
2978 nsTArray<NotNull<RefPtr<FactoryOp>>> mBlocking;
2979 nsTArray<NotNull<RefPtr<FactoryOp>>> mBlockedOn;
2981 nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases;
2983 const PrincipalInfo mPrincipalInfo;
2984 OriginMetadata mOriginMetadata;
2985 Maybe<nsString> mDatabaseName;
2986 Maybe<nsCString> mDatabaseId;
2987 Maybe<nsString> mDatabaseFilePath;
2988 int64_t mDirectoryLockId;
2989 const PersistenceType mPersistenceType;
2990 State mState;
2991 bool mWaitingForPermissionRetry;
2992 bool mEnforcingQuota;
2993 const bool mDeleting;
2994 FlippedOnce<false> mInPrivateBrowsing;
2996 public:
2997 const nsACString& Origin() const {
2998 AssertIsOnOwningThread();
3000 return mOriginMetadata.mOrigin;
3003 const Maybe<nsString>& DatabaseNameRef() const {
3004 AssertIsOnOwningThread();
3006 return mDatabaseName;
3009 bool DatabaseFilePathIsKnown() const {
3010 AssertIsOnOwningThread();
3012 return mDatabaseFilePath.isSome();
3015 const nsAString& DatabaseFilePath() const {
3016 AssertIsOnOwningThread();
3017 MOZ_ASSERT(mDatabaseFilePath);
3019 return mDatabaseFilePath.ref();
3022 void NoteDatabaseBlocked(Database* aDatabase);
3024 void NoteDatabaseClosed(Database* aDatabase);
3026 #ifdef DEBUG
3027 bool HasBlockedDatabases() const { return !mMaybeBlockedDatabases.IsEmpty(); }
3028 #endif
3030 void StringifyState(nsACString& aResult) const;
3032 void Stringify(nsACString& aResult) const;
3034 protected:
3035 FactoryOp(SafeRefPtr<Factory> aFactory,
3036 const Maybe<ContentParentId>& aContentParentId,
3037 const PersistenceType aPersistenceType,
3038 const PrincipalInfo& aPrincipalInfo,
3039 const Maybe<nsString>& aDatabaseName, bool aDeleting);
3041 ~FactoryOp() override {
3042 // Normally this would be out-of-line since it is a virtual function but
3043 // MSVC 2010 fails to link for some reason if it is not inlined here...
3044 MOZ_ASSERT_IF(OperationMayProceed(),
3045 mState == State::Initial || mState == State::Completed);
3048 nsresult Open();
3050 nsresult DirectoryOpen();
3052 nsresult DirectoryWorkDone();
3054 nsresult SendToIOThread();
3056 void WaitForTransactions();
3058 void CleanupMetadata();
3060 void FinishSendResults();
3062 nsresult SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
3063 Maybe<Database&> aOpeningDatabase,
3064 uint64_t aOldVersion,
3065 const Maybe<uint64_t>& aNewVersion);
3067 // Methods that subclasses must implement.
3068 virtual nsresult DoDirectoryWork() = 0;
3070 virtual nsresult DatabaseOpen() = 0;
3072 virtual nsresult DoDatabaseWork() = 0;
3074 virtual nsresult BeginVersionChange() = 0;
3076 virtual bool AreActorsAlive() = 0;
3078 virtual nsresult DispatchToWorkThread() = 0;
3080 // Should only be called by Run().
3081 virtual void SendResults() = 0;
3083 // Common nsIRunnable implementation that subclasses may not override.
3084 NS_IMETHOD
3085 Run() final;
3087 void DirectoryLockAcquired(DirectoryLock* aLock);
3089 void DirectoryLockFailed();
3091 virtual void SendBlockedNotification() = 0;
3093 private:
3094 // Test whether this FactoryOp needs to wait for the given op.
3095 bool MustWaitFor(const FactoryOp& aExistingOp);
3097 void AddBlockingOp(FactoryOp& aOp) {
3098 AssertIsOnOwningThread();
3100 mBlocking.AppendElement(WrapNotNull(&aOp));
3103 void AddBlockedOnOp(FactoryOp& aOp) {
3104 AssertIsOnOwningThread();
3106 mBlockedOn.AppendElement(WrapNotNull(&aOp));
3109 void MaybeUnblock(FactoryOp& aOp) {
3110 AssertIsOnOwningThread();
3112 mBlockedOn.RemoveElement(&aOp);
3113 if (mBlockedOn.IsEmpty()) {
3114 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
3119 class FactoryRequestOp : public FactoryOp,
3120 public PBackgroundIDBFactoryRequestParent {
3121 protected:
3122 const CommonFactoryRequestParams mCommonParams;
3124 FactoryRequestOp(SafeRefPtr<Factory> aFactory,
3125 const Maybe<ContentParentId>& aContentParentId,
3126 const CommonFactoryRequestParams& aCommonParams,
3127 bool aDeleting)
3128 : FactoryOp(std::move(aFactory), aContentParentId,
3129 aCommonParams.metadata().persistenceType(),
3130 aCommonParams.principalInfo(),
3131 Some(aCommonParams.metadata().name()), aDeleting),
3132 mCommonParams(aCommonParams) {}
3134 nsresult DoDirectoryWork() override;
3136 // IPDL methods.
3137 void ActorDestroy(ActorDestroyReason aWhy) override;
3140 class OpenDatabaseOp final : public FactoryRequestOp {
3141 friend class Database;
3142 friend class VersionChangeTransaction;
3144 class VersionChangeOp;
3146 SafeRefPtr<FullDatabaseMetadata> mMetadata;
3148 uint64_t mRequestedVersion;
3149 SafeRefPtr<DatabaseFileManager> mFileManager;
3151 SafeRefPtr<Database> mDatabase;
3152 SafeRefPtr<VersionChangeTransaction> mVersionChangeTransaction;
3154 // This is only set while a VersionChangeOp is live. It holds a strong
3155 // reference to its OpenDatabaseOp object so this is a weak pointer to avoid
3156 // cycles.
3157 VersionChangeOp* mVersionChangeOp;
3159 uint32_t mTelemetryId;
3161 public:
3162 OpenDatabaseOp(SafeRefPtr<Factory> aFactory,
3163 const Maybe<ContentParentId>& aContentParentId,
3164 const CommonFactoryRequestParams& aParams);
3166 private:
3167 ~OpenDatabaseOp() override { MOZ_ASSERT(!mVersionChangeOp); }
3169 nsresult LoadDatabaseInformation(mozIStorageConnection& aConnection);
3171 nsresult SendUpgradeNeeded();
3173 void EnsureDatabaseActor();
3175 nsresult EnsureDatabaseActorIsAlive();
3177 mozilla::Result<DatabaseSpec, nsresult> MetadataToSpec() const;
3179 void AssertMetadataConsistency(const FullDatabaseMetadata& aMetadata)
3180 #ifdef DEBUG
3182 #else
3185 #endif
3187 void ConnectionClosedCallback();
3189 void ActorDestroy(ActorDestroyReason aWhy) override;
3191 nsresult DatabaseOpen() override;
3193 nsresult DoDatabaseWork() override;
3195 nsresult BeginVersionChange() override;
3197 bool AreActorsAlive() override;
3199 void SendBlockedNotification() override;
3201 nsresult DispatchToWorkThread() override;
3203 void SendResults() override;
3205 static nsresult UpdateLocaleAwareIndex(mozIStorageConnection& aConnection,
3206 const IndexMetadata& aIndexMetadata,
3207 const nsCString& aLocale);
3210 class OpenDatabaseOp::VersionChangeOp final
3211 : public TransactionDatabaseOperationBase {
3212 friend class OpenDatabaseOp;
3214 RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
3215 const uint64_t mRequestedVersion;
3216 uint64_t mPreviousVersion;
3218 private:
3219 explicit VersionChangeOp(OpenDatabaseOp* aOpenDatabaseOp)
3220 : TransactionDatabaseOperationBase(
3221 aOpenDatabaseOp->mVersionChangeTransaction.clonePtr(),
3222 /* aRequestId */ 0, aOpenDatabaseOp->LoggingSerialNumber()),
3223 mOpenDatabaseOp(aOpenDatabaseOp),
3224 mRequestedVersion(aOpenDatabaseOp->mRequestedVersion),
3225 mPreviousVersion(
3226 aOpenDatabaseOp->mMetadata->mCommonMetadata.version()) {
3227 MOZ_ASSERT(aOpenDatabaseOp);
3228 MOZ_ASSERT(mRequestedVersion);
3231 ~VersionChangeOp() override { MOZ_ASSERT(!mOpenDatabaseOp); }
3233 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3235 nsresult SendSuccessResult() override;
3237 bool SendFailureResult(nsresult aResultCode) override;
3239 void Cleanup() override;
3242 class DeleteDatabaseOp final : public FactoryRequestOp {
3243 class VersionChangeOp;
3245 nsString mDatabaseDirectoryPath;
3246 nsString mDatabaseFilenameBase;
3247 uint64_t mPreviousVersion;
3249 public:
3250 DeleteDatabaseOp(SafeRefPtr<Factory> aFactory,
3251 const Maybe<ContentParentId>& aContentParentId,
3252 const CommonFactoryRequestParams& aParams)
3253 : FactoryRequestOp(std::move(aFactory), aContentParentId, aParams,
3254 /* aDeleting */ true),
3255 mPreviousVersion(0) {}
3257 private:
3258 ~DeleteDatabaseOp() override = default;
3260 void LoadPreviousVersion(nsIFile& aDatabaseFile);
3262 nsresult DatabaseOpen() override;
3264 nsresult DoDatabaseWork() override;
3266 nsresult BeginVersionChange() override;
3268 bool AreActorsAlive() override;
3270 void SendBlockedNotification() override;
3272 nsresult DispatchToWorkThread() override;
3274 void SendResults() override;
3277 class DeleteDatabaseOp::VersionChangeOp final : public DatabaseOperationBase {
3278 friend class DeleteDatabaseOp;
3280 RefPtr<DeleteDatabaseOp> mDeleteDatabaseOp;
3282 private:
3283 explicit VersionChangeOp(DeleteDatabaseOp* aDeleteDatabaseOp)
3284 : DatabaseOperationBase(aDeleteDatabaseOp->BackgroundChildLoggingId(),
3285 aDeleteDatabaseOp->LoggingSerialNumber()),
3286 mDeleteDatabaseOp(aDeleteDatabaseOp) {
3287 MOZ_ASSERT(aDeleteDatabaseOp);
3288 MOZ_ASSERT(!aDeleteDatabaseOp->mDatabaseDirectoryPath.IsEmpty());
3291 ~VersionChangeOp() override = default;
3293 nsresult RunOnIOThread();
3295 void RunOnOwningThread();
3297 NS_DECL_NSIRUNNABLE
3300 class GetDatabasesOp final : public FactoryOp {
3301 nsTHashMap<nsStringHashKey, DatabaseMetadata> mDatabaseMetadataTable;
3302 nsTArray<DatabaseMetadata> mDatabaseMetadataArray;
3303 Factory::GetDatabasesResolver mResolver;
3305 public:
3306 GetDatabasesOp(SafeRefPtr<Factory> aFactory,
3307 const Maybe<ContentParentId>& aContentParentId,
3308 const PersistenceType aPersistenceType,
3309 const PrincipalInfo& aPrincipalInfo,
3310 Factory::GetDatabasesResolver&& aResolver)
3311 : FactoryOp(std::move(aFactory), aContentParentId, aPersistenceType,
3312 aPrincipalInfo, Nothing(), /* aDeleting */ false),
3313 mResolver(std::move(aResolver)) {}
3315 private:
3316 ~GetDatabasesOp() override = default;
3318 nsresult DatabasesNotAvailable();
3320 nsresult DoDirectoryWork() override;
3322 nsresult DatabaseOpen() override;
3324 nsresult DoDatabaseWork() override;
3326 nsresult BeginVersionChange() override;
3328 bool AreActorsAlive() override;
3330 void SendBlockedNotification() override;
3332 nsresult DispatchToWorkThread() override;
3334 void SendResults() override;
3337 class VersionChangeTransactionOp : public TransactionDatabaseOperationBase {
3338 public:
3339 void Cleanup() override;
3341 protected:
3342 explicit VersionChangeTransactionOp(
3343 SafeRefPtr<VersionChangeTransaction> aTransaction)
3344 : TransactionDatabaseOperationBase(std::move(aTransaction),
3345 /* aRequestId */ 0) {}
3347 ~VersionChangeTransactionOp() override = default;
3349 private:
3350 nsresult SendSuccessResult() override;
3352 bool SendFailureResult(nsresult aResultCode) override;
3355 class CreateObjectStoreOp final : public VersionChangeTransactionOp {
3356 friend class VersionChangeTransaction;
3358 const ObjectStoreMetadata mMetadata;
3360 private:
3361 // Only created by VersionChangeTransaction.
3362 CreateObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3363 const ObjectStoreMetadata& aMetadata)
3364 : VersionChangeTransactionOp(std::move(aTransaction)),
3365 mMetadata(aMetadata) {
3366 MOZ_ASSERT(aMetadata.id());
3369 ~CreateObjectStoreOp() override = default;
3371 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3374 class DeleteObjectStoreOp final : public VersionChangeTransactionOp {
3375 friend class VersionChangeTransaction;
3377 const SafeRefPtr<FullObjectStoreMetadata> mMetadata;
3378 const bool mIsLastObjectStore;
3380 private:
3381 // Only created by VersionChangeTransaction.
3382 DeleteObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3383 SafeRefPtr<FullObjectStoreMetadata> aMetadata,
3384 const bool aIsLastObjectStore)
3385 : VersionChangeTransactionOp(std::move(aTransaction)),
3386 mMetadata(std::move(aMetadata)),
3387 mIsLastObjectStore(aIsLastObjectStore) {
3388 MOZ_ASSERT(mMetadata->mCommonMetadata.id());
3391 ~DeleteObjectStoreOp() override = default;
3393 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3396 class RenameObjectStoreOp final : public VersionChangeTransactionOp {
3397 friend class VersionChangeTransaction;
3399 const int64_t mId;
3400 const nsString mNewName;
3402 private:
3403 // Only created by VersionChangeTransaction.
3404 RenameObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3405 FullObjectStoreMetadata& aMetadata)
3406 : VersionChangeTransactionOp(std::move(aTransaction)),
3407 mId(aMetadata.mCommonMetadata.id()),
3408 mNewName(aMetadata.mCommonMetadata.name()) {
3409 MOZ_ASSERT(mId);
3412 ~RenameObjectStoreOp() override = default;
3414 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3417 class CreateIndexOp final : public VersionChangeTransactionOp {
3418 friend class VersionChangeTransaction;
3420 class UpdateIndexDataValuesFunction;
3422 const IndexMetadata mMetadata;
3423 Maybe<UniqueIndexTable> mMaybeUniqueIndexTable;
3424 const SafeRefPtr<DatabaseFileManager> mFileManager;
3425 const nsCString mDatabaseId;
3426 const IndexOrObjectStoreId mObjectStoreId;
3428 private:
3429 // Only created by VersionChangeTransaction.
3430 CreateIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3431 IndexOrObjectStoreId aObjectStoreId,
3432 const IndexMetadata& aMetadata);
3434 ~CreateIndexOp() override = default;
3436 nsresult InsertDataFromObjectStore(DatabaseConnection* aConnection);
3438 nsresult InsertDataFromObjectStoreInternal(
3439 DatabaseConnection* aConnection) const;
3441 bool Init(TransactionBase& aTransaction) override;
3443 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3446 class CreateIndexOp::UpdateIndexDataValuesFunction final
3447 : public mozIStorageFunction {
3448 RefPtr<CreateIndexOp> mOp;
3449 RefPtr<DatabaseConnection> mConnection;
3450 const NotNull<SafeRefPtr<Database>> mDatabase;
3452 public:
3453 UpdateIndexDataValuesFunction(CreateIndexOp* aOp,
3454 DatabaseConnection* aConnection,
3455 SafeRefPtr<Database> aDatabase)
3456 : mOp(aOp),
3457 mConnection(aConnection),
3458 mDatabase(WrapNotNull(std::move(aDatabase))) {
3459 MOZ_ASSERT(aOp);
3460 MOZ_ASSERT(aConnection);
3461 aConnection->AssertIsOnConnectionThread();
3464 NS_DECL_ISUPPORTS
3466 private:
3467 ~UpdateIndexDataValuesFunction() = default;
3469 NS_DECL_MOZISTORAGEFUNCTION
3472 class DeleteIndexOp final : public VersionChangeTransactionOp {
3473 friend class VersionChangeTransaction;
3475 const IndexOrObjectStoreId mObjectStoreId;
3476 const IndexOrObjectStoreId mIndexId;
3477 const bool mUnique;
3478 const bool mIsLastIndex;
3480 private:
3481 // Only created by VersionChangeTransaction.
3482 DeleteIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3483 IndexOrObjectStoreId aObjectStoreId,
3484 IndexOrObjectStoreId aIndexId, const bool aUnique,
3485 const bool aIsLastIndex);
3487 ~DeleteIndexOp() override = default;
3489 nsresult RemoveReferencesToIndex(
3490 DatabaseConnection* aConnection, const Key& aObjectDataKey,
3491 nsTArray<IndexDataValue>& aIndexValues) const;
3493 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3496 class RenameIndexOp final : public VersionChangeTransactionOp {
3497 friend class VersionChangeTransaction;
3499 const IndexOrObjectStoreId mObjectStoreId;
3500 const IndexOrObjectStoreId mIndexId;
3501 const nsString mNewName;
3503 private:
3504 // Only created by VersionChangeTransaction.
3505 RenameIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
3506 FullIndexMetadata& aMetadata,
3507 IndexOrObjectStoreId aObjectStoreId)
3508 : VersionChangeTransactionOp(std::move(aTransaction)),
3509 mObjectStoreId(aObjectStoreId),
3510 mIndexId(aMetadata.mCommonMetadata.id()),
3511 mNewName(aMetadata.mCommonMetadata.name()) {
3512 MOZ_ASSERT(mIndexId);
3515 ~RenameIndexOp() override = default;
3517 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3520 class NormalTransactionOp : public TransactionDatabaseOperationBase,
3521 public PBackgroundIDBRequestParent {
3522 #ifdef DEBUG
3523 bool mResponseSent;
3524 #endif
3526 public:
3527 void Cleanup() override;
3529 protected:
3530 NormalTransactionOp(SafeRefPtr<TransactionBase> aTransaction,
3531 const int64_t aRequestId)
3532 : TransactionDatabaseOperationBase(std::move(aTransaction), aRequestId)
3533 #ifdef DEBUG
3535 mResponseSent(false)
3536 #endif
3540 ~NormalTransactionOp() override = default;
3542 // An overload of DatabaseOperationBase's function that can avoid doing extra
3543 // work on non-versionchange transactions.
3544 mozilla::Result<bool, nsresult> ObjectStoreHasIndexes(
3545 DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId,
3546 bool aMayHaveIndexes);
3548 virtual mozilla::Result<PreprocessParams, nsresult> GetPreprocessParams();
3550 // Subclasses use this override to set the IPDL response value.
3551 virtual void GetResponse(RequestResponse& aResponse,
3552 size_t* aResponseSize) = 0;
3554 private:
3555 nsresult SendPreprocessInfo() override;
3557 nsresult SendSuccessResult() override;
3559 bool SendFailureResult(nsresult aResultCode) override;
3561 // IPDL methods.
3562 void ActorDestroy(ActorDestroyReason aWhy) override;
3564 mozilla::ipc::IPCResult RecvContinue(
3565 const PreprocessResponse& aResponse) final;
3568 class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp {
3569 friend class TransactionBase;
3571 using PersistenceType = mozilla::dom::quota::PersistenceType;
3573 class StoredFileInfo final {
3574 InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileInfo>>> mFileInfo;
3575 // Either nothing, a file actor or a non-Blob-backed inputstream to write to
3576 // disk.
3577 using FileActorOrInputStream =
3578 Variant<Nothing, RefPtr<DatabaseFile>, nsCOMPtr<nsIInputStream>>;
3579 InitializedOnce<const FileActorOrInputStream> mFileActorOrInputStream;
3580 #ifdef DEBUG
3581 const StructuredCloneFileBase::FileType mType;
3582 #endif
3583 void EnsureCipherKey();
3584 void AssertInvariants() const;
3586 StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo,
3587 RefPtr<DatabaseFile> aFileActor);
3589 StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo,
3590 nsCOMPtr<nsIInputStream> aInputStream);
3592 public:
3593 #if defined(NS_BUILD_REFCNT_LOGGING)
3594 // Only for MOZ_COUNT_CTOR.
3595 StoredFileInfo(StoredFileInfo&& aOther)
3596 : mFileInfo{std::move(aOther.mFileInfo)},
3597 mFileActorOrInputStream{std::move(aOther.mFileActorOrInputStream)}
3598 # ifdef DEBUG
3600 mType{aOther.mType}
3601 # endif
3603 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
3605 #else
3606 StoredFileInfo(StoredFileInfo&&) = default;
3607 #endif
3609 static StoredFileInfo CreateForBlob(SafeRefPtr<DatabaseFileInfo> aFileInfo,
3610 RefPtr<DatabaseFile> aFileActor);
3611 static StoredFileInfo CreateForStructuredClone(
3612 SafeRefPtr<DatabaseFileInfo> aFileInfo,
3613 nsCOMPtr<nsIInputStream> aInputStream);
3615 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
3616 ~StoredFileInfo() {
3617 AssertIsOnBackgroundThread();
3618 AssertInvariants();
3620 MOZ_COUNT_DTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
3622 #endif
3624 bool IsValid() const { return static_cast<bool>(mFileInfo); }
3626 const DatabaseFileInfo& GetFileInfo() const { return **mFileInfo; }
3628 bool ShouldCompress() const;
3630 void NotifyWriteSucceeded() const;
3632 using InputStreamResult =
3633 mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult>;
3634 InputStreamResult GetInputStream();
3636 void Serialize(nsString& aText) const;
3638 class SCInputStream;
3640 ObjectStoreAddPutParams mParams;
3641 Maybe<UniqueIndexTable> mUniqueIndexTable;
3643 // This must be non-const so that we can update the mNextAutoIncrementId field
3644 // if we are modifying an autoIncrement objectStore.
3645 SafeRefPtr<FullObjectStoreMetadata> mMetadata;
3647 nsTArray<StoredFileInfo> mStoredFileInfos;
3649 Key mResponse;
3650 const OriginMetadata mOriginMetadata;
3651 const PersistenceType mPersistenceType;
3652 const bool mOverwrite;
3653 bool mObjectStoreMayHaveIndexes;
3654 bool mDataOverThreshold;
3656 private:
3657 // Only created by TransactionBase.
3658 ObjectStoreAddOrPutRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3659 const int64_t aRequestId,
3660 RequestParams&& aParams);
3662 ~ObjectStoreAddOrPutRequestOp() override = default;
3664 nsresult RemoveOldIndexDataValues(DatabaseConnection* aConnection);
3666 bool Init(TransactionBase& aTransaction) override;
3668 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3670 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
3672 void Cleanup() override;
3675 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::AssertInvariants() const {
3676 // The only allowed types are eStructuredClone, eBlob and eMutableFile.
3677 MOZ_ASSERT(StructuredCloneFileBase::eStructuredClone == mType ||
3678 StructuredCloneFileBase::eBlob == mType ||
3679 StructuredCloneFileBase::eMutableFile == mType);
3681 // mFileInfo and a file actor in mFileActorOrInputStream are present until
3682 // the object is moved away, but an inputStream in mFileActorOrInputStream
3683 // can be released early.
3684 MOZ_ASSERT_IF(static_cast<bool>(mFileActorOrInputStream) &&
3685 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>(),
3686 static_cast<bool>(mFileInfo));
3688 if (mFileInfo) {
3689 // In a non-moved StoredFileInfo, one of the following is true:
3690 // - This was an overflow structured clone (eStructuredClone) and
3691 // storedFileInfo.mFileActorOrInputStream CAN be a non-nullptr input
3692 // stream (but that might have been release by ReleaseInputStream).
3693 MOZ_ASSERT_IF(
3694 StructuredCloneFileBase::eStructuredClone == mType,
3695 !mFileActorOrInputStream ||
3696 (mFileActorOrInputStream->is<nsCOMPtr<nsIInputStream>>() &&
3697 mFileActorOrInputStream->as<nsCOMPtr<nsIInputStream>>()));
3699 // - This is a reference to a Blob (eBlob) that may or may not have
3700 // already been written to disk. storedFileInfo.mFileActorOrInputStream
3701 // MUST be a non-null file actor, but its GetInputStream may return
3702 // nullptr (so don't assert on that).
3703 MOZ_ASSERT_IF(StructuredCloneFileBase::eBlob == mType,
3704 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>() &&
3705 mFileActorOrInputStream->as<RefPtr<DatabaseFile>>());
3707 // - It's a mutable file (eMutableFile). No writing will be performed,
3708 // and storedFileInfo.mFileActorOrInputStream is Nothing.
3709 MOZ_ASSERT_IF(StructuredCloneFileBase::eMutableFile == mType,
3710 mFileActorOrInputStream->is<Nothing>());
3714 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::EnsureCipherKey() {
3715 const auto& fileInfo = GetFileInfo();
3716 const auto& fileManager = fileInfo.Manager();
3718 // No need to generate cipher keys if we are not in PBM
3719 if (!fileManager.IsInPrivateBrowsingMode()) {
3720 return;
3723 nsCString keyId;
3724 keyId.AppendInt(fileInfo.Id());
3726 fileManager.MutableCipherKeyManagerRef().Ensure(keyId);
3729 ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
3730 SafeRefPtr<DatabaseFileInfo> aFileInfo, RefPtr<DatabaseFile> aFileActor)
3731 : mFileInfo{WrapNotNull(std::move(aFileInfo))},
3732 mFileActorOrInputStream{std::move(aFileActor)}
3733 #ifdef DEBUG
3735 mType{StructuredCloneFileBase::eBlob}
3736 #endif
3738 AssertIsOnBackgroundThread();
3739 AssertInvariants();
3741 EnsureCipherKey();
3742 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
3745 ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
3746 SafeRefPtr<DatabaseFileInfo> aFileInfo,
3747 nsCOMPtr<nsIInputStream> aInputStream)
3748 : mFileInfo{WrapNotNull(std::move(aFileInfo))},
3749 mFileActorOrInputStream{std::move(aInputStream)}
3750 #ifdef DEBUG
3752 mType{StructuredCloneFileBase::eStructuredClone}
3753 #endif
3755 AssertIsOnBackgroundThread();
3756 AssertInvariants();
3758 EnsureCipherKey();
3759 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
3762 ObjectStoreAddOrPutRequestOp::StoredFileInfo
3763 ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForBlob(
3764 SafeRefPtr<DatabaseFileInfo> aFileInfo, RefPtr<DatabaseFile> aFileActor) {
3765 return {std::move(aFileInfo), std::move(aFileActor)};
3768 ObjectStoreAddOrPutRequestOp::StoredFileInfo
3769 ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForStructuredClone(
3770 SafeRefPtr<DatabaseFileInfo> aFileInfo,
3771 nsCOMPtr<nsIInputStream> aInputStream) {
3772 return {std::move(aFileInfo), std::move(aInputStream)};
3775 bool ObjectStoreAddOrPutRequestOp::StoredFileInfo::ShouldCompress() const {
3776 // Must not be called after moving.
3777 MOZ_ASSERT(IsValid());
3779 // Compression is only necessary for eStructuredClone, i.e. when
3780 // mFileActorOrInputStream stored an input stream. However, this is only
3781 // called after GetInputStream, when mFileActorOrInputStream has been
3782 // cleared, which is only possible for this type.
3783 const bool res = !mFileActorOrInputStream;
3784 MOZ_ASSERT(res == (StructuredCloneFileBase::eStructuredClone == mType));
3785 return res;
3788 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::NotifyWriteSucceeded()
3789 const {
3790 MOZ_ASSERT(IsValid());
3792 // For eBlob, clear the blob implementation.
3793 if (mFileActorOrInputStream &&
3794 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>()) {
3795 mFileActorOrInputStream->as<RefPtr<DatabaseFile>>()
3796 ->WriteSucceededClearBlobImpl();
3799 // For the other types, no action is necessary.
3802 ObjectStoreAddOrPutRequestOp::StoredFileInfo::InputStreamResult
3803 ObjectStoreAddOrPutRequestOp::StoredFileInfo::GetInputStream() {
3804 if (!mFileActorOrInputStream) {
3805 MOZ_ASSERT(StructuredCloneFileBase::eStructuredClone == mType);
3806 return nsCOMPtr<nsIInputStream>{};
3809 // For the different cases, see also the comments in AssertInvariants.
3810 return mFileActorOrInputStream->match(
3811 [](const Nothing&) -> InputStreamResult {
3812 return nsCOMPtr<nsIInputStream>{};
3814 [](const RefPtr<DatabaseFile>& databaseActor) -> InputStreamResult {
3815 ErrorResult rv;
3816 auto inputStream = databaseActor->GetInputStream(rv);
3817 if (NS_WARN_IF(rv.Failed())) {
3818 return Err(rv.StealNSResult());
3821 return inputStream;
3823 [this](const nsCOMPtr<nsIInputStream>& inputStream) -> InputStreamResult {
3824 auto res = inputStream;
3825 // destroy() clears the inputStream parameter, so we needed to make a
3826 // copy before
3827 mFileActorOrInputStream.destroy();
3828 AssertInvariants();
3829 return res;
3833 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::Serialize(
3834 nsString& aText) const {
3835 AssertInvariants();
3836 MOZ_ASSERT(IsValid());
3838 const int64_t id = (*mFileInfo)->Id();
3840 auto structuredCloneHandler = [&aText, id](const nsCOMPtr<nsIInputStream>&) {
3841 // eStructuredClone
3842 aText.Append('.');
3843 aText.AppendInt(id);
3846 // If mFileActorOrInputStream was moved, we had an inputStream before.
3847 if (!mFileActorOrInputStream) {
3848 structuredCloneHandler(nullptr);
3849 return;
3852 // This encoding is parsed in DeserializeStructuredCloneFile.
3853 mFileActorOrInputStream->match(
3854 [&aText, id](const Nothing&) {
3855 // eMutableFile
3856 aText.AppendInt(-id);
3858 [&aText, id](const RefPtr<DatabaseFile>&) {
3859 // eBlob
3860 aText.AppendInt(id);
3862 structuredCloneHandler);
3865 class ObjectStoreAddOrPutRequestOp::SCInputStream final
3866 : public nsIInputStream {
3867 const JSStructuredCloneData& mData;
3868 JSStructuredCloneData::Iterator mIter;
3870 public:
3871 explicit SCInputStream(const JSStructuredCloneData& aData)
3872 : mData(aData), mIter(aData.Start()) {}
3874 private:
3875 virtual ~SCInputStream() = default;
3877 NS_DECL_THREADSAFE_ISUPPORTS
3878 NS_DECL_NSIINPUTSTREAM
3881 class ObjectStoreGetRequestOp final : public NormalTransactionOp {
3882 friend class TransactionBase;
3884 const IndexOrObjectStoreId mObjectStoreId;
3885 SafeRefPtr<Database> mDatabase;
3886 const Maybe<SerializedKeyRange> mOptionalKeyRange;
3887 AutoTArray<StructuredCloneReadInfoParent, 1> mResponse;
3888 PBackgroundParent* mBackgroundParent;
3889 uint32_t mPreprocessInfoCount;
3890 const uint32_t mLimit;
3891 const bool mGetAll;
3893 private:
3894 // Only created by TransactionBase.
3895 ObjectStoreGetRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3896 const int64_t aRequestId,
3897 const RequestParams& aParams, bool aGetAll);
3899 ~ObjectStoreGetRequestOp() override = default;
3901 template <typename T>
3902 mozilla::Result<T, nsresult> ConvertResponse(
3903 StructuredCloneReadInfoParent&& aInfo);
3905 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3907 bool HasPreprocessInfo() override;
3909 mozilla::Result<PreprocessParams, nsresult> GetPreprocessParams() override;
3911 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
3914 class ObjectStoreGetKeyRequestOp final : public NormalTransactionOp {
3915 friend class TransactionBase;
3917 const IndexOrObjectStoreId mObjectStoreId;
3918 const Maybe<SerializedKeyRange> mOptionalKeyRange;
3919 const uint32_t mLimit;
3920 const bool mGetAll;
3921 nsTArray<Key> mResponse;
3923 private:
3924 // Only created by TransactionBase.
3925 ObjectStoreGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3926 const int64_t aRequestId,
3927 const RequestParams& aParams, bool aGetAll);
3929 ~ObjectStoreGetKeyRequestOp() override = default;
3931 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3933 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
3936 class ObjectStoreDeleteRequestOp final : public NormalTransactionOp {
3937 friend class TransactionBase;
3939 const ObjectStoreDeleteParams mParams;
3940 ObjectStoreDeleteResponse mResponse;
3941 bool mObjectStoreMayHaveIndexes;
3943 private:
3944 ObjectStoreDeleteRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3945 const int64_t aRequestId,
3946 const ObjectStoreDeleteParams& aParams);
3948 ~ObjectStoreDeleteRequestOp() override = default;
3950 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3952 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
3953 aResponse = std::move(mResponse);
3954 *aResponseSize = 0;
3958 class ObjectStoreClearRequestOp final : public NormalTransactionOp {
3959 friend class TransactionBase;
3961 const ObjectStoreClearParams mParams;
3962 ObjectStoreClearResponse mResponse;
3963 bool mObjectStoreMayHaveIndexes;
3965 private:
3966 ObjectStoreClearRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3967 const int64_t aRequestId,
3968 const ObjectStoreClearParams& aParams);
3970 ~ObjectStoreClearRequestOp() override = default;
3972 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3974 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
3975 aResponse = std::move(mResponse);
3976 *aResponseSize = 0;
3980 class ObjectStoreCountRequestOp final : public NormalTransactionOp {
3981 friend class TransactionBase;
3983 const ObjectStoreCountParams mParams;
3984 ObjectStoreCountResponse mResponse;
3986 private:
3987 ObjectStoreCountRequestOp(SafeRefPtr<TransactionBase> aTransaction,
3988 const int64_t aRequestId,
3989 const ObjectStoreCountParams& aParams)
3990 : NormalTransactionOp(std::move(aTransaction), aRequestId),
3991 mParams(aParams) {}
3993 ~ObjectStoreCountRequestOp() override = default;
3995 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
3997 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
3998 aResponse = std::move(mResponse);
3999 *aResponseSize = sizeof(uint64_t);
4003 class IndexRequestOpBase : public NormalTransactionOp {
4004 protected:
4005 const SafeRefPtr<FullIndexMetadata> mMetadata;
4007 protected:
4008 IndexRequestOpBase(SafeRefPtr<TransactionBase> aTransaction,
4009 const int64_t aRequestId, const RequestParams& aParams)
4010 : NormalTransactionOp(std::move(aTransaction), aRequestId),
4011 mMetadata(IndexMetadataForParams(Transaction(), aParams)) {}
4013 ~IndexRequestOpBase() override = default;
4015 private:
4016 static SafeRefPtr<FullIndexMetadata> IndexMetadataForParams(
4017 const TransactionBase& aTransaction, const RequestParams& aParams);
4020 class IndexGetRequestOp final : public IndexRequestOpBase {
4021 friend class TransactionBase;
4023 SafeRefPtr<Database> mDatabase;
4024 const Maybe<SerializedKeyRange> mOptionalKeyRange;
4025 AutoTArray<StructuredCloneReadInfoParent, 1> mResponse;
4026 PBackgroundParent* mBackgroundParent;
4027 const uint32_t mLimit;
4028 const bool mGetAll;
4030 private:
4031 // Only created by TransactionBase.
4032 IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4033 const int64_t aRequestId, const RequestParams& aParams,
4034 bool aGetAll);
4036 ~IndexGetRequestOp() override = default;
4038 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4040 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
4043 class IndexGetKeyRequestOp final : public IndexRequestOpBase {
4044 friend class TransactionBase;
4046 const Maybe<SerializedKeyRange> mOptionalKeyRange;
4047 AutoTArray<Key, 1> mResponse;
4048 const uint32_t mLimit;
4049 const bool mGetAll;
4051 private:
4052 // Only created by TransactionBase.
4053 IndexGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4054 const int64_t aRequestId, const RequestParams& aParams,
4055 bool aGetAll);
4057 ~IndexGetKeyRequestOp() override = default;
4059 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4061 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override;
4064 class IndexCountRequestOp final : public IndexRequestOpBase {
4065 friend class TransactionBase;
4067 const IndexCountParams mParams;
4068 IndexCountResponse mResponse;
4070 private:
4071 // Only created by TransactionBase.
4072 IndexCountRequestOp(SafeRefPtr<TransactionBase> aTransaction,
4073 const int64_t aRequestId, const RequestParams& aParams)
4074 : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams),
4075 mParams(aParams.get_IndexCountParams()) {}
4077 ~IndexCountRequestOp() override = default;
4079 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4081 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override {
4082 aResponse = std::move(mResponse);
4083 *aResponseSize = sizeof(uint64_t);
4087 template <IDBCursorType CursorType>
4088 class Cursor;
4090 constexpr IDBCursorType ToKeyOnlyType(const IDBCursorType aType) {
4091 MOZ_ASSERT(aType == IDBCursorType::ObjectStore ||
4092 aType == IDBCursorType::ObjectStoreKey ||
4093 aType == IDBCursorType::Index || aType == IDBCursorType::IndexKey);
4094 switch (aType) {
4095 case IDBCursorType::ObjectStore:
4096 [[fallthrough]];
4097 case IDBCursorType::ObjectStoreKey:
4098 return IDBCursorType::ObjectStoreKey;
4099 case IDBCursorType::Index:
4100 [[fallthrough]];
4101 case IDBCursorType::IndexKey:
4102 return IDBCursorType::IndexKey;
4106 template <IDBCursorType CursorType>
4107 using CursorPosition = CursorData<ToKeyOnlyType(CursorType)>;
4109 #ifdef DEBUG
4110 constexpr indexedDB::OpenCursorParams::Type ToOpenCursorParamsType(
4111 const IDBCursorType aType) {
4112 MOZ_ASSERT(aType == IDBCursorType::ObjectStore ||
4113 aType == IDBCursorType::ObjectStoreKey ||
4114 aType == IDBCursorType::Index || aType == IDBCursorType::IndexKey);
4115 switch (aType) {
4116 case IDBCursorType::ObjectStore:
4117 return indexedDB::OpenCursorParams::TObjectStoreOpenCursorParams;
4118 case IDBCursorType::ObjectStoreKey:
4119 return indexedDB::OpenCursorParams::TObjectStoreOpenKeyCursorParams;
4120 case IDBCursorType::Index:
4121 return indexedDB::OpenCursorParams::TIndexOpenCursorParams;
4122 case IDBCursorType::IndexKey:
4123 return indexedDB::OpenCursorParams::TIndexOpenKeyCursorParams;
4126 #endif
4128 class CursorBase : public PBackgroundIDBCursorParent {
4129 friend class TransactionBase;
4130 template <IDBCursorType CursorType>
4131 friend class CommonOpenOpHelper;
4133 protected:
4134 const SafeRefPtr<TransactionBase> mTransaction;
4136 // This should only be touched on the PBackground thread to check whether
4137 // the objectStore has been deleted. Holding these saves a hash lookup for
4138 // every call to continue()/advance().
4139 InitializedOnce<const NotNull<SafeRefPtr<FullObjectStoreMetadata>>>
4140 mObjectStoreMetadata;
4142 const IndexOrObjectStoreId mObjectStoreId;
4144 LazyInitializedOnce<const Key>
4145 mLocaleAwareRangeBound; ///< If the cursor is based on a key range, the
4146 ///< bound in the direction of iteration (e.g.
4147 ///< the upper bound in case of mDirection ==
4148 ///< NEXT). If the cursor is based on a key, it
4149 ///< is unset. If mLocale is set, this was
4150 ///< converted to mLocale.
4152 const Direction mDirection;
4154 const int32_t mMaxExtraCount;
4156 const bool mIsSameProcessActor;
4158 struct ConstructFromTransactionBase {};
4160 public:
4161 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::CursorBase,
4162 final)
4164 CursorBase(SafeRefPtr<TransactionBase> aTransaction,
4165 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
4166 Direction aDirection,
4167 ConstructFromTransactionBase aConstructionTag);
4169 protected:
4170 // Reference counted.
4171 ~CursorBase() override { MOZ_ASSERT(!mObjectStoreMetadata); }
4173 private:
4174 virtual bool Start(const int64_t aRequestId,
4175 const OpenCursorParams& aParams) = 0;
4178 class IndexCursorBase : public CursorBase {
4179 public:
4180 bool IsLocaleAware() const { return !mLocale.IsEmpty(); }
4182 IndexCursorBase(SafeRefPtr<TransactionBase> aTransaction,
4183 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
4184 SafeRefPtr<FullIndexMetadata> aIndexMetadata,
4185 Direction aDirection,
4186 ConstructFromTransactionBase aConstructionTag)
4187 : CursorBase{std::move(aTransaction), std::move(aObjectStoreMetadata),
4188 aDirection, aConstructionTag},
4189 mIndexMetadata(WrapNotNull(std::move(aIndexMetadata))),
4190 mIndexId((*mIndexMetadata)->mCommonMetadata.id()),
4191 mUniqueIndex((*mIndexMetadata)->mCommonMetadata.unique()),
4192 mLocale((*mIndexMetadata)->mCommonMetadata.locale()) {}
4194 protected:
4195 IndexOrObjectStoreId Id() const { return mIndexId; }
4197 // This should only be touched on the PBackground thread to check whether
4198 // the index has been deleted. Holding these saves a hash lookup for every
4199 // call to continue()/advance().
4200 InitializedOnce<const NotNull<SafeRefPtr<FullIndexMetadata>>> mIndexMetadata;
4201 const IndexOrObjectStoreId mIndexId;
4202 const bool mUniqueIndex;
4203 const nsCString
4204 mLocale; ///< The locale if the cursor is locale-aware, otherwise empty.
4206 struct ContinueQueries {
4207 nsCString mContinueQuery;
4208 nsCString mContinueToQuery;
4209 nsCString mContinuePrimaryKeyQuery;
4211 const nsACString& GetContinueQuery(const bool hasContinueKey,
4212 const bool hasContinuePrimaryKey) const {
4213 return hasContinuePrimaryKey ? mContinuePrimaryKeyQuery
4214 : hasContinueKey ? mContinueToQuery
4215 : mContinueQuery;
4220 class ObjectStoreCursorBase : public CursorBase {
4221 public:
4222 using CursorBase::CursorBase;
4224 static constexpr bool IsLocaleAware() { return false; }
4226 protected:
4227 IndexOrObjectStoreId Id() const { return mObjectStoreId; }
4229 struct ContinueQueries {
4230 nsCString mContinueQuery;
4231 nsCString mContinueToQuery;
4233 const nsACString& GetContinueQuery(const bool hasContinueKey,
4234 const bool hasContinuePrimaryKey) const {
4235 MOZ_ASSERT(!hasContinuePrimaryKey);
4236 return hasContinueKey ? mContinueToQuery : mContinueQuery;
4241 using FilesArray = nsTArray<nsTArray<StructuredCloneFileParent>>;
4243 struct PseudoFilesArray {
4244 static constexpr bool IsEmpty() { return true; }
4246 static constexpr void Clear() {}
4249 template <IDBCursorType CursorType>
4250 using FilesArrayT =
4251 std::conditional_t<!CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
4252 FilesArray, PseudoFilesArray>;
4254 class ValueCursorBase {
4255 friend struct ValuePopulateResponseHelper<true>;
4256 friend struct ValuePopulateResponseHelper<false>;
4258 protected:
4259 explicit ValueCursorBase(TransactionBase* const aTransaction)
4260 : mDatabase(aTransaction->GetDatabasePtr()),
4261 mFileManager(mDatabase->GetFileManagerPtr()),
4262 mBackgroundParent(WrapNotNull(aTransaction->GetBackgroundParent())) {
4263 MOZ_ASSERT(mDatabase);
4266 void ProcessFiles(CursorResponse& aResponse, const FilesArray& aFiles);
4268 ~ValueCursorBase() { MOZ_ASSERT(!mBackgroundParent); }
4270 const SafeRefPtr<Database> mDatabase;
4271 const NotNull<SafeRefPtr<DatabaseFileManager>> mFileManager;
4273 InitializedOnce<const NotNull<PBackgroundParent*>> mBackgroundParent;
4276 class KeyCursorBase {
4277 protected:
4278 explicit KeyCursorBase(TransactionBase* const /*aTransaction*/) {}
4280 static constexpr void ProcessFiles(CursorResponse& aResponse,
4281 const PseudoFilesArray& aFiles) {}
4284 template <IDBCursorType CursorType>
4285 class CursorOpBaseHelperBase;
4287 template <IDBCursorType CursorType>
4288 class Cursor final
4289 : public std::conditional_t<
4290 CursorTypeTraits<CursorType>::IsObjectStoreCursor,
4291 ObjectStoreCursorBase, IndexCursorBase>,
4292 public std::conditional_t<CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
4293 KeyCursorBase, ValueCursorBase> {
4294 using Base =
4295 std::conditional_t<CursorTypeTraits<CursorType>::IsObjectStoreCursor,
4296 ObjectStoreCursorBase, IndexCursorBase>;
4298 using KeyValueBase =
4299 std::conditional_t<CursorTypeTraits<CursorType>::IsKeyOnlyCursor,
4300 KeyCursorBase, ValueCursorBase>;
4302 static constexpr bool IsIndexCursor =
4303 !CursorTypeTraits<CursorType>::IsObjectStoreCursor;
4305 static constexpr bool IsValueCursor =
4306 !CursorTypeTraits<CursorType>::IsKeyOnlyCursor;
4308 class CursorOpBase;
4309 class OpenOp;
4310 class ContinueOp;
4312 using Base::Id;
4313 using CursorBase::Manager;
4314 using CursorBase::mDirection;
4315 using CursorBase::mObjectStoreId;
4316 using CursorBase::mTransaction;
4317 using typename CursorBase::ActorDestroyReason;
4319 using TypedOpenOpHelper =
4320 std::conditional_t<IsIndexCursor, IndexOpenOpHelper<CursorType>,
4321 ObjectStoreOpenOpHelper<CursorType>>;
4323 friend class CursorOpBaseHelperBase<CursorType>;
4324 friend class CommonOpenOpHelper<CursorType>;
4325 friend TypedOpenOpHelper;
4326 friend class OpenOpHelper<CursorType>;
4328 CursorOpBase* mCurrentlyRunningOp = nullptr;
4330 LazyInitializedOnce<const typename Base::ContinueQueries> mContinueQueries;
4332 // Only called by TransactionBase.
4333 bool Start(const int64_t aRequestId, const OpenCursorParams& aParams) final;
4335 void SendResponseInternal(CursorResponse& aResponse,
4336 const FilesArrayT<CursorType>& aFiles);
4338 // Must call SendResponseInternal!
4339 bool SendResponse(const CursorResponse& aResponse) = delete;
4341 // IPDL methods.
4342 void ActorDestroy(ActorDestroyReason aWhy) override;
4344 mozilla::ipc::IPCResult RecvDeleteMe() override;
4346 mozilla::ipc::IPCResult RecvContinue(
4347 const int64_t& aRequestId, const CursorRequestParams& aParams,
4348 const Key& aCurrentKey, const Key& aCurrentObjectStoreKey) override;
4350 public:
4351 Cursor(SafeRefPtr<TransactionBase> aTransaction,
4352 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
4353 SafeRefPtr<FullIndexMetadata> aIndexMetadata,
4354 typename Base::Direction aDirection,
4355 typename Base::ConstructFromTransactionBase aConstructionTag)
4356 : Base{std::move(aTransaction), std::move(aObjectStoreMetadata),
4357 std::move(aIndexMetadata), aDirection, aConstructionTag},
4358 KeyValueBase{this->mTransaction.unsafeGetRawPtr()} {}
4360 Cursor(SafeRefPtr<TransactionBase> aTransaction,
4361 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
4362 typename Base::Direction aDirection,
4363 typename Base::ConstructFromTransactionBase aConstructionTag)
4364 : Base{std::move(aTransaction), std::move(aObjectStoreMetadata),
4365 aDirection, aConstructionTag},
4366 KeyValueBase{this->mTransaction.unsafeGetRawPtr()} {}
4368 private:
4369 void SetOptionalKeyRange(const Maybe<SerializedKeyRange>& aOptionalKeyRange,
4370 bool* aOpen);
4372 bool VerifyRequestParams(const CursorRequestParams& aParams,
4373 const CursorPosition<CursorType>& aPosition) const;
4375 ~Cursor() final = default;
4378 template <IDBCursorType CursorType>
4379 class Cursor<CursorType>::CursorOpBase
4380 : public TransactionDatabaseOperationBase {
4381 friend class CursorOpBaseHelperBase<CursorType>;
4383 protected:
4384 RefPtr<Cursor> mCursor;
4385 FilesArrayT<CursorType> mFiles; // TODO: Consider removing this member
4386 // entirely if we are no value cursor.
4388 CursorResponse mResponse;
4390 #ifdef DEBUG
4391 bool mResponseSent;
4392 #endif
4394 protected:
4395 explicit CursorOpBase(Cursor* aCursor, const int64_t aRequestId)
4396 : TransactionDatabaseOperationBase(aCursor->mTransaction.clonePtr(),
4397 /* aRequestId */ aRequestId),
4398 mCursor(aCursor)
4399 #ifdef DEBUG
4401 mResponseSent(false)
4402 #endif
4404 AssertIsOnBackgroundThread();
4405 MOZ_ASSERT(aCursor);
4408 ~CursorOpBase() override = default;
4410 bool SendFailureResult(nsresult aResultCode) final;
4411 nsresult SendSuccessResult() final;
4413 void Cleanup() override;
4416 template <IDBCursorType CursorType>
4417 class OpenOpHelper;
4419 using ResponseSizeOrError = Result<size_t, nsresult>;
4421 template <IDBCursorType CursorType>
4422 class CursorOpBaseHelperBase {
4423 public:
4424 explicit CursorOpBaseHelperBase(
4425 typename Cursor<CursorType>::CursorOpBase& aOp)
4426 : mOp{aOp} {}
4428 ResponseSizeOrError PopulateResponseFromStatement(mozIStorageStatement* aStmt,
4429 bool aInitializeResponse,
4430 Key* const aOptOutSortKey);
4432 void PopulateExtraResponses(mozIStorageStatement* aStmt,
4433 uint32_t aMaxExtraCount,
4434 const size_t aInitialResponseSize,
4435 const nsACString& aOperation,
4436 Key* const aOptPreviousSortKey);
4438 protected:
4439 Cursor<CursorType>& GetCursor() {
4440 MOZ_ASSERT(mOp.mCursor);
4441 return *mOp.mCursor;
4444 void SetResponse(CursorResponse aResponse) {
4445 mOp.mResponse = std::move(aResponse);
4448 protected:
4449 typename Cursor<CursorType>::CursorOpBase& mOp;
4452 class CommonOpenOpHelperBase {
4453 protected:
4454 static void AppendConditionClause(const nsACString& aColumnName,
4455 const nsACString& aStatementParameterName,
4456 bool aLessThan, bool aEquals,
4457 nsCString& aResult);
4460 template <IDBCursorType CursorType>
4461 class CommonOpenOpHelper : public CursorOpBaseHelperBase<CursorType>,
4462 protected CommonOpenOpHelperBase {
4463 public:
4464 explicit CommonOpenOpHelper(typename Cursor<CursorType>::OpenOp& aOp)
4465 : CursorOpBaseHelperBase<CursorType>{aOp} {}
4467 protected:
4468 using CursorOpBaseHelperBase<CursorType>::GetCursor;
4469 using CursorOpBaseHelperBase<CursorType>::PopulateExtraResponses;
4470 using CursorOpBaseHelperBase<CursorType>::PopulateResponseFromStatement;
4471 using CursorOpBaseHelperBase<CursorType>::SetResponse;
4473 const Maybe<SerializedKeyRange>& GetOptionalKeyRange() const {
4474 // This downcast is safe, since we initialized mOp from an OpenOp in the
4475 // ctor.
4476 return static_cast<typename Cursor<CursorType>::OpenOp&>(this->mOp)
4477 .mOptionalKeyRange;
4480 nsresult ProcessStatementSteps(mozIStorageStatement* aStmt);
4483 template <IDBCursorType CursorType>
4484 class ObjectStoreOpenOpHelper : protected CommonOpenOpHelper<CursorType> {
4485 public:
4486 using CommonOpenOpHelper<CursorType>::CommonOpenOpHelper;
4488 protected:
4489 using CommonOpenOpHelper<CursorType>::GetCursor;
4490 using CommonOpenOpHelper<CursorType>::GetOptionalKeyRange;
4491 using CommonOpenOpHelper<CursorType>::AppendConditionClause;
4493 void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
4494 const nsACString& aQueryStart);
4497 template <IDBCursorType CursorType>
4498 class IndexOpenOpHelper : protected CommonOpenOpHelper<CursorType> {
4499 public:
4500 using CommonOpenOpHelper<CursorType>::CommonOpenOpHelper;
4502 protected:
4503 using CommonOpenOpHelper<CursorType>::GetCursor;
4504 using CommonOpenOpHelper<CursorType>::GetOptionalKeyRange;
4505 using CommonOpenOpHelper<CursorType>::AppendConditionClause;
4507 void PrepareIndexKeyConditionClause(
4508 const nsACString& aDirectionClause,
4509 const nsLiteralCString& aObjectDataKeyPrefix, nsAutoCString aQueryStart);
4512 template <>
4513 class OpenOpHelper<IDBCursorType::ObjectStore>
4514 : public ObjectStoreOpenOpHelper<IDBCursorType::ObjectStore> {
4515 public:
4516 using ObjectStoreOpenOpHelper<
4517 IDBCursorType::ObjectStore>::ObjectStoreOpenOpHelper;
4519 nsresult DoDatabaseWork(DatabaseConnection* aConnection);
4522 template <>
4523 class OpenOpHelper<IDBCursorType::ObjectStoreKey>
4524 : public ObjectStoreOpenOpHelper<IDBCursorType::ObjectStoreKey> {
4525 public:
4526 using ObjectStoreOpenOpHelper<
4527 IDBCursorType::ObjectStoreKey>::ObjectStoreOpenOpHelper;
4529 nsresult DoDatabaseWork(DatabaseConnection* aConnection);
4532 template <>
4533 class OpenOpHelper<IDBCursorType::Index>
4534 : IndexOpenOpHelper<IDBCursorType::Index> {
4535 private:
4536 void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
4537 nsAutoCString aQueryStart) {
4538 PrepareIndexKeyConditionClause(aDirectionClause, "index_table."_ns,
4539 std::move(aQueryStart));
4542 public:
4543 using IndexOpenOpHelper<IDBCursorType::Index>::IndexOpenOpHelper;
4545 nsresult DoDatabaseWork(DatabaseConnection* aConnection);
4548 template <>
4549 class OpenOpHelper<IDBCursorType::IndexKey>
4550 : IndexOpenOpHelper<IDBCursorType::IndexKey> {
4551 private:
4552 void PrepareKeyConditionClauses(const nsACString& aDirectionClause,
4553 nsAutoCString aQueryStart) {
4554 PrepareIndexKeyConditionClause(aDirectionClause, ""_ns,
4555 std::move(aQueryStart));
4558 public:
4559 using IndexOpenOpHelper<IDBCursorType::IndexKey>::IndexOpenOpHelper;
4561 nsresult DoDatabaseWork(DatabaseConnection* aConnection);
4564 template <IDBCursorType CursorType>
4565 class Cursor<CursorType>::OpenOp final : public CursorOpBase {
4566 friend class Cursor<CursorType>;
4567 friend class CommonOpenOpHelper<CursorType>;
4569 const Maybe<SerializedKeyRange> mOptionalKeyRange;
4571 using CursorOpBase::mCursor;
4572 using CursorOpBase::mResponse;
4574 // Only created by Cursor.
4575 OpenOp(Cursor* const aCursor, const int64_t aRequestId,
4576 const Maybe<SerializedKeyRange>& aOptionalKeyRange)
4577 : CursorOpBase(aCursor, aRequestId),
4578 mOptionalKeyRange(aOptionalKeyRange) {}
4580 // Reference counted.
4581 ~OpenOp() override = default;
4583 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4586 template <IDBCursorType CursorType>
4587 class Cursor<CursorType>::ContinueOp final
4588 : public Cursor<CursorType>::CursorOpBase {
4589 friend class Cursor<CursorType>;
4591 using CursorOpBase::mCursor;
4592 using CursorOpBase::mResponse;
4593 const CursorRequestParams mParams;
4595 // Only created by Cursor.
4596 ContinueOp(Cursor* const aCursor, int64_t aRequestId,
4597 CursorRequestParams aParams, CursorPosition<CursorType> aPosition)
4598 : CursorOpBase(aCursor, aRequestId),
4599 mParams(std::move(aParams)),
4600 mCurrentPosition{std::move(aPosition)} {
4601 MOZ_ASSERT(mParams.type() != CursorRequestParams::T__None);
4604 // Reference counted.
4605 ~ContinueOp() override = default;
4607 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override;
4609 const CursorPosition<CursorType> mCurrentPosition;
4612 class Utils final : public PBackgroundIndexedDBUtilsParent {
4613 #ifdef DEBUG
4614 bool mActorDestroyed;
4615 #endif
4617 public:
4618 Utils();
4620 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Utils)
4622 private:
4623 // Reference counted.
4624 ~Utils() override;
4626 // IPDL methods are only called by IPDL.
4627 void ActorDestroy(ActorDestroyReason aWhy) override;
4629 mozilla::ipc::IPCResult RecvDeleteMe() override;
4631 mozilla::ipc::IPCResult RecvGetFileReferences(
4632 const PersistenceType& aPersistenceType, const nsACString& aOrigin,
4633 const nsAString& aDatabaseName, const int64_t& aFileId, int32_t* aRefCnt,
4634 int32_t* aDBRefCnt, bool* aResult) override;
4637 /*******************************************************************************
4638 * Other class declarations
4639 ******************************************************************************/
4641 struct DatabaseActorInfo final {
4642 friend class mozilla::DefaultDelete<DatabaseActorInfo>;
4644 SafeRefPtr<FullDatabaseMetadata> mMetadata;
4645 nsTArray<NotNull<CheckedUnsafePtr<Database>>> mLiveDatabases;
4646 RefPtr<FactoryOp> mWaitingFactoryOp;
4648 DatabaseActorInfo(SafeRefPtr<FullDatabaseMetadata> aMetadata,
4649 NotNull<Database*> aDatabase)
4650 : mMetadata(std::move(aMetadata)) {
4651 MOZ_COUNT_CTOR(DatabaseActorInfo);
4653 mLiveDatabases.AppendElement(aDatabase);
4656 private:
4657 ~DatabaseActorInfo() {
4658 MOZ_ASSERT(mLiveDatabases.IsEmpty());
4659 MOZ_ASSERT(!mWaitingFactoryOp || !mWaitingFactoryOp->HasBlockedDatabases());
4661 MOZ_COUNT_DTOR(DatabaseActorInfo);
4665 class DatabaseLoggingInfo final {
4666 #ifdef DEBUG
4667 // Just for potential warnings.
4668 friend class Factory;
4669 #endif
4671 LoggingInfo mLoggingInfo;
4673 public:
4674 explicit DatabaseLoggingInfo(const LoggingInfo& aLoggingInfo)
4675 : mLoggingInfo(aLoggingInfo) {
4676 AssertIsOnBackgroundThread();
4677 MOZ_ASSERT(aLoggingInfo.nextTransactionSerialNumber());
4678 MOZ_ASSERT(aLoggingInfo.nextVersionChangeTransactionSerialNumber());
4679 MOZ_ASSERT(aLoggingInfo.nextRequestSerialNumber());
4682 const nsID& Id() const {
4683 AssertIsOnBackgroundThread();
4685 return mLoggingInfo.backgroundChildLoggingId();
4688 int64_t NextTransactionSN(IDBTransaction::Mode aMode) {
4689 AssertIsOnBackgroundThread();
4690 MOZ_ASSERT(mLoggingInfo.nextTransactionSerialNumber() < INT64_MAX);
4691 MOZ_ASSERT(mLoggingInfo.nextVersionChangeTransactionSerialNumber() >
4692 INT64_MIN);
4694 if (aMode == IDBTransaction::Mode::VersionChange) {
4695 return mLoggingInfo.nextVersionChangeTransactionSerialNumber()--;
4698 return mLoggingInfo.nextTransactionSerialNumber()++;
4701 uint64_t NextRequestSN() {
4702 AssertIsOnBackgroundThread();
4703 MOZ_ASSERT(mLoggingInfo.nextRequestSerialNumber() < UINT64_MAX);
4705 return mLoggingInfo.nextRequestSerialNumber()++;
4708 NS_INLINE_DECL_REFCOUNTING(DatabaseLoggingInfo)
4710 private:
4711 ~DatabaseLoggingInfo();
4714 class QuotaClient final : public mozilla::dom::quota::Client {
4715 friend class GetDatabasesOp;
4717 static QuotaClient* sInstance;
4719 nsCOMPtr<nsIEventTarget> mBackgroundThread;
4720 nsCOMPtr<nsITimer> mDeleteTimer;
4721 nsTArray<RefPtr<Maintenance>> mMaintenanceQueue;
4722 RefPtr<Maintenance> mCurrentMaintenance;
4723 RefPtr<nsThreadPool> mMaintenanceThreadPool;
4724 nsClassHashtable<nsRefPtrHashKey<DatabaseFileManager>, nsTArray<int64_t>>
4725 mPendingDeleteInfos;
4727 public:
4728 QuotaClient();
4730 static QuotaClient* GetInstance() {
4731 AssertIsOnBackgroundThread();
4733 return sInstance;
4736 nsIEventTarget* BackgroundThread() const {
4737 MOZ_ASSERT(mBackgroundThread);
4738 return mBackgroundThread;
4741 nsresult AsyncDeleteFile(DatabaseFileManager* aFileManager, int64_t aFileId);
4743 nsresult FlushPendingFileDeletions();
4745 RefPtr<Maintenance> GetCurrentMaintenance() const {
4746 return mCurrentMaintenance;
4749 void NoteFinishedMaintenance(Maintenance* aMaintenance) {
4750 AssertIsOnBackgroundThread();
4751 MOZ_ASSERT(aMaintenance);
4752 MOZ_ASSERT(mCurrentMaintenance == aMaintenance);
4754 mCurrentMaintenance = nullptr;
4756 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::IDB,
4757 "Maintenance finished"_ns);
4759 ProcessMaintenanceQueue();
4762 nsThreadPool* GetOrCreateThreadPool();
4764 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::QuotaClient,
4765 override)
4767 mozilla::dom::quota::Client::Type GetType() override;
4769 nsresult UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) override;
4771 nsresult UpgradeStorageFrom2_1To2_2(nsIFile* aDirectory) override;
4773 Result<UsageInfo, nsresult> InitOrigin(PersistenceType aPersistenceType,
4774 const OriginMetadata& aOriginMetadata,
4775 const AtomicBool& aCanceled) override;
4777 nsresult InitOriginWithoutTracking(PersistenceType aPersistenceType,
4778 const OriginMetadata& aOriginMetadata,
4779 const AtomicBool& aCanceled) override;
4781 Result<UsageInfo, nsresult> GetUsageForOrigin(
4782 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
4783 const AtomicBool& aCanceled) override;
4785 void OnOriginClearCompleted(PersistenceType aPersistenceType,
4786 const nsACString& aOrigin) override;
4788 void OnRepositoryClearCompleted(PersistenceType aPersistenceType) override;
4790 void ReleaseIOThreadObjects() override;
4792 void AbortOperationsForLocks(
4793 const DirectoryLockIdTable& aDirectoryLockIds) override;
4795 void AbortOperationsForProcess(ContentParentId aContentParentId) override;
4797 void AbortAllOperations() override;
4799 void StartIdleMaintenance() override;
4801 void StopIdleMaintenance() override;
4803 private:
4804 ~QuotaClient() override;
4806 void InitiateShutdown() override;
4807 bool IsShutdownCompleted() const override;
4808 nsCString GetShutdownStatus() const override;
4809 void ForceKillActors() override;
4810 void FinalizeShutdown() override;
4812 static void DeleteTimerCallback(nsITimer* aTimer, void* aClosure);
4814 void AbortAllMaintenances();
4816 Result<nsCOMPtr<nsIFile>, nsresult> GetDirectory(
4817 const OriginMetadata& aOriginMetadata);
4819 struct SubdirectoriesToProcessAndDatabaseFilenames {
4820 AutoTArray<nsString, 20> subdirsToProcess;
4821 nsTHashSet<nsString> databaseFilenames{20};
4824 struct SubdirectoriesToProcessAndDatabaseFilenamesAndObsoleteFilenames {
4825 AutoTArray<nsString, 20> subdirsToProcess;
4826 nsTHashSet<nsString> databaseFilenames{20};
4827 nsTHashSet<nsString> obsoleteFilenames{20};
4830 enum class ObsoleteFilenamesHandling { Include, Omit };
4832 template <ObsoleteFilenamesHandling ObsoleteFilenames>
4833 using GetDatabaseFilenamesResult = std::conditional_t<
4834 ObsoleteFilenames == ObsoleteFilenamesHandling::Include,
4835 SubdirectoriesToProcessAndDatabaseFilenamesAndObsoleteFilenames,
4836 SubdirectoriesToProcessAndDatabaseFilenames>;
4838 // Returns a two-part or three-part structure:
4840 // The first part is an array of subdirectories to process.
4842 // The second part is a hashtable of database filenames.
4844 // When ObsoleteFilenames is ObsoleteFilenamesHandling::Include, will also
4845 // collect files based on the marker files. For now,
4846 // GetUsageForOriginInternal() is the only consumer of this result because it
4847 // checks those unfinished deletion and clean them up after that.
4848 template <ObsoleteFilenamesHandling ObsoleteFilenames =
4849 ObsoleteFilenamesHandling::Omit>
4850 Result<GetDatabaseFilenamesResult<ObsoleteFilenames>,
4851 nsresult> static GetDatabaseFilenames(nsIFile& aDirectory,
4852 const AtomicBool& aCanceled);
4854 nsresult GetUsageForOriginInternal(PersistenceType aPersistenceType,
4855 const OriginMetadata& aOriginMetadata,
4856 const AtomicBool& aCanceled,
4857 bool aInitializing, UsageInfo* aUsageInfo);
4859 // Runs on the PBackground thread. Checks to see if there's a queued
4860 // Maintenance to run.
4861 void ProcessMaintenanceQueue();
4864 class DeleteFilesRunnable final : public Runnable {
4865 using DirectoryLock = mozilla::dom::quota::DirectoryLock;
4867 enum State {
4868 // Just created on the PBackground thread. Next step is
4869 // State_DirectoryOpenPending.
4870 State_Initial,
4872 // Waiting for directory open allowed on the main thread. The next step is
4873 // State_DatabaseWorkOpen.
4874 State_DirectoryOpenPending,
4876 // Waiting to do/doing work on the QuotaManager IO thread. The next step is
4877 // State_UnblockingOpen.
4878 State_DatabaseWorkOpen,
4880 // Notifying the QuotaManager that it can proceed to the next operation on
4881 // the main thread. Next step is State_Completed.
4882 State_UnblockingOpen,
4884 // All done.
4885 State_Completed
4888 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
4889 SafeRefPtr<DatabaseFileManager> mFileManager;
4890 RefPtr<DirectoryLock> mDirectoryLock;
4891 nsTArray<int64_t> mFileIds;
4892 State mState;
4893 DEBUGONLY(bool mDEBUGCountsAsPending = false);
4895 static uint64_t sPendingRunnables;
4897 public:
4898 DeleteFilesRunnable(SafeRefPtr<DatabaseFileManager> aFileManager,
4899 nsTArray<int64_t>&& aFileIds);
4901 void RunImmediately();
4903 static bool IsDeletionPending() { return sPendingRunnables > 0; }
4905 private:
4906 #ifdef DEBUG
4907 ~DeleteFilesRunnable();
4908 #else
4909 ~DeleteFilesRunnable() = default;
4910 #endif
4912 void Open();
4914 void DoDatabaseWork();
4916 void Finish();
4918 void UnblockOpen();
4920 NS_DECL_NSIRUNNABLE
4922 void DirectoryLockAcquired(DirectoryLock* aLock);
4924 void DirectoryLockFailed();
4927 class Maintenance final : public Runnable {
4928 struct DirectoryInfo final {
4929 InitializedOnce<const OriginMetadata> mOriginMetadata;
4930 InitializedOnce<const nsTArray<nsString>> mDatabasePaths;
4931 const PersistenceType mPersistenceType;
4933 DirectoryInfo(PersistenceType aPersistenceType,
4934 OriginMetadata aOriginMetadata,
4935 nsTArray<nsString>&& aDatabasePaths);
4937 DirectoryInfo(const DirectoryInfo& aOther) = delete;
4938 DirectoryInfo(DirectoryInfo&& aOther) = delete;
4940 ~DirectoryInfo() { MOZ_COUNT_DTOR(Maintenance::DirectoryInfo); }
4943 enum class State {
4944 // Newly created on the PBackground thread. Will proceed immediately or be
4945 // added to the maintenance queue. The next step is either
4946 // DirectoryOpenPending if IndexedDatabaseManager is running, or
4947 // CreateIndexedDatabaseManager if not.
4948 Initial = 0,
4950 // Create IndexedDatabaseManager on the main thread. The next step is either
4951 // Finishing if IndexedDatabaseManager initialization fails, or
4952 // IndexedDatabaseManagerOpen if initialization succeeds.
4953 CreateIndexedDatabaseManager,
4955 // Call OpenDirectory() on the PBackground thread. The next step is
4956 // DirectoryOpenPending.
4957 IndexedDatabaseManagerOpen,
4959 // Waiting for directory open allowed on the PBackground thread. The next
4960 // step is either Finishing if directory lock failed to acquire, or
4961 // DirectoryWorkOpen if directory lock is acquired.
4962 DirectoryOpenPending,
4964 // Waiting to do/doing work on the QuotaManager IO thread. The next step is
4965 // BeginDatabaseMaintenance.
4966 DirectoryWorkOpen,
4968 // Dispatching a runnable for each database on the PBackground thread. The
4969 // next state is either WaitingForDatabaseMaintenancesToComplete if at least
4970 // one runnable has been dispatched, or Finishing otherwise.
4971 BeginDatabaseMaintenance,
4973 // Waiting for DatabaseMaintenance to finish on maintenance thread pool.
4974 // The next state is Finishing if the last runnable has finished.
4975 WaitingForDatabaseMaintenancesToComplete,
4977 // Waiting to finish/finishing on the PBackground thread. The next step is
4978 // Completed.
4979 Finishing,
4981 // All done.
4982 Complete
4985 RefPtr<QuotaClient> mQuotaClient;
4986 PRTime mStartTime;
4987 RefPtr<UniversalDirectoryLock> mPendingDirectoryLock;
4988 RefPtr<UniversalDirectoryLock> mDirectoryLock;
4989 nsTArray<nsCOMPtr<nsIRunnable>> mCompleteCallbacks;
4990 nsTArray<DirectoryInfo> mDirectoryInfos;
4991 nsTHashMap<nsStringHashKey, DatabaseMaintenance*> mDatabaseMaintenances;
4992 nsresult mResultCode;
4993 Atomic<bool> mAborted;
4994 State mState;
4996 public:
4997 explicit Maintenance(QuotaClient* aQuotaClient)
4998 : Runnable("dom::indexedDB::Maintenance"),
4999 mQuotaClient(aQuotaClient),
5000 mStartTime(PR_Now()),
5001 mResultCode(NS_OK),
5002 mAborted(false),
5003 mState(State::Initial) {
5004 AssertIsOnBackgroundThread();
5005 MOZ_ASSERT(aQuotaClient);
5006 MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
5007 MOZ_ASSERT(mStartTime);
5010 nsIEventTarget* BackgroundThread() const {
5011 MOZ_ASSERT(mQuotaClient);
5012 return mQuotaClient->BackgroundThread();
5015 PRTime StartTime() const { return mStartTime; }
5017 bool IsAborted() const { return mAborted; }
5019 void RunImmediately() {
5020 MOZ_ASSERT(mState == State::Initial);
5022 Unused << this->Run();
5025 void Abort();
5027 void RegisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
5029 void UnregisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
5031 bool HasDatabaseMaintenances() const { return mDatabaseMaintenances.Count(); }
5033 RefPtr<DatabaseMaintenance> GetDatabaseMaintenance(
5034 const nsAString& aDatabasePath) const {
5035 AssertIsOnBackgroundThread();
5037 return mDatabaseMaintenances.Get(aDatabasePath);
5040 void WaitForCompletion(nsIRunnable* aCallback) {
5041 AssertIsOnBackgroundThread();
5042 MOZ_ASSERT(mDatabaseMaintenances.Count());
5044 mCompleteCallbacks.AppendElement(aCallback);
5047 void Stringify(nsACString& aResult) const;
5049 private:
5050 ~Maintenance() override {
5051 MOZ_ASSERT(mState == State::Complete);
5052 MOZ_ASSERT(!mDatabaseMaintenances.Count());
5055 // Runs on the PBackground thread. Checks if IndexedDatabaseManager is
5056 // running. Calls OpenDirectory() or dispatches to the main thread on which
5057 // CreateIndexedDatabaseManager() is called.
5058 nsresult Start();
5060 // Runs on the main thread. Once IndexedDatabaseManager is created it will
5061 // dispatch to the PBackground thread on which OpenDirectory() is called.
5062 nsresult CreateIndexedDatabaseManager();
5064 // Runs on the PBackground thread. Once QuotaManager has given a lock it will
5065 // call DirectoryOpen().
5066 nsresult OpenDirectory();
5068 // Runs on the PBackground thread. Dispatches to the QuotaManager I/O thread.
5069 nsresult DirectoryOpen();
5071 // Runs on the QuotaManager I/O thread. Once it finds databases it will
5072 // dispatch to the PBackground thread on which BeginDatabaseMaintenance()
5073 // is called.
5074 nsresult DirectoryWork();
5076 // Runs on the PBackground thread. It dispatches a runnable for each database.
5077 nsresult BeginDatabaseMaintenance();
5079 // Runs on the PBackground thread. Called when the maintenance is finished or
5080 // if any of above methods fails.
5081 void Finish();
5083 NS_DECL_NSIRUNNABLE
5085 void DirectoryLockAcquired(DirectoryLock* aLock);
5087 void DirectoryLockFailed();
5090 Maintenance::DirectoryInfo::DirectoryInfo(PersistenceType aPersistenceType,
5091 OriginMetadata aOriginMetadata,
5092 nsTArray<nsString>&& aDatabasePaths)
5093 : mOriginMetadata(std::move(aOriginMetadata)),
5094 mDatabasePaths(std::move(aDatabasePaths)),
5095 mPersistenceType(aPersistenceType) {
5096 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
5097 MOZ_ASSERT(!mOriginMetadata->mGroup.IsEmpty());
5098 MOZ_ASSERT(!mOriginMetadata->mOrigin.IsEmpty());
5099 #ifdef DEBUG
5100 MOZ_ASSERT(!mDatabasePaths->IsEmpty());
5101 for (const nsAString& databasePath : *mDatabasePaths) {
5102 MOZ_ASSERT(!databasePath.IsEmpty());
5104 #endif
5106 MOZ_COUNT_CTOR(Maintenance::DirectoryInfo);
5109 class DatabaseMaintenance final : public Runnable {
5110 // The minimum amount of time that has passed since the last vacuum before we
5111 // will attempt to analyze the database for fragmentation.
5112 static const PRTime kMinVacuumAge =
5113 PRTime(PR_USEC_PER_SEC) * 60 * 60 * 24 * 7;
5115 // If the percent of database pages that are not in contiguous order is higher
5116 // than this percentage we will attempt a vacuum.
5117 static const int32_t kPercentUnorderedThreshold = 30;
5119 // If the percent of file size growth since the last vacuum is higher than
5120 // this percentage we will attempt a vacuum.
5121 static const int32_t kPercentFileSizeGrowthThreshold = 10;
5123 // The number of freelist pages beyond which we will favor an incremental
5124 // vacuum over a full vacuum.
5125 static const int32_t kMaxFreelistThreshold = 5;
5127 // If the percent of unused file bytes in the database exceeds this percentage
5128 // then we will attempt a full vacuum.
5129 static const int32_t kPercentUnusedThreshold = 20;
5131 enum class MaintenanceAction { Nothing = 0, IncrementalVacuum, FullVacuum };
5133 RefPtr<Maintenance> mMaintenance;
5134 RefPtr<DirectoryLock> mDirectoryLock;
5135 const OriginMetadata mOriginMetadata;
5136 const nsString mDatabasePath;
5137 int64_t mDirectoryLockId;
5138 nsCOMPtr<nsIRunnable> mCompleteCallback;
5139 const PersistenceType mPersistenceType;
5140 const Maybe<CipherKey> mMaybeKey;
5141 Atomic<bool> mAborted;
5142 DataMutex<nsCOMPtr<mozIStorageConnection>> mSharedStorageConnection;
5144 public:
5145 DatabaseMaintenance(Maintenance* aMaintenance, DirectoryLock* aDirectoryLock,
5146 PersistenceType aPersistenceType,
5147 const OriginMetadata& aOriginMetadata,
5148 const nsAString& aDatabasePath,
5149 const Maybe<CipherKey>& aMaybeKey)
5150 : Runnable("dom::indexedDB::DatabaseMaintenance"),
5151 mMaintenance(aMaintenance),
5152 mDirectoryLock(aDirectoryLock),
5153 mOriginMetadata(aOriginMetadata),
5154 mDatabasePath(aDatabasePath),
5155 mPersistenceType(aPersistenceType),
5156 mMaybeKey{aMaybeKey},
5157 mAborted(false),
5158 mSharedStorageConnection("sharedStorageConnection") {
5159 MOZ_ASSERT(aDirectoryLock);
5161 MOZ_ASSERT(mDirectoryLock->Id() >= 0);
5162 mDirectoryLockId = mDirectoryLock->Id();
5165 const nsAString& DatabasePath() const { return mDatabasePath; }
5167 void WaitForCompletion(nsIRunnable* aCallback) {
5168 AssertIsOnBackgroundThread();
5169 MOZ_ASSERT(!mCompleteCallback);
5171 mCompleteCallback = aCallback;
5174 void Stringify(nsACString& aResult) const;
5176 nsresult Abort();
5178 private:
5179 ~DatabaseMaintenance() override = default;
5181 // Runs on maintenance thread pool. Does maintenance on the database.
5182 void PerformMaintenanceOnDatabase();
5184 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
5185 nsresult CheckIntegrity(mozIStorageConnection& aConnection, bool* aOk);
5187 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
5188 nsresult DetermineMaintenanceAction(mozIStorageConnection& aConnection,
5189 nsIFile* aDatabaseFile,
5190 MaintenanceAction* aMaintenanceAction);
5192 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
5193 void IncrementalVacuum(mozIStorageConnection& aConnection);
5195 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
5196 void FullVacuum(mozIStorageConnection& aConnection, nsIFile* aDatabaseFile);
5198 // Runs on the PBackground thread. It dispatches a complete callback and
5199 // unregisters from Maintenance.
5200 void RunOnOwningThread();
5202 // Runs on maintenance thread pool. Once it performs database maintenance
5203 // it will dispatch to the PBackground thread on which RunOnOwningThread()
5204 // is called.
5205 void RunOnConnectionThread();
5207 // TODO: Could QuotaClient::IsShuttingDownOnNonBackgroundThread() call
5208 // be part of mMaintenance::IsAborted() ?
5209 inline bool IsAborted() const {
5210 return mMaintenance->IsAborted() || mAborted ||
5211 NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread());
5214 NS_DECL_NSIRUNNABLE
5217 #ifdef DEBUG
5219 class DEBUGThreadSlower final : public nsIThreadObserver {
5220 public:
5221 DEBUGThreadSlower() {
5222 AssertIsOnBackgroundThread();
5223 MOZ_ASSERT(kDEBUGThreadSleepMS);
5226 NS_DECL_ISUPPORTS
5228 private:
5229 ~DEBUGThreadSlower() { AssertIsOnBackgroundThread(); }
5231 NS_DECL_NSITHREADOBSERVER
5234 #endif // DEBUG
5236 /*******************************************************************************
5237 * Helper classes
5238 ******************************************************************************/
5240 // XXX Get rid of FileHelper and move the functions into DatabaseFileManager.
5241 // Then, DatabaseFileManager::Get(Journal)Directory and
5242 // DatabaseFileManager::GetFileForId might eventually be made private.
5243 class MOZ_STACK_CLASS FileHelper final {
5244 const SafeRefPtr<DatabaseFileManager> mFileManager;
5246 LazyInitializedOnce<const NotNull<nsCOMPtr<nsIFile>>> mFileDirectory;
5247 LazyInitializedOnce<const NotNull<nsCOMPtr<nsIFile>>> mJournalDirectory;
5249 class ReadCallback;
5250 LazyInitializedOnce<const NotNull<RefPtr<ReadCallback>>> mReadCallback;
5252 public:
5253 explicit FileHelper(SafeRefPtr<DatabaseFileManager>&& aFileManager)
5254 : mFileManager(std::move(aFileManager)) {
5255 MOZ_ASSERT(mFileManager);
5258 nsresult Init();
5260 [[nodiscard]] nsCOMPtr<nsIFile> GetFile(const DatabaseFileInfo& aFileInfo);
5262 [[nodiscard]] nsCOMPtr<nsIFile> GetJournalFile(
5263 const DatabaseFileInfo& aFileInfo);
5265 nsresult CreateFileFromStream(nsIFile& aFile, nsIFile& aJournalFile,
5266 nsIInputStream& aInputStream, bool aCompress,
5267 const Maybe<CipherKey>& aMaybeKey);
5269 private:
5270 nsresult SyncCopy(nsIInputStream& aInputStream,
5271 nsIOutputStream& aOutputStream, char* aBuffer,
5272 uint32_t aBufferSize);
5274 nsresult SyncRead(nsIInputStream& aInputStream, char* aBuffer,
5275 uint32_t aBufferSize, uint32_t* aRead);
5278 /*******************************************************************************
5279 * Helper Functions
5280 ******************************************************************************/
5282 bool GetFilenameBase(const nsAString& aFilename, const nsAString& aSuffix,
5283 nsDependentSubstring& aFilenameBase) {
5284 MOZ_ASSERT(!aFilename.IsEmpty());
5285 MOZ_ASSERT(aFilenameBase.IsEmpty());
5287 if (!StringEndsWith(aFilename, aSuffix) ||
5288 aFilename.Length() == aSuffix.Length()) {
5289 return false;
5292 MOZ_ASSERT(aFilename.Length() > aSuffix.Length());
5294 aFilenameBase.Rebind(aFilename, 0, aFilename.Length() - aSuffix.Length());
5295 return true;
5298 class EncryptedFileBlobImpl final : public FileBlobImpl {
5299 public:
5300 EncryptedFileBlobImpl(const nsCOMPtr<nsIFile>& aNativeFile,
5301 const DatabaseFileInfo::IdType aId,
5302 const CipherKey& aKey)
5303 : FileBlobImpl{aNativeFile}, mKey{aKey} {
5304 SetFileId(aId);
5307 uint64_t GetSize(ErrorResult& aRv) override {
5308 nsCOMPtr<nsIInputStream> inputStream;
5309 CreateInputStream(getter_AddRefs(inputStream), aRv);
5311 if (aRv.Failed()) {
5312 return 0;
5315 MOZ_ASSERT(inputStream);
5317 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(inputStream, Available), 0,
5318 [&aRv](const nsresult rv) { aRv = rv; });
5321 void CreateInputStream(nsIInputStream** aInputStream,
5322 ErrorResult& aRv) const override {
5323 nsCOMPtr<nsIInputStream> baseInputStream;
5324 FileBlobImpl::CreateInputStream(getter_AddRefs(baseInputStream), aRv);
5325 if (NS_WARN_IF(aRv.Failed())) {
5326 return;
5329 *aInputStream =
5330 MakeAndAddRef<DecryptingInputStream<IndexedDBCipherStrategy>>(
5331 WrapNotNull(std::move(baseInputStream)), kEncryptedStreamBlockSize,
5332 mKey)
5333 .take();
5336 void GetBlobImplType(nsAString& aBlobImplType) const override {
5337 aBlobImplType = u"EncryptedFileBlobImpl"_ns;
5340 already_AddRefed<BlobImpl> CreateSlice(uint64_t aStart, uint64_t aLength,
5341 const nsAString& aContentType,
5342 ErrorResult& aRv) const override {
5343 MOZ_CRASH("Not implemented because this should be unreachable.");
5346 private:
5347 const CipherKey mKey;
5350 RefPtr<BlobImpl> CreateFileBlobImpl(const Database& aDatabase,
5351 const nsCOMPtr<nsIFile>& aNativeFile,
5352 const DatabaseFileInfo::IdType aId) {
5353 if (aDatabase.IsInPrivateBrowsing()) {
5354 nsCString keyId;
5355 keyId.AppendInt(aId);
5357 const auto& key =
5358 aDatabase.GetFileManager().MutableCipherKeyManagerRef().Get(keyId);
5360 MOZ_RELEASE_ASSERT(key.isSome());
5361 return MakeRefPtr<EncryptedFileBlobImpl>(aNativeFile, aId, *key);
5364 auto impl = MakeRefPtr<FileBlobImpl>(aNativeFile);
5365 impl->SetFileId(aId);
5367 return impl;
5370 Result<nsTArray<SerializedStructuredCloneFile>, nsresult>
5371 SerializeStructuredCloneFiles(const SafeRefPtr<Database>& aDatabase,
5372 const nsTArray<StructuredCloneFileParent>& aFiles,
5373 bool aForPreprocess) {
5374 AssertIsOnBackgroundThread();
5375 MOZ_ASSERT(aDatabase);
5377 if (aFiles.IsEmpty()) {
5378 return nsTArray<SerializedStructuredCloneFile>{};
5381 const nsCOMPtr<nsIFile> directory =
5382 aDatabase->GetFileManager().GetCheckedDirectory();
5383 QM_TRY(OkIf(directory), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
5384 IDB_REPORT_INTERNAL_ERR_LAMBDA);
5386 nsTArray<SerializedStructuredCloneFile> serializedStructuredCloneFiles;
5387 QM_TRY(OkIf(serializedStructuredCloneFiles.SetCapacity(aFiles.Length(),
5388 fallible)),
5389 Err(NS_ERROR_OUT_OF_MEMORY));
5391 QM_TRY(TransformIfAbortOnErr(
5392 aFiles, MakeBackInserter(serializedStructuredCloneFiles),
5393 [aForPreprocess](const auto& file) {
5394 return !aForPreprocess ||
5395 file.Type() == StructuredCloneFileBase::eStructuredClone;
5397 [&directory, &aDatabase, aForPreprocess](
5398 const auto& file) -> Result<SerializedStructuredCloneFile, nsresult> {
5399 const int64_t fileId = file.FileInfo().Id();
5400 MOZ_ASSERT(fileId > 0);
5402 const nsCOMPtr<nsIFile> nativeFile =
5403 mozilla::dom::indexedDB::DatabaseFileManager::GetCheckedFileForId(
5404 directory, fileId);
5405 QM_TRY(OkIf(nativeFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
5406 IDB_REPORT_INTERNAL_ERR_LAMBDA);
5408 switch (file.Type()) {
5409 case StructuredCloneFileBase::eStructuredClone:
5410 if (!aForPreprocess) {
5411 return SerializedStructuredCloneFile{
5412 null_t(), StructuredCloneFileBase::eStructuredClone};
5415 [[fallthrough]];
5417 case StructuredCloneFileBase::eBlob: {
5418 const auto impl = CreateFileBlobImpl(*aDatabase, nativeFile,
5419 file.FileInfo().Id());
5421 IPCBlob ipcBlob;
5423 // This can only fail if the child has crashed.
5424 QM_TRY(MOZ_TO_RESULT(IPCBlobUtils::Serialize(impl, ipcBlob)),
5425 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
5426 IDB_REPORT_INTERNAL_ERR_LAMBDA);
5428 aDatabase->MapBlob(ipcBlob, file.FileInfoPtr());
5430 return SerializedStructuredCloneFile{ipcBlob, file.Type()};
5433 case StructuredCloneFileBase::eMutableFile:
5434 case StructuredCloneFileBase::eWasmBytecode:
5435 case StructuredCloneFileBase::eWasmCompiled: {
5436 // Set file() to null, support for storing WebAssembly.Modules has
5437 // been removed in bug 1469395. Support for de-serialization of
5438 // WebAssembly.Modules modules has been removed in bug 1561876.
5439 // Support for MutableFile has been removed in bug 1500343. Full
5440 // removal is tracked in bug 1487479.
5442 return SerializedStructuredCloneFile{null_t(), file.Type()};
5445 default:
5446 MOZ_CRASH("Should never get here!");
5448 }));
5450 return std::move(serializedStructuredCloneFiles);
5453 bool IsFileNotFoundError(const nsresult aRv) {
5454 return aRv == NS_ERROR_FILE_NOT_FOUND;
5457 enum struct Idempotency { Yes, No };
5459 // Delete a file, decreasing the quota usage as appropriate. If the file no
5460 // longer exists but aIdempotency is Idempotency::Yes, success is returned,
5461 // although quota usage can't be decreased. (With the assumption being that the
5462 // file was already deleted prior to this logic running, and the non-existent
5463 // file was no longer tracked by quota because it didn't exist at
5464 // initialization time or a previous deletion call updated the usage.)
5465 nsresult DeleteFile(nsIFile& aFile, QuotaManager* const aQuotaManager,
5466 const PersistenceType aPersistenceType,
5467 const OriginMetadata& aOriginMetadata,
5468 const Idempotency aIdempotency) {
5469 MOZ_ASSERT(!NS_IsMainThread());
5470 MOZ_ASSERT(!IsOnBackgroundThread());
5472 // Callers which pass Idempotency::Yes call this function without checking if
5473 // the file already exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used
5474 // here since we just want to log NS_ERROR_FILE_NOT_FOUND results and not spam
5475 // the reports.
5476 // Theoretically, there should be no QM_OR_ELSE_(WARN|LOG_VERBOSE)_IF when a
5477 // caller passes Idempotency::No, but it's simpler when the predicate just
5478 // always returns false in that case.
5480 const auto isIgnorableError = [&aIdempotency]() -> bool (*)(nsresult) {
5481 if (aIdempotency == Idempotency::Yes) {
5482 return IsFileNotFoundError;
5485 return [](const nsresult rv) { return false; };
5486 }();
5488 QM_TRY_INSPECT(
5489 const auto& fileSize,
5490 ([aQuotaManager, &aFile,
5491 isIgnorableError]() -> Result<Maybe<int64_t>, nsresult> {
5492 if (aQuotaManager) {
5493 QM_TRY_INSPECT(
5494 const Maybe<int64_t>& fileSize,
5495 QM_OR_ELSE_LOG_VERBOSE_IF(
5496 // Expression.
5497 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize)
5498 .map([](const int64_t val) { return Some(val); }),
5499 // Predicate.
5500 isIgnorableError,
5501 // Fallback.
5502 ErrToDefaultOk<Maybe<int64_t>>));
5504 // XXX Can we really assert that the file size is not 0 if
5505 // it existed? This might be violated by external
5506 // influences.
5507 MOZ_ASSERT(!fileSize || fileSize.value() >= 0);
5509 return fileSize;
5512 return Some(int64_t(0));
5513 }()));
5515 if (!fileSize) {
5516 return NS_OK;
5519 QM_TRY_INSPECT(const auto& didExist,
5520 QM_OR_ELSE_LOG_VERBOSE_IF(
5521 // Expression.
5522 MOZ_TO_RESULT(aFile.Remove(false)).map(Some<Ok>),
5523 // Predicate.
5524 isIgnorableError,
5525 // Fallback.
5526 ErrToDefaultOk<Maybe<Ok>>));
5528 if (!didExist) {
5529 // XXX If we get here, this means that the file still existed when we
5530 // queried its size, but no longer when we tried to remove it. Not sure if
5531 // this should really be silently accepted in idempotent mode.
5532 return NS_OK;
5535 if (fileSize.value() > 0) {
5536 MOZ_ASSERT(aQuotaManager);
5538 aQuotaManager->DecreaseUsageForClient(
5539 ClientMetadata{aOriginMetadata, Client::IDB}, fileSize.value());
5542 return NS_OK;
5545 nsresult DeleteFile(nsIFile& aDirectory, const nsAString& aFilename,
5546 QuotaManager* const aQuotaManager,
5547 const PersistenceType aPersistenceType,
5548 const OriginMetadata& aOriginMetadata,
5549 const Idempotency aIdempotent) {
5550 AssertIsOnIOThread();
5551 MOZ_ASSERT(!aFilename.IsEmpty());
5553 QM_TRY_INSPECT(const auto& file, CloneFileAndAppend(aDirectory, aFilename));
5555 return DeleteFile(*file, aQuotaManager, aPersistenceType, aOriginMetadata,
5556 aIdempotent);
5559 // Delete files in a directory that you think exists. If the directory doesn't
5560 // exist, an error will not be returned, but warning telemetry will be
5561 // generated! So only call this on directories that you know exist (idempotent
5562 // usage, but it's not recommended).
5563 nsresult DeleteFilesNoQuota(nsIFile& aFile) {
5564 AssertIsOnIOThread();
5566 QM_TRY_INSPECT(const auto& didExist,
5567 QM_OR_ELSE_WARN_IF(
5568 // Expression.
5569 MOZ_TO_RESULT(aFile.Remove(true)).map(Some<Ok>),
5570 // Predicate.
5571 IsFileNotFoundError,
5572 // Fallback.
5573 ErrToDefaultOk<Maybe<Ok>>));
5575 Unused << didExist;
5577 return NS_OK;
5580 nsresult DeleteFilesNoQuota(nsIFile* aDirectory, const nsAString& aFilename) {
5581 AssertIsOnIOThread();
5582 MOZ_ASSERT(aDirectory);
5583 MOZ_ASSERT(!aFilename.IsEmpty());
5585 // The current using function hasn't initialized the origin, so in here we
5586 // don't update the size of origin. Adding this assertion for preventing from
5587 // misusing.
5588 DebugOnly<QuotaManager*> quotaManager = QuotaManager::Get();
5589 MOZ_ASSERT(!quotaManager->IsTemporaryStorageInitializedInternal());
5591 QM_TRY_INSPECT(const auto& file, CloneFileAndAppend(*aDirectory, aFilename));
5593 QM_TRY(MOZ_TO_RESULT(DeleteFilesNoQuota(*file)));
5595 return NS_OK;
5598 // CreateMarkerFile and RemoveMarkerFile are a pair of functions to indicate
5599 // whether having removed all the files successfully. The marker file should
5600 // be checked before executing the next operation or initialization.
5601 Result<nsCOMPtr<nsIFile>, nsresult> CreateMarkerFile(
5602 nsIFile& aBaseDirectory, const nsAString& aDatabaseNameBase) {
5603 AssertIsOnIOThread();
5604 MOZ_ASSERT(!aDatabaseNameBase.IsEmpty());
5606 QM_TRY_INSPECT(
5607 const auto& markerFile,
5608 CloneFileAndAppend(aBaseDirectory,
5609 kIdbDeletionMarkerFilePrefix + aDatabaseNameBase));
5611 // Callers call this function without checking if the file already exists
5612 // (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we just want
5613 // to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the reports.
5615 // TODO: In theory if this file exists, then RemoveDatabaseFilesAndDirectory
5616 // should have cleaned it up, but obviously we can crash and not clean it up,
5617 // which is the whole point of the marker file. In that case, we'll realize
5618 // the marker file exists in OpenDatabaseOp::DoDatabaseWork or
5619 // GetUsageForOriginInternal and resume the removal by calling
5620 // RemoveDatabaseFilesAndDirectory again, but we will also try to create the
5621 // marker file again, so if we see this marker file, it is part
5622 // of our standard operating procedure to redundantly try and create the
5623 // marker here. We currently treat this as idempotent usage, but we could
5624 // add an additional argument to RemoveDatabaseFilesAndDirectory which would
5625 // indicate that we are resuming an unfinished removal, so the marker already
5626 // exists and doesn't have to be created, and change
5627 // QM_OR_ELSE_LOG_VERBOSE_IF to QM_OR_ELSE_WARN_IF in the end.
5628 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
5629 // Expression.
5630 MOZ_TO_RESULT(markerFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)),
5631 // Predicate.
5632 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
5633 // Fallback.
5634 ErrToDefaultOk<>));
5636 return markerFile;
5639 nsresult RemoveMarkerFile(nsIFile* aMarkerFile) {
5640 AssertIsOnIOThread();
5641 MOZ_ASSERT(aMarkerFile);
5643 DebugOnly<bool> exists;
5644 MOZ_ASSERT(NS_SUCCEEDED(aMarkerFile->Exists(&exists)));
5645 MOZ_ASSERT(exists);
5647 QM_TRY(MOZ_TO_RESULT(aMarkerFile->Remove(false)));
5649 return NS_OK;
5652 Result<Ok, nsresult> DeleteFileManagerDirectory(
5653 nsIFile& aFileManagerDirectory, QuotaManager* aQuotaManager,
5654 const PersistenceType aPersistenceType,
5655 const OriginMetadata& aOriginMetadata) {
5656 // XXX In theory, deleting can continue for other files in case of a failure,
5657 // leaving only those files behind that cause the problem actually. However,
5658 // the current architecture doesn't allow having more databases (for the same
5659 // name) on disk, so trying to delete as much as possible won't help much
5660 // because we need to delete entire .files directory in the end anyway.
5661 QM_TRY(DatabaseFileManager::TraverseFiles(
5662 aFileManagerDirectory,
5663 // KnownDirEntryOp
5664 [&aQuotaManager, aPersistenceType, &aOriginMetadata](
5665 nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
5666 if (isDirectory) {
5667 // The journal directory doesn't count towards quota.
5668 QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file)));
5671 // Stored files do count towards quota.
5672 QM_TRY_RETURN(
5673 MOZ_TO_RESULT(DeleteFile(file, aQuotaManager, aPersistenceType,
5674 aOriginMetadata, Idempotency::Yes)));
5676 // UnknownDirEntryOp
5677 [aPersistenceType, &aOriginMetadata](
5678 nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
5679 // Unknown files and directories don't count towards quota.
5681 if (isDirectory) {
5682 QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file)));
5685 QM_TRY_RETURN(MOZ_TO_RESULT(
5686 DeleteFile(file, /* doesn't count */ nullptr, aPersistenceType,
5687 aOriginMetadata, Idempotency::Yes)));
5688 }));
5690 QM_TRY_RETURN(MOZ_TO_RESULT(aFileManagerDirectory.Remove(false)));
5693 // Idempotently delete all the parts of an IndexedDB database including its
5694 // SQLite database file, its WAL journal, it's shared-memory file, and its
5695 // Blob/Files sub-directory. A marker file is created prior to performing the
5696 // deletion so that in the event we crash or fail to successfully delete the
5697 // database and its files, we will re-attempt the deletion the next time the
5698 // origin is initialized using this method. Because this means the method may be
5699 // called on a partially deleted database, this method uses DeleteFile which
5700 // succeeds when the file we ask it to delete does not actually exist. The
5701 // marker file is removed once deletion has successfully completed.
5702 nsresult RemoveDatabaseFilesAndDirectory(nsIFile& aBaseDirectory,
5703 const nsAString& aDatabaseFilenameBase,
5704 QuotaManager* aQuotaManager,
5705 const PersistenceType aPersistenceType,
5706 const OriginMetadata& aOriginMetadata,
5707 const nsAString& aDatabaseName) {
5708 AssertIsOnIOThread();
5709 MOZ_ASSERT(!aDatabaseFilenameBase.IsEmpty());
5711 AUTO_PROFILER_LABEL("RemoveDatabaseFilesAndDirectory", DOM);
5713 QM_TRY_UNWRAP(auto markerFile,
5714 CreateMarkerFile(aBaseDirectory, aDatabaseFilenameBase));
5716 // The database file counts towards quota.
5717 QM_TRY(MOZ_TO_RESULT(DeleteFile(
5718 aBaseDirectory, aDatabaseFilenameBase + kSQLiteSuffix, aQuotaManager,
5719 aPersistenceType, aOriginMetadata, Idempotency::Yes)));
5721 // .sqlite-journal files don't count towards quota.
5722 QM_TRY(MOZ_TO_RESULT(DeleteFile(aBaseDirectory,
5723 aDatabaseFilenameBase + kSQLiteJournalSuffix,
5724 /* doesn't count */ nullptr, aPersistenceType,
5725 aOriginMetadata, Idempotency::Yes)));
5727 // .sqlite-shm files don't count towards quota.
5728 QM_TRY(MOZ_TO_RESULT(DeleteFile(aBaseDirectory,
5729 aDatabaseFilenameBase + kSQLiteSHMSuffix,
5730 /* doesn't count */ nullptr, aPersistenceType,
5731 aOriginMetadata, Idempotency::Yes)));
5733 // .sqlite-wal files do count towards quota.
5734 QM_TRY(MOZ_TO_RESULT(DeleteFile(
5735 aBaseDirectory, aDatabaseFilenameBase + kSQLiteWALSuffix, aQuotaManager,
5736 aPersistenceType, aOriginMetadata, Idempotency::Yes)));
5738 // The files directory counts towards quota.
5739 QM_TRY_INSPECT(
5740 const auto& fmDirectory,
5741 CloneFileAndAppend(aBaseDirectory, aDatabaseFilenameBase +
5742 kFileManagerDirectoryNameSuffix));
5744 QM_TRY_INSPECT(const bool& exists,
5745 MOZ_TO_RESULT_INVOKE_MEMBER(fmDirectory, Exists));
5747 if (exists) {
5748 QM_TRY_INSPECT(const bool& isDirectory,
5749 MOZ_TO_RESULT_INVOKE_MEMBER(fmDirectory, IsDirectory));
5751 QM_TRY(OkIf(isDirectory), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
5753 QM_TRY(DeleteFileManagerDirectory(*fmDirectory, aQuotaManager,
5754 aPersistenceType, aOriginMetadata));
5757 IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
5758 MOZ_ASSERT_IF(aQuotaManager, mgr);
5760 if (mgr) {
5761 mgr->InvalidateFileManager(aPersistenceType, aOriginMetadata.mOrigin,
5762 aDatabaseName);
5765 QM_TRY(MOZ_TO_RESULT(RemoveMarkerFile(markerFile)));
5767 return NS_OK;
5770 /*******************************************************************************
5771 * Globals
5772 ******************************************************************************/
5774 // Counts the number of "live" Factory, FactoryOp and Database instances.
5775 uint64_t gBusyCount = 0;
5777 using FactoryOpArray = nsTArray<CheckedUnsafePtr<FactoryOp>>;
5779 StaticAutoPtr<FactoryOpArray> gFactoryOps;
5781 // Maps a database id to information about live database actors.
5782 using DatabaseActorHashtable =
5783 nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>;
5785 StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;
5787 StaticRefPtr<ConnectionPool> gConnectionPool;
5789 using DatabaseLoggingInfoHashtable =
5790 nsTHashMap<nsIDHashKey, DatabaseLoggingInfo*>;
5792 StaticAutoPtr<DatabaseLoggingInfoHashtable> gLoggingInfoHashtable;
5794 using TelemetryIdHashtable = nsTHashMap<nsUint32HashKey, uint32_t>;
5796 StaticAutoPtr<TelemetryIdHashtable> gTelemetryIdHashtable;
5798 // Protects all reads and writes to gTelemetryIdHashtable.
5799 StaticAutoPtr<Mutex> gTelemetryIdMutex;
5801 // For private browsing, maps the raw database names provided by content to a
5802 // replacement UUID in order to avoid exposing the name of the database on
5803 // disk or a directly derived value, such as the non-private-browsing
5804 // representation. This mapping will be the same for all databases with the
5805 // same name across all storage keys/origins for the lifetime of the IDB
5806 // QuotaClient. In tests, the QuotaClient may be created and destroyed multiple
5807 // times, but for normal browser use the QuotaClient will last until the
5808 // browser shuts down. Bug 1831835 will improve this implementation to avoid
5809 // using the same mapping across storage keys and to deal with the resulting
5810 // lifecycle issues of the additional memory use.
5811 using StorageDatabaseNameHashtable = nsTHashMap<nsString, nsString>;
5813 StaticAutoPtr<StorageDatabaseNameHashtable> gStorageDatabaseNameHashtable;
5815 // Protects all reads and writes to gStorageDatabaseNameHashtable.
5816 StaticAutoPtr<Mutex> gStorageDatabaseNameMutex;
5818 #ifdef DEBUG
5820 StaticRefPtr<DEBUGThreadSlower> gDEBUGThreadSlower;
5822 #endif // DEBUG
5824 void IncreaseBusyCount() {
5825 AssertIsOnBackgroundThread();
5827 // If this is the first instance then we need to do some initialization.
5828 if (!gBusyCount) {
5829 MOZ_ASSERT(!gFactoryOps);
5830 gFactoryOps = new FactoryOpArray();
5832 MOZ_ASSERT(!gLiveDatabaseHashtable);
5833 gLiveDatabaseHashtable = new DatabaseActorHashtable();
5835 MOZ_ASSERT(!gLoggingInfoHashtable);
5836 gLoggingInfoHashtable = new DatabaseLoggingInfoHashtable();
5838 #ifdef DEBUG
5839 if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
5840 NS_WARNING(
5841 "PBackground thread debugging enabled, priority has been "
5842 "modified!");
5843 nsCOMPtr<nsISupportsPriority> thread =
5844 do_QueryInterface(NS_GetCurrentThread());
5845 MOZ_ASSERT(thread);
5847 MOZ_ALWAYS_SUCCEEDS(thread->SetPriority(kDEBUGThreadPriority));
5850 if (kDEBUGThreadSleepMS) {
5851 NS_WARNING(
5852 "PBackground thread debugging enabled, sleeping after every "
5853 "event!");
5854 nsCOMPtr<nsIThreadInternal> thread =
5855 do_QueryInterface(NS_GetCurrentThread());
5856 MOZ_ASSERT(thread);
5858 gDEBUGThreadSlower = new DEBUGThreadSlower();
5860 MOZ_ALWAYS_SUCCEEDS(thread->AddObserver(gDEBUGThreadSlower));
5862 #endif // DEBUG
5865 gBusyCount++;
5868 void DecreaseBusyCount() {
5869 AssertIsOnBackgroundThread();
5870 MOZ_ASSERT(gBusyCount);
5872 // Clean up if there are no more instances.
5873 if (--gBusyCount == 0) {
5874 MOZ_ASSERT(gLoggingInfoHashtable);
5875 gLoggingInfoHashtable = nullptr;
5877 MOZ_ASSERT(gLiveDatabaseHashtable);
5878 MOZ_ASSERT(!gLiveDatabaseHashtable->Count());
5879 gLiveDatabaseHashtable = nullptr;
5881 MOZ_ASSERT(gFactoryOps);
5882 MOZ_ASSERT(gFactoryOps->IsEmpty());
5883 gFactoryOps = nullptr;
5885 #ifdef DEBUG
5886 if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
5887 nsCOMPtr<nsISupportsPriority> thread =
5888 do_QueryInterface(NS_GetCurrentThread());
5889 MOZ_ASSERT(thread);
5891 MOZ_ALWAYS_SUCCEEDS(
5892 thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL));
5895 if (kDEBUGThreadSleepMS) {
5896 MOZ_ASSERT(gDEBUGThreadSlower);
5898 nsCOMPtr<nsIThreadInternal> thread =
5899 do_QueryInterface(NS_GetCurrentThread());
5900 MOZ_ASSERT(thread);
5902 MOZ_ALWAYS_SUCCEEDS(thread->RemoveObserver(gDEBUGThreadSlower));
5904 gDEBUGThreadSlower = nullptr;
5906 #endif // DEBUG
5910 template <typename Condition>
5911 void InvalidateLiveDatabasesMatching(const Condition& aCondition) {
5912 AssertIsOnBackgroundThread();
5914 if (!gLiveDatabaseHashtable) {
5915 return;
5918 // Invalidating a Database will cause it to be removed from the
5919 // gLiveDatabaseHashtable entries' mLiveDatabases, and, if it was the last
5920 // element in mLiveDatabases, to remove the whole hashtable entry. Therefore,
5921 // we need to make a temporary list of the databases to invalidate to avoid
5922 // iterator invalidation.
5924 nsTArray<SafeRefPtr<Database>> databases;
5926 for (const auto& liveDatabasesEntry : gLiveDatabaseHashtable->Values()) {
5927 for (const auto& database : liveDatabasesEntry->mLiveDatabases) {
5928 if (aCondition(*database)) {
5929 databases.AppendElement(
5930 SafeRefPtr{database.get(), AcquireStrongRefFromRawPtr{}});
5935 for (const auto& database : databases) {
5936 database->Invalidate();
5940 uint32_t TelemetryIdForFile(nsIFile* aFile) {
5941 // May be called on any thread!
5943 MOZ_ASSERT(aFile);
5944 MOZ_ASSERT(gTelemetryIdMutex);
5946 // The storage directory is structured like this:
5948 // <profile>/storage/<persistence>/<origin>/idb/<filename>.sqlite
5950 // For the purposes of this function we're only concerned with the
5951 // <persistence>, <origin>, and <filename> pieces.
5953 nsString filename;
5954 MOZ_ALWAYS_SUCCEEDS(aFile->GetLeafName(filename));
5956 // Make sure we were given a database file.
5957 MOZ_ASSERT(StringEndsWith(filename, kSQLiteSuffix));
5959 filename.Truncate(filename.Length() - kSQLiteSuffix.Length());
5961 // Get the "idb" directory.
5962 nsCOMPtr<nsIFile> idbDirectory;
5963 MOZ_ALWAYS_SUCCEEDS(aFile->GetParent(getter_AddRefs(idbDirectory)));
5965 DebugOnly<nsString> idbLeafName;
5966 MOZ_ASSERT(NS_SUCCEEDED(idbDirectory->GetLeafName(idbLeafName)));
5967 MOZ_ASSERT(static_cast<nsString&>(idbLeafName).EqualsLiteral("idb"));
5969 // Get the <origin> directory.
5970 nsCOMPtr<nsIFile> originDirectory;
5971 MOZ_ALWAYS_SUCCEEDS(idbDirectory->GetParent(getter_AddRefs(originDirectory)));
5973 nsString origin;
5974 MOZ_ALWAYS_SUCCEEDS(originDirectory->GetLeafName(origin));
5976 // Any databases in these directories are owned by the application and should
5977 // not have their filenames masked. Hopefully they also appear in the
5978 // Telemetry.cpp whitelist.
5979 if (origin.EqualsLiteral("chrome") ||
5980 origin.EqualsLiteral("moz-safe-about+home")) {
5981 return 0;
5984 // Get the <persistence> directory.
5985 nsCOMPtr<nsIFile> persistenceDirectory;
5986 MOZ_ALWAYS_SUCCEEDS(
5987 originDirectory->GetParent(getter_AddRefs(persistenceDirectory)));
5989 nsString persistence;
5990 MOZ_ALWAYS_SUCCEEDS(persistenceDirectory->GetLeafName(persistence));
5992 constexpr auto separator = u"*"_ns;
5994 uint32_t hashValue =
5995 HashString(persistence + separator + origin + separator + filename);
5997 MutexAutoLock lock(*gTelemetryIdMutex);
5999 if (!gTelemetryIdHashtable) {
6000 gTelemetryIdHashtable = new TelemetryIdHashtable();
6003 return gTelemetryIdHashtable->LookupOrInsertWith(hashValue, [] {
6004 static uint32_t sNextId = 1;
6006 // We're locked, no need for atomics.
6007 return sNextId++;
6011 nsAutoString GetDatabaseFilenameBase(const nsAString& aDatabaseName,
6012 bool aIsPrivate) {
6013 nsAutoString databaseFilenameBase;
6015 if (aIsPrivate) {
6016 MOZ_DIAGNOSTIC_ASSERT(gStorageDatabaseNameMutex);
6018 MutexAutoLock lock(*gStorageDatabaseNameMutex);
6020 if (!gStorageDatabaseNameHashtable) {
6021 gStorageDatabaseNameHashtable = new StorageDatabaseNameHashtable();
6024 databaseFilenameBase.Append(
6025 gStorageDatabaseNameHashtable->LookupOrInsertWith(aDatabaseName, []() {
6026 return NSID_TrimBracketsUTF16(nsID::GenerateUUID());
6027 }));
6029 return databaseFilenameBase;
6032 // WARNING: do not change this hash function. See the comment in HashName()
6033 // for details.
6034 databaseFilenameBase.AppendInt(HashName(aDatabaseName));
6036 nsAutoCString escapedName;
6037 if (!NS_Escape(NS_ConvertUTF16toUTF8(aDatabaseName), escapedName,
6038 url_XPAlphas)) {
6039 MOZ_CRASH("Can't escape database name!");
6042 const char* forwardIter = escapedName.BeginReading();
6043 const char* backwardIter = escapedName.EndReading() - 1;
6045 nsAutoCString substring;
6046 while (forwardIter <= backwardIter && substring.Length() < 21) {
6047 if (substring.Length() % 2) {
6048 substring.Append(*backwardIter--);
6049 } else {
6050 substring.Append(*forwardIter++);
6054 databaseFilenameBase.AppendASCII(substring.get(), substring.Length());
6056 return databaseFilenameBase;
6059 const CommonIndexOpenCursorParams& GetCommonIndexOpenCursorParams(
6060 const OpenCursorParams& aParams) {
6061 switch (aParams.type()) {
6062 case OpenCursorParams::TIndexOpenCursorParams:
6063 return aParams.get_IndexOpenCursorParams().commonIndexParams();
6064 case OpenCursorParams::TIndexOpenKeyCursorParams:
6065 return aParams.get_IndexOpenKeyCursorParams().commonIndexParams();
6066 default:
6067 MOZ_CRASH("Should never get here!");
6071 const CommonOpenCursorParams& GetCommonOpenCursorParams(
6072 const OpenCursorParams& aParams) {
6073 switch (aParams.type()) {
6074 case OpenCursorParams::TObjectStoreOpenCursorParams:
6075 return aParams.get_ObjectStoreOpenCursorParams().commonParams();
6076 case OpenCursorParams::TObjectStoreOpenKeyCursorParams:
6077 return aParams.get_ObjectStoreOpenKeyCursorParams().commonParams();
6078 case OpenCursorParams::TIndexOpenCursorParams:
6079 case OpenCursorParams::TIndexOpenKeyCursorParams:
6080 return GetCommonIndexOpenCursorParams(aParams).commonParams();
6081 default:
6082 MOZ_CRASH("Should never get here!");
6086 // TODO: Using nsCString as a return type here seems to lead to a dependency on
6087 // some temporaries, which I did not expect. Is it a good idea that the default
6088 // operator+ behaviour constructs such strings? It is certainly useful as an
6089 // optimization, but this should be better done via an appropriately named
6090 // function rather than an operator.
6091 nsAutoCString MakeColumnPairSelectionList(
6092 const nsLiteralCString& aPlainColumnName,
6093 const nsLiteralCString& aLocaleAwareColumnName,
6094 const nsLiteralCString& aSortColumnAlias, const bool aIsLocaleAware) {
6095 return aPlainColumnName +
6096 (aIsLocaleAware ? EmptyCString() : " as "_ns + aSortColumnAlias) +
6097 ", "_ns + aLocaleAwareColumnName +
6098 (aIsLocaleAware ? " as "_ns + aSortColumnAlias : EmptyCString());
6101 constexpr bool IsIncreasingOrder(const IDBCursorDirection aDirection) {
6102 MOZ_ASSERT(aDirection == IDBCursorDirection::Next ||
6103 aDirection == IDBCursorDirection::Nextunique ||
6104 aDirection == IDBCursorDirection::Prev ||
6105 aDirection == IDBCursorDirection::Prevunique);
6107 return aDirection == IDBCursorDirection::Next ||
6108 aDirection == IDBCursorDirection::Nextunique;
6111 constexpr bool IsUnique(const IDBCursorDirection aDirection) {
6112 MOZ_ASSERT(aDirection == IDBCursorDirection::Next ||
6113 aDirection == IDBCursorDirection::Nextunique ||
6114 aDirection == IDBCursorDirection::Prev ||
6115 aDirection == IDBCursorDirection::Prevunique);
6117 return aDirection == IDBCursorDirection::Nextunique ||
6118 aDirection == IDBCursorDirection::Prevunique;
6121 // TODO: In principle, this could be constexpr, if operator+(nsLiteralCString,
6122 // nsLiteralCString) were constexpr and returned a literal type.
6123 nsAutoCString MakeDirectionClause(const IDBCursorDirection aDirection) {
6124 return " ORDER BY "_ns + kColumnNameKey +
6125 (IsIncreasingOrder(aDirection) ? " ASC"_ns : " DESC"_ns);
6128 enum struct ComparisonOperator {
6129 LessThan,
6130 LessOrEquals,
6131 Equals,
6132 GreaterThan,
6133 GreaterOrEquals,
6136 constexpr nsLiteralCString GetComparisonOperatorString(
6137 const ComparisonOperator aComparisonOperator) {
6138 switch (aComparisonOperator) {
6139 case ComparisonOperator::LessThan:
6140 return "<"_ns;
6141 case ComparisonOperator::LessOrEquals:
6142 return "<="_ns;
6143 case ComparisonOperator::Equals:
6144 return "=="_ns;
6145 case ComparisonOperator::GreaterThan:
6146 return ">"_ns;
6147 case ComparisonOperator::GreaterOrEquals:
6148 return ">="_ns;
6151 // TODO: This is just to silence the "control reaches end of non-void
6152 // function" warning. Cannot use MOZ_CRASH in a constexpr function,
6153 // unfortunately.
6154 return ""_ns;
6157 nsAutoCString GetKeyClause(const nsACString& aColumnName,
6158 const ComparisonOperator aComparisonOperator,
6159 const nsLiteralCString& aStmtParamName) {
6160 return aColumnName + " "_ns +
6161 GetComparisonOperatorString(aComparisonOperator) + " :"_ns +
6162 aStmtParamName;
6165 nsAutoCString GetSortKeyClause(const ComparisonOperator aComparisonOperator,
6166 const nsLiteralCString& aStmtParamName) {
6167 return GetKeyClause(kColumnNameAliasSortKey, aComparisonOperator,
6168 aStmtParamName);
6171 template <IDBCursorType CursorType>
6172 struct PopulateResponseHelper;
6174 struct CommonPopulateResponseHelper {
6175 explicit CommonPopulateResponseHelper(
6176 const TransactionDatabaseOperationBase& aOp)
6177 : mOp{aOp} {}
6179 nsresult GetKeys(mozIStorageStatement* const aStmt,
6180 Key* const aOptOutSortKey) {
6181 QM_TRY(MOZ_TO_RESULT(GetCommonKeys(aStmt)));
6183 if (aOptOutSortKey) {
6184 *aOptOutSortKey = mPosition;
6187 return NS_OK;
6190 nsresult GetCommonKeys(mozIStorageStatement* const aStmt) {
6191 MOZ_ASSERT(mPosition.IsUnset());
6193 QM_TRY(MOZ_TO_RESULT(mPosition.SetFromStatement(aStmt, 0)));
6195 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
6196 "PRELOAD: Populating response with key %s", "Populating%.0s",
6197 IDB_LOG_ID_STRING(mOp.BackgroundChildLoggingId()),
6198 mOp.TransactionLoggingSerialNumber(), mOp.LoggingSerialNumber(),
6199 mPosition.GetBuffer().get());
6201 return NS_OK;
6204 template <typename Response>
6205 void FillKeys(Response& aResponse) {
6206 MOZ_ASSERT(!mPosition.IsUnset());
6207 aResponse.key() = std::move(mPosition);
6210 template <typename Response>
6211 static size_t GetKeySize(const Response& aResponse) {
6212 return aResponse.key().GetBuffer().Length();
6215 protected:
6216 const Key& GetPosition() const { return mPosition; }
6218 private:
6219 const TransactionDatabaseOperationBase& mOp;
6220 Key mPosition;
6223 struct IndexPopulateResponseHelper : CommonPopulateResponseHelper {
6224 using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
6226 nsresult GetKeys(mozIStorageStatement* const aStmt,
6227 Key* const aOptOutSortKey) {
6228 MOZ_ASSERT(mLocaleAwarePosition.IsUnset());
6229 MOZ_ASSERT(mObjectStorePosition.IsUnset());
6231 QM_TRY(MOZ_TO_RESULT(CommonPopulateResponseHelper::GetCommonKeys(aStmt)));
6233 QM_TRY(MOZ_TO_RESULT(mLocaleAwarePosition.SetFromStatement(aStmt, 1)));
6235 QM_TRY(MOZ_TO_RESULT(mObjectStorePosition.SetFromStatement(aStmt, 2)));
6237 if (aOptOutSortKey) {
6238 *aOptOutSortKey =
6239 mLocaleAwarePosition.IsUnset() ? GetPosition() : mLocaleAwarePosition;
6242 return NS_OK;
6245 template <typename Response>
6246 void FillKeys(Response& aResponse) {
6247 MOZ_ASSERT(!mLocaleAwarePosition.IsUnset());
6248 MOZ_ASSERT(!mObjectStorePosition.IsUnset());
6250 CommonPopulateResponseHelper::FillKeys(aResponse);
6251 aResponse.sortKey() = std::move(mLocaleAwarePosition);
6252 aResponse.objectKey() = std::move(mObjectStorePosition);
6255 template <typename Response>
6256 static size_t GetKeySize(Response& aResponse) {
6257 return CommonPopulateResponseHelper::GetKeySize(aResponse) +
6258 aResponse.sortKey().GetBuffer().Length() +
6259 aResponse.objectKey().GetBuffer().Length();
6262 private:
6263 Key mLocaleAwarePosition, mObjectStorePosition;
6266 struct KeyPopulateResponseHelper {
6267 static constexpr nsresult MaybeGetCloneInfo(
6268 mozIStorageStatement* const /*aStmt*/, const CursorBase& /*aCursor*/) {
6269 return NS_OK;
6272 template <typename Response>
6273 static constexpr void MaybeFillCloneInfo(Response& /*aResponse*/,
6274 FilesArray* const /*aFiles*/) {}
6276 template <typename Response>
6277 static constexpr size_t MaybeGetCloneInfoSize(const Response& /*aResponse*/) {
6278 return 0;
6282 template <bool StatementHasIndexKeyBindings>
6283 struct ValuePopulateResponseHelper {
6284 nsresult MaybeGetCloneInfo(mozIStorageStatement* const aStmt,
6285 const ValueCursorBase& aCursor) {
6286 constexpr auto offset = StatementHasIndexKeyBindings ? 2 : 0;
6288 QM_TRY_UNWRAP(auto cloneInfo,
6289 GetStructuredCloneReadInfoFromStatement(
6290 aStmt, 2 + offset, 1 + offset, *aCursor.mFileManager));
6292 mCloneInfo.init(std::move(cloneInfo));
6294 if (mCloneInfo->HasPreprocessInfo()) {
6295 IDB_WARNING("Preprocessing for cursors not yet implemented!");
6296 return NS_ERROR_NOT_IMPLEMENTED;
6299 return NS_OK;
6302 template <typename Response>
6303 void MaybeFillCloneInfo(Response& aResponse, FilesArray* const aFiles) {
6304 auto cloneInfo = mCloneInfo.release();
6305 aResponse.cloneInfo().data().data = cloneInfo.ReleaseData();
6306 aFiles->AppendElement(cloneInfo.ReleaseFiles());
6309 template <typename Response>
6310 static size_t MaybeGetCloneInfoSize(const Response& aResponse) {
6311 return aResponse.cloneInfo().data().data.Size();
6314 private:
6315 LazyInitializedOnceEarlyDestructible<const StructuredCloneReadInfoParent>
6316 mCloneInfo;
6319 template <>
6320 struct PopulateResponseHelper<IDBCursorType::ObjectStore>
6321 : ValuePopulateResponseHelper<false>, CommonPopulateResponseHelper {
6322 using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
6324 static auto& GetTypedResponse(CursorResponse* const aResponse) {
6325 return aResponse->get_ArrayOfObjectStoreCursorResponse();
6329 template <>
6330 struct PopulateResponseHelper<IDBCursorType::ObjectStoreKey>
6331 : KeyPopulateResponseHelper, CommonPopulateResponseHelper {
6332 using CommonPopulateResponseHelper::CommonPopulateResponseHelper;
6334 static auto& GetTypedResponse(CursorResponse* const aResponse) {
6335 return aResponse->get_ArrayOfObjectStoreKeyCursorResponse();
6339 template <>
6340 struct PopulateResponseHelper<IDBCursorType::Index>
6341 : ValuePopulateResponseHelper<true>, IndexPopulateResponseHelper {
6342 using IndexPopulateResponseHelper::IndexPopulateResponseHelper;
6344 static auto& GetTypedResponse(CursorResponse* const aResponse) {
6345 return aResponse->get_ArrayOfIndexCursorResponse();
6349 template <>
6350 struct PopulateResponseHelper<IDBCursorType::IndexKey>
6351 : KeyPopulateResponseHelper, IndexPopulateResponseHelper {
6352 using IndexPopulateResponseHelper::IndexPopulateResponseHelper;
6354 static auto& GetTypedResponse(CursorResponse* const aResponse) {
6355 return aResponse->get_ArrayOfIndexKeyCursorResponse();
6359 nsresult DispatchAndReturnFileReferences(
6360 PersistenceType aPersistenceType, const nsACString& aOrigin,
6361 const nsAString& aDatabaseName, const int64_t aFileId,
6362 int32_t* const aMemRefCnt, int32_t* const aDBRefCnt, bool* const aResult) {
6363 AssertIsOnBackgroundThread();
6364 MOZ_ASSERT(aMemRefCnt);
6365 MOZ_ASSERT(aDBRefCnt);
6366 MOZ_ASSERT(aResult);
6368 *aResult = false;
6369 *aMemRefCnt = -1;
6370 *aDBRefCnt = -1;
6372 mozilla::Monitor monitor MOZ_ANNOTATED(__func__);
6373 bool waiting = true;
6375 auto lambda = [&] {
6376 AssertIsOnIOThread();
6379 IndexedDatabaseManager* const mgr = IndexedDatabaseManager::Get();
6380 MOZ_ASSERT(mgr);
6382 const SafeRefPtr<DatabaseFileManager> fileManager =
6383 mgr->GetFileManager(aPersistenceType, aOrigin, aDatabaseName);
6385 if (fileManager) {
6386 const SafeRefPtr<DatabaseFileInfo> fileInfo =
6387 fileManager->GetFileInfo(aFileId);
6389 if (fileInfo) {
6390 fileInfo->GetReferences(aMemRefCnt, aDBRefCnt);
6392 if (*aMemRefCnt != -1) {
6393 // We added an extra temp ref, so account for that accordingly.
6394 (*aMemRefCnt)--;
6397 *aResult = true;
6402 mozilla::MonitorAutoLock lock(monitor);
6403 MOZ_ASSERT(waiting);
6405 waiting = false;
6406 lock.Notify();
6409 QuotaManager* const quotaManager = QuotaManager::Get();
6410 MOZ_ASSERT(quotaManager);
6412 // XXX can't we simply use NS_DispatchAndSpinEventLoopUntilComplete instead of
6413 // using a monitor?
6414 QM_TRY(MOZ_TO_RESULT(quotaManager->IOThread()->Dispatch(
6415 NS_NewRunnableFunction("GetFileReferences", std::move(lambda)),
6416 NS_DISPATCH_NORMAL)));
6418 mozilla::MonitorAutoLock autolock(monitor);
6419 while (waiting) {
6420 autolock.Wait();
6423 return NS_OK;
6426 class DeserializeIndexValueHelper final : public Runnable {
6427 public:
6428 DeserializeIndexValueHelper(int64_t aIndexID, const KeyPath& aKeyPath,
6429 bool aMultiEntry, const nsACString& aLocale,
6430 StructuredCloneReadInfoParent& aCloneReadInfo,
6431 nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
6432 : Runnable("DeserializeIndexValueHelper"),
6433 mMonitor("DeserializeIndexValueHelper::mMonitor"),
6434 mIndexID(aIndexID),
6435 mKeyPath(aKeyPath),
6436 mMultiEntry(aMultiEntry),
6437 mLocale(aLocale),
6438 mCloneReadInfo(aCloneReadInfo),
6439 mUpdateInfoArray(aUpdateInfoArray),
6440 mStatus(NS_ERROR_FAILURE) {}
6442 nsresult DispatchAndWait() {
6443 // FIXME(Bug 1637530) Re-enable optimization using a non-system-principaled
6444 // JS context
6445 #if 0
6446 // We don't need to go to the main-thread and use the sandbox. Let's create
6447 // the updateInfo data here.
6448 if (!mCloneReadInfo.Data().Size()) {
6449 AutoJSAPI jsapi;
6450 jsapi.Init();
6452 JS::Rooted<JS::Value> value(jsapi.cx());
6453 value.setUndefined();
6455 ErrorResult rv;
6456 IDBObjectStore::AppendIndexUpdateInfo(
6457 mIndexID, mKeyPath, mMultiEntry, &mUpdateInfoArray,
6458 /* aAutoIncrementedObjectStoreKeyPath */ VoidString(), &rv);
6459 return rv.Failed() ? rv.StealNSResult() : NS_OK;
6461 #endif
6463 // The operation will continue on the main-thread.
6465 MOZ_ASSERT(!(mCloneReadInfo.Data().Size() % sizeof(uint64_t)));
6467 MonitorAutoLock lock(mMonitor);
6469 RefPtr<Runnable> self = this;
6470 QM_TRY(MOZ_TO_RESULT(SchedulerGroup::Dispatch(self.forget())));
6472 lock.Wait();
6473 return mStatus;
6476 NS_IMETHOD
6477 Run() override {
6478 MOZ_ASSERT(NS_IsMainThread());
6480 AutoJSAPI jsapi;
6481 jsapi.Init();
6482 JSContext* const cx = jsapi.cx();
6484 JS::Rooted<JSObject*> global(cx, GetSandbox(cx));
6486 QM_TRY(OkIf(global), NS_OK,
6487 [this](const NotOk) { OperationCompleted(NS_ERROR_FAILURE); });
6489 const JSAutoRealm ar(cx, global);
6491 JS::Rooted<JS::Value> value(cx);
6492 QM_TRY(MOZ_TO_RESULT(DeserializeIndexValue(cx, &value)), NS_OK,
6493 [this](const nsresult rv) { OperationCompleted(rv); });
6495 ErrorResult errorResult;
6496 IDBObjectStore::AppendIndexUpdateInfo(
6497 mIndexID, mKeyPath, mMultiEntry, mLocale, cx, value, &mUpdateInfoArray,
6498 /* aAutoIncrementedObjectStoreKeyPath */ VoidString(), &errorResult);
6499 QM_TRY(OkIf(!errorResult.Failed()), NS_OK,
6500 ([this, &errorResult](const NotOk) {
6501 OperationCompleted(errorResult.StealNSResult());
6502 }));
6504 OperationCompleted(NS_OK);
6505 return NS_OK;
6508 private:
6509 nsresult DeserializeIndexValue(JSContext* aCx,
6510 JS::MutableHandle<JS::Value> aValue) {
6511 static const JSStructuredCloneCallbacks callbacks = {
6512 StructuredCloneReadCallback<StructuredCloneReadInfoParent>,
6513 nullptr,
6514 nullptr,
6515 nullptr,
6516 nullptr,
6517 nullptr,
6518 nullptr,
6519 nullptr};
6521 if (!JS_ReadStructuredClone(
6522 aCx, mCloneReadInfo.Data(), JS_STRUCTURED_CLONE_VERSION,
6523 JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
6524 JS::CloneDataPolicy(), &callbacks, &mCloneReadInfo)) {
6525 return NS_ERROR_DOM_DATA_CLONE_ERR;
6528 return NS_OK;
6531 void OperationCompleted(nsresult aStatus) {
6532 mStatus = aStatus;
6534 MonitorAutoLock lock(mMonitor);
6535 lock.Notify();
6538 Monitor mMonitor MOZ_UNANNOTATED;
6540 const int64_t mIndexID;
6541 const KeyPath& mKeyPath;
6542 const bool mMultiEntry;
6543 const nsCString mLocale;
6544 StructuredCloneReadInfoParent& mCloneReadInfo;
6545 nsTArray<IndexUpdateInfo>& mUpdateInfoArray;
6546 nsresult mStatus;
6549 auto DeserializeIndexValueToUpdateInfos(
6550 int64_t aIndexID, const KeyPath& aKeyPath, bool aMultiEntry,
6551 const nsACString& aLocale, StructuredCloneReadInfoParent& aCloneReadInfo) {
6552 MOZ_ASSERT(!NS_IsMainThread());
6554 using ArrayType = AutoTArray<IndexUpdateInfo, 32>;
6555 using ResultType = Result<ArrayType, nsresult>;
6557 ArrayType updateInfoArray;
6558 const auto helper = MakeRefPtr<DeserializeIndexValueHelper>(
6559 aIndexID, aKeyPath, aMultiEntry, aLocale, aCloneReadInfo,
6560 updateInfoArray);
6561 const nsresult rv = helper->DispatchAndWait();
6562 return NS_FAILED(rv) ? Err(rv) : ResultType{std::move(updateInfoArray)};
6565 bool IsSome(
6566 const Maybe<CachingDatabaseConnection::BorrowedStatement>& aMaybeStmt) {
6567 return aMaybeStmt.isSome();
6570 already_AddRefed<nsIThreadPool> MakeConnectionIOTarget() {
6571 nsCOMPtr<nsIThreadPool> threadPool = new nsThreadPool();
6573 MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(kMaxConnectionThreadCount));
6575 MOZ_ALWAYS_SUCCEEDS(
6576 threadPool->SetIdleThreadLimit(kMaxIdleConnectionThreadCount));
6578 MOZ_ALWAYS_SUCCEEDS(
6579 threadPool->SetIdleThreadTimeout(kConnectionThreadIdleMS));
6581 MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("IndexedDB IO"_ns));
6583 return threadPool.forget();
6586 } // namespace
6588 /*******************************************************************************
6589 * Exported functions
6590 ******************************************************************************/
6592 already_AddRefed<PBackgroundIDBFactoryParent> AllocPBackgroundIDBFactoryParent(
6593 const LoggingInfo& aLoggingInfo, const nsACString& aSystemLocale) {
6594 AssertIsOnBackgroundThread();
6596 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
6597 return nullptr;
6600 if (NS_AUUF_OR_WARN_IF(!aLoggingInfo.nextTransactionSerialNumber()) ||
6601 NS_AUUF_OR_WARN_IF(
6602 !aLoggingInfo.nextVersionChangeTransactionSerialNumber()) ||
6603 NS_AUUF_OR_WARN_IF(!aLoggingInfo.nextRequestSerialNumber())) {
6604 return nullptr;
6607 SafeRefPtr<Factory> actor = Factory::Create(aLoggingInfo, aSystemLocale);
6608 MOZ_ASSERT(actor);
6610 return actor.forget();
6613 bool RecvPBackgroundIDBFactoryConstructor(
6614 PBackgroundIDBFactoryParent* aActor, const LoggingInfo& /* aLoggingInfo */,
6615 const nsACString& /* aSystemLocale */) {
6616 AssertIsOnBackgroundThread();
6617 MOZ_ASSERT(aActor);
6618 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6620 return true;
6623 PBackgroundIndexedDBUtilsParent* AllocPBackgroundIndexedDBUtilsParent() {
6624 AssertIsOnBackgroundThread();
6626 RefPtr<Utils> actor = new Utils();
6628 return actor.forget().take();
6631 bool DeallocPBackgroundIndexedDBUtilsParent(
6632 PBackgroundIndexedDBUtilsParent* aActor) {
6633 AssertIsOnBackgroundThread();
6634 MOZ_ASSERT(aActor);
6636 RefPtr<Utils> actor = dont_AddRef(static_cast<Utils*>(aActor));
6637 return true;
6640 bool RecvFlushPendingFileDeletions() {
6641 AssertIsOnBackgroundThread();
6643 if (QuotaClient* quotaClient = QuotaClient::GetInstance()) {
6644 QM_WARNONLY_TRY(QM_TO_RESULT(quotaClient->FlushPendingFileDeletions()));
6647 return true;
6650 RefPtr<mozilla::dom::quota::Client> CreateQuotaClient() {
6651 AssertIsOnBackgroundThread();
6653 return MakeRefPtr<QuotaClient>();
6656 nsresult DatabaseFileManager::AsyncDeleteFile(int64_t aFileId) {
6657 AssertIsOnBackgroundThread();
6658 MOZ_ASSERT(!mFileInfos.Contains(aFileId));
6660 QuotaClient* quotaClient = QuotaClient::GetInstance();
6661 if (quotaClient) {
6662 QM_TRY(MOZ_TO_RESULT(quotaClient->AsyncDeleteFile(this, aFileId)));
6665 return NS_OK;
6668 /*******************************************************************************
6669 * DatabaseConnection implementation
6670 ******************************************************************************/
6672 DatabaseConnection::DatabaseConnection(
6673 MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection,
6674 MovingNotNull<SafeRefPtr<DatabaseFileManager>> aFileManager)
6675 : CachingDatabaseConnection(std::move(aStorageConnection)),
6676 mFileManager(std::move(aFileManager)),
6677 mInReadTransaction(false),
6678 mInWriteTransaction(false)
6679 #ifdef DEBUG
6681 mDEBUGSavepointCount(0)
6682 #endif
6684 AssertIsOnConnectionThread();
6685 MOZ_ASSERT(mFileManager);
6688 DatabaseConnection::~DatabaseConnection() {
6689 MOZ_ASSERT(!mFileManager);
6690 MOZ_ASSERT(!mUpdateRefcountFunction);
6691 MOZ_DIAGNOSTIC_ASSERT(!mInWriteTransaction);
6692 MOZ_ASSERT(!mDEBUGSavepointCount);
6695 nsresult DatabaseConnection::Init() {
6696 AssertIsOnConnectionThread();
6697 MOZ_ASSERT(!mInReadTransaction);
6698 MOZ_ASSERT(!mInWriteTransaction);
6700 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN;"_ns)));
6702 mInReadTransaction = true;
6704 return NS_OK;
6707 nsresult DatabaseConnection::BeginWriteTransaction() {
6708 AssertIsOnConnectionThread();
6709 MOZ_ASSERT(HasStorageConnection());
6710 MOZ_ASSERT(mInReadTransaction);
6711 MOZ_ASSERT(!mInWriteTransaction);
6713 AUTO_PROFILER_LABEL("DatabaseConnection::BeginWriteTransaction", DOM);
6715 // Release our read locks.
6716 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("ROLLBACK;"_ns)));
6718 mInReadTransaction = false;
6720 if (!mUpdateRefcountFunction) {
6721 MOZ_ASSERT(mFileManager);
6723 RefPtr<UpdateRefcountFunction> function =
6724 new UpdateRefcountFunction(this, **mFileManager);
6726 QM_TRY(MOZ_TO_RESULT(MutableStorageConnection().CreateFunction(
6727 "update_refcount"_ns,
6728 /* aNumArguments */ 2, function)));
6730 mUpdateRefcountFunction = std::move(function);
6733 // This one cannot obviously use ExecuteCachedStatement because of the custom
6734 // error handling for Execute only. If only Execute can produce
6735 // NS_ERROR_STORAGE_BUSY, we could actually use ExecuteCachedStatement and
6736 // simplify this.
6737 QM_TRY_INSPECT(const auto& beginStmt,
6738 BorrowCachedStatement("BEGIN IMMEDIATE;"_ns));
6740 QM_TRY(QM_OR_ELSE_WARN_IF(
6741 // Expression.
6742 MOZ_TO_RESULT(beginStmt->Execute()),
6743 // Predicate.
6744 IsSpecificError<NS_ERROR_STORAGE_BUSY>,
6745 // Fallback.
6746 ([&beginStmt](nsresult rv) {
6747 NS_WARNING(
6748 "Received NS_ERROR_STORAGE_BUSY when attempting to start write "
6749 "transaction, retrying for up to 10 seconds");
6751 // Another thread must be using the database. Wait up to 10 seconds
6752 // for that to complete.
6753 const TimeStamp start = TimeStamp::NowLoRes();
6755 while (true) {
6756 PR_Sleep(PR_MillisecondsToInterval(100));
6758 rv = beginStmt->Execute();
6759 if (rv != NS_ERROR_STORAGE_BUSY ||
6760 TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
6761 break;
6765 return MOZ_TO_RESULT(rv);
6766 })));
6768 mInWriteTransaction = true;
6770 return NS_OK;
6773 nsresult DatabaseConnection::CommitWriteTransaction() {
6774 AssertIsOnConnectionThread();
6775 MOZ_ASSERT(HasStorageConnection());
6776 MOZ_ASSERT(!mInReadTransaction);
6777 MOZ_ASSERT(mInWriteTransaction);
6779 AUTO_PROFILER_LABEL("DatabaseConnection::CommitWriteTransaction", DOM);
6781 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("COMMIT;"_ns)));
6783 mInWriteTransaction = false;
6784 return NS_OK;
6787 void DatabaseConnection::RollbackWriteTransaction() {
6788 AssertIsOnConnectionThread();
6789 MOZ_ASSERT(!mInReadTransaction);
6790 MOZ_DIAGNOSTIC_ASSERT(HasStorageConnection());
6792 AUTO_PROFILER_LABEL("DatabaseConnection::RollbackWriteTransaction", DOM);
6794 if (!mInWriteTransaction) {
6795 return;
6798 QM_WARNONLY_TRY(
6799 BorrowCachedStatement("ROLLBACK;"_ns)
6800 .andThen([&self = *this](const auto& stmt) -> Result<Ok, nsresult> {
6801 // This may fail if SQLite already rolled back the transaction
6802 // so ignore any errors.
6804 // XXX ROLLBACK can fail quite normmally if a previous statement
6805 // failed to execute successfully so SQLite rolled back the
6806 // transaction already. However, if it failed because of some other
6807 // reason, we could try to close the connection.
6808 Unused << stmt->Execute();
6810 self.mInWriteTransaction = false;
6811 return Ok{};
6812 }));
6815 void DatabaseConnection::FinishWriteTransaction() {
6816 AssertIsOnConnectionThread();
6817 MOZ_ASSERT(HasStorageConnection());
6818 MOZ_ASSERT(!mInReadTransaction);
6819 MOZ_ASSERT(!mInWriteTransaction);
6821 AUTO_PROFILER_LABEL("DatabaseConnection::FinishWriteTransaction", DOM);
6823 if (mUpdateRefcountFunction) {
6824 mUpdateRefcountFunction->Reset();
6827 QM_WARNONLY_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN;"_ns))
6828 .andThen([&](const auto) -> Result<Ok, nsresult> {
6829 mInReadTransaction = true;
6830 return Ok{};
6831 }));
6834 nsresult DatabaseConnection::StartSavepoint() {
6835 AssertIsOnConnectionThread();
6836 MOZ_ASSERT(HasStorageConnection());
6837 MOZ_ASSERT(mUpdateRefcountFunction);
6838 MOZ_ASSERT(mInWriteTransaction);
6840 AUTO_PROFILER_LABEL("DatabaseConnection::StartSavepoint", DOM);
6842 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement(SAVEPOINT_CLAUSE)));
6844 mUpdateRefcountFunction->StartSavepoint();
6846 #ifdef DEBUG
6847 MOZ_ASSERT(mDEBUGSavepointCount < UINT32_MAX);
6848 mDEBUGSavepointCount++;
6849 #endif
6851 return NS_OK;
6854 nsresult DatabaseConnection::ReleaseSavepoint() {
6855 AssertIsOnConnectionThread();
6856 MOZ_ASSERT(HasStorageConnection());
6857 MOZ_ASSERT(mUpdateRefcountFunction);
6858 MOZ_ASSERT(mInWriteTransaction);
6860 AUTO_PROFILER_LABEL("DatabaseConnection::ReleaseSavepoint", DOM);
6862 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("RELEASE "_ns SAVEPOINT_CLAUSE)));
6864 mUpdateRefcountFunction->ReleaseSavepoint();
6866 #ifdef DEBUG
6867 MOZ_ASSERT(mDEBUGSavepointCount);
6868 mDEBUGSavepointCount--;
6869 #endif
6871 return NS_OK;
6874 nsresult DatabaseConnection::RollbackSavepoint() {
6875 AssertIsOnConnectionThread();
6876 MOZ_ASSERT(HasStorageConnection());
6877 MOZ_ASSERT(mUpdateRefcountFunction);
6878 MOZ_ASSERT(mInWriteTransaction);
6880 AUTO_PROFILER_LABEL("DatabaseConnection::RollbackSavepoint", DOM);
6882 #ifdef DEBUG
6883 MOZ_ASSERT(mDEBUGSavepointCount);
6884 mDEBUGSavepointCount--;
6885 #endif
6887 mUpdateRefcountFunction->RollbackSavepoint();
6889 QM_TRY_INSPECT(const auto& stmt,
6890 BorrowCachedStatement("ROLLBACK TO "_ns SAVEPOINT_CLAUSE));
6892 // This may fail if SQLite already rolled back the savepoint so ignore any
6893 // errors.
6894 Unused << stmt->Execute();
6896 return NS_OK;
6899 nsresult DatabaseConnection::CheckpointInternal(CheckpointMode aMode) {
6900 AssertIsOnConnectionThread();
6901 MOZ_ASSERT(!mInReadTransaction);
6902 MOZ_ASSERT(!mInWriteTransaction);
6904 AUTO_PROFILER_LABEL("DatabaseConnection::CheckpointInternal", DOM);
6906 nsAutoCString stmtString;
6907 stmtString.AssignLiteral("PRAGMA wal_checkpoint(");
6909 switch (aMode) {
6910 case CheckpointMode::Full:
6911 // Ensures that the database is completely checkpointed and flushed to
6912 // disk.
6913 stmtString.AppendLiteral("FULL");
6914 break;
6916 case CheckpointMode::Restart:
6917 // Like Full, but also ensures that the next write will start overwriting
6918 // the existing WAL file rather than letting the WAL file grow.
6919 stmtString.AppendLiteral("RESTART");
6920 break;
6922 case CheckpointMode::Truncate:
6923 // Like Restart but also truncates the existing WAL file.
6924 stmtString.AppendLiteral("TRUNCATE");
6925 break;
6927 default:
6928 MOZ_CRASH("Unknown CheckpointMode!");
6931 stmtString.AppendLiteral(");");
6933 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement(stmtString)));
6935 return NS_OK;
6938 void DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint,
6939 const Atomic<bool>& aInterrupted) {
6940 AssertIsOnConnectionThread();
6941 MOZ_ASSERT(mInReadTransaction);
6942 MOZ_ASSERT(!mInWriteTransaction);
6944 AUTO_PROFILER_LABEL("DatabaseConnection::DoIdleProcessing", DOM);
6946 CachingDatabaseConnection::CachedStatement freelistStmt;
6947 const uint32_t freelistCount = [this, &freelistStmt] {
6948 QM_TRY_RETURN(GetFreelistCount(freelistStmt), 0u);
6949 }();
6951 CachedStatement rollbackStmt;
6952 CachedStatement beginStmt;
6953 if (aNeedsCheckpoint || freelistCount) {
6954 QM_TRY_UNWRAP(rollbackStmt, GetCachedStatement("ROLLBACK;"_ns), QM_VOID);
6955 QM_TRY_UNWRAP(beginStmt, GetCachedStatement("BEGIN;"_ns), QM_VOID);
6957 // Release the connection's normal transaction. It's possible that it could
6958 // fail, but that isn't a problem here.
6959 Unused << rollbackStmt.Borrow()->Execute();
6961 mInReadTransaction = false;
6964 const bool freedSomePages =
6965 freelistCount && [this, &freelistStmt, &rollbackStmt, freelistCount,
6966 aNeedsCheckpoint, &aInterrupted] {
6967 // Warn in case of an error, but do not propagate it. Just indicate we
6968 // didn't free any pages.
6969 QM_TRY_INSPECT(
6970 const bool& res,
6971 ReclaimFreePagesWhileIdle(freelistStmt, rollbackStmt, freelistCount,
6972 aNeedsCheckpoint, aInterrupted),
6973 false);
6975 // Make sure we didn't leave a transaction running.
6976 MOZ_ASSERT(!mInReadTransaction);
6977 MOZ_ASSERT(!mInWriteTransaction);
6979 return res;
6980 }();
6982 // Truncate the WAL if we were asked to or if we managed to free some space.
6983 if (aNeedsCheckpoint || freedSomePages) {
6984 QM_WARNONLY_TRY(QM_TO_RESULT(CheckpointInternal(CheckpointMode::Truncate)));
6987 // Finally try to restart the read transaction if we rolled it back earlier.
6988 if (beginStmt) {
6989 QM_WARNONLY_TRY(
6990 MOZ_TO_RESULT(beginStmt.Borrow()->Execute())
6991 .andThen([&self = *this](const Ok) -> Result<Ok, nsresult> {
6992 self.mInReadTransaction = true;
6993 return Ok{};
6994 }));
6998 Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle(
6999 CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement,
7000 uint32_t aFreelistCount, bool aNeedsCheckpoint,
7001 const Atomic<bool>& aInterrupted) {
7002 AssertIsOnConnectionThread();
7003 MOZ_ASSERT(aFreelistStatement);
7004 MOZ_ASSERT(aRollbackStatement);
7005 MOZ_ASSERT(aFreelistCount);
7006 MOZ_ASSERT(!mInReadTransaction);
7007 MOZ_ASSERT(!mInWriteTransaction);
7009 AUTO_PROFILER_LABEL("DatabaseConnection::ReclaimFreePagesWhileIdle", DOM);
7011 uint32_t pauseOnConnectionThreadMs = StaticPrefs::
7012 dom_indexedDB_connectionIdleMaintenance_pauseOnConnectionThreadMs();
7013 if (pauseOnConnectionThreadMs > 0) {
7014 PR_Sleep(PR_MillisecondsToInterval(pauseOnConnectionThreadMs));
7017 // Make sure we don't keep working if anything else needs this thread.
7018 if (aInterrupted) {
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;
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 uint64_t previousFreelistCount = (uint64_t)aFreelistCount + 1;
7065 QM_TRY(CollectWhile(
7066 [&aFreelistCount, &previousFreelistCount,
7067 &aInterrupted]() -> Result<bool, nsresult> {
7068 if (aInterrupted) {
7069 // On interrupt, abort and roll back this transaction. It's ok
7070 // if we never make progress here because the idle service
7071 // should eventually reclaim this space.
7072 return false;
7074 // If we were not able to free anything, we might either see
7075 // a DB that has no auto-vacuum support at all or some other
7076 // (hopefully temporary) condition that prevents vacuum from
7077 // working. Just carry on in non-DEBUG.
7078 bool madeProgress = previousFreelistCount != aFreelistCount;
7079 previousFreelistCount = aFreelistCount;
7080 MOZ_ASSERT(madeProgress);
7081 QM_WARNONLY_TRY(MOZ_TO_RESULT(madeProgress));
7082 return madeProgress && (aFreelistCount != 0);
7084 [&aFreelistStatement, &aFreelistCount, &incrementalVacuumStmt,
7085 &freedSomePages, this]() -> mozilla::Result<Ok, nsresult> {
7086 QM_TRY(MOZ_TO_RESULT(incrementalVacuumStmt.Borrow()->Execute()));
7088 freedSomePages = true;
7090 QM_TRY_UNWRAP(aFreelistCount,
7091 GetFreelistCount(aFreelistStatement));
7093 return Ok{};
7095 .andThen([&commitStmt, &freedSomePages, &aInterrupted, &rollback,
7096 this](Ok) -> Result<Ok, nsresult> {
7097 if (aInterrupted) {
7098 rollback(Ok{});
7099 freedSomePages = false;
7102 if (freedSomePages) {
7103 // Commit the write transaction.
7104 QM_TRY(MOZ_TO_RESULT(commitStmt.Borrow()->Execute()),
7105 QM_PROPAGATE,
7106 [](const auto&) { NS_WARNING("Failed to commit!"); });
7108 mInWriteTransaction = false;
7111 return Ok{};
7113 QM_PROPAGATE, rollback);
7115 return freedSomePages;
7118 Result<uint32_t, nsresult> DatabaseConnection::GetFreelistCount(
7119 CachedStatement& aCachedStatement) {
7120 AssertIsOnConnectionThread();
7122 AUTO_PROFILER_LABEL("DatabaseConnection::GetFreelistCount", DOM);
7124 if (!aCachedStatement) {
7125 QM_TRY_UNWRAP(aCachedStatement,
7126 GetCachedStatement("PRAGMA freelist_count;"_ns));
7129 const auto borrowedStatement = aCachedStatement.Borrow();
7131 QM_TRY_UNWRAP(const DebugOnly<bool> hasResult,
7132 MOZ_TO_RESULT_INVOKE_MEMBER(&*borrowedStatement, ExecuteStep));
7134 MOZ_ASSERT(hasResult);
7136 QM_TRY_INSPECT(const int32_t& freelistCount,
7137 MOZ_TO_RESULT_INVOKE_MEMBER(*borrowedStatement, GetInt32, 0));
7139 MOZ_ASSERT(freelistCount >= 0);
7141 return uint32_t(freelistCount);
7144 void DatabaseConnection::Close() {
7145 AssertIsOnConnectionThread();
7146 MOZ_ASSERT(!mDEBUGSavepointCount);
7147 MOZ_DIAGNOSTIC_ASSERT(!mInWriteTransaction);
7149 AUTO_PROFILER_LABEL("DatabaseConnection::Close", DOM);
7151 if (mUpdateRefcountFunction) {
7152 MOZ_ALWAYS_SUCCEEDS(
7153 MutableStorageConnection().RemoveFunction("update_refcount"_ns));
7154 mUpdateRefcountFunction = nullptr;
7157 CachingDatabaseConnection::Close();
7159 mFileManager.destroy();
7162 nsresult DatabaseConnection::DisableQuotaChecks() {
7163 AssertIsOnConnectionThread();
7164 MOZ_ASSERT(HasStorageConnection());
7166 if (!mQuotaObject) {
7167 MOZ_ASSERT(!mJournalQuotaObject);
7169 QM_TRY(MOZ_TO_RESULT(MutableStorageConnection().GetQuotaObjects(
7170 getter_AddRefs(mQuotaObject), getter_AddRefs(mJournalQuotaObject))));
7172 MOZ_ASSERT(mQuotaObject);
7173 MOZ_ASSERT(mJournalQuotaObject);
7176 mQuotaObject->DisableQuotaCheck();
7177 mJournalQuotaObject->DisableQuotaCheck();
7179 return NS_OK;
7182 void DatabaseConnection::EnableQuotaChecks() {
7183 AssertIsOnConnectionThread();
7184 if (!mQuotaObject) {
7185 MOZ_ASSERT(!mJournalQuotaObject);
7187 // DisableQuotaChecks failed earlier, so we don't need to enable quota
7188 // checks again.
7189 return;
7192 MOZ_ASSERT(mJournalQuotaObject);
7194 const RefPtr<QuotaObject> quotaObject = std::move(mQuotaObject);
7195 const RefPtr<QuotaObject> journalQuotaObject = std::move(mJournalQuotaObject);
7197 quotaObject->EnableQuotaCheck();
7198 journalQuotaObject->EnableQuotaCheck();
7200 QM_TRY_INSPECT(const int64_t& fileSize, GetFileSize(quotaObject->Path()),
7201 QM_VOID);
7202 QM_TRY_INSPECT(const int64_t& journalFileSize,
7203 GetFileSize(journalQuotaObject->Path()), QM_VOID);
7205 DebugOnly<bool> result = journalQuotaObject->MaybeUpdateSize(
7206 journalFileSize, /* aTruncate */ true);
7207 MOZ_ASSERT(result);
7209 result = quotaObject->MaybeUpdateSize(fileSize, /* aTruncate */ true);
7210 MOZ_ASSERT(result);
7213 Result<int64_t, nsresult> DatabaseConnection::GetFileSize(
7214 const nsAString& aPath) {
7215 MOZ_ASSERT(!aPath.IsEmpty());
7217 QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath));
7218 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists));
7220 if (exists) {
7221 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(file, GetFileSize));
7224 return 0;
7227 DatabaseConnection::AutoSavepoint::AutoSavepoint()
7228 : mConnection(nullptr)
7229 #ifdef DEBUG
7231 mDEBUGTransaction(nullptr)
7232 #endif
7234 MOZ_COUNT_CTOR(DatabaseConnection::AutoSavepoint);
7237 DatabaseConnection::AutoSavepoint::~AutoSavepoint() {
7238 MOZ_COUNT_DTOR(DatabaseConnection::AutoSavepoint);
7240 if (mConnection) {
7241 mConnection->AssertIsOnConnectionThread();
7242 MOZ_ASSERT(mDEBUGTransaction);
7243 MOZ_ASSERT(
7244 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::ReadWrite ||
7245 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
7246 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
7247 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::VersionChange);
7249 QM_WARNONLY_TRY(QM_TO_RESULT(mConnection->RollbackSavepoint()));
7253 nsresult DatabaseConnection::AutoSavepoint::Start(
7254 const TransactionBase& aTransaction) {
7255 MOZ_ASSERT(aTransaction.GetMode() == IDBTransaction::Mode::ReadWrite ||
7256 aTransaction.GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
7257 aTransaction.GetMode() == IDBTransaction::Mode::Cleanup ||
7258 aTransaction.GetMode() == IDBTransaction::Mode::VersionChange);
7260 DatabaseConnection* connection = aTransaction.GetDatabase().GetConnection();
7261 MOZ_ASSERT(connection);
7262 connection->AssertIsOnConnectionThread();
7264 // The previous operation failed to begin a write transaction and the
7265 // following opertion jumped to the connection thread before the previous
7266 // operation has updated its failure to the transaction.
7267 if (!connection->GetUpdateRefcountFunction()) {
7268 NS_WARNING(
7269 "The connection was closed because the previous operation "
7270 "failed!");
7271 return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
7274 MOZ_ASSERT(!mConnection);
7275 MOZ_ASSERT(!mDEBUGTransaction);
7277 QM_TRY(MOZ_TO_RESULT(connection->StartSavepoint()));
7279 mConnection = connection;
7280 #ifdef DEBUG
7281 mDEBUGTransaction = &aTransaction;
7282 #endif
7284 return NS_OK;
7287 nsresult DatabaseConnection::AutoSavepoint::Commit() {
7288 MOZ_ASSERT(mConnection);
7289 mConnection->AssertIsOnConnectionThread();
7290 MOZ_ASSERT(mDEBUGTransaction);
7292 QM_TRY(MOZ_TO_RESULT(mConnection->ReleaseSavepoint()));
7294 mConnection = nullptr;
7295 #ifdef DEBUG
7296 mDEBUGTransaction = nullptr;
7297 #endif
7299 return NS_OK;
7302 DatabaseConnection::UpdateRefcountFunction::UpdateRefcountFunction(
7303 DatabaseConnection* const aConnection, DatabaseFileManager& aFileManager)
7304 : mConnection(aConnection),
7305 mFileManager(aFileManager),
7306 mInSavepoint(false) {
7307 MOZ_ASSERT(aConnection);
7308 aConnection->AssertIsOnConnectionThread();
7311 nsresult DatabaseConnection::UpdateRefcountFunction::WillCommit() {
7312 MOZ_ASSERT(mConnection);
7313 mConnection->AssertIsOnConnectionThread();
7314 MOZ_ASSERT(mConnection->HasStorageConnection());
7316 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::WillCommit",
7317 DOM);
7319 // The parameter names are not used, parameters are bound by index
7320 // only locally in the same function.
7321 auto update =
7322 [updateStatement = LazyStatement{*mConnection,
7323 "UPDATE file "
7324 "SET refcount = refcount + :delta "
7325 "WHERE id = :id"_ns},
7326 selectStatement = LazyStatement{*mConnection,
7327 "SELECT id "
7328 "FROM file "
7329 "WHERE id = :id"_ns},
7330 insertStatement =
7331 LazyStatement{
7332 *mConnection,
7333 "INSERT INTO file (id, refcount) VALUES(:id, :delta)"_ns},
7334 this](int64_t aId, int32_t aDelta) mutable -> Result<Ok, nsresult> {
7335 AUTO_PROFILER_LABEL(
7336 "DatabaseConnection::UpdateRefcountFunction::WillCommit::Update", DOM);
7338 QM_TRY_INSPECT(const auto& borrowedUpdateStatement,
7339 updateStatement.Borrow());
7341 QM_TRY(
7342 MOZ_TO_RESULT(borrowedUpdateStatement->BindInt32ByIndex(0, aDelta)));
7343 QM_TRY(MOZ_TO_RESULT(borrowedUpdateStatement->BindInt64ByIndex(1, aId)));
7344 QM_TRY(MOZ_TO_RESULT(borrowedUpdateStatement->Execute()));
7347 QM_TRY_INSPECT(
7348 const int32_t& rows,
7349 MOZ_TO_RESULT_INVOKE_MEMBER(mConnection->MutableStorageConnection(),
7350 GetAffectedRows));
7352 if (rows > 0) {
7353 QM_TRY_INSPECT(
7354 const bool& hasResult,
7355 selectStatement
7356 .BorrowAndExecuteSingleStep(
7357 [aId](auto& stmt) -> Result<Ok, nsresult> {
7358 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, aId)));
7359 return Ok{};
7361 .map(IsSome));
7363 if (!hasResult) {
7364 // Don't have to create the journal here, we can create all at once,
7365 // just before commit
7366 mJournalsToCreateBeforeCommit.AppendElement(aId);
7369 return Ok{};
7372 QM_TRY_INSPECT(const auto& borrowedInsertStatement,
7373 insertStatement.Borrow());
7375 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->BindInt64ByIndex(0, aId)));
7376 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->BindInt32ByIndex(1, aDelta)));
7377 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->Execute()));
7379 mJournalsToRemoveAfterCommit.AppendElement(aId);
7381 return Ok{};
7384 QM_TRY(CollectEachInRange(
7385 mFileInfoEntries, [&update](const auto& entry) -> Result<Ok, nsresult> {
7386 const auto delta = entry.GetData()->Delta();
7387 if (delta) {
7388 QM_TRY(update(entry.GetKey(), delta));
7391 return Ok{};
7392 }));
7394 QM_TRY(MOZ_TO_RESULT(CreateJournals()));
7396 return NS_OK;
7399 void DatabaseConnection::UpdateRefcountFunction::DidCommit() {
7400 MOZ_ASSERT(mConnection);
7401 mConnection->AssertIsOnConnectionThread();
7403 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::DidCommit",
7404 DOM);
7406 for (const auto& entry : mFileInfoEntries.Values()) {
7407 entry->MaybeUpdateDBRefs();
7410 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveJournals(mJournalsToRemoveAfterCommit)));
7413 void DatabaseConnection::UpdateRefcountFunction::DidAbort() {
7414 MOZ_ASSERT(mConnection);
7415 mConnection->AssertIsOnConnectionThread();
7417 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::DidAbort",
7418 DOM);
7420 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveJournals(mJournalsToRemoveAfterAbort)));
7423 void DatabaseConnection::UpdateRefcountFunction::StartSavepoint() {
7424 MOZ_ASSERT(mConnection);
7425 mConnection->AssertIsOnConnectionThread();
7426 MOZ_ASSERT(!mInSavepoint);
7427 MOZ_ASSERT(!mSavepointEntriesIndex.Count());
7429 mInSavepoint = true;
7432 void DatabaseConnection::UpdateRefcountFunction::ReleaseSavepoint() {
7433 MOZ_ASSERT(mConnection);
7434 mConnection->AssertIsOnConnectionThread();
7435 MOZ_ASSERT(mInSavepoint);
7437 mSavepointEntriesIndex.Clear();
7438 mInSavepoint = false;
7441 void DatabaseConnection::UpdateRefcountFunction::RollbackSavepoint() {
7442 MOZ_ASSERT(mConnection);
7443 mConnection->AssertIsOnConnectionThread();
7444 MOZ_ASSERT(!IsOnBackgroundThread());
7445 MOZ_ASSERT(mInSavepoint);
7447 for (const auto& entry : mSavepointEntriesIndex.Values()) {
7448 entry->DecBySavepointDelta();
7451 mInSavepoint = false;
7452 mSavepointEntriesIndex.Clear();
7455 void DatabaseConnection::UpdateRefcountFunction::Reset() {
7456 MOZ_ASSERT(mConnection);
7457 mConnection->AssertIsOnConnectionThread();
7458 MOZ_ASSERT(!mSavepointEntriesIndex.Count());
7459 MOZ_ASSERT(!mInSavepoint);
7461 mJournalsToCreateBeforeCommit.Clear();
7462 mJournalsToRemoveAfterCommit.Clear();
7463 mJournalsToRemoveAfterAbort.Clear();
7465 // DatabaseFileInfo implementation automatically removes unreferenced files,
7466 // but it's done asynchronously and with a delay. We want to remove them (and
7467 // decrease quota usage) before we fire the commit event.
7468 for (const auto& entry : mFileInfoEntries.Values()) {
7469 // We need to move mFileInfo into a raw pointer in order to release it
7470 // explicitly with aSyncDeleteFile == true.
7471 DatabaseFileInfo* const fileInfo = entry->ReleaseFileInfo().forget().take();
7472 MOZ_ASSERT(fileInfo);
7474 fileInfo->Release(/* aSyncDeleteFile */ true);
7477 mFileInfoEntries.Clear();
7480 nsresult DatabaseConnection::UpdateRefcountFunction::ProcessValue(
7481 mozIStorageValueArray* aValues, int32_t aIndex, UpdateType aUpdateType) {
7482 MOZ_ASSERT(mConnection);
7483 mConnection->AssertIsOnConnectionThread();
7484 MOZ_ASSERT(aValues);
7486 AUTO_PROFILER_LABEL(
7487 "DatabaseConnection::UpdateRefcountFunction::ProcessValue", DOM);
7489 QM_TRY_INSPECT(const int32_t& type,
7490 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, aIndex));
7492 if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
7493 return NS_OK;
7496 QM_TRY_INSPECT(const auto& ids, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7497 nsString, aValues, GetString, aIndex));
7499 QM_TRY_INSPECT(const auto& files,
7500 DeserializeStructuredCloneFiles(mFileManager, ids));
7502 for (const StructuredCloneFileParent& file : files) {
7503 const int64_t id = file.FileInfo().Id();
7504 MOZ_ASSERT(id > 0);
7506 const auto entry =
7507 WrapNotNull(mFileInfoEntries.GetOrInsertNew(id, file.FileInfoPtr()));
7509 if (mInSavepoint) {
7510 mSavepointEntriesIndex.InsertOrUpdate(id, entry);
7513 switch (aUpdateType) {
7514 case UpdateType::Increment:
7515 entry->IncDeltas(mInSavepoint);
7516 break;
7517 case UpdateType::Decrement:
7518 entry->DecDeltas(mInSavepoint);
7519 break;
7520 default:
7521 MOZ_CRASH("Unknown update type!");
7525 return NS_OK;
7528 nsresult DatabaseConnection::UpdateRefcountFunction::CreateJournals() {
7529 MOZ_ASSERT(mConnection);
7530 mConnection->AssertIsOnConnectionThread();
7532 AUTO_PROFILER_LABEL(
7533 "DatabaseConnection::UpdateRefcountFunction::CreateJournals", DOM);
7535 const nsCOMPtr<nsIFile> journalDirectory = mFileManager.GetJournalDirectory();
7536 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE);
7538 for (const int64_t id : mJournalsToCreateBeforeCommit) {
7539 const nsCOMPtr<nsIFile> file =
7540 DatabaseFileManager::GetFileForId(journalDirectory, id);
7541 QM_TRY(OkIf(file), NS_ERROR_FAILURE);
7543 QM_TRY(MOZ_TO_RESULT(file->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
7545 mJournalsToRemoveAfterAbort.AppendElement(id);
7548 return NS_OK;
7551 nsresult DatabaseConnection::UpdateRefcountFunction::RemoveJournals(
7552 const nsTArray<int64_t>& aJournals) {
7553 MOZ_ASSERT(mConnection);
7554 mConnection->AssertIsOnConnectionThread();
7556 AUTO_PROFILER_LABEL(
7557 "DatabaseConnection::UpdateRefcountFunction::RemoveJournals", DOM);
7559 nsCOMPtr<nsIFile> journalDirectory = mFileManager.GetJournalDirectory();
7560 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE);
7562 for (const auto& journal : aJournals) {
7563 nsCOMPtr<nsIFile> file =
7564 DatabaseFileManager::GetFileForId(journalDirectory, journal);
7565 QM_TRY(OkIf(file), NS_ERROR_FAILURE);
7567 QM_WARNONLY_TRY(QM_TO_RESULT(file->Remove(false)));
7570 return NS_OK;
7573 NS_IMPL_ISUPPORTS(DatabaseConnection::UpdateRefcountFunction,
7574 mozIStorageFunction)
7576 NS_IMETHODIMP
7577 DatabaseConnection::UpdateRefcountFunction::OnFunctionCall(
7578 mozIStorageValueArray* aValues, nsIVariant** _retval) {
7579 MOZ_ASSERT(aValues);
7580 MOZ_ASSERT(_retval);
7582 AUTO_PROFILER_LABEL(
7583 "DatabaseConnection::UpdateRefcountFunction::OnFunctionCall", DOM);
7585 #ifdef DEBUG
7587 QM_TRY_INSPECT(const uint32_t& numEntries,
7588 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetNumEntries),
7589 QM_ASSERT_UNREACHABLE);
7591 MOZ_ASSERT(numEntries == 2);
7593 QM_TRY_INSPECT(const int32_t& type1,
7594 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 0),
7595 QM_ASSERT_UNREACHABLE);
7597 QM_TRY_INSPECT(const int32_t& type2,
7598 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 1),
7599 QM_ASSERT_UNREACHABLE);
7601 MOZ_ASSERT(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL &&
7602 type2 == mozIStorageValueArray::VALUE_TYPE_NULL));
7604 #endif
7606 QM_TRY(MOZ_TO_RESULT(ProcessValue(aValues, 0, UpdateType::Decrement)));
7608 QM_TRY(MOZ_TO_RESULT(ProcessValue(aValues, 1, UpdateType::Increment)));
7610 return NS_OK;
7613 /*******************************************************************************
7614 * ConnectionPool implementation
7615 ******************************************************************************/
7617 ConnectionPool::ConnectionPool()
7618 : mDatabasesMutex("ConnectionPool::mDatabasesMutex"),
7619 mIOTarget(MakeConnectionIOTarget()),
7620 mIdleTimer(NS_NewTimer()),
7621 mNextTransactionId(0) {
7622 AssertIsOnOwningThread();
7623 AssertIsOnBackgroundThread();
7624 MOZ_ASSERT(mIdleTimer);
7627 ConnectionPool::~ConnectionPool() {
7628 AssertIsOnOwningThread();
7629 MOZ_ASSERT(mIdleDatabases.IsEmpty());
7630 MOZ_ASSERT(!mIdleTimer);
7631 MOZ_ASSERT(mTargetIdleTime.IsNull());
7632 MOZ_ASSERT(!mDatabases.Count());
7633 MOZ_ASSERT(!mTransactions.Count());
7634 MOZ_ASSERT(mQueuedTransactions.IsEmpty());
7635 MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
7636 MOZ_ASSERT(mShutdownRequested);
7637 MOZ_ASSERT(mShutdownComplete);
7640 // static
7641 void ConnectionPool::IdleTimerCallback(nsITimer* aTimer, void* aClosure) {
7642 MOZ_ASSERT(aTimer);
7643 MOZ_ASSERT(aClosure);
7645 AUTO_PROFILER_LABEL("ConnectionPool::IdleTimerCallback", DOM);
7647 auto& self = *static_cast<ConnectionPool*>(aClosure);
7648 MOZ_ASSERT(self.mIdleTimer);
7649 MOZ_ASSERT(SameCOMIdentity(self.mIdleTimer, aTimer));
7650 MOZ_ASSERT(!self.mTargetIdleTime.IsNull());
7652 self.mTargetIdleTime = TimeStamp();
7654 // Cheat a little.
7655 const TimeStamp now =
7656 TimeStamp::NowLoRes() + TimeDuration::FromMilliseconds(500);
7658 // XXX Move this to ArrayAlgorithm.h?
7659 const auto removeUntil = [](auto& array, auto&& cond) {
7660 const auto begin = array.begin(), end = array.end();
7661 array.RemoveElementsRange(
7662 begin, std::find_if(begin, end, std::forward<decltype(cond)>(cond)));
7665 removeUntil(self.mIdleDatabases, [now, &self](const auto& info) {
7666 if (now >= info.mIdleTime) {
7667 if ((*info.mDatabaseInfo)->mIdle) {
7668 self.PerformIdleDatabaseMaintenance(*info.mDatabaseInfo.ref());
7669 } else {
7670 self.CloseDatabase(*info.mDatabaseInfo.ref());
7673 return false;
7676 return true;
7679 self.AdjustIdleTimer();
7682 Result<RefPtr<DatabaseConnection>, nsresult>
7683 ConnectionPool::GetOrCreateConnection(const Database& aDatabase) {
7684 MOZ_ASSERT(!NS_IsMainThread());
7685 MOZ_ASSERT(!IsOnBackgroundThread());
7687 AUTO_PROFILER_LABEL("ConnectionPool::GetOrCreateConnection", DOM);
7689 DatabaseInfo* dbInfo;
7691 MutexAutoLock lock(mDatabasesMutex);
7693 dbInfo = mDatabases.Get(aDatabase.Id());
7696 MOZ_ASSERT(dbInfo);
7698 if (dbInfo->mConnection) {
7699 dbInfo->AssertIsOnConnectionThread();
7701 return dbInfo->mConnection;
7704 MOZ_ASSERT(!dbInfo->mDEBUGConnectionEventTarget);
7706 QM_TRY_UNWRAP(
7707 MovingNotNull<nsCOMPtr<mozIStorageConnection>> storageConnection,
7708 GetStorageConnection(aDatabase.FilePath(), aDatabase.DirectoryLockId(),
7709 aDatabase.TelemetryId(), aDatabase.MaybeKeyRef()));
7711 RefPtr<DatabaseConnection> connection = new DatabaseConnection(
7712 std::move(storageConnection), aDatabase.GetFileManagerPtr());
7714 QM_TRY(MOZ_TO_RESULT(connection->Init()));
7716 dbInfo->mConnection = connection;
7718 IDB_DEBUG_LOG(("ConnectionPool created connection 0x%p for '%s'",
7719 dbInfo->mConnection.get(),
7720 NS_ConvertUTF16toUTF8(aDatabase.FilePath()).get()));
7722 #ifdef DEBUG
7723 dbInfo->mDEBUGConnectionEventTarget = GetCurrentSerialEventTarget();
7724 #endif
7726 return connection;
7729 uint64_t ConnectionPool::Start(
7730 const nsID& aBackgroundChildLoggingId, const nsACString& aDatabaseId,
7731 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames,
7732 bool aIsWriteTransaction,
7733 TransactionDatabaseOperationBase* aTransactionOp) {
7734 AssertIsOnOwningThread();
7735 MOZ_ASSERT(!aDatabaseId.IsEmpty());
7736 MOZ_ASSERT(mNextTransactionId < UINT64_MAX);
7737 MOZ_ASSERT(!mShutdownRequested);
7739 AUTO_PROFILER_LABEL("ConnectionPool::Start", DOM);
7741 const uint64_t transactionId = ++mNextTransactionId;
7743 // To avoid always acquiring a lock, we don't use WithEntryHandle here, which
7744 // would require a lock in any case.
7745 DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId);
7747 const bool databaseInfoIsNew = !dbInfo;
7749 if (databaseInfoIsNew) {
7750 MutexAutoLock lock(mDatabasesMutex);
7752 dbInfo = mDatabases
7753 .InsertOrUpdate(aDatabaseId,
7754 MakeUnique<DatabaseInfo>(this, aDatabaseId))
7755 .get();
7758 MOZ_ASSERT(!mTransactions.Contains(transactionId));
7759 auto& transactionInfo = *mTransactions.InsertOrUpdate(
7760 transactionId, MakeUnique<TransactionInfo>(
7761 *dbInfo, aBackgroundChildLoggingId, aDatabaseId,
7762 transactionId, aLoggingSerialNumber, aObjectStoreNames,
7763 aIsWriteTransaction, aTransactionOp));
7765 if (aIsWriteTransaction) {
7766 MOZ_ASSERT(dbInfo->mWriteTransactionCount < UINT32_MAX);
7767 dbInfo->mWriteTransactionCount++;
7768 } else {
7769 MOZ_ASSERT(dbInfo->mReadTransactionCount < UINT32_MAX);
7770 dbInfo->mReadTransactionCount++;
7773 auto& blockingTransactions = dbInfo->mBlockingTransactions;
7775 for (const nsAString& objectStoreName : aObjectStoreNames) {
7776 TransactionInfoPair* blockInfo =
7777 blockingTransactions.GetOrInsertNew(objectStoreName);
7779 // Mark what we are blocking on.
7780 if (const auto maybeBlockingRead = blockInfo->mLastBlockingReads) {
7781 transactionInfo.mBlockedOn.Insert(&maybeBlockingRead.ref());
7782 maybeBlockingRead->AddBlockingTransaction(transactionInfo);
7785 if (aIsWriteTransaction) {
7786 for (const auto blockingWrite : blockInfo->mLastBlockingWrites) {
7787 transactionInfo.mBlockedOn.Insert(blockingWrite);
7788 blockingWrite->AddBlockingTransaction(transactionInfo);
7791 blockInfo->mLastBlockingReads = SomeRef(transactionInfo);
7792 blockInfo->mLastBlockingWrites.Clear();
7793 } else {
7794 blockInfo->mLastBlockingWrites.AppendElement(
7795 WrapNotNullUnchecked(&transactionInfo));
7799 if (!transactionInfo.mBlockedOn.Count()) {
7800 Unused << ScheduleTransaction(transactionInfo,
7801 /* aFromQueuedTransactions */ false);
7804 if (!databaseInfoIsNew &&
7805 (mIdleDatabases.RemoveElement(dbInfo) ||
7806 mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo))) {
7807 AdjustIdleTimer();
7810 return transactionId;
7813 void ConnectionPool::Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable) {
7814 AssertIsOnOwningThread();
7815 MOZ_ASSERT(aRunnable);
7817 AUTO_PROFILER_LABEL("ConnectionPool::Dispatch", DOM);
7819 auto* const transactionInfo = mTransactions.Get(aTransactionId);
7820 MOZ_ASSERT(transactionInfo);
7821 MOZ_ASSERT(!transactionInfo->mFinished);
7823 if (transactionInfo->mRunning) {
7824 DatabaseInfo& dbInfo = transactionInfo->mDatabaseInfo;
7825 MOZ_ASSERT(dbInfo.mEventTarget);
7826 MOZ_ASSERT(!dbInfo.mClosing);
7827 MOZ_ASSERT_IF(
7828 transactionInfo->mIsWriteTransaction,
7829 dbInfo.mRunningWriteTransaction &&
7830 dbInfo.mRunningWriteTransaction.refEquals(*transactionInfo));
7832 MOZ_ALWAYS_SUCCEEDS(
7833 dbInfo.mEventTarget->Dispatch(aRunnable, NS_DISPATCH_NORMAL));
7834 } else {
7835 transactionInfo->mQueuedRunnables.AppendElement(aRunnable);
7839 void ConnectionPool::Finish(uint64_t aTransactionId,
7840 FinishCallback* aCallback) {
7841 AssertIsOnOwningThread();
7843 #ifdef DEBUG
7844 auto* const transactionInfo = mTransactions.Get(aTransactionId);
7845 MOZ_ASSERT(transactionInfo);
7846 MOZ_ASSERT(!transactionInfo->mFinished);
7847 #endif
7849 AUTO_PROFILER_LABEL("ConnectionPool::Finish", DOM);
7851 RefPtr<FinishCallbackWrapper> wrapper =
7852 new FinishCallbackWrapper(this, aTransactionId, aCallback);
7854 Dispatch(aTransactionId, wrapper);
7856 #ifdef DEBUG
7857 transactionInfo->mFinished.Flip();
7858 #endif
7861 void ConnectionPool::WaitForDatabaseToComplete(const nsCString& aDatabaseId,
7862 nsIRunnable* aCallback) {
7863 AssertIsOnOwningThread();
7864 MOZ_ASSERT(!aDatabaseId.IsEmpty());
7865 MOZ_ASSERT(aCallback);
7867 AUTO_PROFILER_LABEL("ConnectionPool::WaitForDatabaseToComplete", DOM);
7869 if (!CloseDatabaseWhenIdleInternal(aDatabaseId)) {
7870 Unused << aCallback->Run();
7871 return;
7874 mCompleteCallbacks.EmplaceBack(
7875 MakeUnique<DatabaseCompleteCallback>(aDatabaseId, aCallback));
7878 void ConnectionPool::Shutdown() {
7879 AssertIsOnOwningThread();
7880 MOZ_ASSERT(!mShutdownComplete);
7882 AUTO_PROFILER_LABEL("ConnectionPool::Shutdown", DOM);
7884 mShutdownRequested.Flip();
7886 CancelIdleTimer();
7887 MOZ_ASSERT(mTargetIdleTime.IsNull());
7889 mIdleTimer = nullptr;
7891 CloseIdleDatabases();
7893 if (!mDatabases.Count()) {
7894 MOZ_ASSERT(!mTransactions.Count());
7896 Cleanup();
7898 MOZ_ASSERT(mShutdownComplete);
7900 mIOTarget->Shutdown();
7902 return;
7905 MOZ_ALWAYS_TRUE(SpinEventLoopUntil("ConnectionPool::Shutdown"_ns, [&]() {
7906 return static_cast<bool>(mShutdownComplete);
7907 }));
7909 mIOTarget->Shutdown();
7912 void ConnectionPool::Cleanup() {
7913 AssertIsOnOwningThread();
7914 MOZ_ASSERT(mShutdownRequested);
7915 MOZ_ASSERT(!mShutdownComplete);
7916 MOZ_ASSERT(!mDatabases.Count());
7917 MOZ_ASSERT(!mTransactions.Count());
7919 AUTO_PROFILER_LABEL("ConnectionPool::Cleanup", DOM);
7921 if (!mCompleteCallbacks.IsEmpty()) {
7922 // Run all callbacks manually now.
7925 auto completeCallbacks = std::move(mCompleteCallbacks);
7926 for (const auto& completeCallback : completeCallbacks) {
7927 MOZ_ASSERT(completeCallback);
7928 MOZ_ASSERT(completeCallback->mCallback);
7930 Unused << completeCallback->mCallback->Run();
7933 // We expect no new callbacks being completed by running the existing
7934 // ones.
7935 MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
7938 // And make sure they get processed.
7939 nsIThread* currentThread = NS_GetCurrentThread();
7940 MOZ_ASSERT(currentThread);
7942 MOZ_ALWAYS_SUCCEEDS(NS_ProcessPendingEvents(currentThread));
7945 mShutdownComplete.Flip();
7948 void ConnectionPool::AdjustIdleTimer() {
7949 AssertIsOnOwningThread();
7950 MOZ_ASSERT(mIdleTimer);
7952 AUTO_PROFILER_LABEL("ConnectionPool::AdjustIdleTimer", DOM);
7954 // Figure out the next time at which we should release idle resources. This
7955 // includes both databases and threads.
7956 TimeStamp newTargetIdleTime;
7957 MOZ_ASSERT(newTargetIdleTime.IsNull());
7959 if (!mIdleDatabases.IsEmpty()) {
7960 newTargetIdleTime = mIdleDatabases[0].mIdleTime;
7963 MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleDatabases.IsEmpty());
7965 // Cancel the timer if it was running and the new target time is different.
7966 if (!mTargetIdleTime.IsNull() &&
7967 (newTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
7968 CancelIdleTimer();
7970 MOZ_ASSERT(mTargetIdleTime.IsNull());
7973 // Schedule the timer if we have a target time different than before.
7974 if (!newTargetIdleTime.IsNull() &&
7975 (mTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
7976 double delta = (newTargetIdleTime - TimeStamp::NowLoRes()).ToMilliseconds();
7978 uint32_t delay;
7979 if (delta > 0) {
7980 delay = uint32_t(std::min(delta, double(UINT32_MAX)));
7981 } else {
7982 delay = 0;
7985 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->InitWithNamedFuncCallback(
7986 IdleTimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT,
7987 "ConnectionPool::IdleTimerCallback"));
7989 mTargetIdleTime = newTargetIdleTime;
7993 void ConnectionPool::CancelIdleTimer() {
7994 AssertIsOnOwningThread();
7995 MOZ_ASSERT(mIdleTimer);
7997 if (!mTargetIdleTime.IsNull()) {
7998 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel());
8000 mTargetIdleTime = TimeStamp();
8001 MOZ_ASSERT(mTargetIdleTime.IsNull());
8005 void ConnectionPool::CloseIdleDatabases() {
8006 AssertIsOnOwningThread();
8007 MOZ_ASSERT(mShutdownRequested);
8009 AUTO_PROFILER_LABEL("ConnectionPool::CloseIdleDatabases", DOM);
8011 if (!mIdleDatabases.IsEmpty()) {
8012 for (IdleDatabaseInfo& idleInfo : mIdleDatabases) {
8013 CloseDatabase(*idleInfo.mDatabaseInfo.ref());
8015 mIdleDatabases.Clear();
8018 if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) {
8019 for (PerformingIdleMaintenanceDatabaseInfo& performingIdleMaintenanceInfo :
8020 mDatabasesPerformingIdleMaintenance) {
8021 CloseDatabase(*performingIdleMaintenanceInfo.mDatabaseInfo);
8023 mDatabasesPerformingIdleMaintenance.Clear();
8027 bool ConnectionPool::ScheduleTransaction(TransactionInfo& aTransactionInfo,
8028 bool aFromQueuedTransactions) {
8029 AssertIsOnOwningThread();
8031 AUTO_PROFILER_LABEL("ConnectionPool::ScheduleTransaction", DOM);
8033 DatabaseInfo& dbInfo = aTransactionInfo.mDatabaseInfo;
8035 dbInfo.mIdle = false;
8037 if (dbInfo.mClosing) {
8038 MOZ_ASSERT(!mIdleDatabases.Contains(&dbInfo));
8039 MOZ_ASSERT(
8040 !dbInfo.mTransactionsScheduledDuringClose.Contains(&aTransactionInfo));
8042 dbInfo.mTransactionsScheduledDuringClose.AppendElement(
8043 WrapNotNullUnchecked(&aTransactionInfo));
8044 return true;
8047 if (!dbInfo.mEventTarget) {
8048 const uint32_t serialNumber = SerialNumber();
8049 const nsCString serialName =
8050 nsPrintfCString("IndexedDB #%" PRIu32, serialNumber);
8052 dbInfo.mEventTarget =
8053 TaskQueue::Create(do_AddRef(mIOTarget), serialName.get());
8054 MOZ_ASSERT(dbInfo.mEventTarget);
8055 IDB_DEBUG_LOG(("ConnectionPool created task queue %" PRIu32, serialNumber));
8058 // The number of active operations equals the number of databases minus idle
8059 // databases. The maximum number of database operations which can make
8060 // progress at the same time is kMaxConnectionThreadCount. If we are at this
8061 // limit, all idle processing is interrupted to make room for user
8062 // transactions.
8063 if (mDatabases.Count() >=
8064 (mIdleDatabases.Length() + kMaxConnectionThreadCount) &&
8065 !mDatabasesPerformingIdleMaintenance.IsEmpty()) {
8066 const auto& busyDbs = mDatabasesPerformingIdleMaintenance;
8067 for (auto dbInfo = busyDbs.rbegin(); dbInfo != busyDbs.rend(); ++dbInfo) {
8068 (*dbInfo).mIdleConnectionRunnable->Interrupt();
8072 if (aTransactionInfo.mIsWriteTransaction) {
8073 if (dbInfo.mRunningWriteTransaction) {
8074 // SQLite only allows one write transaction at a time so queue this
8075 // transaction for later.
8076 MOZ_ASSERT(
8077 !dbInfo.mScheduledWriteTransactions.Contains(&aTransactionInfo));
8079 dbInfo.mScheduledWriteTransactions.AppendElement(
8080 WrapNotNullUnchecked(&aTransactionInfo));
8081 return true;
8084 dbInfo.mRunningWriteTransaction = SomeRef(aTransactionInfo);
8085 dbInfo.mNeedsCheckpoint = true;
8088 MOZ_ASSERT(!aTransactionInfo.mRunning);
8089 aTransactionInfo.mRunning = true;
8091 nsTArray<nsCOMPtr<nsIRunnable>>& queuedRunnables =
8092 aTransactionInfo.mQueuedRunnables;
8094 if (!queuedRunnables.IsEmpty()) {
8095 for (auto& queuedRunnable : queuedRunnables) {
8096 MOZ_ALWAYS_SUCCEEDS(
8097 dbInfo.mEventTarget->Dispatch(queuedRunnable.forget()));
8100 queuedRunnables.Clear();
8103 return true;
8106 void ConnectionPool::NoteFinishedTransaction(uint64_t aTransactionId) {
8107 AssertIsOnOwningThread();
8109 AUTO_PROFILER_LABEL("ConnectionPool::NoteFinishedTransaction", DOM);
8111 auto* const transactionInfo = mTransactions.Get(aTransactionId);
8112 MOZ_ASSERT(transactionInfo);
8113 MOZ_ASSERT(transactionInfo->mRunning);
8114 MOZ_ASSERT(transactionInfo->mFinished);
8116 transactionInfo->mRunning = false;
8118 DatabaseInfo& dbInfo = transactionInfo->mDatabaseInfo;
8119 MOZ_ASSERT(mDatabases.Get(transactionInfo->mDatabaseId) == &dbInfo);
8120 MOZ_ASSERT(dbInfo.mEventTarget);
8122 // Schedule the next write transaction if there are any queued.
8123 if (dbInfo.mRunningWriteTransaction &&
8124 dbInfo.mRunningWriteTransaction.refEquals(*transactionInfo)) {
8125 MOZ_ASSERT(transactionInfo->mIsWriteTransaction);
8126 MOZ_ASSERT(dbInfo.mNeedsCheckpoint);
8128 dbInfo.mRunningWriteTransaction = Nothing();
8130 if (!dbInfo.mScheduledWriteTransactions.IsEmpty()) {
8131 const auto nextWriteTransaction = dbInfo.mScheduledWriteTransactions[0];
8133 dbInfo.mScheduledWriteTransactions.RemoveElementAt(0);
8135 MOZ_ALWAYS_TRUE(ScheduleTransaction(*nextWriteTransaction,
8136 /* aFromQueuedTransactions */ false));
8140 for (const auto& objectStoreName : transactionInfo->mObjectStoreNames) {
8141 TransactionInfoPair* blockInfo =
8142 dbInfo.mBlockingTransactions.Get(objectStoreName);
8143 MOZ_ASSERT(blockInfo);
8145 if (transactionInfo->mIsWriteTransaction && blockInfo->mLastBlockingReads &&
8146 blockInfo->mLastBlockingReads.refEquals(*transactionInfo)) {
8147 blockInfo->mLastBlockingReads = Nothing();
8150 blockInfo->mLastBlockingWrites.RemoveElement(transactionInfo);
8153 transactionInfo->RemoveBlockingTransactions();
8155 if (transactionInfo->mIsWriteTransaction) {
8156 MOZ_ASSERT(dbInfo.mWriteTransactionCount);
8157 dbInfo.mWriteTransactionCount--;
8158 } else {
8159 MOZ_ASSERT(dbInfo.mReadTransactionCount);
8160 dbInfo.mReadTransactionCount--;
8163 mTransactions.Remove(aTransactionId);
8165 if (!dbInfo.TotalTransactionCount()) {
8166 MOZ_ASSERT(!dbInfo.mIdle);
8167 dbInfo.mIdle = true;
8169 NoteIdleDatabase(dbInfo);
8173 void ConnectionPool::ScheduleQueuedTransactions() {
8174 AssertIsOnOwningThread();
8175 MOZ_ASSERT(!mQueuedTransactions.IsEmpty());
8177 AUTO_PROFILER_LABEL("ConnectionPool::ScheduleQueuedTransactions", DOM);
8179 const auto foundIt = std::find_if(
8180 mQueuedTransactions.begin(), mQueuedTransactions.end(),
8181 [&me = *this](const auto& queuedTransaction) {
8182 return !me.ScheduleTransaction(*queuedTransaction,
8183 /* aFromQueuedTransactions */ true);
8186 mQueuedTransactions.RemoveElementsRange(mQueuedTransactions.begin(), foundIt);
8188 AdjustIdleTimer();
8191 void ConnectionPool::NoteIdleDatabase(DatabaseInfo& aDatabaseInfo) {
8192 AssertIsOnOwningThread();
8193 MOZ_ASSERT(!aDatabaseInfo.TotalTransactionCount());
8194 MOZ_ASSERT(aDatabaseInfo.mEventTarget);
8195 MOZ_ASSERT(!mIdleDatabases.Contains(&aDatabaseInfo));
8197 AUTO_PROFILER_LABEL("ConnectionPool::NoteIdleDatabase", DOM);
8199 const bool otherDatabasesWaiting = !mQueuedTransactions.IsEmpty();
8201 // We check mShutdownRequested because when it is true, mIdleTimer is null.
8202 if (mShutdownRequested || otherDatabasesWaiting ||
8203 aDatabaseInfo.mCloseOnIdle) {
8204 // Make sure we close the connection if we're shutting down or giving the
8205 // thread to another database.
8206 CloseDatabase(aDatabaseInfo);
8208 if (otherDatabasesWaiting) {
8209 ScheduleQueuedTransactions();
8212 return;
8215 mIdleDatabases.InsertElementSorted(IdleDatabaseInfo{aDatabaseInfo});
8217 AdjustIdleTimer();
8220 void ConnectionPool::NoteClosedDatabase(DatabaseInfo& aDatabaseInfo) {
8221 AssertIsOnOwningThread();
8222 MOZ_ASSERT(aDatabaseInfo.mClosing);
8223 MOZ_ASSERT(!mIdleDatabases.Contains(&aDatabaseInfo));
8225 AUTO_PROFILER_LABEL("ConnectionPool::NoteClosedDatabase", DOM);
8227 aDatabaseInfo.mClosing = false;
8229 // Schedule any transactions that were started while we were closing the
8230 // connection.
8231 if (!mQueuedTransactions.IsEmpty()) {
8232 ScheduleQueuedTransactions();
8233 } else if (!aDatabaseInfo.TotalTransactionCount() && !mShutdownRequested) {
8234 AdjustIdleTimer();
8237 // Schedule any transactions that were started while we were closing the
8238 // connection.
8239 if (aDatabaseInfo.TotalTransactionCount()) {
8240 auto& scheduledTransactions =
8241 aDatabaseInfo.mTransactionsScheduledDuringClose;
8243 MOZ_ASSERT(!scheduledTransactions.IsEmpty());
8245 for (const auto& scheduledTransaction : scheduledTransactions) {
8246 Unused << ScheduleTransaction(*scheduledTransaction,
8247 /* aFromQueuedTransactions */ false);
8250 scheduledTransactions.Clear();
8252 return;
8255 // There are no more transactions and the connection has been closed. We're
8256 // done with this database.
8258 MutexAutoLock lock(mDatabasesMutex);
8260 mDatabases.Remove(aDatabaseInfo.mDatabaseId);
8263 // That just deleted |aDatabaseInfo|, we must not access that below.
8265 // See if we need to fire any complete callbacks now that the database is
8266 // finished.
8267 mCompleteCallbacks.RemoveLastElements(
8268 mCompleteCallbacks.end() -
8269 std::remove_if(mCompleteCallbacks.begin(), mCompleteCallbacks.end(),
8270 [&me = *this](const auto& completeCallback) {
8271 return me.MaybeFireCallback(completeCallback.get());
8272 }));
8274 // If that was the last database and we're supposed to be shutting down then
8275 // we are finished.
8276 if (mShutdownRequested && !mDatabases.Count()) {
8277 MOZ_ASSERT(!mTransactions.Count());
8278 Cleanup();
8282 bool ConnectionPool::MaybeFireCallback(DatabaseCompleteCallback* aCallback) {
8283 AssertIsOnOwningThread();
8284 MOZ_ASSERT(aCallback);
8285 MOZ_ASSERT(!aCallback->mDatabaseId.IsEmpty());
8286 MOZ_ASSERT(aCallback->mCallback);
8288 AUTO_PROFILER_LABEL("ConnectionPool::MaybeFireCallback", DOM);
8290 if (mDatabases.Get(aCallback->mDatabaseId)) {
8291 return false;
8294 Unused << aCallback->mCallback->Run();
8295 return true;
8298 void ConnectionPool::PerformIdleDatabaseMaintenance(
8299 DatabaseInfo& aDatabaseInfo) {
8300 AssertIsOnOwningThread();
8301 MOZ_ASSERT(!aDatabaseInfo.TotalTransactionCount());
8302 MOZ_ASSERT(aDatabaseInfo.mEventTarget);
8303 MOZ_ASSERT(aDatabaseInfo.mIdle);
8304 MOZ_ASSERT(!aDatabaseInfo.mCloseOnIdle);
8305 MOZ_ASSERT(!aDatabaseInfo.mClosing);
8306 MOZ_ASSERT(mIdleDatabases.Contains(&aDatabaseInfo));
8307 MOZ_ASSERT(!mDatabasesPerformingIdleMaintenance.Contains(&aDatabaseInfo));
8309 const bool neededCheckpoint = aDatabaseInfo.mNeedsCheckpoint;
8311 aDatabaseInfo.mNeedsCheckpoint = false;
8312 aDatabaseInfo.mIdle = false;
8314 auto idleConnectionRunnable =
8315 MakeRefPtr<IdleConnectionRunnable>(aDatabaseInfo, neededCheckpoint);
8317 mDatabasesPerformingIdleMaintenance.AppendElement(
8318 PerformingIdleMaintenanceDatabaseInfo{aDatabaseInfo,
8319 idleConnectionRunnable});
8321 MOZ_ALWAYS_SUCCEEDS(aDatabaseInfo.mEventTarget->Dispatch(
8322 idleConnectionRunnable.forget(), NS_DISPATCH_NORMAL));
8325 void ConnectionPool::CloseDatabase(DatabaseInfo& aDatabaseInfo) const {
8326 AssertIsOnOwningThread();
8327 MOZ_DIAGNOSTIC_ASSERT(!aDatabaseInfo.TotalTransactionCount());
8328 MOZ_ASSERT(aDatabaseInfo.mEventTarget);
8329 MOZ_ASSERT(!aDatabaseInfo.mClosing);
8331 aDatabaseInfo.mIdle = false;
8332 aDatabaseInfo.mNeedsCheckpoint = false;
8333 aDatabaseInfo.mClosing = true;
8335 MOZ_ALWAYS_SUCCEEDS(aDatabaseInfo.mEventTarget->Dispatch(
8336 MakeAndAddRef<CloseConnectionRunnable>(aDatabaseInfo),
8337 NS_DISPATCH_NORMAL));
8340 bool ConnectionPool::CloseDatabaseWhenIdleInternal(
8341 const nsACString& aDatabaseId) {
8342 AssertIsOnOwningThread();
8343 MOZ_ASSERT(!aDatabaseId.IsEmpty());
8345 AUTO_PROFILER_LABEL("ConnectionPool::CloseDatabaseWhenIdleInternal", DOM);
8347 if (DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId)) {
8348 if (mIdleDatabases.RemoveElement(dbInfo) ||
8349 mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo)) {
8350 CloseDatabase(*dbInfo);
8351 AdjustIdleTimer();
8352 } else {
8353 dbInfo->mCloseOnIdle.EnsureFlipped();
8356 return true;
8359 return false;
8362 ConnectionPool::ConnectionRunnable::ConnectionRunnable(
8363 DatabaseInfo& aDatabaseInfo)
8364 : Runnable("dom::indexedDB::ConnectionPool::ConnectionRunnable"),
8365 mDatabaseInfo(aDatabaseInfo),
8366 mOwningEventTarget(GetCurrentSerialEventTarget()) {
8367 AssertIsOnBackgroundThread();
8368 MOZ_ASSERT(aDatabaseInfo.mConnectionPool);
8369 aDatabaseInfo.mConnectionPool->AssertIsOnOwningThread();
8370 MOZ_ASSERT(mOwningEventTarget);
8373 NS_IMETHODIMP
8374 ConnectionPool::IdleConnectionRunnable::Run() {
8375 MOZ_ASSERT(!mDatabaseInfo.mIdle);
8377 const nsCOMPtr<nsIEventTarget> owningThread = std::move(mOwningEventTarget);
8379 if (owningThread) {
8380 mDatabaseInfo.AssertIsOnConnectionThread();
8382 // The connection could be null if EnsureConnection() didn't run or was not
8383 // successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
8384 if (mDatabaseInfo.mConnection) {
8385 mDatabaseInfo.mConnection->DoIdleProcessing(mNeedsCheckpoint,
8386 mInterrupted);
8389 MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
8390 return NS_OK;
8393 AssertIsOnBackgroundThread();
8395 RefPtr<ConnectionPool> connectionPool = mDatabaseInfo.mConnectionPool;
8396 MOZ_ASSERT(connectionPool);
8398 if (mDatabaseInfo.mClosing || mDatabaseInfo.TotalTransactionCount()) {
8399 MOZ_ASSERT(!connectionPool->mDatabasesPerformingIdleMaintenance.Contains(
8400 &mDatabaseInfo));
8401 } else {
8402 MOZ_ALWAYS_TRUE(
8403 connectionPool->mDatabasesPerformingIdleMaintenance.RemoveElement(
8404 &mDatabaseInfo));
8406 connectionPool->NoteIdleDatabase(mDatabaseInfo);
8409 return NS_OK;
8412 NS_IMETHODIMP
8413 ConnectionPool::CloseConnectionRunnable::Run() {
8414 AUTO_PROFILER_LABEL("ConnectionPool::CloseConnectionRunnable::Run", DOM);
8416 if (mOwningEventTarget) {
8417 MOZ_ASSERT(mDatabaseInfo.mClosing);
8419 const nsCOMPtr<nsIEventTarget> owningThread = std::move(mOwningEventTarget);
8421 // The connection could be null if EnsureConnection() didn't run or was not
8422 // successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
8423 if (mDatabaseInfo.mConnection) {
8424 mDatabaseInfo.AssertIsOnConnectionThread();
8426 mDatabaseInfo.mConnection->Close();
8428 IDB_DEBUG_LOG(("ConnectionPool closed connection 0x%p",
8429 mDatabaseInfo.mConnection.get()));
8431 mDatabaseInfo.mConnection = nullptr;
8433 #ifdef DEBUG
8434 mDatabaseInfo.mDEBUGConnectionEventTarget = nullptr;
8435 #endif
8438 MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
8439 return NS_OK;
8442 RefPtr<ConnectionPool> connectionPool = mDatabaseInfo.mConnectionPool;
8443 MOZ_ASSERT(connectionPool);
8445 connectionPool->NoteClosedDatabase(mDatabaseInfo);
8446 return NS_OK;
8449 ConnectionPool::DatabaseInfo::DatabaseInfo(ConnectionPool* aConnectionPool,
8450 const nsACString& aDatabaseId)
8451 : mConnectionPool(aConnectionPool),
8452 mDatabaseId(aDatabaseId),
8453 mReadTransactionCount(0),
8454 mWriteTransactionCount(0),
8455 mNeedsCheckpoint(false),
8456 mIdle(false),
8457 mClosing(false)
8458 #ifdef DEBUG
8460 mDEBUGConnectionEventTarget(nullptr)
8461 #endif
8463 AssertIsOnBackgroundThread();
8464 MOZ_ASSERT(aConnectionPool);
8465 aConnectionPool->AssertIsOnOwningThread();
8466 MOZ_ASSERT(!aDatabaseId.IsEmpty());
8468 MOZ_COUNT_CTOR(ConnectionPool::DatabaseInfo);
8471 ConnectionPool::DatabaseInfo::~DatabaseInfo() {
8472 AssertIsOnBackgroundThread();
8473 MOZ_ASSERT(!mConnection);
8474 MOZ_ASSERT(mScheduledWriteTransactions.IsEmpty());
8475 MOZ_ASSERT(!mRunningWriteTransaction);
8476 MOZ_ASSERT(!TotalTransactionCount());
8478 MOZ_COUNT_DTOR(ConnectionPool::DatabaseInfo);
8481 ConnectionPool::DatabaseCompleteCallback::DatabaseCompleteCallback(
8482 const nsCString& aDatabaseId, nsIRunnable* aCallback)
8483 : mDatabaseId(aDatabaseId), mCallback(aCallback) {
8484 AssertIsOnBackgroundThread();
8485 MOZ_ASSERT(!mDatabaseId.IsEmpty());
8486 MOZ_ASSERT(aCallback);
8488 MOZ_COUNT_CTOR(ConnectionPool::DatabaseCompleteCallback);
8491 ConnectionPool::DatabaseCompleteCallback::~DatabaseCompleteCallback() {
8492 AssertIsOnBackgroundThread();
8494 MOZ_COUNT_DTOR(ConnectionPool::DatabaseCompleteCallback);
8497 ConnectionPool::FinishCallbackWrapper::FinishCallbackWrapper(
8498 ConnectionPool* aConnectionPool, uint64_t aTransactionId,
8499 FinishCallback* aCallback)
8500 : Runnable("dom::indexedDB::ConnectionPool::FinishCallbackWrapper"),
8501 mConnectionPool(aConnectionPool),
8502 mCallback(aCallback),
8503 mOwningEventTarget(GetCurrentSerialEventTarget()),
8504 mTransactionId(aTransactionId),
8505 mHasRunOnce(false) {
8506 AssertIsOnBackgroundThread();
8507 MOZ_ASSERT(aConnectionPool);
8508 MOZ_ASSERT(aCallback);
8509 MOZ_ASSERT(mOwningEventTarget);
8512 ConnectionPool::FinishCallbackWrapper::~FinishCallbackWrapper() {
8513 MOZ_ASSERT(!mConnectionPool);
8514 MOZ_ASSERT(!mCallback);
8517 nsresult ConnectionPool::FinishCallbackWrapper::Run() {
8518 MOZ_ASSERT(mConnectionPool);
8519 MOZ_ASSERT(mCallback);
8520 MOZ_ASSERT(mOwningEventTarget);
8522 AUTO_PROFILER_LABEL("ConnectionPool::FinishCallbackWrapper::Run", DOM);
8524 if (!mHasRunOnce) {
8525 MOZ_ASSERT(!IsOnBackgroundThread());
8527 mHasRunOnce = true;
8529 Unused << mCallback->Run();
8531 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
8533 return NS_OK;
8536 mConnectionPool->AssertIsOnOwningThread();
8537 MOZ_ASSERT(mHasRunOnce);
8539 RefPtr<ConnectionPool> connectionPool = std::move(mConnectionPool);
8540 RefPtr<FinishCallback> callback = std::move(mCallback);
8542 callback->TransactionFinishedBeforeUnblock();
8544 connectionPool->NoteFinishedTransaction(mTransactionId);
8546 callback->TransactionFinishedAfterUnblock();
8548 return NS_OK;
8551 uint32_t ConnectionPool::sSerialNumber = 0u;
8553 ConnectionPool::IdleResource::IdleResource(const TimeStamp& aIdleTime)
8554 : mIdleTime(aIdleTime) {
8555 AssertIsOnBackgroundThread();
8556 MOZ_ASSERT(!aIdleTime.IsNull());
8558 MOZ_COUNT_CTOR(ConnectionPool::IdleResource);
8561 ConnectionPool::IdleResource::~IdleResource() {
8562 AssertIsOnBackgroundThread();
8564 MOZ_COUNT_DTOR(ConnectionPool::IdleResource);
8567 ConnectionPool::IdleDatabaseInfo::IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo)
8568 : IdleResource(
8569 TimeStamp::NowLoRes() +
8570 (aDatabaseInfo.mIdle
8571 ? TimeDuration::FromMilliseconds(kConnectionIdleMaintenanceMS)
8572 : TimeDuration::FromMilliseconds(kConnectionIdleCloseMS))),
8573 mDatabaseInfo(WrapNotNullUnchecked(&aDatabaseInfo)) {
8574 AssertIsOnBackgroundThread();
8576 MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
8579 ConnectionPool::IdleDatabaseInfo::~IdleDatabaseInfo() {
8580 AssertIsOnBackgroundThread();
8582 MOZ_COUNT_DTOR(ConnectionPool::IdleDatabaseInfo);
8585 ConnectionPool::PerformingIdleMaintenanceDatabaseInfo::
8586 PerformingIdleMaintenanceDatabaseInfo(
8587 DatabaseInfo& aDatabaseInfo,
8588 RefPtr<IdleConnectionRunnable> aIdleConnectionRunnable)
8589 : mDatabaseInfo(WrapNotNullUnchecked(&aDatabaseInfo)),
8590 mIdleConnectionRunnable(std::move(aIdleConnectionRunnable)) {
8591 AssertIsOnBackgroundThread();
8592 MOZ_ASSERT(mIdleConnectionRunnable);
8594 MOZ_COUNT_CTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo);
8597 ConnectionPool::PerformingIdleMaintenanceDatabaseInfo::
8598 ~PerformingIdleMaintenanceDatabaseInfo() {
8599 AssertIsOnBackgroundThread();
8601 MOZ_COUNT_DTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo);
8604 ConnectionPool::TransactionInfo::TransactionInfo(
8605 DatabaseInfo& aDatabaseInfo, const nsID& aBackgroundChildLoggingId,
8606 const nsACString& aDatabaseId, uint64_t aTransactionId,
8607 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames,
8608 bool aIsWriteTransaction, TransactionDatabaseOperationBase* aTransactionOp)
8609 : mDatabaseInfo(aDatabaseInfo),
8610 mBackgroundChildLoggingId(aBackgroundChildLoggingId),
8611 mDatabaseId(aDatabaseId),
8612 mTransactionId(aTransactionId),
8613 mLoggingSerialNumber(aLoggingSerialNumber),
8614 mObjectStoreNames(aObjectStoreNames.Clone()),
8615 mIsWriteTransaction(aIsWriteTransaction),
8616 mRunning(false) {
8617 AssertIsOnBackgroundThread();
8618 aDatabaseInfo.mConnectionPool->AssertIsOnOwningThread();
8620 MOZ_COUNT_CTOR(ConnectionPool::TransactionInfo);
8622 if (aTransactionOp) {
8623 mQueuedRunnables.AppendElement(aTransactionOp);
8627 ConnectionPool::TransactionInfo::~TransactionInfo() {
8628 AssertIsOnBackgroundThread();
8629 MOZ_ASSERT(!mBlockedOn.Count());
8630 MOZ_ASSERT(mQueuedRunnables.IsEmpty());
8631 MOZ_ASSERT(!mRunning);
8632 MOZ_ASSERT(mFinished);
8634 MOZ_COUNT_DTOR(ConnectionPool::TransactionInfo);
8637 void ConnectionPool::TransactionInfo::AddBlockingTransaction(
8638 TransactionInfo& aTransactionInfo) {
8639 AssertIsOnBackgroundThread();
8641 // XXX Does it really make sense to have both mBlocking and mBlockingOrdered,
8642 // just to reduce the algorithmic complexity of this Contains check? This was
8643 // mentioned in the context of Bug 1290853, but no real justification was
8644 // given. There was the suggestion of encapsulating this in an
8645 // insertion-ordered hashtable implementation, which seems like a good idea.
8646 // If we had that, this would be the appropriate data structure to use here.
8647 if (mBlocking.EnsureInserted(&aTransactionInfo)) {
8648 mBlockingOrdered.AppendElement(WrapNotNullUnchecked(&aTransactionInfo));
8652 void ConnectionPool::TransactionInfo::RemoveBlockingTransactions() {
8653 AssertIsOnBackgroundThread();
8655 for (const auto blockedInfo : mBlockingOrdered) {
8656 blockedInfo->MaybeUnblock(*this);
8659 mBlocking.Clear();
8660 mBlockingOrdered.Clear();
8663 void ConnectionPool::TransactionInfo::MaybeUnblock(
8664 TransactionInfo& aTransactionInfo) {
8665 AssertIsOnBackgroundThread();
8666 MOZ_ASSERT(mBlockedOn.Contains(&aTransactionInfo));
8668 mBlockedOn.Remove(&aTransactionInfo);
8669 if (mBlockedOn.IsEmpty()) {
8670 ConnectionPool* connectionPool = mDatabaseInfo.mConnectionPool;
8671 MOZ_ASSERT(connectionPool);
8672 connectionPool->AssertIsOnOwningThread();
8674 Unused << connectionPool->ScheduleTransaction(
8675 *this,
8676 /* aFromQueuedTransactions */ false);
8680 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
8681 ConnectionPool::TransactionInfoPair::TransactionInfoPair() {
8682 AssertIsOnBackgroundThread();
8684 MOZ_COUNT_CTOR(ConnectionPool::TransactionInfoPair);
8687 ConnectionPool::TransactionInfoPair::~TransactionInfoPair() {
8688 AssertIsOnBackgroundThread();
8690 MOZ_COUNT_DTOR(ConnectionPool::TransactionInfoPair);
8692 #endif
8694 /*******************************************************************************
8695 * Metadata classes
8696 ******************************************************************************/
8698 bool FullObjectStoreMetadata::HasLiveIndexes() const {
8699 AssertIsOnBackgroundThread();
8701 return std::any_of(mIndexes.Values().cbegin(), mIndexes.Values().cend(),
8702 [](const auto& entry) { return !entry->mDeleted; });
8705 SafeRefPtr<FullDatabaseMetadata> FullDatabaseMetadata::Duplicate() const {
8706 AssertIsOnBackgroundThread();
8708 // FullDatabaseMetadata contains two hash tables of pointers that we need to
8709 // duplicate so we can't just use the copy constructor.
8710 auto newMetadata = MakeSafeRefPtr<FullDatabaseMetadata>(mCommonMetadata);
8712 newMetadata->mDatabaseId = mDatabaseId;
8713 newMetadata->mFilePath = mFilePath;
8714 newMetadata->mNextObjectStoreId = mNextObjectStoreId;
8715 newMetadata->mNextIndexId = mNextIndexId;
8717 for (const auto& objectStoreEntry : mObjectStores) {
8718 const auto& objectStoreValue = objectStoreEntry.GetData();
8720 auto newOSMetadata = MakeSafeRefPtr<FullObjectStoreMetadata>(
8721 objectStoreValue->mCommonMetadata, [&objectStoreValue] {
8722 const auto&& srcLocked = objectStoreValue->mAutoIncrementIds.Lock();
8723 return *srcLocked;
8724 }());
8726 for (const auto& indexEntry : objectStoreValue->mIndexes) {
8727 const auto& value = indexEntry.GetData();
8729 auto newIndexMetadata = MakeSafeRefPtr<FullIndexMetadata>();
8731 newIndexMetadata->mCommonMetadata = value->mCommonMetadata;
8733 if (NS_WARN_IF(!newOSMetadata->mIndexes.InsertOrUpdate(
8734 indexEntry.GetKey(), std::move(newIndexMetadata), fallible))) {
8735 return nullptr;
8739 MOZ_ASSERT(objectStoreValue->mIndexes.Count() ==
8740 newOSMetadata->mIndexes.Count());
8742 if (NS_WARN_IF(!newMetadata->mObjectStores.InsertOrUpdate(
8743 objectStoreEntry.GetKey(), std::move(newOSMetadata), fallible))) {
8744 return nullptr;
8748 MOZ_ASSERT(mObjectStores.Count() == newMetadata->mObjectStores.Count());
8750 return newMetadata;
8753 DatabaseLoggingInfo::~DatabaseLoggingInfo() {
8754 AssertIsOnBackgroundThread();
8756 if (gLoggingInfoHashtable) {
8757 const nsID& backgroundChildLoggingId =
8758 mLoggingInfo.backgroundChildLoggingId();
8760 MOZ_ASSERT(gLoggingInfoHashtable->Get(backgroundChildLoggingId) == this);
8762 gLoggingInfoHashtable->Remove(backgroundChildLoggingId);
8766 /*******************************************************************************
8767 * Factory
8768 ******************************************************************************/
8770 Factory::Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo,
8771 const nsACString& aSystemLocale)
8772 : mSystemLocale(aSystemLocale),
8773 mLoggingInfo(std::move(aLoggingInfo))
8774 #ifdef DEBUG
8776 mActorDestroyed(false)
8777 #endif
8779 AssertIsOnBackgroundThread();
8780 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8783 Factory::~Factory() { MOZ_ASSERT(mActorDestroyed); }
8785 // static
8786 SafeRefPtr<Factory> Factory::Create(const LoggingInfo& aLoggingInfo,
8787 const nsACString& aSystemLocale) {
8788 AssertIsOnBackgroundThread();
8789 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8791 // Balanced in ActoryDestroy().
8792 IncreaseBusyCount();
8794 MOZ_ASSERT(gLoggingInfoHashtable);
8795 RefPtr<DatabaseLoggingInfo> loggingInfo =
8796 gLoggingInfoHashtable->WithEntryHandle(
8797 aLoggingInfo.backgroundChildLoggingId(), [&](auto&& entry) {
8798 if (entry) {
8799 [[maybe_unused]] const auto& loggingInfo = entry.Data();
8800 MOZ_ASSERT(aLoggingInfo.backgroundChildLoggingId() ==
8801 loggingInfo->Id());
8802 #if !FUZZING
8803 NS_WARNING_ASSERTION(
8804 aLoggingInfo.nextTransactionSerialNumber() ==
8805 loggingInfo->mLoggingInfo.nextTransactionSerialNumber(),
8806 "NextTransactionSerialNumber doesn't match!");
8807 NS_WARNING_ASSERTION(
8808 aLoggingInfo.nextVersionChangeTransactionSerialNumber() ==
8809 loggingInfo->mLoggingInfo
8810 .nextVersionChangeTransactionSerialNumber(),
8811 "NextVersionChangeTransactionSerialNumber doesn't match!");
8812 NS_WARNING_ASSERTION(
8813 aLoggingInfo.nextRequestSerialNumber() ==
8814 loggingInfo->mLoggingInfo.nextRequestSerialNumber(),
8815 "NextRequestSerialNumber doesn't match!");
8816 #endif // !FUZZING
8817 } else {
8818 entry.Insert(new DatabaseLoggingInfo(aLoggingInfo));
8821 return do_AddRef(entry.Data());
8824 return MakeSafeRefPtr<Factory>(std::move(loggingInfo), aSystemLocale);
8827 void Factory::ActorDestroy(ActorDestroyReason aWhy) {
8828 AssertIsOnBackgroundThread();
8829 MOZ_ASSERT(!mActorDestroyed);
8831 #ifdef DEBUG
8832 mActorDestroyed = true;
8833 #endif
8835 // Match the IncreaseBusyCount in Create().
8836 DecreaseBusyCount();
8839 mozilla::ipc::IPCResult Factory::RecvDeleteMe() {
8840 AssertIsOnBackgroundThread();
8841 MOZ_ASSERT(!mActorDestroyed);
8843 QM_WARNONLY_TRY(OkIf(PBackgroundIDBFactoryParent::Send__delete__(this)));
8845 return IPC_OK();
8848 PBackgroundIDBFactoryRequestParent*
8849 Factory::AllocPBackgroundIDBFactoryRequestParent(
8850 const FactoryRequestParams& aParams) {
8851 AssertIsOnBackgroundThread();
8852 MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
8854 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
8855 return nullptr;
8858 const CommonFactoryRequestParams* commonParams;
8860 switch (aParams.type()) {
8861 case FactoryRequestParams::TOpenDatabaseRequestParams: {
8862 const OpenDatabaseRequestParams& params =
8863 aParams.get_OpenDatabaseRequestParams();
8864 commonParams = &params.commonParams();
8865 break;
8868 case FactoryRequestParams::TDeleteDatabaseRequestParams: {
8869 const DeleteDatabaseRequestParams& params =
8870 aParams.get_DeleteDatabaseRequestParams();
8871 commonParams = &params.commonParams();
8872 break;
8875 default:
8876 MOZ_CRASH("Should never get here!");
8879 MOZ_ASSERT(commonParams);
8881 const DatabaseMetadata& metadata = commonParams->metadata();
8883 if (NS_AUUF_OR_WARN_IF(!IsValidPersistenceType(metadata.persistenceType()))) {
8884 return nullptr;
8887 const PrincipalInfo& principalInfo = commonParams->principalInfo();
8889 if (NS_AUUF_OR_WARN_IF(!QuotaManager::IsPrincipalInfoValid(principalInfo))) {
8890 IPC_FAIL(this, "Invalid principal!");
8891 return nullptr;
8894 MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
8895 principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
8897 if (NS_AUUF_OR_WARN_IF(
8898 principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
8899 metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) {
8900 return nullptr;
8903 if (NS_AUUF_OR_WARN_IF(
8904 principalInfo.type() == PrincipalInfo::TContentPrincipalInfo &&
8905 QuotaManager::IsOriginInternal(
8906 principalInfo.get_ContentPrincipalInfo().originNoSuffix()) &&
8907 metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) {
8908 return nullptr;
8911 Maybe<ContentParentId> contentParentId = GetContentParentId();
8913 auto actor = [&]() -> RefPtr<FactoryRequestOp> {
8914 if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) {
8915 return MakeRefPtr<OpenDatabaseOp>(SafeRefPtrFromThis(), contentParentId,
8916 *commonParams);
8917 } else {
8918 return MakeRefPtr<DeleteDatabaseOp>(SafeRefPtrFromThis(), contentParentId,
8919 *commonParams);
8921 }();
8923 gFactoryOps->AppendElement(actor);
8925 // Balanced in CleanupMetadata() which is/must always called by SendResults().
8926 IncreaseBusyCount();
8928 // Transfer ownership to IPDL.
8929 return actor.forget().take();
8932 mozilla::ipc::IPCResult Factory::RecvPBackgroundIDBFactoryRequestConstructor(
8933 PBackgroundIDBFactoryRequestParent* aActor,
8934 const FactoryRequestParams& aParams) {
8935 AssertIsOnBackgroundThread();
8936 MOZ_ASSERT(aActor);
8937 MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
8938 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8940 auto* op = static_cast<FactoryRequestOp*>(aActor);
8942 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(op));
8943 return IPC_OK();
8946 bool Factory::DeallocPBackgroundIDBFactoryRequestParent(
8947 PBackgroundIDBFactoryRequestParent* aActor) {
8948 AssertIsOnBackgroundThread();
8949 MOZ_ASSERT(aActor);
8951 // Transfer ownership back from IPDL.
8952 RefPtr<FactoryRequestOp> op =
8953 dont_AddRef(static_cast<FactoryRequestOp*>(aActor));
8954 return true;
8957 mozilla::ipc::IPCResult Factory::RecvGetDatabases(
8958 const PersistenceType& aPersistenceType,
8959 const PrincipalInfo& aPrincipalInfo, GetDatabasesResolver&& aResolve) {
8960 AssertIsOnBackgroundThread();
8962 auto ResolveGetDatabasesAndReturn = [&aResolve](const nsresult rv) {
8963 aResolve(rv);
8964 return IPC_OK();
8967 QM_TRY(MOZ_TO_RESULT(!QuotaClient::IsShuttingDownOnBackgroundThread()),
8968 ResolveGetDatabasesAndReturn);
8970 QM_TRY(MOZ_TO_RESULT(IsValidPersistenceType(aPersistenceType)),
8971 QM_IPC_FAIL(this));
8973 QM_TRY(MOZ_TO_RESULT(QuotaManager::IsPrincipalInfoValid(aPrincipalInfo)),
8974 QM_IPC_FAIL(this));
8976 MOZ_ASSERT(aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
8977 aPrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
8979 PersistenceType persistenceType =
8980 IDBFactory::GetPersistenceType(aPrincipalInfo);
8982 QM_TRY(MOZ_TO_RESULT(aPersistenceType == persistenceType), QM_IPC_FAIL(this));
8984 Maybe<ContentParentId> contentParentId = GetContentParentId();
8986 auto op = MakeRefPtr<GetDatabasesOp>(SafeRefPtrFromThis(), contentParentId,
8987 aPersistenceType, aPrincipalInfo,
8988 std::move(aResolve));
8990 gFactoryOps->AppendElement(op);
8992 // Balanced in CleanupMetadata() which is/must always called by SendResults().
8993 IncreaseBusyCount();
8995 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(op));
8997 return IPC_OK();
9000 Maybe<ContentParentId> Factory::GetContentParentId() const {
9001 uint64_t childID = BackgroundParent::GetChildID(Manager());
9002 if (childID) {
9003 // If childID is not zero we are dealing with an other-process actor. We
9004 // want to initialize OpenDatabaseOp/DeleteDatabaseOp here with the ID
9005 // (and later also Database) in that case, so Database::IsOwnedByProcess
9006 // can find Databases belonging to a particular content process when
9007 // QuotaClient::AbortOperationsForProcess is called which is currently used
9008 // to abort operations for content processes only.
9009 return Some(ContentParentId(childID));
9012 return Nothing();
9015 /*******************************************************************************
9016 * WaitForTransactionsHelper
9017 ******************************************************************************/
9019 void WaitForTransactionsHelper::WaitForTransactions() {
9020 MOZ_ASSERT(mState == State::Initial);
9022 Unused << this->Run();
9025 void WaitForTransactionsHelper::MaybeWaitForTransactions() {
9026 AssertIsOnBackgroundThread();
9027 MOZ_ASSERT(mState == State::Initial);
9029 RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
9030 if (connectionPool) {
9031 mState = State::WaitingForTransactions;
9033 connectionPool->WaitForDatabaseToComplete(mDatabaseId, this);
9035 return;
9038 CallCallback();
9041 void WaitForTransactionsHelper::CallCallback() {
9042 AssertIsOnBackgroundThread();
9043 MOZ_ASSERT(mState == State::Initial ||
9044 mState == State::WaitingForTransactions);
9046 const nsCOMPtr<nsIRunnable> callback = std::move(mCallback);
9048 callback->Run();
9050 mState = State::Complete;
9053 NS_IMETHODIMP
9054 WaitForTransactionsHelper::Run() {
9055 MOZ_ASSERT(mState != State::Complete);
9056 MOZ_ASSERT(mCallback);
9058 switch (mState) {
9059 case State::Initial:
9060 MaybeWaitForTransactions();
9061 break;
9063 case State::WaitingForTransactions:
9064 CallCallback();
9065 break;
9067 default:
9068 MOZ_CRASH("Should never get here!");
9071 return NS_OK;
9074 /*******************************************************************************
9075 * Database
9076 ******************************************************************************/
9078 Database::Database(SafeRefPtr<Factory> aFactory,
9079 const PrincipalInfo& aPrincipalInfo,
9080 const Maybe<ContentParentId>& aOptionalContentParentId,
9081 const quota::OriginMetadata& aOriginMetadata,
9082 uint32_t aTelemetryId,
9083 SafeRefPtr<FullDatabaseMetadata> aMetadata,
9084 SafeRefPtr<DatabaseFileManager> aFileManager,
9085 RefPtr<DirectoryLock> aDirectoryLock,
9086 bool aInPrivateBrowsing,
9087 const Maybe<const CipherKey>& aMaybeKey)
9088 : mFactory(std::move(aFactory)),
9089 mMetadata(std::move(aMetadata)),
9090 mFileManager(std::move(aFileManager)),
9091 mDirectoryLock(std::move(aDirectoryLock)),
9092 mPrincipalInfo(aPrincipalInfo),
9093 mOptionalContentParentId(aOptionalContentParentId),
9094 mOriginMetadata(aOriginMetadata),
9095 mId(mMetadata->mDatabaseId),
9096 mFilePath(mMetadata->mFilePath),
9097 mKey(aMaybeKey),
9098 mTelemetryId(aTelemetryId),
9099 mPersistenceType(mMetadata->mCommonMetadata.persistenceType()),
9100 mInPrivateBrowsing(aInPrivateBrowsing),
9101 mBackgroundThread(GetCurrentSerialEventTarget())
9102 #ifdef DEBUG
9104 mAllBlobsUnmapped(false)
9105 #endif
9107 AssertIsOnBackgroundThread();
9108 MOZ_ASSERT(mFactory);
9109 MOZ_ASSERT(mMetadata);
9110 MOZ_ASSERT(mFileManager);
9112 MOZ_ASSERT(mDirectoryLock);
9113 MOZ_ASSERT(mDirectoryLock->Id() >= 0);
9114 mDirectoryLockId = mDirectoryLock->Id();
9117 template <typename T>
9118 bool Database::InvalidateAll(const nsTBaseHashSet<nsPtrHashKey<T>>& aTable) {
9119 AssertIsOnBackgroundThread();
9121 const uint32_t count = aTable.Count();
9122 if (!count) {
9123 return true;
9126 // XXX Does this really need to be fallible?
9127 QM_TRY_INSPECT(const auto& elementsToInvalidate,
9128 TransformIntoNewArray(
9129 aTable, [](const auto& entry) { return entry; }, fallible),
9130 false);
9132 IDB_REPORT_INTERNAL_ERR();
9134 for (const auto& elementToInvalidate : elementsToInvalidate) {
9135 MOZ_ASSERT(elementToInvalidate);
9137 elementToInvalidate->Invalidate();
9140 return true;
9143 void Database::Invalidate() {
9144 AssertIsOnBackgroundThread();
9146 if (mInvalidated) {
9147 return;
9150 mInvalidated.Flip();
9152 if (mActorWasAlive && !mActorDestroyed) {
9153 Unused << SendInvalidate();
9156 QM_WARNONLY_TRY(OkIf(InvalidateAll(mTransactions)));
9158 MOZ_ALWAYS_TRUE(CloseInternal());
9161 nsresult Database::EnsureConnection() {
9162 MOZ_ASSERT(!NS_IsMainThread());
9163 MOZ_ASSERT(!IsOnBackgroundThread());
9165 AUTO_PROFILER_LABEL("Database::EnsureConnection", DOM);
9167 if (!mConnection || !mConnection->HasStorageConnection()) {
9168 QM_TRY_UNWRAP(mConnection, gConnectionPool->GetOrCreateConnection(*this));
9171 AssertIsOnConnectionThread();
9173 return NS_OK;
9176 bool Database::RegisterTransaction(TransactionBase& aTransaction) {
9177 AssertIsOnBackgroundThread();
9178 MOZ_ASSERT(!mTransactions.Contains(&aTransaction));
9179 MOZ_ASSERT(mDirectoryLock);
9180 MOZ_ASSERT(!mInvalidated);
9181 MOZ_ASSERT(!mClosed);
9183 if (NS_WARN_IF(!mTransactions.Insert(&aTransaction, fallible))) {
9184 return false;
9187 return true;
9190 void Database::UnregisterTransaction(TransactionBase& aTransaction) {
9191 AssertIsOnBackgroundThread();
9192 MOZ_ASSERT(mTransactions.Contains(&aTransaction));
9194 mTransactions.Remove(&aTransaction);
9196 MaybeCloseConnection();
9199 void Database::SetActorAlive() {
9200 AssertIsOnBackgroundThread();
9201 MOZ_ASSERT(!mActorDestroyed);
9203 mActorWasAlive.Flip();
9206 void Database::MapBlob(const IPCBlob& aIPCBlob,
9207 SafeRefPtr<DatabaseFileInfo> aFileInfo) {
9208 AssertIsOnBackgroundThread();
9210 const RemoteLazyStream& stream = aIPCBlob.inputStream();
9211 MOZ_ASSERT(stream.type() == RemoteLazyStream::TRemoteLazyInputStream);
9213 nsID id{};
9214 MOZ_ALWAYS_SUCCEEDS(
9215 stream.get_RemoteLazyInputStream()->GetInternalStreamID(id));
9217 MOZ_ASSERT(!mMappedBlobs.Contains(id));
9218 mMappedBlobs.InsertOrUpdate(id, std::move(aFileInfo));
9220 RefPtr<UnmapBlobCallback> callback =
9221 new UnmapBlobCallback(SafeRefPtrFromThis());
9223 auto storage = RemoteLazyInputStreamStorage::Get();
9224 MOZ_ASSERT(storage.isOk());
9225 storage.inspect()->StoreCallback(id, callback);
9228 void Database::Stringify(nsACString& aResult) const {
9229 AssertIsOnBackgroundThread();
9231 constexpr auto kQuotaGenericDelimiterString = "|"_ns;
9233 aResult.Append(
9234 "DirectoryLock:"_ns + IntToCString(!!mDirectoryLock) +
9235 kQuotaGenericDelimiterString +
9237 "Transactions:"_ns + IntToCString(mTransactions.Count()) +
9238 kQuotaGenericDelimiterString +
9240 "OtherProcessActor:"_ns +
9241 IntToCString(
9242 BackgroundParent::IsOtherProcessActor(GetBackgroundParent())) +
9243 kQuotaGenericDelimiterString +
9245 "Origin:"_ns + AnonymizedOriginString(mOriginMetadata.mOrigin) +
9246 kQuotaGenericDelimiterString +
9248 "PersistenceType:"_ns + PersistenceTypeToString(mPersistenceType) +
9249 kQuotaGenericDelimiterString +
9251 "Closed:"_ns + IntToCString(static_cast<bool>(mClosed)) +
9252 kQuotaGenericDelimiterString +
9254 "Invalidated:"_ns + IntToCString(static_cast<bool>(mInvalidated)) +
9255 kQuotaGenericDelimiterString +
9257 "ActorWasAlive:"_ns + IntToCString(static_cast<bool>(mActorWasAlive)) +
9258 kQuotaGenericDelimiterString +
9260 "ActorDestroyed:"_ns + IntToCString(static_cast<bool>(mActorDestroyed)));
9263 SafeRefPtr<DatabaseFileInfo> Database::GetBlob(const IPCBlob& aIPCBlob) {
9264 AssertIsOnBackgroundThread();
9266 RefPtr<RemoteLazyInputStream> lazyStream;
9267 switch (aIPCBlob.inputStream().type()) {
9268 case RemoteLazyStream::TIPCStream: {
9269 const InputStreamParams& inputStreamParams =
9270 aIPCBlob.inputStream().get_IPCStream().stream();
9271 if (inputStreamParams.type() !=
9272 InputStreamParams::TRemoteLazyInputStreamParams) {
9273 return nullptr;
9275 lazyStream = inputStreamParams.get_RemoteLazyInputStreamParams().stream();
9276 break;
9278 case RemoteLazyStream::TRemoteLazyInputStream:
9279 lazyStream = aIPCBlob.inputStream().get_RemoteLazyInputStream();
9280 break;
9281 default:
9282 MOZ_ASSERT_UNREACHABLE("Unknown RemoteLazyStream type");
9283 return nullptr;
9286 if (!lazyStream) {
9287 MOZ_ASSERT_UNREACHABLE("Unexpected null stream");
9288 return nullptr;
9291 nsID id{};
9292 nsresult rv = lazyStream->GetInternalStreamID(id);
9293 if (NS_FAILED(rv)) {
9294 MOZ_ASSERT_UNREACHABLE(
9295 "Received RemoteLazyInputStream doesn't have an actor connection");
9296 return nullptr;
9299 const auto fileInfo = mMappedBlobs.Lookup(id);
9300 return fileInfo ? fileInfo->clonePtr() : nullptr;
9303 void Database::UnmapBlob(const nsID& aID) {
9304 AssertIsOnBackgroundThread();
9306 MOZ_ASSERT_IF(!mAllBlobsUnmapped, mMappedBlobs.Contains(aID));
9307 mMappedBlobs.Remove(aID);
9310 void Database::UnmapAllBlobs() {
9311 AssertIsOnBackgroundThread();
9313 #ifdef DEBUG
9314 mAllBlobsUnmapped = true;
9315 #endif
9317 mMappedBlobs.Clear();
9320 bool Database::CloseInternal() {
9321 AssertIsOnBackgroundThread();
9323 if (mClosed) {
9324 if (NS_WARN_IF(!IsInvalidated())) {
9325 // Signal misbehaving child for sending the close message twice.
9326 return false;
9329 // Ignore harmless race when we just invalidated the database.
9330 return true;
9333 mClosed.Flip();
9335 if (gConnectionPool) {
9336 gConnectionPool->CloseDatabaseWhenIdle(Id());
9339 DatabaseActorInfo* info;
9340 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
9342 MOZ_ASSERT(info->mLiveDatabases.Contains(this));
9344 if (info->mWaitingFactoryOp) {
9345 info->mWaitingFactoryOp->NoteDatabaseClosed(this);
9348 MaybeCloseConnection();
9350 return true;
9353 void Database::MaybeCloseConnection() {
9354 AssertIsOnBackgroundThread();
9356 if (!mTransactions.Count() && IsClosed() && mDirectoryLock) {
9357 nsCOMPtr<nsIRunnable> callback =
9358 NewRunnableMethod("dom::indexedDB::Database::ConnectionClosedCallback",
9359 this, &Database::ConnectionClosedCallback);
9361 RefPtr<WaitForTransactionsHelper> helper =
9362 new WaitForTransactionsHelper(Id(), callback);
9363 helper->WaitForTransactions();
9367 void Database::ConnectionClosedCallback() {
9368 AssertIsOnBackgroundThread();
9369 MOZ_ASSERT(mClosed);
9370 MOZ_ASSERT(!mTransactions.Count());
9372 mDirectoryLock = nullptr;
9374 CleanupMetadata();
9376 UnmapAllBlobs();
9378 if (IsInvalidated() && IsActorAlive()) {
9379 // Step 3 and 4 of "5.2 Closing a Database":
9380 // 1. Wait for all transactions to complete.
9381 // 2. Fire a close event if forced flag is set, i.e., IsInvalidated() in our
9382 // implementation.
9383 Unused << SendCloseAfterInvalidationComplete();
9387 void Database::CleanupMetadata() {
9388 AssertIsOnBackgroundThread();
9390 DatabaseActorInfo* info;
9391 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
9392 MOZ_ALWAYS_TRUE(info->mLiveDatabases.RemoveElement(this));
9394 QuotaManager::MaybeRecordQuotaClientShutdownStep(
9395 quota::Client::IDB, "Live database entry removed"_ns);
9397 if (info->mLiveDatabases.IsEmpty()) {
9398 MOZ_ASSERT(!info->mWaitingFactoryOp ||
9399 !info->mWaitingFactoryOp->HasBlockedDatabases());
9400 gLiveDatabaseHashtable->Remove(Id());
9402 QuotaManager::MaybeRecordQuotaClientShutdownStep(
9403 quota::Client::IDB, "gLiveDatabaseHashtable entry removed"_ns);
9406 // Match the IncreaseBusyCount in OpenDatabaseOp::EnsureDatabaseActor().
9407 DecreaseBusyCount();
9410 void Database::ActorDestroy(ActorDestroyReason aWhy) {
9411 AssertIsOnBackgroundThread();
9413 mActorDestroyed.Flip();
9415 if (!IsInvalidated()) {
9416 Invalidate();
9420 PBackgroundIDBDatabaseFileParent*
9421 Database::AllocPBackgroundIDBDatabaseFileParent(const IPCBlob& aIPCBlob) {
9422 AssertIsOnBackgroundThread();
9424 SafeRefPtr<DatabaseFileInfo> fileInfo = GetBlob(aIPCBlob);
9425 RefPtr<DatabaseFile> actor;
9427 if (fileInfo) {
9428 actor = new DatabaseFile(std::move(fileInfo));
9429 } else {
9430 // This is a blob we haven't seen before.
9431 fileInfo = mFileManager->CreateFileInfo();
9432 if (NS_WARN_IF(!fileInfo)) {
9433 return nullptr;
9436 actor = new DatabaseFile(IPCBlobUtils::Deserialize(aIPCBlob),
9437 std::move(fileInfo));
9440 MOZ_ASSERT(actor);
9442 return actor.forget().take();
9445 bool Database::DeallocPBackgroundIDBDatabaseFileParent(
9446 PBackgroundIDBDatabaseFileParent* aActor) {
9447 AssertIsOnBackgroundThread();
9448 MOZ_ASSERT(aActor);
9450 RefPtr<DatabaseFile> actor = dont_AddRef(static_cast<DatabaseFile*>(aActor));
9451 return true;
9454 already_AddRefed<PBackgroundIDBTransactionParent>
9455 Database::AllocPBackgroundIDBTransactionParent(
9456 const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode,
9457 const Durability& aDurability) {
9458 AssertIsOnBackgroundThread();
9460 // Once a database is closed it must not try to open new transactions.
9461 if (NS_WARN_IF(mClosed)) {
9462 MOZ_ASSERT_UNLESS_FUZZING(mInvalidated);
9463 return nullptr;
9466 if (NS_AUUF_OR_WARN_IF(aObjectStoreNames.IsEmpty())) {
9467 return nullptr;
9470 if (NS_AUUF_OR_WARN_IF(aMode != IDBTransaction::Mode::ReadOnly &&
9471 aMode != IDBTransaction::Mode::ReadWrite &&
9472 aMode != IDBTransaction::Mode::ReadWriteFlush &&
9473 aMode != IDBTransaction::Mode::Cleanup)) {
9474 return nullptr;
9477 if (NS_AUUF_OR_WARN_IF(aDurability != IDBTransaction::Durability::Default &&
9478 aDurability != IDBTransaction::Durability::Strict &&
9479 aDurability != IDBTransaction::Durability::Relaxed)) {
9480 return nullptr;
9483 const ObjectStoreTable& objectStores = mMetadata->mObjectStores;
9484 const uint32_t nameCount = aObjectStoreNames.Length();
9486 if (NS_AUUF_OR_WARN_IF(nameCount > objectStores.Count())) {
9487 return nullptr;
9490 QM_TRY_UNWRAP(
9491 auto objectStoreMetadatas,
9492 TransformIntoNewArrayAbortOnErr(
9493 aObjectStoreNames,
9494 [lastName = Maybe<const nsString&>{},
9495 &objectStores](const nsString& name) mutable
9496 -> mozilla::Result<SafeRefPtr<FullObjectStoreMetadata>, nsresult> {
9497 if (lastName) {
9498 // Make sure that this name is sorted properly and not a
9499 // duplicate.
9500 if (NS_AUUF_OR_WARN_IF(name <= lastName.ref())) {
9501 return Err(NS_ERROR_FAILURE);
9504 lastName = SomeRef(name);
9506 const auto foundIt =
9507 std::find_if(objectStores.cbegin(), objectStores.cend(),
9508 [&name](const auto& entry) {
9509 const auto& value = entry.GetData();
9510 MOZ_ASSERT(entry.GetKey());
9511 return name == value->mCommonMetadata.name() &&
9512 !value->mDeleted;
9514 if (foundIt == objectStores.cend()) {
9515 MOZ_ASSERT_UNLESS_FUZZING(false, "ObjectStore not found.");
9516 return Err(NS_ERROR_FAILURE);
9519 return foundIt->GetData().clonePtr();
9521 fallible),
9522 nullptr);
9524 return MakeSafeRefPtr<NormalTransaction>(SafeRefPtrFromThis(), aMode,
9525 aDurability,
9526 std::move(objectStoreMetadatas))
9527 .forget();
9530 mozilla::ipc::IPCResult Database::RecvPBackgroundIDBTransactionConstructor(
9531 PBackgroundIDBTransactionParent* aActor,
9532 nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode,
9533 const Durability& aDurability) { // TODO: See bug 1883045
9534 AssertIsOnBackgroundThread();
9535 MOZ_ASSERT(aActor);
9536 MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
9537 MOZ_ASSERT(aMode == IDBTransaction::Mode::ReadOnly ||
9538 aMode == IDBTransaction::Mode::ReadWrite ||
9539 aMode == IDBTransaction::Mode::ReadWriteFlush ||
9540 aMode == IDBTransaction::Mode::Cleanup);
9541 MOZ_ASSERT(aDurability == IDBTransaction::Durability::Default ||
9542 aDurability == IDBTransaction::Durability::Strict ||
9543 aDurability == IDBTransaction::Durability::Relaxed);
9544 MOZ_ASSERT(!mClosed);
9546 if (IsInvalidated()) {
9547 // This is an expected race. We don't want the child to die here, just don't
9548 // actually do any work.
9549 return IPC_OK();
9552 if (!gConnectionPool) {
9553 gConnectionPool = new ConnectionPool();
9556 auto* transaction = static_cast<NormalTransaction*>(aActor);
9558 RefPtr<StartTransactionOp> startOp = new StartTransactionOp(
9559 SafeRefPtr{transaction, AcquireStrongRefFromRawPtr{}});
9561 uint64_t transactionId = startOp->StartOnConnectionPool(
9562 GetLoggingInfo()->Id(), mMetadata->mDatabaseId,
9563 transaction->LoggingSerialNumber(), aObjectStoreNames,
9564 aMode != IDBTransaction::Mode::ReadOnly);
9566 transaction->Init(transactionId);
9568 if (NS_WARN_IF(!RegisterTransaction(*transaction))) {
9569 IDB_REPORT_INTERNAL_ERR();
9570 transaction->Abort(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, /* aForce */ false);
9571 return IPC_OK();
9574 return IPC_OK();
9577 mozilla::ipc::IPCResult Database::RecvDeleteMe() {
9578 AssertIsOnBackgroundThread();
9579 MOZ_ASSERT(!mActorDestroyed);
9581 QM_WARNONLY_TRY(OkIf(PBackgroundIDBDatabaseParent::Send__delete__(this)));
9583 return IPC_OK();
9586 mozilla::ipc::IPCResult Database::RecvBlocked() {
9587 AssertIsOnBackgroundThread();
9589 if (NS_WARN_IF(mClosed)) {
9590 return IPC_FAIL(this, "Database already closed!");
9593 DatabaseActorInfo* info;
9594 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
9595 MOZ_ASSERT(info->mLiveDatabases.Contains(this));
9597 if (NS_WARN_IF(!info->mWaitingFactoryOp)) {
9598 return IPC_FAIL(this, "Database info has no mWaitingFactoryOp!");
9601 info->mWaitingFactoryOp->NoteDatabaseBlocked(this);
9603 return IPC_OK();
9606 mozilla::ipc::IPCResult Database::RecvClose() {
9607 AssertIsOnBackgroundThread();
9609 if (NS_WARN_IF(!CloseInternal())) {
9610 return IPC_FAIL(this, "CloseInternal failed!");
9613 return IPC_OK();
9616 void Database::StartTransactionOp::RunOnConnectionThread() {
9617 MOZ_ASSERT(!IsOnBackgroundThread());
9618 MOZ_ASSERT(!HasFailed());
9620 IDB_LOG_MARK_PARENT_TRANSACTION("Beginning database work", "DB Start",
9621 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
9622 mTransactionLoggingSerialNumber);
9624 TransactionDatabaseOperationBase::RunOnConnectionThread();
9627 nsresult Database::StartTransactionOp::DoDatabaseWork(
9628 DatabaseConnection* aConnection) {
9629 MOZ_ASSERT(aConnection);
9630 aConnection->AssertIsOnConnectionThread();
9632 Transaction().SetActiveOnConnectionThread();
9634 if (Transaction().GetMode() == IDBTransaction::Mode::Cleanup) {
9635 DebugOnly<nsresult> rv = aConnection->DisableQuotaChecks();
9636 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
9637 "DisableQuotaChecks failed, trying to continue "
9638 "cleanup transaction with quota checks enabled");
9641 if (Transaction().GetMode() != IDBTransaction::Mode::ReadOnly) {
9642 QM_TRY(MOZ_TO_RESULT(aConnection->BeginWriteTransaction()));
9645 return NS_OK;
9648 nsresult Database::StartTransactionOp::SendSuccessResult() {
9649 // We don't need to do anything here.
9650 return NS_OK;
9653 bool Database::StartTransactionOp::SendFailureResult(
9654 nsresult /* aResultCode */) {
9655 IDB_REPORT_INTERNAL_ERR();
9657 // Abort the transaction.
9658 return false;
9661 void Database::StartTransactionOp::Cleanup() {
9662 #ifdef DEBUG
9663 // StartTransactionOp is not a normal database operation that is tied to an
9664 // actor. Do this to make our assertions happy.
9665 NoteActorDestroyed();
9666 #endif
9668 TransactionDatabaseOperationBase::Cleanup();
9671 /*******************************************************************************
9672 * TransactionBase
9673 ******************************************************************************/
9675 TransactionBase::TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode,
9676 Durability aDurability)
9677 : mDatabase(std::move(aDatabase)),
9678 mDatabaseId(mDatabase->Id()),
9679 mLoggingSerialNumber(
9680 mDatabase->GetLoggingInfo()->NextTransactionSN(aMode)),
9681 mActiveRequestCount(0),
9682 mInvalidatedOnAnyThread(false),
9683 mMode(aMode),
9684 mDurability(aDurability),
9685 mResultCode(NS_OK) {
9686 AssertIsOnBackgroundThread();
9687 MOZ_ASSERT(mDatabase);
9688 MOZ_ASSERT(mLoggingSerialNumber);
9691 TransactionBase::~TransactionBase() {
9692 MOZ_ASSERT(!mActiveRequestCount);
9693 MOZ_ASSERT(mActorDestroyed);
9694 MOZ_ASSERT_IF(mInitialized, mCommittedOrAborted);
9697 void TransactionBase::Abort(nsresult aResultCode, bool aForce) {
9698 AssertIsOnBackgroundThread();
9699 MOZ_ASSERT(NS_FAILED(aResultCode));
9701 if (NS_SUCCEEDED(mResultCode)) {
9702 mResultCode = aResultCode;
9705 if (aForce) {
9706 mForceAborted.EnsureFlipped();
9709 MaybeCommitOrAbort();
9712 mozilla::ipc::IPCResult TransactionBase::RecvCommit(
9713 IProtocol* aActor, const Maybe<int64_t> aLastRequest) {
9714 AssertIsOnBackgroundThread();
9716 if (NS_WARN_IF(mCommitOrAbortReceived)) {
9717 return IPC_FAIL(
9718 aActor, "Attempt to commit an already comitted/aborted transaction!");
9721 mCommitOrAbortReceived.Flip();
9722 mLastRequestBeforeCommit.init(aLastRequest);
9723 MaybeCommitOrAbort();
9725 return IPC_OK();
9728 mozilla::ipc::IPCResult TransactionBase::RecvAbort(IProtocol* aActor,
9729 nsresult aResultCode) {
9730 AssertIsOnBackgroundThread();
9732 if (NS_WARN_IF(NS_SUCCEEDED(aResultCode))) {
9733 return IPC_FAIL(aActor, "aResultCode must not be a success code!");
9736 if (NS_WARN_IF(NS_ERROR_GET_MODULE(aResultCode) !=
9737 NS_ERROR_MODULE_DOM_INDEXEDDB)) {
9738 return IPC_FAIL(aActor, "aResultCode does not refer to IndexedDB!");
9741 if (NS_WARN_IF(mCommitOrAbortReceived)) {
9742 return IPC_FAIL(
9743 aActor, "Attempt to abort an already comitted/aborted transaction!");
9746 mCommitOrAbortReceived.Flip();
9747 Abort(aResultCode, /* aForce */ false);
9749 return IPC_OK();
9752 void TransactionBase::CommitOrAbort() {
9753 AssertIsOnBackgroundThread();
9755 mCommittedOrAborted.Flip();
9757 if (!mInitialized) {
9758 return;
9761 // In case of a failed request and explicitly committed transaction, abort
9762 // (cf. https://w3c.github.io/IndexedDB/#async-execute-request step 5.3
9763 // vs. 5.4). It's worth emphasizing this can only happen here when we are
9764 // committing explicitly, otherwise the decision is made by the child.
9765 if (NS_SUCCEEDED(mResultCode) && mLastFailedRequest &&
9766 *mLastRequestBeforeCommit &&
9767 *mLastFailedRequest == **mLastRequestBeforeCommit) {
9768 mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
9771 RefPtr<CommitOp> commitOp =
9772 new CommitOp(SafeRefPtrFromThis(), ClampResultCode(mResultCode));
9774 gConnectionPool->Finish(TransactionId(), commitOp);
9777 SafeRefPtr<FullObjectStoreMetadata>
9778 TransactionBase::GetMetadataForObjectStoreId(
9779 IndexOrObjectStoreId aObjectStoreId) const {
9780 AssertIsOnBackgroundThread();
9781 MOZ_ASSERT(aObjectStoreId);
9783 if (!aObjectStoreId) {
9784 return nullptr;
9787 auto metadata = mDatabase->Metadata().mObjectStores.Lookup(aObjectStoreId);
9788 if (!metadata || (*metadata)->mDeleted) {
9789 return nullptr;
9792 MOZ_ASSERT((*metadata)->mCommonMetadata.id() == aObjectStoreId);
9794 return metadata->clonePtr();
9797 SafeRefPtr<FullIndexMetadata> TransactionBase::GetMetadataForIndexId(
9798 FullObjectStoreMetadata& aObjectStoreMetadata,
9799 IndexOrObjectStoreId aIndexId) const {
9800 AssertIsOnBackgroundThread();
9801 MOZ_ASSERT(aIndexId);
9803 if (!aIndexId) {
9804 return nullptr;
9807 auto metadata = aObjectStoreMetadata.mIndexes.Lookup(aIndexId);
9808 if (!metadata || (*metadata)->mDeleted) {
9809 return nullptr;
9812 MOZ_ASSERT((*metadata)->mCommonMetadata.id() == aIndexId);
9814 return metadata->clonePtr();
9817 void TransactionBase::NoteModifiedAutoIncrementObjectStore(
9818 const SafeRefPtr<FullObjectStoreMetadata>& aMetadata) {
9819 AssertIsOnConnectionThread();
9821 if (!mModifiedAutoIncrementObjectStoreMetadataArray.Contains(aMetadata)) {
9822 mModifiedAutoIncrementObjectStoreMetadataArray.AppendElement(
9823 aMetadata.clonePtr());
9827 void TransactionBase::ForgetModifiedAutoIncrementObjectStore(
9828 FullObjectStoreMetadata& aMetadata) {
9829 AssertIsOnConnectionThread();
9831 mModifiedAutoIncrementObjectStoreMetadataArray.RemoveElement(&aMetadata);
9834 bool TransactionBase::VerifyRequestParams(const RequestParams& aParams) const {
9835 AssertIsOnBackgroundThread();
9836 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
9838 switch (aParams.type()) {
9839 case RequestParams::TObjectStoreAddParams: {
9840 const ObjectStoreAddPutParams& params =
9841 aParams.get_ObjectStoreAddParams().commonParams();
9842 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params))) {
9843 return false;
9845 break;
9848 case RequestParams::TObjectStorePutParams: {
9849 const ObjectStoreAddPutParams& params =
9850 aParams.get_ObjectStorePutParams().commonParams();
9851 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params))) {
9852 return false;
9854 break;
9857 case RequestParams::TObjectStoreGetParams: {
9858 const ObjectStoreGetParams& params = aParams.get_ObjectStoreGetParams();
9859 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9860 GetMetadataForObjectStoreId(params.objectStoreId());
9861 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9862 return false;
9864 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
9865 return false;
9867 break;
9870 case RequestParams::TObjectStoreGetKeyParams: {
9871 const ObjectStoreGetKeyParams& params =
9872 aParams.get_ObjectStoreGetKeyParams();
9873 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9874 GetMetadataForObjectStoreId(params.objectStoreId());
9875 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9876 return false;
9878 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
9879 return false;
9881 break;
9884 case RequestParams::TObjectStoreGetAllParams: {
9885 const ObjectStoreGetAllParams& params =
9886 aParams.get_ObjectStoreGetAllParams();
9887 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9888 GetMetadataForObjectStoreId(params.objectStoreId());
9889 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9890 return false;
9892 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
9893 return false;
9895 break;
9898 case RequestParams::TObjectStoreGetAllKeysParams: {
9899 const ObjectStoreGetAllKeysParams& params =
9900 aParams.get_ObjectStoreGetAllKeysParams();
9901 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9902 GetMetadataForObjectStoreId(params.objectStoreId());
9903 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9904 return false;
9906 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
9907 return false;
9909 break;
9912 case RequestParams::TObjectStoreDeleteParams: {
9913 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite &&
9914 mMode != IDBTransaction::Mode::ReadWriteFlush &&
9915 mMode != IDBTransaction::Mode::Cleanup &&
9916 mMode != IDBTransaction::Mode::VersionChange)) {
9917 return false;
9920 const ObjectStoreDeleteParams& params =
9921 aParams.get_ObjectStoreDeleteParams();
9922 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9923 GetMetadataForObjectStoreId(params.objectStoreId());
9924 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9925 return false;
9927 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
9928 return false;
9930 break;
9933 case RequestParams::TObjectStoreClearParams: {
9934 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite &&
9935 mMode != IDBTransaction::Mode::ReadWriteFlush &&
9936 mMode != IDBTransaction::Mode::Cleanup &&
9937 mMode != IDBTransaction::Mode::VersionChange)) {
9938 return false;
9941 const ObjectStoreClearParams& params =
9942 aParams.get_ObjectStoreClearParams();
9943 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9944 GetMetadataForObjectStoreId(params.objectStoreId());
9945 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9946 return false;
9948 break;
9951 case RequestParams::TObjectStoreCountParams: {
9952 const ObjectStoreCountParams& params =
9953 aParams.get_ObjectStoreCountParams();
9954 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9955 GetMetadataForObjectStoreId(params.objectStoreId());
9956 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9957 return false;
9959 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
9960 return false;
9962 break;
9965 case RequestParams::TIndexGetParams: {
9966 const IndexGetParams& params = aParams.get_IndexGetParams();
9967 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9968 GetMetadataForObjectStoreId(params.objectStoreId());
9969 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9970 return false;
9972 const SafeRefPtr<FullIndexMetadata> indexMetadata =
9973 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
9974 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
9975 return false;
9977 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
9978 return false;
9980 break;
9983 case RequestParams::TIndexGetKeyParams: {
9984 const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams();
9985 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
9986 GetMetadataForObjectStoreId(params.objectStoreId());
9987 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
9988 return false;
9990 const SafeRefPtr<FullIndexMetadata> indexMetadata =
9991 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
9992 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
9993 return false;
9995 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
9996 return false;
9998 break;
10001 case RequestParams::TIndexGetAllParams: {
10002 const IndexGetAllParams& params = aParams.get_IndexGetAllParams();
10003 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10004 GetMetadataForObjectStoreId(params.objectStoreId());
10005 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10006 return false;
10008 const SafeRefPtr<FullIndexMetadata> indexMetadata =
10009 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
10010 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10011 return false;
10013 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10014 return false;
10016 break;
10019 case RequestParams::TIndexGetAllKeysParams: {
10020 const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams();
10021 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10022 GetMetadataForObjectStoreId(params.objectStoreId());
10023 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10024 return false;
10026 const SafeRefPtr<FullIndexMetadata> indexMetadata =
10027 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
10028 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10029 return false;
10031 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10032 return false;
10034 break;
10037 case RequestParams::TIndexCountParams: {
10038 const IndexCountParams& params = aParams.get_IndexCountParams();
10039 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
10040 GetMetadataForObjectStoreId(params.objectStoreId());
10041 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10042 return false;
10044 const SafeRefPtr<FullIndexMetadata> indexMetadata =
10045 GetMetadataForIndexId(*objectStoreMetadata, params.indexId());
10046 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10047 return false;
10049 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
10050 return false;
10052 break;
10055 default:
10056 MOZ_CRASH("Should never get here!");
10059 return true;
10062 bool TransactionBase::VerifyRequestParams(
10063 const SerializedKeyRange& aParams) const {
10064 AssertIsOnBackgroundThread();
10066 // XXX Check more here?
10068 if (aParams.isOnly()) {
10069 if (NS_AUUF_OR_WARN_IF(aParams.lower().IsUnset())) {
10070 return false;
10072 if (NS_AUUF_OR_WARN_IF(!aParams.upper().IsUnset())) {
10073 return false;
10075 if (NS_AUUF_OR_WARN_IF(aParams.lowerOpen())) {
10076 return false;
10078 if (NS_AUUF_OR_WARN_IF(aParams.upperOpen())) {
10079 return false;
10081 } else if (NS_AUUF_OR_WARN_IF(aParams.lower().IsUnset() &&
10082 aParams.upper().IsUnset())) {
10083 return false;
10086 return true;
10089 bool TransactionBase::VerifyRequestParams(
10090 const ObjectStoreAddPutParams& aParams) const {
10091 AssertIsOnBackgroundThread();
10093 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite &&
10094 mMode != IDBTransaction::Mode::ReadWriteFlush &&
10095 mMode != IDBTransaction::Mode::VersionChange)) {
10096 return false;
10099 SafeRefPtr<FullObjectStoreMetadata> objMetadata =
10100 GetMetadataForObjectStoreId(aParams.objectStoreId());
10101 if (NS_AUUF_OR_WARN_IF(!objMetadata)) {
10102 return false;
10105 if (NS_AUUF_OR_WARN_IF(!aParams.cloneInfo().data().data.Size())) {
10106 return false;
10109 if (objMetadata->mCommonMetadata.autoIncrement() &&
10110 objMetadata->mCommonMetadata.keyPath().IsValid() &&
10111 aParams.key().IsUnset()) {
10112 const SerializedStructuredCloneWriteInfo& cloneInfo = aParams.cloneInfo();
10114 if (NS_AUUF_OR_WARN_IF(!cloneInfo.offsetToKeyProp())) {
10115 return false;
10118 if (NS_AUUF_OR_WARN_IF(cloneInfo.data().data.Size() < sizeof(uint64_t))) {
10119 return false;
10122 if (NS_AUUF_OR_WARN_IF(cloneInfo.offsetToKeyProp() >
10123 (cloneInfo.data().data.Size() - sizeof(uint64_t)))) {
10124 return false;
10126 } else if (NS_AUUF_OR_WARN_IF(aParams.cloneInfo().offsetToKeyProp())) {
10127 return false;
10130 for (const auto& updateInfo : aParams.indexUpdateInfos()) {
10131 SafeRefPtr<FullIndexMetadata> indexMetadata =
10132 GetMetadataForIndexId(*objMetadata, updateInfo.indexId());
10133 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10134 return false;
10137 if (NS_AUUF_OR_WARN_IF(updateInfo.value().IsUnset())) {
10138 return false;
10141 MOZ_ASSERT(!updateInfo.value().GetBuffer().IsEmpty());
10144 for (const FileAddInfo& fileAddInfo : aParams.fileAddInfos()) {
10145 const PBackgroundIDBDatabaseFileParent* file =
10146 fileAddInfo.file().AsParent();
10148 switch (fileAddInfo.type()) {
10149 case StructuredCloneFileBase::eBlob:
10150 if (NS_AUUF_OR_WARN_IF(!file)) {
10151 return false;
10153 break;
10155 case StructuredCloneFileBase::eMutableFile: {
10156 return false;
10159 case StructuredCloneFileBase::eStructuredClone:
10160 case StructuredCloneFileBase::eWasmBytecode:
10161 case StructuredCloneFileBase::eWasmCompiled:
10162 case StructuredCloneFileBase::eEndGuard:
10163 MOZ_ASSERT_UNLESS_FUZZING(false, "Unsupported.");
10164 return false;
10166 default:
10167 MOZ_CRASH("Should never get here!");
10171 return true;
10174 bool TransactionBase::VerifyRequestParams(
10175 const Maybe<SerializedKeyRange>& aParams) const {
10176 AssertIsOnBackgroundThread();
10178 if (aParams.isSome()) {
10179 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(aParams.ref()))) {
10180 return false;
10184 return true;
10187 void TransactionBase::NoteActiveRequest() {
10188 AssertIsOnBackgroundThread();
10189 MOZ_ASSERT(mActiveRequestCount < UINT64_MAX);
10191 mActiveRequestCount++;
10194 void TransactionBase::NoteFinishedRequest(const int64_t aRequestId,
10195 const nsresult aResultCode) {
10196 AssertIsOnBackgroundThread();
10197 MOZ_ASSERT(mActiveRequestCount);
10199 mActiveRequestCount--;
10201 if (NS_FAILED(aResultCode)) {
10202 mLastFailedRequest = Some(aRequestId);
10205 MaybeCommitOrAbort();
10208 void TransactionBase::Invalidate() {
10209 AssertIsOnBackgroundThread();
10210 MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread);
10212 if (!mInvalidated) {
10213 mInvalidated.Flip();
10214 mInvalidatedOnAnyThread = true;
10216 Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, /* aForce */ false);
10220 PBackgroundIDBRequestParent* TransactionBase::AllocRequest(
10221 const int64_t aRequestId, RequestParams&& aParams, bool aTrustParams) {
10222 AssertIsOnBackgroundThread();
10223 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
10225 #ifdef DEBUG
10226 // Always verify parameters in DEBUG builds!
10227 aTrustParams = false;
10228 #endif
10230 if (NS_AUUF_OR_WARN_IF(!aTrustParams && !VerifyRequestParams(aParams))) {
10231 return nullptr;
10234 if (NS_AUUF_OR_WARN_IF(mCommitOrAbortReceived)) {
10235 return nullptr;
10238 RefPtr<NormalTransactionOp> actor;
10240 switch (aParams.type()) {
10241 case RequestParams::TObjectStoreAddParams:
10242 case RequestParams::TObjectStorePutParams:
10243 actor = new ObjectStoreAddOrPutRequestOp(SafeRefPtrFromThis(), aRequestId,
10244 std::move(aParams));
10245 break;
10247 case RequestParams::TObjectStoreGetParams:
10248 actor =
10249 new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams,
10250 /* aGetAll */ false);
10251 break;
10253 case RequestParams::TObjectStoreGetAllParams:
10254 actor =
10255 new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams,
10256 /* aGetAll */ true);
10257 break;
10259 case RequestParams::TObjectStoreGetKeyParams:
10260 actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId,
10261 aParams,
10262 /* aGetAll */ false);
10263 break;
10265 case RequestParams::TObjectStoreGetAllKeysParams:
10266 actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId,
10267 aParams,
10268 /* aGetAll */ true);
10269 break;
10271 case RequestParams::TObjectStoreDeleteParams:
10272 actor =
10273 new ObjectStoreDeleteRequestOp(SafeRefPtrFromThis(), aRequestId,
10274 aParams.get_ObjectStoreDeleteParams());
10275 break;
10277 case RequestParams::TObjectStoreClearParams:
10278 actor =
10279 new ObjectStoreClearRequestOp(SafeRefPtrFromThis(), aRequestId,
10280 aParams.get_ObjectStoreClearParams());
10281 break;
10283 case RequestParams::TObjectStoreCountParams:
10284 actor =
10285 new ObjectStoreCountRequestOp(SafeRefPtrFromThis(), aRequestId,
10286 aParams.get_ObjectStoreCountParams());
10287 break;
10289 case RequestParams::TIndexGetParams:
10290 actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams,
10291 /* aGetAll */ false);
10292 break;
10294 case RequestParams::TIndexGetKeyParams:
10295 actor =
10296 new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, aParams,
10297 /* aGetAll */ false);
10298 break;
10300 case RequestParams::TIndexGetAllParams:
10301 actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams,
10302 /* aGetAll */ true);
10303 break;
10305 case RequestParams::TIndexGetAllKeysParams:
10306 actor =
10307 new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, aParams,
10308 /* aGetAll */ true);
10309 break;
10311 case RequestParams::TIndexCountParams:
10312 actor =
10313 new IndexCountRequestOp(SafeRefPtrFromThis(), aRequestId, aParams);
10314 break;
10316 default:
10317 MOZ_CRASH("Should never get here!");
10320 MOZ_ASSERT(actor);
10322 // Transfer ownership to IPDL.
10323 return actor.forget().take();
10326 bool TransactionBase::StartRequest(PBackgroundIDBRequestParent* aActor) {
10327 AssertIsOnBackgroundThread();
10328 MOZ_ASSERT(aActor);
10330 auto* op = static_cast<NormalTransactionOp*>(aActor);
10332 if (NS_WARN_IF(!op->Init(*this))) {
10333 op->Cleanup();
10334 return false;
10337 op->DispatchToConnectionPool();
10338 return true;
10341 bool TransactionBase::DeallocRequest(
10342 PBackgroundIDBRequestParent* const aActor) {
10343 AssertIsOnBackgroundThread();
10344 MOZ_ASSERT(aActor);
10346 // Transfer ownership back from IPDL.
10347 const RefPtr<NormalTransactionOp> actor =
10348 dont_AddRef(static_cast<NormalTransactionOp*>(aActor));
10349 return true;
10352 already_AddRefed<PBackgroundIDBCursorParent> TransactionBase::AllocCursor(
10353 const OpenCursorParams& aParams, bool aTrustParams) {
10354 AssertIsOnBackgroundThread();
10355 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
10357 #ifdef DEBUG
10358 // Always verify parameters in DEBUG builds!
10359 aTrustParams = false;
10360 #endif
10362 const OpenCursorParams::Type type = aParams.type();
10363 SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata;
10364 SafeRefPtr<FullIndexMetadata> indexMetadata;
10365 CursorBase::Direction direction;
10367 // First extract the parameters common to all open cursor variants.
10368 const auto& commonParams = GetCommonOpenCursorParams(aParams);
10369 objectStoreMetadata =
10370 GetMetadataForObjectStoreId(commonParams.objectStoreId());
10371 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) {
10372 return nullptr;
10374 if (aTrustParams && NS_AUUF_OR_WARN_IF(!VerifyRequestParams(
10375 commonParams.optionalKeyRange()))) {
10376 return nullptr;
10378 direction = commonParams.direction();
10380 // Now, for the index open cursor variants, extract the additional parameter.
10381 if (type == OpenCursorParams::TIndexOpenCursorParams ||
10382 type == OpenCursorParams::TIndexOpenKeyCursorParams) {
10383 const auto& commonIndexParams = GetCommonIndexOpenCursorParams(aParams);
10384 indexMetadata = GetMetadataForIndexId(*objectStoreMetadata,
10385 commonIndexParams.indexId());
10386 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) {
10387 return nullptr;
10391 if (NS_AUUF_OR_WARN_IF(mCommitOrAbortReceived)) {
10392 return nullptr;
10395 // Create Cursor and transfer ownership to IPDL.
10396 switch (type) {
10397 case OpenCursorParams::TObjectStoreOpenCursorParams:
10398 MOZ_ASSERT(!indexMetadata);
10399 return MakeAndAddRef<Cursor<IDBCursorType::ObjectStore>>(
10400 SafeRefPtrFromThis(), std::move(objectStoreMetadata), direction,
10401 CursorBase::ConstructFromTransactionBase{});
10402 case OpenCursorParams::TObjectStoreOpenKeyCursorParams:
10403 MOZ_ASSERT(!indexMetadata);
10404 return MakeAndAddRef<Cursor<IDBCursorType::ObjectStoreKey>>(
10405 SafeRefPtrFromThis(), std::move(objectStoreMetadata), direction,
10406 CursorBase::ConstructFromTransactionBase{});
10407 case OpenCursorParams::TIndexOpenCursorParams:
10408 return MakeAndAddRef<Cursor<IDBCursorType::Index>>(
10409 SafeRefPtrFromThis(), std::move(objectStoreMetadata),
10410 std::move(indexMetadata), direction,
10411 CursorBase::ConstructFromTransactionBase{});
10412 case OpenCursorParams::TIndexOpenKeyCursorParams:
10413 return MakeAndAddRef<Cursor<IDBCursorType::IndexKey>>(
10414 SafeRefPtrFromThis(), std::move(objectStoreMetadata),
10415 std::move(indexMetadata), direction,
10416 CursorBase::ConstructFromTransactionBase{});
10417 default:
10418 MOZ_CRASH("Cannot get here.");
10422 bool TransactionBase::StartCursor(PBackgroundIDBCursorParent* const aActor,
10423 const int64_t aRequestId,
10424 const OpenCursorParams& aParams) {
10425 AssertIsOnBackgroundThread();
10426 MOZ_ASSERT(aActor);
10427 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
10429 auto* const op = static_cast<CursorBase*>(aActor);
10431 if (NS_WARN_IF(!op->Start(aRequestId, aParams))) {
10432 return false;
10435 return true;
10438 /*******************************************************************************
10439 * NormalTransaction
10440 ******************************************************************************/
10442 NormalTransaction::NormalTransaction(
10443 SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode,
10444 TransactionBase::Durability aDurability,
10445 nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores)
10446 : TransactionBase(std::move(aDatabase), aMode, aDurability),
10447 mObjectStores{std::move(aObjectStores)} {
10448 AssertIsOnBackgroundThread();
10449 MOZ_ASSERT(!mObjectStores.IsEmpty());
10452 bool NormalTransaction::IsSameProcessActor() {
10453 AssertIsOnBackgroundThread();
10455 PBackgroundParent* const actor = Manager()->Manager()->Manager();
10456 MOZ_ASSERT(actor);
10458 return !BackgroundParent::IsOtherProcessActor(actor);
10461 void NormalTransaction::SendCompleteNotification(nsresult aResult) {
10462 AssertIsOnBackgroundThread();
10464 if (!IsActorDestroyed()) {
10465 Unused << SendComplete(aResult);
10469 void NormalTransaction::ActorDestroy(ActorDestroyReason aWhy) {
10470 AssertIsOnBackgroundThread();
10472 NoteActorDestroyed();
10474 if (!mCommittedOrAborted) {
10475 if (NS_SUCCEEDED(mResultCode)) {
10476 IDB_REPORT_INTERNAL_ERR();
10477 mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
10480 mForceAborted.EnsureFlipped();
10482 MaybeCommitOrAbort();
10486 mozilla::ipc::IPCResult NormalTransaction::RecvDeleteMe() {
10487 AssertIsOnBackgroundThread();
10488 MOZ_ASSERT(!IsActorDestroyed());
10490 QM_WARNONLY_TRY(OkIf(PBackgroundIDBTransactionParent::Send__delete__(this)));
10492 return IPC_OK();
10495 mozilla::ipc::IPCResult NormalTransaction::RecvCommit(
10496 const Maybe<int64_t>& aLastRequest) {
10497 AssertIsOnBackgroundThread();
10499 return TransactionBase::RecvCommit(this, aLastRequest);
10502 mozilla::ipc::IPCResult NormalTransaction::RecvAbort(
10503 const nsresult& aResultCode) {
10504 AssertIsOnBackgroundThread();
10506 return TransactionBase::RecvAbort(this, aResultCode);
10509 PBackgroundIDBRequestParent*
10510 NormalTransaction::AllocPBackgroundIDBRequestParent(
10511 const int64_t& aRequestId, const RequestParams& aParams) {
10512 AssertIsOnBackgroundThread();
10513 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
10515 return AllocRequest(aRequestId,
10516 std::move(const_cast<RequestParams&>(aParams)),
10517 IsSameProcessActor());
10520 mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBRequestConstructor(
10521 PBackgroundIDBRequestParent* const aActor, const int64_t& aRequestId,
10522 const RequestParams& aParams) {
10523 AssertIsOnBackgroundThread();
10524 MOZ_ASSERT(aActor);
10525 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
10527 if (!StartRequest(aActor)) {
10528 return IPC_FAIL(this, "StartRequest failed!");
10530 return IPC_OK();
10533 bool NormalTransaction::DeallocPBackgroundIDBRequestParent(
10534 PBackgroundIDBRequestParent* const aActor) {
10535 AssertIsOnBackgroundThread();
10536 MOZ_ASSERT(aActor);
10538 return DeallocRequest(aActor);
10541 already_AddRefed<PBackgroundIDBCursorParent>
10542 NormalTransaction::AllocPBackgroundIDBCursorParent(
10543 const int64_t& aRequestId, const OpenCursorParams& aParams) {
10544 AssertIsOnBackgroundThread();
10546 return AllocCursor(aParams, IsSameProcessActor());
10549 mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBCursorConstructor(
10550 PBackgroundIDBCursorParent* const aActor, const int64_t& aRequestId,
10551 const OpenCursorParams& aParams) {
10552 AssertIsOnBackgroundThread();
10553 MOZ_ASSERT(aActor);
10554 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
10556 if (!StartCursor(aActor, aRequestId, aParams)) {
10557 return IPC_FAIL(this, "StartCursor failed!");
10559 return IPC_OK();
10562 /*******************************************************************************
10563 * VersionChangeTransaction
10564 ******************************************************************************/
10566 VersionChangeTransaction::VersionChangeTransaction(
10567 OpenDatabaseOp* aOpenDatabaseOp)
10568 : TransactionBase(aOpenDatabaseOp->mDatabase.clonePtr(),
10569 IDBTransaction::Mode::VersionChange,
10570 // VersionChange must not change durability.
10571 IDBTransaction::Durability::Default), // Not used.
10572 mOpenDatabaseOp(aOpenDatabaseOp) {
10573 AssertIsOnBackgroundThread();
10574 MOZ_ASSERT(aOpenDatabaseOp);
10577 VersionChangeTransaction::~VersionChangeTransaction() {
10578 #ifdef DEBUG
10579 // Silence the base class' destructor assertion if we never made this actor
10580 // live.
10581 FakeActorDestroyed();
10582 #endif
10585 bool VersionChangeTransaction::IsSameProcessActor() {
10586 AssertIsOnBackgroundThread();
10588 PBackgroundParent* actor = Manager()->Manager()->Manager();
10589 MOZ_ASSERT(actor);
10591 return !BackgroundParent::IsOtherProcessActor(actor);
10594 void VersionChangeTransaction::SetActorAlive() {
10595 AssertIsOnBackgroundThread();
10596 MOZ_ASSERT(!IsActorDestroyed());
10598 mActorWasAlive.Flip();
10601 bool VersionChangeTransaction::CopyDatabaseMetadata() {
10602 AssertIsOnBackgroundThread();
10603 MOZ_ASSERT(!mOldMetadata);
10605 const auto& origMetadata = GetDatabase().Metadata();
10607 SafeRefPtr<FullDatabaseMetadata> newMetadata = origMetadata.Duplicate();
10608 if (NS_WARN_IF(!newMetadata)) {
10609 return false;
10612 // Replace the live metadata with the new mutable copy.
10613 DatabaseActorInfo* info;
10614 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(origMetadata.mDatabaseId, &info));
10615 MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
10616 MOZ_ASSERT(info->mMetadata == &origMetadata);
10618 mOldMetadata = std::move(info->mMetadata);
10619 info->mMetadata = std::move(newMetadata);
10621 // Replace metadata pointers for all live databases.
10622 for (const auto& liveDatabase : info->mLiveDatabases) {
10623 liveDatabase->mMetadata = info->mMetadata.clonePtr();
10626 return true;
10629 void VersionChangeTransaction::UpdateMetadata(nsresult aResult) {
10630 AssertIsOnBackgroundThread();
10631 MOZ_ASSERT(mOpenDatabaseOp);
10632 MOZ_ASSERT(!!mActorWasAlive == !!mOpenDatabaseOp->mDatabase);
10633 MOZ_ASSERT_IF(mActorWasAlive, !mOpenDatabaseOp->mDatabaseId.ref().IsEmpty());
10635 if (IsActorDestroyed() || !mActorWasAlive) {
10636 return;
10639 SafeRefPtr<FullDatabaseMetadata> oldMetadata = std::move(mOldMetadata);
10641 DatabaseActorInfo* info;
10642 if (!gLiveDatabaseHashtable->Get(oldMetadata->mDatabaseId, &info)) {
10643 return;
10646 MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
10648 if (NS_SUCCEEDED(aResult)) {
10649 // Remove all deleted objectStores and indexes, then mark immutable.
10650 info->mMetadata->mObjectStores.RemoveIf([](const auto& objectStoreIter) {
10651 MOZ_ASSERT(objectStoreIter.Key());
10652 const SafeRefPtr<FullObjectStoreMetadata>& metadata =
10653 objectStoreIter.Data();
10654 MOZ_ASSERT(metadata);
10656 if (metadata->mDeleted) {
10657 return true;
10660 metadata->mIndexes.RemoveIf([](const auto& indexIter) -> bool {
10661 MOZ_ASSERT(indexIter.Key());
10662 const SafeRefPtr<FullIndexMetadata>& index = indexIter.Data();
10663 MOZ_ASSERT(index);
10665 return index->mDeleted;
10667 metadata->mIndexes.MarkImmutable();
10669 return false;
10672 info->mMetadata->mObjectStores.MarkImmutable();
10673 } else {
10674 // Replace metadata pointers for all live databases.
10675 info->mMetadata = std::move(oldMetadata);
10677 for (auto& liveDatabase : info->mLiveDatabases) {
10678 liveDatabase->mMetadata = info->mMetadata.clonePtr();
10683 void VersionChangeTransaction::SendCompleteNotification(nsresult aResult) {
10684 AssertIsOnBackgroundThread();
10685 MOZ_ASSERT(mOpenDatabaseOp);
10686 MOZ_ASSERT_IF(!mActorWasAlive, mOpenDatabaseOp->HasFailed());
10687 MOZ_ASSERT_IF(!mActorWasAlive, mOpenDatabaseOp->mState >
10688 OpenDatabaseOp::State::SendingResults);
10690 const RefPtr<OpenDatabaseOp> openDatabaseOp = std::move(mOpenDatabaseOp);
10692 if (!mActorWasAlive) {
10693 return;
10696 if (NS_FAILED(aResult)) {
10697 // 3.3.1 Opening a database:
10698 // "If the upgrade transaction was aborted, run the steps for closing a
10699 // database connection with connection, create and return a new AbortError
10700 // exception and abort these steps."
10701 openDatabaseOp->SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
10704 openDatabaseOp->mState = OpenDatabaseOp::State::SendingResults;
10706 if (!IsActorDestroyed()) {
10707 Unused << SendComplete(aResult);
10710 MOZ_ALWAYS_SUCCEEDS(openDatabaseOp->Run());
10713 void VersionChangeTransaction::ActorDestroy(ActorDestroyReason aWhy) {
10714 AssertIsOnBackgroundThread();
10716 NoteActorDestroyed();
10718 if (!mCommittedOrAborted) {
10719 if (NS_SUCCEEDED(mResultCode)) {
10720 IDB_REPORT_INTERNAL_ERR();
10721 mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
10724 mForceAborted.EnsureFlipped();
10726 MaybeCommitOrAbort();
10730 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteMe() {
10731 AssertIsOnBackgroundThread();
10732 MOZ_ASSERT(!IsActorDestroyed());
10734 QM_WARNONLY_TRY(
10735 OkIf(PBackgroundIDBVersionChangeTransactionParent::Send__delete__(this)));
10737 return IPC_OK();
10740 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCommit(
10741 const Maybe<int64_t>& aLastRequest) {
10742 AssertIsOnBackgroundThread();
10744 return TransactionBase::RecvCommit(this, aLastRequest);
10747 mozilla::ipc::IPCResult VersionChangeTransaction::RecvAbort(
10748 const nsresult& aResultCode) {
10749 AssertIsOnBackgroundThread();
10751 return TransactionBase::RecvAbort(this, aResultCode);
10754 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCreateObjectStore(
10755 const ObjectStoreMetadata& aMetadata) {
10756 AssertIsOnBackgroundThread();
10758 if (NS_WARN_IF(!aMetadata.id())) {
10759 return IPC_FAIL(this, "No metadata ID!");
10762 const SafeRefPtr<FullDatabaseMetadata> dbMetadata =
10763 GetDatabase().MetadataPtr();
10765 if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextObjectStoreId)) {
10766 return IPC_FAIL(this, "Requested metadata ID does not match next ID!");
10769 if (NS_WARN_IF(
10770 MatchMetadataNameOrId(dbMetadata->mObjectStores, aMetadata.id(),
10771 SomeRef<const nsAString&>(aMetadata.name()))
10772 .isSome())) {
10773 return IPC_FAIL(this, "MatchMetadataNameOrId failed!");
10776 if (NS_WARN_IF(mCommitOrAbortReceived)) {
10777 return IPC_FAIL(this, "Transaction is already committed/aborted!");
10780 const int64_t initialAutoIncrementId = aMetadata.autoIncrement() ? 1 : 0;
10781 auto newMetadata = MakeSafeRefPtr<FullObjectStoreMetadata>(
10782 aMetadata, FullObjectStoreMetadata::AutoIncrementIds{
10783 initialAutoIncrementId, initialAutoIncrementId});
10785 if (NS_WARN_IF(!dbMetadata->mObjectStores.InsertOrUpdate(
10786 aMetadata.id(), std::move(newMetadata), fallible))) {
10787 return IPC_FAIL(this, "mObjectStores.InsertOrUpdate failed!");
10790 dbMetadata->mNextObjectStoreId++;
10792 RefPtr<CreateObjectStoreOp> op = new CreateObjectStoreOp(
10793 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aMetadata);
10795 if (NS_WARN_IF(!op->Init(*this))) {
10796 op->Cleanup();
10797 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
10800 op->DispatchToConnectionPool();
10802 return IPC_OK();
10805 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteObjectStore(
10806 const IndexOrObjectStoreId& aObjectStoreId) {
10807 AssertIsOnBackgroundThread();
10809 if (NS_WARN_IF(!aObjectStoreId)) {
10810 return IPC_FAIL(this, "No ObjectStoreId!");
10813 const auto& dbMetadata = GetDatabase().Metadata();
10814 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0);
10816 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) {
10817 return IPC_FAIL(this, "Invalid ObjectStoreId!");
10820 SafeRefPtr<FullObjectStoreMetadata> foundMetadata =
10821 GetMetadataForObjectStoreId(aObjectStoreId);
10823 if (NS_WARN_IF(!foundMetadata)) {
10824 return IPC_FAIL(this, "No metadata found for ObjectStoreId!");
10827 if (NS_WARN_IF(mCommitOrAbortReceived)) {
10828 return IPC_FAIL(this, "Transaction is already committed/aborted!");
10831 foundMetadata->mDeleted.Flip();
10833 DebugOnly<bool> foundTargetId = false;
10834 const bool isLastObjectStore = std::all_of(
10835 dbMetadata.mObjectStores.begin(), dbMetadata.mObjectStores.end(),
10836 [&foundTargetId, aObjectStoreId](const auto& objectStoreEntry) -> bool {
10837 if (uint64_t(aObjectStoreId) == objectStoreEntry.GetKey()) {
10838 foundTargetId = true;
10839 return true;
10842 return objectStoreEntry.GetData()->mDeleted;
10844 MOZ_ASSERT_IF(isLastObjectStore, foundTargetId);
10846 RefPtr<DeleteObjectStoreOp> op = new DeleteObjectStoreOp(
10847 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(),
10848 std::move(foundMetadata), isLastObjectStore);
10850 if (NS_WARN_IF(!op->Init(*this))) {
10851 op->Cleanup();
10852 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
10855 op->DispatchToConnectionPool();
10857 return IPC_OK();
10860 mozilla::ipc::IPCResult VersionChangeTransaction::RecvRenameObjectStore(
10861 const IndexOrObjectStoreId& aObjectStoreId, const nsAString& aName) {
10862 AssertIsOnBackgroundThread();
10864 if (NS_WARN_IF(!aObjectStoreId)) {
10865 return IPC_FAIL(this, "No ObjectStoreId!");
10869 const auto& dbMetadata = GetDatabase().Metadata();
10870 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0);
10872 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) {
10873 return IPC_FAIL(this, "Invalid ObjectStoreId!");
10877 SafeRefPtr<FullObjectStoreMetadata> foundMetadata =
10878 GetMetadataForObjectStoreId(aObjectStoreId);
10880 if (NS_WARN_IF(!foundMetadata)) {
10881 return IPC_FAIL(this, "No metadata found for ObjectStoreId!");
10884 if (NS_WARN_IF(mCommitOrAbortReceived)) {
10885 return IPC_FAIL(this, "Transaction is already committed/aborted!");
10888 foundMetadata->mCommonMetadata.name() = aName;
10890 RefPtr<RenameObjectStoreOp> renameOp = new RenameObjectStoreOp(
10891 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(),
10892 *foundMetadata);
10894 if (NS_WARN_IF(!renameOp->Init(*this))) {
10895 renameOp->Cleanup();
10896 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
10899 renameOp->DispatchToConnectionPool();
10901 return IPC_OK();
10904 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCreateIndex(
10905 const IndexOrObjectStoreId& aObjectStoreId,
10906 const IndexMetadata& aMetadata) {
10907 AssertIsOnBackgroundThread();
10909 if (NS_WARN_IF(!aObjectStoreId)) {
10910 return IPC_FAIL(this, "No ObjectStoreId!");
10913 if (NS_WARN_IF(!aMetadata.id())) {
10914 return IPC_FAIL(this, "No Metadata id!");
10917 const auto dbMetadata = GetDatabase().MetadataPtr();
10919 if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextIndexId)) {
10920 return IPC_FAIL(this, "Requested metadata ID does not match next ID!");
10923 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
10924 GetMetadataForObjectStoreId(aObjectStoreId);
10926 if (NS_WARN_IF(!foundObjectStoreMetadata)) {
10927 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!");
10930 if (NS_WARN_IF(MatchMetadataNameOrId(
10931 foundObjectStoreMetadata->mIndexes, aMetadata.id(),
10932 SomeRef<const nsAString&>(aMetadata.name()))
10933 .isSome())) {
10934 return IPC_FAIL(this, "MatchMetadataNameOrId failed!");
10937 if (NS_WARN_IF(mCommitOrAbortReceived)) {
10938 return IPC_FAIL(this, "Transaction is already committed/aborted!");
10941 auto newMetadata = MakeSafeRefPtr<FullIndexMetadata>();
10942 newMetadata->mCommonMetadata = aMetadata;
10944 if (NS_WARN_IF(!foundObjectStoreMetadata->mIndexes.InsertOrUpdate(
10945 aMetadata.id(), std::move(newMetadata), fallible))) {
10946 return IPC_FAIL(this, "mIndexes.InsertOrUpdate failed!");
10949 dbMetadata->mNextIndexId++;
10951 RefPtr<CreateIndexOp> op = new CreateIndexOp(
10952 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aObjectStoreId,
10953 aMetadata);
10955 if (NS_WARN_IF(!op->Init(*this))) {
10956 op->Cleanup();
10957 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
10960 op->DispatchToConnectionPool();
10962 return IPC_OK();
10965 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteIndex(
10966 const IndexOrObjectStoreId& aObjectStoreId,
10967 const IndexOrObjectStoreId& aIndexId) {
10968 AssertIsOnBackgroundThread();
10970 if (NS_WARN_IF(!aObjectStoreId)) {
10971 return IPC_FAIL(this, "No ObjectStoreId!");
10974 if (NS_WARN_IF(!aIndexId)) {
10975 return IPC_FAIL(this, "No Index id!");
10978 const auto& dbMetadata = GetDatabase().Metadata();
10979 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0);
10980 MOZ_ASSERT(dbMetadata.mNextIndexId > 0);
10982 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) {
10983 return IPC_FAIL(this, "Requested ObjectStoreId does not match next ID!");
10986 if (NS_WARN_IF(aIndexId >= dbMetadata.mNextIndexId)) {
10987 return IPC_FAIL(this, "Requested IndexId does not match next ID!");
10991 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
10992 GetMetadataForObjectStoreId(aObjectStoreId);
10994 if (NS_WARN_IF(!foundObjectStoreMetadata)) {
10995 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!");
10998 SafeRefPtr<FullIndexMetadata> foundIndexMetadata =
10999 GetMetadataForIndexId(*foundObjectStoreMetadata, aIndexId);
11001 if (NS_WARN_IF(!foundIndexMetadata)) {
11002 return IPC_FAIL(this, "GetMetadataForIndexId failed!");
11005 if (NS_WARN_IF(mCommitOrAbortReceived)) {
11006 return IPC_FAIL(this, "Transaction is already committed/aborted!");
11009 foundIndexMetadata->mDeleted.Flip();
11011 DebugOnly<bool> foundTargetId = false;
11012 const bool isLastIndex =
11013 std::all_of(foundObjectStoreMetadata->mIndexes.cbegin(),
11014 foundObjectStoreMetadata->mIndexes.cend(),
11015 [&foundTargetId, aIndexId](const auto& indexEntry) -> bool {
11016 if (uint64_t(aIndexId) == indexEntry.GetKey()) {
11017 foundTargetId = true;
11018 return true;
11021 return indexEntry.GetData()->mDeleted;
11023 MOZ_ASSERT_IF(isLastIndex, foundTargetId);
11025 RefPtr<DeleteIndexOp> op = new DeleteIndexOp(
11026 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aObjectStoreId,
11027 aIndexId, foundIndexMetadata->mCommonMetadata.unique(), isLastIndex);
11029 if (NS_WARN_IF(!op->Init(*this))) {
11030 op->Cleanup();
11031 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
11034 op->DispatchToConnectionPool();
11036 return IPC_OK();
11039 mozilla::ipc::IPCResult VersionChangeTransaction::RecvRenameIndex(
11040 const IndexOrObjectStoreId& aObjectStoreId,
11041 const IndexOrObjectStoreId& aIndexId, const nsAString& aName) {
11042 AssertIsOnBackgroundThread();
11044 if (NS_WARN_IF(!aObjectStoreId)) {
11045 return IPC_FAIL(this, "No ObjectStoreId!");
11048 if (NS_WARN_IF(!aIndexId)) {
11049 return IPC_FAIL(this, "No Index id!");
11052 const SafeRefPtr<FullDatabaseMetadata> dbMetadata =
11053 GetDatabase().MetadataPtr();
11054 MOZ_ASSERT(dbMetadata);
11055 MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
11056 MOZ_ASSERT(dbMetadata->mNextIndexId > 0);
11058 if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
11059 return IPC_FAIL(this, "Requested ObjectStoreId does not match next ID!");
11062 if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) {
11063 return IPC_FAIL(this, "Requested IndexId does not match next ID!");
11066 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
11067 GetMetadataForObjectStoreId(aObjectStoreId);
11069 if (NS_WARN_IF(!foundObjectStoreMetadata)) {
11070 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!");
11073 SafeRefPtr<FullIndexMetadata> foundIndexMetadata =
11074 GetMetadataForIndexId(*foundObjectStoreMetadata, aIndexId);
11076 if (NS_WARN_IF(!foundIndexMetadata)) {
11077 return IPC_FAIL(this, "GetMetadataForIndexId failed!");
11080 if (NS_WARN_IF(mCommitOrAbortReceived)) {
11081 return IPC_FAIL(this, "Transaction is already committed/aborted!");
11084 foundIndexMetadata->mCommonMetadata.name() = aName;
11086 RefPtr<RenameIndexOp> renameOp = new RenameIndexOp(
11087 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(),
11088 *foundIndexMetadata, aObjectStoreId);
11090 if (NS_WARN_IF(!renameOp->Init(*this))) {
11091 renameOp->Cleanup();
11092 return IPC_FAIL(this, "ObjectStoreOp initialization failed!");
11095 renameOp->DispatchToConnectionPool();
11097 return IPC_OK();
11100 PBackgroundIDBRequestParent*
11101 VersionChangeTransaction::AllocPBackgroundIDBRequestParent(
11102 const int64_t& aRequestId, const RequestParams& aParams) {
11103 AssertIsOnBackgroundThread();
11104 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
11106 return AllocRequest(aRequestId,
11107 std::move(const_cast<RequestParams&>(aParams)),
11108 IsSameProcessActor());
11111 mozilla::ipc::IPCResult
11112 VersionChangeTransaction::RecvPBackgroundIDBRequestConstructor(
11113 PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId,
11114 const RequestParams& aParams) {
11115 AssertIsOnBackgroundThread();
11116 MOZ_ASSERT(aActor);
11117 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
11119 if (!StartRequest(aActor)) {
11120 return IPC_FAIL(this, "StartRequest failed!");
11122 return IPC_OK();
11125 bool VersionChangeTransaction::DeallocPBackgroundIDBRequestParent(
11126 PBackgroundIDBRequestParent* aActor) {
11127 AssertIsOnBackgroundThread();
11128 MOZ_ASSERT(aActor);
11130 return DeallocRequest(aActor);
11133 already_AddRefed<PBackgroundIDBCursorParent>
11134 VersionChangeTransaction::AllocPBackgroundIDBCursorParent(
11135 const int64_t& aRequestId, const OpenCursorParams& aParams) {
11136 AssertIsOnBackgroundThread();
11138 return AllocCursor(aParams, IsSameProcessActor());
11141 mozilla::ipc::IPCResult
11142 VersionChangeTransaction::RecvPBackgroundIDBCursorConstructor(
11143 PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId,
11144 const OpenCursorParams& aParams) {
11145 AssertIsOnBackgroundThread();
11146 MOZ_ASSERT(aActor);
11147 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
11149 if (!StartCursor(aActor, aRequestId, aParams)) {
11150 return IPC_FAIL(this, "StartCursor failed!");
11152 return IPC_OK();
11155 /*******************************************************************************
11156 * CursorBase
11157 ******************************************************************************/
11159 CursorBase::CursorBase(SafeRefPtr<TransactionBase> aTransaction,
11160 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata,
11161 const Direction aDirection,
11162 const ConstructFromTransactionBase /*aConstructionTag*/)
11163 : mTransaction(std::move(aTransaction)),
11164 mObjectStoreMetadata(WrapNotNull(std::move(aObjectStoreMetadata))),
11165 mObjectStoreId((*mObjectStoreMetadata)->mCommonMetadata.id()),
11166 mDirection(aDirection),
11167 mMaxExtraCount(IndexedDatabaseManager::MaxPreloadExtraRecords()),
11168 mIsSameProcessActor(!BackgroundParent::IsOtherProcessActor(
11169 mTransaction->GetBackgroundParent())) {
11170 AssertIsOnBackgroundThread();
11171 MOZ_ASSERT(mTransaction);
11173 static_assert(
11174 OpenCursorParams::T__None == 0 && OpenCursorParams::T__Last == 4,
11175 "Lots of code here assumes only four types of cursors!");
11178 template <IDBCursorType CursorType>
11179 bool Cursor<CursorType>::VerifyRequestParams(
11180 const CursorRequestParams& aParams,
11181 const CursorPosition<CursorType>& aPosition) const {
11182 AssertIsOnBackgroundThread();
11183 MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
11184 MOZ_ASSERT(this->mObjectStoreMetadata);
11185 if constexpr (IsIndexCursor) {
11186 MOZ_ASSERT(this->mIndexMetadata);
11189 #ifdef DEBUG
11191 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
11192 mTransaction->GetMetadataForObjectStoreId(mObjectStoreId);
11193 if (objectStoreMetadata) {
11194 MOZ_ASSERT(objectStoreMetadata == (*this->mObjectStoreMetadata));
11195 } else {
11196 MOZ_ASSERT((*this->mObjectStoreMetadata)->mDeleted);
11199 if constexpr (IsIndexCursor) {
11200 if (objectStoreMetadata) {
11201 const SafeRefPtr<FullIndexMetadata> indexMetadata =
11202 mTransaction->GetMetadataForIndexId(*objectStoreMetadata,
11203 this->mIndexId);
11204 if (indexMetadata) {
11205 MOZ_ASSERT(indexMetadata == *this->mIndexMetadata);
11206 } else {
11207 MOZ_ASSERT((*this->mIndexMetadata)->mDeleted);
11212 #endif
11214 if (NS_AUUF_OR_WARN_IF((*this->mObjectStoreMetadata)->mDeleted)) {
11215 return false;
11218 if constexpr (IsIndexCursor) {
11219 if (NS_AUUF_OR_WARN_IF(this->mIndexMetadata &&
11220 (*this->mIndexMetadata)->mDeleted)) {
11221 return false;
11225 const Key& sortKey = aPosition.GetSortKey(this->IsLocaleAware());
11227 switch (aParams.type()) {
11228 case CursorRequestParams::TContinueParams: {
11229 const Key& key = aParams.get_ContinueParams().key();
11230 if (!key.IsUnset()) {
11231 switch (mDirection) {
11232 case IDBCursorDirection::Next:
11233 case IDBCursorDirection::Nextunique:
11234 if (NS_AUUF_OR_WARN_IF(key <= sortKey)) {
11235 return false;
11237 break;
11239 case IDBCursorDirection::Prev:
11240 case IDBCursorDirection::Prevunique:
11241 if (NS_AUUF_OR_WARN_IF(key >= sortKey)) {
11242 return false;
11244 break;
11246 default:
11247 MOZ_CRASH("Should never get here!");
11250 break;
11253 case CursorRequestParams::TContinuePrimaryKeyParams: {
11254 if constexpr (IsIndexCursor) {
11255 const Key& key = aParams.get_ContinuePrimaryKeyParams().key();
11256 const Key& primaryKey =
11257 aParams.get_ContinuePrimaryKeyParams().primaryKey();
11258 MOZ_ASSERT(!key.IsUnset());
11259 MOZ_ASSERT(!primaryKey.IsUnset());
11260 switch (mDirection) {
11261 case IDBCursorDirection::Next:
11262 if (NS_AUUF_OR_WARN_IF(key < sortKey ||
11263 (key == sortKey &&
11264 primaryKey <= aPosition.mObjectStoreKey))) {
11265 return false;
11267 break;
11269 case IDBCursorDirection::Prev:
11270 if (NS_AUUF_OR_WARN_IF(key > sortKey ||
11271 (key == sortKey &&
11272 primaryKey >= aPosition.mObjectStoreKey))) {
11273 return false;
11275 break;
11277 default:
11278 MOZ_CRASH("Should never get here!");
11281 break;
11284 case CursorRequestParams::TAdvanceParams:
11285 if (NS_AUUF_OR_WARN_IF(!aParams.get_AdvanceParams().count())) {
11286 return false;
11288 break;
11290 default:
11291 MOZ_CRASH("Should never get here!");
11294 return true;
11297 template <IDBCursorType CursorType>
11298 bool Cursor<CursorType>::Start(const int64_t aRequestId,
11299 const OpenCursorParams& aParams) {
11300 AssertIsOnBackgroundThread();
11301 MOZ_ASSERT(aParams.type() == ToOpenCursorParamsType(CursorType));
11302 MOZ_ASSERT(this->mObjectStoreMetadata);
11304 if (NS_AUUF_OR_WARN_IF(mCurrentlyRunningOp)) {
11305 return false;
11308 const Maybe<SerializedKeyRange>& optionalKeyRange =
11309 GetCommonOpenCursorParams(aParams).optionalKeyRange();
11311 const RefPtr<OpenOp> openOp = new OpenOp(this, aRequestId, optionalKeyRange);
11313 if (NS_WARN_IF(!openOp->Init(*mTransaction))) {
11314 openOp->Cleanup();
11315 return false;
11318 openOp->DispatchToConnectionPool();
11319 mCurrentlyRunningOp = openOp;
11321 return true;
11324 void ValueCursorBase::ProcessFiles(CursorResponse& aResponse,
11325 const FilesArray& aFiles) {
11326 MOZ_ASSERT_IF(
11327 aResponse.type() == CursorResponse::Tnsresult ||
11328 aResponse.type() == CursorResponse::Tvoid_t ||
11329 aResponse.type() ==
11330 CursorResponse::TArrayOfObjectStoreKeyCursorResponse ||
11331 aResponse.type() == CursorResponse::TArrayOfIndexKeyCursorResponse,
11332 aFiles.IsEmpty());
11334 for (size_t i = 0; i < aFiles.Length(); ++i) {
11335 const auto& files = aFiles[i];
11336 if (!files.IsEmpty()) {
11337 // TODO: Replace this assertion by one that checks if the response type
11338 // matches the cursor type, at a more generic location.
11339 MOZ_ASSERT(aResponse.type() ==
11340 CursorResponse::TArrayOfObjectStoreCursorResponse ||
11341 aResponse.type() ==
11342 CursorResponse::TArrayOfIndexCursorResponse);
11344 SerializedStructuredCloneReadInfo* serializedInfo = nullptr;
11345 switch (aResponse.type()) {
11346 case CursorResponse::TArrayOfObjectStoreCursorResponse: {
11347 auto& responses = aResponse.get_ArrayOfObjectStoreCursorResponse();
11348 MOZ_ASSERT(i < responses.Length());
11349 serializedInfo = &responses[i].cloneInfo();
11350 break;
11353 case CursorResponse::TArrayOfIndexCursorResponse: {
11354 auto& responses = aResponse.get_ArrayOfIndexCursorResponse();
11355 MOZ_ASSERT(i < responses.Length());
11356 serializedInfo = &responses[i].cloneInfo();
11357 break;
11360 default:
11361 MOZ_CRASH("Should never get here!");
11364 MOZ_ASSERT(serializedInfo);
11365 MOZ_ASSERT(serializedInfo->files().IsEmpty());
11366 MOZ_ASSERT(this->mDatabase);
11368 QM_TRY_UNWRAP(serializedInfo->files(),
11369 SerializeStructuredCloneFiles(this->mDatabase, files,
11370 /* aForPreprocess */ false),
11371 QM_VOID, [&aResponse](const nsresult result) {
11372 aResponse = ClampResultCode(result);
11378 template <IDBCursorType CursorType>
11379 void Cursor<CursorType>::SendResponseInternal(
11380 CursorResponse& aResponse, const FilesArrayT<CursorType>& aFiles) {
11381 AssertIsOnBackgroundThread();
11382 MOZ_ASSERT(aResponse.type() != CursorResponse::T__None);
11383 MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult,
11384 NS_FAILED(aResponse.get_nsresult()));
11385 MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult,
11386 NS_ERROR_GET_MODULE(aResponse.get_nsresult()) ==
11387 NS_ERROR_MODULE_DOM_INDEXEDDB);
11388 MOZ_ASSERT(this->mObjectStoreMetadata);
11389 MOZ_ASSERT(mCurrentlyRunningOp);
11391 KeyValueBase::ProcessFiles(aResponse, aFiles);
11393 // Work around the deleted function by casting to the base class.
11394 QM_WARNONLY_TRY(OkIf(
11395 static_cast<PBackgroundIDBCursorParent*>(this)->SendResponse(aResponse)));
11397 mCurrentlyRunningOp = nullptr;
11400 template <IDBCursorType CursorType>
11401 void Cursor<CursorType>::ActorDestroy(ActorDestroyReason aWhy) {
11402 AssertIsOnBackgroundThread();
11404 if (mCurrentlyRunningOp) {
11405 mCurrentlyRunningOp->NoteActorDestroyed();
11408 if constexpr (IsValueCursor) {
11409 this->mBackgroundParent.destroy();
11411 this->mObjectStoreMetadata.destroy();
11412 if constexpr (IsIndexCursor) {
11413 this->mIndexMetadata.destroy();
11417 template <IDBCursorType CursorType>
11418 mozilla::ipc::IPCResult Cursor<CursorType>::RecvDeleteMe() {
11419 AssertIsOnBackgroundThread();
11420 MOZ_ASSERT(this->mObjectStoreMetadata);
11422 if (NS_WARN_IF(mCurrentlyRunningOp)) {
11423 return IPC_FAIL(
11424 this,
11425 "Attempt to delete a cursor with a non-null mCurrentlyRunningOp!");
11428 QM_WARNONLY_TRY(OkIf(PBackgroundIDBCursorParent::Send__delete__(this)));
11430 return IPC_OK();
11433 template <IDBCursorType CursorType>
11434 mozilla::ipc::IPCResult Cursor<CursorType>::RecvContinue(
11435 const int64_t& aRequestId, const CursorRequestParams& aParams,
11436 const Key& aCurrentKey, const Key& aCurrentObjectStoreKey) {
11437 AssertIsOnBackgroundThread();
11438 MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
11439 MOZ_ASSERT(this->mObjectStoreMetadata);
11440 if constexpr (IsIndexCursor) {
11441 MOZ_ASSERT(this->mIndexMetadata);
11444 const bool trustParams =
11445 #ifdef DEBUG
11446 // Always verify parameters in DEBUG builds!
11447 false
11448 #else
11449 this->mIsSameProcessActor
11450 #endif
11453 MOZ_ASSERT(!aCurrentKey.IsUnset());
11455 QM_TRY_UNWRAP(
11456 auto position,
11457 ([&]() -> Result<CursorPosition<CursorType>, mozilla::ipc::IPCResult> {
11458 if constexpr (IsIndexCursor) {
11459 auto localeAwarePosition = Key{};
11460 if (this->IsLocaleAware()) {
11461 QM_TRY_UNWRAP(
11462 localeAwarePosition,
11463 aCurrentKey.ToLocaleAwareKey(this->mLocale),
11464 Err(IPC_FAIL(this, "aCurrentKey.ToLocaleAwareKey failed!")));
11466 return CursorPosition<CursorType>{aCurrentKey, localeAwarePosition,
11467 aCurrentObjectStoreKey};
11468 } else {
11469 return CursorPosition<CursorType>{aCurrentKey};
11471 }()));
11473 if (!trustParams && !VerifyRequestParams(aParams, position)) {
11474 return IPC_FAIL(this, "VerifyRequestParams failed!");
11477 if (NS_WARN_IF(mCurrentlyRunningOp)) {
11478 return IPC_FAIL(this, "Cursor is CurrentlyRunningOp!");
11481 if (NS_WARN_IF(mTransaction->mCommitOrAbortReceived)) {
11482 return IPC_FAIL(this, "Transaction is already committed/aborted!");
11485 const RefPtr<ContinueOp> continueOp =
11486 new ContinueOp(this, aRequestId, aParams, std::move(position));
11487 if (NS_WARN_IF(!continueOp->Init(*mTransaction))) {
11488 continueOp->Cleanup();
11489 return IPC_FAIL(this, "ContinueOp initialization failed!");
11492 continueOp->DispatchToConnectionPool();
11493 mCurrentlyRunningOp = continueOp;
11495 return IPC_OK();
11498 /*******************************************************************************
11499 * DatabaseFileManager
11500 ******************************************************************************/
11502 DatabaseFileManager::MutexType DatabaseFileManager::sMutex;
11504 DatabaseFileManager::DatabaseFileManager(
11505 PersistenceType aPersistenceType,
11506 const quota::OriginMetadata& aOriginMetadata,
11507 const nsAString& aDatabaseName, const nsCString& aDatabaseID,
11508 const nsAString& aDatabaseFilePath, bool aEnforcingQuota,
11509 bool aIsInPrivateBrowsingMode)
11510 : mPersistenceType(aPersistenceType),
11511 mOriginMetadata(aOriginMetadata),
11512 mDatabaseName(aDatabaseName),
11513 mDatabaseID(aDatabaseID),
11514 mDatabaseFilePath(aDatabaseFilePath),
11515 mCipherKeyManager(
11516 aIsInPrivateBrowsingMode
11517 ? new IndexedDBCipherKeyManager("IndexedDBCipherKeyManager")
11518 : nullptr),
11519 mDatabaseVersion(0),
11520 mEnforcingQuota(aEnforcingQuota),
11521 mIsInPrivateBrowsingMode(aIsInPrivateBrowsingMode) {}
11523 nsresult DatabaseFileManager::Init(nsIFile* aDirectory,
11524 mozIStorageConnection& aConnection) {
11525 AssertIsOnIOThread();
11526 MOZ_ASSERT(aDirectory);
11529 QM_TRY_INSPECT(const bool& existsAsDirectory,
11530 ExistsAsDirectory(*aDirectory));
11532 if (!existsAsDirectory) {
11533 QM_TRY(MOZ_TO_RESULT(aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)));
11536 QM_TRY_UNWRAP(auto path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11537 nsString, aDirectory, GetPath));
11539 mDirectoryPath.init(std::move(path));
11542 QM_TRY_INSPECT(const auto& journalDirectory,
11543 CloneFileAndAppend(*aDirectory, kJournalDirectoryName));
11545 // We don't care if it doesn't exist at all, but if it does exist, make sure
11546 // it's a directory.
11547 QM_TRY_INSPECT(const bool& existsAsDirectory,
11548 ExistsAsDirectory(*journalDirectory));
11549 Unused << existsAsDirectory;
11552 QM_TRY_UNWRAP(auto path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11553 nsString, journalDirectory, GetPath));
11555 mJournalDirectoryPath.init(std::move(path));
11558 QM_TRY_INSPECT(const auto& stmt,
11559 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11560 nsCOMPtr<mozIStorageStatement>, aConnection,
11561 CreateStatement, "SELECT id, refcount FROM file"_ns));
11563 QM_TRY(
11564 CollectWhileHasResult(*stmt, [this](auto& stmt) -> Result<Ok, nsresult> {
11565 QM_TRY_INSPECT(const int64_t& id,
11566 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
11567 QM_TRY_INSPECT(const int32_t& dbRefCnt,
11568 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 1));
11570 // We put a raw pointer into the hash table, so the memory refcount will
11571 // be 0, but the dbRefCnt is non-zero, which will keep the
11572 // DatabaseFileInfo object alive.
11573 MOZ_ASSERT(dbRefCnt > 0);
11574 mFileInfos.InsertOrUpdate(
11575 id, MakeNotNull<DatabaseFileInfo*>(
11576 FileInfoManagerGuard{}, SafeRefPtrFromThis(), id,
11577 static_cast<nsrefcnt>(dbRefCnt)));
11579 mLastFileId = std::max(id, mLastFileId);
11581 return Ok{};
11582 }));
11584 mInitialized.Flip();
11586 return NS_OK;
11589 nsCOMPtr<nsIFile> DatabaseFileManager::GetDirectory() {
11590 if (!this->AssertValid()) {
11591 return nullptr;
11594 return GetFileForPath(*mDirectoryPath);
11597 nsCOMPtr<nsIFile> DatabaseFileManager::GetCheckedDirectory() {
11598 auto directory = GetDirectory();
11599 if (NS_WARN_IF(!directory)) {
11600 return nullptr;
11603 DebugOnly<bool> exists;
11604 MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)));
11605 MOZ_ASSERT(exists);
11607 DebugOnly<bool> isDirectory;
11608 MOZ_ASSERT(NS_SUCCEEDED(directory->IsDirectory(&isDirectory)));
11609 MOZ_ASSERT(isDirectory);
11611 return directory;
11614 nsCOMPtr<nsIFile> DatabaseFileManager::GetJournalDirectory() {
11615 if (!this->AssertValid()) {
11616 return nullptr;
11619 return GetFileForPath(*mJournalDirectoryPath);
11622 nsCOMPtr<nsIFile> DatabaseFileManager::EnsureJournalDirectory() {
11623 // This can happen on the IO or on a transaction thread.
11624 MOZ_ASSERT(!NS_IsMainThread());
11626 auto journalDirectory = GetFileForPath(*mJournalDirectoryPath);
11627 QM_TRY(OkIf(journalDirectory), nullptr);
11629 QM_TRY_INSPECT(const bool& exists,
11630 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, Exists),
11631 nullptr);
11633 if (exists) {
11634 QM_TRY_INSPECT(const bool& isDirectory,
11635 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, IsDirectory),
11636 nullptr);
11638 QM_TRY(OkIf(isDirectory), nullptr);
11639 } else {
11640 QM_TRY(
11641 MOZ_TO_RESULT(journalDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
11642 nullptr);
11645 return journalDirectory;
11648 // static
11649 nsCOMPtr<nsIFile> DatabaseFileManager::GetFileForId(nsIFile* aDirectory,
11650 int64_t aId) {
11651 MOZ_ASSERT(aDirectory);
11652 MOZ_ASSERT(aId > 0);
11654 QM_TRY_RETURN(CloneFileAndAppend(*aDirectory, IntToString(aId)), nullptr);
11657 // static
11658 nsCOMPtr<nsIFile> DatabaseFileManager::GetCheckedFileForId(nsIFile* aDirectory,
11659 int64_t aId) {
11660 auto file = GetFileForId(aDirectory, aId);
11661 if (NS_WARN_IF(!file)) {
11662 return nullptr;
11665 DebugOnly<bool> exists;
11666 MOZ_ASSERT(NS_SUCCEEDED(file->Exists(&exists)));
11667 MOZ_ASSERT(exists);
11669 DebugOnly<bool> isFile;
11670 MOZ_ASSERT(NS_SUCCEEDED(file->IsFile(&isFile)));
11671 MOZ_ASSERT(isFile);
11673 return file;
11676 // static
11677 nsresult DatabaseFileManager::InitDirectory(nsIFile& aDirectory,
11678 nsIFile& aDatabaseFile,
11679 const nsACString& aOrigin,
11680 uint32_t aTelemetryId) {
11681 AssertIsOnIOThread();
11684 QM_TRY_INSPECT(const bool& exists,
11685 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
11687 if (!exists) {
11688 return NS_OK;
11691 QM_TRY_INSPECT(const bool& isDirectory,
11692 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
11693 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE);
11696 QM_TRY_INSPECT(const auto& journalDirectory,
11697 CloneFileAndAppend(aDirectory, kJournalDirectoryName));
11699 QM_TRY_INSPECT(const bool& exists,
11700 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, Exists));
11702 if (exists) {
11703 QM_TRY_INSPECT(const bool& isDirectory,
11704 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, IsDirectory));
11705 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE);
11707 bool hasJournals = false;
11709 QM_TRY(CollectEachFile(
11710 *journalDirectory,
11711 [&hasJournals](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
11712 QM_TRY_INSPECT(
11713 const auto& leafName,
11714 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName));
11716 nsresult rv;
11717 leafName.ToInteger64(&rv);
11718 if (NS_SUCCEEDED(rv)) {
11719 hasJournals = true;
11720 } else {
11721 UNKNOWN_FILE_WARNING(leafName);
11724 return Ok{};
11725 }));
11727 if (hasJournals) {
11728 QM_TRY_UNWRAP(const NotNull<nsCOMPtr<mozIStorageConnection>> connection,
11729 CreateStorageConnection(
11730 aDatabaseFile, aDirectory, VoidString(), aOrigin,
11731 /* aDirectoryLockId */ -1, aTelemetryId, Nothing{}));
11733 mozStorageTransaction transaction(connection.get(), false);
11735 QM_TRY(MOZ_TO_RESULT(transaction.Start()))
11737 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(
11738 "CREATE VIRTUAL TABLE fs USING filesystem;"_ns)));
11740 // The parameter names are not used, parameters are bound by index only
11741 // locally in the same function.
11742 QM_TRY_INSPECT(
11743 const auto& stmt,
11744 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11745 nsCOMPtr<mozIStorageStatement>, *connection, CreateStatement,
11746 "SELECT name, (name IN (SELECT id FROM file)) FROM fs WHERE path = :path"_ns));
11748 QM_TRY_INSPECT(const auto& path,
11749 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11750 nsString, journalDirectory, GetPath));
11752 QM_TRY(MOZ_TO_RESULT(stmt->BindStringByIndex(0, path)));
11754 QM_TRY(CollectWhileHasResult(
11755 *stmt,
11756 [&aDirectory, &journalDirectory](auto& stmt) -> Result<Ok, nsresult> {
11757 nsString name;
11758 QM_TRY(MOZ_TO_RESULT(stmt.GetString(0, name)));
11760 nsresult rv;
11761 name.ToInteger64(&rv);
11762 if (NS_FAILED(rv)) {
11763 return Ok{};
11766 int32_t flag = stmt.AsInt32(1);
11768 if (!flag) {
11769 QM_TRY_INSPECT(const auto& file,
11770 CloneFileAndAppend(aDirectory, name));
11772 if (NS_FAILED(file->Remove(false))) {
11773 NS_WARNING("Failed to remove orphaned file!");
11777 QM_TRY_INSPECT(const auto& journalFile,
11778 CloneFileAndAppend(*journalDirectory, name));
11780 if (NS_FAILED(journalFile->Remove(false))) {
11781 NS_WARNING("Failed to remove journal file!");
11784 return Ok{};
11785 }));
11787 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("DROP TABLE fs;"_ns)));
11788 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
11792 return NS_OK;
11795 // static
11796 Result<FileUsageType, nsresult> DatabaseFileManager::GetUsage(
11797 nsIFile* aDirectory) {
11798 AssertIsOnIOThread();
11799 MOZ_ASSERT(aDirectory);
11801 FileUsageType usage;
11803 QM_TRY(TraverseFiles(
11804 *aDirectory,
11805 // KnownDirEntryOp
11806 [&usage](nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
11807 if (isDirectory) {
11808 return Ok{};
11811 // Usually we only use QM_OR_ELSE_LOG_VERBOSE(_IF) with Remove and
11812 // NS_ERROR_FILE_NOT_FOUND check, but the file was found by a directory
11813 // traversal and ToInteger on the name succeeded, so it should be our
11814 // file and if the file disappears, the use of QM_OR_ELSE_WARN_IF is ok
11815 // here.
11816 QM_TRY_INSPECT(const auto& thisUsage,
11817 QM_OR_ELSE_WARN_IF(
11818 // Expression.
11819 MOZ_TO_RESULT_INVOKE_MEMBER(file, GetFileSize)
11820 .map([](const int64_t fileSize) {
11821 return FileUsageType(Some(uint64_t(fileSize)));
11823 // Predicate.
11824 ([](const nsresult rv) {
11825 return rv == NS_ERROR_FILE_NOT_FOUND;
11827 // Fallback. If the file does no longer exist, treat
11828 // it as 0-sized.
11829 ErrToDefaultOk<FileUsageType>));
11831 usage += thisUsage;
11833 return Ok{};
11835 // UnknownDirEntryOp
11836 [](nsIFile&, const bool) -> Result<Ok, nsresult> { return Ok{}; }));
11838 return usage;
11841 nsresult DatabaseFileManager::SyncDeleteFile(const int64_t aId) {
11842 MOZ_ASSERT(!mFileInfos.Contains(aId));
11844 if (!this->AssertValid()) {
11845 return NS_ERROR_UNEXPECTED;
11848 const auto directory = GetDirectory();
11849 QM_TRY(OkIf(directory), NS_ERROR_FAILURE);
11851 const auto journalDirectory = GetJournalDirectory();
11852 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE);
11854 const nsCOMPtr<nsIFile> file = GetFileForId(directory, aId);
11855 QM_TRY(OkIf(file), NS_ERROR_FAILURE);
11857 const nsCOMPtr<nsIFile> journalFile = GetFileForId(journalDirectory, aId);
11858 QM_TRY(OkIf(journalFile), NS_ERROR_FAILURE);
11860 return SyncDeleteFile(*file, *journalFile);
11863 nsresult DatabaseFileManager::SyncDeleteFile(nsIFile& aFile,
11864 nsIFile& aJournalFile) const {
11865 QuotaManager* const quotaManager =
11866 EnforcingQuota() ? QuotaManager::Get() : nullptr;
11867 MOZ_ASSERT_IF(EnforcingQuota(), quotaManager);
11869 QM_TRY(MOZ_TO_RESULT(DeleteFile(aFile, quotaManager, Type(), OriginMetadata(),
11870 Idempotency::No)));
11872 QM_TRY(MOZ_TO_RESULT(aJournalFile.Remove(false)));
11874 return NS_OK;
11877 nsresult DatabaseFileManager::Invalidate() {
11878 if (mCipherKeyManager) {
11879 mCipherKeyManager->Invalidate();
11882 QM_TRY(MOZ_TO_RESULT(FileInfoManager::Invalidate()));
11884 return NS_OK;
11887 /*******************************************************************************
11888 * QuotaClient
11889 ******************************************************************************/
11891 QuotaClient* QuotaClient::sInstance = nullptr;
11893 QuotaClient::QuotaClient() : mDeleteTimer(NS_NewTimer()) {
11894 AssertIsOnBackgroundThread();
11895 MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
11896 MOZ_ASSERT(!gTelemetryIdMutex);
11898 // Always create this so that later access to gTelemetryIdHashtable can be
11899 // properly synchronized.
11900 gTelemetryIdMutex = new Mutex("IndexedDB gTelemetryIdMutex");
11902 gStorageDatabaseNameMutex = new Mutex("IndexedDB gStorageDatabaseNameMutex");
11904 sInstance = this;
11907 QuotaClient::~QuotaClient() {
11908 AssertIsOnBackgroundThread();
11909 MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
11910 MOZ_ASSERT(gTelemetryIdMutex);
11911 MOZ_ASSERT(!mMaintenanceThreadPool);
11913 // No one else should be able to touch gTelemetryIdHashtable now that the
11914 // QuotaClient has gone away.
11915 gTelemetryIdHashtable = nullptr;
11916 gTelemetryIdMutex = nullptr;
11918 gStorageDatabaseNameHashtable = nullptr;
11919 gStorageDatabaseNameMutex = nullptr;
11921 sInstance = nullptr;
11924 nsresult QuotaClient::AsyncDeleteFile(DatabaseFileManager* aFileManager,
11925 int64_t aFileId) {
11926 AssertIsOnBackgroundThread();
11928 if (IsShuttingDownOnBackgroundThread()) {
11929 // Whoops! We want to delete an IndexedDB disk-backed File but it's too late
11930 // to actually delete the file! This means we're going to "leak" the file
11931 // and leave it around when we shouldn't! (The file will stay around until
11932 // next storage initialization is triggered when the app is started again).
11933 // Fixing this is tracked by bug 1539377.
11935 return NS_OK;
11938 MOZ_ASSERT(mDeleteTimer);
11939 MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel());
11941 QM_TRY(MOZ_TO_RESULT(mDeleteTimer->InitWithNamedFuncCallback(
11942 DeleteTimerCallback, this, kDeleteTimeoutMs, nsITimer::TYPE_ONE_SHOT,
11943 "dom::indexeddb::QuotaClient::AsyncDeleteFile")));
11945 mPendingDeleteInfos.GetOrInsertNew(aFileManager)->AppendElement(aFileId);
11947 return NS_OK;
11950 nsresult QuotaClient::FlushPendingFileDeletions() {
11951 AssertIsOnBackgroundThread();
11953 QM_TRY(MOZ_TO_RESULT(mDeleteTimer->Cancel()));
11955 DeleteTimerCallback(mDeleteTimer, this);
11957 return NS_OK;
11960 nsThreadPool* QuotaClient::GetOrCreateThreadPool() {
11961 AssertIsOnBackgroundThread();
11962 MOZ_ASSERT(!IsShuttingDownOnBackgroundThread());
11964 if (!mMaintenanceThreadPool) {
11965 RefPtr<nsThreadPool> threadPool = new nsThreadPool();
11967 // PR_GetNumberOfProcessors() can return -1 on error, so make sure we
11968 // don't set some huge number here. We add 2 in case some threads block on
11969 // the disk I/O.
11970 const uint32_t threadCount =
11971 std::max(int32_t(PR_GetNumberOfProcessors()), int32_t(1)) + 2;
11973 MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(threadCount));
11975 // Don't keep more than one idle thread.
11976 MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadLimit(1));
11978 // Don't keep idle threads alive very long.
11979 MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadTimeout(5 * PR_MSEC_PER_SEC));
11981 MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("IndexedDB Mnt"_ns));
11983 mMaintenanceThreadPool = std::move(threadPool);
11986 return mMaintenanceThreadPool;
11989 mozilla::dom::quota::Client::Type QuotaClient::GetType() {
11990 return QuotaClient::IDB;
11993 nsresult QuotaClient::UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) {
11994 AssertIsOnIOThread();
11995 MOZ_ASSERT(aDirectory);
11997 QM_TRY_INSPECT((const auto& [subdirsToProcess, databaseFilenames]),
11998 GetDatabaseFilenames(*aDirectory,
11999 /* aCanceled */ AtomicBool{false}));
12001 QM_TRY(CollectEachInRange(
12002 subdirsToProcess,
12003 [&databaseFilenames = databaseFilenames,
12004 aDirectory](const nsAString& subdirName) -> Result<Ok, nsresult> {
12005 // If the directory has the correct suffix then it should exist in
12006 // databaseFilenames.
12007 nsDependentSubstring subdirNameBase;
12008 if (GetFilenameBase(subdirName, kFileManagerDirectoryNameSuffix,
12009 subdirNameBase)) {
12010 QM_WARNONLY_TRY(OkIf(databaseFilenames.Contains(subdirNameBase)));
12011 return Ok{};
12014 // The directory didn't have the right suffix but we might need to
12015 // rename it. Check to see if we have a database that references this
12016 // directory.
12017 QM_TRY_INSPECT(
12018 const auto& subdirNameWithSuffix,
12019 ([&databaseFilenames,
12020 &subdirName]() -> Result<nsAutoString, NotOk> {
12021 if (databaseFilenames.Contains(subdirName)) {
12022 return nsAutoString{subdirName +
12023 kFileManagerDirectoryNameSuffix};
12026 // Windows doesn't allow a directory to end with a dot ('.'), so
12027 // we have to check that possibility here too. We do this on all
12028 // platforms, because the origin directory may have been created
12029 // on Windows and now accessed on different OS.
12030 const nsAutoString subdirNameWithDot = subdirName + u"."_ns;
12031 QM_TRY(OkIf(databaseFilenames.Contains(subdirNameWithDot)),
12032 Err(NotOk{}));
12034 return nsAutoString{subdirNameWithDot +
12035 kFileManagerDirectoryNameSuffix};
12036 }()),
12037 Ok{});
12039 // We do have a database that uses this subdir so we should rename it
12040 // now.
12041 QM_TRY_INSPECT(const auto& subdir,
12042 CloneFileAndAppend(*aDirectory, subdirName));
12044 DebugOnly<bool> isDirectory;
12045 MOZ_ASSERT(NS_SUCCEEDED(subdir->IsDirectory(&isDirectory)));
12046 MOZ_ASSERT(isDirectory);
12048 // Check if the subdir with suffix already exists before renaming.
12049 QM_TRY_INSPECT(const auto& subdirWithSuffix,
12050 CloneFileAndAppend(*aDirectory, subdirNameWithSuffix));
12052 QM_TRY_INSPECT(const bool& exists,
12053 MOZ_TO_RESULT_INVOKE_MEMBER(subdirWithSuffix, Exists));
12055 if (exists) {
12056 IDB_WARNING("Deleting old %s files directory!",
12057 NS_ConvertUTF16toUTF8(subdirName).get());
12059 QM_TRY(MOZ_TO_RESULT(subdir->Remove(/* aRecursive */ true)));
12061 return Ok{};
12064 // Finally, rename the subdir.
12065 QM_TRY(MOZ_TO_RESULT(subdir->RenameTo(nullptr, subdirNameWithSuffix)));
12067 return Ok{};
12068 }));
12070 return NS_OK;
12073 nsresult QuotaClient::UpgradeStorageFrom2_1To2_2(nsIFile* aDirectory) {
12074 AssertIsOnIOThread();
12075 MOZ_ASSERT(aDirectory);
12077 QM_TRY(CollectEachFile(
12078 *aDirectory, [](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
12079 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
12081 switch (dirEntryKind) {
12082 case nsIFileKind::ExistsAsDirectory:
12083 break;
12085 case nsIFileKind::ExistsAsFile: {
12086 QM_TRY_INSPECT(
12087 const auto& leafName,
12088 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName));
12090 // It's reported that files ending with ".tmp" somehow live in the
12091 // indexedDB directories in Bug 1503883. Such files shouldn't exist
12092 // in the indexedDB directory so remove them in this upgrade.
12093 if (StringEndsWith(leafName, u".tmp"_ns)) {
12094 IDB_WARNING("Deleting unknown temporary file!");
12096 QM_TRY(MOZ_TO_RESULT(file->Remove(false)));
12099 break;
12102 case nsIFileKind::DoesNotExist:
12103 // Ignore files that got removed externally while iterating.
12104 break;
12107 return Ok{};
12108 }));
12110 return NS_OK;
12113 Result<UsageInfo, nsresult> QuotaClient::InitOrigin(
12114 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
12115 const AtomicBool& aCanceled) {
12116 AssertIsOnIOThread();
12118 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(this, GetUsageForOriginInternal,
12119 aPersistenceType, aOriginMetadata,
12120 aCanceled,
12121 /* aInitializing*/ true));
12124 nsresult QuotaClient::InitOriginWithoutTracking(
12125 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
12126 const AtomicBool& aCanceled) {
12127 AssertIsOnIOThread();
12129 return GetUsageForOriginInternal(aPersistenceType, aOriginMetadata, aCanceled,
12130 /* aInitializing*/ true, nullptr);
12133 Result<UsageInfo, nsresult> QuotaClient::GetUsageForOrigin(
12134 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
12135 const AtomicBool& aCanceled) {
12136 AssertIsOnIOThread();
12138 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(this, GetUsageForOriginInternal,
12139 aPersistenceType, aOriginMetadata,
12140 aCanceled,
12141 /* aInitializing*/ false));
12144 nsresult QuotaClient::GetUsageForOriginInternal(
12145 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
12146 const AtomicBool& aCanceled, const bool aInitializing,
12147 UsageInfo* aUsageInfo) {
12148 AssertIsOnIOThread();
12149 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
12151 QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& directory,
12152 GetDirectory(aOriginMetadata));
12154 // We need to see if there are any files in the directory already. If they
12155 // are database files then we need to cleanup stored files (if it's needed)
12156 // and also get the usage.
12158 // XXX Can we avoid unwrapping into non-const variables here? (Only
12159 // databaseFilenames is currently modified below)
12160 QM_TRY_UNWRAP((auto [subdirsToProcess, databaseFilenames, obsoleteFilenames]),
12161 GetDatabaseFilenames<ObsoleteFilenamesHandling::Include>(
12162 *directory, aCanceled));
12164 if (aInitializing) {
12165 QM_TRY(CollectEachInRange(
12166 subdirsToProcess,
12167 [&directory, &obsoleteFilenames = obsoleteFilenames,
12168 &databaseFilenames = databaseFilenames, aPersistenceType,
12169 &aOriginMetadata](
12170 const nsAString& subdirName) -> Result<Ok, nsresult> {
12171 // The directory must have the correct suffix.
12172 nsDependentSubstring subdirNameBase;
12173 QM_TRY(QM_OR_ELSE_WARN(
12174 // Expression.
12175 ([&subdirName, &subdirNameBase] {
12176 QM_TRY_RETURN(OkIf(GetFilenameBase(
12177 subdirName, kFileManagerDirectoryNameSuffix,
12178 subdirNameBase)));
12179 }()),
12180 // Fallback.
12181 ([&directory,
12182 &subdirName](const NotOk) -> Result<Ok, nsresult> {
12183 // If there is an unexpected directory in the idb
12184 // directory, trying to delete at first instead of
12185 // breaking the whole initialization.
12186 QM_TRY(MOZ_TO_RESULT(
12187 DeleteFilesNoQuota(directory, subdirName)),
12188 Err(NS_ERROR_UNEXPECTED));
12190 return Ok{};
12191 })),
12192 Ok{});
12194 if (obsoleteFilenames.Contains(subdirNameBase)) {
12195 // If this fails, it probably means we are in a serious situation.
12196 // e.g. Filesystem corruption. Will handle this in bug 1521541.
12197 QM_TRY(MOZ_TO_RESULT(RemoveDatabaseFilesAndDirectory(
12198 *directory, subdirNameBase, /* aQuotaManager */ nullptr,
12199 aPersistenceType, aOriginMetadata,
12200 /* aDatabaseName */ u""_ns)),
12201 Err(NS_ERROR_UNEXPECTED));
12203 databaseFilenames.Remove(subdirNameBase);
12204 return Ok{};
12207 // The directory base must exist in databaseFilenames.
12208 // If there is an unexpected directory in the idb directory, trying to
12209 // delete at first instead of breaking the whole initialization.
12211 // XXX This is still somewhat quirky. It would be nice to make it
12212 // clear that the warning handler is infallible, which would also
12213 // remove the need for the error type conversion.
12214 QM_WARNONLY_TRY(QM_OR_ELSE_WARN(
12215 // Expression.
12216 OkIf(databaseFilenames.Contains(subdirNameBase))
12217 .mapErr([](const NotOk) { return NS_ERROR_FAILURE; }),
12218 // Fallback.
12219 ([&directory,
12220 &subdirName](const nsresult) -> Result<Ok, nsresult> {
12221 // XXX It seems if we really got here, we can fail the
12222 // MOZ_ASSERT(!quotaManager->IsTemporaryStorageInitializedInternal());
12223 // assertion in DeleteFilesNoQuota.
12224 QM_TRY(MOZ_TO_RESULT(DeleteFilesNoQuota(directory, subdirName)),
12225 Err(NS_ERROR_UNEXPECTED));
12227 return Ok{};
12228 })));
12230 return Ok{};
12231 }));
12234 for (const auto& databaseFilename : databaseFilenames) {
12235 if (aCanceled) {
12236 break;
12239 QM_TRY_INSPECT(
12240 const auto& fmDirectory,
12241 CloneFileAndAppend(*directory,
12242 databaseFilename + kFileManagerDirectoryNameSuffix));
12244 QM_TRY_INSPECT(
12245 const auto& databaseFile,
12246 CloneFileAndAppend(*directory, databaseFilename + kSQLiteSuffix));
12248 if (aInitializing) {
12249 QM_TRY(MOZ_TO_RESULT(DatabaseFileManager::InitDirectory(
12250 *fmDirectory, *databaseFile, aOriginMetadata.mOrigin,
12251 TelemetryIdForFile(databaseFile))));
12254 if (aUsageInfo) {
12256 QM_TRY_INSPECT(const int64_t& fileSize,
12257 MOZ_TO_RESULT_INVOKE_MEMBER(databaseFile, GetFileSize));
12259 MOZ_ASSERT(fileSize >= 0);
12261 *aUsageInfo += DatabaseUsageType(Some(uint64_t(fileSize)));
12265 QM_TRY_INSPECT(const auto& walFile,
12266 CloneFileAndAppend(*directory,
12267 databaseFilename + kSQLiteWALSuffix));
12269 // QM_OR_ELSE_WARN_IF is not used here since we just want to log
12270 // NS_ERROR_FILE_NOT_FOUND result and not spam the reports (the -wal
12271 // file doesn't have to exist).
12272 QM_TRY_INSPECT(const int64_t& walFileSize,
12273 QM_OR_ELSE_LOG_VERBOSE_IF(
12274 // Expression.
12275 MOZ_TO_RESULT_INVOKE_MEMBER(walFile, GetFileSize),
12276 // Predicate.
12277 ([](const nsresult rv) {
12278 return rv == NS_ERROR_FILE_NOT_FOUND;
12280 // Fallback.
12281 (ErrToOk<0, int64_t>)));
12282 MOZ_ASSERT(walFileSize >= 0);
12283 *aUsageInfo += DatabaseUsageType(Some(uint64_t(walFileSize)));
12287 QM_TRY_INSPECT(const auto& fileUsage,
12288 DatabaseFileManager::GetUsage(fmDirectory));
12290 *aUsageInfo += fileUsage;
12295 return NS_OK;
12298 void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
12299 const nsACString& aOrigin) {
12300 AssertIsOnIOThread();
12302 if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
12303 mgr->InvalidateFileManagers(aPersistenceType, aOrigin);
12307 void QuotaClient::OnRepositoryClearCompleted(PersistenceType aPersistenceType) {
12308 AssertIsOnIOThread();
12310 if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
12311 mgr->InvalidateFileManagers(aPersistenceType);
12315 void QuotaClient::ReleaseIOThreadObjects() {
12316 AssertIsOnIOThread();
12318 if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
12319 mgr->InvalidateAllFileManagers();
12323 void QuotaClient::AbortOperationsForLocks(
12324 const DirectoryLockIdTable& aDirectoryLockIds) {
12325 AssertIsOnBackgroundThread();
12327 InvalidateLiveDatabasesMatching([&aDirectoryLockIds](const auto& database) {
12328 // If the database is registered in gLiveDatabaseHashtable then it must have
12329 // a directory lock.
12330 return IsLockForObjectContainedInLockTable(database, aDirectoryLockIds);
12334 void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) {
12335 AssertIsOnBackgroundThread();
12337 InvalidateLiveDatabasesMatching([&aContentParentId](const auto& database) {
12338 return database.IsOwnedByProcess(aContentParentId);
12342 void QuotaClient::AbortAllOperations() {
12343 AssertIsOnBackgroundThread();
12345 AbortAllMaintenances();
12347 InvalidateLiveDatabasesMatching([](const auto&) { return true; });
12350 void QuotaClient::StartIdleMaintenance() {
12351 AssertIsOnBackgroundThread();
12352 if (IsShuttingDownOnBackgroundThread()) {
12353 MOZ_ASSERT(false, "!IsShuttingDownOnBackgroundThread()");
12354 return;
12357 if (!mBackgroundThread) {
12358 mBackgroundThread = GetCurrentSerialEventTarget();
12361 mMaintenanceQueue.EmplaceBack(MakeRefPtr<Maintenance>(this));
12362 ProcessMaintenanceQueue();
12365 void QuotaClient::StopIdleMaintenance() {
12366 AssertIsOnBackgroundThread();
12368 AbortAllMaintenances();
12371 void QuotaClient::InitiateShutdown() {
12372 AssertIsOnBackgroundThread();
12373 MOZ_ASSERT(IsShuttingDownOnBackgroundThread());
12375 if (mDeleteTimer) {
12376 // QuotaClient::AsyncDeleteFile will not schedule new timers beyond
12377 // shutdown. And we expect all critical (PBM) deletions to have been
12378 // triggered before this point via ClearPrivateRepository (w/out using
12379 // DeleteFilesRunnable at all).
12380 mDeleteTimer->Cancel();
12381 mDeleteTimer = nullptr;
12382 mPendingDeleteInfos.Clear();
12385 AbortAllOperations();
12388 bool QuotaClient::IsShutdownCompleted() const {
12389 return (!gFactoryOps || gFactoryOps->IsEmpty()) &&
12390 (!gLiveDatabaseHashtable || !gLiveDatabaseHashtable->Count()) &&
12391 !mCurrentMaintenance && !DeleteFilesRunnable::IsDeletionPending();
12394 void QuotaClient::ForceKillActors() {
12395 // Currently we don't implement force killing actors.
12398 nsCString QuotaClient::GetShutdownStatus() const {
12399 AssertIsOnBackgroundThread();
12401 nsCString data;
12403 if (gFactoryOps && !gFactoryOps->IsEmpty()) {
12404 data.Append("FactoryOperations: "_ns +
12405 IntToCString(static_cast<uint32_t>(gFactoryOps->Length())) +
12406 " ("_ns);
12408 // XXX It might be confusing to remove duplicates here, as the actual list
12409 // won't match the count then.
12410 nsTHashSet<nsCString> ids;
12411 std::transform(gFactoryOps->cbegin(), gFactoryOps->cend(),
12412 MakeInserter(ids), [](const auto& factoryOp) {
12413 MOZ_ASSERT(factoryOp);
12415 nsCString id;
12416 factoryOp->Stringify(id);
12417 return id;
12420 StringJoinAppend(data, ", "_ns, ids);
12422 data.Append(")\n");
12425 if (gLiveDatabaseHashtable && gLiveDatabaseHashtable->Count()) {
12426 data.Append("LiveDatabases: "_ns +
12427 IntToCString(gLiveDatabaseHashtable->Count()) + " ("_ns);
12429 // XXX What's the purpose of adding these to a hashtable before joining them
12430 // to the string? (Maybe this used to be an ordered container before???)
12431 nsTHashSet<nsCString> ids;
12433 for (const auto& entry : gLiveDatabaseHashtable->Values()) {
12434 MOZ_ASSERT(entry);
12436 std::transform(entry->mLiveDatabases.cbegin(),
12437 entry->mLiveDatabases.cend(), MakeInserter(ids),
12438 [](const auto& database) {
12439 nsCString id;
12440 database->Stringify(id);
12441 return id;
12445 StringJoinAppend(data, ", "_ns, ids);
12447 data.Append(")\n");
12450 if (mCurrentMaintenance) {
12451 data.Append("IdleMaintenance: 1 (");
12452 mCurrentMaintenance->Stringify(data);
12453 data.Append(")\n");
12456 return data;
12459 void QuotaClient::FinalizeShutdown() {
12460 RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
12461 if (connectionPool) {
12462 connectionPool->Shutdown();
12464 gConnectionPool = nullptr;
12467 if (mMaintenanceThreadPool) {
12468 mMaintenanceThreadPool->Shutdown();
12469 mMaintenanceThreadPool = nullptr;
12473 void QuotaClient::DeleteTimerCallback(nsITimer* aTimer, void* aClosure) {
12474 AssertIsOnBackgroundThread();
12475 MOZ_ASSERT(aTimer);
12477 // Even though we do not schedule new timers after shutdown has started,
12478 // an already existing one might fire afterwards (actually we think it
12479 // shouldn't, but there is no reason to enforce this invariant). We can
12480 // just ignore it, the cleanup work is done in InitiateShutdown.
12481 if (NS_WARN_IF(IsShuttingDownOnBackgroundThread())) {
12482 return;
12485 auto* const self = static_cast<QuotaClient*>(aClosure);
12486 MOZ_ASSERT(self);
12487 MOZ_ASSERT(self->mDeleteTimer);
12488 MOZ_ASSERT(SameCOMIdentity(self->mDeleteTimer, aTimer));
12490 for (const auto& pendingDeleteInfoEntry : self->mPendingDeleteInfos) {
12491 const auto& key = pendingDeleteInfoEntry.GetKey();
12492 const auto& value = pendingDeleteInfoEntry.GetData();
12493 MOZ_ASSERT(!value->IsEmpty());
12495 RefPtr<DeleteFilesRunnable> runnable = new DeleteFilesRunnable(
12496 SafeRefPtr{key, AcquireStrongRefFromRawPtr{}}, std::move(*value));
12498 MOZ_ASSERT(value->IsEmpty());
12500 runnable->RunImmediately();
12503 self->mPendingDeleteInfos.Clear();
12506 void QuotaClient::AbortAllMaintenances() {
12507 if (mCurrentMaintenance) {
12508 mCurrentMaintenance->Abort();
12511 for (const auto& maintenance : mMaintenanceQueue) {
12512 maintenance->Abort();
12516 Result<nsCOMPtr<nsIFile>, nsresult> QuotaClient::GetDirectory(
12517 const OriginMetadata& aOriginMetadata) {
12518 QuotaManager* const quotaManager = QuotaManager::Get();
12519 NS_ASSERTION(quotaManager, "This should never fail!");
12521 QM_TRY_INSPECT(const auto& directory,
12522 quotaManager->GetOriginDirectory(aOriginMetadata));
12524 MOZ_ASSERT(directory);
12526 QM_TRY(MOZ_TO_RESULT(
12527 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME))));
12529 return directory;
12532 template <QuotaClient::ObsoleteFilenamesHandling ObsoleteFilenames>
12533 Result<QuotaClient::GetDatabaseFilenamesResult<ObsoleteFilenames>, nsresult>
12534 QuotaClient::GetDatabaseFilenames(nsIFile& aDirectory,
12535 const AtomicBool& aCanceled) {
12536 AssertIsOnIOThread();
12538 GetDatabaseFilenamesResult<ObsoleteFilenames> result;
12540 QM_TRY(CollectEachFileAtomicCancelable(
12541 aDirectory, aCanceled,
12542 [&result](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
12543 QM_TRY_INSPECT(const auto& leafName, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
12544 nsString, file, GetLeafName));
12546 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
12548 switch (dirEntryKind) {
12549 case nsIFileKind::ExistsAsDirectory:
12550 result.subdirsToProcess.AppendElement(leafName);
12551 break;
12553 case nsIFileKind::ExistsAsFile: {
12554 if constexpr (ObsoleteFilenames ==
12555 ObsoleteFilenamesHandling::Include) {
12556 if (StringBeginsWith(leafName, kIdbDeletionMarkerFilePrefix)) {
12557 result.obsoleteFilenames.Insert(
12558 Substring(leafName, kIdbDeletionMarkerFilePrefix.Length()));
12559 break;
12563 // Skip OS metadata files. These files are only used in different
12564 // platforms, but the profile can be shared across different
12565 // operating systems, so we check it on all platforms.
12566 if (QuotaManager::IsOSMetadata(leafName)) {
12567 break;
12570 // Skip files starting with ".".
12571 if (QuotaManager::IsDotFile(leafName)) {
12572 break;
12575 // Skip SQLite temporary files. These files take up space on disk
12576 // but will be deleted as soon as the database is opened, so we
12577 // don't count them towards quota.
12578 if (StringEndsWith(leafName, kSQLiteJournalSuffix) ||
12579 StringEndsWith(leafName, kSQLiteSHMSuffix)) {
12580 break;
12583 // The SQLite WAL file does count towards quota, but it is handled
12584 // below once we find the actual database file.
12585 if (StringEndsWith(leafName, kSQLiteWALSuffix)) {
12586 break;
12589 nsDependentSubstring leafNameBase;
12590 if (!GetFilenameBase(leafName, kSQLiteSuffix, leafNameBase)) {
12591 UNKNOWN_FILE_WARNING(leafName);
12592 break;
12595 result.databaseFilenames.Insert(leafNameBase);
12596 break;
12599 case nsIFileKind::DoesNotExist:
12600 // Ignore files that got removed externally while iterating.
12601 break;
12604 return Ok{};
12605 }));
12607 return result;
12610 void QuotaClient::ProcessMaintenanceQueue() {
12611 AssertIsOnBackgroundThread();
12613 if (mCurrentMaintenance || mMaintenanceQueue.IsEmpty()) {
12614 return;
12617 mCurrentMaintenance = mMaintenanceQueue[0];
12618 mMaintenanceQueue.RemoveElementAt(0);
12620 mCurrentMaintenance->RunImmediately();
12623 /*******************************************************************************
12624 * DeleteFilesRunnable
12625 ******************************************************************************/
12627 uint64_t DeleteFilesRunnable::sPendingRunnables = 0;
12629 DeleteFilesRunnable::DeleteFilesRunnable(
12630 SafeRefPtr<DatabaseFileManager> aFileManager, nsTArray<int64_t>&& aFileIds)
12631 : Runnable("dom::indexeddb::DeleteFilesRunnable"),
12632 mOwningEventTarget(GetCurrentSerialEventTarget()),
12633 mFileManager(std::move(aFileManager)),
12634 mFileIds(std::move(aFileIds)),
12635 mState(State_Initial) {}
12637 #ifdef DEBUG
12638 DeleteFilesRunnable::~DeleteFilesRunnable() {
12639 MOZ_ASSERT(!mDEBUGCountsAsPending);
12641 #endif
12643 void DeleteFilesRunnable::RunImmediately() {
12644 AssertIsOnBackgroundThread();
12645 MOZ_ASSERT(mState == State_Initial);
12647 Unused << this->Run();
12650 void DeleteFilesRunnable::Open() {
12651 AssertIsOnBackgroundThread();
12652 MOZ_ASSERT(mState == State_Initial);
12654 MOZ_ASSERT(!mDEBUGCountsAsPending);
12655 sPendingRunnables++;
12656 DEBUGONLY(mDEBUGCountsAsPending = true);
12658 QuotaManager* const quotaManager = QuotaManager::Get();
12659 if (NS_WARN_IF(!quotaManager)) {
12660 Finish();
12661 return;
12664 mState = State_DirectoryOpenPending;
12666 quotaManager
12667 ->OpenClientDirectory(
12668 {mFileManager->OriginMetadata(), quota::Client::IDB})
12669 ->Then(
12670 GetCurrentSerialEventTarget(), __func__,
12671 [self = RefPtr(this)](
12672 const ClientDirectoryLockPromise::ResolveOrRejectValue& aValue) {
12673 if (aValue.IsResolve()) {
12674 self->DirectoryLockAcquired(aValue.ResolveValue());
12675 } else {
12676 self->DirectoryLockFailed();
12681 void DeleteFilesRunnable::DoDatabaseWork() {
12682 AssertIsOnIOThread();
12683 MOZ_ASSERT(mState == State_DatabaseWorkOpen);
12685 if (!mFileManager->Invalidated()) {
12686 for (int64_t fileId : mFileIds) {
12687 if (NS_FAILED(mFileManager->SyncDeleteFile(fileId))) {
12688 NS_WARNING("Failed to delete file!");
12693 Finish();
12696 void DeleteFilesRunnable::Finish() {
12697 MOZ_ASSERT(mState != State_UnblockingOpen);
12699 // Must set mState before dispatching otherwise we will race with the main
12700 // thread.
12701 mState = State_UnblockingOpen;
12703 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
12706 void DeleteFilesRunnable::UnblockOpen() {
12707 AssertIsOnBackgroundThread();
12708 MOZ_ASSERT(mState == State_UnblockingOpen);
12710 mDirectoryLock = nullptr;
12711 MOZ_ASSERT(mDEBUGCountsAsPending);
12712 sPendingRunnables--;
12713 DEBUGONLY(mDEBUGCountsAsPending = false);
12715 mState = State_Completed;
12718 NS_IMETHODIMP
12719 DeleteFilesRunnable::Run() {
12720 switch (mState) {
12721 case State_Initial:
12722 Open();
12723 break;
12725 case State_DatabaseWorkOpen:
12726 DoDatabaseWork();
12727 break;
12729 case State_UnblockingOpen:
12730 UnblockOpen();
12731 break;
12733 case State_DirectoryOpenPending:
12734 default:
12735 MOZ_CRASH("Should never get here!");
12738 return NS_OK;
12741 void DeleteFilesRunnable::DirectoryLockAcquired(DirectoryLock* aLock) {
12742 AssertIsOnBackgroundThread();
12743 MOZ_ASSERT(mState == State_DirectoryOpenPending);
12744 MOZ_ASSERT(!mDirectoryLock);
12746 mDirectoryLock = aLock;
12748 QuotaManager* const quotaManager = QuotaManager::Get();
12749 MOZ_ASSERT(quotaManager);
12751 // Must set this before dispatching otherwise we will race with the IO thread
12752 mState = State_DatabaseWorkOpen;
12754 QM_TRY(MOZ_TO_RESULT(
12755 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
12756 QM_VOID, [this](const nsresult) { Finish(); });
12759 void DeleteFilesRunnable::DirectoryLockFailed() {
12760 AssertIsOnBackgroundThread();
12761 MOZ_ASSERT(mState == State_DirectoryOpenPending);
12762 MOZ_ASSERT(!mDirectoryLock);
12764 Finish();
12767 void Maintenance::Abort() {
12768 AssertIsOnBackgroundThread();
12770 // Safe because mDatabaseMaintenances is modified
12771 // only in the background thread
12772 for (const auto& aDatabaseMaintenance : mDatabaseMaintenances) {
12773 aDatabaseMaintenance.GetData()->Abort();
12776 // mDirectoryLock must be cleared before transition to finished state
12777 mDirectoryLock = nullptr;
12778 mAborted = true;
12781 void Maintenance::RegisterDatabaseMaintenance(
12782 DatabaseMaintenance* aDatabaseMaintenance) {
12783 AssertIsOnBackgroundThread();
12784 MOZ_ASSERT(aDatabaseMaintenance);
12785 MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
12786 MOZ_ASSERT(
12787 !mDatabaseMaintenances.Contains(aDatabaseMaintenance->DatabasePath()));
12789 mDatabaseMaintenances.InsertOrUpdate(aDatabaseMaintenance->DatabasePath(),
12790 aDatabaseMaintenance);
12793 void Maintenance::UnregisterDatabaseMaintenance(
12794 DatabaseMaintenance* aDatabaseMaintenance) {
12795 AssertIsOnBackgroundThread();
12796 MOZ_ASSERT(aDatabaseMaintenance);
12797 MOZ_ASSERT(mState == State::WaitingForDatabaseMaintenancesToComplete);
12798 MOZ_ASSERT(mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
12800 mDatabaseMaintenances.Remove(aDatabaseMaintenance->DatabasePath());
12802 if (mDatabaseMaintenances.Count()) {
12803 return;
12806 for (const auto& completeCallback : mCompleteCallbacks) {
12807 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(completeCallback));
12809 mCompleteCallbacks.Clear();
12811 mState = State::Finishing;
12812 Finish();
12815 void Maintenance::Stringify(nsACString& aResult) const {
12816 AssertIsOnBackgroundThread();
12818 aResult.Append("DatabaseMaintenances: "_ns +
12819 IntToCString(mDatabaseMaintenances.Count()) + " ("_ns);
12821 // XXX It might be confusing to remove duplicates here, as the actual list
12822 // won't match the count then.
12823 nsTHashSet<nsCString> ids;
12824 std::transform(mDatabaseMaintenances.Values().cbegin(),
12825 mDatabaseMaintenances.Values().cend(), MakeInserter(ids),
12826 [](const auto& entry) {
12827 MOZ_ASSERT(entry);
12829 nsCString id;
12830 entry->Stringify(id);
12832 return id;
12835 StringJoinAppend(aResult, ", "_ns, ids);
12837 aResult.Append(")");
12840 nsresult Maintenance::Start() {
12841 AssertIsOnBackgroundThread();
12842 MOZ_ASSERT(mState == State::Initial);
12844 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
12845 IsAborted()) {
12846 return NS_ERROR_ABORT;
12849 // Make sure that the IndexedDatabaseManager is running so that we can check
12850 // for low disk space mode.
12852 if (IndexedDatabaseManager::Get()) {
12853 OpenDirectory();
12854 return NS_OK;
12857 mState = State::CreateIndexedDatabaseManager;
12858 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
12860 return NS_OK;
12863 nsresult Maintenance::CreateIndexedDatabaseManager() {
12864 MOZ_ASSERT(NS_IsMainThread());
12865 MOZ_ASSERT(mState == State::CreateIndexedDatabaseManager);
12867 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
12868 IsAborted()) {
12869 return NS_ERROR_ABORT;
12872 IndexedDatabaseManager* const mgr = IndexedDatabaseManager::GetOrCreate();
12873 if (NS_WARN_IF(!mgr)) {
12874 return NS_ERROR_FAILURE;
12877 mState = State::IndexedDatabaseManagerOpen;
12878 MOZ_ALWAYS_SUCCEEDS(
12879 mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
12881 return NS_OK;
12884 nsresult Maintenance::OpenDirectory() {
12885 AssertIsOnBackgroundThread();
12886 MOZ_ASSERT(mState == State::Initial ||
12887 mState == State::IndexedDatabaseManagerOpen);
12888 MOZ_ASSERT(!mDirectoryLock);
12889 MOZ_ASSERT(QuotaManager::Get());
12891 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
12892 IsAborted()) {
12893 return NS_ERROR_ABORT;
12896 QuotaManager* quotaManager = QuotaManager::Get();
12897 MOZ_ASSERT(quotaManager);
12899 // Get a shared lock for <profile>/storage/*/*/idb
12901 mState = State::DirectoryOpenPending;
12903 quotaManager
12904 ->OpenStorageDirectory(
12905 Nullable<PersistenceType>(), OriginScope::FromNull(),
12906 Nullable<Client::Type>(Client::IDB), /* aExclusive */ false,
12907 DirectoryLockCategory::None, SomeRef(mPendingDirectoryLock))
12908 ->Then(GetCurrentSerialEventTarget(), __func__,
12909 [self = RefPtr(this)](
12910 const UniversalDirectoryLockPromise::ResolveOrRejectValue&
12911 aValue) {
12912 if (aValue.IsResolve()) {
12913 self->DirectoryLockAcquired(aValue.ResolveValue());
12914 } else {
12915 self->DirectoryLockFailed();
12919 return NS_OK;
12922 nsresult Maintenance::DirectoryOpen() {
12923 AssertIsOnBackgroundThread();
12924 MOZ_ASSERT(mState == State::DirectoryOpenPending);
12925 MOZ_ASSERT(mDirectoryLock);
12927 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
12928 IsAborted()) {
12929 return NS_ERROR_ABORT;
12932 QuotaManager* const quotaManager = QuotaManager::Get();
12933 MOZ_ASSERT(quotaManager);
12935 mState = State::DirectoryWorkOpen;
12937 QM_TRY(MOZ_TO_RESULT(
12938 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
12939 NS_ERROR_FAILURE);
12941 return NS_OK;
12944 nsresult Maintenance::DirectoryWork() {
12945 AssertIsOnIOThread();
12946 MOZ_ASSERT(mState == State::DirectoryWorkOpen);
12948 // The storage directory is structured like this:
12950 // <profile>/storage/<persistence>/<origin>/idb/*.sqlite
12952 // We have to find all database files that match any persistence type and any
12953 // origin. We ignore anything out of the ordinary for now.
12955 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
12956 IsAborted()) {
12957 return NS_ERROR_ABORT;
12960 QuotaManager* const quotaManager = QuotaManager::Get();
12961 MOZ_ASSERT(quotaManager);
12963 // Since idle maintenance may occur before temporary storage is initialized,
12964 // make sure it's initialized here (all non-persistent origins need to be
12965 // cleaned up and quota info needs to be loaded for them).
12967 // Don't fail whole idle maintenance in case of an error, the persistent
12968 // repository can still
12969 // be processed.
12970 const bool initTemporaryStorageFailed = [&quotaManager] {
12971 QM_TRY(MOZ_TO_RESULT(
12972 quotaManager->EnsureTemporaryStorageIsInitializedInternal()),
12973 true);
12974 return false;
12975 }();
12977 const nsCOMPtr<nsIFile> storageDir =
12978 GetFileForPath(quotaManager->GetStoragePath());
12979 QM_TRY(OkIf(storageDir), NS_ERROR_FAILURE);
12982 QM_TRY_INSPECT(const bool& exists,
12983 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists));
12985 // XXX No warning here?
12986 if (!exists) {
12987 return NS_ERROR_NOT_AVAILABLE;
12992 QM_TRY_INSPECT(const bool& isDirectory,
12993 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, IsDirectory));
12995 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE);
12998 // There are currently only 4 persistence types, and we want to iterate them
12999 // in this order:
13000 static const PersistenceType kPersistenceTypes[] = {
13001 PERSISTENCE_TYPE_PERSISTENT, PERSISTENCE_TYPE_DEFAULT,
13002 PERSISTENCE_TYPE_TEMPORARY, PERSISTENCE_TYPE_PRIVATE};
13004 static_assert(
13005 ArrayLength(kPersistenceTypes) == size_t(PERSISTENCE_TYPE_INVALID),
13006 "Something changed with available persistence types!");
13008 constexpr auto idbDirName =
13009 NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME);
13011 for (const PersistenceType persistenceType : kPersistenceTypes) {
13012 // Loop over "<persistence>" directories.
13013 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13014 IsAborted()) {
13015 return NS_ERROR_ABORT;
13018 // Don't do any maintenance for private browsing databases, which are only
13019 // temporary.
13020 if (persistenceType == PERSISTENCE_TYPE_PRIVATE) {
13021 continue;
13024 const bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT;
13026 if (!persistent && initTemporaryStorageFailed) {
13027 // Non-persistent (best effort) repositories can't be processed if
13028 // temporary storage initialization failed.
13029 continue;
13032 // XXX persistenceType == PERSISTENCE_TYPE_PERSISTENT shouldn't be a special
13033 // case...
13034 const auto persistenceTypeString =
13035 persistenceType == PERSISTENCE_TYPE_PERSISTENT
13036 ? "permanent"_ns
13037 : PersistenceTypeToString(persistenceType);
13039 QM_TRY_INSPECT(const auto& persistenceDir,
13040 CloneFileAndAppend(*storageDir, NS_ConvertASCIItoUTF16(
13041 persistenceTypeString)));
13044 QM_TRY_INSPECT(const bool& exists,
13045 MOZ_TO_RESULT_INVOKE_MEMBER(persistenceDir, Exists));
13047 if (!exists) {
13048 continue;
13051 QM_TRY_INSPECT(const bool& isDirectory,
13052 MOZ_TO_RESULT_INVOKE_MEMBER(persistenceDir, IsDirectory));
13054 if (NS_WARN_IF(!isDirectory)) {
13055 continue;
13059 // Loop over "<origin>/idb" directories.
13060 QM_TRY(CollectEachFile(
13061 *persistenceDir,
13062 [this, &quotaManager, persistent, persistenceType, &idbDirName](
13063 const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
13064 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
13065 IsAborted()) {
13066 return Err(NS_ERROR_ABORT);
13069 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
13071 switch (dirEntryKind) {
13072 case nsIFileKind::ExistsAsFile:
13073 break;
13075 case nsIFileKind::ExistsAsDirectory: {
13076 // Get the necessary information about the origin
13077 // (GetOriginMetadata also checks if it's a valid origin).
13079 QM_TRY_INSPECT(const auto& metadata,
13080 quotaManager->GetOriginMetadata(originDir),
13081 // Not much we can do here...
13082 Ok{});
13084 // We now use a dedicated repository for private browsing
13085 // databases, but there could be some forgotten private browsing
13086 // databases in other repositories, so it's better to check for
13087 // that and don't do any maintenance for such databases.
13088 if (metadata.mIsPrivate) {
13089 return Ok{};
13092 if (persistent) {
13093 // We have to check that all persistent origins are cleaned up,
13094 // but there's no way to do that by one call, we need to
13095 // initialize (and possibly clean up) them one by one
13096 // (EnsureTemporaryStorageIsInitializedInternal cleans up only
13097 // non-persistent origins).
13099 QM_TRY_UNWRAP(
13100 const DebugOnly<bool> created,
13101 quotaManager->EnsurePersistentOriginIsInitialized(metadata)
13102 .map([](const auto& res) { return res.second; }),
13103 // Not much we can do here...
13104 Ok{});
13106 // We found this origin directory by traversing the repository,
13107 // so EnsurePersistentOriginIsInitialized shouldn't report that
13108 // a new directory has been created.
13109 MOZ_ASSERT(!created);
13112 QM_TRY_INSPECT(const auto& idbDir,
13113 CloneFileAndAppend(*originDir, idbDirName));
13115 QM_TRY_INSPECT(const bool& exists,
13116 MOZ_TO_RESULT_INVOKE_MEMBER(idbDir, Exists));
13118 if (!exists) {
13119 return Ok{};
13122 QM_TRY_INSPECT(const bool& isDirectory,
13123 MOZ_TO_RESULT_INVOKE_MEMBER(idbDir, IsDirectory));
13125 QM_TRY(OkIf(isDirectory), Ok{});
13127 nsTArray<nsString> databasePaths;
13129 // Loop over files in the "idb" directory.
13130 QM_TRY(CollectEachFile(
13131 *idbDir,
13132 [this, &databasePaths](const nsCOMPtr<nsIFile>& idbDirFile)
13133 -> Result<Ok, nsresult> {
13134 if (NS_WARN_IF(QuotaClient::
13135 IsShuttingDownOnNonBackgroundThread()) ||
13136 IsAborted()) {
13137 return Err(NS_ERROR_ABORT);
13140 QM_TRY_UNWRAP(auto idbFilePath,
13141 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
13142 nsString, idbDirFile, GetPath));
13144 if (!StringEndsWith(idbFilePath, kSQLiteSuffix)) {
13145 return Ok{};
13148 QM_TRY_INSPECT(const auto& dirEntryKind,
13149 GetDirEntryKind(*idbDirFile));
13151 switch (dirEntryKind) {
13152 case nsIFileKind::ExistsAsDirectory:
13153 break;
13155 case nsIFileKind::ExistsAsFile:
13156 // Found a database.
13158 MOZ_ASSERT(!databasePaths.Contains(idbFilePath));
13160 databasePaths.AppendElement(std::move(idbFilePath));
13161 break;
13163 case nsIFileKind::DoesNotExist:
13164 // Ignore files that got removed externally while
13165 // iterating.
13166 break;
13169 return Ok{};
13170 }));
13172 if (!databasePaths.IsEmpty()) {
13173 mDirectoryInfos.EmplaceBack(persistenceType, metadata,
13174 std::move(databasePaths));
13177 break;
13180 case nsIFileKind::DoesNotExist:
13181 // Ignore files that got removed externally while iterating.
13182 break;
13185 return Ok{};
13186 }));
13189 mState = State::BeginDatabaseMaintenance;
13191 MOZ_ALWAYS_SUCCEEDS(
13192 mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
13194 return NS_OK;
13197 nsresult Maintenance::BeginDatabaseMaintenance() {
13198 AssertIsOnBackgroundThread();
13199 MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
13201 class MOZ_STACK_CLASS Helper final {
13202 public:
13203 static bool IsSafeToRunMaintenance(const nsAString& aDatabasePath) {
13204 if (gFactoryOps) {
13205 for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
13206 CheckedUnsafePtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
13208 if (existingOp->DatabaseNameRef().isNothing()) {
13209 return false;
13212 if (!existingOp->DatabaseFilePathIsKnown()) {
13213 continue;
13216 if (existingOp->DatabaseFilePath() == aDatabasePath) {
13217 return false;
13222 if (gLiveDatabaseHashtable) {
13223 return std::all_of(
13224 gLiveDatabaseHashtable->Values().cbegin(),
13225 gLiveDatabaseHashtable->Values().cend(),
13226 [&aDatabasePath](const auto& liveDatabasesEntry) {
13227 const auto& liveDatabases = liveDatabasesEntry->mLiveDatabases;
13228 return std::all_of(liveDatabases.cbegin(), liveDatabases.cend(),
13229 [&aDatabasePath](const auto& database) {
13230 return database->FilePath() != aDatabasePath;
13235 return true;
13239 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
13240 IsAborted()) {
13241 return NS_ERROR_ABORT;
13244 RefPtr<nsThreadPool> threadPool;
13246 for (DirectoryInfo& directoryInfo : mDirectoryInfos) {
13247 RefPtr<DirectoryLock> directoryLock;
13249 for (const nsAString& databasePath : *directoryInfo.mDatabasePaths) {
13250 if (Helper::IsSafeToRunMaintenance(databasePath)) {
13251 if (!directoryLock) {
13252 directoryLock = mDirectoryLock->SpecializeForClient(
13253 directoryInfo.mPersistenceType, *directoryInfo.mOriginMetadata,
13254 Client::IDB);
13255 MOZ_ASSERT(directoryLock);
13258 // No key needs to be passed here, because we skip encrypted databases
13259 // in DoDirectoryWork as long as they are only used in private browsing
13260 // mode.
13261 const auto databaseMaintenance = MakeRefPtr<DatabaseMaintenance>(
13262 this, directoryLock, directoryInfo.mPersistenceType,
13263 *directoryInfo.mOriginMetadata, databasePath, Nothing{});
13265 if (!threadPool) {
13266 threadPool = mQuotaClient->GetOrCreateThreadPool();
13267 MOZ_ASSERT(threadPool);
13270 // Perform database maintenance on a TaskQueue, as database connections
13271 // require a serial event target when being opened in order to allow
13272 // memory pressure notifications to clear caches (bug 1806751).
13273 const auto taskQueue = TaskQueue::Create(
13274 do_AddRef(threadPool), "IndexedDB Database Maintenance");
13276 MOZ_ALWAYS_SUCCEEDS(
13277 taskQueue->Dispatch(databaseMaintenance, NS_DISPATCH_NORMAL));
13279 RegisterDatabaseMaintenance(databaseMaintenance);
13284 mDirectoryInfos.Clear();
13286 mDirectoryLock = nullptr;
13288 if (mDatabaseMaintenances.Count()) {
13289 mState = State::WaitingForDatabaseMaintenancesToComplete;
13290 } else {
13291 mState = State::Finishing;
13292 Finish();
13295 return NS_OK;
13298 void Maintenance::Finish() {
13299 AssertIsOnBackgroundThread();
13300 MOZ_ASSERT(!mDirectoryLock);
13301 MOZ_ASSERT(mState == State::Finishing);
13303 if (NS_FAILED(mResultCode)) {
13304 nsCString errorName;
13305 GetErrorName(mResultCode, errorName);
13307 IDB_WARNING("Maintenance finished with error: %s", errorName.get());
13310 // It can happen that we are only referenced by mCurrentMaintenance which is
13311 // cleared in NoteFinishedMaintenance()
13312 const RefPtr<Maintenance> kungFuDeathGrip = this;
13314 mQuotaClient->NoteFinishedMaintenance(this);
13316 mState = State::Complete;
13319 NS_IMETHODIMP
13320 Maintenance::Run() {
13321 MOZ_ASSERT(mState != State::Complete);
13323 const auto handleError = [this](const nsresult rv) {
13324 if (mState != State::Finishing) {
13325 if (NS_SUCCEEDED(mResultCode)) {
13326 mResultCode = rv;
13329 // Must set mState before dispatching otherwise we will race with the
13330 // owning thread.
13331 mState = State::Finishing;
13333 if (IsOnBackgroundThread()) {
13334 Finish();
13335 } else {
13336 MOZ_ALWAYS_SUCCEEDS(mQuotaClient->BackgroundThread()->Dispatch(
13337 this, NS_DISPATCH_NORMAL));
13342 switch (mState) {
13343 case State::Initial:
13344 QM_TRY(MOZ_TO_RESULT(Start()), NS_OK, handleError);
13345 break;
13347 case State::CreateIndexedDatabaseManager:
13348 QM_TRY(MOZ_TO_RESULT(CreateIndexedDatabaseManager()), NS_OK, handleError);
13349 break;
13351 case State::IndexedDatabaseManagerOpen:
13352 QM_TRY(MOZ_TO_RESULT(OpenDirectory()), NS_OK, handleError);
13353 break;
13355 case State::DirectoryWorkOpen:
13356 QM_TRY(MOZ_TO_RESULT(DirectoryWork()), NS_OK, handleError);
13357 break;
13359 case State::BeginDatabaseMaintenance:
13360 QM_TRY(MOZ_TO_RESULT(BeginDatabaseMaintenance()), NS_OK, handleError);
13361 break;
13363 case State::Finishing:
13364 Finish();
13365 break;
13367 default:
13368 MOZ_CRASH("Bad state!");
13371 return NS_OK;
13374 void Maintenance::DirectoryLockAcquired(DirectoryLock* aLock) {
13375 AssertIsOnBackgroundThread();
13376 MOZ_ASSERT(mState == State::DirectoryOpenPending);
13377 MOZ_ASSERT(!mDirectoryLock);
13379 mDirectoryLock = std::exchange(mPendingDirectoryLock, nullptr);
13381 nsresult rv = DirectoryOpen();
13382 if (NS_WARN_IF(NS_FAILED(rv))) {
13383 if (NS_SUCCEEDED(mResultCode)) {
13384 mResultCode = rv;
13387 mState = State::Finishing;
13388 Finish();
13390 return;
13394 void Maintenance::DirectoryLockFailed() {
13395 AssertIsOnBackgroundThread();
13396 MOZ_ASSERT(mState == State::DirectoryOpenPending);
13397 MOZ_ASSERT(!mDirectoryLock);
13399 mPendingDirectoryLock = nullptr;
13401 if (NS_SUCCEEDED(mResultCode)) {
13402 mResultCode = NS_ERROR_FAILURE;
13405 mState = State::Finishing;
13406 Finish();
13409 void DatabaseMaintenance::Stringify(nsACString& aResult) const {
13410 AssertIsOnBackgroundThread();
13412 aResult.AppendLiteral("Origin:");
13413 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin));
13414 aResult.Append(kQuotaGenericDelimiter);
13416 aResult.AppendLiteral("PersistenceType:");
13417 aResult.Append(PersistenceTypeToString(mPersistenceType));
13418 aResult.Append(kQuotaGenericDelimiter);
13420 aResult.AppendLiteral("Duration:");
13421 aResult.AppendInt((PR_Now() - mMaintenance->StartTime()) / PR_USEC_PER_MSEC);
13424 nsresult DatabaseMaintenance::Abort() {
13425 AssertIsOnBackgroundThread();
13427 // StopIdleMaintenance and AbortAllOperations may request abort independently
13428 if (!mAborted.compareExchange(false, true)) {
13429 return NS_OK;
13433 auto shardStorageConnectionLocked = mSharedStorageConnection.Lock();
13434 if (nsCOMPtr<mozIStorageConnection> connection =
13435 *shardStorageConnectionLocked) {
13436 QM_TRY(MOZ_TO_RESULT(connection->Interrupt()));
13440 // mDirectoryLock must not be released here - otherwise QuotaVFS of storage
13441 // emits a crash to disallow getting a quota object for an unregistered
13442 // directory lock when connection is closed.
13443 // mDirectoryLock will be dropped by RunOnOwningThread in a timely fashion
13444 // after the interrupted maintenance completes.
13446 return NS_OK;
13449 void DatabaseMaintenance::PerformMaintenanceOnDatabase() {
13450 MOZ_ASSERT(!NS_IsMainThread());
13451 MOZ_ASSERT(!IsOnBackgroundThread());
13452 MOZ_ASSERT(mMaintenance);
13453 MOZ_ASSERT(mMaintenance->StartTime());
13454 MOZ_ASSERT(mDirectoryLock);
13455 MOZ_ASSERT(!mDatabasePath.IsEmpty());
13456 MOZ_ASSERT(!mOriginMetadata.mGroup.IsEmpty());
13457 MOZ_ASSERT(!mOriginMetadata.mOrigin.IsEmpty());
13459 if (NS_WARN_IF(IsAborted())) {
13460 return;
13463 const nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath);
13464 MOZ_ASSERT(databaseFile);
13466 QM_TRY_UNWRAP(
13467 const NotNull<nsCOMPtr<mozIStorageConnection>> connection,
13468 GetStorageConnection(*databaseFile, mDirectoryLockId,
13469 TelemetryIdForFile(databaseFile), mMaybeKey),
13470 QM_VOID);
13472 auto autoClearConnection = MakeScopeExit([&]() {
13473 auto sharedStorageConnectionLocked = mSharedStorageConnection.Lock();
13474 sharedStorageConnectionLocked.ref() = nullptr;
13475 connection->Close();
13479 auto sharedStorageConnectionLocked = mSharedStorageConnection.Lock();
13480 sharedStorageConnectionLocked.ref() = connection;
13483 auto databaseIsOk = false;
13484 QM_TRY(MOZ_TO_RESULT(CheckIntegrity(*connection, &databaseIsOk)), QM_VOID);
13486 QM_TRY(OkIf(databaseIsOk), QM_VOID, [](auto result) {
13487 // XXX Handle this somehow! Probably need to clear all storage for the
13488 // origin. See Bug 1760612.
13489 MOZ_ASSERT(false, "Database corruption detected!");
13492 MaintenanceAction maintenanceAction;
13493 QM_TRY(MOZ_TO_RESULT(DetermineMaintenanceAction(*connection, databaseFile,
13494 &maintenanceAction)),
13495 QM_VOID);
13497 switch (maintenanceAction) {
13498 case MaintenanceAction::Nothing:
13499 break;
13501 case MaintenanceAction::IncrementalVacuum:
13502 IncrementalVacuum(*connection);
13503 break;
13505 case MaintenanceAction::FullVacuum:
13506 FullVacuum(*connection, databaseFile);
13507 break;
13509 default:
13510 MOZ_CRASH("Unknown MaintenanceAction!");
13514 nsresult DatabaseMaintenance::CheckIntegrity(mozIStorageConnection& aConnection,
13515 bool* aOk) {
13516 MOZ_ASSERT(!NS_IsMainThread());
13517 MOZ_ASSERT(!IsOnBackgroundThread());
13518 MOZ_ASSERT(aOk);
13520 if (NS_WARN_IF(IsAborted())) {
13521 return NS_ERROR_ABORT;
13524 // First do a full integrity_check. Scope statements tightly here because
13525 // later operations require zero live statements.
13527 QM_TRY_INSPECT(const auto& stmt,
13528 CreateAndExecuteSingleStepStatement(
13529 aConnection, "PRAGMA integrity_check(1);"_ns));
13531 QM_TRY_INSPECT(const auto& result, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
13532 nsString, *stmt, GetString, 0));
13534 QM_TRY(OkIf(result.EqualsLiteral("ok")), NS_OK,
13535 [&aOk](const auto) { *aOk = false; });
13538 // Now enable and check for foreign key constraints.
13540 QM_TRY_INSPECT(
13541 const int32_t& foreignKeysWereEnabled,
13542 ([&aConnection]() -> Result<int32_t, nsresult> {
13543 QM_TRY_INSPECT(const auto& stmt,
13544 CreateAndExecuteSingleStepStatement(
13545 aConnection, "PRAGMA foreign_keys;"_ns));
13547 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
13548 }()));
13550 if (!foreignKeysWereEnabled) {
13551 QM_TRY(MOZ_TO_RESULT(
13552 aConnection.ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
13555 QM_TRY_INSPECT(const bool& foreignKeyError,
13556 CreateAndExecuteSingleStepStatement<
13557 SingleStepResult::ReturnNullIfNoResult>(
13558 aConnection, "PRAGMA foreign_key_check;"_ns));
13560 if (!foreignKeysWereEnabled) {
13561 QM_TRY(MOZ_TO_RESULT(
13562 aConnection.ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns)));
13565 if (foreignKeyError) {
13566 *aOk = false;
13567 return NS_OK;
13571 *aOk = true;
13572 return NS_OK;
13575 nsresult DatabaseMaintenance::DetermineMaintenanceAction(
13576 mozIStorageConnection& aConnection, nsIFile* aDatabaseFile,
13577 MaintenanceAction* aMaintenanceAction) {
13578 MOZ_ASSERT(!NS_IsMainThread());
13579 MOZ_ASSERT(!IsOnBackgroundThread());
13580 MOZ_ASSERT(aDatabaseFile);
13581 MOZ_ASSERT(aMaintenanceAction);
13583 if (NS_WARN_IF(IsAborted())) {
13584 return NS_ERROR_ABORT;
13587 QM_TRY_INSPECT(const int32_t& schemaVersion,
13588 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
13590 // Don't do anything if the schema version is less than 18; before that
13591 // version no databases had |auto_vacuum == INCREMENTAL| set and we didn't
13592 // track the values needed for the heuristics below.
13593 if (schemaVersion < MakeSchemaVersion(18, 0)) {
13594 *aMaintenanceAction = MaintenanceAction::Nothing;
13595 return NS_OK;
13598 // This method shouldn't make any permanent changes to the database, so make
13599 // sure everything gets rolled back when we leave.
13600 mozStorageTransaction transaction(&aConnection,
13601 /* aCommitOnComplete */ false);
13603 QM_TRY(MOZ_TO_RESULT(transaction.Start()))
13605 // Check to see when we last vacuumed this database.
13606 QM_TRY_INSPECT(const auto& stmt,
13607 CreateAndExecuteSingleStepStatement(
13608 aConnection,
13609 "SELECT last_vacuum_time, last_vacuum_size "
13610 "FROM database;"_ns));
13612 QM_TRY_INSPECT(const PRTime& lastVacuumTime,
13613 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 0));
13615 QM_TRY_INSPECT(const int64_t& lastVacuumSize,
13616 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 1));
13618 NS_ASSERTION(lastVacuumSize > 0,
13619 "Thy last vacuum size shall be greater than zero, less than "
13620 "zero shall thy last vacuum size not be. Zero is right out.");
13622 const PRTime startTime = mMaintenance->StartTime();
13624 // This shouldn't really be possible...
13625 if (NS_WARN_IF(startTime <= lastVacuumTime)) {
13626 *aMaintenanceAction = MaintenanceAction::Nothing;
13627 return NS_OK;
13630 if (startTime - lastVacuumTime < kMinVacuumAge) {
13631 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
13632 return NS_OK;
13635 // It has been more than a week since the database was vacuumed, so gather
13636 // statistics on its usage to see if vacuuming is worthwhile.
13638 // Create a temporary copy of the dbstat table to speed up the queries that
13639 // come later.
13640 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
13641 "CREATE VIRTUAL TABLE __stats__ USING dbstat;"
13642 "CREATE TEMP TABLE __temp_stats__ AS SELECT * FROM __stats__;"_ns)));
13644 { // Calculate the percentage of the database pages that are not in
13645 // contiguous order.
13646 QM_TRY_INSPECT(
13647 const auto& stmt,
13648 CreateAndExecuteSingleStepStatement(
13649 aConnection,
13650 "SELECT SUM(__ts1__.pageno != __ts2__.pageno + 1) * 100.0 / "
13651 "COUNT(*) "
13652 "FROM __temp_stats__ AS __ts1__, __temp_stats__ AS __ts2__ "
13653 "WHERE __ts1__.name = __ts2__.name "
13654 "AND __ts1__.rowid = __ts2__.rowid + 1;"_ns));
13656 QM_TRY_INSPECT(const int32_t& percentUnordered,
13657 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
13659 MOZ_ASSERT(percentUnordered >= 0);
13660 MOZ_ASSERT(percentUnordered <= 100);
13662 if (percentUnordered >= kPercentUnorderedThreshold) {
13663 *aMaintenanceAction = MaintenanceAction::FullVacuum;
13664 return NS_OK;
13668 // Don't try a full vacuum if the file hasn't grown by 10%.
13669 QM_TRY_INSPECT(const int64_t& currentFileSize,
13670 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, GetFileSize));
13672 if (currentFileSize <= lastVacuumSize ||
13673 (((currentFileSize - lastVacuumSize) * 100 / currentFileSize) <
13674 kPercentFileSizeGrowthThreshold)) {
13675 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
13676 return NS_OK;
13679 { // See if there are any free pages that we can reclaim.
13680 QM_TRY_INSPECT(const auto& stmt,
13681 CreateAndExecuteSingleStepStatement(
13682 aConnection, "PRAGMA freelist_count;"_ns));
13684 QM_TRY_INSPECT(const int32_t& freelistCount,
13685 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
13687 MOZ_ASSERT(freelistCount >= 0);
13689 // If we have too many free pages then we should try an incremental
13690 // vacuum. If that causes too much fragmentation then we'll try a full
13691 // vacuum later.
13692 if (freelistCount > kMaxFreelistThreshold) {
13693 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
13694 return NS_OK;
13698 { // Calculate the percentage of unused bytes on pages in the database.
13699 QM_TRY_INSPECT(
13700 const auto& stmt,
13701 CreateAndExecuteSingleStepStatement(
13702 aConnection,
13703 "SELECT SUM(unused) * 100.0 / SUM(pgsize) FROM __temp_stats__;"_ns));
13705 QM_TRY_INSPECT(const int32_t& percentUnused,
13706 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
13708 MOZ_ASSERT(percentUnused >= 0);
13709 MOZ_ASSERT(percentUnused <= 100);
13711 *aMaintenanceAction = percentUnused >= kPercentUnusedThreshold
13712 ? MaintenanceAction::FullVacuum
13713 : MaintenanceAction::IncrementalVacuum;
13716 return NS_OK;
13719 void DatabaseMaintenance::IncrementalVacuum(
13720 mozIStorageConnection& aConnection) {
13721 MOZ_ASSERT(!NS_IsMainThread());
13722 MOZ_ASSERT(!IsOnBackgroundThread());
13724 if (NS_WARN_IF(IsAborted())) {
13725 return;
13728 nsresult rv = aConnection.ExecuteSimpleSQL("PRAGMA incremental_vacuum;"_ns);
13729 if (NS_WARN_IF(NS_FAILED(rv))) {
13730 return;
13734 void DatabaseMaintenance::FullVacuum(mozIStorageConnection& aConnection,
13735 nsIFile* aDatabaseFile) {
13736 MOZ_ASSERT(!NS_IsMainThread());
13737 MOZ_ASSERT(!IsOnBackgroundThread());
13738 MOZ_ASSERT(aDatabaseFile);
13740 if (NS_WARN_IF(IsAborted())) {
13741 return;
13744 QM_WARNONLY_TRY(([&]() -> Result<Ok, nsresult> {
13745 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("VACUUM;"_ns)));
13747 const PRTime vacuumTime = PR_Now();
13748 MOZ_ASSERT(vacuumTime > 0);
13750 QM_TRY_INSPECT(const int64_t& fileSize,
13751 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, GetFileSize));
13753 MOZ_ASSERT(fileSize > 0);
13755 // The parameter names are not used, parameters are bound by index only
13756 // locally in the same function.
13757 QM_TRY_INSPECT(const auto& stmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
13758 nsCOMPtr<mozIStorageStatement>,
13759 aConnection, CreateStatement,
13760 "UPDATE database "
13761 "SET last_vacuum_time = :time"
13762 ", last_vacuum_size = :size;"_ns));
13764 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByIndex(0, vacuumTime)));
13766 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByIndex(1, fileSize)));
13768 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
13769 return Ok{};
13770 }()));
13773 void DatabaseMaintenance::RunOnOwningThread() {
13774 AssertIsOnBackgroundThread();
13776 mDirectoryLock = nullptr;
13778 if (mCompleteCallback) {
13779 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget()));
13782 mMaintenance->UnregisterDatabaseMaintenance(this);
13785 void DatabaseMaintenance::RunOnConnectionThread() {
13786 MOZ_ASSERT(!NS_IsMainThread());
13787 MOZ_ASSERT(!IsOnBackgroundThread());
13789 PerformMaintenanceOnDatabase();
13791 MOZ_ALWAYS_SUCCEEDS(
13792 mMaintenance->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
13795 NS_IMETHODIMP
13796 DatabaseMaintenance::Run() {
13797 if (IsOnBackgroundThread()) {
13798 RunOnOwningThread();
13799 } else {
13800 RunOnConnectionThread();
13803 return NS_OK;
13806 /*******************************************************************************
13807 * Local class implementations
13808 ******************************************************************************/
13810 // static
13811 nsAutoCString DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
13812 const Maybe<SerializedKeyRange>& aOptionalKeyRange,
13813 const nsACString& aKeyColumnName) {
13814 return aOptionalKeyRange.isSome()
13815 ? GetBindingClauseForKeyRange(aOptionalKeyRange.ref(),
13816 aKeyColumnName)
13817 : nsAutoCString{};
13820 // static
13821 nsAutoCString DatabaseOperationBase::GetBindingClauseForKeyRange(
13822 const SerializedKeyRange& aKeyRange, const nsACString& aKeyColumnName) {
13823 MOZ_ASSERT(!IsOnBackgroundThread());
13824 MOZ_ASSERT(!aKeyColumnName.IsEmpty());
13826 constexpr auto andStr = " AND "_ns;
13827 constexpr auto spacecolon = " :"_ns;
13829 nsAutoCString result;
13830 if (aKeyRange.isOnly()) {
13831 // Both keys equal.
13832 result =
13833 andStr + aKeyColumnName + " ="_ns + spacecolon + kStmtParamNameLowerKey;
13834 } else {
13835 if (!aKeyRange.lower().IsUnset()) {
13836 // Lower key is set.
13837 result.Append(andStr + aKeyColumnName);
13838 result.AppendLiteral(" >");
13839 if (!aKeyRange.lowerOpen()) {
13840 result.AppendLiteral("=");
13842 result.Append(spacecolon + kStmtParamNameLowerKey);
13845 if (!aKeyRange.upper().IsUnset()) {
13846 // Upper key is set.
13847 result.Append(andStr + aKeyColumnName);
13848 result.AppendLiteral(" <");
13849 if (!aKeyRange.upperOpen()) {
13850 result.AppendLiteral("=");
13852 result.Append(spacecolon + kStmtParamNameUpperKey);
13856 MOZ_ASSERT(!result.IsEmpty());
13858 return result;
13861 // static
13862 uint64_t DatabaseOperationBase::ReinterpretDoubleAsUInt64(double aDouble) {
13863 // This is a duplicate of the js engine's byte munging in StructuredClone.cpp
13864 return BitwiseCast<uint64_t>(aDouble);
13867 // static
13868 template <typename KeyTransformation>
13869 nsresult DatabaseOperationBase::MaybeBindKeyToStatement(
13870 const Key& aKey, mozIStorageStatement* const aStatement,
13871 const nsACString& aParameterName,
13872 const KeyTransformation& aKeyTransformation) {
13873 MOZ_ASSERT(!IsOnBackgroundThread());
13874 MOZ_ASSERT(aStatement);
13876 if (!aKey.IsUnset()) {
13877 // XXX This case distinction could be avoided if QM_TRY_INSPECT would also
13878 // work with a function not returning a Result<V, E> but simply a V (which
13879 // is const Key& here) and then assuming it is always a success. Or the
13880 // transformation could be changed to return Result<const V&, void> but I
13881 // don't think that Result supports that at the moment.
13882 if constexpr (std::is_reference_v<
13883 std::invoke_result_t<KeyTransformation, Key>>) {
13884 QM_TRY(MOZ_TO_RESULT(aKeyTransformation(aKey).BindToStatement(
13885 aStatement, aParameterName)));
13886 } else {
13887 QM_TRY_INSPECT(const auto& transformedKey, aKeyTransformation(aKey));
13888 QM_TRY(MOZ_TO_RESULT(
13889 transformedKey.BindToStatement(aStatement, aParameterName)));
13893 return NS_OK;
13896 // static
13897 template <typename KeyTransformation>
13898 nsresult DatabaseOperationBase::BindTransformedKeyRangeToStatement(
13899 const SerializedKeyRange& aKeyRange, mozIStorageStatement* const aStatement,
13900 const KeyTransformation& aKeyTransformation) {
13901 MOZ_ASSERT(!IsOnBackgroundThread());
13902 MOZ_ASSERT(aStatement);
13904 QM_TRY(MOZ_TO_RESULT(MaybeBindKeyToStatement(aKeyRange.lower(), aStatement,
13905 kStmtParamNameLowerKey,
13906 aKeyTransformation)));
13908 if (aKeyRange.isOnly()) {
13909 return NS_OK;
13912 QM_TRY(MOZ_TO_RESULT(MaybeBindKeyToStatement(aKeyRange.upper(), aStatement,
13913 kStmtParamNameUpperKey,
13914 aKeyTransformation)));
13916 return NS_OK;
13919 // static
13920 nsresult DatabaseOperationBase::BindKeyRangeToStatement(
13921 const SerializedKeyRange& aKeyRange,
13922 mozIStorageStatement* const aStatement) {
13923 return BindTransformedKeyRangeToStatement(
13924 aKeyRange, aStatement, [](const Key& key) -> const auto& { return key; });
13927 // static
13928 nsresult DatabaseOperationBase::BindKeyRangeToStatement(
13929 const SerializedKeyRange& aKeyRange, mozIStorageStatement* const aStatement,
13930 const nsCString& aLocale) {
13931 MOZ_ASSERT(!aLocale.IsEmpty());
13933 return BindTransformedKeyRangeToStatement(
13934 aKeyRange, aStatement,
13935 [&aLocale](const Key& key) { return key.ToLocaleAwareKey(aLocale); });
13938 // static
13939 void CommonOpenOpHelperBase::AppendConditionClause(
13940 const nsACString& aColumnName, const nsACString& aStatementParameterName,
13941 bool aLessThan, bool aEquals, nsCString& aResult) {
13942 aResult += " AND "_ns + aColumnName + " "_ns;
13944 if (aLessThan) {
13945 aResult.Append('<');
13946 } else {
13947 aResult.Append('>');
13950 if (aEquals) {
13951 aResult.Append('=');
13954 aResult += " :"_ns + aStatementParameterName;
13957 // static
13958 Result<IndexDataValuesAutoArray, nsresult>
13959 DatabaseOperationBase::IndexDataValuesFromUpdateInfos(
13960 const nsTArray<IndexUpdateInfo>& aUpdateInfos,
13961 const UniqueIndexTable& aUniqueIndexTable) {
13962 MOZ_ASSERT_IF(!aUpdateInfos.IsEmpty(), aUniqueIndexTable.Count());
13964 AUTO_PROFILER_LABEL("DatabaseOperationBase::IndexDataValuesFromUpdateInfos",
13965 DOM);
13967 // XXX We could use TransformIntoNewArray here if it allowed to specify that
13968 // an AutoArray should be created.
13969 IndexDataValuesAutoArray indexValues;
13971 if (NS_WARN_IF(!indexValues.SetCapacity(aUpdateInfos.Length(), fallible))) {
13972 IDB_REPORT_INTERNAL_ERR();
13973 return Err(NS_ERROR_OUT_OF_MEMORY);
13976 std::transform(aUpdateInfos.cbegin(), aUpdateInfos.cend(),
13977 MakeBackInserter(indexValues),
13978 [&aUniqueIndexTable](const IndexUpdateInfo& updateInfo) {
13979 const IndexOrObjectStoreId& indexId = updateInfo.indexId();
13981 bool unique = false;
13982 MOZ_ALWAYS_TRUE(aUniqueIndexTable.Get(indexId, &unique));
13984 return IndexDataValue{indexId, unique, updateInfo.value(),
13985 updateInfo.localizedValue()};
13987 indexValues.Sort();
13989 return indexValues;
13992 // static
13993 nsresult DatabaseOperationBase::InsertIndexTableRows(
13994 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId,
13995 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues) {
13996 MOZ_ASSERT(aConnection);
13997 aConnection->AssertIsOnConnectionThread();
13998 MOZ_ASSERT(!aObjectStoreKey.IsUnset());
14000 AUTO_PROFILER_LABEL("DatabaseOperationBase::InsertIndexTableRows", DOM);
14002 const uint32_t count = aIndexValues.Length();
14003 if (!count) {
14004 return NS_OK;
14007 auto insertUniqueStmt = DatabaseConnection::LazyStatement{
14008 *aConnection,
14009 "INSERT INTO unique_index_data "
14010 "(index_id, value, object_store_id, "
14011 "object_data_key, value_locale) "
14012 "VALUES (:"_ns +
14013 kStmtParamNameIndexId + ", :"_ns + kStmtParamNameValue + ", :"_ns +
14014 kStmtParamNameObjectStoreId + ", :"_ns + kStmtParamNameObjectDataKey +
14015 ", :"_ns + kStmtParamNameValueLocale + ");"_ns};
14016 auto insertStmt = DatabaseConnection::LazyStatement{
14017 *aConnection,
14018 "INSERT OR IGNORE INTO index_data "
14019 "(index_id, value, object_data_key, "
14020 "object_store_id, value_locale) "
14021 "VALUES (:"_ns +
14022 kStmtParamNameIndexId + ", :"_ns + kStmtParamNameValue + ", :"_ns +
14023 kStmtParamNameObjectDataKey + ", :"_ns + kStmtParamNameObjectStoreId +
14024 ", :"_ns + kStmtParamNameValueLocale + ");"_ns};
14026 for (uint32_t index = 0; index < count; index++) {
14027 const IndexDataValue& info = aIndexValues[index];
14029 auto& stmt = info.mUnique ? insertUniqueStmt : insertStmt;
14031 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow());
14033 QM_TRY(MOZ_TO_RESULT(
14034 borrowedStmt->BindInt64ByName(kStmtParamNameIndexId, info.mIndexId)));
14035 QM_TRY(MOZ_TO_RESULT(
14036 info.mPosition.BindToStatement(&*borrowedStmt, kStmtParamNameValue)));
14037 QM_TRY(MOZ_TO_RESULT(info.mLocaleAwarePosition.BindToStatement(
14038 &*borrowedStmt, kStmtParamNameValueLocale)));
14039 QM_TRY(MOZ_TO_RESULT(borrowedStmt->BindInt64ByName(
14040 kStmtParamNameObjectStoreId, aObjectStoreId)));
14041 QM_TRY(MOZ_TO_RESULT(aObjectStoreKey.BindToStatement(
14042 &*borrowedStmt, kStmtParamNameObjectDataKey)));
14044 // QM_OR_ELSE_WARN_IF is not used here since we just want to log the
14045 // collision and not spam the reports.
14046 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
14047 // Expression.
14048 MOZ_TO_RESULT(borrowedStmt->Execute()),
14049 // Predicate.
14050 ([&info, index, &aIndexValues](nsresult rv) {
14051 if (rv == NS_ERROR_STORAGE_CONSTRAINT && info.mUnique) {
14052 // If we're inserting multiple entries for the same unique
14053 // index, then we might have failed to insert due to
14054 // colliding with another entry for the same index in which
14055 // case we should ignore it.
14056 for (int32_t index2 = int32_t(index) - 1;
14057 index2 >= 0 && aIndexValues[index2].mIndexId == info.mIndexId;
14058 --index2) {
14059 if (info.mPosition == aIndexValues[index2].mPosition) {
14060 // We found a key with the same value for the same
14061 // index. So we must have had a collision with a value
14062 // we just inserted.
14063 return true;
14068 return false;
14070 // Fallback.
14071 ErrToDefaultOk<>));
14074 return NS_OK;
14077 // static
14078 nsresult DatabaseOperationBase::DeleteIndexDataTableRows(
14079 DatabaseConnection* aConnection, const Key& aObjectStoreKey,
14080 const nsTArray<IndexDataValue>& aIndexValues) {
14081 MOZ_ASSERT(aConnection);
14082 aConnection->AssertIsOnConnectionThread();
14083 MOZ_ASSERT(!aObjectStoreKey.IsUnset());
14085 AUTO_PROFILER_LABEL("DatabaseOperationBase::DeleteIndexDataTableRows", DOM);
14087 const uint32_t count = aIndexValues.Length();
14088 if (!count) {
14089 return NS_OK;
14092 auto deleteUniqueStmt = DatabaseConnection::LazyStatement{
14093 *aConnection, "DELETE FROM unique_index_data WHERE index_id = :"_ns +
14094 kStmtParamNameIndexId + " AND value = :"_ns +
14095 kStmtParamNameValue + ";"_ns};
14096 auto deleteStmt = DatabaseConnection::LazyStatement{
14097 *aConnection, "DELETE FROM index_data WHERE index_id = :"_ns +
14098 kStmtParamNameIndexId + " AND value = :"_ns +
14099 kStmtParamNameValue + " AND object_data_key = :"_ns +
14100 kStmtParamNameObjectDataKey + ";"_ns};
14102 for (uint32_t index = 0; index < count; index++) {
14103 const IndexDataValue& indexValue = aIndexValues[index];
14105 auto& stmt = indexValue.mUnique ? deleteUniqueStmt : deleteStmt;
14107 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow());
14109 QM_TRY(MOZ_TO_RESULT(borrowedStmt->BindInt64ByName(kStmtParamNameIndexId,
14110 indexValue.mIndexId)));
14112 QM_TRY(MOZ_TO_RESULT(indexValue.mPosition.BindToStatement(
14113 &*borrowedStmt, kStmtParamNameValue)));
14115 if (!indexValue.mUnique) {
14116 QM_TRY(MOZ_TO_RESULT(aObjectStoreKey.BindToStatement(
14117 &*borrowedStmt, kStmtParamNameObjectDataKey)));
14120 QM_TRY(MOZ_TO_RESULT(borrowedStmt->Execute()));
14123 return NS_OK;
14126 // static
14127 nsresult DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes(
14128 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId,
14129 const Maybe<SerializedKeyRange>& aKeyRange) {
14130 MOZ_ASSERT(aConnection);
14131 aConnection->AssertIsOnConnectionThread();
14132 MOZ_ASSERT(aObjectStoreId);
14134 #ifdef DEBUG
14136 QM_TRY_INSPECT(const bool& hasIndexes,
14137 ObjectStoreHasIndexes(*aConnection, aObjectStoreId),
14138 QM_PROPAGATE, [](const auto&) { MOZ_ASSERT(false); });
14139 MOZ_ASSERT(hasIndexes,
14140 "Don't use this slow method if there are no indexes!");
14142 #endif
14144 AUTO_PROFILER_LABEL(
14145 "DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes", DOM);
14147 const bool singleRowOnly = aKeyRange.isSome() && aKeyRange.ref().isOnly();
14149 const auto keyRangeClause =
14150 MaybeGetBindingClauseForKeyRange(aKeyRange, kColumnNameKey);
14152 Key objectStoreKey;
14153 QM_TRY_INSPECT(
14154 const auto& selectStmt,
14155 ([singleRowOnly, &aConnection, &objectStoreKey, &aKeyRange,
14156 &keyRangeClause]()
14157 -> Result<CachingDatabaseConnection::BorrowedStatement, nsresult> {
14158 if (singleRowOnly) {
14159 QM_TRY_UNWRAP(auto selectStmt,
14160 aConnection->BorrowCachedStatement(
14161 "SELECT index_data_values "
14162 "FROM object_data "
14163 "WHERE object_store_id = :"_ns +
14164 kStmtParamNameObjectStoreId + " AND key = :"_ns +
14165 kStmtParamNameKey + ";"_ns));
14167 objectStoreKey = aKeyRange.ref().lower();
14169 QM_TRY(MOZ_TO_RESULT(
14170 objectStoreKey.BindToStatement(&*selectStmt, kStmtParamNameKey)));
14172 return selectStmt;
14175 QM_TRY_UNWRAP(
14176 auto selectStmt,
14177 aConnection->BorrowCachedStatement(
14178 "SELECT index_data_values, "_ns + kColumnNameKey +
14179 " FROM object_data WHERE object_store_id = :"_ns +
14180 kStmtParamNameObjectStoreId + keyRangeClause + ";"_ns));
14182 if (aKeyRange.isSome()) {
14183 QM_TRY(MOZ_TO_RESULT(
14184 BindKeyRangeToStatement(aKeyRange.ref(), &*selectStmt)));
14187 return selectStmt;
14188 }()));
14190 QM_TRY(MOZ_TO_RESULT(selectStmt->BindInt64ByName(kStmtParamNameObjectStoreId,
14191 aObjectStoreId)));
14193 DebugOnly<uint32_t> resultCountDEBUG = 0;
14195 QM_TRY(CollectWhileHasResult(
14196 *selectStmt,
14197 [singleRowOnly, &objectStoreKey, &aConnection, &resultCountDEBUG,
14198 indexValues = IndexDataValuesAutoArray{}](
14199 auto& selectStmt) mutable -> Result<Ok, nsresult> {
14200 if (!singleRowOnly) {
14201 QM_TRY(
14202 MOZ_TO_RESULT(objectStoreKey.SetFromStatement(&selectStmt, 1)));
14204 indexValues.ClearAndRetainStorage();
14207 QM_TRY(MOZ_TO_RESULT(
14208 ReadCompressedIndexDataValues(selectStmt, 0, indexValues)));
14209 QM_TRY(MOZ_TO_RESULT(DeleteIndexDataTableRows(
14210 aConnection, objectStoreKey, indexValues)));
14212 resultCountDEBUG++;
14214 return Ok{};
14215 }));
14217 MOZ_ASSERT_IF(singleRowOnly, resultCountDEBUG <= 1);
14219 QM_TRY_UNWRAP(
14220 auto deleteManyStmt,
14221 aConnection->BorrowCachedStatement(
14222 "DELETE FROM object_data "_ns + "WHERE object_store_id = :"_ns +
14223 kStmtParamNameObjectStoreId + keyRangeClause + ";"_ns));
14225 QM_TRY(MOZ_TO_RESULT(deleteManyStmt->BindInt64ByName(
14226 kStmtParamNameObjectStoreId, aObjectStoreId)));
14228 if (aKeyRange.isSome()) {
14229 QM_TRY(MOZ_TO_RESULT(
14230 BindKeyRangeToStatement(aKeyRange.ref(), &*deleteManyStmt)));
14233 QM_TRY(MOZ_TO_RESULT(deleteManyStmt->Execute()));
14235 return NS_OK;
14238 // static
14239 nsresult DatabaseOperationBase::UpdateIndexValues(
14240 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId,
14241 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues) {
14242 MOZ_ASSERT(aConnection);
14243 aConnection->AssertIsOnConnectionThread();
14244 MOZ_ASSERT(!aObjectStoreKey.IsUnset());
14246 AUTO_PROFILER_LABEL("DatabaseOperationBase::UpdateIndexValues", DOM);
14248 QM_TRY_UNWRAP((auto [indexDataValues, indexDataValuesLength]),
14249 MakeCompressedIndexDataValues(aIndexValues));
14251 MOZ_ASSERT(!indexDataValuesLength == !(indexDataValues.get()));
14253 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
14254 "UPDATE object_data SET index_data_values = :"_ns +
14255 kStmtParamNameIndexDataValues + " WHERE object_store_id = :"_ns +
14256 kStmtParamNameObjectStoreId + " AND key = :"_ns + kStmtParamNameKey +
14257 ";"_ns,
14258 [&indexDataValues = indexDataValues,
14259 indexDataValuesLength = indexDataValuesLength, aObjectStoreId,
14260 &aObjectStoreKey](
14261 mozIStorageStatement& updateStmt) -> Result<Ok, nsresult> {
14262 QM_TRY(MOZ_TO_RESULT(
14263 indexDataValues
14264 ? updateStmt.BindAdoptedBlobByName(
14265 kStmtParamNameIndexDataValues, indexDataValues.release(),
14266 indexDataValuesLength)
14267 : updateStmt.BindNullByName(kStmtParamNameIndexDataValues)));
14269 QM_TRY(MOZ_TO_RESULT(updateStmt.BindInt64ByName(
14270 kStmtParamNameObjectStoreId, aObjectStoreId)));
14272 QM_TRY(MOZ_TO_RESULT(
14273 aObjectStoreKey.BindToStatement(&updateStmt, kStmtParamNameKey)));
14275 return Ok{};
14276 })));
14278 return NS_OK;
14281 // static
14282 Result<bool, nsresult> DatabaseOperationBase::ObjectStoreHasIndexes(
14283 DatabaseConnection& aConnection,
14284 const IndexOrObjectStoreId aObjectStoreId) {
14285 aConnection.AssertIsOnConnectionThread();
14286 MOZ_ASSERT(aObjectStoreId);
14288 QM_TRY_RETURN(aConnection
14289 .BorrowAndExecuteSingleStepStatement(
14290 "SELECT id "
14291 "FROM object_store_index "
14292 "WHERE object_store_id = :"_ns +
14293 kStmtParamNameObjectStoreId + kOpenLimit + "1;"_ns,
14294 [aObjectStoreId](auto& stmt) -> Result<Ok, nsresult> {
14295 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(
14296 kStmtParamNameObjectStoreId, aObjectStoreId)));
14297 return Ok{};
14299 .map(IsSome));
14302 NS_IMPL_ISUPPORTS_INHERITED(DatabaseOperationBase, Runnable,
14303 mozIStorageProgressHandler)
14305 NS_IMETHODIMP
14306 DatabaseOperationBase::OnProgress(mozIStorageConnection* aConnection,
14307 bool* _retval) {
14308 MOZ_ASSERT(!IsOnBackgroundThread());
14309 MOZ_ASSERT(_retval);
14311 // This is intentionally racy.
14312 *_retval = QuotaClient::IsShuttingDownOnNonBackgroundThread() ||
14313 !OperationMayProceed();
14314 return NS_OK;
14317 DatabaseOperationBase::AutoSetProgressHandler::AutoSetProgressHandler()
14318 : mConnection(Nothing())
14319 #ifdef DEBUG
14321 mDEBUGDatabaseOp(nullptr)
14322 #endif
14324 MOZ_ASSERT(!IsOnBackgroundThread());
14327 DatabaseOperationBase::AutoSetProgressHandler::~AutoSetProgressHandler() {
14328 MOZ_ASSERT(!IsOnBackgroundThread());
14330 if (mConnection) {
14331 Unregister();
14335 nsresult DatabaseOperationBase::AutoSetProgressHandler::Register(
14336 mozIStorageConnection& aConnection, DatabaseOperationBase* aDatabaseOp) {
14337 MOZ_ASSERT(!IsOnBackgroundThread());
14338 MOZ_ASSERT(aDatabaseOp);
14339 MOZ_ASSERT(!mConnection);
14341 QM_TRY_UNWRAP(
14342 const DebugOnly oldProgressHandler,
14343 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
14344 nsCOMPtr<mozIStorageProgressHandler>, aConnection, SetProgressHandler,
14345 kStorageProgressGranularity, aDatabaseOp));
14347 MOZ_ASSERT(!oldProgressHandler.inspect());
14349 mConnection = SomeRef(aConnection);
14350 #ifdef DEBUG
14351 mDEBUGDatabaseOp = aDatabaseOp;
14352 #endif
14354 return NS_OK;
14357 void DatabaseOperationBase::AutoSetProgressHandler::Unregister() {
14358 MOZ_ASSERT(!IsOnBackgroundThread());
14359 MOZ_ASSERT(mConnection);
14361 nsCOMPtr<mozIStorageProgressHandler> oldHandler;
14362 MOZ_ALWAYS_SUCCEEDS(
14363 mConnection->RemoveProgressHandler(getter_AddRefs(oldHandler)));
14364 MOZ_ASSERT(oldHandler == mDEBUGDatabaseOp);
14366 mConnection = Nothing();
14369 FactoryOp::FactoryOp(SafeRefPtr<Factory> aFactory,
14370 const Maybe<ContentParentId>& aContentParentId,
14371 const PersistenceType aPersistenceType,
14372 const PrincipalInfo& aPrincipalInfo,
14373 const Maybe<nsString>& aDatabaseName, bool aDeleting)
14374 : DatabaseOperationBase(aFactory->GetLoggingInfo()->Id(),
14375 aFactory->GetLoggingInfo()->NextRequestSN()),
14376 mFactory(std::move(aFactory)),
14377 mContentParentId(aContentParentId),
14378 mPrincipalInfo(aPrincipalInfo),
14379 mDatabaseName(aDatabaseName),
14380 mDirectoryLockId(-1),
14381 mPersistenceType(aPersistenceType),
14382 mState(State::Initial),
14383 mWaitingForPermissionRetry(false),
14384 mEnforcingQuota(true),
14385 mDeleting(aDeleting) {
14386 AssertIsOnBackgroundThread();
14387 MOZ_ASSERT(mFactory);
14388 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
14391 void FactoryOp::NoteDatabaseBlocked(Database* aDatabase) {
14392 AssertIsOnOwningThread();
14393 MOZ_ASSERT(aDatabase);
14394 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
14395 MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
14396 MOZ_ASSERT(mMaybeBlockedDatabases.Contains(aDatabase));
14398 // Only send the blocked event if all databases have reported back. If the
14399 // database was closed then it will have been removed from the array.
14400 // Otherwise if it was blocked its |mBlocked| flag will be true.
14401 bool sendBlockedEvent = true;
14403 for (auto& info : mMaybeBlockedDatabases) {
14404 if (info == aDatabase) {
14405 // This database was blocked, mark accordingly.
14406 info.mBlocked = true;
14407 } else if (!info.mBlocked) {
14408 // A database has not yet reported back yet, don't send the event yet.
14409 sendBlockedEvent = false;
14413 if (sendBlockedEvent) {
14414 SendBlockedNotification();
14418 void FactoryOp::NoteDatabaseClosed(Database* const aDatabase) {
14419 AssertIsOnOwningThread();
14420 MOZ_ASSERT(aDatabase);
14421 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
14422 MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
14423 MOZ_ASSERT(mMaybeBlockedDatabases.Contains(aDatabase));
14425 mMaybeBlockedDatabases.RemoveElement(aDatabase);
14427 if (!mMaybeBlockedDatabases.IsEmpty()) {
14428 return;
14431 DatabaseActorInfo* info;
14432 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info));
14433 MOZ_ASSERT(info->mWaitingFactoryOp == this);
14435 if (AreActorsAlive()) {
14436 // The IPDL strong reference has not yet been released, so we can clear
14437 // mWaitingFactoryOp immediately.
14438 info->mWaitingFactoryOp = nullptr;
14440 WaitForTransactions();
14441 return;
14444 // The IPDL strong reference has been released, mWaitingFactoryOp holds the
14445 // last strong reference to us, so we need to move it to a stack variable
14446 // instead of clearing it immediately (We could clear it immediately if only
14447 // the other actor is destroyed, but we don't need to optimize for that, and
14448 // move it anyway).
14449 const RefPtr<FactoryOp> waitingFactoryOp = std::move(info->mWaitingFactoryOp);
14451 IDB_REPORT_INTERNAL_ERR();
14452 SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
14454 // We hold a strong ref in waitingFactoryOp, so it's safe to call Run()
14455 // directly.
14457 mState = State::SendingResults;
14458 MOZ_ALWAYS_SUCCEEDS(Run());
14461 void FactoryOp::StringifyState(nsACString& aResult) const {
14462 AssertIsOnOwningThread();
14464 switch (mState) {
14465 case State::Initial:
14466 aResult.AppendLiteral("Initial");
14467 return;
14469 case State::DirectoryOpenPending:
14470 aResult.AppendLiteral("DirectoryOpenPending");
14471 return;
14473 case State::DirectoryWorkOpen:
14474 aResult.AppendLiteral("DirectoryWorkOpen");
14475 return;
14477 case State::DirectoryWorkDone:
14478 aResult.AppendLiteral("DirectoryWorkDone");
14479 return;
14481 case State::DatabaseOpenPending:
14482 aResult.AppendLiteral("DatabaseOpenPending");
14483 return;
14485 case State::DatabaseWorkOpen:
14486 aResult.AppendLiteral("DatabaseWorkOpen");
14487 return;
14489 case State::BeginVersionChange:
14490 aResult.AppendLiteral("BeginVersionChange");
14491 return;
14493 case State::WaitingForOtherDatabasesToClose:
14494 aResult.AppendLiteral("WaitingForOtherDatabasesToClose");
14495 return;
14497 case State::WaitingForTransactionsToComplete:
14498 aResult.AppendLiteral("WaitingForTransactionsToComplete");
14499 return;
14501 case State::DatabaseWorkVersionChange:
14502 aResult.AppendLiteral("DatabaseWorkVersionChange");
14503 return;
14505 case State::SendingResults:
14506 aResult.AppendLiteral("SendingResults");
14507 return;
14509 case State::Completed:
14510 aResult.AppendLiteral("Completed");
14511 return;
14513 default:
14514 MOZ_CRASH("Bad state!");
14518 void FactoryOp::Stringify(nsACString& aResult) const {
14519 AssertIsOnOwningThread();
14521 aResult.AppendLiteral("PersistenceType:");
14522 aResult.Append(PersistenceTypeToString(mPersistenceType));
14523 aResult.Append(kQuotaGenericDelimiter);
14525 aResult.AppendLiteral("Origin:");
14526 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin));
14527 aResult.Append(kQuotaGenericDelimiter);
14529 aResult.AppendLiteral("State:");
14530 StringifyState(aResult);
14533 nsresult FactoryOp::Open() {
14534 AssertIsOnOwningThread();
14535 MOZ_ASSERT(mState == State::Initial);
14536 MOZ_ASSERT(mOriginMetadata.mOrigin.IsEmpty());
14537 MOZ_ASSERT(!mDirectoryLock);
14539 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
14540 IsActorDestroyed()) {
14541 IDB_REPORT_INTERNAL_ERR();
14542 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
14545 QM_TRY(QuotaManager::EnsureCreated());
14547 QuotaManager* const quotaManager = QuotaManager::Get();
14548 MOZ_ASSERT(quotaManager);
14550 QM_TRY_UNWRAP(
14551 auto principalMetadata,
14552 quotaManager->GetInfoFromValidatedPrincipalInfo(mPrincipalInfo));
14554 mOriginMetadata = {std::move(principalMetadata), mPersistenceType};
14556 if (mPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
14557 MOZ_ASSERT(mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
14559 mEnforcingQuota = false;
14560 } else if (mPrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo) {
14561 const ContentPrincipalInfo& contentPrincipalInfo =
14562 mPrincipalInfo.get_ContentPrincipalInfo();
14564 MOZ_ASSERT_IF(
14565 QuotaManager::IsOriginInternal(contentPrincipalInfo.originNoSuffix()),
14566 mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
14568 mEnforcingQuota = mPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
14570 if (mOriginMetadata.mIsPrivate) {
14571 if (StaticPrefs::dom_indexedDB_privateBrowsing_enabled()) {
14572 // Explicitly disallow moz-extension urls from using the encrypted
14573 // indexedDB storage mode when the caller is an extension (see Bug
14574 // 1841806).
14575 if (StringBeginsWith(contentPrincipalInfo.originNoSuffix(),
14576 "moz-extension:"_ns)) {
14577 return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
14580 mInPrivateBrowsing.Flip();
14581 } else {
14582 return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
14585 } else {
14586 MOZ_ASSERT(false);
14589 if (mDatabaseName.isSome()) {
14590 nsCString databaseId;
14592 QuotaManager::GetStorageId(mPersistenceType, mOriginMetadata.mOrigin,
14593 Client::IDB, databaseId);
14595 databaseId.Append('*');
14596 databaseId.Append(NS_ConvertUTF16toUTF8(mDatabaseName.ref()));
14598 mDatabaseId = Some(std::move(databaseId));
14600 // Need to get database file path before opening the directory.
14601 // XXX: For what reason?
14602 QM_TRY_UNWRAP(
14603 auto databaseFilePath,
14604 ([this, quotaManager]() -> mozilla::Result<nsString, nsresult> {
14605 QM_TRY_INSPECT(const auto& dbFile,
14606 quotaManager->GetOriginDirectory(mOriginMetadata));
14608 QM_TRY(MOZ_TO_RESULT(dbFile->Append(
14609 NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME))));
14611 QM_TRY(MOZ_TO_RESULT(dbFile->Append(
14612 GetDatabaseFilenameBase(mDatabaseName.ref(),
14613 mOriginMetadata.mIsPrivate) +
14614 kSQLiteSuffix)));
14616 QM_TRY_RETURN(
14617 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath));
14618 }()));
14620 mDatabaseFilePath = Some(std::move(databaseFilePath));
14623 // Open directory
14624 mState = State::DirectoryOpenPending;
14626 quotaManager->OpenClientDirectory({mOriginMetadata, Client::IDB})
14627 ->Then(
14628 GetCurrentSerialEventTarget(), __func__,
14629 [self = RefPtr(this)](
14630 const ClientDirectoryLockPromise::ResolveOrRejectValue& aValue) {
14631 if (aValue.IsResolve()) {
14632 self->DirectoryLockAcquired(aValue.ResolveValue());
14633 } else {
14634 self->DirectoryLockFailed();
14638 return NS_OK;
14641 nsresult FactoryOp::DirectoryOpen() {
14642 AssertIsOnOwningThread();
14643 MOZ_ASSERT(mState == State::DirectoryOpenPending);
14644 MOZ_ASSERT(mDirectoryLock);
14646 if (mDatabaseName.isNothing()) {
14647 QuotaManager* const quotaManager = QuotaManager::Get();
14648 MOZ_ASSERT(quotaManager);
14650 // Must set this before dispatching otherwise we will race with the IO
14651 // thread.
14652 mState = State::DirectoryWorkOpen;
14654 QM_TRY(MOZ_TO_RESULT(
14655 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
14656 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA);
14658 return NS_OK;
14661 mState = State::DirectoryWorkDone;
14662 MOZ_ALWAYS_SUCCEEDS(Run());
14664 return NS_OK;
14667 nsresult FactoryOp::DirectoryWorkDone() {
14668 AssertIsOnOwningThread();
14669 MOZ_ASSERT(mState == State::DirectoryWorkDone);
14670 MOZ_ASSERT(mDirectoryLock);
14671 MOZ_ASSERT(gFactoryOps);
14673 // See if this FactoryOp needs to wait.
14674 const bool blocked = [&self = *this] {
14675 bool foundThis = false;
14676 bool blocked = false;
14678 for (const auto& existingOp : Reversed(*gFactoryOps)) {
14679 if (existingOp == &self) {
14680 foundThis = true;
14681 continue;
14684 if (foundThis && self.MustWaitFor(*existingOp)) {
14685 existingOp->AddBlockingOp(self);
14686 self.AddBlockedOnOp(*existingOp);
14687 blocked = true;
14691 return blocked;
14692 }() || [&self = *this] {
14693 QuotaClient* quotaClient = QuotaClient::GetInstance();
14694 MOZ_ASSERT(quotaClient);
14696 if (RefPtr<Maintenance> currentMaintenance =
14697 quotaClient->GetCurrentMaintenance()) {
14698 if (self.mDatabaseName.isSome()) {
14699 if (RefPtr<DatabaseMaintenance> databaseMaintenance =
14700 currentMaintenance->GetDatabaseMaintenance(
14701 self.mDatabaseFilePath.ref())) {
14702 databaseMaintenance->WaitForCompletion(&self);
14703 return true;
14705 } else if (currentMaintenance->HasDatabaseMaintenances()) {
14706 currentMaintenance->WaitForCompletion(&self);
14707 return true;
14711 return false;
14712 }();
14714 mState = State::DatabaseOpenPending;
14715 if (!blocked) {
14716 QM_TRY(MOZ_TO_RESULT(DatabaseOpen()));
14719 return NS_OK;
14722 nsresult FactoryOp::SendToIOThread() {
14723 AssertIsOnOwningThread();
14724 MOZ_ASSERT(mState == State::DatabaseOpenPending);
14726 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
14727 !OperationMayProceed()) {
14728 IDB_REPORT_INTERNAL_ERR();
14729 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
14732 QuotaManager* const quotaManager = QuotaManager::Get();
14733 MOZ_ASSERT(quotaManager);
14735 // Must set this before dispatching otherwise we will race with the IO thread.
14736 mState = State::DatabaseWorkOpen;
14738 QM_TRY(MOZ_TO_RESULT(
14739 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
14740 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA);
14742 return NS_OK;
14745 void FactoryOp::WaitForTransactions() {
14746 AssertIsOnOwningThread();
14747 MOZ_ASSERT(mState == State::BeginVersionChange ||
14748 mState == State::WaitingForOtherDatabasesToClose);
14749 MOZ_ASSERT(!mDatabaseId.ref().IsEmpty());
14750 MOZ_ASSERT(!IsActorDestroyed());
14752 mState = State::WaitingForTransactionsToComplete;
14754 RefPtr<WaitForTransactionsHelper> helper =
14755 new WaitForTransactionsHelper(mDatabaseId.ref(), this);
14756 helper->WaitForTransactions();
14759 void FactoryOp::CleanupMetadata() {
14760 AssertIsOnOwningThread();
14762 for (const NotNull<RefPtr<FactoryOp>>& blockingOp : mBlocking) {
14763 blockingOp->MaybeUnblock(*this);
14765 mBlocking.Clear();
14767 MOZ_ASSERT(gFactoryOps);
14768 gFactoryOps->RemoveElement(this);
14770 // We might get here even after QuotaManagerOpen failed, so we need to check
14771 // if we have a quota manager.
14772 quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
14773 quota::Client::IDB, "An element was removed from gFactoryOps"_ns);
14775 // Match the IncreaseBusyCount in AllocPBackgroundIDBFactoryRequestParent().
14776 DecreaseBusyCount();
14779 void FactoryOp::FinishSendResults() {
14780 AssertIsOnOwningThread();
14781 MOZ_ASSERT(mState == State::SendingResults);
14782 MOZ_ASSERT(mFactory);
14784 mState = State::Completed;
14786 // Make sure to release the factory on this thread.
14787 mFactory = nullptr;
14790 nsresult FactoryOp::SendVersionChangeMessages(
14791 DatabaseActorInfo* aDatabaseActorInfo, Maybe<Database&> aOpeningDatabase,
14792 uint64_t aOldVersion, const Maybe<uint64_t>& aNewVersion) {
14793 AssertIsOnOwningThread();
14794 MOZ_ASSERT(aDatabaseActorInfo);
14795 MOZ_ASSERT(mState == State::BeginVersionChange);
14796 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
14797 MOZ_ASSERT(!IsActorDestroyed());
14799 const uint32_t expectedCount = mDeleting ? 0 : 1;
14800 const uint32_t liveCount = aDatabaseActorInfo->mLiveDatabases.Length();
14801 if (liveCount > expectedCount) {
14802 nsTArray<MaybeBlockedDatabaseInfo> maybeBlockedDatabases;
14803 for (const auto& database : aDatabaseActorInfo->mLiveDatabases) {
14804 if ((!aOpeningDatabase || database.get() != &aOpeningDatabase.ref()) &&
14805 !database->IsClosed() &&
14806 NS_WARN_IF(!maybeBlockedDatabases.AppendElement(
14807 SafeRefPtr{database.get(), AcquireStrongRefFromRawPtr{}},
14808 fallible))) {
14809 return NS_ERROR_OUT_OF_MEMORY;
14813 mMaybeBlockedDatabases = std::move(maybeBlockedDatabases);
14816 // We don't want to wait forever if we were not able to send the
14817 // message.
14818 mMaybeBlockedDatabases.RemoveLastElements(
14819 mMaybeBlockedDatabases.end() -
14820 std::remove_if(mMaybeBlockedDatabases.begin(),
14821 mMaybeBlockedDatabases.end(),
14822 [aOldVersion, &aNewVersion](auto& maybeBlockedDatabase) {
14823 return !maybeBlockedDatabase->SendVersionChange(
14824 aOldVersion, aNewVersion);
14825 }));
14827 return NS_OK;
14828 } // namespace indexedDB
14830 bool FactoryOp::MustWaitFor(const FactoryOp& aExistingOp) {
14831 AssertIsOnOwningThread();
14833 // If the persistence types don't overlap, the op can proceed.
14834 if (aExistingOp.mPersistenceType != mPersistenceType) {
14835 return false;
14838 // If the origins don't overlap, the op can proceed.
14839 if (aExistingOp.mOriginMetadata.mOrigin != mOriginMetadata.mOrigin) {
14840 return false;
14843 // If the database ids don't overlap, the op can proceed.
14844 if (!aExistingOp.mDatabaseId.isNothing() && !mDatabaseId.isNothing() &&
14845 aExistingOp.mDatabaseId.ref() != mDatabaseId.ref()) {
14846 return false;
14849 return true;
14852 // Run() assumes that the caller holds a strong reference to the object that
14853 // can't be cleared while Run() is being executed.
14854 // So if you call Run() directly (as opposed to dispatching to an event queue)
14855 // you need to make sure there's such a reference.
14856 // See bug 1356824 for more details.
14857 NS_IMETHODIMP
14858 FactoryOp::Run() {
14859 const auto handleError = [this](const nsresult rv) {
14860 if (mState != State::SendingResults) {
14861 SetFailureCodeIfUnset(rv);
14863 // Must set mState before dispatching otherwise we will race with the
14864 // owning thread.
14865 mState = State::SendingResults;
14867 if (IsOnOwningThread()) {
14868 SendResults();
14869 } else {
14870 MOZ_ALWAYS_SUCCEEDS(
14871 mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
14876 switch (mState) {
14877 case State::Initial:
14878 QM_WARNONLY_TRY(MOZ_TO_RESULT(Open()), handleError);
14879 break;
14881 case State::DirectoryWorkOpen:
14882 QM_WARNONLY_TRY(MOZ_TO_RESULT(DoDirectoryWork()), handleError);
14883 break;
14885 case State::DirectoryWorkDone:
14886 QM_WARNONLY_TRY(MOZ_TO_RESULT(DirectoryWorkDone()), handleError);
14887 break;
14889 case State::DatabaseOpenPending:
14890 QM_WARNONLY_TRY(MOZ_TO_RESULT(DatabaseOpen()), handleError);
14891 break;
14893 case State::DatabaseWorkOpen:
14894 QM_WARNONLY_TRY(MOZ_TO_RESULT(DoDatabaseWork()), handleError);
14895 break;
14897 case State::BeginVersionChange:
14898 QM_WARNONLY_TRY(MOZ_TO_RESULT(BeginVersionChange()), handleError);
14899 break;
14901 case State::WaitingForTransactionsToComplete:
14902 QM_WARNONLY_TRY(MOZ_TO_RESULT(DispatchToWorkThread()), handleError);
14903 break;
14905 case State::SendingResults:
14906 SendResults();
14907 break;
14909 default:
14910 MOZ_CRASH("Bad state!");
14913 return NS_OK;
14916 void FactoryOp::DirectoryLockAcquired(DirectoryLock* aLock) {
14917 AssertIsOnOwningThread();
14918 MOZ_ASSERT(aLock);
14919 MOZ_ASSERT(mState == State::DirectoryOpenPending);
14920 MOZ_ASSERT(!mDirectoryLock);
14922 mDirectoryLock = aLock;
14924 MOZ_ASSERT(mDirectoryLock->Id() >= 0);
14925 mDirectoryLockId = mDirectoryLock->Id();
14927 QM_WARNONLY_TRY(MOZ_TO_RESULT(DirectoryOpen()), [this](const nsresult rv) {
14928 SetFailureCodeIfUnset(rv);
14930 // The caller holds a strong reference to us, no need for a self reference
14931 // before calling Run().
14933 mState = State::SendingResults;
14934 MOZ_ALWAYS_SUCCEEDS(Run());
14938 void FactoryOp::DirectoryLockFailed() {
14939 AssertIsOnOwningThread();
14940 MOZ_ASSERT(mState == State::DirectoryOpenPending);
14941 MOZ_ASSERT(!mDirectoryLock);
14943 if (!HasFailed()) {
14944 IDB_REPORT_INTERNAL_ERR();
14945 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
14948 // The caller holds a strong reference to us, no need for a self reference
14949 // before calling Run().
14951 mState = State::SendingResults;
14952 MOZ_ALWAYS_SUCCEEDS(Run());
14955 nsresult FactoryRequestOp::DoDirectoryWork() {
14956 MOZ_CRASH("Not implemented because this should be unreachable.");
14959 void FactoryRequestOp::ActorDestroy(ActorDestroyReason aWhy) {
14960 AssertIsOnBackgroundThread();
14962 NoteActorDestroyed();
14965 OpenDatabaseOp::OpenDatabaseOp(SafeRefPtr<Factory> aFactory,
14966 const Maybe<ContentParentId>& aContentParentId,
14967 const CommonFactoryRequestParams& aParams)
14968 : FactoryRequestOp(std::move(aFactory), aContentParentId, aParams,
14969 /* aDeleting */ false),
14970 mMetadata(MakeSafeRefPtr<FullDatabaseMetadata>(aParams.metadata())),
14971 mRequestedVersion(aParams.metadata().version()),
14972 mVersionChangeOp(nullptr),
14973 mTelemetryId(0) {}
14975 void OpenDatabaseOp::ActorDestroy(ActorDestroyReason aWhy) {
14976 AssertIsOnOwningThread();
14978 FactoryRequestOp::ActorDestroy(aWhy);
14980 if (mVersionChangeOp) {
14981 mVersionChangeOp->NoteActorDestroyed();
14985 nsresult OpenDatabaseOp::DatabaseOpen() {
14986 AssertIsOnOwningThread();
14987 MOZ_ASSERT(mState == State::DatabaseOpenPending);
14989 nsresult rv = SendToIOThread();
14990 if (NS_WARN_IF(NS_FAILED(rv))) {
14991 return rv;
14994 return NS_OK;
14997 nsresult OpenDatabaseOp::DoDatabaseWork() {
14998 AssertIsOnIOThread();
14999 MOZ_ASSERT(mState == State::DatabaseWorkOpen);
15001 AUTO_PROFILER_LABEL("OpenDatabaseOp::DoDatabaseWork", DOM);
15003 QM_TRY(OkIf(!QuotaClient::IsShuttingDownOnNonBackgroundThread()),
15004 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA);
15006 if (!OperationMayProceed()) {
15007 IDB_REPORT_INTERNAL_ERR();
15008 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15011 const nsAString& databaseName = mCommonParams.metadata().name();
15012 const PersistenceType persistenceType =
15013 mCommonParams.metadata().persistenceType();
15015 QuotaManager* const quotaManager = QuotaManager::Get();
15016 MOZ_ASSERT(quotaManager);
15018 QM_TRY_INSPECT(
15019 const auto& dbDirectory,
15020 ([persistenceType, &quotaManager, this]()
15021 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
15022 if (persistenceType == PERSISTENCE_TYPE_PERSISTENT) {
15023 QM_TRY_RETURN(quotaManager->EnsurePersistentOriginIsInitialized(
15024 mOriginMetadata));
15027 QM_TRY(MOZ_TO_RESULT(
15028 quotaManager->EnsureTemporaryStorageIsInitializedInternal()));
15029 QM_TRY_RETURN(quotaManager->EnsureTemporaryOriginIsInitialized(
15030 persistenceType, mOriginMetadata));
15032 .map([](const auto& res) { return res.first; })));
15034 QM_TRY(MOZ_TO_RESULT(
15035 dbDirectory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME))));
15038 QM_TRY_INSPECT(const bool& exists,
15039 MOZ_TO_RESULT_INVOKE_MEMBER(dbDirectory, Exists));
15041 if (!exists) {
15042 QM_TRY(MOZ_TO_RESULT(dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)));
15044 #ifdef DEBUG
15045 else {
15046 bool isDirectory;
15047 MOZ_ASSERT(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory)));
15048 MOZ_ASSERT(isDirectory);
15050 #endif
15053 const auto databaseFilenameBase =
15054 GetDatabaseFilenameBase(databaseName, mOriginMetadata.mIsPrivate);
15056 QM_TRY_INSPECT(const auto& markerFile,
15057 CloneFileAndAppend(*dbDirectory, kIdbDeletionMarkerFilePrefix +
15058 databaseFilenameBase));
15060 QM_TRY_INSPECT(const bool& exists,
15061 MOZ_TO_RESULT_INVOKE_MEMBER(markerFile, Exists));
15063 if (exists) {
15064 // Delete the database and directroy since they should be deleted in
15065 // previous operation.
15066 // Note: only update usage to the QuotaManager when mEnforcingQuota == true
15067 QM_TRY(MOZ_TO_RESULT(RemoveDatabaseFilesAndDirectory(
15068 *dbDirectory, databaseFilenameBase,
15069 mEnforcingQuota ? quotaManager : nullptr, persistenceType,
15070 mOriginMetadata, databaseName)));
15073 QM_TRY_INSPECT(
15074 const auto& dbFile,
15075 CloneFileAndAppend(*dbDirectory, databaseFilenameBase + kSQLiteSuffix));
15077 mTelemetryId = TelemetryIdForFile(dbFile);
15079 #ifdef DEBUG
15081 QM_TRY_INSPECT(
15082 const auto& databaseFilePath,
15083 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath));
15085 MOZ_ASSERT(databaseFilePath == mDatabaseFilePath.ref());
15087 #endif
15089 QM_TRY_INSPECT(
15090 const auto& fmDirectory,
15091 CloneFileAndAppend(*dbDirectory, databaseFilenameBase +
15092 kFileManagerDirectoryNameSuffix));
15094 IndexedDatabaseManager* const idm = IndexedDatabaseManager::Get();
15095 MOZ_ASSERT(idm);
15097 SafeRefPtr<DatabaseFileManager> fileManager = idm->GetFileManager(
15098 persistenceType, mOriginMetadata.mOrigin, databaseName);
15100 if (!fileManager) {
15101 fileManager = MakeSafeRefPtr<DatabaseFileManager>(
15102 persistenceType, mOriginMetadata, databaseName, mDatabaseId.ref(),
15103 mDatabaseFilePath.ref(), mEnforcingQuota, mInPrivateBrowsing);
15106 Maybe<const CipherKey> maybeKey =
15107 mInPrivateBrowsing
15108 ? Some(fileManager->MutableCipherKeyManagerRef().Ensure())
15109 : Nothing();
15111 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());
15113 QM_TRY_UNWRAP(
15114 NotNull<nsCOMPtr<mozIStorageConnection>> connection,
15115 CreateStorageConnection(*dbFile, *fmDirectory, databaseName,
15116 mOriginMetadata.mOrigin, mDirectoryLockId,
15117 mTelemetryId, maybeKey));
15119 AutoSetProgressHandler asph;
15120 QM_TRY(MOZ_TO_RESULT(asph.Register(*connection, this)));
15122 QM_TRY(MOZ_TO_RESULT(LoadDatabaseInformation(*connection)));
15124 MOZ_ASSERT(mMetadata->mNextObjectStoreId > mMetadata->mObjectStores.Count());
15125 MOZ_ASSERT(mMetadata->mNextIndexId > 0);
15127 // See if we need to do a versionchange transaction
15129 // Optional version semantics.
15130 if (!mRequestedVersion) {
15131 // If the requested version was not specified and the database was created,
15132 // treat it as if version 1 were requested.
15133 // Otherwise, treat it as if the current version were requested.
15134 mRequestedVersion = mMetadata->mCommonMetadata.version() == 0
15136 : mMetadata->mCommonMetadata.version();
15139 QM_TRY(OkIf(mMetadata->mCommonMetadata.version() <= mRequestedVersion),
15140 NS_ERROR_DOM_INDEXEDDB_VERSION_ERR);
15142 if (!fileManager->Initialized()) {
15143 QM_TRY(MOZ_TO_RESULT(fileManager->Init(fmDirectory, *connection)));
15145 idm->AddFileManager(fileManager.clonePtr());
15148 mFileManager = std::move(fileManager);
15150 // Must close connection before dispatching otherwise we might race with the
15151 // connection thread which needs to open the same database.
15152 asph.Unregister();
15154 MOZ_ALWAYS_SUCCEEDS(connection->Close());
15156 // Must set mState before dispatching otherwise we will race with the owning
15157 // thread.
15158 mState = (mMetadata->mCommonMetadata.version() == mRequestedVersion)
15159 ? State::SendingResults
15160 : State::BeginVersionChange;
15162 QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
15164 return NS_OK;
15167 nsresult OpenDatabaseOp::LoadDatabaseInformation(
15168 mozIStorageConnection& aConnection) {
15169 AssertIsOnIOThread();
15170 MOZ_ASSERT(mMetadata);
15173 // Load version information.
15174 QM_TRY_INSPECT(
15175 const auto& stmt,
15176 CreateAndExecuteSingleStepStatement<
15177 SingleStepResult::ReturnNullIfNoResult>(
15178 aConnection, "SELECT name, origin, version FROM database"_ns));
15180 QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED);
15182 QM_TRY_INSPECT(const auto& databaseName, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15183 nsString, stmt, GetString, 0));
15185 QM_TRY(OkIf(mCommonParams.metadata().name() == databaseName),
15186 NS_ERROR_FILE_CORRUPTED);
15188 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15189 nsCString, stmt, GetUTF8String, 1));
15191 // We can't just compare these strings directly. See bug 1339081 comment 69.
15192 QM_TRY(OkIf(QuotaManager::AreOriginsEqualOnDisk(mOriginMetadata.mOrigin,
15193 origin)),
15194 NS_ERROR_FILE_CORRUPTED);
15196 QM_TRY_INSPECT(const int64_t& version,
15197 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 2));
15199 mMetadata->mCommonMetadata.version() = uint64_t(version);
15202 ObjectStoreTable& objectStores = mMetadata->mObjectStores;
15204 QM_TRY_INSPECT(
15205 const auto& lastObjectStoreId,
15206 ([&aConnection,
15207 &objectStores]() -> mozilla::Result<IndexOrObjectStoreId, nsresult> {
15208 // Load object store names and ids.
15209 QM_TRY_INSPECT(
15210 const auto& stmt,
15211 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15212 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
15213 "SELECT id, auto_increment, name, key_path "
15214 "FROM object_store"_ns));
15216 IndexOrObjectStoreId lastObjectStoreId = 0;
15218 QM_TRY(CollectWhileHasResult(
15219 *stmt,
15220 [&lastObjectStoreId, &objectStores,
15221 usedIds = Maybe<nsTHashSet<uint64_t>>{},
15222 usedNames = Maybe<nsTHashSet<nsString>>{}](
15223 auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
15224 QM_TRY_INSPECT(const IndexOrObjectStoreId& objectStoreId,
15225 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
15227 if (!usedIds) {
15228 usedIds.emplace();
15231 QM_TRY(OkIf(objectStoreId > 0), Err(NS_ERROR_FILE_CORRUPTED));
15232 QM_TRY(OkIf(!usedIds.ref().Contains(objectStoreId)),
15233 Err(NS_ERROR_FILE_CORRUPTED));
15235 QM_TRY(OkIf(usedIds.ref().Insert(objectStoreId, fallible)),
15236 Err(NS_ERROR_OUT_OF_MEMORY));
15238 nsString name;
15239 QM_TRY(MOZ_TO_RESULT(stmt.GetString(2, name)));
15241 if (!usedNames) {
15242 usedNames.emplace();
15245 QM_TRY(OkIf(!usedNames.ref().Contains(name)),
15246 Err(NS_ERROR_FILE_CORRUPTED));
15248 QM_TRY(OkIf(usedNames.ref().Insert(name, fallible)),
15249 Err(NS_ERROR_OUT_OF_MEMORY));
15251 ObjectStoreMetadata commonMetadata;
15252 commonMetadata.id() = objectStoreId;
15253 commonMetadata.name() = std::move(name);
15255 QM_TRY_INSPECT(
15256 const int32_t& columnType,
15257 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetTypeOfIndex, 3));
15259 if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
15260 commonMetadata.keyPath() = KeyPath(0);
15261 } else {
15262 MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_TEXT);
15264 nsString keyPathSerialization;
15265 QM_TRY(MOZ_TO_RESULT(stmt.GetString(3, keyPathSerialization)));
15267 commonMetadata.keyPath() =
15268 KeyPath::DeserializeFromString(keyPathSerialization);
15269 QM_TRY(OkIf(commonMetadata.keyPath().IsValid()),
15270 Err(NS_ERROR_FILE_CORRUPTED));
15273 QM_TRY_INSPECT(const int64_t& nextAutoIncrementId,
15274 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1));
15276 commonMetadata.autoIncrement() = !!nextAutoIncrementId;
15278 QM_TRY(OkIf(objectStores.InsertOrUpdate(
15279 objectStoreId,
15280 MakeSafeRefPtr<FullObjectStoreMetadata>(
15281 std::move(commonMetadata),
15282 FullObjectStoreMetadata::AutoIncrementIds{
15283 nextAutoIncrementId, nextAutoIncrementId}),
15284 fallible)),
15285 Err(NS_ERROR_OUT_OF_MEMORY));
15287 lastObjectStoreId = std::max(lastObjectStoreId, objectStoreId);
15289 return Ok{};
15290 }));
15292 return lastObjectStoreId;
15293 }()));
15295 QM_TRY_INSPECT(
15296 const auto& lastIndexId,
15297 ([this, &aConnection,
15298 &objectStores]() -> mozilla::Result<IndexOrObjectStoreId, nsresult> {
15299 // Load index information
15300 QM_TRY_INSPECT(
15301 const auto& stmt,
15302 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15303 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
15304 "SELECT "
15305 "id, object_store_id, name, key_path, "
15306 "unique_index, multientry, "
15307 "locale, is_auto_locale "
15308 "FROM object_store_index"_ns));
15310 IndexOrObjectStoreId lastIndexId = 0;
15312 QM_TRY(CollectWhileHasResult(
15313 *stmt,
15314 [this, &lastIndexId, &objectStores, &aConnection,
15315 usedIds = Maybe<nsTHashSet<uint64_t>>{},
15316 usedNames = Maybe<nsTHashSet<nsString>>{}](
15317 auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
15318 QM_TRY_INSPECT(const IndexOrObjectStoreId& objectStoreId,
15319 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1));
15321 // XXX Why does this return NS_ERROR_OUT_OF_MEMORY if we don't
15322 // know the object store id?
15324 auto objectStoreMetadata = objectStores.Lookup(objectStoreId);
15325 QM_TRY(OkIf(static_cast<bool>(objectStoreMetadata)),
15326 Err(NS_ERROR_OUT_OF_MEMORY));
15328 MOZ_ASSERT((*objectStoreMetadata)->mCommonMetadata.id() ==
15329 objectStoreId);
15331 IndexOrObjectStoreId indexId;
15332 QM_TRY(MOZ_TO_RESULT(stmt.GetInt64(0, &indexId)));
15334 if (!usedIds) {
15335 usedIds.emplace();
15338 QM_TRY(OkIf(indexId > 0), Err(NS_ERROR_FILE_CORRUPTED));
15339 QM_TRY(OkIf(!usedIds.ref().Contains(indexId)),
15340 Err(NS_ERROR_FILE_CORRUPTED));
15342 QM_TRY(OkIf(usedIds.ref().Insert(indexId, fallible)),
15343 Err(NS_ERROR_OUT_OF_MEMORY));
15345 nsString name;
15346 QM_TRY(MOZ_TO_RESULT(stmt.GetString(2, name)));
15348 const nsAutoString hashName =
15349 IntToString(indexId) + u":"_ns + name;
15351 if (!usedNames) {
15352 usedNames.emplace();
15355 QM_TRY(OkIf(!usedNames.ref().Contains(hashName)),
15356 Err(NS_ERROR_FILE_CORRUPTED));
15358 QM_TRY(OkIf(usedNames.ref().Insert(hashName, fallible)),
15359 Err(NS_ERROR_OUT_OF_MEMORY));
15361 auto indexMetadata = MakeSafeRefPtr<FullIndexMetadata>();
15362 indexMetadata->mCommonMetadata.id() = indexId;
15363 indexMetadata->mCommonMetadata.name() = name;
15365 #ifdef DEBUG
15367 int32_t columnType;
15368 nsresult rv = stmt.GetTypeOfIndex(3, &columnType);
15369 MOZ_ASSERT(NS_SUCCEEDED(rv));
15370 MOZ_ASSERT(columnType != mozIStorageStatement::VALUE_TYPE_NULL);
15372 #endif
15374 nsString keyPathSerialization;
15375 QM_TRY(MOZ_TO_RESULT(stmt.GetString(3, keyPathSerialization)));
15377 indexMetadata->mCommonMetadata.keyPath() =
15378 KeyPath::DeserializeFromString(keyPathSerialization);
15379 QM_TRY(OkIf(indexMetadata->mCommonMetadata.keyPath().IsValid()),
15380 Err(NS_ERROR_FILE_CORRUPTED));
15382 int32_t scratch;
15383 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(4, &scratch)));
15385 indexMetadata->mCommonMetadata.unique() = !!scratch;
15387 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(5, &scratch)));
15389 indexMetadata->mCommonMetadata.multiEntry() = !!scratch;
15391 const bool localeAware = !stmt.IsNull(6);
15392 if (localeAware) {
15393 QM_TRY(MOZ_TO_RESULT(stmt.GetUTF8String(
15394 6, indexMetadata->mCommonMetadata.locale())));
15396 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(7, &scratch)));
15398 indexMetadata->mCommonMetadata.autoLocale() = !!scratch;
15400 // Update locale-aware indexes if necessary
15401 const nsCString& indexedLocale =
15402 indexMetadata->mCommonMetadata.locale();
15403 const bool& isAutoLocale =
15404 indexMetadata->mCommonMetadata.autoLocale();
15405 const nsCString& systemLocale = mFactory->GetSystemLocale();
15406 if (!systemLocale.IsEmpty() && isAutoLocale &&
15407 !indexedLocale.Equals(systemLocale)) {
15408 QM_TRY(MOZ_TO_RESULT(UpdateLocaleAwareIndex(
15409 aConnection, indexMetadata->mCommonMetadata,
15410 systemLocale)));
15414 QM_TRY(OkIf((*objectStoreMetadata)
15415 ->mIndexes.InsertOrUpdate(
15416 indexId, std::move(indexMetadata), fallible)),
15417 Err(NS_ERROR_OUT_OF_MEMORY));
15419 lastIndexId = std::max(lastIndexId, indexId);
15421 return Ok{};
15422 }));
15424 return lastIndexId;
15425 }()));
15427 QM_TRY(OkIf(lastObjectStoreId != INT64_MAX),
15428 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA);
15429 QM_TRY(OkIf(lastIndexId != INT64_MAX), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
15430 IDB_REPORT_INTERNAL_ERR_LAMBDA);
15432 mMetadata->mNextObjectStoreId = lastObjectStoreId + 1;
15433 mMetadata->mNextIndexId = lastIndexId + 1;
15435 return NS_OK;
15438 /* static */
15439 nsresult OpenDatabaseOp::UpdateLocaleAwareIndex(
15440 mozIStorageConnection& aConnection, const IndexMetadata& aIndexMetadata,
15441 const nsCString& aLocale) {
15442 const auto indexTable =
15443 aIndexMetadata.unique() ? "unique_index_data"_ns : "index_data"_ns;
15445 // The parameter names are not used, parameters are bound by index only
15446 // locally in the same function.
15447 const nsCString readQuery = "SELECT value, object_data_key FROM "_ns +
15448 indexTable + " WHERE index_id = :index_id"_ns;
15450 QM_TRY_INSPECT(const auto& readStmt,
15451 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15452 nsCOMPtr<mozIStorageStatement>, aConnection,
15453 CreateStatement, readQuery));
15455 QM_TRY(MOZ_TO_RESULT(readStmt->BindInt64ByIndex(0, aIndexMetadata.id())));
15457 QM_TRY(CollectWhileHasResult(
15458 *readStmt,
15459 [&aConnection, &indexTable, &aIndexMetadata, &aLocale,
15460 writeStmt = nsCOMPtr<mozIStorageStatement>{}](
15461 auto& readStmt) mutable -> mozilla::Result<Ok, nsresult> {
15462 if (!writeStmt) {
15463 QM_TRY_UNWRAP(
15464 writeStmt,
15465 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15466 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
15467 "UPDATE "_ns + indexTable + "SET value_locale = :"_ns +
15468 kStmtParamNameValueLocale + " WHERE index_id = :"_ns +
15469 kStmtParamNameIndexId + " AND value = :"_ns +
15470 kStmtParamNameValue + " AND object_data_key = :"_ns +
15471 kStmtParamNameObjectDataKey));
15474 mozStorageStatementScoper scoper(writeStmt);
15475 QM_TRY(MOZ_TO_RESULT(writeStmt->BindInt64ByName(kStmtParamNameIndexId,
15476 aIndexMetadata.id())));
15478 Key oldKey, objectStorePosition;
15479 QM_TRY(MOZ_TO_RESULT(oldKey.SetFromStatement(&readStmt, 0)));
15480 QM_TRY(MOZ_TO_RESULT(
15481 oldKey.BindToStatement(writeStmt, kStmtParamNameValue)));
15483 QM_TRY_INSPECT(const auto& newSortKey,
15484 oldKey.ToLocaleAwareKey(aLocale));
15486 QM_TRY(MOZ_TO_RESULT(
15487 newSortKey.BindToStatement(writeStmt, kStmtParamNameValueLocale)));
15488 QM_TRY(
15489 MOZ_TO_RESULT(objectStorePosition.SetFromStatement(&readStmt, 1)));
15490 QM_TRY(MOZ_TO_RESULT(objectStorePosition.BindToStatement(
15491 writeStmt, kStmtParamNameObjectDataKey)));
15493 QM_TRY(MOZ_TO_RESULT(writeStmt->Execute()));
15495 return Ok{};
15496 }));
15498 // The parameter names are not used, parameters are bound by index only
15499 // locally in the same function.
15500 static constexpr auto metaQuery =
15501 "UPDATE object_store_index SET "
15502 "locale = :locale WHERE id = :id"_ns;
15504 QM_TRY_INSPECT(const auto& metaStmt,
15505 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
15506 nsCOMPtr<mozIStorageStatement>, aConnection,
15507 CreateStatement, metaQuery));
15509 QM_TRY(MOZ_TO_RESULT(
15510 metaStmt->BindStringByIndex(0, NS_ConvertASCIItoUTF16(aLocale))));
15512 QM_TRY(MOZ_TO_RESULT(metaStmt->BindInt64ByIndex(1, aIndexMetadata.id())));
15514 QM_TRY(MOZ_TO_RESULT(metaStmt->Execute()));
15516 return NS_OK;
15519 nsresult OpenDatabaseOp::BeginVersionChange() {
15520 AssertIsOnOwningThread();
15521 MOZ_ASSERT(mState == State::BeginVersionChange);
15522 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
15523 MOZ_ASSERT(mMetadata->mCommonMetadata.version() <= mRequestedVersion);
15524 MOZ_ASSERT(!mDatabase);
15525 MOZ_ASSERT(!mVersionChangeTransaction);
15527 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
15528 IsActorDestroyed()) {
15529 IDB_REPORT_INTERNAL_ERR();
15530 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR));
15533 EnsureDatabaseActor();
15535 if (mDatabase->IsInvalidated()) {
15536 IDB_REPORT_INTERNAL_ERR();
15537 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR));
15540 MOZ_ASSERT(!mDatabase->IsClosed());
15542 DatabaseActorInfo* info;
15543 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info));
15545 MOZ_ASSERT(info->mLiveDatabases.Contains(mDatabase.unsafeGetRawPtr()));
15546 MOZ_ASSERT(!info->mWaitingFactoryOp);
15547 MOZ_ASSERT(info->mMetadata == mMetadata);
15549 auto transaction = MakeSafeRefPtr<VersionChangeTransaction>(this);
15551 if (NS_WARN_IF(!transaction->CopyDatabaseMetadata())) {
15552 return NS_ERROR_OUT_OF_MEMORY;
15555 MOZ_ASSERT(info->mMetadata != mMetadata);
15556 mMetadata = info->mMetadata.clonePtr();
15558 const Maybe<uint64_t> newVersion = Some(mRequestedVersion);
15560 QM_TRY(MOZ_TO_RESULT(SendVersionChangeMessages(
15561 info, mDatabase.maybeDeref(), mMetadata->mCommonMetadata.version(),
15562 newVersion)));
15564 mVersionChangeTransaction = std::move(transaction);
15566 if (mMaybeBlockedDatabases.IsEmpty()) {
15567 // We don't need to wait on any databases, just jump to the transaction
15568 // pool.
15569 WaitForTransactions();
15570 return NS_OK;
15573 // If the actor gets destroyed, mWaitingFactoryOp will hold the last strong
15574 // reference to us.
15575 info->mWaitingFactoryOp = this;
15577 mState = State::WaitingForOtherDatabasesToClose;
15578 return NS_OK;
15581 bool OpenDatabaseOp::AreActorsAlive() {
15582 AssertIsOnOwningThread();
15583 MOZ_ASSERT(mDatabase);
15585 return !(IsActorDestroyed() || mDatabase->IsActorDestroyed());
15588 void OpenDatabaseOp::SendBlockedNotification() {
15589 AssertIsOnOwningThread();
15590 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
15592 if (!IsActorDestroyed()) {
15593 Unused << SendBlocked(mMetadata->mCommonMetadata.version());
15597 nsresult OpenDatabaseOp::DispatchToWorkThread() {
15598 AssertIsOnOwningThread();
15599 MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
15600 MOZ_ASSERT(mVersionChangeTransaction);
15601 MOZ_ASSERT(mVersionChangeTransaction->GetMode() ==
15602 IDBTransaction::Mode::VersionChange);
15603 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
15605 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
15606 IsActorDestroyed() || mDatabase->IsInvalidated()) {
15607 IDB_REPORT_INTERNAL_ERR();
15608 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15611 mState = State::DatabaseWorkVersionChange;
15613 // Intentionally empty.
15614 nsTArray<nsString> objectStoreNames;
15616 const int64_t loggingSerialNumber =
15617 mVersionChangeTransaction->LoggingSerialNumber();
15618 const nsID& backgroundChildLoggingId =
15619 mVersionChangeTransaction->GetLoggingInfo()->Id();
15621 if (NS_WARN_IF(!mDatabase->RegisterTransaction(*mVersionChangeTransaction))) {
15622 return NS_ERROR_OUT_OF_MEMORY;
15625 if (!gConnectionPool) {
15626 gConnectionPool = new ConnectionPool();
15629 RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
15631 uint64_t transactionId = versionChangeOp->StartOnConnectionPool(
15632 backgroundChildLoggingId, mVersionChangeTransaction->DatabaseId(),
15633 loggingSerialNumber, objectStoreNames,
15634 /* aIsWriteTransaction */ true);
15636 mVersionChangeOp = versionChangeOp;
15638 mVersionChangeTransaction->NoteActiveRequest();
15639 mVersionChangeTransaction->Init(transactionId);
15641 return NS_OK;
15644 nsresult OpenDatabaseOp::SendUpgradeNeeded() {
15645 AssertIsOnOwningThread();
15646 MOZ_ASSERT(mState == State::DatabaseWorkVersionChange);
15647 MOZ_ASSERT(mVersionChangeTransaction);
15648 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
15649 MOZ_ASSERT(!HasFailed());
15650 MOZ_ASSERT_IF(!IsActorDestroyed(), mDatabase);
15652 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
15653 IsActorDestroyed()) {
15654 IDB_REPORT_INTERNAL_ERR();
15655 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15658 const SafeRefPtr<VersionChangeTransaction> transaction =
15659 std::move(mVersionChangeTransaction);
15661 nsresult rv = EnsureDatabaseActorIsAlive();
15662 if (NS_WARN_IF(NS_FAILED(rv))) {
15663 return rv;
15666 // Transfer ownership to IPDL.
15667 transaction->SetActorAlive();
15669 if (!mDatabase->SendPBackgroundIDBVersionChangeTransactionConstructor(
15670 transaction.unsafeGetRawPtr(), mMetadata->mCommonMetadata.version(),
15671 mRequestedVersion, mMetadata->mNextObjectStoreId,
15672 mMetadata->mNextIndexId)) {
15673 IDB_REPORT_INTERNAL_ERR();
15674 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15677 return NS_OK;
15680 void OpenDatabaseOp::SendResults() {
15681 AssertIsOnOwningThread();
15682 MOZ_ASSERT(mState == State::SendingResults);
15683 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
15684 MOZ_ASSERT_IF(!HasFailed(), !mVersionChangeTransaction);
15686 DebugOnly<DatabaseActorInfo*> info = nullptr;
15687 MOZ_ASSERT_IF(mDatabaseId.isSome() && gLiveDatabaseHashtable &&
15688 gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info),
15689 !info->mWaitingFactoryOp);
15691 if (mVersionChangeTransaction) {
15692 MOZ_ASSERT(HasFailed());
15694 mVersionChangeTransaction->Abort(ResultCode(), /* aForce */ true);
15695 mVersionChangeTransaction = nullptr;
15698 if (IsActorDestroyed()) {
15699 SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
15700 } else {
15701 FactoryRequestResponse response;
15703 if (!HasFailed()) {
15704 // If we just successfully completed a versionchange operation then we
15705 // need to update the version in our metadata.
15706 mMetadata->mCommonMetadata.version() = mRequestedVersion;
15708 mFileManager->UpdateDatabaseVersion(mRequestedVersion);
15710 nsresult rv = EnsureDatabaseActorIsAlive();
15711 if (NS_SUCCEEDED(rv)) {
15712 // We successfully opened a database so use its actor as the success
15713 // result for this request.
15715 // XXX OpenDatabaseRequestResponse stores a raw pointer, can this be
15716 // avoided?
15717 response = OpenDatabaseRequestResponse{
15718 WrapNotNull(mDatabase.unsafeGetRawPtr())};
15719 } else {
15720 response = ClampResultCode(rv);
15721 #ifdef DEBUG
15722 SetFailureCode(response.get_nsresult());
15723 #endif
15725 } else {
15726 #ifdef DEBUG
15727 // If something failed then our metadata pointer is now bad. No one should
15728 // ever touch it again though so just null it out in DEBUG builds to make
15729 // sure we find such cases.
15730 mMetadata = nullptr;
15731 #endif
15732 response = ClampResultCode(ResultCode());
15735 Unused << PBackgroundIDBFactoryRequestParent::Send__delete__(this,
15736 response);
15739 if (mDatabase) {
15740 MOZ_ASSERT(!mDirectoryLock);
15742 if (HasFailed()) {
15743 mDatabase->Invalidate();
15746 // Make sure to release the database on this thread.
15747 mDatabase = nullptr;
15749 CleanupMetadata();
15750 } else if (mDirectoryLock) {
15751 // ConnectionClosedCallback will call CleanupMetadata().
15752 nsCOMPtr<nsIRunnable> callback = NewRunnableMethod(
15753 "dom::indexedDB::OpenDatabaseOp::ConnectionClosedCallback", this,
15754 &OpenDatabaseOp::ConnectionClosedCallback);
15756 RefPtr<WaitForTransactionsHelper> helper =
15757 new WaitForTransactionsHelper(mDatabaseId.ref(), callback);
15758 helper->WaitForTransactions();
15759 } else {
15760 CleanupMetadata();
15763 FinishSendResults();
15766 void OpenDatabaseOp::ConnectionClosedCallback() {
15767 AssertIsOnOwningThread();
15768 MOZ_ASSERT(HasFailed());
15769 MOZ_ASSERT(mDirectoryLock);
15771 mDirectoryLock = nullptr;
15773 CleanupMetadata();
15776 void OpenDatabaseOp::EnsureDatabaseActor() {
15777 AssertIsOnOwningThread();
15778 MOZ_ASSERT(mState == State::BeginVersionChange ||
15779 mState == State::DatabaseWorkVersionChange ||
15780 mState == State::SendingResults);
15781 MOZ_ASSERT(!HasFailed());
15782 MOZ_ASSERT(mDatabaseFilePath.isSome());
15783 MOZ_ASSERT(!IsActorDestroyed());
15785 if (mDatabase) {
15786 return;
15789 MOZ_ASSERT(mMetadata->mDatabaseId.IsEmpty());
15790 mMetadata->mDatabaseId = mDatabaseId.ref();
15792 MOZ_ASSERT(mMetadata->mFilePath.IsEmpty());
15793 mMetadata->mFilePath = mDatabaseFilePath.ref();
15795 DatabaseActorInfo* info;
15796 if (gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)) {
15797 AssertMetadataConsistency(*info->mMetadata);
15798 mMetadata = info->mMetadata.clonePtr();
15801 Maybe<const CipherKey> maybeKey =
15802 mInPrivateBrowsing ? mFileManager->MutableCipherKeyManagerRef().Get()
15803 : Nothing();
15805 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());
15807 // XXX Shouldn't Manager() return already_AddRefed when
15808 // PBackgroundIDBFactoryParent is declared refcounted?
15809 mDatabase = MakeSafeRefPtr<Database>(
15810 SafeRefPtr{static_cast<Factory*>(Manager()),
15811 AcquireStrongRefFromRawPtr{}},
15812 mCommonParams.principalInfo(), mContentParentId, mOriginMetadata,
15813 mTelemetryId, mMetadata.clonePtr(), mFileManager.clonePtr(),
15814 std::move(mDirectoryLock), mInPrivateBrowsing, maybeKey);
15816 if (info) {
15817 info->mLiveDatabases.AppendElement(
15818 WrapNotNullUnchecked(mDatabase.unsafeGetRawPtr()));
15819 } else {
15820 // XXX Maybe use LookupOrInsertWith above, to avoid a second lookup here?
15821 info = gLiveDatabaseHashtable
15822 ->InsertOrUpdate(
15823 mDatabaseId.ref(),
15824 MakeUnique<DatabaseActorInfo>(
15825 mMetadata.clonePtr(),
15826 WrapNotNullUnchecked(mDatabase.unsafeGetRawPtr())))
15827 .get();
15830 // Balanced in Database::CleanupMetadata().
15831 IncreaseBusyCount();
15834 nsresult OpenDatabaseOp::EnsureDatabaseActorIsAlive() {
15835 AssertIsOnOwningThread();
15836 MOZ_ASSERT(mState == State::DatabaseWorkVersionChange ||
15837 mState == State::SendingResults);
15838 MOZ_ASSERT(!HasFailed());
15839 MOZ_ASSERT(!IsActorDestroyed());
15841 EnsureDatabaseActor();
15843 if (mDatabase->IsActorAlive()) {
15844 return NS_OK;
15847 auto* const factory = static_cast<Factory*>(Manager());
15849 QM_TRY_INSPECT(const auto& spec, MetadataToSpec());
15851 mDatabase->SetActorAlive();
15853 if (!factory->SendPBackgroundIDBDatabaseConstructor(
15854 mDatabase.unsafeGetRawPtr(), spec, WrapNotNull(this))) {
15855 IDB_REPORT_INTERNAL_ERR();
15856 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
15859 return NS_OK;
15862 Result<DatabaseSpec, nsresult> OpenDatabaseOp::MetadataToSpec() const {
15863 AssertIsOnOwningThread();
15864 MOZ_ASSERT(mMetadata);
15866 DatabaseSpec spec;
15867 spec.metadata() = mMetadata->mCommonMetadata;
15869 QM_TRY_UNWRAP(spec.objectStores(),
15870 TransformIntoNewArrayAbortOnErr(
15871 mMetadata->mObjectStores,
15872 [](const auto& objectStoreEntry)
15873 -> mozilla::Result<ObjectStoreSpec, nsresult> {
15874 FullObjectStoreMetadata* metadata =
15875 objectStoreEntry.GetWeak();
15876 MOZ_ASSERT(objectStoreEntry.GetKey());
15877 MOZ_ASSERT(metadata);
15879 ObjectStoreSpec objectStoreSpec;
15880 objectStoreSpec.metadata() = metadata->mCommonMetadata;
15882 QM_TRY_UNWRAP(auto indexes,
15883 TransformIntoNewArray(
15884 metadata->mIndexes,
15885 [](const auto& indexEntry) {
15886 FullIndexMetadata* indexMetadata =
15887 indexEntry.GetWeak();
15888 MOZ_ASSERT(indexEntry.GetKey());
15889 MOZ_ASSERT(indexMetadata);
15891 return indexMetadata->mCommonMetadata;
15893 fallible));
15895 objectStoreSpec.indexes() = std::move(indexes);
15897 return objectStoreSpec;
15899 fallible));
15901 return spec;
15904 #ifdef DEBUG
15906 void OpenDatabaseOp::AssertMetadataConsistency(
15907 const FullDatabaseMetadata& aMetadata) {
15908 AssertIsOnBackgroundThread();
15910 const FullDatabaseMetadata& thisDB = *mMetadata;
15911 const FullDatabaseMetadata& otherDB = aMetadata;
15913 MOZ_ASSERT(&thisDB != &otherDB);
15915 MOZ_ASSERT(thisDB.mCommonMetadata.name() == otherDB.mCommonMetadata.name());
15916 MOZ_ASSERT(thisDB.mCommonMetadata.version() ==
15917 otherDB.mCommonMetadata.version());
15918 MOZ_ASSERT(thisDB.mCommonMetadata.persistenceType() ==
15919 otherDB.mCommonMetadata.persistenceType());
15920 MOZ_ASSERT(thisDB.mDatabaseId == otherDB.mDatabaseId);
15921 MOZ_ASSERT(thisDB.mFilePath == otherDB.mFilePath);
15923 // |thisDB| reflects the latest objectStore and index ids that have committed
15924 // to disk. The in-memory metadata |otherDB| keeps track of objectStores and
15925 // indexes that were created and then removed as well, so the next ids for
15926 // |otherDB| may be higher than for |thisDB|.
15927 MOZ_ASSERT(thisDB.mNextObjectStoreId <= otherDB.mNextObjectStoreId);
15928 MOZ_ASSERT(thisDB.mNextIndexId <= otherDB.mNextIndexId);
15930 MOZ_ASSERT(thisDB.mObjectStores.Count() == otherDB.mObjectStores.Count());
15932 for (const auto& thisObjectStore : thisDB.mObjectStores.Values()) {
15933 MOZ_ASSERT(thisObjectStore);
15934 MOZ_ASSERT(!thisObjectStore->mDeleted);
15936 auto otherObjectStore = MatchMetadataNameOrId(
15937 otherDB.mObjectStores, thisObjectStore->mCommonMetadata.id());
15938 MOZ_ASSERT(otherObjectStore);
15940 MOZ_ASSERT(thisObjectStore != &otherObjectStore.ref());
15942 MOZ_ASSERT(thisObjectStore->mCommonMetadata.id() ==
15943 otherObjectStore->mCommonMetadata.id());
15944 MOZ_ASSERT(thisObjectStore->mCommonMetadata.name() ==
15945 otherObjectStore->mCommonMetadata.name());
15946 MOZ_ASSERT(thisObjectStore->mCommonMetadata.autoIncrement() ==
15947 otherObjectStore->mCommonMetadata.autoIncrement());
15948 MOZ_ASSERT(thisObjectStore->mCommonMetadata.keyPath() ==
15949 otherObjectStore->mCommonMetadata.keyPath());
15950 // mNextAutoIncrementId and mCommittedAutoIncrementId may be modified
15951 // concurrently with this OpenOp, so it is not possible to assert equality
15952 // here. It's also possible that we've written the new ids to disk but not
15953 // yet updated the in-memory count.
15954 // TODO The first part of the comment should probably be rephrased. I think
15955 // it still applies but it sounds as if this were thread-unsafe like it was
15956 // before, which isn't true anymore.
15958 const auto&& thisAutoIncrementIds =
15959 thisObjectStore->mAutoIncrementIds.Lock();
15960 const auto&& otherAutoIncrementIds =
15961 otherObjectStore->mAutoIncrementIds.Lock();
15963 MOZ_ASSERT(thisAutoIncrementIds->next <= otherAutoIncrementIds->next);
15964 MOZ_ASSERT(
15965 thisAutoIncrementIds->committed <= otherAutoIncrementIds->committed ||
15966 thisAutoIncrementIds->committed == otherAutoIncrementIds->next);
15968 MOZ_ASSERT(!otherObjectStore->mDeleted);
15970 MOZ_ASSERT(thisObjectStore->mIndexes.Count() ==
15971 otherObjectStore->mIndexes.Count());
15973 for (const auto& thisIndex : thisObjectStore->mIndexes.Values()) {
15974 MOZ_ASSERT(thisIndex);
15975 MOZ_ASSERT(!thisIndex->mDeleted);
15977 auto otherIndex = MatchMetadataNameOrId(otherObjectStore->mIndexes,
15978 thisIndex->mCommonMetadata.id());
15979 MOZ_ASSERT(otherIndex);
15981 MOZ_ASSERT(thisIndex != &otherIndex.ref());
15983 MOZ_ASSERT(thisIndex->mCommonMetadata.id() ==
15984 otherIndex->mCommonMetadata.id());
15985 MOZ_ASSERT(thisIndex->mCommonMetadata.name() ==
15986 otherIndex->mCommonMetadata.name());
15987 MOZ_ASSERT(thisIndex->mCommonMetadata.keyPath() ==
15988 otherIndex->mCommonMetadata.keyPath());
15989 MOZ_ASSERT(thisIndex->mCommonMetadata.unique() ==
15990 otherIndex->mCommonMetadata.unique());
15991 MOZ_ASSERT(thisIndex->mCommonMetadata.multiEntry() ==
15992 otherIndex->mCommonMetadata.multiEntry());
15993 MOZ_ASSERT(!otherIndex->mDeleted);
15998 #endif // DEBUG
16000 nsresult OpenDatabaseOp::VersionChangeOp::DoDatabaseWork(
16001 DatabaseConnection* aConnection) {
16002 MOZ_ASSERT(aConnection);
16003 aConnection->AssertIsOnConnectionThread();
16004 MOZ_ASSERT(mOpenDatabaseOp);
16005 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
16007 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
16008 !OperationMayProceed()) {
16009 IDB_REPORT_INTERNAL_ERR();
16010 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16013 AUTO_PROFILER_LABEL("OpenDatabaseOp::VersionChangeOp::DoDatabaseWork", DOM);
16015 IDB_LOG_MARK_PARENT_TRANSACTION("Beginning database work", "DB Start",
16016 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
16017 mTransactionLoggingSerialNumber);
16019 Transaction().SetActiveOnConnectionThread();
16021 QM_TRY(MOZ_TO_RESULT(aConnection->BeginWriteTransaction()));
16023 // The parameter names are not used, parameters are bound by index only
16024 // locally in the same function.
16025 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
16026 "UPDATE database SET version = :version;"_ns,
16027 ([&self = *this](
16028 mozIStorageStatement& updateStmt) -> mozilla::Result<Ok, nsresult> {
16029 QM_TRY(MOZ_TO_RESULT(
16030 updateStmt.BindInt64ByIndex(0, int64_t(self.mRequestedVersion))));
16032 return Ok{};
16033 }))));
16035 return NS_OK;
16038 nsresult OpenDatabaseOp::VersionChangeOp::SendSuccessResult() {
16039 AssertIsOnOwningThread();
16040 MOZ_ASSERT(mOpenDatabaseOp);
16041 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
16042 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
16044 nsresult rv = mOpenDatabaseOp->SendUpgradeNeeded();
16045 if (NS_WARN_IF(NS_FAILED(rv))) {
16046 return rv;
16049 return NS_OK;
16052 bool OpenDatabaseOp::VersionChangeOp::SendFailureResult(nsresult aResultCode) {
16053 AssertIsOnOwningThread();
16054 MOZ_ASSERT(mOpenDatabaseOp);
16055 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
16056 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
16058 mOpenDatabaseOp->SetFailureCode(aResultCode);
16059 mOpenDatabaseOp->mState = State::SendingResults;
16061 MOZ_ALWAYS_SUCCEEDS(mOpenDatabaseOp->Run());
16063 return false;
16066 void OpenDatabaseOp::VersionChangeOp::Cleanup() {
16067 AssertIsOnOwningThread();
16068 MOZ_ASSERT(mOpenDatabaseOp);
16069 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
16071 mOpenDatabaseOp->mVersionChangeOp = nullptr;
16072 mOpenDatabaseOp = nullptr;
16074 #ifdef DEBUG
16075 // A bit hacky but the VersionChangeOp is not generated in response to a
16076 // child request like most other database operations. Do this to make our
16077 // assertions happy.
16079 // XXX: Depending on timing, in most cases, NoteActorDestroyed will not have
16080 // been destroyed before, but in some cases it has. This should be reworked in
16081 // a way this hack is not necessary. There are also several similar cases in
16082 // other *Op classes.
16083 if (!IsActorDestroyed()) {
16084 NoteActorDestroyed();
16086 #endif
16088 TransactionDatabaseOperationBase::Cleanup();
16091 void DeleteDatabaseOp::LoadPreviousVersion(nsIFile& aDatabaseFile) {
16092 AssertIsOnIOThread();
16093 MOZ_ASSERT(mState == State::DatabaseWorkOpen);
16094 MOZ_ASSERT(!mPreviousVersion);
16096 AUTO_PROFILER_LABEL("DeleteDatabaseOp::LoadPreviousVersion", DOM);
16098 nsresult rv;
16100 nsCOMPtr<mozIStorageService> ss =
16101 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
16102 if (NS_WARN_IF(NS_FAILED(rv))) {
16103 return;
16106 IndexedDatabaseManager* const idm = IndexedDatabaseManager::Get();
16107 MOZ_ASSERT(idm);
16109 const PersistenceType persistenceType =
16110 mCommonParams.metadata().persistenceType();
16111 const nsAString& databaseName = mCommonParams.metadata().name();
16113 SafeRefPtr<DatabaseFileManager> fileManager = idm->GetFileManager(
16114 persistenceType, mOriginMetadata.mOrigin, databaseName);
16116 if (!fileManager) {
16117 fileManager = MakeSafeRefPtr<DatabaseFileManager>(
16118 persistenceType, mOriginMetadata, databaseName, mDatabaseId.ref(),
16119 mDatabaseFilePath.ref(), mEnforcingQuota, mInPrivateBrowsing);
16122 const auto maybeKey =
16123 mInPrivateBrowsing
16124 ? Some(fileManager->MutableCipherKeyManagerRef().Ensure())
16125 : Nothing();
16127 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());
16129 // Pass -1 as the directoryLockId to disable quota checking, since we might
16130 // temporarily exceed quota before deleting the database.
16131 QM_TRY_INSPECT(const auto& dbFileUrl,
16132 GetDatabaseFileURL(aDatabaseFile, -1, maybeKey), QM_VOID);
16134 QM_TRY_UNWRAP(const NotNull<nsCOMPtr<mozIStorageConnection>> connection,
16135 OpenDatabaseAndHandleBusy(*ss, *dbFileUrl), QM_VOID);
16137 #ifdef DEBUG
16139 QM_TRY_INSPECT(const auto& stmt,
16140 CreateAndExecuteSingleStepStatement<
16141 SingleStepResult::ReturnNullIfNoResult>(
16142 *connection, "SELECT name FROM database"_ns),
16143 QM_VOID);
16145 QM_TRY(OkIf(stmt), QM_VOID);
16147 nsString databaseName;
16148 rv = stmt->GetString(0, databaseName);
16149 if (NS_WARN_IF(NS_FAILED(rv))) {
16150 return;
16153 MOZ_ASSERT(mCommonParams.metadata().name() == databaseName);
16155 #endif
16157 QM_TRY_INSPECT(const auto& stmt,
16158 CreateAndExecuteSingleStepStatement<
16159 SingleStepResult::ReturnNullIfNoResult>(
16160 *connection, "SELECT version FROM database"_ns),
16161 QM_VOID);
16163 QM_TRY(OkIf(stmt), QM_VOID);
16165 int64_t version;
16166 rv = stmt->GetInt64(0, &version);
16167 if (NS_WARN_IF(NS_FAILED(rv))) {
16168 return;
16171 mPreviousVersion = uint64_t(version);
16174 nsresult DeleteDatabaseOp::DatabaseOpen() {
16175 AssertIsOnOwningThread();
16176 MOZ_ASSERT(mState == State::DatabaseOpenPending);
16178 nsresult rv = SendToIOThread();
16179 if (NS_WARN_IF(NS_FAILED(rv))) {
16180 return rv;
16183 return NS_OK;
16186 nsresult DeleteDatabaseOp::DoDatabaseWork() {
16187 AssertIsOnIOThread();
16188 MOZ_ASSERT(mState == State::DatabaseWorkOpen);
16189 MOZ_ASSERT(mOriginMetadata.mPersistenceType ==
16190 mCommonParams.metadata().persistenceType());
16192 AUTO_PROFILER_LABEL("DeleteDatabaseOp::DoDatabaseWork", DOM);
16194 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
16195 !OperationMayProceed()) {
16196 IDB_REPORT_INTERNAL_ERR();
16197 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16200 const nsAString& databaseName = mCommonParams.metadata().name();
16202 QuotaManager* const quotaManager = QuotaManager::Get();
16203 MOZ_ASSERT(quotaManager);
16205 QM_TRY_UNWRAP(auto directory,
16206 quotaManager->GetOriginDirectory(mOriginMetadata));
16208 QM_TRY(MOZ_TO_RESULT(
16209 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME))));
16211 QM_TRY_UNWRAP(mDatabaseDirectoryPath, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
16212 nsString, directory, GetPath));
16214 mDatabaseFilenameBase =
16215 GetDatabaseFilenameBase(databaseName, mOriginMetadata.mIsPrivate);
16217 QM_TRY_INSPECT(
16218 const auto& dbFile,
16219 CloneFileAndAppend(*directory, mDatabaseFilenameBase + kSQLiteSuffix));
16221 #ifdef DEBUG
16223 QM_TRY_INSPECT(
16224 const auto& databaseFilePath,
16225 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath));
16227 MOZ_ASSERT(databaseFilePath == mDatabaseFilePath.ref());
16229 #endif
16231 QM_TRY_INSPECT(const bool& exists,
16232 MOZ_TO_RESULT_INVOKE_MEMBER(dbFile, Exists));
16234 if (exists) {
16235 // Parts of this function may fail but that shouldn't prevent us from
16236 // deleting the file eventually.
16237 LoadPreviousVersion(*dbFile);
16239 mState = State::BeginVersionChange;
16240 } else {
16241 mState = State::SendingResults;
16244 QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
16246 return NS_OK;
16249 nsresult DeleteDatabaseOp::BeginVersionChange() {
16250 AssertIsOnOwningThread();
16251 MOZ_ASSERT(mState == State::BeginVersionChange);
16252 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
16254 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
16255 IsActorDestroyed()) {
16256 IDB_REPORT_INTERNAL_ERR();
16257 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR));
16260 DatabaseActorInfo* info;
16261 if (gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)) {
16262 MOZ_ASSERT(!info->mWaitingFactoryOp);
16264 nsresult rv =
16265 SendVersionChangeMessages(info, Nothing(), mPreviousVersion, Nothing());
16266 if (NS_WARN_IF(NS_FAILED(rv))) {
16267 return rv;
16270 if (!mMaybeBlockedDatabases.IsEmpty()) {
16271 // If the actor gets destroyed, mWaitingFactoryOp will hold the last
16272 // strong reference to us.
16273 info->mWaitingFactoryOp = this;
16275 mState = State::WaitingForOtherDatabasesToClose;
16276 return NS_OK;
16280 // No other databases need to be notified, just make sure that all
16281 // transactions are complete.
16282 WaitForTransactions();
16283 return NS_OK;
16286 bool DeleteDatabaseOp::AreActorsAlive() {
16287 AssertIsOnOwningThread();
16289 return !IsActorDestroyed();
16292 nsresult DeleteDatabaseOp::DispatchToWorkThread() {
16293 AssertIsOnOwningThread();
16294 MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
16295 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
16297 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
16298 IsActorDestroyed()) {
16299 IDB_REPORT_INTERNAL_ERR();
16300 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16303 mState = State::DatabaseWorkVersionChange;
16305 RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
16307 QuotaManager* const quotaManager = QuotaManager::Get();
16308 MOZ_ASSERT(quotaManager);
16310 nsresult rv = quotaManager->IOThread()->Dispatch(versionChangeOp.forget(),
16311 NS_DISPATCH_NORMAL);
16312 if (NS_WARN_IF(NS_FAILED(rv))) {
16313 IDB_REPORT_INTERNAL_ERR();
16314 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16317 return NS_OK;
16320 void DeleteDatabaseOp::SendBlockedNotification() {
16321 AssertIsOnOwningThread();
16322 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
16324 if (!IsActorDestroyed()) {
16325 Unused << SendBlocked(mPreviousVersion);
16329 void DeleteDatabaseOp::SendResults() {
16330 AssertIsOnOwningThread();
16331 MOZ_ASSERT(mState == State::SendingResults);
16332 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
16334 DebugOnly<DatabaseActorInfo*> info = nullptr;
16335 MOZ_ASSERT_IF(mDatabaseId.isSome() && gLiveDatabaseHashtable &&
16336 gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info),
16337 !info->mWaitingFactoryOp);
16339 if (!IsActorDestroyed()) {
16340 FactoryRequestResponse response;
16342 if (!HasFailed()) {
16343 response = DeleteDatabaseRequestResponse(mPreviousVersion);
16344 } else {
16345 response = ClampResultCode(ResultCode());
16348 Unused << PBackgroundIDBFactoryRequestParent::Send__delete__(this,
16349 response);
16352 mDirectoryLock = nullptr;
16354 CleanupMetadata();
16356 FinishSendResults();
16359 nsresult DeleteDatabaseOp::VersionChangeOp::RunOnIOThread() {
16360 AssertIsOnIOThread();
16361 MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
16363 AUTO_PROFILER_LABEL("DeleteDatabaseOp::VersionChangeOp::RunOnIOThread", DOM);
16365 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
16366 !OperationMayProceed()) {
16367 IDB_REPORT_INTERNAL_ERR();
16368 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16371 const PersistenceType& persistenceType =
16372 mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
16374 QuotaManager* quotaManager =
16375 mDeleteDatabaseOp->mEnforcingQuota ? QuotaManager::Get() : nullptr;
16377 MOZ_ASSERT_IF(mDeleteDatabaseOp->mEnforcingQuota, quotaManager);
16379 nsCOMPtr<nsIFile> directory =
16380 GetFileForPath(mDeleteDatabaseOp->mDatabaseDirectoryPath);
16381 if (NS_WARN_IF(!directory)) {
16382 IDB_REPORT_INTERNAL_ERR();
16383 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16386 nsresult rv = RemoveDatabaseFilesAndDirectory(
16387 *directory, mDeleteDatabaseOp->mDatabaseFilenameBase, quotaManager,
16388 persistenceType, mDeleteDatabaseOp->mOriginMetadata,
16389 mDeleteDatabaseOp->mCommonParams.metadata().name());
16390 if (NS_WARN_IF(NS_FAILED(rv))) {
16391 return rv;
16394 rv = mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
16395 if (NS_WARN_IF(NS_FAILED(rv))) {
16396 return rv;
16399 return NS_OK;
16402 void DeleteDatabaseOp::VersionChangeOp::RunOnOwningThread() {
16403 AssertIsOnOwningThread();
16404 MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
16406 const RefPtr<DeleteDatabaseOp> deleteOp = std::move(mDeleteDatabaseOp);
16408 if (deleteOp->IsActorDestroyed()) {
16409 IDB_REPORT_INTERNAL_ERR();
16410 deleteOp->SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
16411 } else if (HasFailed()) {
16412 deleteOp->SetFailureCodeIfUnset(ResultCode());
16413 } else {
16414 DatabaseActorInfo* info;
16416 // Inform all the other databases that they are now invalidated. That
16417 // should remove the previous metadata from our table.
16418 if (gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId.ref(), &info)) {
16419 MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
16420 MOZ_ASSERT(!info->mWaitingFactoryOp);
16422 nsTArray<SafeRefPtr<Database>> liveDatabases;
16423 if (NS_WARN_IF(!liveDatabases.SetCapacity(info->mLiveDatabases.Length(),
16424 fallible))) {
16425 deleteOp->SetFailureCode(NS_ERROR_OUT_OF_MEMORY);
16426 } else {
16427 std::transform(info->mLiveDatabases.cbegin(),
16428 info->mLiveDatabases.cend(),
16429 MakeBackInserter(liveDatabases),
16430 [](const auto& aDatabase) -> SafeRefPtr<Database> {
16431 return {aDatabase.get(), AcquireStrongRefFromRawPtr{}};
16434 #ifdef DEBUG
16435 // The code below should result in the deletion of |info|. Set to null
16436 // here to make sure we find invalid uses later.
16437 info = nullptr;
16438 #endif
16440 for (const auto& database : liveDatabases) {
16441 database->Invalidate();
16444 MOZ_ASSERT(!gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId.ref()));
16449 // We hold a strong ref to the deleteOp, so it's safe to call Run() directly.
16451 deleteOp->mState = State::SendingResults;
16452 MOZ_ALWAYS_SUCCEEDS(deleteOp->Run());
16454 #ifdef DEBUG
16455 // A bit hacky but the DeleteDatabaseOp::VersionChangeOp is not really a
16456 // normal database operation that is tied to an actor. Do this to make our
16457 // assertions happy.
16458 NoteActorDestroyed();
16459 #endif
16462 nsresult DeleteDatabaseOp::VersionChangeOp::Run() {
16463 nsresult rv;
16465 if (IsOnIOThread()) {
16466 rv = RunOnIOThread();
16467 } else {
16468 RunOnOwningThread();
16469 rv = NS_OK;
16472 if (NS_WARN_IF(NS_FAILED(rv))) {
16473 SetFailureCodeIfUnset(rv);
16475 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
16478 return NS_OK;
16481 nsresult GetDatabasesOp::DatabasesNotAvailable() {
16482 AssertIsOnIOThread();
16483 MOZ_ASSERT(mState == State::DatabaseWorkOpen);
16485 mState = State::SendingResults;
16487 QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
16489 return NS_OK;
16492 nsresult GetDatabasesOp::DoDirectoryWork() {
16493 AssertIsOnIOThread();
16494 MOZ_ASSERT(mState == State::DirectoryWorkOpen);
16496 // This state (DirectoryWorkOpen) runs immediately on the I/O thread, before
16497 // waiting for existing factory operations to complete (at which point
16498 // DoDatabaseWork will be invoked). To match the spec, we must snapshot the
16499 // current state of any databases that are being created (version = 0) or
16500 // upgraded (version >= 1) now. If we only sampled these values in
16501 // DoDatabaseWork, we would only see their post-creation/post-upgrade
16502 // versions, which would be incorrect.
16504 IndexedDatabaseManager* const idm = IndexedDatabaseManager::Get();
16505 MOZ_ASSERT(idm);
16507 const auto& fileManagers =
16508 idm->GetFileManagers(mPersistenceType, mOriginMetadata.mOrigin);
16510 for (const auto& fileManager : fileManagers) {
16511 auto& metadata =
16512 mDatabaseMetadataTable.LookupOrInsert(fileManager->DatabaseFilePath());
16513 metadata.name() = fileManager->DatabaseName();
16514 metadata.version() = fileManager->DatabaseVersion();
16517 // Must set this before dispatching otherwise we will race with the IO thread.
16518 mState = State::DirectoryWorkDone;
16520 QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
16522 return NS_OK;
16525 nsresult GetDatabasesOp::DatabaseOpen() {
16526 AssertIsOnOwningThread();
16527 MOZ_ASSERT(mState == State::DatabaseOpenPending);
16529 nsresult rv = SendToIOThread();
16530 if (NS_WARN_IF(NS_FAILED(rv))) {
16531 return rv;
16534 return NS_OK;
16537 nsresult GetDatabasesOp::DoDatabaseWork() {
16538 AssertIsOnIOThread();
16539 MOZ_ASSERT(mState == State::DatabaseWorkOpen);
16541 AUTO_PROFILER_LABEL("GetDatabasesOp::DoDatabaseWork", DOM);
16543 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
16544 !OperationMayProceed()) {
16545 IDB_REPORT_INTERNAL_ERR();
16546 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
16549 QuotaManager* const quotaManager = QuotaManager::Get();
16550 MOZ_ASSERT(quotaManager);
16552 if (mPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
16553 QM_TRY(MOZ_TO_RESULT(
16554 quotaManager->EnsureTemporaryStorageIsInitializedInternal()));
16558 QM_TRY_INSPECT(const bool& exists,
16559 quotaManager->DoesOriginDirectoryExist(mOriginMetadata));
16560 if (!exists) {
16561 return DatabasesNotAvailable();
16565 QM_TRY(([&quotaManager, this]()
16566 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
16567 if (mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
16568 QM_TRY_RETURN(
16569 quotaManager->EnsurePersistentOriginIsInitialized(mOriginMetadata));
16572 QM_TRY_RETURN(quotaManager->EnsureTemporaryOriginIsInitialized(
16573 mPersistenceType, mOriginMetadata));
16575 .map([](const auto& res) { return Ok{}; })));
16578 QM_TRY_INSPECT(const bool& exists,
16579 quotaManager->DoesClientDirectoryExist(
16580 ClientMetadata{mOriginMetadata, Client::IDB}));
16581 if (!exists) {
16582 return DatabasesNotAvailable();
16586 QM_TRY_INSPECT(
16587 const auto& clientDirectory,
16588 ([&quotaManager, this]()
16589 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
16590 if (mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
16591 QM_TRY_RETURN(quotaManager->EnsurePersistentClientIsInitialized(
16592 ClientMetadata{mOriginMetadata, Client::IDB}));
16595 QM_TRY_RETURN(quotaManager->EnsureTemporaryClientIsInitialized(
16596 ClientMetadata{mOriginMetadata, Client::IDB}));
16598 .map([](const auto& res) { return res.first; })));
16600 QM_TRY_INSPECT(
16601 (const auto& [subdirsToProcess, databaseFilenames]),
16602 QuotaClient::GetDatabaseFilenames(*clientDirectory,
16603 /* aCanceled */ Atomic<bool>{false}));
16605 for (const auto& databaseFilename : databaseFilenames) {
16606 QM_TRY_INSPECT(
16607 const auto& databaseFile,
16608 CloneFileAndAppend(*clientDirectory, databaseFilename + kSQLiteSuffix));
16610 nsString path;
16611 databaseFile->GetPath(path);
16613 // Use the snapshotted values from DoDirectoryWork which correctly
16614 // snapshotted the state of any pending creations/upgrades. This does mean
16615 // that we need to skip reporting databases that had a version of 0 at that
16616 // time because they were still being created. In the event that any other
16617 // creation or upgrade requests are made after our operation is created,
16618 // this operation will block those, so it's not possible for this set of
16619 // data to get out of sync. The snapshotting (using cached database name
16620 // and version in DatabaseFileManager) also guarantees that we are not
16621 // touching the SQLite database here on the QuotaManager I/O thread which
16622 // is already open on the connection thread.
16624 auto metadata = mDatabaseMetadataTable.Lookup(path);
16625 if (metadata) {
16626 if (metadata->version() != 0) {
16627 mDatabaseMetadataArray.AppendElement(DatabaseMetadata(
16628 metadata->name(), metadata->version(), mPersistenceType));
16631 continue;
16634 // Since the database is not already open (there was no DatabaseFileManager
16635 // for snapshotting in DoDirectoryWork which could provide us with the
16636 // database name and version without needing to open the SQLite database),
16637 // it is safe and necessary for us to open the database on this thread and
16638 // retrieve its name and version. We do not need to worry about racing a
16639 // database open because database opens can only be processed on this
16640 // thread and we are performing the steps below synchronously.
16642 QM_TRY_INSPECT(
16643 const auto& fmDirectory,
16644 CloneFileAndAppend(*clientDirectory,
16645 databaseFilename + kFileManagerDirectoryNameSuffix));
16647 QM_TRY_UNWRAP(
16648 const NotNull<nsCOMPtr<mozIStorageConnection>> connection,
16649 CreateStorageConnection(*databaseFile, *fmDirectory, VoidString(),
16650 mOriginMetadata.mOrigin, mDirectoryLockId,
16651 TelemetryIdForFile(databaseFile), Nothing{}));
16654 // Load version information.
16655 QM_TRY_INSPECT(const auto& stmt,
16656 CreateAndExecuteSingleStepStatement<
16657 SingleStepResult::ReturnNullIfNoResult>(
16658 *connection, "SELECT name, version FROM database"_ns));
16660 QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED);
16662 QM_TRY_INSPECT(
16663 const auto& databaseName,
16664 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, stmt, GetString, 0));
16666 QM_TRY_INSPECT(const int64_t& version,
16667 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1));
16669 mDatabaseMetadataArray.AppendElement(
16670 DatabaseMetadata(databaseName, version, mPersistenceType));
16674 mState = State::SendingResults;
16676 QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)));
16678 return NS_OK;
16681 nsresult GetDatabasesOp::BeginVersionChange() {
16682 MOZ_CRASH("Not implemented because this should be unreachable.");
16685 bool GetDatabasesOp::AreActorsAlive() {
16686 MOZ_CRASH("Not implemented because this should be unreachable.");
16689 void GetDatabasesOp::SendBlockedNotification() {
16690 MOZ_CRASH("Not implemented because this should be unreachable.");
16693 nsresult GetDatabasesOp::DispatchToWorkThread() {
16694 MOZ_CRASH("Not implemented because this should be unreachable.");
16697 void GetDatabasesOp::SendResults() {
16698 AssertIsOnOwningThread();
16699 MOZ_ASSERT(mState == State::SendingResults);
16701 #ifdef DEBUG
16702 NoteActorDestroyed();
16703 #endif
16705 mResolver(mDatabaseMetadataArray);
16707 mDirectoryLock = nullptr;
16709 CleanupMetadata();
16711 FinishSendResults();
16714 TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
16715 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId)
16716 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
16717 aTransaction->GetLoggingInfo()->NextRequestSN()),
16718 mTransaction(WrapNotNull(std::move(aTransaction))),
16719 mRequestId(aRequestId),
16720 mTransactionIsAborted((*mTransaction)->IsAborted()),
16721 mTransactionLoggingSerialNumber((*mTransaction)->LoggingSerialNumber()) {
16722 MOZ_ASSERT(LoggingSerialNumber());
16725 TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
16726 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId,
16727 uint64_t aLoggingSerialNumber)
16728 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
16729 aLoggingSerialNumber),
16730 mTransaction(WrapNotNull(std::move(aTransaction))),
16731 mRequestId(aRequestId),
16732 mTransactionIsAborted((*mTransaction)->IsAborted()),
16733 mTransactionLoggingSerialNumber((*mTransaction)->LoggingSerialNumber()) {}
16735 TransactionDatabaseOperationBase::~TransactionDatabaseOperationBase() {
16736 MOZ_ASSERT(mInternalState == InternalState::Completed);
16737 MOZ_ASSERT(!mTransaction,
16738 "TransactionDatabaseOperationBase::Cleanup() was not called by a "
16739 "subclass!");
16742 #ifdef DEBUG
16744 void TransactionDatabaseOperationBase::AssertIsOnConnectionThread() const {
16745 (*mTransaction)->AssertIsOnConnectionThread();
16748 #endif // DEBUG
16750 uint64_t TransactionDatabaseOperationBase::StartOnConnectionPool(
16751 const nsID& aBackgroundChildLoggingId, const nsACString& aDatabaseId,
16752 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames,
16753 bool aIsWriteTransaction) {
16754 AssertIsOnOwningThread();
16755 MOZ_ASSERT(mInternalState == InternalState::Initial);
16757 // Must set mInternalState before dispatching otherwise we will race with the
16758 // connection thread.
16759 mInternalState = InternalState::DatabaseWork;
16761 return gConnectionPool->Start(aBackgroundChildLoggingId, aDatabaseId,
16762 aLoggingSerialNumber, aObjectStoreNames,
16763 aIsWriteTransaction, this);
16766 void TransactionDatabaseOperationBase::DispatchToConnectionPool() {
16767 AssertIsOnOwningThread();
16768 MOZ_ASSERT(mInternalState == InternalState::Initial);
16770 Unused << this->Run();
16773 void TransactionDatabaseOperationBase::RunOnConnectionThread() {
16774 MOZ_ASSERT(!IsOnBackgroundThread());
16775 MOZ_ASSERT(mInternalState == InternalState::DatabaseWork);
16776 MOZ_ASSERT(!HasFailed());
16778 AUTO_PROFILER_LABEL("TransactionDatabaseOperationBase::RunOnConnectionThread",
16779 DOM);
16781 // There are several cases where we don't actually have to to any work here.
16783 if (mTransactionIsAborted || (*mTransaction)->IsInvalidatedOnAnyThread()) {
16784 // This transaction is already set to be aborted or invalidated.
16785 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
16786 } else if (!OperationMayProceed()) {
16787 // The operation was canceled in some way, likely because the child process
16788 // has crashed.
16789 IDB_REPORT_INTERNAL_ERR();
16790 OverrideFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
16791 } else {
16792 Database& database = (*mTransaction)->GetMutableDatabase();
16794 // Here we're actually going to perform the database operation.
16795 nsresult rv = database.EnsureConnection();
16796 if (NS_WARN_IF(NS_FAILED(rv))) {
16797 SetFailureCode(rv);
16798 } else {
16799 DatabaseConnection* connection = database.GetConnection();
16800 MOZ_ASSERT(connection);
16802 auto& storageConnection = connection->MutableStorageConnection();
16804 AutoSetProgressHandler autoProgress;
16805 if (mLoggingSerialNumber) {
16806 rv = autoProgress.Register(storageConnection, this);
16807 if (NS_WARN_IF(NS_FAILED(rv))) {
16808 SetFailureCode(rv);
16812 if (NS_SUCCEEDED(rv)) {
16813 if (mLoggingSerialNumber) {
16814 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
16815 "Beginning database work", "DB Start",
16816 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
16817 mTransactionLoggingSerialNumber, mLoggingSerialNumber);
16820 rv = DoDatabaseWork(connection);
16822 if (mLoggingSerialNumber) {
16823 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
16824 "Finished database work", "DB End",
16825 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
16826 mTransactionLoggingSerialNumber, mLoggingSerialNumber);
16829 if (NS_FAILED(rv)) {
16830 SetFailureCode(rv);
16836 // Must set mInternalState before dispatching otherwise we will race with the
16837 // owning thread.
16838 if (HasPreprocessInfo()) {
16839 mInternalState = InternalState::SendingPreprocess;
16840 } else {
16841 mInternalState = InternalState::SendingResults;
16844 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
16847 bool TransactionDatabaseOperationBase::HasPreprocessInfo() { return false; }
16849 nsresult TransactionDatabaseOperationBase::SendPreprocessInfo() {
16850 return NS_OK;
16853 void TransactionDatabaseOperationBase::NoteContinueReceived() {
16854 AssertIsOnOwningThread();
16855 MOZ_ASSERT(mInternalState == InternalState::WaitingForContinue);
16857 mWaitingForContinue = false;
16859 mInternalState = InternalState::SendingResults;
16861 // This TransactionDatabaseOperationBase can only be held alive by the IPDL.
16862 // Run() can end up with clearing that last reference. So we need to add
16863 // a self reference here.
16864 RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip = this;
16866 Unused << this->Run();
16869 void TransactionDatabaseOperationBase::SendToConnectionPool() {
16870 AssertIsOnOwningThread();
16871 MOZ_ASSERT(mInternalState == InternalState::Initial);
16873 // Must set mInternalState before dispatching otherwise we will race with the
16874 // connection thread.
16875 mInternalState = InternalState::DatabaseWork;
16877 gConnectionPool->Dispatch((*mTransaction)->TransactionId(), this);
16879 (*mTransaction)->NoteActiveRequest();
16882 void TransactionDatabaseOperationBase::SendPreprocess() {
16883 AssertIsOnOwningThread();
16884 MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess);
16886 SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ true);
16889 void TransactionDatabaseOperationBase::SendResults() {
16890 AssertIsOnOwningThread();
16891 MOZ_ASSERT(mInternalState == InternalState::SendingResults);
16893 SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ false);
16896 void TransactionDatabaseOperationBase::SendPreprocessInfoOrResults(
16897 bool aSendPreprocessInfo) {
16898 AssertIsOnOwningThread();
16899 MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess ||
16900 mInternalState == InternalState::SendingResults);
16902 // The flag is raised only when there is no mUpdateRefcountFunction for the
16903 // executing operation. It assume that is because the previous
16904 // StartTransactionOp was failed to begin a write transaction and it reported
16905 // when this operation has already jumped to the Connection thread.
16906 MOZ_DIAGNOSTIC_ASSERT_IF(mAssumingPreviousOperationFail,
16907 (*mTransaction)->IsAborted());
16909 if (NS_WARN_IF(IsActorDestroyed())) {
16910 // Normally we wouldn't need to send any notifications if the actor was
16911 // already destroyed, but this can be a VersionChangeOp which needs to
16912 // notify its parent operation (OpenDatabaseOp) about the failure.
16913 // So SendFailureResult needs to be called even when the actor was
16914 // destroyed. Normal operations redundantly check if the actor was
16915 // destroyed in SendSuccessResult and SendFailureResult, therefore it's
16916 // ok to call it in all cases here.
16917 if (!HasFailed()) {
16918 IDB_REPORT_INTERNAL_ERR();
16919 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
16921 } else if ((*mTransaction)->IsInvalidated() || (*mTransaction)->IsAborted()) {
16922 // Aborted transactions always see their requests fail with ABORT_ERR,
16923 // even if the request succeeded or failed with another error.
16924 OverrideFailureCode(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
16927 const nsresult rv = [aSendPreprocessInfo, this] {
16928 if (HasFailed()) {
16929 return ResultCode();
16931 if (aSendPreprocessInfo) {
16932 // This should not release the IPDL reference.
16933 return SendPreprocessInfo();
16935 // This may release the IPDL reference.
16936 return SendSuccessResult();
16937 }();
16939 if (NS_FAILED(rv)) {
16940 SetFailureCodeIfUnset(rv);
16942 // This should definitely release the IPDL reference.
16943 if (!SendFailureResult(rv)) {
16944 // Abort the transaction.
16945 (*mTransaction)->Abort(rv, /* aForce */ false);
16949 if (aSendPreprocessInfo && !HasFailed()) {
16950 mInternalState = InternalState::WaitingForContinue;
16952 mWaitingForContinue = true;
16953 } else {
16954 if (mLoggingSerialNumber) {
16955 (*mTransaction)->NoteFinishedRequest(mRequestId, ResultCode());
16958 Cleanup();
16960 mInternalState = InternalState::Completed;
16964 bool TransactionDatabaseOperationBase::Init(TransactionBase& aTransaction) {
16965 AssertIsOnBackgroundThread();
16966 MOZ_ASSERT(mInternalState == InternalState::Initial);
16968 return true;
16971 void TransactionDatabaseOperationBase::Cleanup() {
16972 AssertIsOnOwningThread();
16973 MOZ_ASSERT(mInternalState == InternalState::SendingResults);
16975 mTransaction.destroy();
16978 NS_IMETHODIMP
16979 TransactionDatabaseOperationBase::Run() {
16980 switch (mInternalState) {
16981 case InternalState::Initial:
16982 SendToConnectionPool();
16983 return NS_OK;
16985 case InternalState::DatabaseWork:
16986 RunOnConnectionThread();
16987 return NS_OK;
16989 case InternalState::SendingPreprocess:
16990 SendPreprocess();
16991 return NS_OK;
16993 case InternalState::SendingResults:
16994 SendResults();
16995 return NS_OK;
16997 default:
16998 MOZ_CRASH("Bad state!");
17002 TransactionBase::CommitOp::CommitOp(SafeRefPtr<TransactionBase> aTransaction,
17003 nsresult aResultCode)
17004 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
17005 aTransaction->GetLoggingInfo()->NextRequestSN()),
17006 mTransaction(std::move(aTransaction)),
17007 mResultCode(aResultCode) {
17008 MOZ_ASSERT(mTransaction);
17009 MOZ_ASSERT(LoggingSerialNumber());
17012 nsresult TransactionBase::CommitOp::WriteAutoIncrementCounts() {
17013 MOZ_ASSERT(mTransaction);
17014 mTransaction->AssertIsOnConnectionThread();
17015 MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::Mode::ReadWrite ||
17016 mTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
17017 mTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
17018 mTransaction->GetMode() == IDBTransaction::Mode::VersionChange);
17020 const nsTArray<SafeRefPtr<FullObjectStoreMetadata>>& metadataArray =
17021 mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray;
17023 if (!metadataArray.IsEmpty()) {
17024 DatabaseConnection* connection =
17025 mTransaction->GetDatabase().GetConnection();
17026 MOZ_ASSERT(connection);
17028 // The parameter names are not used, parameters are bound by index only
17029 // locally in the same function.
17030 auto stmt = DatabaseConnection::LazyStatement(
17031 *connection,
17032 "UPDATE object_store "
17033 "SET auto_increment = :auto_increment WHERE id "
17034 "= :object_store_id;"_ns);
17036 for (const auto& metadata : metadataArray) {
17037 MOZ_ASSERT(!metadata->mDeleted);
17039 const int64_t nextAutoIncrementId = [&metadata] {
17040 const auto&& lockedAutoIncrementIds =
17041 metadata->mAutoIncrementIds.Lock();
17042 return lockedAutoIncrementIds->next;
17043 }();
17045 MOZ_ASSERT(nextAutoIncrementId > 1);
17047 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow());
17049 QM_TRY(MOZ_TO_RESULT(
17050 borrowedStmt->BindInt64ByIndex(1, metadata->mCommonMetadata.id())));
17052 QM_TRY(MOZ_TO_RESULT(
17053 borrowedStmt->BindInt64ByIndex(0, nextAutoIncrementId)));
17055 QM_TRY(MOZ_TO_RESULT(borrowedStmt->Execute()));
17059 return NS_OK;
17062 void TransactionBase::CommitOp::CommitOrRollbackAutoIncrementCounts() {
17063 MOZ_ASSERT(mTransaction);
17064 mTransaction->AssertIsOnConnectionThread();
17065 MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::Mode::ReadWrite ||
17066 mTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
17067 mTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
17068 mTransaction->GetMode() == IDBTransaction::Mode::VersionChange);
17070 const auto& metadataArray =
17071 mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray;
17073 if (!metadataArray.IsEmpty()) {
17074 bool committed = NS_SUCCEEDED(mResultCode);
17076 for (const auto& metadata : metadataArray) {
17077 auto&& lockedAutoIncrementIds = metadata->mAutoIncrementIds.Lock();
17079 if (committed) {
17080 lockedAutoIncrementIds->committed = lockedAutoIncrementIds->next;
17081 } else {
17082 lockedAutoIncrementIds->next = lockedAutoIncrementIds->committed;
17088 #ifdef DEBUG
17090 void TransactionBase::CommitOp::AssertForeignKeyConsistency(
17091 DatabaseConnection* aConnection) {
17092 MOZ_ASSERT(aConnection);
17093 MOZ_ASSERT(mTransaction);
17094 mTransaction->AssertIsOnConnectionThread();
17095 MOZ_ASSERT(mTransaction->GetMode() != IDBTransaction::Mode::ReadOnly);
17098 QM_TRY_INSPECT(
17099 const auto& pragmaStmt,
17100 CreateAndExecuteSingleStepStatement(
17101 aConnection->MutableStorageConnection(), "PRAGMA foreign_keys;"_ns),
17102 QM_ASSERT_UNREACHABLE_VOID);
17104 int32_t foreignKeysEnabled;
17105 MOZ_ALWAYS_SUCCEEDS(pragmaStmt->GetInt32(0, &foreignKeysEnabled));
17107 MOZ_ASSERT(foreignKeysEnabled,
17108 "Database doesn't have foreign keys enabled!");
17112 QM_TRY_INSPECT(const bool& foreignKeyError,
17113 CreateAndExecuteSingleStepStatement<
17114 SingleStepResult::ReturnNullIfNoResult>(
17115 aConnection->MutableStorageConnection(),
17116 "PRAGMA foreign_key_check;"_ns),
17117 QM_ASSERT_UNREACHABLE_VOID);
17119 MOZ_ASSERT(!foreignKeyError, "Database has inconsisistent foreign keys!");
17123 #endif // DEBUG
17125 NS_IMPL_ISUPPORTS_INHERITED0(TransactionBase::CommitOp, DatabaseOperationBase)
17127 NS_IMETHODIMP
17128 TransactionBase::CommitOp::Run() {
17129 MOZ_ASSERT(mTransaction);
17130 mTransaction->AssertIsOnConnectionThread();
17132 AUTO_PROFILER_LABEL("TransactionBase::CommitOp::Run", DOM);
17134 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
17135 "Beginning database work", "DB Start",
17136 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
17137 mTransaction->LoggingSerialNumber(), mLoggingSerialNumber);
17139 if (mTransaction->GetMode() != IDBTransaction::Mode::ReadOnly &&
17140 mTransaction->mHasBeenActiveOnConnectionThread) {
17141 if (DatabaseConnection* connection =
17142 mTransaction->GetDatabase().GetConnection()) {
17143 // May be null if the VersionChangeOp was canceled.
17144 DatabaseConnection::UpdateRefcountFunction* fileRefcountFunction =
17145 connection->GetUpdateRefcountFunction();
17147 if (NS_SUCCEEDED(mResultCode)) {
17148 if (fileRefcountFunction) {
17149 mResultCode = fileRefcountFunction->WillCommit();
17150 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode),
17151 "WillCommit() failed!");
17154 if (NS_SUCCEEDED(mResultCode)) {
17155 mResultCode = WriteAutoIncrementCounts();
17156 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode),
17157 "WriteAutoIncrementCounts() failed!");
17159 if (NS_SUCCEEDED(mResultCode)) {
17160 AssertForeignKeyConsistency(connection);
17162 mResultCode = connection->CommitWriteTransaction();
17163 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode), "Commit failed!");
17165 if (NS_SUCCEEDED(mResultCode) &&
17166 mTransaction->GetMode() ==
17167 IDBTransaction::Mode::ReadWriteFlush) {
17168 mResultCode = connection->Checkpoint();
17171 if (NS_SUCCEEDED(mResultCode) && fileRefcountFunction) {
17172 fileRefcountFunction->DidCommit();
17178 if (NS_FAILED(mResultCode)) {
17179 if (fileRefcountFunction) {
17180 fileRefcountFunction->DidAbort();
17183 connection->RollbackWriteTransaction();
17186 CommitOrRollbackAutoIncrementCounts();
17188 connection->FinishWriteTransaction();
17190 if (mTransaction->GetMode() == IDBTransaction::Mode::Cleanup) {
17191 connection->DoIdleProcessing(/* aNeedsCheckpoint */ true,
17192 /* aInterrupted */ Atomic<bool>(false));
17194 connection->EnableQuotaChecks();
17199 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
17200 "Finished database work", "DB End",
17201 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
17202 mTransaction->LoggingSerialNumber(), mLoggingSerialNumber);
17204 IDB_LOG_MARK_PARENT_TRANSACTION("Finished database work", "DB End",
17205 IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
17206 mTransaction->LoggingSerialNumber());
17208 return NS_OK;
17211 void TransactionBase::CommitOp::TransactionFinishedBeforeUnblock() {
17212 AssertIsOnBackgroundThread();
17213 MOZ_ASSERT(mTransaction);
17215 AUTO_PROFILER_LABEL("CommitOp::TransactionFinishedBeforeUnblock", DOM);
17217 if (!IsActorDestroyed()) {
17218 mTransaction->UpdateMetadata(mResultCode);
17222 void TransactionBase::CommitOp::TransactionFinishedAfterUnblock() {
17223 AssertIsOnBackgroundThread();
17224 MOZ_ASSERT(mTransaction);
17226 IDB_LOG_MARK_PARENT_TRANSACTION(
17227 "Finished with result 0x%" PRIx32, "Transaction finished (0x%" PRIx32 ")",
17228 IDB_LOG_ID_STRING(mTransaction->GetLoggingInfo()->Id()),
17229 mTransaction->LoggingSerialNumber(), static_cast<uint32_t>(mResultCode));
17231 mTransaction->SendCompleteNotification(ClampResultCode(mResultCode));
17233 mTransaction->GetMutableDatabase().UnregisterTransaction(*mTransaction);
17235 mTransaction = nullptr;
17237 #ifdef DEBUG
17238 // A bit hacky but the CommitOp is not really a normal database operation
17239 // that is tied to an actor. Do this to make our assertions happy.
17240 NoteActorDestroyed();
17241 #endif
17244 nsresult VersionChangeTransactionOp::SendSuccessResult() {
17245 AssertIsOnOwningThread();
17247 // Nothing to send here, the API assumes that this request always succeeds.
17248 return NS_OK;
17251 bool VersionChangeTransactionOp::SendFailureResult(nsresult aResultCode) {
17252 AssertIsOnOwningThread();
17254 // The only option here is to cause the transaction to abort.
17255 return false;
17258 void VersionChangeTransactionOp::Cleanup() {
17259 AssertIsOnOwningThread();
17261 #ifdef DEBUG
17262 // A bit hacky but the VersionChangeTransactionOp is not generated in response
17263 // to a child request like most other database operations. Do this to make our
17264 // assertions happy.
17265 NoteActorDestroyed();
17266 #endif
17268 TransactionDatabaseOperationBase::Cleanup();
17271 nsresult CreateObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17272 MOZ_ASSERT(aConnection);
17273 aConnection->AssertIsOnConnectionThread();
17275 AUTO_PROFILER_LABEL("CreateObjectStoreOp::DoDatabaseWork", DOM);
17277 #ifdef DEBUG
17279 // Make sure that we're not creating an object store with the same name as
17280 // another that already exists. This should be impossible because we should
17281 // have thrown an error long before now...
17282 // The parameter names are not used, parameters are bound by index only
17283 // locally in the same function.
17284 QM_TRY_INSPECT(const bool& hasResult,
17285 aConnection
17286 ->BorrowAndExecuteSingleStepStatement(
17287 "SELECT name "
17288 "FROM object_store "
17289 "WHERE name = :name;"_ns,
17290 [&self = *this](auto& stmt) -> Result<Ok, nsresult> {
17291 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(
17292 0, self.mMetadata.name())));
17293 return Ok{};
17295 .map(IsSome),
17296 QM_ASSERT_UNREACHABLE);
17298 MOZ_ASSERT(!hasResult);
17300 #endif
17302 DatabaseConnection::AutoSavepoint autoSave;
17303 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17304 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17306 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17307 #endif
17310 // The parameter names are not used, parameters are bound by index only
17311 // locally in the same function.
17312 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17313 "INSERT INTO object_store (id, auto_increment, name, key_path) "
17314 "VALUES (:id, :auto_increment, :name, :key_path);"_ns,
17315 [&metadata =
17316 mMetadata](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17317 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, metadata.id())));
17319 QM_TRY(MOZ_TO_RESULT(
17320 stmt.BindInt32ByIndex(1, metadata.autoIncrement() ? 1 : 0)));
17322 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(2, metadata.name())));
17324 if (metadata.keyPath().IsValid()) {
17325 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(
17326 3, metadata.keyPath().SerializeToString())));
17327 } else {
17328 QM_TRY(MOZ_TO_RESULT(stmt.BindNullByIndex(3)));
17331 return Ok{};
17332 })));
17334 #ifdef DEBUG
17336 int64_t id;
17337 MOZ_ALWAYS_SUCCEEDS(
17338 aConnection->MutableStorageConnection().GetLastInsertRowID(&id));
17339 MOZ_ASSERT(mMetadata.id() == id);
17341 #endif
17343 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
17345 return NS_OK;
17348 nsresult DeleteObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17349 MOZ_ASSERT(aConnection);
17350 aConnection->AssertIsOnConnectionThread();
17352 AUTO_PROFILER_LABEL("DeleteObjectStoreOp::DoDatabaseWork", DOM);
17354 #ifdef DEBUG
17356 // Make sure |mIsLastObjectStore| is telling the truth.
17357 QM_TRY_INSPECT(
17358 const auto& stmt,
17359 aConnection->BorrowCachedStatement("SELECT id FROM object_store;"_ns),
17360 QM_ASSERT_UNREACHABLE);
17362 bool foundThisObjectStore = false;
17363 bool foundOtherObjectStore = false;
17365 while (true) {
17366 bool hasResult;
17367 MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
17369 if (!hasResult) {
17370 break;
17373 int64_t id;
17374 MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id));
17376 if (id == mMetadata->mCommonMetadata.id()) {
17377 foundThisObjectStore = true;
17378 } else {
17379 foundOtherObjectStore = true;
17383 MOZ_ASSERT_IF(mIsLastObjectStore,
17384 foundThisObjectStore && !foundOtherObjectStore);
17385 MOZ_ASSERT_IF(!mIsLastObjectStore,
17386 foundThisObjectStore && foundOtherObjectStore);
17388 #endif
17390 DatabaseConnection::AutoSavepoint autoSave;
17391 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17392 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17394 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17395 #endif
17398 if (mIsLastObjectStore) {
17399 // We can just delete everything if this is the last object store.
17400 QM_TRY(MOZ_TO_RESULT(
17401 aConnection->ExecuteCachedStatement("DELETE FROM index_data;"_ns)));
17403 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17404 "DELETE FROM unique_index_data;"_ns)));
17406 QM_TRY(MOZ_TO_RESULT(
17407 aConnection->ExecuteCachedStatement("DELETE FROM object_data;"_ns)));
17409 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17410 "DELETE FROM object_store_index;"_ns)));
17412 QM_TRY(MOZ_TO_RESULT(
17413 aConnection->ExecuteCachedStatement("DELETE FROM object_store;"_ns)));
17414 } else {
17415 QM_TRY_INSPECT(
17416 const bool& hasIndexes,
17417 ObjectStoreHasIndexes(*aConnection, mMetadata->mCommonMetadata.id()));
17419 const auto bindObjectStoreIdToFirstParameter =
17420 [this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17421 QM_TRY(MOZ_TO_RESULT(
17422 stmt.BindInt64ByIndex(0, mMetadata->mCommonMetadata.id())));
17424 return Ok{};
17427 // The parameter name :object_store_id in the SQL statements below is not
17428 // used for binding, parameters are bound by index only locally by
17429 // bindObjectStoreIdToFirstParameter.
17430 if (hasIndexes) {
17431 QM_TRY(MOZ_TO_RESULT(DeleteObjectStoreDataTableRowsWithIndexes(
17432 aConnection, mMetadata->mCommonMetadata.id(), Nothing())));
17434 // Now clean up the object store index table.
17435 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17436 "DELETE FROM object_store_index "
17437 "WHERE object_store_id = :object_store_id;"_ns,
17438 bindObjectStoreIdToFirstParameter)));
17439 } else {
17440 // We only have to worry about object data if this object store has no
17441 // indexes.
17442 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17443 "DELETE FROM object_data "
17444 "WHERE object_store_id = :object_store_id;"_ns,
17445 bindObjectStoreIdToFirstParameter)));
17448 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17449 "DELETE FROM object_store "
17450 "WHERE id = :object_store_id;"_ns,
17451 bindObjectStoreIdToFirstParameter)));
17453 #ifdef DEBUG
17455 int32_t deletedRowCount;
17456 MOZ_ALWAYS_SUCCEEDS(
17457 aConnection->MutableStorageConnection().GetAffectedRows(
17458 &deletedRowCount));
17459 MOZ_ASSERT(deletedRowCount == 1);
17461 #endif
17464 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
17466 if (mMetadata->mCommonMetadata.autoIncrement()) {
17467 Transaction().ForgetModifiedAutoIncrementObjectStore(*mMetadata);
17470 return NS_OK;
17473 nsresult RenameObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17474 MOZ_ASSERT(aConnection);
17475 aConnection->AssertIsOnConnectionThread();
17477 AUTO_PROFILER_LABEL("RenameObjectStoreOp::DoDatabaseWork", DOM);
17479 #ifdef DEBUG
17481 // Make sure that we're not renaming an object store with the same name as
17482 // another that already exists. This should be impossible because we should
17483 // have thrown an error long before now...
17484 // The parameter names are not used, parameters are bound by index only
17485 // locally in the same function.
17486 QM_TRY_INSPECT(
17487 const bool& hasResult,
17488 aConnection
17489 ->BorrowAndExecuteSingleStepStatement(
17490 "SELECT name "
17491 "FROM object_store "
17492 "WHERE name = :name AND id != :id;"_ns,
17493 [&self = *this](auto& stmt) -> Result<Ok, nsresult> {
17494 QM_TRY(
17495 MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName)));
17497 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mId)));
17498 return Ok{};
17500 .map(IsSome),
17501 QM_ASSERT_UNREACHABLE);
17503 MOZ_ASSERT(!hasResult);
17505 #endif
17507 DatabaseConnection::AutoSavepoint autoSave;
17508 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17509 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17511 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17512 #endif
17515 // The parameter names are not used, parameters are bound by index only
17516 // locally in the same function.
17517 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17518 "UPDATE object_store "
17519 "SET name = :name "
17520 "WHERE id = :id;"_ns,
17521 [&self = *this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17522 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName)));
17524 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mId)));
17526 return Ok{};
17527 })));
17529 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
17531 return NS_OK;
17534 CreateIndexOp::CreateIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
17535 const IndexOrObjectStoreId aObjectStoreId,
17536 const IndexMetadata& aMetadata)
17537 : VersionChangeTransactionOp(std::move(aTransaction)),
17538 mMetadata(aMetadata),
17539 mFileManager(Transaction().GetDatabase().GetFileManagerPtr()),
17540 mDatabaseId(Transaction().DatabaseId()),
17541 mObjectStoreId(aObjectStoreId) {
17542 MOZ_ASSERT(aObjectStoreId);
17543 MOZ_ASSERT(aMetadata.id());
17544 MOZ_ASSERT(mFileManager);
17545 MOZ_ASSERT(!mDatabaseId.IsEmpty());
17548 nsresult CreateIndexOp::InsertDataFromObjectStore(
17549 DatabaseConnection* aConnection) {
17550 MOZ_ASSERT(aConnection);
17551 aConnection->AssertIsOnConnectionThread();
17552 MOZ_ASSERT(mMaybeUniqueIndexTable);
17554 AUTO_PROFILER_LABEL("CreateIndexOp::InsertDataFromObjectStore", DOM);
17556 auto& storageConnection = aConnection->MutableStorageConnection();
17558 RefPtr<UpdateIndexDataValuesFunction> updateFunction =
17559 new UpdateIndexDataValuesFunction(this, aConnection,
17560 Transaction().GetDatabasePtr());
17562 constexpr auto updateFunctionName = "update_index_data_values"_ns;
17564 nsresult rv =
17565 storageConnection.CreateFunction(updateFunctionName, 4, updateFunction);
17566 if (NS_WARN_IF(NS_FAILED(rv))) {
17567 return rv;
17570 rv = InsertDataFromObjectStoreInternal(aConnection);
17572 MOZ_ALWAYS_SUCCEEDS(storageConnection.RemoveFunction(updateFunctionName));
17574 if (NS_WARN_IF(NS_FAILED(rv))) {
17575 return rv;
17578 return NS_OK;
17581 nsresult CreateIndexOp::InsertDataFromObjectStoreInternal(
17582 DatabaseConnection* aConnection) const {
17583 MOZ_ASSERT(aConnection);
17584 aConnection->AssertIsOnConnectionThread();
17585 MOZ_ASSERT(mMaybeUniqueIndexTable);
17587 MOZ_ASSERT(aConnection->HasStorageConnection());
17589 // The parameter names are not used, parameters are bound by index only
17590 // locally in the same function.
17591 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17592 "UPDATE object_data "
17593 "SET index_data_values = update_index_data_values "
17594 "(key, index_data_values, file_ids, data) "
17595 "WHERE object_store_id = :object_store_id;"_ns,
17596 [objectStoredId =
17597 mObjectStoreId](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17598 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, objectStoredId)));
17600 return Ok{};
17601 })));
17603 return NS_OK;
17606 bool CreateIndexOp::Init(TransactionBase& aTransaction) {
17607 AssertIsOnBackgroundThread();
17608 MOZ_ASSERT(mObjectStoreId);
17609 MOZ_ASSERT(mMaybeUniqueIndexTable.isNothing());
17611 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
17612 aTransaction.GetMetadataForObjectStoreId(mObjectStoreId);
17613 MOZ_ASSERT(objectStoreMetadata);
17615 const uint32_t indexCount = objectStoreMetadata->mIndexes.Count();
17616 if (!indexCount) {
17617 return true;
17620 auto uniqueIndexTable = UniqueIndexTable{indexCount};
17622 for (const auto& value : objectStoreMetadata->mIndexes.Values()) {
17623 MOZ_ASSERT(!uniqueIndexTable.Contains(value->mCommonMetadata.id()));
17625 if (NS_WARN_IF(!uniqueIndexTable.InsertOrUpdate(
17626 value->mCommonMetadata.id(), value->mCommonMetadata.unique(),
17627 fallible))) {
17628 IDB_REPORT_INTERNAL_ERR();
17629 NS_WARNING("out of memory");
17630 return false;
17634 uniqueIndexTable.MarkImmutable();
17636 mMaybeUniqueIndexTable.emplace(std::move(uniqueIndexTable));
17638 return true;
17641 nsresult CreateIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17642 MOZ_ASSERT(aConnection);
17643 aConnection->AssertIsOnConnectionThread();
17645 AUTO_PROFILER_LABEL("CreateIndexOp::DoDatabaseWork", DOM);
17647 #ifdef DEBUG
17649 // Make sure that we're not creating an index with the same name and object
17650 // store as another that already exists. This should be impossible because
17651 // we should have thrown an error long before now...
17652 // The parameter names are not used, parameters are bound by index only
17653 // locally in the same function.
17654 QM_TRY_INSPECT(
17655 const bool& hasResult,
17656 aConnection
17657 ->BorrowAndExecuteSingleStepStatement(
17658 "SELECT name "
17659 "FROM object_store_index "
17660 "WHERE object_store_id = :object_store_id AND name = :name;"_ns,
17661 [&self = *this](auto& stmt) -> Result<Ok, nsresult> {
17662 QM_TRY(MOZ_TO_RESULT(
17663 stmt.BindInt64ByIndex(0, self.mObjectStoreId)));
17664 QM_TRY(MOZ_TO_RESULT(
17665 stmt.BindStringByIndex(1, self.mMetadata.name())));
17666 return Ok{};
17668 .map(IsSome),
17669 QM_ASSERT_UNREACHABLE);
17671 MOZ_ASSERT(!hasResult);
17673 #endif
17675 DatabaseConnection::AutoSavepoint autoSave;
17676 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
17677 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
17679 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
17680 #endif
17683 // The parameter names are not used, parameters are bound by index only
17684 // locally in the same function.
17685 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
17686 "INSERT INTO object_store_index (id, name, key_path, unique_index, "
17687 "multientry, object_store_id, locale, "
17688 "is_auto_locale) "
17689 "VALUES (:id, :name, :key_path, :unique, :multientry, "
17690 ":object_store_id, :locale, :is_auto_locale)"_ns,
17691 [&metadata = mMetadata, objectStoreId = mObjectStoreId](
17692 mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
17693 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, metadata.id())));
17695 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(1, metadata.name())));
17697 QM_TRY(MOZ_TO_RESULT(
17698 stmt.BindStringByIndex(2, metadata.keyPath().SerializeToString())));
17700 QM_TRY(
17701 MOZ_TO_RESULT(stmt.BindInt32ByIndex(3, metadata.unique() ? 1 : 0)));
17703 QM_TRY(MOZ_TO_RESULT(
17704 stmt.BindInt32ByIndex(4, metadata.multiEntry() ? 1 : 0)));
17705 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(5, objectStoreId)));
17707 QM_TRY(MOZ_TO_RESULT(
17708 metadata.locale().IsEmpty()
17709 ? stmt.BindNullByIndex(6)
17710 : stmt.BindUTF8StringByIndex(6, metadata.locale())));
17712 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByIndex(7, metadata.autoLocale())));
17714 return Ok{};
17715 })));
17717 #ifdef DEBUG
17719 int64_t id;
17720 MOZ_ALWAYS_SUCCEEDS(
17721 aConnection->MutableStorageConnection().GetLastInsertRowID(&id));
17722 MOZ_ASSERT(mMetadata.id() == id);
17724 #endif
17726 QM_TRY(MOZ_TO_RESULT(InsertDataFromObjectStore(aConnection)));
17728 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
17730 return NS_OK;
17733 NS_IMPL_ISUPPORTS(CreateIndexOp::UpdateIndexDataValuesFunction,
17734 mozIStorageFunction);
17736 NS_IMETHODIMP
17737 CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall(
17738 mozIStorageValueArray* aValues, nsIVariant** _retval) {
17739 MOZ_ASSERT(aValues);
17740 MOZ_ASSERT(_retval);
17741 MOZ_ASSERT(mConnection);
17742 mConnection->AssertIsOnConnectionThread();
17743 MOZ_ASSERT(mOp);
17744 MOZ_ASSERT(mOp->mFileManager);
17746 AUTO_PROFILER_LABEL(
17747 "CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall", DOM);
17749 #ifdef DEBUG
17751 uint32_t argCount;
17752 MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
17753 MOZ_ASSERT(argCount == 4); // key, index_data_values, file_ids, data
17755 int32_t valueType;
17756 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
17757 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
17759 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType));
17760 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
17761 valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
17763 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType));
17764 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
17765 valueType == mozIStorageValueArray::VALUE_TYPE_TEXT);
17767 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType));
17768 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB ||
17769 valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
17771 #endif
17773 QM_TRY_UNWRAP(auto cloneInfo, GetStructuredCloneReadInfoFromValueArray(
17774 aValues,
17775 /* aDataIndex */ 3,
17776 /* aFileIdsIndex */ 2, *mOp->mFileManager));
17778 const IndexMetadata& metadata = mOp->mMetadata;
17779 const IndexOrObjectStoreId& objectStoreId = mOp->mObjectStoreId;
17781 // XXX does this really need a non-const cloneInfo?
17782 QM_TRY_INSPECT(const auto& updateInfos,
17783 DeserializeIndexValueToUpdateInfos(
17784 metadata.id(), metadata.keyPath(), metadata.multiEntry(),
17785 metadata.locale(), cloneInfo));
17787 if (updateInfos.IsEmpty()) {
17788 // XXX See if we can do this without copying...
17790 nsCOMPtr<nsIVariant> unmodifiedValue;
17792 // No changes needed, just return the original value.
17793 QM_TRY_INSPECT(const int32_t& valueType,
17794 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 1));
17796 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
17797 valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
17799 if (valueType == mozIStorageValueArray::VALUE_TYPE_NULL) {
17800 unmodifiedValue = new storage::NullVariant();
17801 unmodifiedValue.forget(_retval);
17802 return NS_OK;
17805 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
17807 const uint8_t* blobData;
17808 uint32_t blobDataLength;
17809 QM_TRY(
17810 MOZ_TO_RESULT(aValues->GetSharedBlob(1, &blobDataLength, &blobData)));
17812 const std::pair<uint8_t*, int> copiedBlobDataPair(
17813 static_cast<uint8_t*>(malloc(blobDataLength)), blobDataLength);
17815 if (!copiedBlobDataPair.first) {
17816 IDB_REPORT_INTERNAL_ERR();
17817 return NS_ERROR_OUT_OF_MEMORY;
17820 memcpy(copiedBlobDataPair.first, blobData, blobDataLength);
17822 unmodifiedValue = new storage::AdoptedBlobVariant(copiedBlobDataPair);
17823 unmodifiedValue.forget(_retval);
17825 return NS_OK;
17828 Key key;
17829 QM_TRY(MOZ_TO_RESULT(key.SetFromValueArray(aValues, 0)));
17831 QM_TRY_UNWRAP(auto indexValues, ReadCompressedIndexDataValues(*aValues, 1));
17833 const bool hadPreviousIndexValues = !indexValues.IsEmpty();
17835 const uint32_t updateInfoCount = updateInfos.Length();
17837 QM_TRY(OkIf(indexValues.SetCapacity(indexValues.Length() + updateInfoCount,
17838 fallible)),
17839 NS_ERROR_OUT_OF_MEMORY, IDB_REPORT_INTERNAL_ERR_LAMBDA);
17841 // First construct the full list to update the index_data_values row.
17842 for (const IndexUpdateInfo& info : updateInfos) {
17843 MOZ_ALWAYS_TRUE(indexValues.InsertElementSorted(
17844 IndexDataValue(metadata.id(), metadata.unique(), info.value(),
17845 info.localizedValue()),
17846 fallible));
17849 QM_TRY_UNWRAP((auto [indexValuesBlob, indexValuesBlobLength]),
17850 MakeCompressedIndexDataValues(indexValues));
17852 MOZ_ASSERT(!indexValuesBlobLength == !(indexValuesBlob.get()));
17854 nsCOMPtr<nsIVariant> value;
17856 if (!indexValuesBlob) {
17857 value = new storage::NullVariant();
17859 value.forget(_retval);
17860 return NS_OK;
17863 // Now insert the new table rows. We only need to construct a new list if
17864 // the full list is different.
17865 if (hadPreviousIndexValues) {
17866 indexValues.ClearAndRetainStorage();
17868 MOZ_ASSERT(indexValues.Capacity() >= updateInfoCount);
17870 for (const IndexUpdateInfo& info : updateInfos) {
17871 MOZ_ALWAYS_TRUE(indexValues.InsertElementSorted(
17872 IndexDataValue(metadata.id(), metadata.unique(), info.value(),
17873 info.localizedValue()),
17874 fallible));
17878 QM_TRY(MOZ_TO_RESULT(
17879 InsertIndexTableRows(mConnection, objectStoreId, key, indexValues)));
17881 value = new storage::AdoptedBlobVariant(
17882 std::pair(indexValuesBlob.release(), indexValuesBlobLength));
17884 value.forget(_retval);
17885 return NS_OK;
17888 DeleteIndexOp::DeleteIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction,
17889 const IndexOrObjectStoreId aObjectStoreId,
17890 const IndexOrObjectStoreId aIndexId,
17891 const bool aUnique, const bool aIsLastIndex)
17892 : VersionChangeTransactionOp(std::move(aTransaction)),
17893 mObjectStoreId(aObjectStoreId),
17894 mIndexId(aIndexId),
17895 mUnique(aUnique),
17896 mIsLastIndex(aIsLastIndex) {
17897 MOZ_ASSERT(aObjectStoreId);
17898 MOZ_ASSERT(aIndexId);
17901 nsresult DeleteIndexOp::RemoveReferencesToIndex(
17902 DatabaseConnection* aConnection, const Key& aObjectStoreKey,
17903 nsTArray<IndexDataValue>& aIndexValues) const {
17904 MOZ_ASSERT(!NS_IsMainThread());
17905 MOZ_ASSERT(!IsOnBackgroundThread());
17906 MOZ_ASSERT(aConnection);
17907 MOZ_ASSERT(!aObjectStoreKey.IsUnset());
17908 MOZ_ASSERT_IF(!mIsLastIndex, !aIndexValues.IsEmpty());
17910 AUTO_PROFILER_LABEL("DeleteIndexOp::RemoveReferencesToIndex", DOM);
17912 if (mIsLastIndex) {
17913 // There is no need to parse the previous entry in the index_data_values
17914 // column if this is the last index. Simply set it to NULL.
17915 QM_TRY_INSPECT(const auto& stmt,
17916 aConnection->BorrowCachedStatement(
17917 "UPDATE object_data "
17918 "SET index_data_values = NULL "
17919 "WHERE object_store_id = :"_ns +
17920 kStmtParamNameObjectStoreId + " AND key = :"_ns +
17921 kStmtParamNameKey + ";"_ns));
17923 QM_TRY(MOZ_TO_RESULT(
17924 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId)));
17926 QM_TRY(MOZ_TO_RESULT(
17927 aObjectStoreKey.BindToStatement(&*stmt, kStmtParamNameKey)));
17929 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
17931 return NS_OK;
17935 IndexDataValue search;
17936 search.mIndexId = mIndexId;
17938 // Use raw pointers for search to avoid redundant index validity checks.
17939 // Maybe this should better be encapsulated in nsTArray.
17940 const auto* const begin = aIndexValues.Elements();
17941 const auto* const end = aIndexValues.Elements() + aIndexValues.Length();
17943 const auto indexIdComparator = [](const IndexDataValue& aA,
17944 const IndexDataValue& aB) {
17945 return aA.mIndexId < aB.mIndexId;
17948 MOZ_ASSERT(std::is_sorted(begin, end, indexIdComparator));
17950 const auto [beginRange, endRange] =
17951 std::equal_range(begin, end, search, indexIdComparator);
17952 if (beginRange == end) {
17953 IDB_REPORT_INTERNAL_ERR();
17954 return NS_ERROR_FILE_CORRUPTED;
17957 aIndexValues.RemoveElementsAt(beginRange - begin, endRange - beginRange);
17960 QM_TRY(MOZ_TO_RESULT(UpdateIndexValues(aConnection, mObjectStoreId,
17961 aObjectStoreKey, aIndexValues)));
17963 return NS_OK;
17966 nsresult DeleteIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) {
17967 MOZ_ASSERT(aConnection);
17968 aConnection->AssertIsOnConnectionThread();
17970 #ifdef DEBUG
17972 // Make sure |mIsLastIndex| is telling the truth.
17973 // The parameter names are not used, parameters are bound by index only
17974 // locally in the same function.
17975 QM_TRY_INSPECT(const auto& stmt,
17976 aConnection->BorrowCachedStatement(
17977 "SELECT id "
17978 "FROM object_store_index "
17979 "WHERE object_store_id = :object_store_id;"_ns),
17980 QM_ASSERT_UNREACHABLE);
17982 MOZ_ALWAYS_SUCCEEDS(stmt->BindInt64ByIndex(0, mObjectStoreId));
17984 bool foundThisIndex = false;
17985 bool foundOtherIndex = false;
17987 while (true) {
17988 bool hasResult;
17989 MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
17991 if (!hasResult) {
17992 break;
17995 int64_t id;
17996 MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id));
17998 if (id == mIndexId) {
17999 foundThisIndex = true;
18000 } else {
18001 foundOtherIndex = true;
18005 MOZ_ASSERT_IF(mIsLastIndex, foundThisIndex && !foundOtherIndex);
18006 MOZ_ASSERT_IF(!mIsLastIndex, foundThisIndex && foundOtherIndex);
18008 #endif
18010 AUTO_PROFILER_LABEL("DeleteIndexOp::DoDatabaseWork", DOM);
18012 DatabaseConnection::AutoSavepoint autoSave;
18013 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
18014 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
18016 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
18017 #endif
18020 // mozStorage warns that these statements trigger a sort operation but we
18021 // don't care because this is a very rare call and we expect it to be slow.
18022 // The cost of having an index on this field is too high.
18023 QM_TRY_INSPECT(
18024 const auto& selectStmt,
18025 aConnection->BorrowCachedStatement(
18026 mUnique
18027 ? (mIsLastIndex
18028 ? "/* do not warn (bug someone else) */ "
18029 "SELECT value, object_data_key "
18030 "FROM unique_index_data "
18031 "WHERE index_id = :"_ns +
18032 kStmtParamNameIndexId +
18033 " ORDER BY object_data_key ASC;"_ns
18034 : "/* do not warn (bug out) */ "
18035 "SELECT unique_index_data.value, "
18036 "unique_index_data.object_data_key, "
18037 "object_data.index_data_values "
18038 "FROM unique_index_data "
18039 "JOIN object_data "
18040 "ON unique_index_data.object_data_key = object_data.key "
18041 "WHERE unique_index_data.index_id = :"_ns +
18042 kStmtParamNameIndexId +
18043 " AND object_data.object_store_id = :"_ns +
18044 kStmtParamNameObjectStoreId +
18045 " ORDER BY unique_index_data.object_data_key ASC;"_ns)
18046 : (mIsLastIndex
18047 ? "/* do not warn (bug me not) */ "
18048 "SELECT value, object_data_key "
18049 "FROM index_data "
18050 "WHERE index_id = :"_ns +
18051 kStmtParamNameIndexId +
18052 " AND object_store_id = :"_ns +
18053 kStmtParamNameObjectStoreId +
18054 " ORDER BY object_data_key ASC;"_ns
18055 : "/* do not warn (bug off) */ "
18056 "SELECT index_data.value, "
18057 "index_data.object_data_key, "
18058 "object_data.index_data_values "
18059 "FROM index_data "
18060 "JOIN object_data "
18061 "ON index_data.object_data_key = object_data.key "
18062 "WHERE index_data.index_id = :"_ns +
18063 kStmtParamNameIndexId +
18064 " AND object_data.object_store_id = :"_ns +
18065 kStmtParamNameObjectStoreId +
18066 " ORDER BY index_data.object_data_key ASC;"_ns)));
18068 QM_TRY(MOZ_TO_RESULT(
18069 selectStmt->BindInt64ByName(kStmtParamNameIndexId, mIndexId)));
18071 if (!mUnique || !mIsLastIndex) {
18072 QM_TRY(MOZ_TO_RESULT(selectStmt->BindInt64ByName(
18073 kStmtParamNameObjectStoreId, mObjectStoreId)));
18076 Key lastObjectStoreKey;
18077 IndexDataValuesAutoArray lastIndexValues;
18079 QM_TRY(CollectWhileHasResult(
18080 *selectStmt,
18081 [this, &aConnection, &lastObjectStoreKey, &lastIndexValues,
18082 deleteIndexRowStmt =
18083 DatabaseConnection::LazyStatement{
18084 *aConnection,
18085 mUnique
18086 ? "DELETE FROM unique_index_data "
18087 "WHERE index_id = :"_ns +
18088 kStmtParamNameIndexId + " AND value = :"_ns +
18089 kStmtParamNameValue + ";"_ns
18090 : "DELETE FROM index_data "
18091 "WHERE index_id = :"_ns +
18092 kStmtParamNameIndexId + " AND value = :"_ns +
18093 kStmtParamNameValue + " AND object_data_key = :"_ns +
18094 kStmtParamNameObjectDataKey + ";"_ns}](
18095 auto& selectStmt) mutable -> Result<Ok, nsresult> {
18096 // We always need the index key to delete the index row.
18097 Key indexKey;
18098 QM_TRY(MOZ_TO_RESULT(indexKey.SetFromStatement(&selectStmt, 0)));
18100 QM_TRY(OkIf(!indexKey.IsUnset()), Err(NS_ERROR_FILE_CORRUPTED),
18101 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18103 // Don't call |lastObjectStoreKey.BindToStatement()| directly because we
18104 // don't want to copy the same key multiple times.
18105 const uint8_t* objectStoreKeyData;
18106 uint32_t objectStoreKeyDataLength;
18107 QM_TRY(MOZ_TO_RESULT(selectStmt.GetSharedBlob(
18108 1, &objectStoreKeyDataLength, &objectStoreKeyData)));
18110 QM_TRY(OkIf(objectStoreKeyDataLength), Err(NS_ERROR_FILE_CORRUPTED),
18111 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18113 const nsDependentCString currentObjectStoreKeyBuffer(
18114 reinterpret_cast<const char*>(objectStoreKeyData),
18115 objectStoreKeyDataLength);
18116 if (currentObjectStoreKeyBuffer != lastObjectStoreKey.GetBuffer()) {
18117 // We just walked to the next object store key.
18118 if (!lastObjectStoreKey.IsUnset()) {
18119 // Before we move on to the next key we need to update the previous
18120 // key's index_data_values column.
18121 QM_TRY(MOZ_TO_RESULT(RemoveReferencesToIndex(
18122 aConnection, lastObjectStoreKey, lastIndexValues)));
18125 // Save the object store key.
18126 lastObjectStoreKey = Key(currentObjectStoreKeyBuffer);
18128 // And the |index_data_values| row if this isn't the only index.
18129 if (!mIsLastIndex) {
18130 lastIndexValues.ClearAndRetainStorage();
18131 QM_TRY(MOZ_TO_RESULT(
18132 ReadCompressedIndexDataValues(selectStmt, 2, lastIndexValues)));
18134 QM_TRY(OkIf(!lastIndexValues.IsEmpty()),
18135 Err(NS_ERROR_FILE_CORRUPTED),
18136 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18140 // Now delete the index row.
18142 QM_TRY_INSPECT(const auto& borrowedDeleteIndexRowStmt,
18143 deleteIndexRowStmt.Borrow());
18145 QM_TRY(MOZ_TO_RESULT(borrowedDeleteIndexRowStmt->BindInt64ByName(
18146 kStmtParamNameIndexId, mIndexId)));
18148 QM_TRY(MOZ_TO_RESULT(indexKey.BindToStatement(
18149 &*borrowedDeleteIndexRowStmt, kStmtParamNameValue)));
18151 if (!mUnique) {
18152 QM_TRY(MOZ_TO_RESULT(lastObjectStoreKey.BindToStatement(
18153 &*borrowedDeleteIndexRowStmt, kStmtParamNameObjectDataKey)));
18156 QM_TRY(MOZ_TO_RESULT(borrowedDeleteIndexRowStmt->Execute()));
18159 return Ok{};
18160 }));
18162 // Take care of the last key.
18163 if (!lastObjectStoreKey.IsUnset()) {
18164 MOZ_ASSERT_IF(!mIsLastIndex, !lastIndexValues.IsEmpty());
18166 QM_TRY(MOZ_TO_RESULT(RemoveReferencesToIndex(
18167 aConnection, lastObjectStoreKey, lastIndexValues)));
18170 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
18171 "DELETE FROM object_store_index "
18172 "WHERE id = :index_id;"_ns,
18173 [indexId =
18174 mIndexId](mozIStorageStatement& deleteStmt) -> Result<Ok, nsresult> {
18175 QM_TRY(MOZ_TO_RESULT(deleteStmt.BindInt64ByIndex(0, indexId)));
18177 return Ok{};
18178 })));
18180 #ifdef DEBUG
18182 int32_t deletedRowCount;
18183 MOZ_ALWAYS_SUCCEEDS(aConnection->MutableStorageConnection().GetAffectedRows(
18184 &deletedRowCount));
18185 MOZ_ASSERT(deletedRowCount == 1);
18187 #endif
18189 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
18191 return NS_OK;
18194 nsresult RenameIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) {
18195 MOZ_ASSERT(aConnection);
18196 aConnection->AssertIsOnConnectionThread();
18198 AUTO_PROFILER_LABEL("RenameIndexOp::DoDatabaseWork", DOM);
18200 #ifdef DEBUG
18202 // Make sure that we're not renaming an index with the same name as another
18203 // that already exists. This should be impossible because we should have
18204 // thrown an error long before now...
18205 // The parameter names are not used, parameters are bound by index only
18206 // locally in the same function.
18207 QM_TRY_INSPECT(const bool& hasResult,
18208 aConnection
18209 ->BorrowAndExecuteSingleStepStatement(
18210 "SELECT name "
18211 "FROM object_store_index "
18212 "WHERE object_store_id = :object_store_id "
18213 "AND name = :name "
18214 "AND id != :id;"_ns,
18215 [&self = *this](auto& stmt) -> Result<Ok, nsresult> {
18216 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(
18217 0, self.mObjectStoreId)));
18218 QM_TRY(MOZ_TO_RESULT(
18219 stmt.BindStringByIndex(1, self.mNewName)));
18220 QM_TRY(MOZ_TO_RESULT(
18221 stmt.BindInt64ByIndex(2, self.mIndexId)));
18223 return Ok{};
18225 .map(IsSome),
18226 QM_ASSERT_UNREACHABLE);
18228 MOZ_ASSERT(!hasResult);
18230 #else
18231 Unused << mObjectStoreId;
18232 #endif
18234 DatabaseConnection::AutoSavepoint autoSave;
18235 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
18236 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
18238 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
18239 #endif
18242 // The parameter names are not used, parameters are bound by index only
18243 // locally in the same function.
18244 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
18245 "UPDATE object_store_index "
18246 "SET name = :name "
18247 "WHERE id = :id;"_ns,
18248 [&self = *this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> {
18249 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName)));
18251 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mIndexId)));
18253 return Ok{};
18254 })));
18256 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
18258 return NS_OK;
18261 Result<bool, nsresult> NormalTransactionOp::ObjectStoreHasIndexes(
18262 DatabaseConnection& aConnection, const IndexOrObjectStoreId aObjectStoreId,
18263 const bool aMayHaveIndexes) {
18264 aConnection.AssertIsOnConnectionThread();
18265 MOZ_ASSERT(aObjectStoreId);
18267 if (Transaction().GetMode() == IDBTransaction::Mode::VersionChange &&
18268 aMayHaveIndexes) {
18269 // If this is a version change transaction then mObjectStoreMayHaveIndexes
18270 // could be wrong (e.g. if a unique index failed to be created due to a
18271 // constraint error). We have to check on this thread by asking the database
18272 // directly.
18273 QM_TRY_RETURN(DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
18274 aObjectStoreId));
18277 #ifdef DEBUG
18278 QM_TRY_INSPECT(
18279 const bool& hasIndexes,
18280 DatabaseOperationBase::ObjectStoreHasIndexes(aConnection, aObjectStoreId),
18281 QM_ASSERT_UNREACHABLE);
18282 MOZ_ASSERT(aMayHaveIndexes == hasIndexes);
18283 #endif
18285 return aMayHaveIndexes;
18288 Result<PreprocessParams, nsresult> NormalTransactionOp::GetPreprocessParams() {
18289 return PreprocessParams{};
18292 nsresult NormalTransactionOp::SendPreprocessInfo() {
18293 AssertIsOnOwningThread();
18294 MOZ_ASSERT(!IsActorDestroyed());
18296 QM_TRY_INSPECT(const auto& params, GetPreprocessParams());
18298 MOZ_ASSERT(params.type() != PreprocessParams::T__None);
18300 if (NS_WARN_IF(!PBackgroundIDBRequestParent::SendPreprocess(params))) {
18301 IDB_REPORT_INTERNAL_ERR();
18302 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
18305 return NS_OK;
18308 nsresult NormalTransactionOp::SendSuccessResult() {
18309 AssertIsOnOwningThread();
18311 if (!IsActorDestroyed()) {
18312 static const size_t kMaxIDBMsgOverhead = 1024 * 1024 * 10; // 10MB
18313 const uint32_t maximalSizeFromPref =
18314 IndexedDatabaseManager::MaxSerializedMsgSize();
18315 MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead);
18316 const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead;
18318 RequestResponse response;
18319 size_t responseSize = kMaxMessageSize;
18320 GetResponse(response, &responseSize);
18322 if (responseSize >= kMaxMessageSize) {
18323 nsPrintfCString warning(
18324 "The serialized value is too large"
18325 " (size=%zu bytes, max=%zu bytes).",
18326 responseSize, kMaxMessageSize);
18327 NS_WARNING(warning.get());
18328 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
18331 MOZ_ASSERT(response.type() != RequestResponse::T__None);
18333 if (response.type() == RequestResponse::Tnsresult) {
18334 MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
18336 return response.get_nsresult();
18339 if (NS_WARN_IF(
18340 !PBackgroundIDBRequestParent::Send__delete__(this, response))) {
18341 IDB_REPORT_INTERNAL_ERR();
18342 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
18346 #ifdef DEBUG
18347 mResponseSent = true;
18348 #endif
18350 return NS_OK;
18353 bool NormalTransactionOp::SendFailureResult(nsresult aResultCode) {
18354 AssertIsOnOwningThread();
18355 MOZ_ASSERT(NS_FAILED(aResultCode));
18357 bool result = false;
18359 if (!IsActorDestroyed()) {
18360 result = PBackgroundIDBRequestParent::Send__delete__(
18361 this, ClampResultCode(aResultCode));
18364 #ifdef DEBUG
18365 mResponseSent = true;
18366 #endif
18368 return result;
18371 void NormalTransactionOp::Cleanup() {
18372 AssertIsOnOwningThread();
18373 MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
18375 TransactionDatabaseOperationBase::Cleanup();
18378 void NormalTransactionOp::ActorDestroy(ActorDestroyReason aWhy) {
18379 AssertIsOnOwningThread();
18381 NoteActorDestroyed();
18383 // Assume ActorDestroy can happen at any time, so we can't probe the current
18384 // state since mInternalState can be modified on any thread (only one thread
18385 // at a time based on the state machine).
18386 // However we can use mWaitingForContinue which is only touched on the owning
18387 // thread. If mWaitingForContinue is true, we can also modify mInternalState
18388 // since we are guaranteed that there are no pending runnables which would
18389 // probe mInternalState to decide what code needs to run (there shouldn't be
18390 // any running runnables on other threads either).
18392 if (IsWaitingForContinue()) {
18393 NoteContinueReceived();
18396 // We don't have to handle the case when mWaitingForContinue is not true since
18397 // it means that either nothing has been initialized yet, so nothing to
18398 // cleanup or there are pending runnables that will detect that the actor has
18399 // been destroyed and cleanup accordingly.
18402 mozilla::ipc::IPCResult NormalTransactionOp::RecvContinue(
18403 const PreprocessResponse& aResponse) {
18404 AssertIsOnOwningThread();
18406 switch (aResponse.type()) {
18407 case PreprocessResponse::Tnsresult:
18408 SetFailureCode(aResponse.get_nsresult());
18409 break;
18411 case PreprocessResponse::TObjectStoreGetPreprocessResponse:
18412 case PreprocessResponse::TObjectStoreGetAllPreprocessResponse:
18413 break;
18415 default:
18416 MOZ_CRASH("Should never get here!");
18419 NoteContinueReceived();
18421 return IPC_OK();
18424 ObjectStoreAddOrPutRequestOp::ObjectStoreAddOrPutRequestOp(
18425 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId,
18426 RequestParams&& aParams)
18427 : NormalTransactionOp(std::move(aTransaction), aRequestId),
18428 mParams(
18429 std::move(aParams.type() == RequestParams::TObjectStoreAddParams
18430 ? aParams.get_ObjectStoreAddParams().commonParams()
18431 : aParams.get_ObjectStorePutParams().commonParams())),
18432 mOriginMetadata(Transaction().GetDatabase().OriginMetadata()),
18433 mPersistenceType(Transaction().GetDatabase().Type()),
18434 mOverwrite(aParams.type() == RequestParams::TObjectStorePutParams),
18435 mObjectStoreMayHaveIndexes(false) {
18436 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreAddParams ||
18437 aParams.type() == RequestParams::TObjectStorePutParams);
18439 mMetadata =
18440 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId());
18441 MOZ_ASSERT(mMetadata);
18443 mObjectStoreMayHaveIndexes = mMetadata->HasLiveIndexes();
18445 mDataOverThreshold =
18446 snappy::MaxCompressedLength(mParams.cloneInfo().data().data.Size()) >
18447 IndexedDatabaseManager::DataThreshold();
18450 nsresult ObjectStoreAddOrPutRequestOp::RemoveOldIndexDataValues(
18451 DatabaseConnection* aConnection) {
18452 AssertIsOnConnectionThread();
18453 MOZ_ASSERT(aConnection);
18454 MOZ_ASSERT(mOverwrite);
18455 MOZ_ASSERT(!mResponse.IsUnset());
18457 #ifdef DEBUG
18459 QM_TRY_INSPECT(const bool& hasIndexes,
18460 DatabaseOperationBase::ObjectStoreHasIndexes(
18461 *aConnection, mParams.objectStoreId()),
18462 QM_ASSERT_UNREACHABLE);
18464 MOZ_ASSERT(hasIndexes,
18465 "Don't use this slow method if there are no indexes!");
18467 #endif
18469 QM_TRY_INSPECT(
18470 const auto& indexValuesStmt,
18471 aConnection->BorrowAndExecuteSingleStepStatement(
18472 "SELECT index_data_values "
18473 "FROM object_data "
18474 "WHERE object_store_id = :"_ns +
18475 kStmtParamNameObjectStoreId + " AND key = :"_ns +
18476 kStmtParamNameKey + ";"_ns,
18477 [&self = *this](auto& stmt) -> mozilla::Result<Ok, nsresult> {
18478 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(
18479 kStmtParamNameObjectStoreId, self.mParams.objectStoreId())));
18481 QM_TRY(MOZ_TO_RESULT(
18482 self.mResponse.BindToStatement(&stmt, kStmtParamNameKey)));
18484 return Ok{};
18485 }));
18487 if (indexValuesStmt) {
18488 QM_TRY_INSPECT(const auto& existingIndexValues,
18489 ReadCompressedIndexDataValues(**indexValuesStmt, 0));
18491 QM_TRY(MOZ_TO_RESULT(
18492 DeleteIndexDataTableRows(aConnection, mResponse, existingIndexValues)));
18495 return NS_OK;
18498 bool ObjectStoreAddOrPutRequestOp::Init(TransactionBase& aTransaction) {
18499 AssertIsOnOwningThread();
18501 const nsTArray<IndexUpdateInfo>& indexUpdateInfos =
18502 mParams.indexUpdateInfos();
18504 if (!indexUpdateInfos.IsEmpty()) {
18505 mUniqueIndexTable.emplace();
18507 for (const auto& updateInfo : indexUpdateInfos) {
18508 auto indexMetadata = mMetadata->mIndexes.Lookup(updateInfo.indexId());
18509 MOZ_ALWAYS_TRUE(indexMetadata);
18511 MOZ_ASSERT(!(*indexMetadata)->mDeleted);
18513 const IndexOrObjectStoreId& indexId =
18514 (*indexMetadata)->mCommonMetadata.id();
18515 const bool& unique = (*indexMetadata)->mCommonMetadata.unique();
18517 MOZ_ASSERT(indexId == updateInfo.indexId());
18518 MOZ_ASSERT_IF(!(*indexMetadata)->mCommonMetadata.multiEntry(),
18519 !mUniqueIndexTable.ref().Contains(indexId));
18521 if (NS_WARN_IF(!mUniqueIndexTable.ref().InsertOrUpdate(indexId, unique,
18522 fallible))) {
18523 return false;
18526 } else if (mOverwrite) {
18527 mUniqueIndexTable.emplace();
18530 if (mUniqueIndexTable.isSome()) {
18531 mUniqueIndexTable.ref().MarkImmutable();
18534 QM_TRY_UNWRAP(
18535 mStoredFileInfos,
18536 TransformIntoNewArray(
18537 mParams.fileAddInfos(),
18538 [](const auto& fileAddInfo) {
18539 MOZ_ASSERT(fileAddInfo.type() == StructuredCloneFileBase::eBlob ||
18540 fileAddInfo.type() ==
18541 StructuredCloneFileBase::eMutableFile);
18543 switch (fileAddInfo.type()) {
18544 case StructuredCloneFileBase::eBlob: {
18545 PBackgroundIDBDatabaseFileParent* file =
18546 fileAddInfo.file().AsParent();
18547 MOZ_ASSERT(file);
18549 auto* const fileActor = static_cast<DatabaseFile*>(file);
18550 MOZ_ASSERT(fileActor);
18552 return StoredFileInfo::CreateForBlob(
18553 fileActor->GetFileInfoPtr(), fileActor);
18556 default:
18557 MOZ_CRASH("Should never get here!");
18560 fallible),
18561 false);
18563 if (mDataOverThreshold) {
18564 auto fileInfo =
18565 aTransaction.GetDatabase().GetFileManager().CreateFileInfo();
18566 if (NS_WARN_IF(!fileInfo)) {
18567 return false;
18570 mStoredFileInfos.EmplaceBack(StoredFileInfo::CreateForStructuredClone(
18571 std::move(fileInfo),
18572 MakeRefPtr<SCInputStream>(mParams.cloneInfo().data().data)));
18575 return true;
18578 nsresult ObjectStoreAddOrPutRequestOp::DoDatabaseWork(
18579 DatabaseConnection* aConnection) {
18580 MOZ_ASSERT(aConnection);
18581 aConnection->AssertIsOnConnectionThread();
18582 MOZ_ASSERT(aConnection->HasStorageConnection());
18584 AUTO_PROFILER_LABEL("ObjectStoreAddOrPutRequestOp::DoDatabaseWork", DOM);
18586 DatabaseConnection::AutoSavepoint autoSave;
18587 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
18588 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
18590 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
18591 #endif
18594 QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
18595 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
18596 mObjectStoreMayHaveIndexes));
18598 // This will be the final key we use.
18599 Key& key = mResponse;
18600 key = mParams.key();
18602 const bool keyUnset = key.IsUnset();
18603 const IndexOrObjectStoreId osid = mParams.objectStoreId();
18605 // First delete old index_data_values if we're overwriting something and we
18606 // have indexes.
18607 if (mOverwrite && !keyUnset && objectStoreHasIndexes) {
18608 QM_TRY(MOZ_TO_RESULT(RemoveOldIndexDataValues(aConnection)));
18611 int64_t autoIncrementNum = 0;
18614 // The "|| keyUnset" here is mostly a debugging tool. If a key isn't
18615 // specified we should never have a collision and so it shouldn't matter
18616 // if we allow overwrite or not. By not allowing overwrite we raise
18617 // detectable errors rather than corrupting data.
18618 const auto optReplaceDirective =
18619 (!mOverwrite || keyUnset) ? ""_ns : "OR REPLACE "_ns;
18620 QM_TRY_INSPECT(const auto& stmt,
18621 aConnection->BorrowCachedStatement(
18622 "INSERT "_ns + optReplaceDirective +
18623 "INTO object_data "
18624 "(object_store_id, key, file_ids, data) "
18625 "VALUES (:"_ns +
18626 kStmtParamNameObjectStoreId + ", :"_ns +
18627 kStmtParamNameKey + ", :"_ns + kStmtParamNameFileIds +
18628 ", :"_ns + kStmtParamNameData + ");"_ns));
18630 QM_TRY(MOZ_TO_RESULT(
18631 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, osid)));
18633 const SerializedStructuredCloneWriteInfo& cloneInfo = mParams.cloneInfo();
18634 const JSStructuredCloneData& cloneData = cloneInfo.data().data;
18635 const size_t cloneDataSize = cloneData.Size();
18637 MOZ_ASSERT(!keyUnset || mMetadata->mCommonMetadata.autoIncrement(),
18638 "Should have key unless autoIncrement");
18640 if (mMetadata->mCommonMetadata.autoIncrement()) {
18641 if (keyUnset) {
18643 const auto&& lockedAutoIncrementIds =
18644 mMetadata->mAutoIncrementIds.Lock();
18646 autoIncrementNum = lockedAutoIncrementIds->next;
18649 MOZ_ASSERT(autoIncrementNum > 0);
18651 if (autoIncrementNum > (1LL << 53)) {
18652 return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
18655 QM_TRY(key.SetFromInteger(autoIncrementNum));
18657 // Update index keys if primary key is preserved in child.
18658 for (auto& updateInfo : mParams.indexUpdateInfos()) {
18659 updateInfo.value().MaybeUpdateAutoIncrementKey(autoIncrementNum);
18661 } else if (key.IsFloat()) {
18662 double numericKey = key.ToFloat();
18663 numericKey = std::min(numericKey, double(1LL << 53));
18664 numericKey = floor(numericKey);
18666 const auto&& lockedAutoIncrementIds =
18667 mMetadata->mAutoIncrementIds.Lock();
18668 if (numericKey >= lockedAutoIncrementIds->next) {
18669 autoIncrementNum = numericKey;
18673 if (keyUnset && mMetadata->mCommonMetadata.keyPath().IsValid()) {
18674 const SerializedStructuredCloneWriteInfo& cloneInfo =
18675 mParams.cloneInfo();
18676 MOZ_ASSERT(cloneInfo.offsetToKeyProp());
18677 MOZ_ASSERT(cloneDataSize > sizeof(uint64_t));
18678 MOZ_ASSERT(cloneInfo.offsetToKeyProp() <=
18679 (cloneDataSize - sizeof(uint64_t)));
18681 // Special case where someone put an object into an autoIncrement'ing
18682 // objectStore with no key in its keyPath set. We needed to figure out
18683 // which row id we would get above before we could set that properly.
18684 uint64_t keyPropValue =
18685 ReinterpretDoubleAsUInt64(static_cast<double>(autoIncrementNum));
18687 static const size_t keyPropSize = sizeof(uint64_t);
18689 char keyPropBuffer[keyPropSize];
18690 LittleEndian::writeUint64(keyPropBuffer, keyPropValue);
18692 auto iter = cloneData.Start();
18693 MOZ_ALWAYS_TRUE(cloneData.Advance(iter, cloneInfo.offsetToKeyProp()));
18694 MOZ_ALWAYS_TRUE(
18695 cloneData.UpdateBytes(iter, keyPropBuffer, keyPropSize));
18699 key.BindToStatement(&*stmt, kStmtParamNameKey);
18701 if (mDataOverThreshold) {
18702 // The data we store in the SQLite database is a (signed) 64-bit integer.
18703 // The flags are left-shifted 32 bits so the max value is 0xFFFFFFFF.
18704 // The file_ids index occupies the lower 32 bits and its max is
18705 // 0xFFFFFFFF.
18706 static const uint32_t kCompressedFlag = (1 << 0);
18708 uint32_t flags = 0;
18709 flags |= kCompressedFlag;
18711 const uint32_t index = mStoredFileInfos.Length() - 1;
18713 const int64_t data = (uint64_t(flags) << 32) | index;
18715 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameData, data)));
18716 } else {
18717 AutoTArray<char, 4096> flatCloneData; // 4096 from JSStructuredCloneData
18718 QM_TRY(OkIf(flatCloneData.SetLength(cloneDataSize, fallible)),
18719 Err(NS_ERROR_OUT_OF_MEMORY));
18722 auto iter = cloneData.Start();
18723 MOZ_ALWAYS_TRUE(
18724 cloneData.ReadBytes(iter, flatCloneData.Elements(), cloneDataSize));
18727 // Compress the bytes before adding into the database.
18728 const char* const uncompressed = flatCloneData.Elements();
18729 const size_t uncompressedLength = cloneDataSize;
18731 size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
18733 UniqueFreePtr<char> compressed(
18734 static_cast<char*>(malloc(compressedLength)));
18735 if (NS_WARN_IF(!compressed)) {
18736 return NS_ERROR_OUT_OF_MEMORY;
18739 snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(),
18740 &compressedLength);
18742 uint8_t* const dataBuffer =
18743 reinterpret_cast<uint8_t*>(compressed.release());
18744 const size_t dataBufferLength = compressedLength;
18746 QM_TRY(MOZ_TO_RESULT(stmt->BindAdoptedBlobByName(
18747 kStmtParamNameData, dataBuffer, dataBufferLength)));
18750 if (!mStoredFileInfos.IsEmpty()) {
18751 // Moved outside the loop to allow it to be cached when demanded by the
18752 // first write. (We may have mStoredFileInfos without any required
18753 // writes.)
18754 Maybe<FileHelper> fileHelper;
18755 nsAutoString fileIds;
18757 for (auto& storedFileInfo : mStoredFileInfos) {
18758 MOZ_ASSERT(storedFileInfo.IsValid());
18760 QM_TRY_INSPECT(const auto& inputStream,
18761 storedFileInfo.GetInputStream());
18763 if (inputStream) {
18764 if (fileHelper.isNothing()) {
18765 fileHelper.emplace(Transaction().GetDatabase().GetFileManagerPtr());
18766 QM_TRY(MOZ_TO_RESULT(fileHelper->Init()),
18767 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
18768 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18771 const DatabaseFileInfo& fileInfo = storedFileInfo.GetFileInfo();
18772 const DatabaseFileManager& fileManager = fileInfo.Manager();
18774 const auto file = fileHelper->GetFile(fileInfo);
18775 QM_TRY(OkIf(file), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
18776 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18778 const auto journalFile = fileHelper->GetJournalFile(fileInfo);
18779 QM_TRY(OkIf(journalFile), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
18780 IDB_REPORT_INTERNAL_ERR_LAMBDA);
18782 nsCString fileKeyId;
18783 fileKeyId.AppendInt(fileInfo.Id());
18785 const auto maybeKey =
18786 fileManager.IsInPrivateBrowsingMode()
18787 ? fileManager.MutableCipherKeyManagerRef().Get(fileKeyId)
18788 : Nothing();
18790 QM_TRY(MOZ_TO_RESULT(fileHelper->CreateFileFromStream(
18791 *file, *journalFile, *inputStream,
18792 storedFileInfo.ShouldCompress(), maybeKey))
18793 .mapErr([](const nsresult rv) {
18794 if (NS_ERROR_GET_MODULE(rv) !=
18795 NS_ERROR_MODULE_DOM_INDEXEDDB) {
18796 IDB_REPORT_INTERNAL_ERR();
18797 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
18799 return rv;
18801 QM_PROPAGATE,
18802 ([&fileManager, &file = *file,
18803 &journalFile = *journalFile](const auto) {
18804 // Try to remove the file if the copy failed.
18805 QM_TRY(MOZ_TO_RESULT(
18806 fileManager.SyncDeleteFile(file, journalFile)),
18807 QM_VOID);
18808 }));
18810 storedFileInfo.NotifyWriteSucceeded();
18813 if (!fileIds.IsEmpty()) {
18814 fileIds.Append(' ');
18816 storedFileInfo.Serialize(fileIds);
18819 QM_TRY(MOZ_TO_RESULT(
18820 stmt->BindStringByName(kStmtParamNameFileIds, fileIds)));
18821 } else {
18822 QM_TRY(MOZ_TO_RESULT(stmt->BindNullByName(kStmtParamNameFileIds)));
18825 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_PROPAGATE,
18826 [keyUnset = DebugOnly{keyUnset}](const nsresult rv) {
18827 if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
18828 MOZ_ASSERT(!keyUnset, "Generated key had a collision!");
18833 // Update our indexes if needed.
18834 if (!mParams.indexUpdateInfos().IsEmpty()) {
18835 MOZ_ASSERT(mUniqueIndexTable.isSome());
18837 // Write the index_data_values column.
18838 QM_TRY_INSPECT(const auto& indexValues,
18839 IndexDataValuesFromUpdateInfos(mParams.indexUpdateInfos(),
18840 mUniqueIndexTable.ref()));
18842 QM_TRY(
18843 MOZ_TO_RESULT(UpdateIndexValues(aConnection, osid, key, indexValues)));
18845 QM_TRY(MOZ_TO_RESULT(
18846 InsertIndexTableRows(aConnection, osid, key, indexValues)));
18849 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
18851 if (autoIncrementNum) {
18853 auto&& lockedAutoIncrementIds = mMetadata->mAutoIncrementIds.Lock();
18855 lockedAutoIncrementIds->next = autoIncrementNum + 1;
18858 Transaction().NoteModifiedAutoIncrementObjectStore(mMetadata);
18861 return NS_OK;
18864 void ObjectStoreAddOrPutRequestOp::GetResponse(RequestResponse& aResponse,
18865 size_t* aResponseSize) {
18866 AssertIsOnOwningThread();
18868 if (mOverwrite) {
18869 aResponse = ObjectStorePutResponse(mResponse);
18870 *aResponseSize = mResponse.GetBuffer().Length();
18871 } else {
18872 aResponse = ObjectStoreAddResponse(mResponse);
18873 *aResponseSize = mResponse.GetBuffer().Length();
18877 void ObjectStoreAddOrPutRequestOp::Cleanup() {
18878 AssertIsOnOwningThread();
18880 mStoredFileInfos.Clear();
18882 NormalTransactionOp::Cleanup();
18885 NS_IMPL_ISUPPORTS(ObjectStoreAddOrPutRequestOp::SCInputStream, nsIInputStream)
18887 NS_IMETHODIMP
18888 ObjectStoreAddOrPutRequestOp::SCInputStream::Close() { return NS_OK; }
18890 NS_IMETHODIMP
18891 ObjectStoreAddOrPutRequestOp::SCInputStream::Available(uint64_t* _retval) {
18892 return NS_ERROR_NOT_IMPLEMENTED;
18895 NS_IMETHODIMP
18896 ObjectStoreAddOrPutRequestOp::SCInputStream::StreamStatus() { return NS_OK; }
18898 NS_IMETHODIMP
18899 ObjectStoreAddOrPutRequestOp::SCInputStream::Read(char* aBuf, uint32_t aCount,
18900 uint32_t* _retval) {
18901 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
18904 NS_IMETHODIMP
18905 ObjectStoreAddOrPutRequestOp::SCInputStream::ReadSegments(
18906 nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
18907 uint32_t* _retval) {
18908 *_retval = 0;
18910 while (aCount) {
18911 uint32_t count = std::min(uint32_t(mIter.RemainingInSegment()), aCount);
18912 if (!count) {
18913 // We've run out of data in the last segment.
18914 break;
18917 uint32_t written;
18918 nsresult rv =
18919 aWriter(this, aClosure, mIter.Data(), *_retval, count, &written);
18920 if (NS_WARN_IF(NS_FAILED(rv))) {
18921 // InputStreams do not propagate errors to caller.
18922 return NS_OK;
18925 // Writer should write what we asked it to write.
18926 MOZ_ASSERT(written == count);
18928 *_retval += count;
18929 aCount -= count;
18931 if (NS_WARN_IF(!mData.Advance(mIter, count))) {
18932 // InputStreams do not propagate errors to caller.
18933 return NS_OK;
18937 return NS_OK;
18940 NS_IMETHODIMP
18941 ObjectStoreAddOrPutRequestOp::SCInputStream::IsNonBlocking(bool* _retval) {
18942 *_retval = false;
18943 return NS_OK;
18946 ObjectStoreGetRequestOp::ObjectStoreGetRequestOp(
18947 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId,
18948 const RequestParams& aParams, bool aGetAll)
18949 : NormalTransactionOp(std::move(aTransaction), aRequestId),
18950 mObjectStoreId(aGetAll
18951 ? aParams.get_ObjectStoreGetAllParams().objectStoreId()
18952 : aParams.get_ObjectStoreGetParams().objectStoreId()),
18953 mDatabase(Transaction().GetDatabasePtr()),
18954 mOptionalKeyRange(
18955 aGetAll ? aParams.get_ObjectStoreGetAllParams().optionalKeyRange()
18956 : Some(aParams.get_ObjectStoreGetParams().keyRange())),
18957 mBackgroundParent(Transaction().GetBackgroundParent()),
18958 mPreprocessInfoCount(0),
18959 mLimit(aGetAll ? aParams.get_ObjectStoreGetAllParams().limit() : 1),
18960 mGetAll(aGetAll) {
18961 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetParams ||
18962 aParams.type() == RequestParams::TObjectStoreGetAllParams);
18963 MOZ_ASSERT(mObjectStoreId);
18964 MOZ_ASSERT(mDatabase);
18965 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome());
18966 MOZ_ASSERT(mBackgroundParent);
18969 template <typename T>
18970 Result<T, nsresult> ObjectStoreGetRequestOp::ConvertResponse(
18971 StructuredCloneReadInfoParent&& aInfo) {
18972 T result;
18974 static_assert(std::is_same_v<T, SerializedStructuredCloneReadInfo> ||
18975 std::is_same_v<T, PreprocessInfo>);
18977 if constexpr (std::is_same_v<T, SerializedStructuredCloneReadInfo>) {
18978 result.data().data = aInfo.ReleaseData();
18979 result.hasPreprocessInfo() = aInfo.HasPreprocessInfo();
18982 QM_TRY_UNWRAP(result.files(), SerializeStructuredCloneFiles(
18983 mDatabase, aInfo.Files(),
18984 std::is_same_v<T, PreprocessInfo>));
18986 return result;
18989 nsresult ObjectStoreGetRequestOp::DoDatabaseWork(
18990 DatabaseConnection* aConnection) {
18991 MOZ_ASSERT(aConnection);
18992 aConnection->AssertIsOnConnectionThread();
18993 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome());
18994 MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
18996 AUTO_PROFILER_LABEL("ObjectStoreGetRequestOp::DoDatabaseWork", DOM);
18998 const nsCString query =
18999 "SELECT file_ids, data "
19000 "FROM object_data "
19001 "WHERE object_store_id = :"_ns +
19002 kStmtParamNameObjectStoreId +
19003 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameKey) +
19004 " ORDER BY key ASC"_ns +
19005 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString());
19007 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query));
19009 QM_TRY(MOZ_TO_RESULT(
19010 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId)));
19012 if (mOptionalKeyRange.isSome()) {
19013 QM_TRY(MOZ_TO_RESULT(
19014 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt)));
19017 QM_TRY(CollectWhileHasResult(
19018 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
19019 QM_TRY_UNWRAP(auto cloneInfo,
19020 GetStructuredCloneReadInfoFromStatement(
19021 &stmt, 1, 0, mDatabase->GetFileManager()));
19023 if (cloneInfo.HasPreprocessInfo()) {
19024 mPreprocessInfoCount++;
19027 QM_TRY(OkIf(mResponse.EmplaceBack(fallible, std::move(cloneInfo))),
19028 Err(NS_ERROR_OUT_OF_MEMORY));
19030 return Ok{};
19031 }));
19033 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19035 return NS_OK;
19038 bool ObjectStoreGetRequestOp::HasPreprocessInfo() {
19039 return mPreprocessInfoCount > 0;
19042 Result<PreprocessParams, nsresult>
19043 ObjectStoreGetRequestOp::GetPreprocessParams() {
19044 AssertIsOnOwningThread();
19045 MOZ_ASSERT(!mResponse.IsEmpty());
19047 if (mGetAll) {
19048 auto params = ObjectStoreGetAllPreprocessParams();
19050 auto& preprocessInfos = params.preprocessInfos();
19051 if (NS_WARN_IF(
19052 !preprocessInfos.SetCapacity(mPreprocessInfoCount, fallible))) {
19053 return Err(NS_ERROR_OUT_OF_MEMORY);
19056 QM_TRY(TransformIfAbortOnErr(
19057 std::make_move_iterator(mResponse.begin()),
19058 std::make_move_iterator(mResponse.end()),
19059 MakeBackInserter(preprocessInfos),
19060 [](const auto& info) { return info.HasPreprocessInfo(); },
19061 [&self = *this](StructuredCloneReadInfoParent&& info) {
19062 return self.ConvertResponse<PreprocessInfo>(std::move(info));
19063 }));
19065 return PreprocessParams{std::move(params)};
19068 auto params = ObjectStoreGetPreprocessParams();
19070 QM_TRY_UNWRAP(params.preprocessInfo(),
19071 ConvertResponse<PreprocessInfo>(std::move(mResponse[0])));
19073 return PreprocessParams{std::move(params)};
19076 void ObjectStoreGetRequestOp::GetResponse(RequestResponse& aResponse,
19077 size_t* aResponseSize) {
19078 MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
19080 if (mGetAll) {
19081 aResponse = ObjectStoreGetAllResponse();
19082 *aResponseSize = 0;
19084 if (!mResponse.IsEmpty()) {
19085 QM_TRY_UNWRAP(
19086 aResponse.get_ObjectStoreGetAllResponse().cloneInfos(),
19087 TransformIntoNewArrayAbortOnErr(
19088 std::make_move_iterator(mResponse.begin()),
19089 std::make_move_iterator(mResponse.end()),
19090 [this, &aResponseSize](StructuredCloneReadInfoParent&& info) {
19091 *aResponseSize += info.Size();
19092 return ConvertResponse<SerializedStructuredCloneReadInfo>(
19093 std::move(info));
19095 fallible),
19096 QM_VOID, [&aResponse](const nsresult result) { aResponse = result; });
19099 return;
19102 aResponse = ObjectStoreGetResponse();
19103 *aResponseSize = 0;
19105 if (!mResponse.IsEmpty()) {
19106 SerializedStructuredCloneReadInfo& serializedInfo =
19107 aResponse.get_ObjectStoreGetResponse().cloneInfo();
19109 *aResponseSize += mResponse[0].Size();
19110 QM_TRY_UNWRAP(serializedInfo,
19111 ConvertResponse<SerializedStructuredCloneReadInfo>(
19112 std::move(mResponse[0])),
19113 QM_VOID,
19114 [&aResponse](const nsresult result) { aResponse = result; });
19118 ObjectStoreGetKeyRequestOp::ObjectStoreGetKeyRequestOp(
19119 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId,
19120 const RequestParams& aParams, bool aGetAll)
19121 : NormalTransactionOp(std::move(aTransaction), aRequestId),
19122 mObjectStoreId(
19123 aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().objectStoreId()
19124 : aParams.get_ObjectStoreGetKeyParams().objectStoreId()),
19125 mOptionalKeyRange(
19126 aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().optionalKeyRange()
19127 : Some(aParams.get_ObjectStoreGetKeyParams().keyRange())),
19128 mLimit(aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().limit() : 1),
19129 mGetAll(aGetAll) {
19130 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetKeyParams ||
19131 aParams.type() == RequestParams::TObjectStoreGetAllKeysParams);
19132 MOZ_ASSERT(mObjectStoreId);
19133 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome());
19136 nsresult ObjectStoreGetKeyRequestOp::DoDatabaseWork(
19137 DatabaseConnection* aConnection) {
19138 MOZ_ASSERT(aConnection);
19139 aConnection->AssertIsOnConnectionThread();
19141 AUTO_PROFILER_LABEL("ObjectStoreGetKeyRequestOp::DoDatabaseWork", DOM);
19143 const nsCString query =
19144 "SELECT key "
19145 "FROM object_data "
19146 "WHERE object_store_id = :"_ns +
19147 kStmtParamNameObjectStoreId +
19148 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameKey) +
19149 " ORDER BY key ASC"_ns +
19150 (mLimit ? " LIMIT "_ns + IntToCString(mLimit) : EmptyCString());
19152 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query));
19154 nsresult rv =
19155 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId);
19156 if (NS_WARN_IF(NS_FAILED(rv))) {
19157 return rv;
19160 if (mOptionalKeyRange.isSome()) {
19161 rv = BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt);
19162 if (NS_WARN_IF(NS_FAILED(rv))) {
19163 return rv;
19167 QM_TRY(CollectWhileHasResult(
19168 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
19169 Key* const key = mResponse.AppendElement(fallible);
19170 QM_TRY(OkIf(key), Err(NS_ERROR_OUT_OF_MEMORY));
19171 QM_TRY(MOZ_TO_RESULT(key->SetFromStatement(&stmt, 0)));
19173 return Ok{};
19174 }));
19176 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19178 return NS_OK;
19181 void ObjectStoreGetKeyRequestOp::GetResponse(RequestResponse& aResponse,
19182 size_t* aResponseSize) {
19183 MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
19185 if (mGetAll) {
19186 aResponse = ObjectStoreGetAllKeysResponse();
19187 *aResponseSize = std::accumulate(mResponse.begin(), mResponse.end(), 0u,
19188 [](size_t old, const auto& entry) {
19189 return old + entry.GetBuffer().Length();
19192 aResponse.get_ObjectStoreGetAllKeysResponse().keys() = std::move(mResponse);
19194 return;
19197 aResponse = ObjectStoreGetKeyResponse();
19198 *aResponseSize = 0;
19200 if (!mResponse.IsEmpty()) {
19201 *aResponseSize = mResponse[0].GetBuffer().Length();
19202 aResponse.get_ObjectStoreGetKeyResponse().key() = std::move(mResponse[0]);
19206 ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp(
19207 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId,
19208 const ObjectStoreDeleteParams& aParams)
19209 : NormalTransactionOp(std::move(aTransaction), aRequestId),
19210 mParams(aParams),
19211 mObjectStoreMayHaveIndexes(false) {
19212 AssertIsOnBackgroundThread();
19214 SafeRefPtr<FullObjectStoreMetadata> metadata =
19215 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId());
19216 MOZ_ASSERT(metadata);
19218 mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes();
19221 nsresult ObjectStoreDeleteRequestOp::DoDatabaseWork(
19222 DatabaseConnection* aConnection) {
19223 MOZ_ASSERT(aConnection);
19224 aConnection->AssertIsOnConnectionThread();
19225 AUTO_PROFILER_LABEL("ObjectStoreDeleteRequestOp::DoDatabaseWork", DOM);
19227 DatabaseConnection::AutoSavepoint autoSave;
19228 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
19229 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
19231 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
19232 #endif
19235 QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
19236 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
19237 mObjectStoreMayHaveIndexes));
19239 if (objectStoreHasIndexes) {
19240 QM_TRY(MOZ_TO_RESULT(DeleteObjectStoreDataTableRowsWithIndexes(
19241 aConnection, mParams.objectStoreId(), Some(mParams.keyRange()))));
19242 } else {
19243 const auto keyRangeClause =
19244 GetBindingClauseForKeyRange(mParams.keyRange(), kColumnNameKey);
19246 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
19247 "DELETE FROM object_data "
19248 "WHERE object_store_id = :"_ns +
19249 kStmtParamNameObjectStoreId + keyRangeClause + ";"_ns,
19250 [&params = mParams](
19251 mozIStorageStatement& stmt) -> mozilla::Result<Ok, nsresult> {
19252 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(kStmtParamNameObjectStoreId,
19253 params.objectStoreId())));
19255 QM_TRY(
19256 MOZ_TO_RESULT(BindKeyRangeToStatement(params.keyRange(), &stmt)));
19258 return Ok{};
19259 })));
19262 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
19264 return NS_OK;
19267 ObjectStoreClearRequestOp::ObjectStoreClearRequestOp(
19268 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId,
19269 const ObjectStoreClearParams& aParams)
19270 : NormalTransactionOp(std::move(aTransaction), aRequestId),
19271 mParams(aParams),
19272 mObjectStoreMayHaveIndexes(false) {
19273 AssertIsOnBackgroundThread();
19275 SafeRefPtr<FullObjectStoreMetadata> metadata =
19276 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId());
19277 MOZ_ASSERT(metadata);
19279 mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes();
19282 nsresult ObjectStoreClearRequestOp::DoDatabaseWork(
19283 DatabaseConnection* aConnection) {
19284 MOZ_ASSERT(aConnection);
19285 aConnection->AssertIsOnConnectionThread();
19287 AUTO_PROFILER_LABEL("ObjectStoreClearRequestOp::DoDatabaseWork", DOM);
19289 DatabaseConnection::AutoSavepoint autoSave;
19290 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
19291 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
19293 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
19294 #endif
19297 QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
19298 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
19299 mObjectStoreMayHaveIndexes));
19301 // The parameter names are not used, parameters are bound by index only
19302 // locally in the same function.
19303 QM_TRY(MOZ_TO_RESULT(
19304 objectStoreHasIndexes
19305 ? DeleteObjectStoreDataTableRowsWithIndexes(
19306 aConnection, mParams.objectStoreId(), Nothing())
19307 : aConnection->ExecuteCachedStatement(
19308 "DELETE FROM object_data "
19309 "WHERE object_store_id = :object_store_id;"_ns,
19310 [objectStoreId =
19311 mParams.objectStoreId()](mozIStorageStatement& stmt)
19312 -> mozilla::Result<Ok, nsresult> {
19313 QM_TRY(
19314 MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, objectStoreId)));
19316 return Ok{};
19317 })));
19319 QM_TRY(MOZ_TO_RESULT(autoSave.Commit()));
19321 return NS_OK;
19324 nsresult ObjectStoreCountRequestOp::DoDatabaseWork(
19325 DatabaseConnection* aConnection) {
19326 MOZ_ASSERT(aConnection);
19327 aConnection->AssertIsOnConnectionThread();
19329 AUTO_PROFILER_LABEL("ObjectStoreCountRequestOp::DoDatabaseWork", DOM);
19331 const auto keyRangeClause = MaybeGetBindingClauseForKeyRange(
19332 mParams.optionalKeyRange(), kColumnNameKey);
19334 QM_TRY_INSPECT(
19335 const auto& maybeStmt,
19336 aConnection->BorrowAndExecuteSingleStepStatement(
19337 "SELECT count(*) "
19338 "FROM object_data "
19339 "WHERE object_store_id = :"_ns +
19340 kStmtParamNameObjectStoreId + keyRangeClause,
19341 [&params = mParams](auto& stmt) -> mozilla::Result<Ok, nsresult> {
19342 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(
19343 kStmtParamNameObjectStoreId, params.objectStoreId())));
19345 if (params.optionalKeyRange().isSome()) {
19346 QM_TRY(MOZ_TO_RESULT(BindKeyRangeToStatement(
19347 params.optionalKeyRange().ref(), &stmt)));
19350 return Ok{};
19351 }));
19353 QM_TRY(OkIf(maybeStmt.isSome()), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
19354 [](const auto) {
19355 // XXX Why do we have an assertion here, but not at most other
19356 // places using IDB_REPORT_INTERNAL_ERR(_LAMBDA)?
19357 MOZ_ASSERT(false, "This should never be possible!");
19358 IDB_REPORT_INTERNAL_ERR();
19361 const auto& stmt = *maybeStmt;
19363 const int64_t count = stmt->AsInt64(0);
19364 QM_TRY(OkIf(count >= 0), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, [](const auto) {
19365 // XXX Why do we have an assertion here, but not at most other places using
19366 // IDB_REPORT_INTERNAL_ERR(_LAMBDA)?
19367 MOZ_ASSERT(false, "This should never be possible!");
19368 IDB_REPORT_INTERNAL_ERR();
19371 mResponse.count() = count;
19373 return NS_OK;
19376 // static
19377 SafeRefPtr<FullIndexMetadata> IndexRequestOpBase::IndexMetadataForParams(
19378 const TransactionBase& aTransaction, const RequestParams& aParams) {
19379 AssertIsOnBackgroundThread();
19380 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams ||
19381 aParams.type() == RequestParams::TIndexGetKeyParams ||
19382 aParams.type() == RequestParams::TIndexGetAllParams ||
19383 aParams.type() == RequestParams::TIndexGetAllKeysParams ||
19384 aParams.type() == RequestParams::TIndexCountParams);
19386 IndexOrObjectStoreId objectStoreId;
19387 IndexOrObjectStoreId indexId;
19389 switch (aParams.type()) {
19390 case RequestParams::TIndexGetParams: {
19391 const IndexGetParams& params = aParams.get_IndexGetParams();
19392 objectStoreId = params.objectStoreId();
19393 indexId = params.indexId();
19394 break;
19397 case RequestParams::TIndexGetKeyParams: {
19398 const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams();
19399 objectStoreId = params.objectStoreId();
19400 indexId = params.indexId();
19401 break;
19404 case RequestParams::TIndexGetAllParams: {
19405 const IndexGetAllParams& params = aParams.get_IndexGetAllParams();
19406 objectStoreId = params.objectStoreId();
19407 indexId = params.indexId();
19408 break;
19411 case RequestParams::TIndexGetAllKeysParams: {
19412 const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams();
19413 objectStoreId = params.objectStoreId();
19414 indexId = params.indexId();
19415 break;
19418 case RequestParams::TIndexCountParams: {
19419 const IndexCountParams& params = aParams.get_IndexCountParams();
19420 objectStoreId = params.objectStoreId();
19421 indexId = params.indexId();
19422 break;
19425 default:
19426 MOZ_CRASH("Should never get here!");
19429 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata =
19430 aTransaction.GetMetadataForObjectStoreId(objectStoreId);
19431 MOZ_ASSERT(objectStoreMetadata);
19433 SafeRefPtr<FullIndexMetadata> indexMetadata =
19434 aTransaction.GetMetadataForIndexId(*objectStoreMetadata, indexId);
19435 MOZ_ASSERT(indexMetadata);
19437 return indexMetadata;
19440 IndexGetRequestOp::IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction,
19441 const int64_t aRequestId,
19442 const RequestParams& aParams, bool aGetAll)
19443 : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams),
19444 mDatabase(Transaction().GetDatabasePtr()),
19445 mOptionalKeyRange(aGetAll
19446 ? aParams.get_IndexGetAllParams().optionalKeyRange()
19447 : Some(aParams.get_IndexGetParams().keyRange())),
19448 mBackgroundParent(Transaction().GetBackgroundParent()),
19449 mLimit(aGetAll ? aParams.get_IndexGetAllParams().limit() : 1),
19450 mGetAll(aGetAll) {
19451 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams ||
19452 aParams.type() == RequestParams::TIndexGetAllParams);
19453 MOZ_ASSERT(mDatabase);
19454 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome());
19455 MOZ_ASSERT(mBackgroundParent);
19458 nsresult IndexGetRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) {
19459 MOZ_ASSERT(aConnection);
19460 aConnection->AssertIsOnConnectionThread();
19461 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome());
19462 MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
19464 AUTO_PROFILER_LABEL("IndexGetRequestOp::DoDatabaseWork", DOM);
19466 const auto indexTable = mMetadata->mCommonMetadata.unique()
19467 ? "unique_index_data "_ns
19468 : "index_data "_ns;
19470 QM_TRY_INSPECT(
19471 const auto& stmt,
19472 aConnection->BorrowCachedStatement(
19473 "SELECT file_ids, data "
19474 "FROM object_data "
19475 "INNER JOIN "_ns +
19476 indexTable +
19477 "AS index_table "
19478 "ON object_data.object_store_id = "
19479 "index_table.object_store_id "
19480 "AND object_data.key = "
19481 "index_table.object_data_key "
19482 "WHERE index_id = :"_ns +
19483 kStmtParamNameIndexId +
19484 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange,
19485 kColumnNameValue) +
19486 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString())));
19488 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameIndexId,
19489 mMetadata->mCommonMetadata.id())));
19491 if (mOptionalKeyRange.isSome()) {
19492 QM_TRY(MOZ_TO_RESULT(
19493 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt)));
19496 QM_TRY(CollectWhileHasResult(
19497 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
19498 QM_TRY_UNWRAP(auto cloneInfo,
19499 GetStructuredCloneReadInfoFromStatement(
19500 &stmt, 1, 0, mDatabase->GetFileManager()));
19502 if (cloneInfo.HasPreprocessInfo()) {
19503 IDB_WARNING("Preprocessing for indexes not yet implemented!");
19504 return Err(NS_ERROR_NOT_IMPLEMENTED);
19507 QM_TRY(OkIf(mResponse.EmplaceBack(fallible, std::move(cloneInfo))),
19508 Err(NS_ERROR_OUT_OF_MEMORY));
19510 return Ok{};
19511 }));
19513 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19515 return NS_OK;
19518 // XXX This is more or less a duplicate of ObjectStoreGetRequestOp::GetResponse
19519 void IndexGetRequestOp::GetResponse(RequestResponse& aResponse,
19520 size_t* aResponseSize) {
19521 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19523 auto convertResponse = [this](StructuredCloneReadInfoParent&& info)
19524 -> mozilla::Result<SerializedStructuredCloneReadInfo, nsresult> {
19525 SerializedStructuredCloneReadInfo result;
19527 result.data().data = info.ReleaseData();
19529 QM_TRY_UNWRAP(result.files(), SerializeStructuredCloneFiles(
19530 mDatabase, info.Files(), false));
19532 return result;
19535 if (mGetAll) {
19536 aResponse = IndexGetAllResponse();
19537 *aResponseSize = 0;
19539 if (!mResponse.IsEmpty()) {
19540 QM_TRY_UNWRAP(
19541 aResponse.get_IndexGetAllResponse().cloneInfos(),
19542 TransformIntoNewArrayAbortOnErr(
19543 std::make_move_iterator(mResponse.begin()),
19544 std::make_move_iterator(mResponse.end()),
19545 [convertResponse,
19546 &aResponseSize](StructuredCloneReadInfoParent&& info) {
19547 *aResponseSize += info.Size();
19548 return convertResponse(std::move(info));
19550 fallible),
19551 QM_VOID, [&aResponse](const nsresult result) { aResponse = result; });
19554 return;
19557 aResponse = IndexGetResponse();
19558 *aResponseSize = 0;
19560 if (!mResponse.IsEmpty()) {
19561 SerializedStructuredCloneReadInfo& serializedInfo =
19562 aResponse.get_IndexGetResponse().cloneInfo();
19564 *aResponseSize += mResponse[0].Size();
19565 QM_TRY_UNWRAP(serializedInfo, convertResponse(std::move(mResponse[0])),
19566 QM_VOID,
19567 [&aResponse](const nsresult result) { aResponse = result; });
19571 IndexGetKeyRequestOp::IndexGetKeyRequestOp(
19572 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId,
19573 const RequestParams& aParams, bool aGetAll)
19574 : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams),
19575 mOptionalKeyRange(
19576 aGetAll ? aParams.get_IndexGetAllKeysParams().optionalKeyRange()
19577 : Some(aParams.get_IndexGetKeyParams().keyRange())),
19578 mLimit(aGetAll ? aParams.get_IndexGetAllKeysParams().limit() : 1),
19579 mGetAll(aGetAll) {
19580 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetKeyParams ||
19581 aParams.type() == RequestParams::TIndexGetAllKeysParams);
19582 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome());
19585 nsresult IndexGetKeyRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) {
19586 MOZ_ASSERT(aConnection);
19587 aConnection->AssertIsOnConnectionThread();
19588 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome());
19589 MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
19591 AUTO_PROFILER_LABEL("IndexGetKeyRequestOp::DoDatabaseWork", DOM);
19593 const bool hasKeyRange = mOptionalKeyRange.isSome();
19595 const auto indexTable = mMetadata->mCommonMetadata.unique()
19596 ? "unique_index_data "_ns
19597 : "index_data "_ns;
19599 const nsCString query =
19600 "SELECT object_data_key "
19601 "FROM "_ns +
19602 indexTable + "WHERE index_id = :"_ns + kStmtParamNameIndexId +
19603 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameValue) +
19604 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString());
19606 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query));
19608 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameIndexId,
19609 mMetadata->mCommonMetadata.id())));
19611 if (hasKeyRange) {
19612 QM_TRY(MOZ_TO_RESULT(
19613 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt)));
19616 QM_TRY(CollectWhileHasResult(
19617 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> {
19618 Key* const key = mResponse.AppendElement(fallible);
19619 QM_TRY(OkIf(key), Err(NS_ERROR_OUT_OF_MEMORY));
19620 QM_TRY(MOZ_TO_RESULT(key->SetFromStatement(&stmt, 0)));
19622 return Ok{};
19623 }));
19625 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19627 return NS_OK;
19630 void IndexGetKeyRequestOp::GetResponse(RequestResponse& aResponse,
19631 size_t* aResponseSize) {
19632 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
19634 if (mGetAll) {
19635 aResponse = IndexGetAllKeysResponse();
19636 *aResponseSize = std::accumulate(mResponse.begin(), mResponse.end(), 0u,
19637 [](size_t old, const auto& entry) {
19638 return old + entry.GetBuffer().Length();
19641 aResponse.get_IndexGetAllKeysResponse().keys() = std::move(mResponse);
19643 return;
19646 aResponse = IndexGetKeyResponse();
19647 *aResponseSize = 0;
19649 if (!mResponse.IsEmpty()) {
19650 *aResponseSize = mResponse[0].GetBuffer().Length();
19651 aResponse.get_IndexGetKeyResponse().key() = std::move(mResponse[0]);
19655 nsresult IndexCountRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) {
19656 MOZ_ASSERT(aConnection);
19657 aConnection->AssertIsOnConnectionThread();
19659 AUTO_PROFILER_LABEL("IndexCountRequestOp::DoDatabaseWork", DOM);
19661 const auto indexTable = mMetadata->mCommonMetadata.unique()
19662 ? "unique_index_data "_ns
19663 : "index_data "_ns;
19665 const auto keyRangeClause = MaybeGetBindingClauseForKeyRange(
19666 mParams.optionalKeyRange(), kColumnNameValue);
19668 QM_TRY_INSPECT(
19669 const auto& maybeStmt,
19670 aConnection->BorrowAndExecuteSingleStepStatement(
19671 "SELECT count(*) "
19672 "FROM "_ns +
19673 indexTable + "WHERE index_id = :"_ns + kStmtParamNameIndexId +
19674 keyRangeClause,
19675 [&self = *this](auto& stmt) -> mozilla::Result<Ok, nsresult> {
19676 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(
19677 kStmtParamNameIndexId, self.mMetadata->mCommonMetadata.id())));
19679 if (self.mParams.optionalKeyRange().isSome()) {
19680 QM_TRY(MOZ_TO_RESULT(BindKeyRangeToStatement(
19681 self.mParams.optionalKeyRange().ref(), &stmt)));
19684 return Ok{};
19685 }));
19687 QM_TRY(OkIf(maybeStmt.isSome()), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
19688 [](const auto) {
19689 // XXX Why do we have an assertion here, but not at most other
19690 // places using IDB_REPORT_INTERNAL_ERR(_LAMBDA)?
19691 MOZ_ASSERT(false, "This should never be possible!");
19692 IDB_REPORT_INTERNAL_ERR();
19695 const auto& stmt = *maybeStmt;
19697 const int64_t count = stmt->AsInt64(0);
19698 QM_TRY(OkIf(count >= 0), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, [](const auto) {
19699 // XXX Why do we have an assertion here, but not at most other places using
19700 // IDB_REPORT_INTERNAL_ERR(_LAMBDA)?
19701 MOZ_ASSERT(false, "This should never be possible!");
19702 IDB_REPORT_INTERNAL_ERR();
19705 mResponse.count() = count;
19707 return NS_OK;
19710 template <IDBCursorType CursorType>
19711 bool Cursor<CursorType>::CursorOpBase::SendFailureResult(nsresult aResultCode) {
19712 AssertIsOnOwningThread();
19713 MOZ_ASSERT(NS_FAILED(aResultCode));
19714 MOZ_ASSERT(mCursor);
19715 MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
19716 MOZ_ASSERT(!mResponseSent);
19718 if (!IsActorDestroyed()) {
19719 mResponse = ClampResultCode(aResultCode);
19721 // This is an expected race when the transaction is invalidated after
19722 // data is retrieved from database.
19724 // TODO: There seem to be other cases when mFiles is non-empty here, which
19725 // have been present before adding cursor preloading, but with cursor
19726 // preloading they have become more frequent (also during startup). One
19727 // possible cause with cursor preloading is to be addressed by Bug 1597191.
19728 NS_WARNING_ASSERTION(
19729 !mFiles.IsEmpty() && !Transaction().IsInvalidated(),
19730 "Expected empty mFiles when transaction has not been invalidated");
19732 // SendResponseInternal will assert when mResponse.type() is
19733 // CursorResponse::Tnsresult and mFiles is non-empty, so we clear mFiles
19734 // here.
19735 mFiles.Clear();
19737 mCursor->SendResponseInternal(mResponse, mFiles);
19740 #ifdef DEBUG
19741 mResponseSent = true;
19742 #endif
19743 return false;
19746 template <IDBCursorType CursorType>
19747 void Cursor<CursorType>::CursorOpBase::Cleanup() {
19748 AssertIsOnOwningThread();
19749 MOZ_ASSERT(mCursor);
19750 MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
19752 mCursor = nullptr;
19754 #ifdef DEBUG
19755 // A bit hacky but the CursorOp request is not generated in response to a
19756 // child request like most other database operations. Do this to make our
19757 // assertions happy.
19758 NoteActorDestroyed();
19759 #endif
19761 TransactionDatabaseOperationBase::Cleanup();
19764 template <IDBCursorType CursorType>
19765 ResponseSizeOrError
19766 CursorOpBaseHelperBase<CursorType>::PopulateResponseFromStatement(
19767 mozIStorageStatement* const aStmt, const bool aInitializeResponse,
19768 Key* const aOptOutSortKey) {
19769 mOp.Transaction().AssertIsOnConnectionThread();
19770 MOZ_ASSERT_IF(aInitializeResponse,
19771 mOp.mResponse.type() == CursorResponse::T__None);
19772 MOZ_ASSERT_IF(!aInitializeResponse,
19773 mOp.mResponse.type() != CursorResponse::T__None);
19774 MOZ_ASSERT_IF(
19775 mOp.mFiles.IsEmpty() &&
19776 (mOp.mResponse.type() ==
19777 CursorResponse::TArrayOfObjectStoreCursorResponse ||
19778 mOp.mResponse.type() == CursorResponse::TArrayOfIndexCursorResponse),
19779 aInitializeResponse);
19781 auto populateResponseHelper = PopulateResponseHelper<CursorType>{mOp};
19782 auto previousKey = aOptOutSortKey ? std::move(*aOptOutSortKey) : Key{};
19784 QM_TRY(MOZ_TO_RESULT(populateResponseHelper.GetKeys(aStmt, aOptOutSortKey)));
19786 // aOptOutSortKey must be set iff the cursor is a unique cursor. For unique
19787 // cursors, we need to skip records with the same key. The SQL queries
19788 // currently do not filter these out.
19789 if (aOptOutSortKey && !previousKey.IsUnset() &&
19790 previousKey == *aOptOutSortKey) {
19791 return 0;
19794 QM_TRY(MOZ_TO_RESULT(
19795 populateResponseHelper.MaybeGetCloneInfo(aStmt, GetCursor())));
19797 // CAUTION: It is important that only the part of the function above this
19798 // comment may fail, and modifications to the data structure (in particular
19799 // mResponse and mFiles) may only be made below. This is necessary to allow to
19800 // discard entries that were attempted to be preloaded without causing an
19801 // inconsistent state.
19803 if (aInitializeResponse) {
19804 mOp.mResponse = std::remove_reference_t<
19805 decltype(populateResponseHelper.GetTypedResponse(&mOp.mResponse))>();
19808 auto& responses = populateResponseHelper.GetTypedResponse(&mOp.mResponse);
19809 auto& response = *responses.AppendElement();
19811 populateResponseHelper.FillKeys(response);
19812 if constexpr (!CursorTypeTraits<CursorType>::IsKeyOnlyCursor) {
19813 populateResponseHelper.MaybeFillCloneInfo(response, &mOp.mFiles);
19816 return populateResponseHelper.GetKeySize(response) +
19817 populateResponseHelper.MaybeGetCloneInfoSize(response);
19820 template <IDBCursorType CursorType>
19821 void CursorOpBaseHelperBase<CursorType>::PopulateExtraResponses(
19822 mozIStorageStatement* const aStmt, const uint32_t aMaxExtraCount,
19823 const size_t aInitialResponseSize, const nsACString& aOperation,
19824 Key* const aOptPreviousSortKey) {
19825 mOp.AssertIsOnConnectionThread();
19827 const auto extraCount = [&]() -> uint32_t {
19828 auto accumulatedResponseSize = aInitialResponseSize;
19829 uint32_t extraCount = 0;
19831 do {
19832 bool hasResult;
19833 nsresult rv = aStmt->ExecuteStep(&hasResult);
19834 if (NS_WARN_IF(NS_FAILED(rv))) {
19835 // In case of a failure on one step, do not attempt to execute further
19836 // steps, but use the results already populated.
19838 break;
19841 if (!hasResult) {
19842 break;
19845 // PopulateResponseFromStatement does not modify the data in case of
19846 // failure, so we can just use the results already populated, and discard
19847 // any remaining entries, and signal overall success. Probably, future
19848 // attempts to access the same entry will fail as well, but it might never
19849 // be accessed by the application.
19850 QM_TRY_INSPECT(
19851 const auto& responseSize,
19852 PopulateResponseFromStatement(aStmt, false, aOptPreviousSortKey),
19853 extraCount, [](const auto&) {
19854 // TODO: Maybe disable preloading for this cursor? The problem will
19855 // probably reoccur on the next attempt, and disabling preloading
19856 // will reduce latency. However, if some problematic entry will be
19857 // skipped over, after that it might be fine again. To judge this,
19858 // the causes for such failures would need to be analyzed more
19859 // thoroughly. Since this seems to be rare, maybe no further action
19860 // is necessary at all.
19863 // Check accumulated size of individual responses and maybe break early.
19864 accumulatedResponseSize += responseSize;
19865 if (accumulatedResponseSize > IPC::Channel::kMaximumMessageSize / 2) {
19866 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
19867 "PRELOAD: %s: Dropping entries because maximum message size is "
19868 "exceeded: %" PRIu32 "/%zu bytes",
19869 "%.0s Dropping too large (%" PRIu32 "/%zu)",
19870 IDB_LOG_ID_STRING(mOp.mBackgroundChildLoggingId),
19871 mOp.mTransactionLoggingSerialNumber, mOp.mLoggingSerialNumber,
19872 PromiseFlatCString(aOperation).get(), extraCount,
19873 accumulatedResponseSize);
19875 break;
19878 // TODO: Do not count entries skipped for unique cursors.
19879 ++extraCount;
19880 } while (true);
19882 return extraCount;
19883 }();
19885 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST(
19886 "PRELOAD: %s: Number of extra results populated: %" PRIu32 "/%" PRIu32,
19887 "%.0s Populated (%" PRIu32 "/%" PRIu32 ")",
19888 IDB_LOG_ID_STRING(mOp.mBackgroundChildLoggingId),
19889 mOp.mTransactionLoggingSerialNumber, mOp.mLoggingSerialNumber,
19890 PromiseFlatCString(aOperation).get(), extraCount, aMaxExtraCount);
19893 template <IDBCursorType CursorType>
19894 void Cursor<CursorType>::SetOptionalKeyRange(
19895 const Maybe<SerializedKeyRange>& aOptionalKeyRange, bool* const aOpen) {
19896 MOZ_ASSERT(aOpen);
19898 Key localeAwareRangeBound;
19900 if (aOptionalKeyRange.isSome()) {
19901 const SerializedKeyRange& range = aOptionalKeyRange.ref();
19903 const bool lowerBound = !IsIncreasingOrder(mDirection);
19904 *aOpen =
19905 !range.isOnly() && (lowerBound ? range.lowerOpen() : range.upperOpen());
19907 const auto& bound =
19908 (range.isOnly() || lowerBound) ? range.lower() : range.upper();
19909 if constexpr (IsIndexCursor) {
19910 if (this->IsLocaleAware()) {
19911 // XXX Don't we need to propagate the error?
19912 QM_TRY_UNWRAP(localeAwareRangeBound,
19913 bound.ToLocaleAwareKey(this->mLocale), QM_VOID);
19914 } else {
19915 localeAwareRangeBound = bound;
19917 } else {
19918 localeAwareRangeBound = bound;
19920 } else {
19921 *aOpen = false;
19924 this->mLocaleAwareRangeBound.init(std::move(localeAwareRangeBound));
19927 template <IDBCursorType CursorType>
19928 void ObjectStoreOpenOpHelper<CursorType>::PrepareKeyConditionClauses(
19929 const nsACString& aDirectionClause, const nsACString& aQueryStart) {
19930 const bool isIncreasingOrder = IsIncreasingOrder(GetCursor().mDirection);
19932 nsAutoCString keyRangeClause;
19933 nsAutoCString continueToKeyRangeClause;
19934 AppendConditionClause(kStmtParamNameKey, kStmtParamNameCurrentKey,
19935 !isIncreasingOrder, false, keyRangeClause);
19936 AppendConditionClause(kStmtParamNameKey, kStmtParamNameCurrentKey,
19937 !isIncreasingOrder, true, continueToKeyRangeClause);
19940 bool open;
19941 GetCursor().SetOptionalKeyRange(GetOptionalKeyRange(), &open);
19943 if (GetOptionalKeyRange().isSome() &&
19944 !GetCursor().mLocaleAwareRangeBound->IsUnset()) {
19945 AppendConditionClause(kStmtParamNameKey, kStmtParamNameRangeBound,
19946 isIncreasingOrder, !open, keyRangeClause);
19947 AppendConditionClause(kStmtParamNameKey, kStmtParamNameRangeBound,
19948 isIncreasingOrder, !open, continueToKeyRangeClause);
19952 const nsAutoCString suffix =
19953 aDirectionClause + kOpenLimit + ":"_ns + kStmtParamNameLimit;
19955 GetCursor().mContinueQueries.init(
19956 aQueryStart + keyRangeClause + suffix,
19957 aQueryStart + continueToKeyRangeClause + suffix);
19960 template <IDBCursorType CursorType>
19961 void IndexOpenOpHelper<CursorType>::PrepareIndexKeyConditionClause(
19962 const nsACString& aDirectionClause,
19963 const nsLiteralCString& aObjectDataKeyPrefix, nsAutoCString aQueryStart) {
19964 const bool isIncreasingOrder = IsIncreasingOrder(GetCursor().mDirection);
19967 bool open;
19968 GetCursor().SetOptionalKeyRange(GetOptionalKeyRange(), &open);
19969 if (GetOptionalKeyRange().isSome() &&
19970 !GetCursor().mLocaleAwareRangeBound->IsUnset()) {
19971 AppendConditionClause(kColumnNameAliasSortKey, kStmtParamNameRangeBound,
19972 isIncreasingOrder, !open, aQueryStart);
19976 nsCString continueQuery, continueToQuery, continuePrimaryKeyQuery;
19978 continueToQuery =
19979 aQueryStart + " AND "_ns +
19980 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterOrEquals
19981 : ComparisonOperator::LessOrEquals,
19982 kStmtParamNameCurrentKey);
19984 switch (GetCursor().mDirection) {
19985 case IDBCursorDirection::Next:
19986 case IDBCursorDirection::Prev:
19987 continueQuery =
19988 aQueryStart + " AND "_ns +
19989 GetSortKeyClause(isIncreasingOrder
19990 ? ComparisonOperator::GreaterOrEquals
19991 : ComparisonOperator::LessOrEquals,
19992 kStmtParamNameCurrentKey) +
19993 " AND ( "_ns +
19994 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan
19995 : ComparisonOperator::LessThan,
19996 kStmtParamNameCurrentKey) +
19997 " OR "_ns +
19998 GetKeyClause(aObjectDataKeyPrefix + "object_data_key"_ns,
19999 isIncreasingOrder ? ComparisonOperator::GreaterThan
20000 : ComparisonOperator::LessThan,
20001 kStmtParamNameObjectStorePosition) +
20002 " ) "_ns;
20004 continuePrimaryKeyQuery =
20005 aQueryStart +
20006 " AND ("
20007 "("_ns +
20008 GetSortKeyClause(ComparisonOperator::Equals,
20009 kStmtParamNameCurrentKey) +
20010 " AND "_ns +
20011 GetKeyClause(aObjectDataKeyPrefix + "object_data_key"_ns,
20012 isIncreasingOrder ? ComparisonOperator::GreaterOrEquals
20013 : ComparisonOperator::LessOrEquals,
20014 kStmtParamNameObjectStorePosition) +
20015 ") OR "_ns +
20016 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan
20017 : ComparisonOperator::LessThan,
20018 kStmtParamNameCurrentKey) +
20019 ")"_ns;
20020 break;
20022 case IDBCursorDirection::Nextunique:
20023 case IDBCursorDirection::Prevunique:
20024 continueQuery =
20025 aQueryStart + " AND "_ns +
20026 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan
20027 : ComparisonOperator::LessThan,
20028 kStmtParamNameCurrentKey);
20029 break;
20031 default:
20032 MOZ_CRASH("Should never get here!");
20035 const nsAutoCString suffix =
20036 aDirectionClause + kOpenLimit + ":"_ns + kStmtParamNameLimit;
20037 continueQuery += suffix;
20038 continueToQuery += suffix;
20039 if (!continuePrimaryKeyQuery.IsEmpty()) {
20040 continuePrimaryKeyQuery += suffix;
20043 GetCursor().mContinueQueries.init(std::move(continueQuery),
20044 std::move(continueToQuery),
20045 std::move(continuePrimaryKeyQuery));
20048 template <IDBCursorType CursorType>
20049 nsresult CommonOpenOpHelper<CursorType>::ProcessStatementSteps(
20050 mozIStorageStatement* const aStmt) {
20051 QM_TRY_INSPECT(const bool& hasResult,
20052 MOZ_TO_RESULT_INVOKE_MEMBER(aStmt, ExecuteStep));
20054 if (!hasResult) {
20055 SetResponse(void_t{});
20056 return NS_OK;
20059 Key previousKey;
20060 auto* optPreviousKey =
20061 IsUnique(GetCursor().mDirection) ? &previousKey : nullptr;
20063 QM_TRY_INSPECT(const auto& responseSize,
20064 PopulateResponseFromStatement(aStmt, true, optPreviousKey));
20066 // The degree to which extra responses on OpenOp can actually be used depends
20067 // on the parameters of subsequent ContinueOp operations, see also comment in
20068 // ContinueOp::DoDatabaseWork.
20070 // TODO: We should somehow evaluate the effects of this. Maybe use a smaller
20071 // extra count than for ContinueOp?
20072 PopulateExtraResponses(aStmt, GetCursor().mMaxExtraCount, responseSize,
20073 "OpenOp"_ns, optPreviousKey);
20075 return NS_OK;
20078 nsresult OpenOpHelper<IDBCursorType::ObjectStore>::DoDatabaseWork(
20079 DatabaseConnection* aConnection) {
20080 MOZ_ASSERT(aConnection);
20081 aConnection->AssertIsOnConnectionThread();
20082 MOZ_ASSERT(GetCursor().mObjectStoreId);
20084 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoObjectStoreDatabaseWork", DOM);
20086 const bool usingKeyRange = GetOptionalKeyRange().isSome();
20088 const nsCString queryStart = "SELECT "_ns + kColumnNameKey +
20089 ", file_ids, data "
20090 "FROM object_data "
20091 "WHERE object_store_id = :"_ns +
20092 kStmtParamNameId;
20094 const auto keyRangeClause =
20095 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
20096 GetOptionalKeyRange(), kColumnNameKey);
20098 const auto& directionClause = MakeDirectionClause(GetCursor().mDirection);
20100 // Note: Changing the number or order of SELECT columns in the query will
20101 // require changes to CursorOpBase::PopulateResponseFromStatement.
20102 const nsCString firstQuery = queryStart + keyRangeClause + directionClause +
20103 kOpenLimit +
20104 IntToCString(1 + GetCursor().mMaxExtraCount);
20106 QM_TRY_INSPECT(const auto& stmt,
20107 aConnection->BorrowCachedStatement(firstQuery));
20109 QM_TRY(MOZ_TO_RESULT(
20110 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mObjectStoreId)));
20112 if (usingKeyRange) {
20113 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20114 GetOptionalKeyRange().ref(), &*stmt)));
20117 // Now we need to make the query for ContinueOp.
20118 PrepareKeyConditionClauses(directionClause, queryStart);
20120 return ProcessStatementSteps(&*stmt);
20123 nsresult OpenOpHelper<IDBCursorType::ObjectStoreKey>::DoDatabaseWork(
20124 DatabaseConnection* aConnection) {
20125 MOZ_ASSERT(aConnection);
20126 aConnection->AssertIsOnConnectionThread();
20127 MOZ_ASSERT(GetCursor().mObjectStoreId);
20129 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoObjectStoreKeyDatabaseWork", DOM);
20131 const bool usingKeyRange = GetOptionalKeyRange().isSome();
20133 const nsCString queryStart = "SELECT "_ns + kColumnNameKey +
20134 " FROM object_data "
20135 "WHERE object_store_id = :"_ns +
20136 kStmtParamNameId;
20138 const auto keyRangeClause =
20139 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
20140 GetOptionalKeyRange(), kColumnNameKey);
20142 const auto& directionClause = MakeDirectionClause(GetCursor().mDirection);
20144 // Note: Changing the number or order of SELECT columns in the query will
20145 // require changes to CursorOpBase::PopulateResponseFromStatement.
20146 const nsCString firstQuery =
20147 queryStart + keyRangeClause + directionClause + kOpenLimit + "1"_ns;
20149 QM_TRY_INSPECT(const auto& stmt,
20150 aConnection->BorrowCachedStatement(firstQuery));
20152 QM_TRY(MOZ_TO_RESULT(
20153 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mObjectStoreId)));
20155 if (usingKeyRange) {
20156 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20157 GetOptionalKeyRange().ref(), &*stmt)));
20160 // Now we need to make the query to get the next match.
20161 PrepareKeyConditionClauses(directionClause, queryStart);
20163 return ProcessStatementSteps(&*stmt);
20166 nsresult OpenOpHelper<IDBCursorType::Index>::DoDatabaseWork(
20167 DatabaseConnection* aConnection) {
20168 MOZ_ASSERT(aConnection);
20169 aConnection->AssertIsOnConnectionThread();
20170 MOZ_ASSERT(GetCursor().mObjectStoreId);
20171 MOZ_ASSERT(GetCursor().mIndexId);
20173 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoIndexDatabaseWork", DOM);
20175 const bool usingKeyRange = GetOptionalKeyRange().isSome();
20177 const auto indexTable =
20178 GetCursor().mUniqueIndex ? "unique_index_data"_ns : "index_data"_ns;
20180 // The result of MakeColumnPairSelectionList is stored in a local variable,
20181 // since inlining it into the next statement causes a crash on some Mac OS X
20182 // builds (see https://bugzilla.mozilla.org/show_bug.cgi?id=1168606#c110).
20183 const auto columnPairSelectionList = MakeColumnPairSelectionList(
20184 "index_table.value"_ns, "index_table.value_locale"_ns,
20185 kColumnNameAliasSortKey, GetCursor().IsLocaleAware());
20186 const nsCString sortColumnAlias =
20187 "SELECT "_ns + columnPairSelectionList + ", "_ns;
20189 const nsAutoCString queryStart = sortColumnAlias +
20190 "index_table.object_data_key, "
20191 "object_data.file_ids, "
20192 "object_data.data "
20193 "FROM "_ns +
20194 indexTable +
20195 " AS index_table "
20196 "JOIN object_data "
20197 "ON index_table.object_store_id = "
20198 "object_data.object_store_id "
20199 "AND index_table.object_data_key = "
20200 "object_data.key "
20201 "WHERE index_table.index_id = :"_ns +
20202 kStmtParamNameId;
20204 const auto keyRangeClause =
20205 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
20206 GetOptionalKeyRange(), kColumnNameAliasSortKey);
20208 nsAutoCString directionClause = " ORDER BY "_ns + kColumnNameAliasSortKey;
20210 switch (GetCursor().mDirection) {
20211 case IDBCursorDirection::Next:
20212 case IDBCursorDirection::Nextunique:
20213 directionClause.AppendLiteral(" ASC, index_table.object_data_key ASC");
20214 break;
20216 case IDBCursorDirection::Prev:
20217 directionClause.AppendLiteral(" DESC, index_table.object_data_key DESC");
20218 break;
20220 case IDBCursorDirection::Prevunique:
20221 directionClause.AppendLiteral(" DESC, index_table.object_data_key ASC");
20222 break;
20224 default:
20225 MOZ_CRASH("Should never get here!");
20228 // Note: Changing the number or order of SELECT columns in the query will
20229 // require changes to CursorOpBase::PopulateResponseFromStatement.
20230 const nsCString firstQuery = queryStart + keyRangeClause + directionClause +
20231 kOpenLimit +
20232 IntToCString(1 + GetCursor().mMaxExtraCount);
20234 QM_TRY_INSPECT(const auto& stmt,
20235 aConnection->BorrowCachedStatement(firstQuery));
20237 QM_TRY(MOZ_TO_RESULT(
20238 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mIndexId)));
20240 if (usingKeyRange) {
20241 if (GetCursor().IsLocaleAware()) {
20242 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20243 GetOptionalKeyRange().ref(), &*stmt, GetCursor().mLocale)));
20244 } else {
20245 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20246 GetOptionalKeyRange().ref(), &*stmt)));
20250 // TODO: At least the last two statements are almost the same in all
20251 // DoDatabaseWork variants, consider removing this duplication.
20253 // Now we need to make the query to get the next match.
20254 PrepareKeyConditionClauses(directionClause, std::move(queryStart));
20256 return ProcessStatementSteps(&*stmt);
20259 nsresult OpenOpHelper<IDBCursorType::IndexKey>::DoDatabaseWork(
20260 DatabaseConnection* aConnection) {
20261 MOZ_ASSERT(aConnection);
20262 aConnection->AssertIsOnConnectionThread();
20263 MOZ_ASSERT(GetCursor().mObjectStoreId);
20264 MOZ_ASSERT(GetCursor().mIndexId);
20266 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoIndexKeyDatabaseWork", DOM);
20268 const bool usingKeyRange = GetOptionalKeyRange().isSome();
20270 const auto table =
20271 GetCursor().mUniqueIndex ? "unique_index_data"_ns : "index_data"_ns;
20273 // The result of MakeColumnPairSelectionList is stored in a local variable,
20274 // since inlining it into the next statement causes a crash on some Mac OS X
20275 // builds (see https://bugzilla.mozilla.org/show_bug.cgi?id=1168606#c110).
20276 const auto columnPairSelectionList = MakeColumnPairSelectionList(
20277 "value"_ns, "value_locale"_ns, kColumnNameAliasSortKey,
20278 GetCursor().IsLocaleAware());
20279 const nsCString sortColumnAlias =
20280 "SELECT "_ns + columnPairSelectionList + ", "_ns;
20282 const nsAutoCString queryStart = sortColumnAlias +
20283 "object_data_key "
20284 " FROM "_ns +
20285 table + " WHERE index_id = :"_ns +
20286 kStmtParamNameId;
20288 const auto keyRangeClause =
20289 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange(
20290 GetOptionalKeyRange(), kColumnNameAliasSortKey);
20292 nsAutoCString directionClause = " ORDER BY "_ns + kColumnNameAliasSortKey;
20294 switch (GetCursor().mDirection) {
20295 case IDBCursorDirection::Next:
20296 case IDBCursorDirection::Nextunique:
20297 directionClause.AppendLiteral(" ASC, object_data_key ASC");
20298 break;
20300 case IDBCursorDirection::Prev:
20301 directionClause.AppendLiteral(" DESC, object_data_key DESC");
20302 break;
20304 case IDBCursorDirection::Prevunique:
20305 directionClause.AppendLiteral(" DESC, object_data_key ASC");
20306 break;
20308 default:
20309 MOZ_CRASH("Should never get here!");
20312 // Note: Changing the number or order of SELECT columns in the query will
20313 // require changes to CursorOpBase::PopulateResponseFromStatement.
20314 const nsCString firstQuery =
20315 queryStart + keyRangeClause + directionClause + kOpenLimit + "1"_ns;
20317 QM_TRY_INSPECT(const auto& stmt,
20318 aConnection->BorrowCachedStatement(firstQuery));
20320 QM_TRY(MOZ_TO_RESULT(
20321 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mIndexId)));
20323 if (usingKeyRange) {
20324 if (GetCursor().IsLocaleAware()) {
20325 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20326 GetOptionalKeyRange().ref(), &*stmt, GetCursor().mLocale)));
20327 } else {
20328 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement(
20329 GetOptionalKeyRange().ref(), &*stmt)));
20333 // Now we need to make the query to get the next match.
20334 PrepareKeyConditionClauses(directionClause, std::move(queryStart));
20336 return ProcessStatementSteps(&*stmt);
20339 template <IDBCursorType CursorType>
20340 nsresult Cursor<CursorType>::OpenOp::DoDatabaseWork(
20341 DatabaseConnection* aConnection) {
20342 MOZ_ASSERT(aConnection);
20343 aConnection->AssertIsOnConnectionThread();
20344 MOZ_ASSERT(mCursor);
20345 MOZ_ASSERT(!mCursor->mContinueQueries);
20347 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoDatabaseWork", DOM);
20349 auto helper = OpenOpHelper<CursorType>{*this};
20350 const auto rv = helper.DoDatabaseWork(aConnection);
20351 if (NS_WARN_IF(NS_FAILED(rv))) {
20352 return rv;
20355 return NS_OK;
20358 template <IDBCursorType CursorType>
20359 nsresult Cursor<CursorType>::CursorOpBase::SendSuccessResult() {
20360 AssertIsOnOwningThread();
20361 MOZ_ASSERT(mCursor);
20362 MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
20363 MOZ_ASSERT(mResponse.type() != CursorResponse::T__None);
20365 if (IsActorDestroyed()) {
20366 return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
20369 mCursor->SendResponseInternal(mResponse, mFiles);
20371 #ifdef DEBUG
20372 mResponseSent = true;
20373 #endif
20374 return NS_OK;
20377 template <IDBCursorType CursorType>
20378 nsresult Cursor<CursorType>::ContinueOp::DoDatabaseWork(
20379 DatabaseConnection* aConnection) {
20380 MOZ_ASSERT(aConnection);
20381 aConnection->AssertIsOnConnectionThread();
20382 MOZ_ASSERT(mCursor);
20383 MOZ_ASSERT(mCursor->mObjectStoreId);
20384 MOZ_ASSERT(!mCursor->mContinueQueries->mContinueQuery.IsEmpty());
20385 MOZ_ASSERT(!mCursor->mContinueQueries->mContinueToQuery.IsEmpty());
20386 MOZ_ASSERT(!mCurrentPosition.mKey.IsUnset());
20388 if constexpr (IsIndexCursor) {
20389 MOZ_ASSERT_IF(
20390 mCursor->mDirection == IDBCursorDirection::Next ||
20391 mCursor->mDirection == IDBCursorDirection::Prev,
20392 !mCursor->mContinueQueries->mContinuePrimaryKeyQuery.IsEmpty());
20393 MOZ_ASSERT(mCursor->mIndexId);
20394 MOZ_ASSERT(!mCurrentPosition.mObjectStoreKey.IsUnset());
20397 AUTO_PROFILER_LABEL("Cursor::ContinueOp::DoDatabaseWork", DOM);
20399 // We need to pick a query based on whether or not a key was passed to the
20400 // continue function. If not we'll grab the next item in the database that
20401 // is greater than (or less than, if we're running a PREV cursor) the current
20402 // key. If a key was passed we'll grab the next item in the database that is
20403 // greater than (or less than, if we're running a PREV cursor) or equal to the
20404 // key that was specified.
20406 // TODO: The description above is not complete, it does not take account of
20407 // ContinuePrimaryKey nor Advance.
20409 // Note: Changing the number or order of SELECT columns in the query will
20410 // require changes to CursorOpBase::PopulateResponseFromStatement.
20412 const uint32_t advanceCount =
20413 mParams.type() == CursorRequestParams::TAdvanceParams
20414 ? mParams.get_AdvanceParams().count()
20415 : 1;
20416 MOZ_ASSERT(advanceCount > 0);
20418 bool hasContinueKey = false;
20419 bool hasContinuePrimaryKey = false;
20421 auto explicitContinueKey = Key{};
20423 switch (mParams.type()) {
20424 case CursorRequestParams::TContinueParams:
20425 if (!mParams.get_ContinueParams().key().IsUnset()) {
20426 hasContinueKey = true;
20427 explicitContinueKey = mParams.get_ContinueParams().key();
20429 break;
20430 case CursorRequestParams::TContinuePrimaryKeyParams:
20431 MOZ_ASSERT(!mParams.get_ContinuePrimaryKeyParams().key().IsUnset());
20432 MOZ_ASSERT(
20433 !mParams.get_ContinuePrimaryKeyParams().primaryKey().IsUnset());
20434 MOZ_ASSERT(mCursor->mDirection == IDBCursorDirection::Next ||
20435 mCursor->mDirection == IDBCursorDirection::Prev);
20436 hasContinueKey = true;
20437 hasContinuePrimaryKey = true;
20438 explicitContinueKey = mParams.get_ContinuePrimaryKeyParams().key();
20439 break;
20440 case CursorRequestParams::TAdvanceParams:
20441 break;
20442 default:
20443 MOZ_CRASH("Should never get here!");
20446 // TODO: Whether it makes sense to preload depends on the kind of the
20447 // subsequent operations, not of the current operation. We could assume that
20448 // the subsequent operations are:
20449 // - the same as the current operation (with the same parameter values)
20450 // - as above, except for Advance, where we assume the count will be 1 on the
20451 // next call
20452 // - basic operations (Advance with count 1 or Continue-without-key)
20454 // For now, we implement the second option for now (which correspond to
20455 // !hasContinueKey).
20457 // Based on that, we could in both cases either preload for any assumed
20458 // subsequent operations, or only for the basic operations. For now, we
20459 // preload only for an assumed basic operation. Other operations would require
20460 // more work on the client side for invalidation, and may not make any sense
20461 // at all.
20462 const uint32_t maxExtraCount = hasContinueKey ? 0 : mCursor->mMaxExtraCount;
20464 QM_TRY_INSPECT(const auto& stmt,
20465 aConnection->BorrowCachedStatement(
20466 mCursor->mContinueQueries->GetContinueQuery(
20467 hasContinueKey, hasContinuePrimaryKey)));
20469 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName(
20470 kStmtParamNameLimit,
20471 IntToCString(advanceCount + mCursor->mMaxExtraCount))));
20473 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameId, mCursor->Id())));
20475 // Bind current key.
20476 const auto& continueKey =
20477 hasContinueKey ? explicitContinueKey
20478 : mCurrentPosition.GetSortKey(mCursor->IsLocaleAware());
20479 QM_TRY(MOZ_TO_RESULT(
20480 continueKey.BindToStatement(&*stmt, kStmtParamNameCurrentKey)));
20482 // Bind range bound if it is specified.
20483 if (!mCursor->mLocaleAwareRangeBound->IsUnset()) {
20484 QM_TRY(MOZ_TO_RESULT(mCursor->mLocaleAwareRangeBound->BindToStatement(
20485 &*stmt, kStmtParamNameRangeBound)));
20488 // Bind object store position if duplicates are allowed and we're not
20489 // continuing to a specific key.
20490 if constexpr (IsIndexCursor) {
20491 if (!hasContinueKey && (mCursor->mDirection == IDBCursorDirection::Next ||
20492 mCursor->mDirection == IDBCursorDirection::Prev)) {
20493 QM_TRY(MOZ_TO_RESULT(mCurrentPosition.mObjectStoreKey.BindToStatement(
20494 &*stmt, kStmtParamNameObjectStorePosition)));
20495 } else if (hasContinuePrimaryKey) {
20496 QM_TRY(MOZ_TO_RESULT(
20497 mParams.get_ContinuePrimaryKeyParams().primaryKey().BindToStatement(
20498 &*stmt, kStmtParamNameObjectStorePosition)));
20502 // TODO: Why do we query the records we don't need and skip them here, rather
20503 // than using a OFFSET clause in the query?
20504 for (uint32_t index = 0; index < advanceCount; index++) {
20505 QM_TRY_INSPECT(const bool& hasResult,
20506 MOZ_TO_RESULT_INVOKE_MEMBER(&*stmt, ExecuteStep));
20508 if (!hasResult) {
20509 mResponse = void_t();
20510 return NS_OK;
20514 Key previousKey;
20515 auto* const optPreviousKey =
20516 IsUnique(mCursor->mDirection) ? &previousKey : nullptr;
20518 auto helper = CursorOpBaseHelperBase<CursorType>{*this};
20519 QM_TRY_INSPECT(const auto& responseSize, helper.PopulateResponseFromStatement(
20520 &*stmt, true, optPreviousKey));
20522 helper.PopulateExtraResponses(&*stmt, maxExtraCount, responseSize,
20523 "ContinueOp"_ns, optPreviousKey);
20525 return NS_OK;
20528 Utils::Utils()
20529 #ifdef DEBUG
20530 : mActorDestroyed(false)
20531 #endif
20533 AssertIsOnBackgroundThread();
20536 Utils::~Utils() { MOZ_ASSERT(mActorDestroyed); }
20538 void Utils::ActorDestroy(ActorDestroyReason aWhy) {
20539 AssertIsOnBackgroundThread();
20540 MOZ_ASSERT(!mActorDestroyed);
20542 #ifdef DEBUG
20543 mActorDestroyed = true;
20544 #endif
20547 mozilla::ipc::IPCResult Utils::RecvDeleteMe() {
20548 AssertIsOnBackgroundThread();
20549 MOZ_ASSERT(!mActorDestroyed);
20551 QM_WARNONLY_TRY(OkIf(PBackgroundIndexedDBUtilsParent::Send__delete__(this)));
20553 return IPC_OK();
20556 mozilla::ipc::IPCResult Utils::RecvGetFileReferences(
20557 const PersistenceType& aPersistenceType, const nsACString& aOrigin,
20558 const nsAString& aDatabaseName, const int64_t& aFileId, int32_t* aRefCnt,
20559 int32_t* aDBRefCnt, bool* aResult) {
20560 AssertIsOnBackgroundThread();
20561 MOZ_ASSERT(aRefCnt);
20562 MOZ_ASSERT(aDBRefCnt);
20563 MOZ_ASSERT(aResult);
20564 MOZ_ASSERT(!mActorDestroyed);
20566 if (NS_WARN_IF(!IndexedDatabaseManager::Get())) {
20567 return IPC_FAIL(this, "No IndexedDatabaseManager active!");
20570 if (NS_WARN_IF(!QuotaManager::Get())) {
20571 return IPC_FAIL(this, "No QuotaManager active!");
20574 if (NS_WARN_IF(!StaticPrefs::dom_indexedDB_testing())) {
20575 return IPC_FAIL(this, "IndexedDB is not in testing mode!");
20578 if (NS_WARN_IF(!IsValidPersistenceType(aPersistenceType))) {
20579 return IPC_FAIL(this, "PersistenceType is not valid!");
20582 if (NS_WARN_IF(aOrigin.IsEmpty())) {
20583 return IPC_FAIL(this, "Origin is empty!");
20586 if (NS_WARN_IF(aDatabaseName.IsEmpty())) {
20587 return IPC_FAIL(this, "DatabaseName is empty!");
20590 if (NS_WARN_IF(aFileId == 0)) {
20591 return IPC_FAIL(this, "No FileId!");
20594 nsresult rv =
20595 DispatchAndReturnFileReferences(aPersistenceType, aOrigin, aDatabaseName,
20596 aFileId, aRefCnt, aDBRefCnt, aResult);
20597 if (NS_WARN_IF(NS_FAILED(rv))) {
20598 return IPC_FAIL(this, "DispatchAndReturnFileReferences failed!");
20601 return IPC_OK();
20604 #ifdef DEBUG
20606 NS_IMPL_ISUPPORTS(DEBUGThreadSlower, nsIThreadObserver)
20608 NS_IMETHODIMP
20609 DEBUGThreadSlower::OnDispatchedEvent() { MOZ_CRASH("Should never be called!"); }
20611 NS_IMETHODIMP
20612 DEBUGThreadSlower::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
20613 bool /* aMayWait */) {
20614 return NS_OK;
20617 NS_IMETHODIMP
20618 DEBUGThreadSlower::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
20619 bool /* aEventWasProcessed */) {
20620 MOZ_ASSERT(kDEBUGThreadSleepMS);
20622 MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(kDEBUGThreadSleepMS)) ==
20623 PR_SUCCESS);
20624 return NS_OK;
20627 #endif // DEBUG
20629 nsresult FileHelper::Init() {
20630 MOZ_ASSERT(!IsOnBackgroundThread());
20632 auto fileDirectory = mFileManager->GetCheckedDirectory();
20633 if (NS_WARN_IF(!fileDirectory)) {
20634 return NS_ERROR_FAILURE;
20637 auto journalDirectory = mFileManager->EnsureJournalDirectory();
20638 if (NS_WARN_IF(!journalDirectory)) {
20639 return NS_ERROR_FAILURE;
20642 DebugOnly<bool> exists;
20643 MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists)));
20644 MOZ_ASSERT(exists);
20646 DebugOnly<bool> isDirectory;
20647 MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory)));
20648 MOZ_ASSERT(isDirectory);
20650 mFileDirectory.init(WrapNotNullUnchecked(std::move(fileDirectory)));
20651 mJournalDirectory.init(WrapNotNullUnchecked(std::move(journalDirectory)));
20653 return NS_OK;
20656 nsCOMPtr<nsIFile> FileHelper::GetFile(const DatabaseFileInfo& aFileInfo) {
20657 MOZ_ASSERT(!IsOnBackgroundThread());
20659 return mFileManager->GetFileForId(mFileDirectory->get(), aFileInfo.Id());
20662 nsCOMPtr<nsIFile> FileHelper::GetJournalFile(
20663 const DatabaseFileInfo& aFileInfo) {
20664 MOZ_ASSERT(!IsOnBackgroundThread());
20666 return mFileManager->GetFileForId(mJournalDirectory->get(), aFileInfo.Id());
20669 nsresult FileHelper::CreateFileFromStream(nsIFile& aFile, nsIFile& aJournalFile,
20670 nsIInputStream& aInputStream,
20671 bool aCompress,
20672 const Maybe<CipherKey>& aMaybeKey) {
20673 MOZ_ASSERT(!IsOnBackgroundThread());
20675 QM_TRY_INSPECT(const auto& exists,
20676 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists));
20678 // DOM blobs that are being stored in IDB are cached by calling
20679 // IDBDatabase::GetOrCreateFileActorForBlob. So if the same DOM blob is stored
20680 // again under a different key or in a different object store, we just add
20681 // a new reference instead of creating a new copy (all such stored blobs share
20682 // the same id).
20683 // However, it can happen that CreateFileFromStream failed due to quota
20684 // exceeded error and for some reason the orphaned file couldn't be deleted
20685 // immediately. Now, if the operation is being repeated, the DOM blob is
20686 // already cached, so it has the same file id which clashes with the orphaned
20687 // file. We could do some tricks to restore previous copy loop, but it's safer
20688 // to just delete the orphaned file and start from scratch.
20689 // This corner case is partially simulated in test_file_copy_failure.js
20690 if (exists) {
20691 QM_TRY_INSPECT(const auto& isFile,
20692 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, IsFile));
20694 QM_TRY(OkIf(isFile), NS_ERROR_FAILURE);
20696 QM_TRY_INSPECT(const auto& journalExists,
20697 MOZ_TO_RESULT_INVOKE_MEMBER(aJournalFile, Exists));
20699 QM_TRY(OkIf(journalExists), NS_ERROR_FAILURE);
20701 QM_TRY_INSPECT(const auto& journalIsFile,
20702 MOZ_TO_RESULT_INVOKE_MEMBER(aJournalFile, IsFile));
20704 QM_TRY(OkIf(journalIsFile), NS_ERROR_FAILURE);
20706 IDB_WARNING("Deleting orphaned file!");
20708 QM_TRY(MOZ_TO_RESULT(mFileManager->SyncDeleteFile(aFile, aJournalFile)));
20711 // Create a journal file first.
20712 QM_TRY(MOZ_TO_RESULT(aJournalFile.Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
20714 // Now try to copy the stream.
20715 QM_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileOutputStream,
20716 CreateFileOutputStream(mFileManager->Type(),
20717 mFileManager->OriginMetadata(),
20718 Client::IDB, &aFile));
20720 AutoTArray<char, kFileCopyBufferSize> buffer;
20721 const auto actualOutputStream =
20722 [aCompress, &aMaybeKey, &buffer,
20723 baseOutputStream =
20724 std::move(fileOutputStream)]() mutable -> nsCOMPtr<nsIOutputStream> {
20725 if (aMaybeKey) {
20726 baseOutputStream =
20727 MakeRefPtr<EncryptingOutputStream<IndexedDBCipherStrategy>>(
20728 std::move(baseOutputStream), kEncryptedStreamBlockSize,
20729 *aMaybeKey);
20732 if (aCompress) {
20733 auto snappyOutputStream =
20734 MakeRefPtr<SnappyCompressOutputStream>(baseOutputStream);
20736 buffer.SetLength(snappyOutputStream->BlockSize());
20738 return snappyOutputStream;
20741 buffer.SetLength(kFileCopyBufferSize);
20742 return std::move(baseOutputStream);
20743 }();
20745 QM_TRY(MOZ_TO_RESULT(SyncCopy(aInputStream, *actualOutputStream,
20746 buffer.Elements(), buffer.Length())));
20748 return NS_OK;
20751 class FileHelper::ReadCallback final : public nsIInputStreamCallback {
20752 public:
20753 NS_DECL_THREADSAFE_ISUPPORTS
20755 ReadCallback()
20756 : mMutex("ReadCallback::mMutex"),
20757 mCondVar(mMutex, "ReadCallback::mCondVar"),
20758 mInputAvailable(false) {}
20760 NS_IMETHOD
20761 OnInputStreamReady(nsIAsyncInputStream* aStream) override {
20762 mozilla::MutexAutoLock autolock(mMutex);
20764 mInputAvailable = true;
20765 mCondVar.Notify();
20767 return NS_OK;
20770 nsresult AsyncWait(nsIAsyncInputStream* aStream, uint32_t aBufferSize,
20771 nsIEventTarget* aTarget) {
20772 MOZ_ASSERT(aStream);
20773 mozilla::MutexAutoLock autolock(mMutex);
20775 nsresult rv = aStream->AsyncWait(this, 0, aBufferSize, aTarget);
20776 if (NS_WARN_IF(NS_FAILED(rv))) {
20777 return rv;
20780 mInputAvailable = false;
20781 while (!mInputAvailable) {
20782 mCondVar.Wait();
20785 return NS_OK;
20788 private:
20789 ~ReadCallback() = default;
20791 mozilla::Mutex mMutex MOZ_UNANNOTATED;
20792 mozilla::CondVar mCondVar;
20793 bool mInputAvailable;
20796 NS_IMPL_ADDREF(FileHelper::ReadCallback);
20797 NS_IMPL_RELEASE(FileHelper::ReadCallback);
20799 NS_INTERFACE_MAP_BEGIN(FileHelper::ReadCallback)
20800 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
20801 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamCallback)
20802 NS_INTERFACE_MAP_END
20804 nsresult FileHelper::SyncRead(nsIInputStream& aInputStream, char* const aBuffer,
20805 const uint32_t aBufferSize,
20806 uint32_t* const aRead) {
20807 MOZ_ASSERT(!IsOnBackgroundThread());
20809 // Let's try to read, directly.
20810 nsresult rv = aInputStream.Read(aBuffer, aBufferSize, aRead);
20811 if (NS_SUCCEEDED(rv) || rv != NS_BASE_STREAM_WOULD_BLOCK) {
20812 return rv;
20815 // We need to proceed async.
20816 nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(&aInputStream);
20817 if (!asyncStream) {
20818 return rv;
20821 if (!mReadCallback) {
20822 mReadCallback.init(MakeNotNull<RefPtr<ReadCallback>>());
20825 // We just need any thread with an event loop for receiving the
20826 // OnInputStreamReady callback. Let's use the I/O thread.
20827 nsCOMPtr<nsIEventTarget> target =
20828 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
20829 MOZ_ASSERT(target);
20831 rv = (*mReadCallback)->AsyncWait(asyncStream, aBufferSize, target);
20832 if (NS_WARN_IF(NS_FAILED(rv))) {
20833 return rv;
20836 return SyncRead(aInputStream, aBuffer, aBufferSize, aRead);
20839 nsresult FileHelper::SyncCopy(nsIInputStream& aInputStream,
20840 nsIOutputStream& aOutputStream,
20841 char* const aBuffer, const uint32_t aBufferSize) {
20842 MOZ_ASSERT(!IsOnBackgroundThread());
20844 AUTO_PROFILER_LABEL("FileHelper::SyncCopy", DOM);
20846 nsresult rv;
20848 do {
20849 uint32_t numRead;
20850 rv = SyncRead(aInputStream, aBuffer, aBufferSize, &numRead);
20851 if (NS_WARN_IF(NS_FAILED(rv))) {
20852 break;
20855 if (!numRead) {
20856 break;
20859 uint32_t numWrite;
20860 rv = aOutputStream.Write(aBuffer, numRead, &numWrite);
20861 if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
20862 rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
20864 if (NS_WARN_IF(NS_FAILED(rv))) {
20865 break;
20868 if (NS_WARN_IF(numWrite != numRead)) {
20869 rv = NS_ERROR_FAILURE;
20870 break;
20872 } while (true);
20874 if (NS_SUCCEEDED(rv)) {
20875 rv = aOutputStream.Flush();
20876 if (NS_WARN_IF(NS_FAILED(rv))) {
20877 return rv;
20881 nsresult rv2 = aOutputStream.Close();
20882 if (NS_WARN_IF(NS_FAILED(rv2))) {
20883 return NS_SUCCEEDED(rv) ? rv2 : rv;
20886 return rv;
20889 } // namespace dom::indexedDB
20890 } // namespace mozilla
20892 #undef IDB_MOBILE
20893 #undef IDB_DEBUG_LOG