ActorsParent.cpp (707023B)
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/. */ 6 7 #include "ActorsParent.h" 8 9 #include <inttypes.h> 10 #include <math.h> 11 #include <stdlib.h> 12 #include <string.h> 13 14 #include <algorithm> 15 #include <cstdint> 16 #include <functional> 17 #include <iterator> 18 #include <new> 19 #include <numeric> 20 #include <type_traits> 21 #include <utility> 22 23 #include "ActorsParentCommon.h" 24 #include "CrashAnnotations.h" 25 #include "DBSchema.h" 26 #include "DatabaseFileInfo.h" 27 #include "DatabaseFileManager.h" 28 #include "DatabaseFileManagerImpl.h" 29 #include "ErrorList.h" 30 #include "IDBCursorType.h" 31 #include "IDBObjectStore.h" 32 #include "IDBTransaction.h" 33 #include "IndexedDBCipherKeyManager.h" 34 #include "IndexedDBCommon.h" 35 #include "IndexedDatabaseInlines.h" 36 #include "IndexedDatabaseManager.h" 37 #include "KeyPath.h" 38 #include "MainThreadUtils.h" 39 #include "NotifyUtils.h" 40 #include "ProfilerHelpers.h" 41 #include "ReportInternalError.h" 42 #include "SafeRefPtr.h" 43 #include "SchemaUpgrades.h" 44 #include "chrome/common/ipc_channel.h" 45 #include "ipc/IPCMessageUtils.h" 46 #include "js/RootingAPI.h" 47 #include "js/StructuredClone.h" 48 #include "js/Value.h" 49 #include "jsapi.h" 50 #include "mozIStorageAsyncConnection.h" 51 #include "mozIStorageConnection.h" 52 #include "mozIStorageFunction.h" 53 #include "mozIStorageProgressHandler.h" 54 #include "mozIStorageService.h" 55 #include "mozIStorageStatement.h" 56 #include "mozIStorageValueArray.h" 57 #include "mozStorageCID.h" 58 #include "mozStorageHelper.h" 59 #include "mozilla/Algorithm.h" 60 #include "mozilla/ArrayAlgorithm.h" 61 #include "mozilla/ArrayIterator.h" 62 #include "mozilla/Assertions.h" 63 #include "mozilla/Atomics.h" 64 #include "mozilla/Attributes.h" 65 #include "mozilla/Casting.h" 66 #include "mozilla/CondVar.h" 67 #include "mozilla/DebugOnly.h" 68 #include "mozilla/EndianUtils.h" 69 #include "mozilla/ErrorNames.h" 70 #include "mozilla/ErrorResult.h" 71 #include "mozilla/GeckoTrace.h" 72 #include "mozilla/InitializedOnce.h" 73 #include "mozilla/Logging.h" 74 #include "mozilla/Maybe.h" 75 #include "mozilla/Monitor.h" 76 #include "mozilla/Mutex.h" 77 #include "mozilla/NotNull.h" 78 #include "mozilla/Preferences.h" 79 #include "mozilla/ProfilerLabels.h" 80 #include "mozilla/RefCountType.h" 81 #include "mozilla/RefCounted.h" 82 #include "mozilla/RemoteLazyInputStreamParent.h" 83 #include "mozilla/RemoteLazyInputStreamStorage.h" 84 #include "mozilla/Result.h" 85 #include "mozilla/ResultExtensions.h" 86 #include "mozilla/SchedulerGroup.h" 87 #include "mozilla/SnappyCompressOutputStream.h" 88 #include "mozilla/SpinEventLoopUntil.h" 89 #include "mozilla/StaticPtr.h" 90 #include "mozilla/TimeStamp.h" 91 #include "mozilla/UniquePtr.h" 92 #include "mozilla/Variant.h" 93 #include "mozilla/dom/BlobImpl.h" 94 #include "mozilla/dom/ContentParent.h" 95 #include "mozilla/dom/FileBlobImpl.h" 96 #include "mozilla/dom/FlippedOnce.h" 97 #include "mozilla/dom/IDBCursorBinding.h" 98 #include "mozilla/dom/IDBFactory.h" 99 #include "mozilla/dom/IPCBlob.h" 100 #include "mozilla/dom/IPCBlobUtils.h" 101 #include "mozilla/dom/IndexedDatabase.h" 102 #include "mozilla/dom/Nullable.h" 103 #include "mozilla/dom/PContentParent.h" 104 #include "mozilla/dom/ScriptSettings.h" 105 #include "mozilla/dom/indexedDB/IDBResult.h" 106 #include "mozilla/dom/indexedDB/Key.h" 107 #include "mozilla/dom/indexedDB/PBackgroundIDBCursor.h" 108 #include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h" 109 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabase.h" 110 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h" 111 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h" 112 #include "mozilla/dom/indexedDB/PBackgroundIDBFactory.h" 113 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h" 114 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h" 115 #include "mozilla/dom/indexedDB/PBackgroundIDBRequest.h" 116 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h" 117 #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h" 118 #include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h" 119 #include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h" 120 #include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h" 121 #include "mozilla/dom/ipc/IdType.h" 122 #include "mozilla/dom/quota/Assertions.h" 123 #include "mozilla/dom/quota/CachingDatabaseConnection.h" 124 #include "mozilla/dom/quota/Client.h" 125 #include "mozilla/dom/quota/ClientDirectoryLock.h" 126 #include "mozilla/dom/quota/ClientDirectoryLockHandle.h" 127 #include "mozilla/dom/quota/ClientImpl.h" 128 #include "mozilla/dom/quota/ConditionalCompilation.h" 129 #include "mozilla/dom/quota/Date.h" 130 #include "mozilla/dom/quota/DecryptingInputStream_impl.h" 131 #include "mozilla/dom/quota/DirectoryLock.h" 132 #include "mozilla/dom/quota/DirectoryLockInlines.h" 133 #include "mozilla/dom/quota/DirectoryMetadata.h" 134 #include "mozilla/dom/quota/EncryptingOutputStream_impl.h" 135 #include "mozilla/dom/quota/ErrorHandling.h" 136 #include "mozilla/dom/quota/FileStreams.h" 137 #include "mozilla/dom/quota/OriginScope.h" 138 #include "mozilla/dom/quota/PersistenceScope.h" 139 #include "mozilla/dom/quota/PersistenceType.h" 140 #include "mozilla/dom/quota/PrincipalUtils.h" 141 #include "mozilla/dom/quota/QuotaCommon.h" 142 #include "mozilla/dom/quota/QuotaManager.h" 143 #include "mozilla/dom/quota/QuotaObject.h" 144 #include "mozilla/dom/quota/ResultExtensions.h" 145 #include "mozilla/dom/quota/ThreadUtils.h" 146 #include "mozilla/dom/quota/UniversalDirectoryLock.h" 147 #include "mozilla/dom/quota/UsageInfo.h" 148 #include "mozilla/fallible.h" 149 #include "mozilla/glean/DomIndexedDBMetrics.h" 150 #include "mozilla/ipc/BackgroundParent.h" 151 #include "mozilla/ipc/BackgroundUtils.h" 152 #include "mozilla/ipc/InputStreamParams.h" 153 #include "mozilla/ipc/PBackgroundParent.h" 154 #include "mozilla/ipc/PBackgroundSharedTypes.h" 155 #include "mozilla/ipc/ProtocolUtils.h" 156 #include "mozilla/mozalloc.h" 157 #include "mozilla/storage/Variant.h" 158 #include "nsBaseHashtable.h" 159 #include "nsCOMPtr.h" 160 #include "nsClassHashtable.h" 161 #include "nsContentUtils.h" 162 #include "nsDebug.h" 163 #include "nsError.h" 164 #include "nsEscape.h" 165 #include "nsHashKeys.h" 166 #include "nsIAsyncInputStream.h" 167 #include "nsID.h" 168 #include "nsIDUtils.h" 169 #include "nsIDirectoryEnumerator.h" 170 #include "nsIEventTarget.h" 171 #include "nsIFile.h" 172 #include "nsIFileProtocolHandler.h" 173 #include "nsIFileStreams.h" 174 #include "nsIFileURL.h" 175 #include "nsIInputStream.h" 176 #include "nsIOutputStream.h" 177 #include "nsIProtocolHandler.h" 178 #include "nsIRunnable.h" 179 #include "nsISupports.h" 180 #include "nsISupportsPriority.h" 181 #include "nsISupportsUtils.h" 182 #include "nsIThread.h" 183 #include "nsIThreadInternal.h" 184 #include "nsITimer.h" 185 #include "nsIURIMutator.h" 186 #include "nsIVariant.h" 187 #include "nsLiteralString.h" 188 #include "nsNetCID.h" 189 #include "nsPrintfCString.h" 190 #include "nsProxyRelease.h" 191 #include "nsServiceManagerUtils.h" 192 #include "nsStreamUtils.h" 193 #include "nsString.h" 194 #include "nsStringFlags.h" 195 #include "nsStringFwd.h" 196 #include "nsTArray.h" 197 #include "nsTHashMap.h" 198 #include "nsTHashSet.h" 199 #include "nsTHashtable.h" 200 #include "nsTLiteralString.h" 201 #include "nsTStringRepr.h" 202 #include "nsThreadPool.h" 203 #include "nsThreadUtils.h" 204 #include "nscore.h" 205 #include "prinrval.h" 206 #include "prio.h" 207 #include "prsystem.h" 208 #include "prthread.h" 209 #include "prtime.h" 210 #include "prtypes.h" 211 #include "snappy/snappy.h" 212 213 struct JSContext; 214 class JSObject; 215 template <class T> 216 class nsPtrHashKey; 217 218 #define IDB_DEBUG_LOG(_args) \ 219 MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), LogLevel::Debug, _args) 220 221 #if defined(MOZ_WIDGET_ANDROID) 222 # define IDB_MOBILE 223 #endif 224 225 // Helper macros to reduce assertion verbosity 226 // AUUF == ASSERT_UNREACHABLE_UNLESS_FUZZING 227 #ifdef DEBUG 228 # ifdef FUZZING 229 # define NS_AUUF_OR_WARN(...) NS_WARNING(__VA_ARGS__) 230 # else 231 # define NS_AUUF_OR_WARN(...) MOZ_ASSERT(false, __VA_ARGS__) 232 # endif 233 # define NS_AUUF_OR_WARN_IF(cond) \ 234 [](bool aCond) { \ 235 if (MOZ_UNLIKELY(aCond)) { \ 236 NS_AUUF_OR_WARN(#cond); \ 237 } \ 238 return aCond; \ 239 }((cond)) 240 #else 241 # define NS_AUUF_OR_WARN(...) \ 242 do { \ 243 } while (false) 244 # define NS_AUUF_OR_WARN_IF(cond) static_cast<bool>(cond) 245 #endif 246 247 namespace mozilla { 248 249 namespace dom::indexedDB { 250 251 using namespace mozilla::dom::quota; 252 using namespace mozilla::ipc; 253 using mozilla::dom::quota::Client; 254 255 namespace { 256 257 class ConnectionPool; 258 class Database; 259 struct DatabaseActorInfo; 260 class DatabaseFile; 261 class DatabaseLoggingInfo; 262 class DatabaseMaintenance; 263 class Factory; 264 class Maintenance; 265 class OpenDatabaseOp; 266 class TransactionBase; 267 class TransactionDatabaseOperationBase; 268 class VersionChangeTransaction; 269 template <bool StatementHasIndexKeyBindings> 270 struct ValuePopulateResponseHelper; 271 272 /******************************************************************************* 273 * Constants 274 ******************************************************************************/ 275 276 const int32_t kStorageProgressGranularity = 1000; 277 278 // Changing the value here will override the page size of new databases only. 279 // A journal mode change and VACUUM are needed to change existing databases, so 280 // the best way to do that is to use the schema version upgrade mechanism. 281 const uint32_t kSQLitePageSizeOverride = 282 #ifdef IDB_MOBILE 283 2048; 284 #else 285 4096; 286 #endif 287 288 static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 || 289 (kSQLitePageSizeOverride % 2 == 0 && 290 kSQLitePageSizeOverride >= 512 && 291 kSQLitePageSizeOverride <= 65536), 292 "Must be 0 (disabled) or a power of 2 between 512 and 65536!"); 293 294 // Set to -1 to use SQLite's default, 0 to disable, or some positive number to 295 // enforce a custom limit. 296 const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile. 297 298 // Set to some multiple of the page size to grow the database in larger chunks. 299 const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2; 300 301 static_assert(kSQLiteGrowthIncrement >= 0 && 302 kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 && 303 kSQLiteGrowthIncrement < uint32_t(INT32_MAX), 304 "Must be 0 (disabled) or a positive multiple of the page size!"); 305 306 // The maximum number of threads that can be used for database activity at a 307 // single time. Please keep in sync with the constants in 308 // test_connection_idle_maintenance*.js tests 309 const uint32_t kMaxConnectionThreadCount = 20; 310 311 static_assert(kMaxConnectionThreadCount, "Must have at least one thread!"); 312 313 // The maximum number of threads to keep when idle. Until we switch to the STS 314 // pool, we can reduce the number of idle threads kept around thanks to the 315 // grace timeout. 316 const uint32_t kMaxIdleConnectionThreadCount = 1; 317 318 static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount, 319 "Idle thread limit must be less than total thread limit!"); 320 321 // The length of time that wanted idle threads will stay alive before being shut 322 // down. 323 const uint32_t kConnectionThreadMaxIdleMS = 30 * 1000; // 30 seconds 324 325 // The length of time that excess idle threads will stay alive before being shut 326 // down. 327 const uint32_t kConnectionThreadGraceIdleMS = 500; // 0.5 seconds 328 329 // The length of time that database connections will be held open after all 330 // transactions have completed before doing idle maintenance. Please keep in 331 // sync with the timeouts in test_connection_idle_maintenance*.js tests 332 const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds 333 334 // The length of time that database connections will be held open after all 335 // transactions and maintenance have completed. 336 const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds 337 338 #define SAVEPOINT_CLAUSE "SAVEPOINT sp;"_ns 339 340 // For efficiency reasons, kEncryptedStreamBlockSize must be a multiple of large 341 // 4k disk sectors. 342 static_assert(kEncryptedStreamBlockSize % 4096 == 0); 343 // Similarly, the file copy buffer size must be a multiple of the encrypted 344 // block size. 345 static_assert(kFileCopyBufferSize % kEncryptedStreamBlockSize == 0); 346 347 constexpr auto kFileManagerDirectoryNameSuffix = u".files"_ns; 348 constexpr auto kSQLiteSuffix = u".sqlite"_ns; 349 constexpr auto kSQLiteJournalSuffix = u".sqlite-journal"_ns; 350 constexpr auto kSQLiteSHMSuffix = u".sqlite-shm"_ns; 351 constexpr auto kSQLiteWALSuffix = u".sqlite-wal"_ns; 352 353 // The following constants define all names of binding parameters in statements, 354 // where they are bound by name. This should include all parameter names which 355 // are bound by name. Binding may be done by index when the statement definition 356 // and binding are done in the same local scope, and no other reasons prevent 357 // using the indexes (e.g. multiple statement variants with differing number or 358 // order of parameters). Neither the styles of specifying parameter names 359 // (literally vs. via these constants) nor the binding styles (by index vs. by 360 // name) should not be mixed for the same statement. The decision must be made 361 // for each statement based on the proximity of statement and binding calls. 362 constexpr auto kStmtParamNameCurrentKey = "current_key"_ns; 363 constexpr auto kStmtParamNameRangeBound = "range_bound"_ns; 364 constexpr auto kStmtParamNameObjectStorePosition = "object_store_position"_ns; 365 constexpr auto kStmtParamNameLowerKey = "lower_key"_ns; 366 constexpr auto kStmtParamNameUpperKey = "upper_key"_ns; 367 constexpr auto kStmtParamNameKey = "key"_ns; 368 constexpr auto kStmtParamNameObjectStoreId = "object_store_id"_ns; 369 constexpr auto kStmtParamNameIndexId = "index_id"_ns; 370 // TODO: Maybe the uses of kStmtParamNameId should be replaced by more 371 // specific constants such as kStmtParamNameObjectStoreId. 372 constexpr auto kStmtParamNameId = "id"_ns; 373 constexpr auto kStmtParamNameValue = "value"_ns; 374 constexpr auto kStmtParamNameObjectDataKey = "object_data_key"_ns; 375 constexpr auto kStmtParamNameIndexDataValues = "index_data_values"_ns; 376 constexpr auto kStmtParamNameData = "data"_ns; 377 constexpr auto kStmtParamNameFileIds = "file_ids"_ns; 378 constexpr auto kStmtParamNameValueLocale = "value_locale"_ns; 379 constexpr auto kStmtParamNameLimit = "limit"_ns; 380 381 // The following constants define some names of columns in tables, which are 382 // referred to in remote locations, e.g. in calls to 383 // GetBindingClauseForKeyRange. 384 constexpr auto kColumnNameKey = "key"_ns; 385 constexpr auto kColumnNameValue = "value"_ns; 386 constexpr auto kColumnNameAliasSortKey = "sort_column"_ns; 387 388 // SQL fragments used at multiple locations. 389 constexpr auto kOpenLimit = " LIMIT "_ns; 390 391 // The deletion marker file is created before RemoveDatabaseFilesAndDirectory 392 // begins deleting a database. It is removed as the last step of deletion. If a 393 // deletion marker file is found when initializing the origin, the deletion 394 // routine is run again to ensure that the database and all of its related files 395 // are removed. The primary goal of this mechanism is to avoid situations where 396 // a database has been partially deleted, leading to inconsistent state for the 397 // origin. 398 constexpr auto kIdbDeletionMarkerFilePrefix = u"idb-deleting-"_ns; 399 400 const uint32_t kDeleteTimeoutMs = 1000; 401 402 #ifdef DEBUG 403 404 const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL; 405 const uint32_t kDEBUGThreadSleepMS = 0; 406 407 // Set to a non-zero number to enable debugging of transaction event targets. 408 // It will cause sleeping after every transaction runnable! 409 // 410 // This can be useful for discovering race conditions related to switching to 411 // another thread. Such races are usually avoided by using MozPromise or 412 // RunAfterProcessingCurrentEvent. Chaos mode doesn't always help with 413 // uncovering these issues, and only a precisely targeted sleep call can 414 // simulate the problem. 415 const uint32_t kDEBUGTransactionThreadSleepMS = 0; 416 417 // Make sure that we notice if we ever accidentally check in a non-zero value. 418 # ifdef MOZILLA_OFFICIAL 419 static_assert(kDEBUGTransactionThreadSleepMS == 0); 420 # endif 421 422 #endif 423 424 /******************************************************************************* 425 * Metadata classes 426 ******************************************************************************/ 427 428 // Can be instantiated either on the QuotaManager IO thread or on a 429 // versionchange transaction thread. These threads can never race so this is 430 // totally safe. 431 struct FullIndexMetadata { 432 IndexMetadata mCommonMetadata = {0, nsString(), KeyPath(0), nsCString(), 433 false, false, false}; 434 435 FlippedOnce<false> mDeleted; 436 437 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata) 438 439 private: 440 ~FullIndexMetadata() = default; 441 }; 442 443 using IndexTable = nsTHashMap<nsUint64HashKey, SafeRefPtr<FullIndexMetadata>>; 444 445 // Can be instantiated either on the QuotaManager IO thread or on a 446 // versionchange transaction thread. These threads can never race so this is 447 // totally safe. 448 struct FullObjectStoreMetadata { 449 ObjectStoreMetadata mCommonMetadata; 450 IndexTable mIndexes; 451 452 // The auto increment ids are touched on both the background thread and the 453 // transaction I/O thread, and they must be kept in sync, so we need a mutex 454 // to protect them. 455 struct AutoIncrementIds { 456 int64_t next; 457 int64_t committed; 458 }; 459 DataMutex<AutoIncrementIds> mAutoIncrementIds; 460 461 FlippedOnce<false> mDeleted; 462 463 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata); 464 465 bool HasLiveIndexes() const; 466 467 FullObjectStoreMetadata(ObjectStoreMetadata aCommonMetadata, 468 const AutoIncrementIds& aAutoIncrementIds) 469 : mCommonMetadata{std::move(aCommonMetadata)}, 470 mAutoIncrementIds{AutoIncrementIds{aAutoIncrementIds}, 471 "FullObjectStoreMetadata"} {} 472 473 private: 474 ~FullObjectStoreMetadata() = default; 475 }; 476 477 using ObjectStoreTable = 478 nsTHashMap<nsUint64HashKey, SafeRefPtr<FullObjectStoreMetadata>>; 479 480 static_assert( 481 std::is_same_v<IndexOrObjectStoreId, 482 std::remove_cv_t<std::remove_reference_t< 483 decltype(std::declval<const ObjectStoreGetParams&>() 484 .objectStoreId())>>>); 485 static_assert( 486 std::is_same_v< 487 IndexOrObjectStoreId, 488 std::remove_cv_t<std::remove_reference_t< 489 decltype(std::declval<const IndexGetParams&>().objectStoreId())>>>); 490 491 struct FullDatabaseMetadata final : AtomicSafeRefCounted<FullDatabaseMetadata> { 492 DatabaseMetadata mCommonMetadata; 493 nsCString mDatabaseId; 494 nsString mFilePath; 495 ObjectStoreTable mObjectStores; 496 497 IndexOrObjectStoreId mNextObjectStoreId = 0; 498 IndexOrObjectStoreId mNextIndexId = 0; 499 500 public: 501 explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata) 502 : mCommonMetadata(aCommonMetadata) { 503 AssertIsOnBackgroundThread(); 504 } 505 506 [[nodiscard]] SafeRefPtr<FullDatabaseMetadata> Duplicate() const; 507 508 MOZ_DECLARE_REFCOUNTED_TYPENAME(FullDatabaseMetadata) 509 }; 510 511 template <class Enumerable> 512 auto MatchMetadataNameOrId(const Enumerable& aEnumerable, 513 IndexOrObjectStoreId aId, 514 Maybe<const nsAString&> aName = Nothing()) { 515 AssertIsOnBackgroundThread(); 516 MOZ_ASSERT(aId); 517 518 const auto it = std::find_if( 519 aEnumerable.cbegin(), aEnumerable.cend(), 520 [aId, aName](const auto& entry) { 521 MOZ_ASSERT(entry.GetKey() != 0); 522 523 const auto& value = entry.GetData(); 524 MOZ_ASSERT(value); 525 526 return !value->mDeleted && 527 (aId == value->mCommonMetadata.id() || 528 (aName && *aName == value->mCommonMetadata.name())); 529 }); 530 531 return ToMaybeRef(it != aEnumerable.cend() ? it->GetData().unsafeGetRawPtr() 532 : nullptr); 533 } 534 535 /******************************************************************************* 536 * SQLite functions 537 ******************************************************************************/ 538 539 // WARNING: the hash function used for the database name must not change. 540 // That's why this function exists separately from mozilla::HashString(), even 541 // though it is (at the time of writing) equivalent. See bug 780408 and bug 542 // 940315 for details. 543 uint32_t HashName(const nsAString& aName) { 544 struct Helper { 545 static uint32_t RotateBitsLeft32(uint32_t aValue, uint8_t aBits) { 546 MOZ_ASSERT(aBits < 32); 547 return (aValue << aBits) | (aValue >> (32 - aBits)); 548 } 549 }; 550 551 static const uint32_t kGoldenRatioU32 = 0x9e3779b9u; 552 553 return std::accumulate(aName.BeginReading(), aName.EndReading(), uint32_t(0), 554 [](uint32_t hash, char16_t ch) { 555 return kGoldenRatioU32 * 556 (Helper::RotateBitsLeft32(hash, 5) ^ ch); 557 }); 558 } 559 560 nsresult ClampResultCode(nsresult aResultCode) { 561 if (NS_SUCCEEDED(aResultCode) || 562 NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) { 563 return aResultCode; 564 } 565 566 switch (aResultCode) { 567 case NS_ERROR_FILE_NO_DEVICE_SPACE: 568 return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; 569 case NS_ERROR_STORAGE_CONSTRAINT: 570 return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; 571 default: 572 #ifdef DEBUG 573 nsPrintfCString message("Converting non-IndexedDB error code (0x%" PRIX32 574 ") to " 575 "NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR", 576 static_cast<uint32_t>(aResultCode)); 577 NS_WARNING(message.get()); 578 #else 579 ; 580 #endif 581 } 582 583 IDB_REPORT_INTERNAL_ERR(); 584 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 585 } 586 587 Result<nsCOMPtr<nsIFileURL>, nsresult> GetDatabaseFileURL( 588 nsIFile& aDatabaseFile, const int64_t aDirectoryLockId, 589 const Maybe<CipherKey>& aMaybeKey) { 590 MOZ_ASSERT(aDirectoryLockId >= -1); 591 592 QM_TRY_INSPECT( 593 const auto& protocolHandler, 594 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIProtocolHandler>, 595 MOZ_SELECT_OVERLOAD(do_GetService), 596 NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file")); 597 598 QM_TRY_INSPECT(const auto& fileHandler, 599 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFileProtocolHandler>, 600 MOZ_SELECT_OVERLOAD(do_QueryInterface), 601 protocolHandler)); 602 603 QM_TRY_INSPECT(const auto& mutator, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 604 nsCOMPtr<nsIURIMutator>, fileHandler, 605 NewFileURIMutator, &aDatabaseFile)); 606 607 // aDirectoryLockId should only be -1 when we are called 608 // - from DatabaseFileManager::InitDirectory when the temporary storage 609 // hasn't been initialized yet. At that time, the in-memory objects (e.g. 610 // OriginInfo) are only being created so it doesn't make sense to tunnel 611 // quota information to QuotaVFS to get corresponding QuotaObject instances 612 // for SQLite files. 613 // - from DeleteDatabaseOp::LoadPreviousVersion, since this might require 614 // temporarily exceeding the quota limit before the database can be 615 // deleted. 616 const nsCString directoryLockIdClause = 617 "&directoryLockId="_ns + IntToCString(aDirectoryLockId); 618 619 const auto keyClause = [&aMaybeKey] { 620 nsAutoCString keyClause; 621 if (aMaybeKey) { 622 keyClause.AssignLiteral("&key="); 623 for (uint8_t byte : IndexedDBCipherStrategy::SerializeKey(*aMaybeKey)) { 624 keyClause.AppendPrintf("%02x", byte); 625 } 626 } 627 return keyClause; 628 }(); 629 630 QM_TRY_UNWRAP(auto result, ([&mutator, &directoryLockIdClause, &keyClause] { 631 nsCOMPtr<nsIFileURL> result; 632 nsresult rv = NS_MutateURI(mutator) 633 .SetQuery("cache=private"_ns + 634 directoryLockIdClause + keyClause) 635 .Finalize(result); 636 return NS_SUCCEEDED(rv) 637 ? Result<nsCOMPtr<nsIFileURL>, nsresult>{result} 638 : Err(rv); 639 }())); 640 641 return result; 642 } 643 644 nsLiteralCString GetDefaultSynchronousMode() { 645 return IndexedDatabaseManager::FullSynchronous() ? "FULL"_ns : "NORMAL"_ns; 646 } 647 648 nsresult SetDefaultPragmas(mozIStorageConnection& aConnection) { 649 MOZ_ASSERT(!NS_IsMainThread()); 650 651 static constexpr auto kBuiltInPragmas = 652 // We use foreign keys in DEBUG builds only because there is a performance 653 // cost to using them. 654 "PRAGMA foreign_keys = " 655 #ifdef DEBUG 656 "ON" 657 #else 658 "OFF" 659 #endif 660 ";" 661 662 // The "INSERT OR REPLACE" statement doesn't fire the update trigger, 663 // instead it fires only the insert trigger. This confuses the update 664 // refcount function. This behavior changes with enabled recursive 665 // triggers, so the statement fires the delete trigger first and then the 666 // insert trigger. 667 "PRAGMA recursive_triggers = ON;" 668 669 // We aggressively truncate the database file when idle so don't bother 670 // overwriting the WAL with 0 during active periods. 671 "PRAGMA secure_delete = OFF;"_ns; 672 673 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(kBuiltInPragmas))); 674 675 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsAutoCString{ 676 "PRAGMA synchronous = "_ns + GetDefaultSynchronousMode() + ";"_ns}))); 677 678 #ifndef IDB_MOBILE 679 if (kSQLiteGrowthIncrement) { 680 // This is just an optimization so ignore the failure if the disk is 681 // currently too full. 682 QM_TRY(QM_OR_ELSE_WARN_IF( 683 // Expression. 684 MOZ_TO_RESULT( 685 aConnection.SetGrowthIncrement(kSQLiteGrowthIncrement, ""_ns)), 686 // Predicate. 687 IsSpecificError<NS_ERROR_FILE_TOO_BIG>, 688 // Fallback. 689 ErrToDefaultOk<>)); 690 } 691 #endif // IDB_MOBILE 692 693 return NS_OK; 694 } 695 696 nsresult SetJournalMode(mozIStorageConnection& aConnection) { 697 MOZ_ASSERT(!NS_IsMainThread()); 698 699 // Try enabling WAL mode. This can fail in various circumstances so we have to 700 // check the results here. 701 constexpr auto journalModeQueryStart = "PRAGMA journal_mode = "_ns; 702 constexpr auto journalModeWAL = "wal"_ns; 703 704 QM_TRY_INSPECT(const auto& stmt, 705 CreateAndExecuteSingleStepStatement( 706 aConnection, journalModeQueryStart + journalModeWAL)); 707 708 QM_TRY_INSPECT( 709 const auto& journalMode, 710 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt, GetUTF8String, 0)); 711 712 if (journalMode.Equals(journalModeWAL)) { 713 // WAL mode successfully enabled. Maybe set limits on its size here. 714 if (kMaxWALPages >= 0) { 715 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL( 716 "PRAGMA wal_autocheckpoint = "_ns + IntToCString(kMaxWALPages)))); 717 } 718 } else { 719 NS_WARNING("Failed to set WAL mode, falling back to normal journal mode."); 720 #ifdef IDB_MOBILE 721 QM_TRY(MOZ_TO_RESULT( 722 aConnection.ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns))); 723 #endif 724 } 725 726 return NS_OK; 727 } 728 729 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> OpenDatabase( 730 mozIStorageService& aStorageService, nsIFileURL& aFileURL, 731 const uint32_t aTelemetryId = 0) { 732 const nsAutoCString telemetryFilename = 733 aTelemetryId ? "indexedDB-"_ns + IntToCString(aTelemetryId) + 734 NS_ConvertUTF16toUTF8(kSQLiteSuffix) 735 : nsAutoCString(); 736 737 QM_TRY_UNWRAP(auto connection, 738 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 739 nsCOMPtr<mozIStorageConnection>, aStorageService, 740 OpenDatabaseWithFileURL, &aFileURL, telemetryFilename, 741 mozIStorageService::CONNECTION_INTERRUPTIBLE)); 742 743 return WrapMovingNotNull(std::move(connection)); 744 } 745 746 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> 747 OpenDatabaseAndHandleBusy(mozIStorageService& aStorageService, 748 nsIFileURL& aFileURL, 749 const uint32_t aTelemetryId = 0) { 750 MOZ_ASSERT(!NS_IsMainThread()); 751 MOZ_ASSERT(!IsOnBackgroundThread()); 752 753 using ConnectionType = Maybe<MovingNotNull<nsCOMPtr<mozIStorageConnection>>>; 754 755 QM_TRY_UNWRAP(auto connection, 756 QM_OR_ELSE_WARN_IF( 757 // Expression 758 OpenDatabase(aStorageService, aFileURL, aTelemetryId) 759 .map([](auto connection) -> ConnectionType { 760 return Some(std::move(connection)); 761 }), 762 // Predicate. 763 IsSpecificError<NS_ERROR_STORAGE_BUSY>, 764 // Fallback. 765 ErrToDefaultOk<ConnectionType>)); 766 767 if (connection.isNothing()) { 768 #ifdef DEBUG 769 { 770 nsCString path; 771 MOZ_ALWAYS_SUCCEEDS(aFileURL.GetFileName(path)); 772 773 nsPrintfCString message( 774 "Received NS_ERROR_STORAGE_BUSY when attempting to open database " 775 "'%s', retrying for up to 10 seconds", 776 path.get()); 777 NS_WARNING(message.get()); 778 } 779 #endif 780 781 // Another thread must be checkpointing the WAL. Wait up to 10 seconds for 782 // that to complete. 783 const TimeStamp start = TimeStamp::NowLoRes(); 784 785 // Use exponential backoff: start at 1ms, double up to 100ms max 786 uint32_t sleepMs = 1; 787 constexpr uint32_t kMaxSleepMs = 100; 788 789 do { 790 PR_Sleep(PR_MillisecondsToInterval(sleepMs)); 791 792 // Exponential backoff with cap 793 sleepMs = std::min(sleepMs * 2, kMaxSleepMs); 794 795 QM_TRY_UNWRAP(connection, 796 QM_OR_ELSE_WARN_IF( 797 // Expression. 798 OpenDatabase(aStorageService, aFileURL, aTelemetryId) 799 .map([](auto connection) -> ConnectionType { 800 return Some(std::move(connection)); 801 }), 802 // Predicate. 803 ([&start](nsresult aValue) { 804 return aValue == NS_ERROR_STORAGE_BUSY && 805 TimeStamp::NowLoRes() - start <= 806 TimeDuration::FromSeconds(10); 807 }), 808 // Fallback. 809 ErrToDefaultOk<ConnectionType>)); 810 } while (connection.isNothing()); 811 } 812 813 return connection.extract(); 814 } 815 816 // Returns true if a given nsIFile exists and is a directory. Returns false if 817 // it doesn't exist. Returns an error if it exists, but is not a directory, or 818 // any other error occurs. 819 Result<bool, nsresult> ExistsAsDirectory(nsIFile& aDirectory) { 820 QM_TRY_INSPECT(const bool& exists, 821 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists)); 822 823 if (exists) { 824 QM_TRY_INSPECT(const bool& isDirectory, 825 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory)); 826 827 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE)); 828 } 829 830 return exists; 831 } 832 833 constexpr nsresult mapNoDeviceSpaceError(nsresult aRv) { 834 if (aRv == NS_ERROR_FILE_NO_DEVICE_SPACE) { 835 // mozstorage translates SQLITE_FULL to 836 // NS_ERROR_FILE_NO_DEVICE_SPACE, which we know better as 837 // NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR. 838 return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; 839 } 840 return aRv; 841 } 842 843 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> 844 CreateStorageConnection(nsIFile& aDBFile, nsIFile& aFMDirectory, 845 const nsAString& aName, const nsACString& aOrigin, 846 const int64_t aDirectoryLockId, 847 const uint32_t aTelemetryId, 848 const Maybe<CipherKey>& aMaybeKey) { 849 AssertIsOnIOThread(); 850 MOZ_ASSERT(aDirectoryLockId >= -1); 851 852 AUTO_PROFILER_LABEL("CreateStorageConnection", DOM); 853 854 QM_TRY_INSPECT(const auto& dbFileUrl, 855 GetDatabaseFileURL(aDBFile, aDirectoryLockId, aMaybeKey)); 856 857 QM_TRY_INSPECT(const auto& storageService, 858 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 859 MOZ_SELECT_OVERLOAD(do_GetService), 860 MOZ_STORAGE_SERVICE_CONTRACTID)); 861 862 QM_TRY_UNWRAP( 863 auto connection, 864 QM_OR_ELSE_WARN_IF( 865 // Expression. 866 OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId) 867 .map([](auto connection) -> nsCOMPtr<mozIStorageConnection> { 868 return std::move(connection).unwrapBasePtr(); 869 }), 870 // Predicate. 871 ([&aName](nsresult aValue) { 872 // If we're just opening the database during origin initialization, 873 // then we don't want to erase any files. The failure here will fail 874 // origin initialization too. 875 return IsDatabaseCorruptionError(aValue) && !aName.IsVoid(); 876 }), 877 // Fallback. 878 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>)); 879 880 if (!connection) { 881 // XXX Shouldn't we also update quota usage? 882 883 // Nuke the database file. 884 QM_TRY(MOZ_TO_RESULT(aDBFile.Remove(false))); 885 QM_TRY_INSPECT(const bool& existsAsDirectory, 886 ExistsAsDirectory(aFMDirectory)); 887 888 if (existsAsDirectory) { 889 QM_TRY(MOZ_TO_RESULT(aFMDirectory.Remove(true))); 890 } 891 892 QM_TRY_UNWRAP(connection, OpenDatabaseAndHandleBusy( 893 *storageService, *dbFileUrl, aTelemetryId)); 894 } 895 896 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection))); 897 QM_TRY(MOZ_TO_RESULT(connection->EnableModule("filesystem"_ns))); 898 899 // Check to make sure that the database schema is correct. 900 QM_TRY_INSPECT(const int32_t& schemaVersion, 901 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion)); 902 903 // Unknown schema will fail origin initialization too. 904 QM_TRY(OkIf(schemaVersion || !aName.IsVoid()), 905 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) { 906 IDB_WARNING("Unable to open IndexedDB database, schema is not set!"); 907 }); 908 909 QM_TRY( 910 OkIf(schemaVersion <= kSQLiteSchemaVersion), 911 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), [](const auto&) { 912 IDB_WARNING("Unable to open IndexedDB database, schema is too high!"); 913 }); 914 915 bool journalModeSet = false; 916 917 if (schemaVersion != kSQLiteSchemaVersion) { 918 const bool newDatabase = !schemaVersion; 919 920 if (newDatabase) { 921 // Set the page size first. 922 const auto sqlitePageSizeOverride = 923 aMaybeKey ? 8192 : kSQLitePageSizeOverride; 924 if (sqlitePageSizeOverride) { 925 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString( 926 "PRAGMA page_size = %" PRIu32 ";", sqlitePageSizeOverride)))); 927 } 928 929 // We have to set the auto_vacuum mode before opening a transaction. 930 QM_TRY((MOZ_TO_RESULT_INVOKE_MEMBER( 931 connection, ExecuteSimpleSQL, 932 #ifdef IDB_MOBILE 933 // Turn on full auto_vacuum mode to reclaim disk space on 934 // mobile devices (at the cost of some COMMIT speed). 935 "PRAGMA auto_vacuum = FULL;"_ns 936 #else 937 // Turn on incremental auto_vacuum mode on desktop builds. 938 "PRAGMA auto_vacuum = INCREMENTAL;"_ns 939 #endif 940 ) 941 .mapErr(mapNoDeviceSpaceError))); 942 943 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection))); 944 945 journalModeSet = true; 946 } else { 947 #ifdef DEBUG 948 // Disable foreign key support while upgrading. This has to be done before 949 // starting a transaction. 950 MOZ_ALWAYS_SUCCEEDS( 951 connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns)); 952 #endif 953 } 954 955 bool vacuumNeeded = false; 956 957 mozStorageTransaction transaction( 958 connection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); 959 960 QM_TRY(MOZ_TO_RESULT(transaction.Start())); 961 962 if (newDatabase) { 963 QM_TRY(MOZ_TO_RESULT(CreateTables(*connection))); 964 965 #ifdef DEBUG 966 { 967 QM_TRY_INSPECT( 968 const int32_t& schemaVersion, 969 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion), 970 QM_ASSERT_UNREACHABLE); 971 MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion); 972 } 973 #endif 974 975 // The parameter names are not used, parameters are bound by index only 976 // locally in the same function. 977 QM_TRY_INSPECT( 978 const auto& stmt, 979 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 980 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement, 981 "INSERT INTO database (name, origin) " 982 "VALUES (:name, :origin)"_ns)); 983 984 QM_TRY(MOZ_TO_RESULT(stmt->BindStringByIndex(0, aName))); 985 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByIndex(1, aOrigin))); 986 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 987 } else { 988 QM_TRY_UNWRAP(vacuumNeeded, MaybeUpgradeSchema(*connection, schemaVersion, 989 aFMDirectory, aOrigin)); 990 } 991 992 QM_TRY(MOZ_TO_RESULT_INVOKE_MEMBER(transaction, Commit) 993 .mapErr(mapNoDeviceSpaceError)); 994 995 #ifdef DEBUG 996 if (!newDatabase) { 997 // Re-enable foreign key support after doing a foreign key check. 998 QM_TRY_INSPECT(const bool& foreignKeyError, 999 CreateAndExecuteSingleStepStatement< 1000 SingleStepResult::ReturnNullIfNoResult>( 1001 *connection, "PRAGMA foreign_key_check;"_ns), 1002 QM_ASSERT_UNREACHABLE); 1003 1004 MOZ_ASSERT(!foreignKeyError, "Database has inconsisistent foreign keys!"); 1005 1006 MOZ_ALWAYS_SUCCEEDS( 1007 connection->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns)); 1008 } 1009 #endif 1010 1011 if (kSQLitePageSizeOverride && !newDatabase) { 1012 QM_TRY_INSPECT(const auto& stmt, 1013 CreateAndExecuteSingleStepStatement( 1014 *connection, "PRAGMA page_size;"_ns)); 1015 1016 QM_TRY_INSPECT(const int32_t& pageSize, 1017 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0)); 1018 MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536); 1019 1020 if (kSQLitePageSizeOverride != uint32_t(pageSize)) { 1021 // We must not be in WAL journal mode to change the page size. 1022 QM_TRY(MOZ_TO_RESULT( 1023 connection->ExecuteSimpleSQL("PRAGMA journal_mode = DELETE;"_ns))); 1024 1025 QM_TRY_INSPECT(const auto& stmt, 1026 CreateAndExecuteSingleStepStatement( 1027 *connection, "PRAGMA journal_mode;"_ns)); 1028 1029 QM_TRY_INSPECT(const auto& journalMode, 1030 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, *stmt, 1031 GetUTF8String, 0)); 1032 1033 if (journalMode.EqualsLiteral("delete")) { 1034 // Successfully set to rollback journal mode so changing the page size 1035 // is possible with a VACUUM. 1036 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString( 1037 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride)))); 1038 1039 // We will need to VACUUM in order to change the page size. 1040 vacuumNeeded = true; 1041 } else { 1042 NS_WARNING( 1043 "Failed to set journal_mode for database, unable to " 1044 "change the page size!"); 1045 } 1046 } 1047 } 1048 1049 if (vacuumNeeded) { 1050 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("VACUUM;"_ns))); 1051 } 1052 1053 if (newDatabase || vacuumNeeded) { 1054 if (journalModeSet) { 1055 // Make sure we checkpoint to get an accurate file size. 1056 QM_TRY(MOZ_TO_RESULT( 1057 connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(FULL);"_ns))); 1058 } 1059 1060 QM_TRY_INSPECT(const int64_t& fileSize, 1061 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, GetFileSize)); 1062 MOZ_ASSERT(fileSize > 0); 1063 1064 PRTime vacuumTime = PR_Now(); 1065 MOZ_ASSERT(vacuumTime); 1066 1067 // The parameter names are not used, parameters are bound by index only 1068 // locally in the same function. 1069 QM_TRY_INSPECT( 1070 const auto& vacuumTimeStmt, 1071 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<mozIStorageStatement>, 1072 connection, CreateStatement, 1073 "UPDATE database " 1074 "SET last_vacuum_time = :time" 1075 ", last_vacuum_size = :size;"_ns)); 1076 1077 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(0, vacuumTime))); 1078 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByIndex(1, fileSize))); 1079 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->Execute())); 1080 } 1081 } 1082 1083 if (!journalModeSet) { 1084 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection))); 1085 } 1086 1087 return WrapMovingNotNullUnchecked(std::move(connection)); 1088 } 1089 1090 nsCOMPtr<nsIFile> GetFileForPath(const nsAString& aPath) { 1091 MOZ_ASSERT(!aPath.IsEmpty()); 1092 1093 QM_TRY_RETURN(QM_NewLocalFile(aPath), nullptr); 1094 } 1095 1096 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> 1097 GetStorageConnection(nsIFile& aDatabaseFile, const int64_t aDirectoryLockId, 1098 const uint32_t aTelemetryId, 1099 const Maybe<CipherKey>& aMaybeKey) { 1100 MOZ_ASSERT(!NS_IsMainThread()); 1101 MOZ_ASSERT(!IsOnBackgroundThread()); 1102 MOZ_ASSERT(aDirectoryLockId >= 0); 1103 1104 AUTO_PROFILER_LABEL("GetStorageConnection", DOM); 1105 1106 QM_TRY_INSPECT(const bool& exists, 1107 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, Exists)); 1108 1109 QM_TRY(OkIf(exists), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), 1110 IDB_REPORT_INTERNAL_ERR_LAMBDA); 1111 1112 QM_TRY_INSPECT( 1113 const auto& dbFileUrl, 1114 GetDatabaseFileURL(aDatabaseFile, aDirectoryLockId, aMaybeKey)); 1115 1116 QM_TRY_INSPECT(const auto& storageService, 1117 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 1118 MOZ_SELECT_OVERLOAD(do_GetService), 1119 MOZ_STORAGE_SERVICE_CONTRACTID)); 1120 1121 QM_TRY_UNWRAP( 1122 nsCOMPtr<mozIStorageConnection> connection, 1123 OpenDatabaseAndHandleBusy(*storageService, *dbFileUrl, aTelemetryId)); 1124 1125 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(*connection))); 1126 1127 QM_TRY(MOZ_TO_RESULT(SetJournalMode(*connection))); 1128 1129 return WrapMovingNotNullUnchecked(std::move(connection)); 1130 } 1131 1132 Result<MovingNotNull<nsCOMPtr<mozIStorageConnection>>, nsresult> 1133 GetStorageConnection(const nsAString& aDatabaseFilePath, 1134 const int64_t aDirectoryLockId, 1135 const uint32_t aTelemetryId, 1136 const Maybe<CipherKey>& aMaybeKey) { 1137 MOZ_ASSERT(!NS_IsMainThread()); 1138 MOZ_ASSERT(!IsOnBackgroundThread()); 1139 MOZ_ASSERT(!aDatabaseFilePath.IsEmpty()); 1140 MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, kSQLiteSuffix)); 1141 MOZ_ASSERT(aDirectoryLockId >= 0); 1142 1143 nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath); 1144 1145 QM_TRY(OkIf(dbFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), 1146 IDB_REPORT_INTERNAL_ERR_LAMBDA); 1147 1148 return GetStorageConnection(*dbFile, aDirectoryLockId, aTelemetryId, 1149 aMaybeKey); 1150 } 1151 1152 /******************************************************************************* 1153 * ConnectionPool declarations 1154 ******************************************************************************/ 1155 1156 class DatabaseConnection final : public CachingDatabaseConnection { 1157 friend class ConnectionPool; 1158 1159 enum class CheckpointMode { Full, Restart, Truncate }; 1160 1161 public: 1162 class AutoSavepoint; 1163 class UpdateRefcountFunction; 1164 1165 private: 1166 InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileManager>>> mFileManager; 1167 RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction; 1168 RefPtr<QuotaObject> mQuotaObject; 1169 RefPtr<QuotaObject> mJournalQuotaObject; 1170 IDBTransaction::Durability mLastDurability; 1171 bool mInReadTransaction; 1172 bool mInWriteTransaction; 1173 1174 #ifdef DEBUG 1175 uint32_t mDEBUGSavepointCount; 1176 #endif 1177 1178 public: 1179 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection) 1180 1181 UpdateRefcountFunction* GetUpdateRefcountFunction() const { 1182 AssertIsOnConnectionThread(); 1183 1184 return mUpdateRefcountFunction; 1185 } 1186 1187 nsresult BeginWriteTransaction(const IDBTransaction::Durability aDurability); 1188 1189 nsresult CommitWriteTransaction(); 1190 1191 void RollbackWriteTransaction(); 1192 1193 void FinishWriteTransaction(); 1194 1195 nsresult StartSavepoint(); 1196 1197 nsresult ReleaseSavepoint(); 1198 1199 nsresult RollbackSavepoint(); 1200 1201 nsresult Checkpoint() { 1202 AssertIsOnConnectionThread(); 1203 1204 return CheckpointInternal(CheckpointMode::Full); 1205 } 1206 1207 void DoIdleProcessing(bool aNeedsCheckpoint, 1208 const Atomic<bool>& aInterrupted); 1209 1210 void Close(); 1211 1212 nsresult DisableQuotaChecks(); 1213 1214 void EnableQuotaChecks(); 1215 1216 private: 1217 DatabaseConnection( 1218 MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection, 1219 MovingNotNull<SafeRefPtr<DatabaseFileManager>> aFileManager); 1220 1221 ~DatabaseConnection(); 1222 1223 nsresult Init(); 1224 1225 nsresult CheckpointInternal(CheckpointMode aMode); 1226 1227 Result<uint32_t, nsresult> GetFreelistCount( 1228 CachedStatement& aCachedStatement); 1229 1230 /** 1231 * On success, returns whether some pages were freed. 1232 */ 1233 Result<bool, nsresult> ReclaimFreePagesWhileIdle( 1234 CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement, 1235 uint32_t aFreelistCount, bool aNeedsCheckpoint, 1236 const Atomic<bool>& aInterrupted); 1237 1238 Result<int64_t, nsresult> GetFileSize(const nsAString& aPath); 1239 }; 1240 1241 class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final { 1242 DatabaseConnection* mConnection; 1243 #ifdef DEBUG 1244 const TransactionBase* mDEBUGTransaction; 1245 #endif 1246 1247 public: 1248 AutoSavepoint(); 1249 ~AutoSavepoint(); 1250 1251 nsresult Start(const TransactionBase& aTransaction); 1252 1253 nsresult Commit(); 1254 }; 1255 1256 class DatabaseConnection::UpdateRefcountFunction final 1257 : public mozIStorageFunction { 1258 class FileInfoEntry; 1259 1260 enum class UpdateType { Increment, Decrement }; 1261 1262 DatabaseConnection* const mConnection; 1263 DatabaseFileManager& mFileManager; 1264 nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries; 1265 nsTHashMap<nsUint64HashKey, NotNull<FileInfoEntry*>> mSavepointEntriesIndex; 1266 1267 nsTArray<int64_t> mJournalsToCreateBeforeCommit; 1268 nsTArray<int64_t> mJournalsToRemoveAfterCommit; 1269 nsTArray<int64_t> mJournalsToRemoveAfterAbort; 1270 1271 bool mInSavepoint; 1272 1273 public: 1274 NS_DECL_ISUPPORTS_ONEVENTTARGET 1275 NS_DECL_MOZISTORAGEFUNCTION 1276 1277 UpdateRefcountFunction(DatabaseConnection* aConnection, 1278 DatabaseFileManager& aFileManager); 1279 1280 nsresult WillCommit(); 1281 1282 void DidCommit(); 1283 1284 void DidAbort(); 1285 1286 void StartSavepoint(); 1287 1288 void ReleaseSavepoint(); 1289 1290 void RollbackSavepoint(); 1291 1292 void Reset(); 1293 1294 private: 1295 ~UpdateRefcountFunction() = default; 1296 1297 nsresult ProcessValue(mozIStorageValueArray* aValues, int32_t aIndex, 1298 UpdateType aUpdateType); 1299 1300 nsresult CreateJournals(); 1301 1302 nsresult RemoveJournals(const nsTArray<int64_t>& aJournals); 1303 }; 1304 1305 class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final { 1306 SafeRefPtr<DatabaseFileInfo> mFileInfo; 1307 int32_t mDelta; 1308 int32_t mSavepointDelta; 1309 1310 public: 1311 explicit FileInfoEntry(SafeRefPtr<DatabaseFileInfo> aFileInfo) 1312 : mFileInfo(std::move(aFileInfo)), mDelta(0), mSavepointDelta(0) { 1313 MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry); 1314 } 1315 1316 void IncDeltas(bool aUpdateSavepointDelta) { 1317 ++mDelta; 1318 if (aUpdateSavepointDelta) { 1319 ++mSavepointDelta; 1320 } 1321 } 1322 void DecDeltas(bool aUpdateSavepointDelta) { 1323 --mDelta; 1324 if (aUpdateSavepointDelta) { 1325 --mSavepointDelta; 1326 } 1327 } 1328 void DecBySavepointDelta() { mDelta -= mSavepointDelta; } 1329 SafeRefPtr<DatabaseFileInfo> ReleaseFileInfo() { 1330 return std::move(mFileInfo); 1331 } 1332 void MaybeUpdateDBRefs() { 1333 if (mDelta) { 1334 mFileInfo->UpdateDBRefs(mDelta); 1335 } 1336 } 1337 1338 int32_t Delta() const { return mDelta; } 1339 int32_t SavepointDelta() const { return mSavepointDelta; } 1340 1341 ~FileInfoEntry() { 1342 MOZ_COUNT_DTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry); 1343 } 1344 }; 1345 1346 class ConnectionPool final { 1347 public: 1348 class FinishCallback; 1349 1350 private: 1351 class ConnectionRunnable; 1352 class CloseConnectionRunnable; 1353 struct DatabaseInfo; 1354 struct DatabaseCompleteCallback; 1355 class FinishCallbackWrapper; 1356 class IdleConnectionRunnable; 1357 1358 #ifdef DEBUG 1359 class TransactionRunnable; 1360 #endif 1361 class TransactionInfo; 1362 struct TransactionInfoPair; 1363 1364 struct IdleResource { 1365 TimeStamp mIdleTime; 1366 1367 IdleResource(const IdleResource& aOther) = delete; 1368 IdleResource(IdleResource&& aOther) noexcept 1369 : IdleResource(aOther.mIdleTime) {} 1370 IdleResource& operator=(const IdleResource& aOther) = delete; 1371 IdleResource& operator=(IdleResource&& aOther) = delete; 1372 1373 protected: 1374 explicit IdleResource(const TimeStamp& aIdleTime); 1375 1376 ~IdleResource(); 1377 }; 1378 1379 struct IdleDatabaseInfo final : public IdleResource { 1380 InitializedOnce<const NotNull<DatabaseInfo*>> mDatabaseInfo; 1381 1382 public: 1383 explicit IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo); 1384 1385 IdleDatabaseInfo(const IdleDatabaseInfo& aOther) = delete; 1386 IdleDatabaseInfo(IdleDatabaseInfo&& aOther) noexcept 1387 : IdleResource(std::move(aOther)), 1388 mDatabaseInfo{std::move(aOther.mDatabaseInfo)} { 1389 MOZ_ASSERT(mDatabaseInfo); 1390 1391 MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo); 1392 } 1393 IdleDatabaseInfo& operator=(const IdleDatabaseInfo& aOther) = delete; 1394 IdleDatabaseInfo& operator=(IdleDatabaseInfo&& aOther) = delete; 1395 1396 ~IdleDatabaseInfo(); 1397 1398 bool operator==(const IdleDatabaseInfo& aOther) const { 1399 return *mDatabaseInfo == *aOther.mDatabaseInfo; 1400 } 1401 1402 bool operator==(const DatabaseInfo* aDatabaseInfo) const { 1403 return *mDatabaseInfo == aDatabaseInfo; 1404 } 1405 1406 bool operator<(const IdleDatabaseInfo& aOther) const { 1407 return mIdleTime < aOther.mIdleTime; 1408 } 1409 }; 1410 1411 struct PerformingIdleMaintenanceDatabaseInfo { 1412 const NotNull<DatabaseInfo*> mDatabaseInfo; 1413 RefPtr<IdleConnectionRunnable> mIdleConnectionRunnable; 1414 1415 PerformingIdleMaintenanceDatabaseInfo( 1416 DatabaseInfo& aDatabaseInfo, 1417 RefPtr<IdleConnectionRunnable> aIdleConnectionRunnable); 1418 1419 PerformingIdleMaintenanceDatabaseInfo( 1420 const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete; 1421 PerformingIdleMaintenanceDatabaseInfo( 1422 PerformingIdleMaintenanceDatabaseInfo&& aOther) noexcept 1423 : mDatabaseInfo{aOther.mDatabaseInfo}, 1424 mIdleConnectionRunnable{std::move(aOther.mIdleConnectionRunnable)} { 1425 MOZ_COUNT_CTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo); 1426 } 1427 PerformingIdleMaintenanceDatabaseInfo& operator=( 1428 const PerformingIdleMaintenanceDatabaseInfo& aOther) = delete; 1429 PerformingIdleMaintenanceDatabaseInfo& operator=( 1430 PerformingIdleMaintenanceDatabaseInfo&& aOther) = delete; 1431 1432 ~PerformingIdleMaintenanceDatabaseInfo(); 1433 1434 bool operator==(const DatabaseInfo* aDatabaseInfo) const { 1435 return mDatabaseInfo == aDatabaseInfo; 1436 } 1437 }; 1438 1439 // This mutex guards mDatabases, see below. 1440 Mutex mDatabasesMutex MOZ_UNANNOTATED; 1441 1442 nsCOMPtr<nsIThreadPool> mIOTarget; 1443 nsTArray<IdleDatabaseInfo> mIdleDatabases; 1444 nsTArray<PerformingIdleMaintenanceDatabaseInfo> 1445 mDatabasesPerformingIdleMaintenance; 1446 nsCOMPtr<nsITimer> mIdleTimer; 1447 TimeStamp mTargetIdleTime; 1448 1449 // Only modifed on the owning thread, but read on multiple threads. Therefore 1450 // all modifications and all reads off the owning thread must be protected by 1451 // mDatabasesMutex. 1452 nsClassHashtable<nsCStringHashKey, DatabaseInfo> mDatabases; 1453 1454 nsClassHashtable<nsUint64HashKey, TransactionInfo> mTransactions; 1455 nsTArray<NotNull<TransactionInfo*>> mQueuedTransactions; 1456 1457 nsTArray<UniquePtr<DatabaseCompleteCallback>> mCompleteCallbacks; 1458 1459 uint64_t mNextTransactionId; 1460 FlippedOnce<false> mShutdownRequested; 1461 FlippedOnce<false> mShutdownComplete; 1462 1463 public: 1464 ConnectionPool(); 1465 1466 void AssertIsOnOwningThread() const { 1467 NS_ASSERT_OWNINGTHREAD(ConnectionPool); 1468 } 1469 1470 Result<RefPtr<DatabaseConnection>, nsresult> GetOrCreateConnection( 1471 const Database& aDatabase); 1472 1473 uint64_t Start(const nsID& aBackgroundChildLoggingId, 1474 const nsACString& aDatabaseId, int64_t aLoggingSerialNumber, 1475 const nsTArray<nsString>& aObjectStoreNames, 1476 bool aIsWriteTransaction, 1477 TransactionDatabaseOperationBase* aTransactionOp); 1478 1479 /** 1480 * Starts a new operation associated with the given transaction. 1481 * 1482 * This method initiates an operation by: 1483 * 1. Dispatching the provided runnable to the task queue created on top of 1484 * the I/O thread pool if the transaction is currently running. 1485 * 2. Queuing the runnable for later execution if the transaction is not yet 1486 * running. 1487 * 1488 * It is mandatory for all operations to call StartOp to ensure proper 1489 * handling and sequencing within the transaction context. 1490 * 1491 * Note: 1492 * - For more complex operations that involve work on other threads or require 1493 * communication with content processes, StartOp should not be called again 1494 * to dispatch to the task queue, as this could disrupt proper queuing and 1495 * execution. 1496 */ 1497 void StartOp(uint64_t aTransactionId, nsCOMPtr<nsIRunnable> aRunnable); 1498 1499 /** 1500 * Marks the completion of an operation associated with the given transaction. 1501 * 1502 * This method signals that the current operation has finished, allowing the 1503 * next queued operation (if any) for the transaction to start. 1504 */ 1505 void FinishOp(uint64_t aTransactionId); 1506 1507 void Finish(uint64_t aTransactionId, FinishCallback* aCallback); 1508 1509 void CloseDatabaseWhenIdle(const nsACString& aDatabaseId) { 1510 (void)CloseDatabaseWhenIdleInternal(aDatabaseId); 1511 } 1512 1513 void WaitForDatabaseToComplete(const nsCString& aDatabaseId, 1514 nsIRunnable* aCallback); 1515 1516 void Shutdown(); 1517 1518 NS_INLINE_DECL_REFCOUNTING(ConnectionPool) 1519 1520 private: 1521 ~ConnectionPool(); 1522 1523 static void IdleTimerCallback(nsITimer* aTimer, void* aClosure); 1524 1525 static uint32_t SerialNumber() { return ++sSerialNumber; } 1526 1527 static uint32_t sSerialNumber; 1528 1529 void Cleanup(); 1530 1531 void AdjustIdleTimer(); 1532 1533 void CancelIdleTimer(); 1534 1535 void CloseIdleDatabases(); 1536 1537 bool ScheduleTransaction(TransactionInfo& aTransactionInfo, 1538 bool aFromQueuedTransactions); 1539 1540 void NoteFinishedTransaction(uint64_t aTransactionId); 1541 1542 void ScheduleQueuedTransactions(); 1543 1544 void NoteIdleDatabase(DatabaseInfo& aDatabaseInfo); 1545 1546 void NoteClosedDatabase(DatabaseInfo& aDatabaseInfo); 1547 1548 bool MaybeFireCallback(DatabaseCompleteCallback* aCallback); 1549 1550 void PerformIdleDatabaseMaintenance(DatabaseInfo& aDatabaseInfo); 1551 1552 void CloseDatabase(DatabaseInfo& aDatabaseInfo) const; 1553 1554 bool CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId); 1555 }; 1556 1557 class ConnectionPool::ConnectionRunnable : public Runnable { 1558 protected: 1559 DatabaseInfo& mDatabaseInfo; 1560 nsCOMPtr<nsIEventTarget> mOwningEventTarget; 1561 1562 explicit ConnectionRunnable(DatabaseInfo& aDatabaseInfo); 1563 1564 ~ConnectionRunnable() override = default; 1565 }; 1566 1567 class ConnectionPool::IdleConnectionRunnable final : public ConnectionRunnable { 1568 const bool mNeedsCheckpoint; 1569 Atomic<bool> mInterrupted; 1570 1571 public: 1572 IdleConnectionRunnable(DatabaseInfo& aDatabaseInfo, bool aNeedsCheckpoint) 1573 : ConnectionRunnable(aDatabaseInfo), mNeedsCheckpoint(aNeedsCheckpoint) {} 1574 1575 NS_INLINE_DECL_REFCOUNTING_INHERITED(IdleConnectionRunnable, 1576 ConnectionRunnable) 1577 1578 void Interrupt() { mInterrupted = true; } 1579 1580 private: 1581 ~IdleConnectionRunnable() override = default; 1582 1583 NS_DECL_NSIRUNNABLE 1584 }; 1585 1586 class ConnectionPool::CloseConnectionRunnable final 1587 : public ConnectionRunnable { 1588 public: 1589 explicit CloseConnectionRunnable(DatabaseInfo& aDatabaseInfo) 1590 : ConnectionRunnable(aDatabaseInfo) {} 1591 1592 NS_INLINE_DECL_REFCOUNTING_INHERITED(CloseConnectionRunnable, 1593 ConnectionRunnable) 1594 1595 private: 1596 ~CloseConnectionRunnable() override = default; 1597 1598 NS_DECL_NSIRUNNABLE 1599 }; 1600 1601 struct ConnectionPool::DatabaseInfo final { 1602 friend mozilla::DefaultDelete<DatabaseInfo>; 1603 1604 RefPtr<ConnectionPool> mConnectionPool; 1605 const nsCString mDatabaseId; 1606 RefPtr<DatabaseConnection> mConnection; 1607 nsClassHashtable<nsStringHashKey, TransactionInfoPair> mBlockingTransactions; 1608 nsTArray<NotNull<TransactionInfo*>> mTransactionsScheduledDuringClose; 1609 nsTArray<NotNull<TransactionInfo*>> mScheduledWriteTransactions; 1610 Maybe<TransactionInfo&> mRunningWriteTransaction; 1611 RefPtr<TaskQueue> mEventTarget; 1612 uint32_t mReadTransactionCount; 1613 uint32_t mWriteTransactionCount; 1614 bool mNeedsCheckpoint; 1615 bool mIdle; 1616 FlippedOnce<false> mCloseOnIdle; 1617 bool mClosing; 1618 1619 #ifdef DEBUG 1620 nsISerialEventTarget* mDEBUGConnectionEventTarget; 1621 #endif 1622 1623 DatabaseInfo(ConnectionPool* aConnectionPool, const nsACString& aDatabaseId); 1624 1625 void AssertIsOnConnectionThread() const { 1626 MOZ_ASSERT(mDEBUGConnectionEventTarget); 1627 MOZ_ASSERT(GetCurrentSerialEventTarget() == mDEBUGConnectionEventTarget); 1628 } 1629 1630 uint64_t TotalTransactionCount() const { 1631 return mReadTransactionCount + mWriteTransactionCount; 1632 } 1633 1634 nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable); 1635 1636 private: 1637 ~DatabaseInfo(); 1638 1639 DatabaseInfo(const DatabaseInfo&) = delete; 1640 DatabaseInfo& operator=(const DatabaseInfo&) = delete; 1641 }; 1642 1643 struct ConnectionPool::DatabaseCompleteCallback final { 1644 friend DefaultDelete<DatabaseCompleteCallback>; 1645 1646 nsCString mDatabaseId; 1647 nsCOMPtr<nsIRunnable> mCallback; 1648 1649 DatabaseCompleteCallback(const nsCString& aDatabaseIds, 1650 nsIRunnable* aCallback); 1651 1652 private: 1653 ~DatabaseCompleteCallback(); 1654 }; 1655 1656 class NS_NO_VTABLE ConnectionPool::FinishCallback : public nsIRunnable { 1657 public: 1658 // Called on the owning thread before any additional transactions are 1659 // unblocked. 1660 virtual void TransactionFinishedBeforeUnblock() = 0; 1661 1662 // Called on the owning thread after additional transactions may have been 1663 // unblocked. 1664 virtual void TransactionFinishedAfterUnblock() = 0; 1665 1666 protected: 1667 FinishCallback() = default; 1668 1669 virtual ~FinishCallback() = default; 1670 }; 1671 1672 class ConnectionPool::FinishCallbackWrapper final : public Runnable { 1673 RefPtr<ConnectionPool> mConnectionPool; 1674 RefPtr<FinishCallback> mCallback; 1675 nsCOMPtr<nsIEventTarget> mOwningEventTarget; 1676 uint64_t mTransactionId; 1677 bool mHasRunOnce; 1678 1679 public: 1680 FinishCallbackWrapper(ConnectionPool* aConnectionPool, 1681 uint64_t aTransactionId, FinishCallback* aCallback); 1682 1683 NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishCallbackWrapper, Runnable) 1684 1685 private: 1686 ~FinishCallbackWrapper() override; 1687 1688 NS_DECL_NSIRUNNABLE 1689 }; 1690 1691 #ifdef DEBUG 1692 1693 class ConnectionPool::TransactionRunnable final : public Runnable { 1694 public: 1695 explicit TransactionRunnable(nsCOMPtr<nsIRunnable> aRunnable); 1696 1697 private: 1698 NS_DECL_NSIRUNNABLE 1699 1700 nsCOMPtr<nsIRunnable> mRunnable; 1701 }; 1702 1703 #endif 1704 1705 class ConnectionPool::TransactionInfo final { 1706 friend mozilla::DefaultDelete<TransactionInfo>; 1707 1708 nsTHashSet<TransactionInfo*> mBlocking; 1709 nsTArray<NotNull<TransactionInfo*>> mBlockingOrdered; 1710 1711 public: 1712 DatabaseInfo& mDatabaseInfo; 1713 const nsID mBackgroundChildLoggingId; 1714 const nsCString mDatabaseId; 1715 const uint64_t mTransactionId; 1716 const int64_t mLoggingSerialNumber; 1717 const nsTArray<nsString> mObjectStoreNames; 1718 nsTHashSet<TransactionInfo*> mBlockedOn; 1719 mozilla::Queue<nsCOMPtr<nsIRunnable>, 16> mQueuedOps; 1720 const bool mIsWriteTransaction; 1721 bool mRunning; 1722 bool mRunningOp; 1723 1724 #ifdef DEBUG 1725 FlippedOnce<false> mFinished; 1726 #endif 1727 1728 TransactionInfo(DatabaseInfo& aDatabaseInfo, 1729 const nsID& aBackgroundChildLoggingId, 1730 const nsACString& aDatabaseId, uint64_t aTransactionId, 1731 int64_t aLoggingSerialNumber, 1732 const nsTArray<nsString>& aObjectStoreNames, 1733 bool aIsWriteTransaction, 1734 TransactionDatabaseOperationBase* aTransactionOp); 1735 1736 void AddBlockingTransaction(TransactionInfo& aTransactionInfo); 1737 1738 void RemoveBlockingTransactions(); 1739 1740 void SetRunning(); 1741 1742 void StartOp(nsCOMPtr<nsIRunnable> aRunnable); 1743 1744 void FinishOp(); 1745 1746 private: 1747 ~TransactionInfo(); 1748 1749 void MaybeUnblock(TransactionInfo& aTransactionInfo); 1750 }; 1751 1752 struct ConnectionPool::TransactionInfoPair final { 1753 // Multiple reading transactions can block future writes. 1754 nsTArray<NotNull<TransactionInfo*>> mLastBlockingWrites; 1755 // But only a single writing transaction can block future reads. 1756 Maybe<TransactionInfo&> mLastBlockingReads; 1757 1758 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) 1759 TransactionInfoPair(); 1760 ~TransactionInfoPair(); 1761 #endif 1762 }; 1763 1764 /******************************************************************************* 1765 * Actor class declarations 1766 ******************************************************************************/ 1767 1768 template <IDBCursorType CursorType> 1769 class CommonOpenOpHelper; 1770 template <IDBCursorType CursorType> 1771 class IndexOpenOpHelper; 1772 template <IDBCursorType CursorType> 1773 class ObjectStoreOpenOpHelper; 1774 template <IDBCursorType CursorType> 1775 class OpenOpHelper; 1776 1777 class DatabaseOperationBase : public Runnable, 1778 public mozIStorageProgressHandler { 1779 template <IDBCursorType CursorType> 1780 friend class OpenOpHelper; 1781 1782 protected: 1783 class AutoSetProgressHandler; 1784 1785 using UniqueIndexTable = nsTHashMap<nsUint64HashKey, bool>; 1786 1787 const nsCOMPtr<nsIEventTarget> mOwningEventTarget; 1788 const nsID mBackgroundChildLoggingId; 1789 const uint64_t mLoggingSerialNumber; 1790 1791 private: 1792 nsresult mResultCode = NS_OK; 1793 Atomic<bool> mOperationMayProceed; 1794 FlippedOnce<false> mActorDestroyed; 1795 1796 public: 1797 NS_DECL_ISUPPORTS_INHERITED 1798 1799 bool IsOnOwningThread() const { 1800 MOZ_ASSERT(mOwningEventTarget); 1801 1802 bool current; 1803 return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) && 1804 current; 1805 } 1806 1807 void AssertIsOnOwningThread() const { 1808 MOZ_ASSERT(IsOnBackgroundThread()); 1809 MOZ_ASSERT(IsOnOwningThread()); 1810 } 1811 1812 void NoteActorDestroyed() { 1813 AssertIsOnOwningThread(); 1814 1815 mActorDestroyed.EnsureFlipped(); 1816 mOperationMayProceed = false; 1817 } 1818 1819 bool IsActorDestroyed() const { 1820 AssertIsOnOwningThread(); 1821 1822 return mActorDestroyed; 1823 } 1824 1825 // May be called on any thread, but you should call IsActorDestroyed() if 1826 // you know you're on the background thread because it is slightly faster. 1827 bool OperationMayProceed() const { return mOperationMayProceed; } 1828 1829 const nsID& BackgroundChildLoggingId() const { 1830 return mBackgroundChildLoggingId; 1831 } 1832 1833 uint64_t LoggingSerialNumber() const { return mLoggingSerialNumber; } 1834 1835 nsresult ResultCode() const { return mResultCode; } 1836 1837 void SetFailureCode(nsresult aFailureCode) { 1838 MOZ_ASSERT(NS_SUCCEEDED(mResultCode)); 1839 OverrideFailureCode(aFailureCode); 1840 } 1841 1842 void SetFailureCodeIfUnset(nsresult aFailureCode) { 1843 if (NS_SUCCEEDED(mResultCode)) { 1844 OverrideFailureCode(aFailureCode); 1845 } 1846 } 1847 1848 bool HasFailed() const { return NS_FAILED(mResultCode); } 1849 1850 protected: 1851 DatabaseOperationBase(const nsID& aBackgroundChildLoggingId, 1852 uint64_t aLoggingSerialNumber) 1853 : Runnable("dom::indexedDB::DatabaseOperationBase"), 1854 mOwningEventTarget(GetCurrentSerialEventTarget()), 1855 mBackgroundChildLoggingId(aBackgroundChildLoggingId), 1856 mLoggingSerialNumber(aLoggingSerialNumber), 1857 mOperationMayProceed(true) { 1858 AssertIsOnOwningThread(); 1859 } 1860 1861 ~DatabaseOperationBase() override { MOZ_ASSERT(mActorDestroyed); } 1862 1863 void OverrideFailureCode(nsresult aFailureCode) { 1864 MOZ_ASSERT(NS_FAILED(aFailureCode)); 1865 1866 mResultCode = aFailureCode; 1867 } 1868 1869 static nsAutoCString MaybeGetBindingClauseForKeyRange( 1870 const Maybe<SerializedKeyRange>& aOptionalKeyRange, 1871 const nsACString& aKeyColumnName); 1872 1873 static nsAutoCString GetBindingClauseForKeyRange( 1874 const SerializedKeyRange& aKeyRange, const nsACString& aKeyColumnName); 1875 1876 static uint64_t ReinterpretDoubleAsUInt64(double aDouble); 1877 1878 static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange, 1879 mozIStorageStatement* aStatement); 1880 1881 static nsresult BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange, 1882 mozIStorageStatement* aStatement, 1883 const nsCString& aLocale); 1884 1885 static Result<IndexDataValuesAutoArray, nsresult> 1886 IndexDataValuesFromUpdateInfos(const nsTArray<IndexUpdateInfo>& aUpdateInfos, 1887 const UniqueIndexTable& aUniqueIndexTable); 1888 1889 static nsresult InsertIndexTableRows( 1890 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId, 1891 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues); 1892 1893 static nsresult DeleteIndexDataTableRows( 1894 DatabaseConnection* aConnection, const Key& aObjectStoreKey, 1895 const nsTArray<IndexDataValue>& aIndexValues); 1896 1897 static nsresult DeleteObjectStoreDataTableRowsWithIndexes( 1898 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId, 1899 const Maybe<SerializedKeyRange>& aKeyRange); 1900 1901 static nsresult UpdateIndexValues( 1902 DatabaseConnection* aConnection, IndexOrObjectStoreId aObjectStoreId, 1903 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues); 1904 1905 static Result<bool, nsresult> ObjectStoreHasIndexes( 1906 DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId); 1907 1908 private: 1909 template <typename KeyTransformation> 1910 static nsresult MaybeBindKeyToStatement( 1911 const Key& aKey, mozIStorageStatement* aStatement, 1912 const nsACString& aParameterName, 1913 const KeyTransformation& aKeyTransformation); 1914 1915 template <typename KeyTransformation> 1916 static nsresult BindTransformedKeyRangeToStatement( 1917 const SerializedKeyRange& aKeyRange, mozIStorageStatement* aStatement, 1918 const KeyTransformation& aKeyTransformation); 1919 1920 // Not to be overridden by subclasses. 1921 NS_DECL_MOZISTORAGEPROGRESSHANDLER 1922 }; 1923 1924 class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final { 1925 Maybe<mozIStorageConnection&> mConnection; 1926 #ifdef DEBUG 1927 DatabaseOperationBase* mDEBUGDatabaseOp; 1928 #endif 1929 1930 public: 1931 AutoSetProgressHandler(); 1932 1933 ~AutoSetProgressHandler(); 1934 1935 nsresult Register(mozIStorageConnection& aConnection, 1936 DatabaseOperationBase* aDatabaseOp); 1937 1938 void Unregister(); 1939 }; 1940 1941 class TransactionDatabaseOperationBase : public DatabaseOperationBase { 1942 enum class InternalState { 1943 Initial, 1944 DatabaseWork, 1945 SendingPreprocess, 1946 WaitingForContinue, 1947 SendingResults, 1948 Completed 1949 }; 1950 1951 InitializedOnce<const NotNull<SafeRefPtr<TransactionBase>>> mTransaction; 1952 // Unique request id within the context of the transaction, allocated by the 1953 // transaction in the content process starting from 0. Values less than 0 are 1954 // impossible and forbidden. Used to support the explicit commit() request. 1955 const int64_t mRequestId; 1956 InternalState mInternalState = InternalState::Initial; 1957 bool mWaitingForContinue = false; 1958 const bool mTransactionIsAborted; 1959 1960 protected: 1961 const int64_t mTransactionLoggingSerialNumber; 1962 1963 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 1964 protected: 1965 // A check only enables when the diagnostic assert turns on. It assumes the 1966 // mUpdateRefcountFunction is a nullptr because the previous 1967 // StartTransactionOp failed on the connection thread and the next write 1968 // operation (e.g. ObjectstoreAddOrPutRequestOp) doesn't have enough time to 1969 // catch up the failure information. 1970 bool mAssumingPreviousOperationFail = false; 1971 #endif 1972 1973 public: 1974 void AssertIsOnConnectionThread() const 1975 #ifdef DEBUG 1976 ; 1977 #else 1978 { 1979 } 1980 #endif 1981 1982 uint64_t StartOnConnectionPool(const nsID& aBackgroundChildLoggingId, 1983 const nsACString& aDatabaseId, 1984 int64_t aLoggingSerialNumber, 1985 const nsTArray<nsString>& aObjectStoreNames, 1986 bool aIsWriteTransaction); 1987 1988 void DispatchToConnectionPool(); 1989 1990 TransactionBase& Transaction() { return **mTransaction; } 1991 1992 const TransactionBase& Transaction() const { return **mTransaction; } 1993 1994 bool IsWaitingForContinue() const { 1995 AssertIsOnOwningThread(); 1996 1997 return mWaitingForContinue; 1998 } 1999 2000 void NoteContinueReceived(); 2001 2002 int64_t TransactionLoggingSerialNumber() const { 2003 return mTransactionLoggingSerialNumber; 2004 } 2005 2006 // May be overridden by subclasses if they need to perform work on the 2007 // background thread before being dispatched. Returning false will kill the 2008 // child actors and prevent dispatch. 2009 virtual bool Init(TransactionBase& aTransaction); 2010 2011 // This callback will be called on the background thread before releasing the 2012 // final reference to this request object. Subclasses may perform any 2013 // additional cleanup here but must always call the base class implementation. 2014 virtual void Cleanup(); 2015 2016 protected: 2017 TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction, 2018 int64_t aRequestId); 2019 2020 TransactionDatabaseOperationBase(SafeRefPtr<TransactionBase> aTransaction, 2021 const int64_t aRequestId, 2022 uint64_t aLoggingSerialNumber); 2023 2024 ~TransactionDatabaseOperationBase() override; 2025 2026 virtual void RunOnConnectionThread(); 2027 2028 // Must be overridden in subclasses. Called on the target thread to allow the 2029 // subclass to perform necessary database or file operations. A successful 2030 // return value will trigger a SendSuccessResult callback on the background 2031 // thread while a failure value will trigger a SendFailureResult callback. 2032 virtual nsresult DoDatabaseWork(DatabaseConnection* aConnection) = 0; 2033 2034 // May be overriden in subclasses. Called on the background thread to decide 2035 // if the subclass needs to send any preprocess info to the child actor. 2036 virtual bool HasPreprocessInfo(); 2037 2038 // May be overriden in subclasses. Called on the background thread to allow 2039 // the subclass to serialize its preprocess info and send it to the child 2040 // actor. A successful return value will trigger a wait for a 2041 // NoteContinueReceived callback on the background thread while a failure 2042 // value will trigger a SendFailureResult callback. 2043 virtual nsresult SendPreprocessInfo(); 2044 2045 // Must be overridden in subclasses. Called on the background thread to allow 2046 // the subclass to serialize its results and send them to the child actor. A 2047 // failed return value will trigger a SendFailureResult callback. 2048 virtual nsresult SendSuccessResult() = 0; 2049 2050 // Must be overridden in subclasses. Called on the background thread to allow 2051 // the subclass to send its failure code. Returning false will cause the 2052 // transaction to be aborted with aResultCode. Returning true will not cause 2053 // the transaction to be aborted. 2054 virtual bool SendFailureResult(nsresult aResultCode) = 0; 2055 2056 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 2057 auto MakeAutoSavepointCleanupHandler(DatabaseConnection& aConnection) { 2058 return [this, &aConnection](const auto) { 2059 if (!aConnection.GetUpdateRefcountFunction()) { 2060 mAssumingPreviousOperationFail = true; 2061 } 2062 }; 2063 } 2064 #endif 2065 2066 private: 2067 void SendToConnectionPool(); 2068 2069 void SendPreprocess(); 2070 2071 void SendResults(); 2072 2073 void SendPreprocessInfoOrResults(bool aSendPreprocessInfo); 2074 2075 // Not to be overridden by subclasses. 2076 NS_DECL_NSIRUNNABLE 2077 }; 2078 2079 class Factory final : public PBackgroundIDBFactoryParent, 2080 public AtomicSafeRefCounted<Factory> { 2081 nsCString mSystemLocale; 2082 RefPtr<DatabaseLoggingInfo> mLoggingInfo; 2083 2084 #ifdef DEBUG 2085 bool mActorDestroyed; 2086 #endif 2087 2088 // Reference counted. 2089 ~Factory() override; 2090 2091 public: 2092 [[nodiscard]] static SafeRefPtr<Factory> Create( 2093 const LoggingInfo& aLoggingInfo, const nsACString& aSystemLocale); 2094 2095 DatabaseLoggingInfo* GetLoggingInfo() const { 2096 AssertIsOnBackgroundThread(); 2097 MOZ_ASSERT(mLoggingInfo); 2098 2099 return mLoggingInfo; 2100 } 2101 2102 const nsCString& GetSystemLocale() const { return mSystemLocale; } 2103 2104 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Factory) 2105 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(Factory, AtomicSafeRefCounted) 2106 2107 // Only constructed in Create(). 2108 Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo, 2109 const nsACString& aSystemLocale); 2110 2111 // IPDL methods are only called by IPDL. 2112 void ActorDestroy(ActorDestroyReason aWhy) override; 2113 2114 mozilla::ipc::IPCResult RecvDeleteMe() override; 2115 2116 PBackgroundIDBFactoryRequestParent* AllocPBackgroundIDBFactoryRequestParent( 2117 const FactoryRequestParams& aParams) override; 2118 2119 mozilla::ipc::IPCResult RecvPBackgroundIDBFactoryRequestConstructor( 2120 PBackgroundIDBFactoryRequestParent* aActor, 2121 const FactoryRequestParams& aParams) override; 2122 2123 bool DeallocPBackgroundIDBFactoryRequestParent( 2124 PBackgroundIDBFactoryRequestParent* aActor) override; 2125 2126 mozilla::ipc::IPCResult RecvGetDatabases( 2127 const PersistenceType& aPersistenceType, 2128 const PrincipalInfo& aPrincipalInfo, 2129 GetDatabasesResolver&& aResolve) override; 2130 2131 private: 2132 Maybe<ContentParentId> GetContentParentId() const; 2133 }; 2134 2135 class WaitForTransactionsHelper final : public Runnable { 2136 const nsCString mDatabaseId; 2137 nsCOMPtr<nsIRunnable> mCallback; 2138 2139 enum class State { Initial = 0, WaitingForTransactions, Complete } mState; 2140 2141 public: 2142 WaitForTransactionsHelper(const nsACString& aDatabaseId, 2143 nsIRunnable* aCallback) 2144 : Runnable("dom::indexedDB::WaitForTransactionsHelper"), 2145 mDatabaseId(aDatabaseId), 2146 mCallback(aCallback), 2147 mState(State::Initial) { 2148 AssertIsOnBackgroundThread(); 2149 MOZ_ASSERT(!aDatabaseId.IsEmpty()); 2150 MOZ_ASSERT(aCallback); 2151 } 2152 2153 void WaitForTransactions(); 2154 2155 NS_INLINE_DECL_REFCOUNTING_INHERITED(WaitForTransactionsHelper, Runnable) 2156 2157 private: 2158 ~WaitForTransactionsHelper() override { 2159 MOZ_ASSERT(!mCallback); 2160 MOZ_ASSERT(mState == State::Complete); 2161 } 2162 2163 void MaybeWaitForTransactions(); 2164 2165 void CallCallback(); 2166 2167 NS_DECL_NSIRUNNABLE 2168 }; 2169 2170 class Database final : public PBackgroundIDBDatabaseParent, 2171 public LinkedListElement<Database>, 2172 public AtomicSafeRefCounted<Database> { 2173 friend class VersionChangeTransaction; 2174 2175 class StartTransactionOp; 2176 class UnmapBlobCallback; 2177 2178 private: 2179 SafeRefPtr<Factory> mFactory; 2180 SafeRefPtr<FullDatabaseMetadata> mMetadata; 2181 SafeRefPtr<DatabaseFileManager> mFileManager; 2182 ClientDirectoryLockHandle mDirectoryLockHandle; 2183 nsTHashSet<TransactionBase*> mTransactions; 2184 nsTHashMap<nsIDHashKey, SafeRefPtr<DatabaseFileInfo>> mMappedBlobs; 2185 RefPtr<DatabaseConnection> mConnection; 2186 const PrincipalInfo mPrincipalInfo; 2187 const Maybe<ContentParentId> mOptionalContentParentId; 2188 // XXX Consider changing this to ClientMetadata. 2189 const quota::OriginMetadata mOriginMetadata; 2190 const nsCString mId; 2191 const nsString mFilePath; 2192 const Maybe<const CipherKey> mKey; 2193 int64_t mDirectoryLockId; 2194 const uint32_t mTelemetryId; 2195 const PersistenceType mPersistenceType; 2196 const bool mInPrivateBrowsing; 2197 FlippedOnce<false> mClosed; 2198 FlippedOnce<false> mInvalidated; 2199 FlippedOnce<false> mActorWasAlive; 2200 FlippedOnce<false> mActorDestroyed; 2201 nsCOMPtr<nsIEventTarget> mBackgroundThread; 2202 #ifdef DEBUG 2203 bool mAllBlobsUnmapped; 2204 #endif 2205 2206 public: 2207 // Created by OpenDatabaseOp. 2208 Database(SafeRefPtr<Factory> aFactory, const PrincipalInfo& aPrincipalInfo, 2209 const Maybe<ContentParentId>& aOptionalContentParentId, 2210 const quota::OriginMetadata& aOriginMetadata, uint32_t aTelemetryId, 2211 SafeRefPtr<FullDatabaseMetadata> aMetadata, 2212 SafeRefPtr<DatabaseFileManager> aFileManager, 2213 ClientDirectoryLockHandle aDirectoryLockHandle, 2214 bool aInPrivateBrowsing, const Maybe<const CipherKey>& aMaybeKey); 2215 2216 void AssertIsOnConnectionThread() const { 2217 #ifdef DEBUG 2218 // mConnection is used to cache the result from ConnectionPool's 2219 // GetOrCreateConnection method (potentially avoiding a lock and a hash 2220 // lookup). However, once the connection is closed, the task queue for the 2221 // given database is also destroyed, so the connection, which caches the 2222 // event target it was created on, is no longer reliable for asserting that 2223 // the current thread is the connection thread (mConnection might be reset 2224 // when EnsureConnection is called again, but in the meantime, we have to 2225 // fallback to just checking the main thread and the PBackgroud thread). 2226 if (mConnection && !mConnection->Closed()) { 2227 mConnection->AssertIsOnConnectionThread(); 2228 } else { 2229 MOZ_ASSERT(!NS_IsMainThread()); 2230 MOZ_ASSERT(!IsOnBackgroundThread()); 2231 MOZ_ASSERT(mInvalidated); 2232 } 2233 #endif 2234 } 2235 2236 NS_IMETHOD_(MozExternalRefCountType) AddRef() override { 2237 return AtomicSafeRefCounted<Database>::AddRef(); 2238 } 2239 NS_IMETHOD_(MozExternalRefCountType) Release() override { 2240 return AtomicSafeRefCounted<Database>::Release(); 2241 } 2242 2243 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::Database) 2244 2245 void Invalidate(); 2246 2247 bool IsOwnedByProcess(ContentParentId aContentParentId) const { 2248 return mOptionalContentParentId && 2249 mOptionalContentParentId.value() == aContentParentId; 2250 } 2251 2252 const quota::OriginMetadata& OriginMetadata() const { 2253 return mOriginMetadata; 2254 } 2255 2256 const nsCString& Id() const { return mId; } 2257 2258 Maybe<ClientDirectoryLock&> MaybeDirectoryLockRef() const { 2259 AssertIsOnBackgroundThread(); 2260 2261 return ToMaybeRef(mDirectoryLockHandle.get()); 2262 } 2263 2264 int64_t DirectoryLockId() const { return mDirectoryLockId; } 2265 2266 uint32_t TelemetryId() const { return mTelemetryId; } 2267 2268 PersistenceType Type() const { return mPersistenceType; } 2269 2270 const nsString& FilePath() const { return mFilePath; } 2271 2272 DatabaseFileManager& GetFileManager() const { return *mFileManager; } 2273 2274 MovingNotNull<SafeRefPtr<DatabaseFileManager>> GetFileManagerPtr() const { 2275 return WrapMovingNotNull(mFileManager.clonePtr()); 2276 } 2277 2278 const FullDatabaseMetadata& Metadata() const { 2279 MOZ_ASSERT(mMetadata); 2280 return *mMetadata; 2281 } 2282 2283 SafeRefPtr<FullDatabaseMetadata> MetadataPtr() const { 2284 MOZ_ASSERT(mMetadata); 2285 return mMetadata.clonePtr(); 2286 } 2287 2288 PBackgroundParent* GetBackgroundParent() const { 2289 AssertIsOnBackgroundThread(); 2290 MOZ_ASSERT(!IsActorDestroyed()); 2291 2292 return Manager()->Manager(); 2293 } 2294 2295 DatabaseLoggingInfo* GetLoggingInfo() const { 2296 AssertIsOnBackgroundThread(); 2297 MOZ_ASSERT(mFactory); 2298 2299 return mFactory->GetLoggingInfo(); 2300 } 2301 2302 bool RegisterTransaction(TransactionBase& aTransaction); 2303 2304 void UnregisterTransaction(TransactionBase& aTransaction); 2305 2306 void SetActorAlive(); 2307 2308 void MapBlob(const IPCBlob& aIPCBlob, SafeRefPtr<DatabaseFileInfo> aFileInfo); 2309 2310 bool IsActorAlive() const { 2311 AssertIsOnBackgroundThread(); 2312 2313 return mActorWasAlive && !mActorDestroyed; 2314 } 2315 2316 bool IsActorDestroyed() const { 2317 AssertIsOnBackgroundThread(); 2318 2319 return mActorWasAlive && mActorDestroyed; 2320 } 2321 2322 bool IsClosed() const { 2323 AssertIsOnBackgroundThread(); 2324 2325 return mClosed; 2326 } 2327 2328 bool IsInvalidated() const { 2329 AssertIsOnBackgroundThread(); 2330 2331 return mInvalidated; 2332 } 2333 2334 nsresult EnsureConnection(); 2335 2336 DatabaseConnection* GetConnection() const { 2337 #ifdef DEBUG 2338 if (mConnection) { 2339 mConnection->AssertIsOnConnectionThread(); 2340 } 2341 #endif 2342 2343 return mConnection; 2344 } 2345 2346 void Stringify(nsACString& aResult) const; 2347 2348 bool IsInPrivateBrowsing() const { 2349 AssertIsOnBackgroundThread(); 2350 return mInPrivateBrowsing; 2351 } 2352 2353 const Maybe<const CipherKey>& MaybeKeyRef() const { 2354 // This can be called on any thread, as it is const. 2355 MOZ_ASSERT(mKey.isSome() == mInPrivateBrowsing); 2356 return mKey; 2357 } 2358 2359 ~Database() override { 2360 MOZ_ASSERT(mClosed); 2361 MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed); 2362 MOZ_DIAGNOSTIC_ASSERT(!isInList()); 2363 2364 NS_ProxyRelease("ReleaseIDBFactory", mBackgroundThread.get(), 2365 mFactory.forget()); 2366 } 2367 2368 private: 2369 [[nodiscard]] SafeRefPtr<DatabaseFileInfo> GetBlob(const IPCBlob& aIPCBlob); 2370 2371 void UnmapBlob(const nsID& aID); 2372 2373 void UnmapAllBlobs(); 2374 2375 bool CloseInternal(); 2376 2377 void MaybeCloseConnection(); 2378 2379 void ConnectionClosedCallback(); 2380 2381 void CleanupMetadata(); 2382 2383 // IPDL methods are only called by IPDL. 2384 void ActorDestroy(ActorDestroyReason aWhy) override; 2385 2386 PBackgroundIDBDatabaseFileParent* AllocPBackgroundIDBDatabaseFileParent( 2387 const IPCBlob& aIPCBlob) override; 2388 2389 bool DeallocPBackgroundIDBDatabaseFileParent( 2390 PBackgroundIDBDatabaseFileParent* aActor) override; 2391 2392 already_AddRefed<PBackgroundIDBTransactionParent> 2393 AllocPBackgroundIDBTransactionParent( 2394 const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode, 2395 const Durability& aDurability) override; 2396 2397 mozilla::ipc::IPCResult RecvPBackgroundIDBTransactionConstructor( 2398 PBackgroundIDBTransactionParent* aActor, 2399 nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode, 2400 const Durability& aDurability) override; 2401 2402 mozilla::ipc::IPCResult RecvDeleteMe() override; 2403 2404 mozilla::ipc::IPCResult RecvBlocked() override; 2405 2406 mozilla::ipc::IPCResult RecvClose() override; 2407 2408 template <typename T> 2409 static bool InvalidateAll(const nsTBaseHashSet<nsPtrHashKey<T>>& aTable); 2410 }; 2411 2412 class Database::StartTransactionOp final 2413 : public TransactionDatabaseOperationBase { 2414 friend class Database; 2415 2416 private: 2417 explicit StartTransactionOp(SafeRefPtr<TransactionBase> aTransaction) 2418 : TransactionDatabaseOperationBase(std::move(aTransaction), 2419 /* aRequestId */ 0, 2420 /* aLoggingSerialNumber */ 0) {} 2421 2422 ~StartTransactionOp() override = default; 2423 2424 void RunOnConnectionThread() override; 2425 2426 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 2427 2428 nsresult SendSuccessResult() override; 2429 2430 bool SendFailureResult(nsresult aResultCode) override; 2431 2432 void Cleanup() override; 2433 }; 2434 2435 class Database::UnmapBlobCallback final 2436 : public RemoteLazyInputStreamParentCallback { 2437 SafeRefPtr<Database> mDatabase; 2438 nsCOMPtr<nsISerialEventTarget> mBackgroundThread; 2439 2440 public: 2441 explicit UnmapBlobCallback(SafeRefPtr<Database> aDatabase) 2442 : mDatabase(std::move(aDatabase)), 2443 mBackgroundThread(GetCurrentSerialEventTarget()) { 2444 AssertIsOnBackgroundThread(); 2445 } 2446 2447 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Database::UnmapBlobCallback, override) 2448 2449 void ActorDestroyed(const nsID& aID) override { 2450 MOZ_ASSERT(mDatabase); 2451 mBackgroundThread->Dispatch(NS_NewRunnableFunction( 2452 "UnmapBlobCallback", [aID, database = std::move(mDatabase)] { 2453 AssertIsOnBackgroundThread(); 2454 database->UnmapBlob(aID); 2455 })); 2456 } 2457 2458 private: 2459 ~UnmapBlobCallback() = default; 2460 }; 2461 2462 /** 2463 * In coordination with IDBDatabase's mFileActors weak-map on the child side, a 2464 * long-lived mapping from a child process's live Blobs to their corresponding 2465 * DatabaseFileInfo in our owning database. Assists in avoiding redundant IPC 2466 * traffic and disk storage. This includes both: 2467 * - Blobs retrieved from this database and sent to the child that do not need 2468 * to be written to disk because they already exist on disk in this database's 2469 * files directory. 2470 * - Blobs retrieved from other databases or from anywhere else that will need 2471 * to be written to this database's files directory. In this case we will 2472 * hold a reference to its BlobImpl in mBlobImpl until we have successfully 2473 * written the Blob to disk. 2474 * 2475 * Relevant Blob context: Blobs sent from the parent process to child processes 2476 * are automatically linked back to their source BlobImpl when the child process 2477 * references the Blob via IPC. This is done using the internal IPCBlob 2478 * inputStream actor ID to DatabaseFileInfo mapping. However, when getting an 2479 * actor in the child process for sending an in-child-created Blob to the 2480 * parent process, there is (currently) no Blob machinery to automatically 2481 * establish and reuse a long-lived Actor. As a result, without IDB's weak-map 2482 * cleverness, a memory-backed Blob repeatedly sent from the child to the parent 2483 * would appear as a different Blob each time, requiring the Blob data to be 2484 * sent over IPC each time as well as potentially needing to be written to disk 2485 * each time. 2486 * 2487 * This object remains alive as long as there is an active child actor or an 2488 * ObjectStoreAddOrPutRequestOp::StoredFileInfo for a queued or active add/put 2489 * op is holding a reference to us. 2490 */ 2491 class DatabaseFile final : public PBackgroundIDBDatabaseFileParent { 2492 // mBlobImpl's ownership lifecycle: 2493 // - Initialized on the background thread at creation time. Then 2494 // responsibility is handed off to the connection thread. 2495 // - Checked and used by the connection thread to generate a stream to write 2496 // the blob to disk by an add/put operation. 2497 // - Cleared on the connection thread once the file has successfully been 2498 // written to disk. 2499 InitializedOnce<const RefPtr<BlobImpl>> mBlobImpl; 2500 const SafeRefPtr<DatabaseFileInfo> mFileInfo; 2501 2502 public: 2503 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::DatabaseFile); 2504 2505 const DatabaseFileInfo& GetFileInfo() const { 2506 AssertIsOnBackgroundThread(); 2507 2508 return *mFileInfo; 2509 } 2510 2511 SafeRefPtr<DatabaseFileInfo> GetFileInfoPtr() const { 2512 AssertIsOnBackgroundThread(); 2513 2514 return mFileInfo.clonePtr(); 2515 } 2516 2517 /** 2518 * If mBlobImpl is non-null (implying the contents of this file have not yet 2519 * been written to disk), then return an input stream. Otherwise, if mBlobImpl 2520 * is null (because the contents have been written to disk), returns null. 2521 */ 2522 [[nodiscard]] nsCOMPtr<nsIInputStream> GetInputStream(ErrorResult& rv) const; 2523 2524 /** 2525 * To be called upon successful copying of the stream GetInputStream() 2526 * returned so that we won't try and redundantly write the file to disk in the 2527 * future. This is a separate step from GetInputStream() because 2528 * the write could fail due to quota errors that happen now but that might 2529 * not happen in a future attempt. 2530 */ 2531 void WriteSucceededClearBlobImpl() { 2532 MOZ_ASSERT(!IsOnBackgroundThread()); 2533 2534 MOZ_ASSERT(*mBlobImpl); 2535 mBlobImpl.destroy(); 2536 } 2537 2538 public: 2539 // Called when sending to the child. 2540 explicit DatabaseFile(SafeRefPtr<DatabaseFileInfo> aFileInfo) 2541 : mBlobImpl{nullptr}, mFileInfo(std::move(aFileInfo)) { 2542 AssertIsOnBackgroundThread(); 2543 MOZ_ASSERT(mFileInfo); 2544 } 2545 2546 // Called when receiving from the child. 2547 DatabaseFile(RefPtr<BlobImpl> aBlobImpl, 2548 SafeRefPtr<DatabaseFileInfo> aFileInfo) 2549 : mBlobImpl(std::move(aBlobImpl)), mFileInfo(std::move(aFileInfo)) { 2550 AssertIsOnBackgroundThread(); 2551 MOZ_ASSERT(*mBlobImpl); 2552 MOZ_ASSERT(mFileInfo); 2553 } 2554 2555 private: 2556 ~DatabaseFile() override = default; 2557 2558 void ActorDestroy(ActorDestroyReason aWhy) override { 2559 AssertIsOnBackgroundThread(); 2560 } 2561 }; 2562 2563 nsCOMPtr<nsIInputStream> DatabaseFile::GetInputStream(ErrorResult& rv) const { 2564 // We should only be called from our DB connection thread, not the background 2565 // thread. 2566 MOZ_ASSERT(!IsOnBackgroundThread()); 2567 2568 // If we were constructed without a BlobImpl, or WriteSucceededClearBlobImpl 2569 // was already called, return nullptr. 2570 if (!mBlobImpl || !*mBlobImpl) { 2571 return nullptr; 2572 } 2573 2574 nsCOMPtr<nsIInputStream> inputStream; 2575 (*mBlobImpl)->CreateInputStream(getter_AddRefs(inputStream), rv); 2576 if (rv.Failed()) { 2577 return nullptr; 2578 } 2579 2580 return inputStream; 2581 } 2582 2583 class TransactionBase : public AtomicSafeRefCounted<TransactionBase> { 2584 friend class CursorBase; 2585 2586 template <IDBCursorType CursorType> 2587 friend class Cursor; 2588 2589 class CommitOp; 2590 2591 protected: 2592 using Mode = IDBTransaction::Mode; 2593 using Durability = IDBTransaction::Durability; 2594 2595 private: 2596 const SafeRefPtr<Database> mDatabase; 2597 nsTArray<SafeRefPtr<FullObjectStoreMetadata>> 2598 mModifiedAutoIncrementObjectStoreMetadataArray; 2599 LazyInitializedOnceNotNull<const uint64_t> mTransactionId; 2600 const nsCString mDatabaseId; 2601 const int64_t mLoggingSerialNumber; 2602 uint64_t mActiveRequestCount; 2603 Atomic<bool> mInvalidatedOnAnyThread; 2604 const Mode mMode; 2605 const Durability mDurability; 2606 FlippedOnce<false> mInitialized; 2607 FlippedOnce<false> mHasBeenActiveOnConnectionThread; 2608 FlippedOnce<false> mActorDestroyed; 2609 FlippedOnce<false> mInvalidated; 2610 2611 protected: 2612 nsresult mResultCode; 2613 FlippedOnce<false> mCommitOrAbortReceived; 2614 FlippedOnce<false> mCommittedOrAborted; 2615 FlippedOnce<false> mForceAborted; 2616 LazyInitializedOnce<const Maybe<int64_t>> mLastRequestBeforeCommit; 2617 Maybe<int64_t> mLastFailedRequest; 2618 2619 public: 2620 void AssertIsOnConnectionThread() const { 2621 MOZ_ASSERT(mDatabase); 2622 mDatabase->AssertIsOnConnectionThread(); 2623 } 2624 2625 bool IsActorDestroyed() const { 2626 AssertIsOnBackgroundThread(); 2627 2628 return mActorDestroyed; 2629 } 2630 2631 // Must be called on the background thread. 2632 bool IsInvalidated() const { 2633 MOZ_ASSERT(IsOnBackgroundThread(), "Use IsInvalidatedOnAnyThread()"); 2634 MOZ_ASSERT_IF(mInvalidated, NS_FAILED(mResultCode)); 2635 2636 return mInvalidated; 2637 } 2638 2639 // May be called on any thread, but is more expensive than IsInvalidated(). 2640 bool IsInvalidatedOnAnyThread() const { return mInvalidatedOnAnyThread; } 2641 2642 void Init(const uint64_t aTransactionId) { 2643 AssertIsOnBackgroundThread(); 2644 MOZ_ASSERT(aTransactionId); 2645 2646 mTransactionId.init(aTransactionId); 2647 mInitialized.Flip(); 2648 } 2649 2650 void SetActiveOnConnectionThread() { 2651 AssertIsOnConnectionThread(); 2652 mHasBeenActiveOnConnectionThread.Flip(); 2653 } 2654 2655 MOZ_DECLARE_REFCOUNTED_TYPENAME(mozilla::dom::indexedDB::TransactionBase) 2656 2657 void Abort(nsresult aResultCode, bool aForce); 2658 2659 uint64_t TransactionId() const { return *mTransactionId; } 2660 2661 const nsACString& DatabaseId() const { return mDatabaseId; } 2662 2663 Mode GetMode() const { return mMode; } 2664 2665 Durability GetDurability() const { return mDurability; } 2666 2667 const Database& GetDatabase() const { 2668 MOZ_ASSERT(mDatabase); 2669 2670 return *mDatabase; 2671 } 2672 2673 Database& GetMutableDatabase() const { 2674 MOZ_ASSERT(mDatabase); 2675 2676 return *mDatabase; 2677 } 2678 2679 SafeRefPtr<Database> GetDatabasePtr() const { 2680 MOZ_ASSERT(mDatabase); 2681 2682 return mDatabase.clonePtr(); 2683 } 2684 2685 DatabaseLoggingInfo* GetLoggingInfo() const { 2686 AssertIsOnBackgroundThread(); 2687 MOZ_ASSERT(mDatabase); 2688 2689 return mDatabase->GetLoggingInfo(); 2690 } 2691 2692 int64_t LoggingSerialNumber() const { return mLoggingSerialNumber; } 2693 2694 bool IsAborted() const { 2695 AssertIsOnBackgroundThread(); 2696 2697 return NS_FAILED(mResultCode); 2698 } 2699 2700 [[nodiscard]] SafeRefPtr<FullObjectStoreMetadata> GetMetadataForObjectStoreId( 2701 IndexOrObjectStoreId aObjectStoreId) const; 2702 2703 [[nodiscard]] SafeRefPtr<FullIndexMetadata> GetMetadataForIndexId( 2704 FullObjectStoreMetadata& aObjectStoreMetadata, 2705 IndexOrObjectStoreId aIndexId) const; 2706 2707 PBackgroundParent* GetBackgroundParent() const { 2708 AssertIsOnBackgroundThread(); 2709 MOZ_ASSERT(!IsActorDestroyed()); 2710 2711 return GetDatabase().GetBackgroundParent(); 2712 } 2713 2714 void NoteModifiedAutoIncrementObjectStore( 2715 const SafeRefPtr<FullObjectStoreMetadata>& aMetadata); 2716 2717 void ForgetModifiedAutoIncrementObjectStore( 2718 FullObjectStoreMetadata& aMetadata); 2719 2720 void NoteActiveRequest(); 2721 2722 void NoteFinishedRequest(int64_t aRequestId, nsresult aResultCode); 2723 2724 void Invalidate(); 2725 2726 virtual ~TransactionBase(); 2727 2728 protected: 2729 TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode, 2730 Durability aDurability); 2731 2732 void NoteActorDestroyed() { 2733 AssertIsOnBackgroundThread(); 2734 2735 mActorDestroyed.Flip(); 2736 } 2737 2738 #ifdef DEBUG 2739 // Only called by VersionChangeTransaction. 2740 void FakeActorDestroyed() { mActorDestroyed.EnsureFlipped(); } 2741 #endif 2742 2743 mozilla::ipc::IPCResult RecvCommit(IProtocol* aActor, 2744 const Maybe<int64_t> aLastRequest); 2745 2746 mozilla::ipc::IPCResult RecvAbort(IProtocol* aActor, nsresult aResultCode); 2747 2748 void MaybeCommitOrAbort() { 2749 AssertIsOnBackgroundThread(); 2750 2751 // If we've already committed or aborted then there's nothing else to do. 2752 if (mCommittedOrAborted) { 2753 return; 2754 } 2755 2756 // If there are active requests then we have to wait for those requests to 2757 // complete (see NoteFinishedRequest). 2758 if (mActiveRequestCount) { 2759 return; 2760 } 2761 2762 // If we haven't yet received a commit or abort message then there could be 2763 // additional requests coming so we should wait unless we're being forced to 2764 // abort. 2765 if (!mCommitOrAbortReceived && !mForceAborted) { 2766 return; 2767 } 2768 2769 CommitOrAbort(); 2770 } 2771 2772 PBackgroundIDBRequestParent* AllocRequest(const int64_t aRequestId, 2773 RequestParams&& aParams, 2774 bool aTrustParams); 2775 2776 bool StartRequest(PBackgroundIDBRequestParent* aActor); 2777 2778 bool DeallocRequest(PBackgroundIDBRequestParent* aActor); 2779 2780 already_AddRefed<PBackgroundIDBCursorParent> AllocCursor( 2781 const OpenCursorParams& aParams, bool aTrustParams); 2782 2783 bool StartCursor(PBackgroundIDBCursorParent* aActor, const int64_t aRequestId, 2784 const OpenCursorParams& aParams); 2785 2786 virtual void UpdateMetadata(nsresult aResult) {} 2787 2788 virtual void SendCompleteNotification(nsresult aResult) = 0; 2789 2790 private: 2791 bool VerifyRequestParams(const RequestParams& aParams) const; 2792 2793 bool VerifyRequestParams(const SerializedKeyRange& aParams) const; 2794 2795 bool VerifyRequestParams(const ObjectStoreAddPutParams& aParams) const; 2796 2797 bool VerifyRequestParams(const Maybe<SerializedKeyRange>& aParams) const; 2798 2799 void CommitOrAbort(); 2800 }; 2801 2802 class TransactionBase::CommitOp final : public DatabaseOperationBase, 2803 public ConnectionPool::FinishCallback { 2804 friend class TransactionBase; 2805 2806 SafeRefPtr<TransactionBase> mTransaction; 2807 nsresult mResultCode; ///< TODO: There is also a mResultCode in 2808 ///< DatabaseOperationBase. Is there a reason not to 2809 ///< use that? At least a more specific name should be 2810 ///< given to this one. 2811 2812 private: 2813 CommitOp(SafeRefPtr<TransactionBase> aTransaction, nsresult aResultCode); 2814 2815 ~CommitOp() override = default; 2816 2817 // Writes new autoIncrement counts to database. 2818 nsresult WriteAutoIncrementCounts(); 2819 2820 // Updates counts after a database activity has finished. 2821 void CommitOrRollbackAutoIncrementCounts(); 2822 2823 void AssertForeignKeyConsistency(DatabaseConnection* aConnection) 2824 #ifdef DEBUG 2825 ; 2826 #else 2827 { 2828 } 2829 #endif 2830 2831 NS_DECL_NSIRUNNABLE 2832 2833 void TransactionFinishedBeforeUnblock() override; 2834 2835 void TransactionFinishedAfterUnblock() override; 2836 2837 public: 2838 // We need to declare all of nsISupports, because FinishCallback has 2839 // a pure-virtual nsISupports declaration. 2840 NS_DECL_ISUPPORTS_INHERITED 2841 }; 2842 2843 class NormalTransaction final : public TransactionBase, 2844 public PBackgroundIDBTransactionParent { 2845 nsTArray<SafeRefPtr<FullObjectStoreMetadata>> mObjectStores; 2846 2847 // Reference counted. 2848 ~NormalTransaction() override = default; 2849 2850 bool IsSameProcessActor(); 2851 2852 // Only called by TransactionBase. 2853 void SendCompleteNotification(nsresult aResult) override; 2854 2855 // IPDL methods are only called by IPDL. 2856 void ActorDestroy(ActorDestroyReason aWhy) override; 2857 2858 mozilla::ipc::IPCResult RecvDeleteMe() override; 2859 2860 mozilla::ipc::IPCResult RecvCommit( 2861 const Maybe<int64_t>& aLastRequest) override; 2862 2863 mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override; 2864 2865 PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent( 2866 const int64_t& aRequestId, const RequestParams& aParams) override; 2867 2868 mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor( 2869 PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId, 2870 const RequestParams& aParams) override; 2871 2872 bool DeallocPBackgroundIDBRequestParent( 2873 PBackgroundIDBRequestParent* aActor) override; 2874 2875 already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent( 2876 const int64_t& aRequestId, const OpenCursorParams& aParams) override; 2877 2878 mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor( 2879 PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId, 2880 const OpenCursorParams& aParams) override; 2881 2882 public: 2883 // This constructor is only called by Database. 2884 NormalTransaction( 2885 SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode, 2886 TransactionBase::Durability aDurability, 2887 nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores); 2888 2889 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(NormalTransaction, TransactionBase) 2890 }; 2891 2892 class VersionChangeTransaction final 2893 : public TransactionBase, 2894 public PBackgroundIDBVersionChangeTransactionParent { 2895 friend class OpenDatabaseOp; 2896 2897 RefPtr<OpenDatabaseOp> mOpenDatabaseOp; 2898 SafeRefPtr<FullDatabaseMetadata> mOldMetadata; 2899 2900 FlippedOnce<false> mActorWasAlive; 2901 2902 public: 2903 // Only called by OpenDatabaseOp. 2904 explicit VersionChangeTransaction(OpenDatabaseOp* aOpenDatabaseOp); 2905 2906 MOZ_INLINE_DECL_SAFEREFCOUNTING_INHERITED(VersionChangeTransaction, 2907 TransactionBase) 2908 2909 private: 2910 // Reference counted. 2911 ~VersionChangeTransaction() override; 2912 2913 bool IsSameProcessActor(); 2914 2915 // Only called by OpenDatabaseOp. 2916 bool CopyDatabaseMetadata(); 2917 2918 void SetActorAlive(); 2919 2920 // Only called by TransactionBase. 2921 void UpdateMetadata(nsresult aResult) override; 2922 2923 // Only called by TransactionBase. 2924 void SendCompleteNotification(nsresult aResult) override; 2925 2926 // IPDL methods are only called by IPDL. 2927 void ActorDestroy(ActorDestroyReason aWhy) override; 2928 2929 mozilla::ipc::IPCResult RecvDeleteMe() override; 2930 2931 mozilla::ipc::IPCResult RecvCommit( 2932 const Maybe<int64_t>& aLastRequest) override; 2933 2934 mozilla::ipc::IPCResult RecvAbort(const nsresult& aResultCode) override; 2935 2936 mozilla::ipc::IPCResult RecvCreateObjectStore( 2937 const ObjectStoreMetadata& aMetadata) override; 2938 2939 mozilla::ipc::IPCResult RecvDeleteObjectStore( 2940 const IndexOrObjectStoreId& aObjectStoreId) override; 2941 2942 mozilla::ipc::IPCResult RecvRenameObjectStore( 2943 const IndexOrObjectStoreId& aObjectStoreId, 2944 const nsAString& aName) override; 2945 2946 mozilla::ipc::IPCResult RecvCreateIndex( 2947 const IndexOrObjectStoreId& aObjectStoreId, 2948 const IndexMetadata& aMetadata) override; 2949 2950 mozilla::ipc::IPCResult RecvDeleteIndex( 2951 const IndexOrObjectStoreId& aObjectStoreId, 2952 const IndexOrObjectStoreId& aIndexId) override; 2953 2954 mozilla::ipc::IPCResult RecvRenameIndex( 2955 const IndexOrObjectStoreId& aObjectStoreId, 2956 const IndexOrObjectStoreId& aIndexId, const nsAString& aName) override; 2957 2958 PBackgroundIDBRequestParent* AllocPBackgroundIDBRequestParent( 2959 const int64_t& aRequestId, const RequestParams& aParams) override; 2960 2961 mozilla::ipc::IPCResult RecvPBackgroundIDBRequestConstructor( 2962 PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId, 2963 const RequestParams& aParams) override; 2964 2965 bool DeallocPBackgroundIDBRequestParent( 2966 PBackgroundIDBRequestParent* aActor) override; 2967 2968 already_AddRefed<PBackgroundIDBCursorParent> AllocPBackgroundIDBCursorParent( 2969 const int64_t& aRequestId, const OpenCursorParams& aParams) override; 2970 2971 mozilla::ipc::IPCResult RecvPBackgroundIDBCursorConstructor( 2972 PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId, 2973 const OpenCursorParams& aParams) override; 2974 }; 2975 2976 class FactoryOp : public DatabaseOperationBase, 2977 public LinkedListElement<FactoryOp> { 2978 public: 2979 struct MaybeBlockedDatabaseInfo final { 2980 SafeRefPtr<Database> mDatabase; 2981 bool mBlocked; 2982 2983 MaybeBlockedDatabaseInfo(MaybeBlockedDatabaseInfo&&) = default; 2984 MaybeBlockedDatabaseInfo& operator=(MaybeBlockedDatabaseInfo&&) = default; 2985 2986 MOZ_IMPLICIT MaybeBlockedDatabaseInfo(SafeRefPtr<Database> aDatabase) 2987 : mDatabase(std::move(aDatabase)), mBlocked(false) { 2988 MOZ_ASSERT(mDatabase); 2989 2990 MOZ_COUNT_CTOR(FactoryOp::MaybeBlockedDatabaseInfo); 2991 } 2992 2993 ~MaybeBlockedDatabaseInfo() { 2994 MOZ_COUNT_DTOR(FactoryOp::MaybeBlockedDatabaseInfo); 2995 } 2996 2997 bool operator==(const Database* aOther) const { 2998 return mDatabase == aOther; 2999 } 3000 3001 Database* operator->() const& MOZ_NO_ADDREF_RELEASE_ON_RETURN { 3002 return mDatabase.unsafeGetRawPtr(); 3003 } 3004 }; 3005 3006 protected: 3007 enum class State { 3008 // Just created on the PBackground thread, dispatched to the current thread. 3009 // Next step is either SendingResults if opening initialization failed, or 3010 // DirectoryOpenPending if the opening initialization succeeded. 3011 Initial, 3012 3013 // Waiting for directory open allowed on the PBackground thread. The next 3014 // step is either SendingResults if directory lock failed to acquire, or 3015 // DirectoryWorkOpen if the factory operation is not tied up to a specific 3016 // database, or DatabaseOpenPending otherwise. 3017 DirectoryOpenPending, 3018 3019 // Waiting to do/doing directory work on the QuotaManager IO thread. Its 3020 // next step is DirectoryWorkDone if directory work was successful or 3021 // SendingResults if directory work failed. 3022 DirectoryWorkOpen, 3023 3024 // Checking if database work can be started. If the database is not blocked 3025 // by other factory operations then the next step is DatabaseWorkOpen. 3026 // Otherwise the next step is DatabaseOpenPending. 3027 DirectoryWorkDone, 3028 3029 // Waiting for database open allowed on the PBackground thread. The next 3030 // step is DatabaseWorkOpen. 3031 DatabaseOpenPending, 3032 3033 // Waiting to do/doing work on the QuotaManager IO thread. Its next step is 3034 // either BeginVersionChange if the requested version doesn't match the 3035 // existing database version or SendingResults if the versions match. 3036 DatabaseWorkOpen, 3037 3038 // Starting a version change transaction or deleting a database on the 3039 // PBackground thread. We need to notify other databases that a version 3040 // change is about to happen, and maybe tell the request that a version 3041 // change has been blocked. If databases are notified then the next step is 3042 // WaitingForOtherDatabasesToClose. Otherwise the next step is 3043 // WaitingForTransactionsToComplete. 3044 BeginVersionChange, 3045 3046 // Waiting for other databases to close on the PBackground thread. This 3047 // state may persist until all databases are closed. The next state is 3048 // WaitingForTransactionsToComplete. 3049 WaitingForOtherDatabasesToClose, 3050 3051 // Waiting for all transactions that could interfere with this operation to 3052 // complete on the PBackground thread. Next state is 3053 // DatabaseWorkVersionChange. 3054 WaitingForTransactionsToComplete, 3055 3056 // Waiting to do/doing work on the "work thread". This involves waiting for 3057 // the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a 3058 // different implementation) to do its work. If the VersionChangeOp is 3059 // OpenDatabaseOp and it succeeded then the next state is 3060 // DatabaseWorkVersionUpdate. Otherwise the next step is SendingResults. 3061 DatabaseWorkVersionChange, 3062 3063 // Waiting to do/doing finalization work on the QuotaManager IO thread. 3064 // Eventually the state will transition to SendingResults. 3065 DatabaseWorkVersionUpdate, 3066 3067 // Waiting to send/sending results on the PBackground thread. Next step is 3068 // Completed. 3069 SendingResults, 3070 3071 // All done. 3072 Completed 3073 }; 3074 3075 // Must be released on the background thread! 3076 SafeRefPtr<Factory> mFactory; 3077 3078 Maybe<ContentParentId> mContentParentId; 3079 3080 // Must be released on the main thread! 3081 ClientDirectoryLockHandle mDirectoryLockHandle; 3082 3083 nsTArray<NotNull<RefPtr<FactoryOp>>> mBlocking; 3084 nsTHashSet<RefPtr<FactoryOp>> mBlockedOn; 3085 3086 nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases; 3087 3088 const PrincipalInfo mPrincipalInfo; 3089 OriginMetadata mOriginMetadata; 3090 Maybe<nsString> mDatabaseName; 3091 Maybe<nsCString> mDatabaseId; 3092 Maybe<nsString> mDatabaseFilePath; 3093 int64_t mDirectoryLockId; 3094 const PersistenceType mPersistenceType; 3095 State mState; 3096 bool mWaitingForPermissionRetry; 3097 bool mEnforcingQuota; 3098 const bool mDeleting; 3099 FlippedOnce<false> mInPrivateBrowsing; 3100 3101 public: 3102 const nsACString& Origin() const { 3103 AssertIsOnOwningThread(); 3104 3105 return mOriginMetadata.mOrigin; 3106 } 3107 3108 const Maybe<nsString>& DatabaseNameRef() const { 3109 AssertIsOnOwningThread(); 3110 3111 return mDatabaseName; 3112 } 3113 3114 bool DatabaseFilePathIsKnown() const { 3115 AssertIsOnOwningThread(); 3116 3117 return mDatabaseFilePath.isSome(); 3118 } 3119 3120 const nsAString& DatabaseFilePath() const { 3121 AssertIsOnOwningThread(); 3122 MOZ_ASSERT(mDatabaseFilePath); 3123 3124 return mDatabaseFilePath.ref(); 3125 } 3126 3127 nsresult DispatchThisAfterProcessingCurrentEvent( 3128 nsCOMPtr<nsIEventTarget> aEventTarget); 3129 3130 void NoteDatabaseBlocked(Database* aDatabase); 3131 3132 void NoteDatabaseClosed(Database* aDatabase); 3133 3134 #ifdef DEBUG 3135 bool HasBlockedDatabases() const { return !mMaybeBlockedDatabases.IsEmpty(); } 3136 #endif 3137 3138 void StringifyState(nsACString& aResult) const; 3139 3140 void Stringify(nsACString& aResult) const; 3141 3142 protected: 3143 FactoryOp(SafeRefPtr<Factory> aFactory, 3144 const Maybe<ContentParentId>& aContentParentId, 3145 const PersistenceType aPersistenceType, 3146 const PrincipalInfo& aPrincipalInfo, 3147 const Maybe<nsString>& aDatabaseName, bool aDeleting); 3148 3149 ~FactoryOp() override { 3150 // Normally this would be out-of-line since it is a virtual function but 3151 // MSVC 2010 fails to link for some reason if it is not inlined here... 3152 MOZ_ASSERT_IF(OperationMayProceed(), 3153 mState == State::Initial || mState == State::Completed); 3154 MOZ_DIAGNOSTIC_ASSERT(!isInList()); 3155 } 3156 3157 nsresult Open(); 3158 3159 nsresult DirectoryOpen(); 3160 3161 nsresult DirectoryWorkDone(); 3162 3163 nsresult SendToIOThread(); 3164 3165 void WaitForTransactions(); 3166 3167 void CleanupMetadata(); 3168 3169 void FinishSendResults(); 3170 3171 nsresult SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo, 3172 Maybe<Database&> aOpeningDatabase, 3173 uint64_t aOldVersion, 3174 const Maybe<uint64_t>& aNewVersion); 3175 3176 // Methods that subclasses must implement. 3177 virtual nsresult DoDirectoryWork() = 0; 3178 3179 virtual nsresult DatabaseOpen() = 0; 3180 3181 virtual nsresult DoDatabaseWork() = 0; 3182 3183 virtual nsresult BeginVersionChange() = 0; 3184 3185 virtual bool AreActorsAlive() = 0; 3186 3187 virtual nsresult DispatchToWorkThread() = 0; 3188 3189 virtual nsresult DoVersionUpdate() = 0; 3190 3191 // Should only be called by Run(). 3192 virtual void SendResults() = 0; 3193 3194 // Common nsIRunnable implementation that subclasses may not override. 3195 NS_IMETHOD 3196 Run() final; 3197 3198 void DirectoryLockAcquired(ClientDirectoryLockHandle aLockHandle); 3199 3200 void DirectoryLockFailed(); 3201 3202 virtual void SendBlockedNotification() = 0; 3203 3204 private: 3205 // Test whether this FactoryOp needs to wait for the given op. 3206 bool MustWaitFor(const FactoryOp& aExistingOp); 3207 3208 void AddBlockingOp(FactoryOp& aOp) { 3209 AssertIsOnOwningThread(); 3210 3211 mBlocking.AppendElement(WrapNotNull(&aOp)); 3212 } 3213 3214 void AddBlockedOnOp(FactoryOp& aOp) { 3215 AssertIsOnOwningThread(); 3216 3217 mBlockedOn.Insert(&aOp); 3218 } 3219 3220 void MaybeUnblock(FactoryOp& aOp) { 3221 AssertIsOnOwningThread(); 3222 3223 mBlockedOn.Remove(&aOp); 3224 if (mBlockedOn.IsEmpty()) { 3225 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this)); 3226 } 3227 } 3228 }; 3229 3230 class FactoryRequestOp : public FactoryOp, 3231 public PBackgroundIDBFactoryRequestParent { 3232 protected: 3233 const CommonFactoryRequestParams mCommonParams; 3234 3235 FactoryRequestOp(SafeRefPtr<Factory> aFactory, 3236 const Maybe<ContentParentId>& aContentParentId, 3237 const CommonFactoryRequestParams& aCommonParams, 3238 bool aDeleting) 3239 : FactoryOp(std::move(aFactory), aContentParentId, 3240 aCommonParams.metadata().persistenceType(), 3241 aCommonParams.principalInfo(), 3242 Some(aCommonParams.metadata().name()), aDeleting), 3243 mCommonParams(aCommonParams) {} 3244 3245 nsresult DoDirectoryWork() override; 3246 3247 // IPDL methods. 3248 void ActorDestroy(ActorDestroyReason aWhy) override; 3249 }; 3250 3251 class OpenDatabaseOp final : public FactoryRequestOp { 3252 friend class Database; 3253 friend class VersionChangeTransaction; 3254 3255 class VersionChangeOp; 3256 3257 SafeRefPtr<FullDatabaseMetadata> mMetadata; 3258 3259 uint64_t mRequestedVersion; 3260 SafeRefPtr<DatabaseFileManager> mFileManager; 3261 3262 SafeRefPtr<Database> mDatabase; 3263 SafeRefPtr<VersionChangeTransaction> mVersionChangeTransaction; 3264 3265 // This is only set while a VersionChangeOp is live. It holds a strong 3266 // reference to its OpenDatabaseOp object so this is a weak pointer to avoid 3267 // cycles. 3268 VersionChangeOp* mVersionChangeOp; 3269 3270 MoveOnlyFunction<void()> mCompleteCallback; 3271 3272 uint32_t mTelemetryId; 3273 3274 public: 3275 OpenDatabaseOp(SafeRefPtr<Factory> aFactory, 3276 const Maybe<ContentParentId>& aContentParentId, 3277 const CommonFactoryRequestParams& aParams); 3278 3279 private: 3280 ~OpenDatabaseOp() override { MOZ_ASSERT(!mVersionChangeOp); } 3281 3282 nsresult LoadDatabaseInformation(mozIStorageConnection& aConnection); 3283 3284 nsresult SendUpgradeNeeded(); 3285 3286 void EnsureDatabaseActor(); 3287 3288 nsresult EnsureDatabaseActorIsAlive(); 3289 3290 mozilla::Result<DatabaseSpec, nsresult> MetadataToSpec() const; 3291 3292 void AssertMetadataConsistency(const FullDatabaseMetadata& aMetadata) 3293 #ifdef DEBUG 3294 ; 3295 #else 3296 { 3297 } 3298 #endif 3299 3300 void ConnectionClosedCallback(); 3301 3302 void ActorDestroy(ActorDestroyReason aWhy) override; 3303 3304 nsresult DatabaseOpen() override; 3305 3306 nsresult DoDatabaseWork() override; 3307 3308 nsresult BeginVersionChange() override; 3309 3310 bool AreActorsAlive() override; 3311 3312 void SendBlockedNotification() override; 3313 3314 nsresult DispatchToWorkThread() override; 3315 3316 nsresult DoVersionUpdate() override; 3317 3318 void SendResults() override; 3319 3320 static nsresult UpdateLocaleAwareIndex(mozIStorageConnection& aConnection, 3321 const IndexMetadata& aIndexMetadata, 3322 const nsCString& aLocale); 3323 }; 3324 3325 class OpenDatabaseOp::VersionChangeOp final 3326 : public TransactionDatabaseOperationBase { 3327 friend class OpenDatabaseOp; 3328 3329 RefPtr<OpenDatabaseOp> mOpenDatabaseOp; 3330 const uint64_t mRequestedVersion; 3331 uint64_t mPreviousVersion; 3332 3333 private: 3334 explicit VersionChangeOp(OpenDatabaseOp* aOpenDatabaseOp) 3335 : TransactionDatabaseOperationBase( 3336 aOpenDatabaseOp->mVersionChangeTransaction.clonePtr(), 3337 /* aRequestId */ 0, aOpenDatabaseOp->LoggingSerialNumber()), 3338 mOpenDatabaseOp(aOpenDatabaseOp), 3339 mRequestedVersion(aOpenDatabaseOp->mRequestedVersion), 3340 mPreviousVersion( 3341 aOpenDatabaseOp->mMetadata->mCommonMetadata.version()) { 3342 MOZ_ASSERT(aOpenDatabaseOp); 3343 MOZ_ASSERT(mRequestedVersion); 3344 } 3345 3346 ~VersionChangeOp() override { MOZ_ASSERT(!mOpenDatabaseOp); } 3347 3348 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 3349 3350 nsresult SendSuccessResult() override; 3351 3352 bool SendFailureResult(nsresult aResultCode) override; 3353 3354 void Cleanup() override; 3355 }; 3356 3357 class DeleteDatabaseOp final : public FactoryRequestOp { 3358 class VersionChangeOp; 3359 3360 nsString mDatabaseDirectoryPath; 3361 nsString mDatabaseFilenameBase; 3362 uint64_t mPreviousVersion; 3363 3364 public: 3365 DeleteDatabaseOp(SafeRefPtr<Factory> aFactory, 3366 const Maybe<ContentParentId>& aContentParentId, 3367 const CommonFactoryRequestParams& aParams) 3368 : FactoryRequestOp(std::move(aFactory), aContentParentId, aParams, 3369 /* aDeleting */ true), 3370 mPreviousVersion(0) {} 3371 3372 private: 3373 ~DeleteDatabaseOp() override = default; 3374 3375 void LoadPreviousVersion(nsIFile& aDatabaseFile); 3376 3377 nsresult DatabaseOpen() override; 3378 3379 nsresult DoDatabaseWork() override; 3380 3381 nsresult BeginVersionChange() override; 3382 3383 bool AreActorsAlive() override; 3384 3385 void SendBlockedNotification() override; 3386 3387 nsresult DispatchToWorkThread() override; 3388 3389 nsresult DoVersionUpdate() override; 3390 3391 void SendResults() override; 3392 }; 3393 3394 class DeleteDatabaseOp::VersionChangeOp final : public DatabaseOperationBase { 3395 friend class DeleteDatabaseOp; 3396 3397 RefPtr<DeleteDatabaseOp> mDeleteDatabaseOp; 3398 3399 private: 3400 explicit VersionChangeOp(DeleteDatabaseOp* aDeleteDatabaseOp) 3401 : DatabaseOperationBase(aDeleteDatabaseOp->BackgroundChildLoggingId(), 3402 aDeleteDatabaseOp->LoggingSerialNumber()), 3403 mDeleteDatabaseOp(aDeleteDatabaseOp) { 3404 MOZ_ASSERT(aDeleteDatabaseOp); 3405 MOZ_ASSERT(!aDeleteDatabaseOp->mDatabaseDirectoryPath.IsEmpty()); 3406 } 3407 3408 ~VersionChangeOp() override = default; 3409 3410 nsresult RunOnIOThread(); 3411 3412 void RunOnOwningThread(); 3413 3414 NS_DECL_NSIRUNNABLE 3415 }; 3416 3417 class GetDatabasesOp final : public FactoryOp { 3418 nsTHashMap<nsStringHashKey, DatabaseMetadata> mDatabaseMetadataTable; 3419 nsTArray<DatabaseMetadata> mDatabaseMetadataArray; 3420 Factory::GetDatabasesResolver mResolver; 3421 3422 public: 3423 GetDatabasesOp(SafeRefPtr<Factory> aFactory, 3424 const Maybe<ContentParentId>& aContentParentId, 3425 const PersistenceType aPersistenceType, 3426 const PrincipalInfo& aPrincipalInfo, 3427 Factory::GetDatabasesResolver&& aResolver) 3428 : FactoryOp(std::move(aFactory), aContentParentId, aPersistenceType, 3429 aPrincipalInfo, Nothing(), /* aDeleting */ false), 3430 mResolver(std::move(aResolver)) {} 3431 3432 private: 3433 ~GetDatabasesOp() override = default; 3434 3435 nsresult DatabasesNotAvailable(); 3436 3437 nsresult DoDirectoryWork() override; 3438 3439 nsresult DatabaseOpen() override; 3440 3441 nsresult DoDatabaseWork() override; 3442 3443 nsresult BeginVersionChange() override; 3444 3445 bool AreActorsAlive() override; 3446 3447 void SendBlockedNotification() override; 3448 3449 nsresult DispatchToWorkThread() override; 3450 3451 nsresult DoVersionUpdate() override; 3452 3453 void SendResults() override; 3454 }; 3455 3456 class VersionChangeTransactionOp : public TransactionDatabaseOperationBase { 3457 public: 3458 void Cleanup() override; 3459 3460 protected: 3461 explicit VersionChangeTransactionOp( 3462 SafeRefPtr<VersionChangeTransaction> aTransaction) 3463 : TransactionDatabaseOperationBase(std::move(aTransaction), 3464 /* aRequestId */ 0) {} 3465 3466 ~VersionChangeTransactionOp() override = default; 3467 3468 private: 3469 nsresult SendSuccessResult() override; 3470 3471 bool SendFailureResult(nsresult aResultCode) override; 3472 }; 3473 3474 class CreateObjectStoreOp final : public VersionChangeTransactionOp { 3475 friend class VersionChangeTransaction; 3476 3477 const ObjectStoreMetadata mMetadata; 3478 3479 private: 3480 // Only created by VersionChangeTransaction. 3481 CreateObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction, 3482 const ObjectStoreMetadata& aMetadata) 3483 : VersionChangeTransactionOp(std::move(aTransaction)), 3484 mMetadata(aMetadata) { 3485 MOZ_ASSERT(aMetadata.id()); 3486 } 3487 3488 ~CreateObjectStoreOp() override = default; 3489 3490 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 3491 }; 3492 3493 class DeleteObjectStoreOp final : public VersionChangeTransactionOp { 3494 friend class VersionChangeTransaction; 3495 3496 const SafeRefPtr<FullObjectStoreMetadata> mMetadata; 3497 const bool mIsLastObjectStore; 3498 3499 private: 3500 // Only created by VersionChangeTransaction. 3501 DeleteObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction, 3502 SafeRefPtr<FullObjectStoreMetadata> aMetadata, 3503 const bool aIsLastObjectStore) 3504 : VersionChangeTransactionOp(std::move(aTransaction)), 3505 mMetadata(std::move(aMetadata)), 3506 mIsLastObjectStore(aIsLastObjectStore) { 3507 MOZ_ASSERT(mMetadata->mCommonMetadata.id()); 3508 } 3509 3510 ~DeleteObjectStoreOp() override = default; 3511 3512 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 3513 }; 3514 3515 class RenameObjectStoreOp final : public VersionChangeTransactionOp { 3516 friend class VersionChangeTransaction; 3517 3518 const int64_t mId; 3519 const nsString mNewName; 3520 3521 private: 3522 // Only created by VersionChangeTransaction. 3523 RenameObjectStoreOp(SafeRefPtr<VersionChangeTransaction> aTransaction, 3524 FullObjectStoreMetadata& aMetadata) 3525 : VersionChangeTransactionOp(std::move(aTransaction)), 3526 mId(aMetadata.mCommonMetadata.id()), 3527 mNewName(aMetadata.mCommonMetadata.name()) { 3528 MOZ_ASSERT(mId); 3529 } 3530 3531 ~RenameObjectStoreOp() override = default; 3532 3533 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 3534 }; 3535 3536 class CreateIndexOp final : public VersionChangeTransactionOp { 3537 friend class VersionChangeTransaction; 3538 3539 class UpdateIndexDataValuesFunction; 3540 3541 const IndexMetadata mMetadata; 3542 Maybe<UniqueIndexTable> mMaybeUniqueIndexTable; 3543 const SafeRefPtr<DatabaseFileManager> mFileManager; 3544 const nsCString mDatabaseId; 3545 const IndexOrObjectStoreId mObjectStoreId; 3546 3547 private: 3548 // Only created by VersionChangeTransaction. 3549 CreateIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction, 3550 IndexOrObjectStoreId aObjectStoreId, 3551 const IndexMetadata& aMetadata); 3552 3553 ~CreateIndexOp() override = default; 3554 3555 nsresult InsertDataFromObjectStore(DatabaseConnection* aConnection); 3556 3557 nsresult InsertDataFromObjectStoreInternal( 3558 DatabaseConnection* aConnection) const; 3559 3560 bool Init(TransactionBase& aTransaction) override; 3561 3562 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 3563 }; 3564 3565 class CreateIndexOp::UpdateIndexDataValuesFunction final 3566 : public mozIStorageFunction { 3567 RefPtr<CreateIndexOp> mOp; 3568 RefPtr<DatabaseConnection> mConnection; 3569 const NotNull<SafeRefPtr<Database>> mDatabase; 3570 3571 public: 3572 UpdateIndexDataValuesFunction(CreateIndexOp* aOp, 3573 DatabaseConnection* aConnection, 3574 SafeRefPtr<Database> aDatabase) 3575 : mOp(aOp), 3576 mConnection(aConnection), 3577 mDatabase(WrapNotNull(std::move(aDatabase))) { 3578 MOZ_ASSERT(aOp); 3579 MOZ_ASSERT(aConnection); 3580 aConnection->AssertIsOnConnectionThread(); 3581 } 3582 3583 NS_DECL_ISUPPORTS 3584 3585 private: 3586 ~UpdateIndexDataValuesFunction() = default; 3587 3588 NS_DECL_MOZISTORAGEFUNCTION 3589 }; 3590 3591 class DeleteIndexOp final : public VersionChangeTransactionOp { 3592 friend class VersionChangeTransaction; 3593 3594 const IndexOrObjectStoreId mObjectStoreId; 3595 const IndexOrObjectStoreId mIndexId; 3596 const bool mUnique; 3597 const bool mIsLastIndex; 3598 3599 private: 3600 // Only created by VersionChangeTransaction. 3601 DeleteIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction, 3602 IndexOrObjectStoreId aObjectStoreId, 3603 IndexOrObjectStoreId aIndexId, const bool aUnique, 3604 const bool aIsLastIndex); 3605 3606 ~DeleteIndexOp() override = default; 3607 3608 nsresult RemoveReferencesToIndex( 3609 DatabaseConnection* aConnection, const Key& aObjectDataKey, 3610 nsTArray<IndexDataValue>& aIndexValues) const; 3611 3612 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 3613 }; 3614 3615 class RenameIndexOp final : public VersionChangeTransactionOp { 3616 friend class VersionChangeTransaction; 3617 3618 const IndexOrObjectStoreId mObjectStoreId; 3619 const IndexOrObjectStoreId mIndexId; 3620 const nsString mNewName; 3621 3622 private: 3623 // Only created by VersionChangeTransaction. 3624 RenameIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction, 3625 FullIndexMetadata& aMetadata, 3626 IndexOrObjectStoreId aObjectStoreId) 3627 : VersionChangeTransactionOp(std::move(aTransaction)), 3628 mObjectStoreId(aObjectStoreId), 3629 mIndexId(aMetadata.mCommonMetadata.id()), 3630 mNewName(aMetadata.mCommonMetadata.name()) { 3631 MOZ_ASSERT(mIndexId); 3632 } 3633 3634 ~RenameIndexOp() override = default; 3635 3636 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 3637 }; 3638 3639 class NormalTransactionOp : public TransactionDatabaseOperationBase, 3640 public PBackgroundIDBRequestParent { 3641 #ifdef DEBUG 3642 bool mResponseSent; 3643 #endif 3644 3645 public: 3646 void Cleanup() override; 3647 3648 protected: 3649 NormalTransactionOp(SafeRefPtr<TransactionBase> aTransaction, 3650 const int64_t aRequestId) 3651 : TransactionDatabaseOperationBase(std::move(aTransaction), aRequestId) 3652 #ifdef DEBUG 3653 , 3654 mResponseSent(false) 3655 #endif 3656 { 3657 } 3658 3659 ~NormalTransactionOp() override = default; 3660 3661 // An overload of DatabaseOperationBase's function that can avoid doing extra 3662 // work on non-versionchange transactions. 3663 mozilla::Result<bool, nsresult> ObjectStoreHasIndexes( 3664 DatabaseConnection& aConnection, IndexOrObjectStoreId aObjectStoreId, 3665 bool aMayHaveIndexes); 3666 3667 virtual mozilla::Result<PreprocessParams, nsresult> GetPreprocessParams(); 3668 3669 // Subclasses use this override to set the IPDL response value. 3670 virtual void GetResponse(RequestResponse& aResponse, 3671 size_t* aResponseSize) = 0; 3672 3673 private: 3674 nsresult SendPreprocessInfo() override; 3675 3676 nsresult SendSuccessResult() override; 3677 3678 bool SendFailureResult(nsresult aResultCode) override; 3679 3680 // IPDL methods. 3681 void ActorDestroy(ActorDestroyReason aWhy) override; 3682 3683 mozilla::ipc::IPCResult RecvContinue( 3684 const PreprocessResponse& aResponse) final; 3685 }; 3686 3687 class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp { 3688 friend class TransactionBase; 3689 3690 using PersistenceType = mozilla::dom::quota::PersistenceType; 3691 3692 class StoredFileInfo final { 3693 InitializedOnce<const NotNull<SafeRefPtr<DatabaseFileInfo>>> mFileInfo; 3694 // Either nothing, a file actor or a non-Blob-backed inputstream to write to 3695 // disk. 3696 using FileActorOrInputStream = 3697 Variant<Nothing, RefPtr<DatabaseFile>, nsCOMPtr<nsIInputStream>>; 3698 InitializedOnce<const FileActorOrInputStream> mFileActorOrInputStream; 3699 #ifdef DEBUG 3700 const StructuredCloneFileBase::FileType mType; 3701 #endif 3702 void EnsureCipherKey(); 3703 void AssertInvariants() const; 3704 3705 StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo, 3706 RefPtr<DatabaseFile> aFileActor); 3707 3708 StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo, 3709 nsCOMPtr<nsIInputStream> aInputStream); 3710 3711 public: 3712 #if defined(NS_BUILD_REFCNT_LOGGING) 3713 // Only for MOZ_COUNT_CTOR. 3714 StoredFileInfo(StoredFileInfo&& aOther) 3715 : mFileInfo{std::move(aOther.mFileInfo)}, 3716 mFileActorOrInputStream{std::move(aOther.mFileActorOrInputStream)} 3717 # ifdef DEBUG 3718 , 3719 mType{aOther.mType} 3720 # endif 3721 { 3722 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo); 3723 } 3724 #else 3725 StoredFileInfo(StoredFileInfo&&) = default; 3726 #endif 3727 3728 static StoredFileInfo CreateForBlob(SafeRefPtr<DatabaseFileInfo> aFileInfo, 3729 RefPtr<DatabaseFile> aFileActor); 3730 static StoredFileInfo CreateForStructuredClone( 3731 SafeRefPtr<DatabaseFileInfo> aFileInfo, 3732 nsCOMPtr<nsIInputStream> aInputStream); 3733 3734 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) 3735 ~StoredFileInfo() { 3736 AssertIsOnBackgroundThread(); 3737 AssertInvariants(); 3738 3739 MOZ_COUNT_DTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo); 3740 } 3741 #endif 3742 3743 bool IsValid() const { return static_cast<bool>(mFileInfo); } 3744 3745 const DatabaseFileInfo& GetFileInfo() const { return **mFileInfo; } 3746 3747 bool ShouldCompress() const; 3748 3749 void NotifyWriteSucceeded() const; 3750 3751 using InputStreamResult = 3752 mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult>; 3753 InputStreamResult GetInputStream(); 3754 3755 void Serialize(nsString& aText) const; 3756 }; 3757 class SCInputStream; 3758 3759 ObjectStoreAddPutParams mParams; 3760 Maybe<UniqueIndexTable> mUniqueIndexTable; 3761 3762 // This must be non-const so that we can update the mNextAutoIncrementId field 3763 // if we are modifying an autoIncrement objectStore. 3764 SafeRefPtr<FullObjectStoreMetadata> mMetadata; 3765 3766 nsTArray<StoredFileInfo> mStoredFileInfos; 3767 3768 Key mResponse; 3769 const OriginMetadata mOriginMetadata; 3770 const PersistenceType mPersistenceType; 3771 const bool mOverwrite; 3772 bool mObjectStoreMayHaveIndexes; 3773 bool mDataOverThreshold; 3774 3775 private: 3776 // Only created by TransactionBase. 3777 ObjectStoreAddOrPutRequestOp(SafeRefPtr<TransactionBase> aTransaction, 3778 const int64_t aRequestId, 3779 RequestParams&& aParams); 3780 3781 ~ObjectStoreAddOrPutRequestOp() override = default; 3782 3783 nsresult RemoveOldIndexDataValues(DatabaseConnection* aConnection); 3784 3785 bool Init(TransactionBase& aTransaction) override; 3786 3787 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 3788 3789 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override; 3790 3791 void Cleanup() override; 3792 }; 3793 3794 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::AssertInvariants() const { 3795 // The only allowed types are eStructuredClone, eBlob and eMutableFile. 3796 MOZ_ASSERT(StructuredCloneFileBase::eStructuredClone == mType || 3797 StructuredCloneFileBase::eBlob == mType || 3798 StructuredCloneFileBase::eMutableFile == mType); 3799 3800 // mFileInfo and a file actor in mFileActorOrInputStream are present until 3801 // the object is moved away, but an inputStream in mFileActorOrInputStream 3802 // can be released early. 3803 MOZ_ASSERT_IF(static_cast<bool>(mFileActorOrInputStream) && 3804 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>(), 3805 static_cast<bool>(mFileInfo)); 3806 3807 if (mFileInfo) { 3808 // In a non-moved StoredFileInfo, one of the following is true: 3809 // - This was an overflow structured clone (eStructuredClone) and 3810 // storedFileInfo.mFileActorOrInputStream CAN be a non-nullptr input 3811 // stream (but that might have been release by ReleaseInputStream). 3812 MOZ_ASSERT_IF( 3813 StructuredCloneFileBase::eStructuredClone == mType, 3814 !mFileActorOrInputStream || 3815 (mFileActorOrInputStream->is<nsCOMPtr<nsIInputStream>>() && 3816 mFileActorOrInputStream->as<nsCOMPtr<nsIInputStream>>())); 3817 3818 // - This is a reference to a Blob (eBlob) that may or may not have 3819 // already been written to disk. storedFileInfo.mFileActorOrInputStream 3820 // MUST be a non-null file actor, but its GetInputStream may return 3821 // nullptr (so don't assert on that). 3822 MOZ_ASSERT_IF(StructuredCloneFileBase::eBlob == mType, 3823 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>() && 3824 mFileActorOrInputStream->as<RefPtr<DatabaseFile>>()); 3825 3826 // - It's a mutable file (eMutableFile). No writing will be performed, 3827 // and storedFileInfo.mFileActorOrInputStream is Nothing. 3828 MOZ_ASSERT_IF(StructuredCloneFileBase::eMutableFile == mType, 3829 mFileActorOrInputStream->is<Nothing>()); 3830 } 3831 } 3832 3833 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::EnsureCipherKey() { 3834 const auto& fileInfo = GetFileInfo(); 3835 const auto& fileManager = fileInfo.Manager(); 3836 3837 // No need to generate cipher keys if we are not in PBM 3838 if (!fileManager.IsInPrivateBrowsingMode()) { 3839 return; 3840 } 3841 3842 nsCString keyId; 3843 keyId.AppendInt(fileInfo.Id()); 3844 3845 fileManager.MutableCipherKeyManagerRef().Ensure(keyId); 3846 } 3847 3848 ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo( 3849 SafeRefPtr<DatabaseFileInfo> aFileInfo, RefPtr<DatabaseFile> aFileActor) 3850 : mFileInfo{WrapNotNull(std::move(aFileInfo))}, 3851 mFileActorOrInputStream{std::move(aFileActor)} 3852 #ifdef DEBUG 3853 , 3854 mType{StructuredCloneFileBase::eBlob} 3855 #endif 3856 { 3857 AssertIsOnBackgroundThread(); 3858 AssertInvariants(); 3859 3860 EnsureCipherKey(); 3861 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo); 3862 } 3863 3864 ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo( 3865 SafeRefPtr<DatabaseFileInfo> aFileInfo, 3866 nsCOMPtr<nsIInputStream> aInputStream) 3867 : mFileInfo{WrapNotNull(std::move(aFileInfo))}, 3868 mFileActorOrInputStream{std::move(aInputStream)} 3869 #ifdef DEBUG 3870 , 3871 mType{StructuredCloneFileBase::eStructuredClone} 3872 #endif 3873 { 3874 AssertIsOnBackgroundThread(); 3875 AssertInvariants(); 3876 3877 EnsureCipherKey(); 3878 MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo); 3879 } 3880 3881 ObjectStoreAddOrPutRequestOp::StoredFileInfo 3882 ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForBlob( 3883 SafeRefPtr<DatabaseFileInfo> aFileInfo, RefPtr<DatabaseFile> aFileActor) { 3884 return {std::move(aFileInfo), std::move(aFileActor)}; 3885 } 3886 3887 ObjectStoreAddOrPutRequestOp::StoredFileInfo 3888 ObjectStoreAddOrPutRequestOp::StoredFileInfo::CreateForStructuredClone( 3889 SafeRefPtr<DatabaseFileInfo> aFileInfo, 3890 nsCOMPtr<nsIInputStream> aInputStream) { 3891 return {std::move(aFileInfo), std::move(aInputStream)}; 3892 } 3893 3894 bool ObjectStoreAddOrPutRequestOp::StoredFileInfo::ShouldCompress() const { 3895 // Must not be called after moving. 3896 MOZ_ASSERT(IsValid()); 3897 3898 // Compression is only necessary for eStructuredClone, i.e. when 3899 // mFileActorOrInputStream stored an input stream. However, this is only 3900 // called after GetInputStream, when mFileActorOrInputStream has been 3901 // cleared, which is only possible for this type. 3902 const bool res = !mFileActorOrInputStream; 3903 MOZ_ASSERT(res == (StructuredCloneFileBase::eStructuredClone == mType)); 3904 return res; 3905 } 3906 3907 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::NotifyWriteSucceeded() 3908 const { 3909 MOZ_ASSERT(IsValid()); 3910 3911 // For eBlob, clear the blob implementation. 3912 if (mFileActorOrInputStream && 3913 mFileActorOrInputStream->is<RefPtr<DatabaseFile>>()) { 3914 mFileActorOrInputStream->as<RefPtr<DatabaseFile>>() 3915 ->WriteSucceededClearBlobImpl(); 3916 } 3917 3918 // For the other types, no action is necessary. 3919 } 3920 3921 ObjectStoreAddOrPutRequestOp::StoredFileInfo::InputStreamResult 3922 ObjectStoreAddOrPutRequestOp::StoredFileInfo::GetInputStream() { 3923 if (!mFileActorOrInputStream) { 3924 MOZ_ASSERT(StructuredCloneFileBase::eStructuredClone == mType); 3925 return nsCOMPtr<nsIInputStream>{}; 3926 } 3927 3928 // For the different cases, see also the comments in AssertInvariants. 3929 return mFileActorOrInputStream->match( 3930 [](const Nothing&) -> InputStreamResult { 3931 return nsCOMPtr<nsIInputStream>{}; 3932 }, 3933 [](const RefPtr<DatabaseFile>& databaseActor) -> InputStreamResult { 3934 ErrorResult rv; 3935 auto inputStream = databaseActor->GetInputStream(rv); 3936 if (NS_WARN_IF(rv.Failed())) { 3937 return Err(rv.StealNSResult()); 3938 } 3939 3940 return inputStream; 3941 }, 3942 [this](const nsCOMPtr<nsIInputStream>& inputStream) -> InputStreamResult { 3943 auto res = inputStream; 3944 // destroy() clears the inputStream parameter, so we needed to make a 3945 // copy before 3946 mFileActorOrInputStream.destroy(); 3947 AssertInvariants(); 3948 return res; 3949 }); 3950 } 3951 3952 void ObjectStoreAddOrPutRequestOp::StoredFileInfo::Serialize( 3953 nsString& aText) const { 3954 AssertInvariants(); 3955 MOZ_ASSERT(IsValid()); 3956 3957 const int64_t id = (*mFileInfo)->Id(); 3958 3959 auto structuredCloneHandler = [&aText, id](const nsCOMPtr<nsIInputStream>&) { 3960 // eStructuredClone 3961 aText.Append('.'); 3962 aText.AppendInt(id); 3963 }; 3964 3965 // If mFileActorOrInputStream was moved, we had an inputStream before. 3966 if (!mFileActorOrInputStream) { 3967 structuredCloneHandler(nullptr); 3968 return; 3969 } 3970 3971 // This encoding is parsed in DeserializeStructuredCloneFile. 3972 mFileActorOrInputStream->match( 3973 [&aText, id](const Nothing&) { 3974 // eMutableFile 3975 aText.AppendInt(-id); 3976 }, 3977 [&aText, id](const RefPtr<DatabaseFile>&) { 3978 // eBlob 3979 aText.AppendInt(id); 3980 }, 3981 structuredCloneHandler); 3982 } 3983 3984 class ObjectStoreAddOrPutRequestOp::SCInputStream final 3985 : public nsIInputStream { 3986 const JSStructuredCloneData& mData; 3987 JSStructuredCloneData::Iterator mIter; 3988 3989 public: 3990 explicit SCInputStream(const JSStructuredCloneData& aData) 3991 : mData(aData), mIter(aData.Start()) {} 3992 3993 private: 3994 virtual ~SCInputStream() = default; 3995 3996 NS_DECL_THREADSAFE_ISUPPORTS 3997 NS_DECL_NSIINPUTSTREAM 3998 }; 3999 4000 class ObjectStoreGetRequestOp final : public NormalTransactionOp { 4001 friend class TransactionBase; 4002 4003 const IndexOrObjectStoreId mObjectStoreId; 4004 SafeRefPtr<Database> mDatabase; 4005 const Maybe<SerializedKeyRange> mOptionalKeyRange; 4006 AutoTArray<StructuredCloneReadInfoParent, 1> mResponse; 4007 PBackgroundParent* mBackgroundParent; 4008 uint32_t mPreprocessInfoCount; 4009 const uint32_t mLimit; 4010 const bool mGetAll; 4011 4012 private: 4013 // Only created by TransactionBase. 4014 ObjectStoreGetRequestOp(SafeRefPtr<TransactionBase> aTransaction, 4015 const int64_t aRequestId, 4016 const RequestParams& aParams, bool aGetAll); 4017 4018 ~ObjectStoreGetRequestOp() override = default; 4019 4020 template <typename T> 4021 mozilla::Result<T, nsresult> ConvertResponse( 4022 StructuredCloneReadInfoParent&& aInfo); 4023 4024 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4025 4026 bool HasPreprocessInfo() override; 4027 4028 mozilla::Result<PreprocessParams, nsresult> GetPreprocessParams() override; 4029 4030 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override; 4031 }; 4032 4033 class ObjectStoreGetKeyRequestOp final : public NormalTransactionOp { 4034 friend class TransactionBase; 4035 4036 const IndexOrObjectStoreId mObjectStoreId; 4037 const Maybe<SerializedKeyRange> mOptionalKeyRange; 4038 const uint32_t mLimit; 4039 const bool mGetAll; 4040 nsTArray<Key> mResponse; 4041 4042 private: 4043 // Only created by TransactionBase. 4044 ObjectStoreGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction, 4045 const int64_t aRequestId, 4046 const RequestParams& aParams, bool aGetAll); 4047 4048 ~ObjectStoreGetKeyRequestOp() override = default; 4049 4050 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4051 4052 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override; 4053 }; 4054 4055 class ObjectStoreDeleteRequestOp final : public NormalTransactionOp { 4056 friend class TransactionBase; 4057 4058 const ObjectStoreDeleteParams mParams; 4059 ObjectStoreDeleteResponse mResponse; 4060 bool mObjectStoreMayHaveIndexes; 4061 4062 private: 4063 ObjectStoreDeleteRequestOp(SafeRefPtr<TransactionBase> aTransaction, 4064 const int64_t aRequestId, 4065 const ObjectStoreDeleteParams& aParams); 4066 4067 ~ObjectStoreDeleteRequestOp() override = default; 4068 4069 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4070 4071 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override { 4072 aResponse = std::move(mResponse); 4073 *aResponseSize = 0; 4074 } 4075 }; 4076 4077 class ObjectStoreClearRequestOp final : public NormalTransactionOp { 4078 friend class TransactionBase; 4079 4080 const ObjectStoreClearParams mParams; 4081 ObjectStoreClearResponse mResponse; 4082 bool mObjectStoreMayHaveIndexes; 4083 4084 private: 4085 ObjectStoreClearRequestOp(SafeRefPtr<TransactionBase> aTransaction, 4086 const int64_t aRequestId, 4087 const ObjectStoreClearParams& aParams); 4088 4089 ~ObjectStoreClearRequestOp() override = default; 4090 4091 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4092 4093 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override { 4094 aResponse = std::move(mResponse); 4095 *aResponseSize = 0; 4096 } 4097 }; 4098 4099 class ObjectStoreCountRequestOp final : public NormalTransactionOp { 4100 friend class TransactionBase; 4101 4102 const ObjectStoreCountParams mParams; 4103 ObjectStoreCountResponse mResponse; 4104 4105 private: 4106 ObjectStoreCountRequestOp(SafeRefPtr<TransactionBase> aTransaction, 4107 const int64_t aRequestId, 4108 const ObjectStoreCountParams& aParams) 4109 : NormalTransactionOp(std::move(aTransaction), aRequestId), 4110 mParams(aParams) {} 4111 4112 ~ObjectStoreCountRequestOp() override = default; 4113 4114 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4115 4116 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override { 4117 aResponse = std::move(mResponse); 4118 *aResponseSize = sizeof(uint64_t); 4119 } 4120 }; 4121 4122 class IndexRequestOpBase : public NormalTransactionOp { 4123 protected: 4124 const SafeRefPtr<FullIndexMetadata> mMetadata; 4125 4126 protected: 4127 IndexRequestOpBase(SafeRefPtr<TransactionBase> aTransaction, 4128 const int64_t aRequestId, const RequestParams& aParams) 4129 : NormalTransactionOp(std::move(aTransaction), aRequestId), 4130 mMetadata(IndexMetadataForParams(Transaction(), aParams)) {} 4131 4132 ~IndexRequestOpBase() override = default; 4133 4134 private: 4135 static SafeRefPtr<FullIndexMetadata> IndexMetadataForParams( 4136 const TransactionBase& aTransaction, const RequestParams& aParams); 4137 }; 4138 4139 class IndexGetRequestOp final : public IndexRequestOpBase { 4140 friend class TransactionBase; 4141 4142 SafeRefPtr<Database> mDatabase; 4143 const Maybe<SerializedKeyRange> mOptionalKeyRange; 4144 AutoTArray<StructuredCloneReadInfoParent, 1> mResponse; 4145 PBackgroundParent* mBackgroundParent; 4146 const uint32_t mLimit; 4147 const bool mGetAll; 4148 4149 private: 4150 // Only created by TransactionBase. 4151 IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction, 4152 const int64_t aRequestId, const RequestParams& aParams, 4153 bool aGetAll); 4154 4155 ~IndexGetRequestOp() override = default; 4156 4157 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4158 4159 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override; 4160 }; 4161 4162 class IndexGetKeyRequestOp final : public IndexRequestOpBase { 4163 friend class TransactionBase; 4164 4165 const Maybe<SerializedKeyRange> mOptionalKeyRange; 4166 AutoTArray<Key, 1> mResponse; 4167 const uint32_t mLimit; 4168 const bool mGetAll; 4169 4170 private: 4171 // Only created by TransactionBase. 4172 IndexGetKeyRequestOp(SafeRefPtr<TransactionBase> aTransaction, 4173 const int64_t aRequestId, const RequestParams& aParams, 4174 bool aGetAll); 4175 4176 ~IndexGetKeyRequestOp() override = default; 4177 4178 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4179 4180 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override; 4181 }; 4182 4183 class IndexCountRequestOp final : public IndexRequestOpBase { 4184 friend class TransactionBase; 4185 4186 const IndexCountParams mParams; 4187 IndexCountResponse mResponse; 4188 4189 private: 4190 // Only created by TransactionBase. 4191 IndexCountRequestOp(SafeRefPtr<TransactionBase> aTransaction, 4192 const int64_t aRequestId, const RequestParams& aParams) 4193 : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams), 4194 mParams(aParams.get_IndexCountParams()) {} 4195 4196 ~IndexCountRequestOp() override = default; 4197 4198 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4199 4200 void GetResponse(RequestResponse& aResponse, size_t* aResponseSize) override { 4201 aResponse = std::move(mResponse); 4202 *aResponseSize = sizeof(uint64_t); 4203 } 4204 }; 4205 4206 template <IDBCursorType CursorType> 4207 class Cursor; 4208 4209 constexpr IDBCursorType ToKeyOnlyType(const IDBCursorType aType) { 4210 MOZ_ASSERT(aType == IDBCursorType::ObjectStore || 4211 aType == IDBCursorType::ObjectStoreKey || 4212 aType == IDBCursorType::Index || aType == IDBCursorType::IndexKey); 4213 switch (aType) { 4214 case IDBCursorType::ObjectStore: 4215 [[fallthrough]]; 4216 case IDBCursorType::ObjectStoreKey: 4217 return IDBCursorType::ObjectStoreKey; 4218 case IDBCursorType::Index: 4219 [[fallthrough]]; 4220 case IDBCursorType::IndexKey: 4221 return IDBCursorType::IndexKey; 4222 } 4223 } 4224 4225 template <IDBCursorType CursorType> 4226 using CursorPosition = CursorData<ToKeyOnlyType(CursorType)>; 4227 4228 #ifdef DEBUG 4229 constexpr indexedDB::OpenCursorParams::Type ToOpenCursorParamsType( 4230 const IDBCursorType aType) { 4231 MOZ_ASSERT(aType == IDBCursorType::ObjectStore || 4232 aType == IDBCursorType::ObjectStoreKey || 4233 aType == IDBCursorType::Index || aType == IDBCursorType::IndexKey); 4234 switch (aType) { 4235 case IDBCursorType::ObjectStore: 4236 return indexedDB::OpenCursorParams::TObjectStoreOpenCursorParams; 4237 case IDBCursorType::ObjectStoreKey: 4238 return indexedDB::OpenCursorParams::TObjectStoreOpenKeyCursorParams; 4239 case IDBCursorType::Index: 4240 return indexedDB::OpenCursorParams::TIndexOpenCursorParams; 4241 case IDBCursorType::IndexKey: 4242 return indexedDB::OpenCursorParams::TIndexOpenKeyCursorParams; 4243 } 4244 } 4245 #endif 4246 4247 class CursorBase : public PBackgroundIDBCursorParent { 4248 friend class TransactionBase; 4249 template <IDBCursorType CursorType> 4250 friend class CommonOpenOpHelper; 4251 4252 protected: 4253 const SafeRefPtr<TransactionBase> mTransaction; 4254 4255 // This should only be touched on the PBackground thread to check whether 4256 // the objectStore has been deleted. Holding these saves a hash lookup for 4257 // every call to continue()/advance(). 4258 InitializedOnce<const NotNull<SafeRefPtr<FullObjectStoreMetadata>>> 4259 mObjectStoreMetadata; 4260 4261 const IndexOrObjectStoreId mObjectStoreId; 4262 4263 LazyInitializedOnce<const Key> 4264 mLocaleAwareRangeBound; ///< If the cursor is based on a key range, the 4265 ///< bound in the direction of iteration (e.g. 4266 ///< the upper bound in case of mDirection == 4267 ///< NEXT). If the cursor is based on a key, it 4268 ///< is unset. If mLocale is set, this was 4269 ///< converted to mLocale. 4270 4271 const Direction mDirection; 4272 4273 const int32_t mMaxExtraCount; 4274 4275 const bool mIsSameProcessActor; 4276 4277 struct ConstructFromTransactionBase {}; 4278 4279 public: 4280 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::CursorBase, 4281 final) 4282 4283 CursorBase(SafeRefPtr<TransactionBase> aTransaction, 4284 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata, 4285 Direction aDirection, 4286 ConstructFromTransactionBase aConstructionTag); 4287 4288 protected: 4289 // Reference counted. 4290 ~CursorBase() override { MOZ_ASSERT(!mObjectStoreMetadata); } 4291 4292 private: 4293 virtual bool Start(const int64_t aRequestId, 4294 const OpenCursorParams& aParams) = 0; 4295 }; 4296 4297 class IndexCursorBase : public CursorBase { 4298 public: 4299 bool IsLocaleAware() const { return !mLocale.IsEmpty(); } 4300 4301 IndexCursorBase(SafeRefPtr<TransactionBase> aTransaction, 4302 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata, 4303 SafeRefPtr<FullIndexMetadata> aIndexMetadata, 4304 Direction aDirection, 4305 ConstructFromTransactionBase aConstructionTag) 4306 : CursorBase{std::move(aTransaction), std::move(aObjectStoreMetadata), 4307 aDirection, aConstructionTag}, 4308 mIndexMetadata(WrapNotNull(std::move(aIndexMetadata))), 4309 mIndexId((*mIndexMetadata)->mCommonMetadata.id()), 4310 mUniqueIndex((*mIndexMetadata)->mCommonMetadata.unique()), 4311 mLocale((*mIndexMetadata)->mCommonMetadata.locale()) {} 4312 4313 protected: 4314 IndexOrObjectStoreId Id() const { return mIndexId; } 4315 4316 // This should only be touched on the PBackground thread to check whether 4317 // the index has been deleted. Holding these saves a hash lookup for every 4318 // call to continue()/advance(). 4319 InitializedOnce<const NotNull<SafeRefPtr<FullIndexMetadata>>> mIndexMetadata; 4320 const IndexOrObjectStoreId mIndexId; 4321 const bool mUniqueIndex; 4322 const nsCString 4323 mLocale; ///< The locale if the cursor is locale-aware, otherwise empty. 4324 4325 struct ContinueQueries { 4326 nsCString mContinueQuery; 4327 nsCString mContinueToQuery; 4328 nsCString mContinuePrimaryKeyQuery; 4329 4330 const nsACString& GetContinueQuery(const bool hasContinueKey, 4331 const bool hasContinuePrimaryKey) const { 4332 return hasContinuePrimaryKey ? mContinuePrimaryKeyQuery 4333 : hasContinueKey ? mContinueToQuery 4334 : mContinueQuery; 4335 } 4336 }; 4337 }; 4338 4339 class ObjectStoreCursorBase : public CursorBase { 4340 public: 4341 using CursorBase::CursorBase; 4342 4343 static constexpr bool IsLocaleAware() { return false; } 4344 4345 protected: 4346 IndexOrObjectStoreId Id() const { return mObjectStoreId; } 4347 4348 struct ContinueQueries { 4349 nsCString mContinueQuery; 4350 nsCString mContinueToQuery; 4351 4352 const nsACString& GetContinueQuery(const bool hasContinueKey, 4353 const bool hasContinuePrimaryKey) const { 4354 MOZ_ASSERT(!hasContinuePrimaryKey); 4355 return hasContinueKey ? mContinueToQuery : mContinueQuery; 4356 } 4357 }; 4358 }; 4359 4360 using FilesArray = nsTArray<nsTArray<StructuredCloneFileParent>>; 4361 4362 struct PseudoFilesArray { 4363 static constexpr bool IsEmpty() { return true; } 4364 4365 static constexpr void Clear() {} 4366 }; 4367 4368 template <IDBCursorType CursorType> 4369 using FilesArrayT = 4370 std::conditional_t<!CursorTypeTraits<CursorType>::IsKeyOnlyCursor, 4371 FilesArray, PseudoFilesArray>; 4372 4373 class ValueCursorBase { 4374 friend struct ValuePopulateResponseHelper<true>; 4375 friend struct ValuePopulateResponseHelper<false>; 4376 4377 protected: 4378 explicit ValueCursorBase(TransactionBase* const aTransaction) 4379 : mDatabase(aTransaction->GetDatabasePtr()), 4380 mFileManager(mDatabase->GetFileManagerPtr()), 4381 mBackgroundParent(WrapNotNull(aTransaction->GetBackgroundParent())) { 4382 MOZ_ASSERT(mDatabase); 4383 } 4384 4385 void ProcessFiles(CursorResponse& aResponse, const FilesArray& aFiles); 4386 4387 ~ValueCursorBase() { MOZ_ASSERT(!mBackgroundParent); } 4388 4389 const SafeRefPtr<Database> mDatabase; 4390 const NotNull<SafeRefPtr<DatabaseFileManager>> mFileManager; 4391 4392 InitializedOnce<const NotNull<PBackgroundParent*>> mBackgroundParent; 4393 }; 4394 4395 class KeyCursorBase { 4396 protected: 4397 explicit KeyCursorBase(TransactionBase* const /*aTransaction*/) {} 4398 4399 static constexpr void ProcessFiles(CursorResponse& aResponse, 4400 const PseudoFilesArray& aFiles) {} 4401 }; 4402 4403 template <IDBCursorType CursorType> 4404 class CursorOpBaseHelperBase; 4405 4406 template <IDBCursorType CursorType> 4407 class Cursor final 4408 : public std::conditional_t< 4409 CursorTypeTraits<CursorType>::IsObjectStoreCursor, 4410 ObjectStoreCursorBase, IndexCursorBase>, 4411 public std::conditional_t<CursorTypeTraits<CursorType>::IsKeyOnlyCursor, 4412 KeyCursorBase, ValueCursorBase> { 4413 using Base = 4414 std::conditional_t<CursorTypeTraits<CursorType>::IsObjectStoreCursor, 4415 ObjectStoreCursorBase, IndexCursorBase>; 4416 4417 using KeyValueBase = 4418 std::conditional_t<CursorTypeTraits<CursorType>::IsKeyOnlyCursor, 4419 KeyCursorBase, ValueCursorBase>; 4420 4421 static constexpr bool IsIndexCursor = 4422 !CursorTypeTraits<CursorType>::IsObjectStoreCursor; 4423 4424 static constexpr bool IsValueCursor = 4425 !CursorTypeTraits<CursorType>::IsKeyOnlyCursor; 4426 4427 class CursorOpBase; 4428 class OpenOp; 4429 class ContinueOp; 4430 4431 using Base::Id; 4432 using CursorBase::Manager; 4433 using CursorBase::mDirection; 4434 using CursorBase::mObjectStoreId; 4435 using CursorBase::mTransaction; 4436 using typename CursorBase::ActorDestroyReason; 4437 4438 using TypedOpenOpHelper = 4439 std::conditional_t<IsIndexCursor, IndexOpenOpHelper<CursorType>, 4440 ObjectStoreOpenOpHelper<CursorType>>; 4441 4442 friend class CursorOpBaseHelperBase<CursorType>; 4443 friend class CommonOpenOpHelper<CursorType>; 4444 friend TypedOpenOpHelper; 4445 friend class OpenOpHelper<CursorType>; 4446 4447 CursorOpBase* mCurrentlyRunningOp = nullptr; 4448 4449 LazyInitializedOnce<const typename Base::ContinueQueries> mContinueQueries; 4450 4451 // Only called by TransactionBase. 4452 bool Start(const int64_t aRequestId, const OpenCursorParams& aParams) final; 4453 4454 void SendResponseInternal(CursorResponse& aResponse, 4455 const FilesArrayT<CursorType>& aFiles); 4456 4457 // Must call SendResponseInternal! 4458 bool SendResponse(const CursorResponse& aResponse) = delete; 4459 4460 // IPDL methods. 4461 void ActorDestroy(ActorDestroyReason aWhy) override; 4462 4463 mozilla::ipc::IPCResult RecvDeleteMe() override; 4464 4465 mozilla::ipc::IPCResult RecvContinue( 4466 const int64_t& aRequestId, const CursorRequestParams& aParams, 4467 const Key& aCurrentKey, const Key& aCurrentObjectStoreKey) override; 4468 4469 public: 4470 Cursor(SafeRefPtr<TransactionBase> aTransaction, 4471 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata, 4472 SafeRefPtr<FullIndexMetadata> aIndexMetadata, 4473 typename Base::Direction aDirection, 4474 typename Base::ConstructFromTransactionBase aConstructionTag) 4475 : Base{std::move(aTransaction), std::move(aObjectStoreMetadata), 4476 std::move(aIndexMetadata), aDirection, aConstructionTag}, 4477 KeyValueBase{this->mTransaction.unsafeGetRawPtr()} {} 4478 4479 Cursor(SafeRefPtr<TransactionBase> aTransaction, 4480 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata, 4481 typename Base::Direction aDirection, 4482 typename Base::ConstructFromTransactionBase aConstructionTag) 4483 : Base{std::move(aTransaction), std::move(aObjectStoreMetadata), 4484 aDirection, aConstructionTag}, 4485 KeyValueBase{this->mTransaction.unsafeGetRawPtr()} {} 4486 4487 private: 4488 void SetOptionalKeyRange(const Maybe<SerializedKeyRange>& aOptionalKeyRange, 4489 bool* aOpen); 4490 4491 bool VerifyRequestParams(const CursorRequestParams& aParams, 4492 const CursorPosition<CursorType>& aPosition) const; 4493 4494 ~Cursor() final = default; 4495 }; 4496 4497 template <IDBCursorType CursorType> 4498 class Cursor<CursorType>::CursorOpBase 4499 : public TransactionDatabaseOperationBase { 4500 friend class CursorOpBaseHelperBase<CursorType>; 4501 4502 protected: 4503 RefPtr<Cursor> mCursor; 4504 FilesArrayT<CursorType> mFiles; // TODO: Consider removing this member 4505 // entirely if we are no value cursor. 4506 4507 CursorResponse mResponse; 4508 4509 #ifdef DEBUG 4510 bool mResponseSent; 4511 #endif 4512 4513 protected: 4514 explicit CursorOpBase(Cursor* aCursor, const int64_t aRequestId) 4515 : TransactionDatabaseOperationBase(aCursor->mTransaction.clonePtr(), 4516 /* aRequestId */ aRequestId), 4517 mCursor(aCursor) 4518 #ifdef DEBUG 4519 , 4520 mResponseSent(false) 4521 #endif 4522 { 4523 AssertIsOnBackgroundThread(); 4524 MOZ_ASSERT(aCursor); 4525 } 4526 4527 ~CursorOpBase() override = default; 4528 4529 bool SendFailureResult(nsresult aResultCode) final; 4530 nsresult SendSuccessResult() final; 4531 4532 void Cleanup() override; 4533 }; 4534 4535 template <IDBCursorType CursorType> 4536 class OpenOpHelper; 4537 4538 using ResponseSizeOrError = Result<size_t, nsresult>; 4539 4540 template <IDBCursorType CursorType> 4541 class CursorOpBaseHelperBase { 4542 public: 4543 explicit CursorOpBaseHelperBase( 4544 typename Cursor<CursorType>::CursorOpBase& aOp) 4545 : mOp{aOp} {} 4546 4547 ResponseSizeOrError PopulateResponseFromStatement(mozIStorageStatement* aStmt, 4548 bool aInitializeResponse, 4549 Key* const aOptOutSortKey); 4550 4551 void PopulateExtraResponses(mozIStorageStatement* aStmt, 4552 uint32_t aMaxExtraCount, 4553 const size_t aInitialResponseSize, 4554 const nsACString& aOperation, 4555 Key* const aOptPreviousSortKey); 4556 4557 protected: 4558 Cursor<CursorType>& GetCursor() { 4559 MOZ_ASSERT(mOp.mCursor); 4560 return *mOp.mCursor; 4561 } 4562 4563 void SetResponse(CursorResponse aResponse) { 4564 mOp.mResponse = std::move(aResponse); 4565 } 4566 4567 protected: 4568 typename Cursor<CursorType>::CursorOpBase& mOp; 4569 }; 4570 4571 class CommonOpenOpHelperBase { 4572 protected: 4573 static void AppendConditionClause(const nsACString& aColumnName, 4574 const nsACString& aStatementParameterName, 4575 bool aLessThan, bool aEquals, 4576 nsCString& aResult); 4577 }; 4578 4579 template <IDBCursorType CursorType> 4580 class CommonOpenOpHelper : public CursorOpBaseHelperBase<CursorType>, 4581 protected CommonOpenOpHelperBase { 4582 public: 4583 explicit CommonOpenOpHelper(typename Cursor<CursorType>::OpenOp& aOp) 4584 : CursorOpBaseHelperBase<CursorType>{aOp} {} 4585 4586 protected: 4587 using CursorOpBaseHelperBase<CursorType>::GetCursor; 4588 using CursorOpBaseHelperBase<CursorType>::PopulateExtraResponses; 4589 using CursorOpBaseHelperBase<CursorType>::PopulateResponseFromStatement; 4590 using CursorOpBaseHelperBase<CursorType>::SetResponse; 4591 4592 const Maybe<SerializedKeyRange>& GetOptionalKeyRange() const { 4593 // This downcast is safe, since we initialized mOp from an OpenOp in the 4594 // ctor. 4595 return static_cast<typename Cursor<CursorType>::OpenOp&>(this->mOp) 4596 .mOptionalKeyRange; 4597 } 4598 4599 nsresult ProcessStatementSteps(mozIStorageStatement* aStmt); 4600 }; 4601 4602 template <IDBCursorType CursorType> 4603 class ObjectStoreOpenOpHelper : protected CommonOpenOpHelper<CursorType> { 4604 public: 4605 using CommonOpenOpHelper<CursorType>::CommonOpenOpHelper; 4606 4607 protected: 4608 using CommonOpenOpHelper<CursorType>::GetCursor; 4609 using CommonOpenOpHelper<CursorType>::GetOptionalKeyRange; 4610 using CommonOpenOpHelper<CursorType>::AppendConditionClause; 4611 4612 void PrepareKeyConditionClauses(const nsACString& aDirectionClause, 4613 const nsACString& aQueryStart); 4614 }; 4615 4616 template <IDBCursorType CursorType> 4617 class IndexOpenOpHelper : protected CommonOpenOpHelper<CursorType> { 4618 public: 4619 using CommonOpenOpHelper<CursorType>::CommonOpenOpHelper; 4620 4621 protected: 4622 using CommonOpenOpHelper<CursorType>::GetCursor; 4623 using CommonOpenOpHelper<CursorType>::GetOptionalKeyRange; 4624 using CommonOpenOpHelper<CursorType>::AppendConditionClause; 4625 4626 void PrepareIndexKeyConditionClause( 4627 const nsACString& aDirectionClause, 4628 const nsLiteralCString& aObjectDataKeyPrefix, nsAutoCString aQueryStart); 4629 }; 4630 4631 template <> 4632 class OpenOpHelper<IDBCursorType::ObjectStore> 4633 : public ObjectStoreOpenOpHelper<IDBCursorType::ObjectStore> { 4634 public: 4635 using ObjectStoreOpenOpHelper< 4636 IDBCursorType::ObjectStore>::ObjectStoreOpenOpHelper; 4637 4638 nsresult DoDatabaseWork(DatabaseConnection* aConnection); 4639 }; 4640 4641 template <> 4642 class OpenOpHelper<IDBCursorType::ObjectStoreKey> 4643 : public ObjectStoreOpenOpHelper<IDBCursorType::ObjectStoreKey> { 4644 public: 4645 using ObjectStoreOpenOpHelper< 4646 IDBCursorType::ObjectStoreKey>::ObjectStoreOpenOpHelper; 4647 4648 nsresult DoDatabaseWork(DatabaseConnection* aConnection); 4649 }; 4650 4651 template <> 4652 class OpenOpHelper<IDBCursorType::Index> 4653 : IndexOpenOpHelper<IDBCursorType::Index> { 4654 private: 4655 void PrepareKeyConditionClauses(const nsACString& aDirectionClause, 4656 nsAutoCString aQueryStart) { 4657 PrepareIndexKeyConditionClause(aDirectionClause, "index_table."_ns, 4658 std::move(aQueryStart)); 4659 } 4660 4661 public: 4662 using IndexOpenOpHelper<IDBCursorType::Index>::IndexOpenOpHelper; 4663 4664 nsresult DoDatabaseWork(DatabaseConnection* aConnection); 4665 }; 4666 4667 template <> 4668 class OpenOpHelper<IDBCursorType::IndexKey> 4669 : IndexOpenOpHelper<IDBCursorType::IndexKey> { 4670 private: 4671 void PrepareKeyConditionClauses(const nsACString& aDirectionClause, 4672 nsAutoCString aQueryStart) { 4673 PrepareIndexKeyConditionClause(aDirectionClause, ""_ns, 4674 std::move(aQueryStart)); 4675 } 4676 4677 public: 4678 using IndexOpenOpHelper<IDBCursorType::IndexKey>::IndexOpenOpHelper; 4679 4680 nsresult DoDatabaseWork(DatabaseConnection* aConnection); 4681 }; 4682 4683 template <IDBCursorType CursorType> 4684 class Cursor<CursorType>::OpenOp final : public CursorOpBase { 4685 friend class Cursor<CursorType>; 4686 friend class CommonOpenOpHelper<CursorType>; 4687 4688 const Maybe<SerializedKeyRange> mOptionalKeyRange; 4689 4690 using CursorOpBase::mCursor; 4691 using CursorOpBase::mResponse; 4692 4693 // Only created by Cursor. 4694 OpenOp(Cursor* const aCursor, const int64_t aRequestId, 4695 const Maybe<SerializedKeyRange>& aOptionalKeyRange) 4696 : CursorOpBase(aCursor, aRequestId), 4697 mOptionalKeyRange(aOptionalKeyRange) {} 4698 4699 // Reference counted. 4700 ~OpenOp() override = default; 4701 4702 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4703 }; 4704 4705 template <IDBCursorType CursorType> 4706 class Cursor<CursorType>::ContinueOp final 4707 : public Cursor<CursorType>::CursorOpBase { 4708 friend class Cursor<CursorType>; 4709 4710 using CursorOpBase::mCursor; 4711 using CursorOpBase::mResponse; 4712 const CursorRequestParams mParams; 4713 4714 // Only created by Cursor. 4715 ContinueOp(Cursor* const aCursor, int64_t aRequestId, 4716 CursorRequestParams aParams, CursorPosition<CursorType> aPosition) 4717 : CursorOpBase(aCursor, aRequestId), 4718 mParams(std::move(aParams)), 4719 mCurrentPosition{std::move(aPosition)} { 4720 MOZ_ASSERT(mParams.type() != CursorRequestParams::T__None); 4721 } 4722 4723 // Reference counted. 4724 ~ContinueOp() override = default; 4725 4726 nsresult DoDatabaseWork(DatabaseConnection* aConnection) override; 4727 4728 const CursorPosition<CursorType> mCurrentPosition; 4729 }; 4730 4731 class Utils final : public PBackgroundIndexedDBUtilsParent { 4732 #ifdef DEBUG 4733 bool mActorDestroyed; 4734 #endif 4735 4736 public: 4737 Utils(); 4738 4739 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Utils) 4740 4741 private: 4742 // Reference counted. 4743 ~Utils() override; 4744 4745 // IPDL methods are only called by IPDL. 4746 void ActorDestroy(ActorDestroyReason aWhy) override; 4747 4748 mozilla::ipc::IPCResult RecvDeleteMe() override; 4749 4750 mozilla::ipc::IPCResult RecvGetFileReferences( 4751 const PersistenceType& aPersistenceType, const nsACString& aOrigin, 4752 const nsAString& aDatabaseName, const int64_t& aFileId, int32_t* aRefCnt, 4753 int32_t* aDBRefCnt, bool* aResult) override; 4754 4755 mozilla::ipc::IPCResult RecvDoMaintenance( 4756 DoMaintenanceResolver&& aResolver) override; 4757 }; 4758 4759 /******************************************************************************* 4760 * Other class declarations 4761 ******************************************************************************/ 4762 4763 struct DatabaseActorInfo final { 4764 friend mozilla::DefaultDelete<DatabaseActorInfo>; 4765 4766 SafeRefPtr<FullDatabaseMetadata> mMetadata; 4767 // We don't use LinkedList<CheckedUnsafePtr<Database>> because 4768 // CheckedUnsafePtr is not suitable for use within LinkedList. While it's 4769 // theoretically possible to adapt LinkedList to support it, doing so would 4770 // introduce unnecessary overhead. Instead, we use a simpler and more 4771 // efficient approach. Each Database instance asserts !isInList() in its 4772 // destructor to catch dangling pointer issues. 4773 LinkedList<Database> mLiveDatabases; 4774 RefPtr<FactoryOp> mWaitingFactoryOp; 4775 4776 DatabaseActorInfo(SafeRefPtr<FullDatabaseMetadata> aMetadata, 4777 NotNull<Database*> aDatabase) 4778 : mMetadata(std::move(aMetadata)) { 4779 MOZ_COUNT_CTOR(DatabaseActorInfo); 4780 4781 mLiveDatabases.insertBack(aDatabase); 4782 } 4783 4784 private: 4785 ~DatabaseActorInfo() { 4786 MOZ_ASSERT(mLiveDatabases.isEmpty()); 4787 MOZ_ASSERT(!mWaitingFactoryOp || !mWaitingFactoryOp->HasBlockedDatabases()); 4788 4789 MOZ_COUNT_DTOR(DatabaseActorInfo); 4790 } 4791 }; 4792 4793 class DatabaseLoggingInfo final { 4794 #ifdef DEBUG 4795 // Just for potential warnings. 4796 friend class Factory; 4797 #endif 4798 4799 LoggingInfo mLoggingInfo; 4800 4801 public: 4802 explicit DatabaseLoggingInfo(const LoggingInfo& aLoggingInfo) 4803 : mLoggingInfo(aLoggingInfo) { 4804 AssertIsOnBackgroundThread(); 4805 MOZ_ASSERT(aLoggingInfo.nextTransactionSerialNumber()); 4806 MOZ_ASSERT(aLoggingInfo.nextVersionChangeTransactionSerialNumber()); 4807 MOZ_ASSERT(aLoggingInfo.nextRequestSerialNumber()); 4808 } 4809 4810 const nsID& Id() const { 4811 AssertIsOnBackgroundThread(); 4812 4813 return mLoggingInfo.backgroundChildLoggingId(); 4814 } 4815 4816 int64_t NextTransactionSN(IDBTransaction::Mode aMode) { 4817 AssertIsOnBackgroundThread(); 4818 MOZ_ASSERT(mLoggingInfo.nextTransactionSerialNumber() < INT64_MAX); 4819 MOZ_ASSERT(mLoggingInfo.nextVersionChangeTransactionSerialNumber() > 4820 INT64_MIN); 4821 4822 if (aMode == IDBTransaction::Mode::VersionChange) { 4823 return mLoggingInfo.nextVersionChangeTransactionSerialNumber()--; 4824 } 4825 4826 return mLoggingInfo.nextTransactionSerialNumber()++; 4827 } 4828 4829 uint64_t NextRequestSN() { 4830 AssertIsOnBackgroundThread(); 4831 MOZ_ASSERT(mLoggingInfo.nextRequestSerialNumber() < UINT64_MAX); 4832 4833 return mLoggingInfo.nextRequestSerialNumber()++; 4834 } 4835 4836 NS_INLINE_DECL_REFCOUNTING(DatabaseLoggingInfo) 4837 4838 private: 4839 ~DatabaseLoggingInfo(); 4840 }; 4841 4842 class QuotaClient final : public mozilla::dom::quota::Client { 4843 friend class GetDatabasesOp; 4844 4845 static QuotaClient* sInstance; 4846 4847 nsCOMPtr<nsIEventTarget> mBackgroundThread; 4848 nsCOMPtr<nsITimer> mDeleteTimer; 4849 nsTArray<RefPtr<Maintenance>> mMaintenanceQueue; 4850 RefPtr<Maintenance> mCurrentMaintenance; 4851 RefPtr<nsThreadPool> mMaintenanceThreadPool; 4852 nsClassHashtable<nsRefPtrHashKey<DatabaseFileManager>, nsTArray<int64_t>> 4853 mPendingDeleteInfos; 4854 4855 public: 4856 QuotaClient(); 4857 4858 static QuotaClient* GetInstance() { 4859 AssertIsOnBackgroundThread(); 4860 4861 return sInstance; 4862 } 4863 4864 nsIEventTarget* BackgroundThread() const { 4865 MOZ_ASSERT(mBackgroundThread); 4866 return mBackgroundThread; 4867 } 4868 4869 nsresult AsyncDeleteFile(DatabaseFileManager* aFileManager, int64_t aFileId); 4870 4871 nsresult FlushPendingFileDeletions(); 4872 4873 RefPtr<BoolPromise> DoMaintenance(); 4874 4875 RefPtr<Maintenance> GetCurrentMaintenance() const { 4876 return mCurrentMaintenance; 4877 } 4878 4879 void NoteFinishedMaintenance(Maintenance* aMaintenance) { 4880 AssertIsOnBackgroundThread(); 4881 MOZ_ASSERT(aMaintenance); 4882 MOZ_ASSERT(mCurrentMaintenance == aMaintenance); 4883 4884 mCurrentMaintenance = nullptr; 4885 4886 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::IDB, 4887 "Maintenance finished"_ns); 4888 4889 ProcessMaintenanceQueue(); 4890 } 4891 4892 nsThreadPool* GetOrCreateThreadPool(); 4893 4894 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::QuotaClient, 4895 override) 4896 4897 mozilla::dom::quota::Client::Type GetType() override; 4898 4899 nsresult UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) override; 4900 4901 nsresult UpgradeStorageFrom2_1To2_2(nsIFile* aDirectory) override; 4902 4903 Result<UsageInfo, nsresult> InitOrigin(PersistenceType aPersistenceType, 4904 const OriginMetadata& aOriginMetadata, 4905 const AtomicBool& aCanceled) override; 4906 4907 nsresult InitOriginWithoutTracking(PersistenceType aPersistenceType, 4908 const OriginMetadata& aOriginMetadata, 4909 const AtomicBool& aCanceled) override; 4910 4911 Result<UsageInfo, nsresult> GetUsageForOrigin( 4912 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 4913 const AtomicBool& aCanceled) override; 4914 4915 void OnOriginClearCompleted(const OriginMetadata& aOriginMetadata) override; 4916 4917 void OnRepositoryClearCompleted(PersistenceType aPersistenceType) override; 4918 4919 void ReleaseIOThreadObjects() override; 4920 4921 void AbortOperationsForLocks( 4922 const DirectoryLockIdTable& aDirectoryLockIds) override; 4923 4924 void AbortOperationsForProcess(ContentParentId aContentParentId) override; 4925 4926 void AbortAllOperations() override; 4927 4928 void StartIdleMaintenance() override; 4929 4930 void StopIdleMaintenance() override; 4931 4932 private: 4933 ~QuotaClient() override; 4934 4935 void InitiateShutdown() override; 4936 bool IsShutdownCompleted() const override; 4937 nsCString GetShutdownStatus() const override; 4938 void ForceKillActors() override; 4939 void FinalizeShutdown() override; 4940 4941 static void DeleteTimerCallback(nsITimer* aTimer, void* aClosure); 4942 4943 void AbortAllMaintenances(); 4944 4945 Result<nsCOMPtr<nsIFile>, nsresult> GetDirectory( 4946 const OriginMetadata& aOriginMetadata); 4947 4948 struct SubdirectoriesToProcessAndDatabaseFilenames { 4949 AutoTArray<nsString, 20> subdirsToProcess; 4950 nsTHashSet<nsString> databaseFilenames{20}; 4951 }; 4952 4953 struct SubdirectoriesToProcessAndDatabaseFilenamesAndObsoleteFilenames { 4954 AutoTArray<nsString, 20> subdirsToProcess; 4955 nsTHashSet<nsString> databaseFilenames{20}; 4956 nsTHashSet<nsString> obsoleteFilenames{20}; 4957 }; 4958 4959 enum class ObsoleteFilenamesHandling { Include, Omit }; 4960 4961 template <ObsoleteFilenamesHandling ObsoleteFilenames> 4962 using GetDatabaseFilenamesResult = std::conditional_t< 4963 ObsoleteFilenames == ObsoleteFilenamesHandling::Include, 4964 SubdirectoriesToProcessAndDatabaseFilenamesAndObsoleteFilenames, 4965 SubdirectoriesToProcessAndDatabaseFilenames>; 4966 4967 // Returns a two-part or three-part structure: 4968 // 4969 // The first part is an array of subdirectories to process. 4970 // 4971 // The second part is a hashtable of database filenames. 4972 // 4973 // When ObsoleteFilenames is ObsoleteFilenamesHandling::Include, will also 4974 // collect files based on the marker files. For now, 4975 // GetUsageForOriginInternal() is the only consumer of this result because it 4976 // checks those unfinished deletion and clean them up after that. 4977 template <ObsoleteFilenamesHandling ObsoleteFilenames = 4978 ObsoleteFilenamesHandling::Omit> 4979 Result<GetDatabaseFilenamesResult<ObsoleteFilenames>, 4980 nsresult> static GetDatabaseFilenames(nsIFile& aDirectory, 4981 const AtomicBool& aCanceled); 4982 4983 nsresult GetUsageForOriginInternal(PersistenceType aPersistenceType, 4984 const OriginMetadata& aOriginMetadata, 4985 const AtomicBool& aCanceled, 4986 bool aInitializing, UsageInfo* aUsageInfo); 4987 4988 // Runs on the PBackground thread. Checks to see if there's a queued 4989 // Maintenance to run. 4990 void ProcessMaintenanceQueue(); 4991 }; 4992 4993 class DeleteFilesRunnable final : public Runnable { 4994 using ClientDirectoryLock = mozilla::dom::quota::ClientDirectoryLock; 4995 4996 enum State { 4997 // Just created on the PBackground thread. Next step is 4998 // State_DirectoryOpenPending. 4999 State_Initial, 5000 5001 // Waiting for directory open allowed on the main thread. The next step is 5002 // State_DatabaseWorkOpen. 5003 State_DirectoryOpenPending, 5004 5005 // Waiting to do/doing work on the QuotaManager IO thread. The next step is 5006 // State_UnblockingOpen. 5007 State_DatabaseWorkOpen, 5008 5009 // Notifying the QuotaManager that it can proceed to the next operation on 5010 // the main thread. Next step is State_Completed. 5011 State_UnblockingOpen, 5012 5013 // All done. 5014 State_Completed 5015 }; 5016 5017 nsCOMPtr<nsIEventTarget> mOwningEventTarget; 5018 SafeRefPtr<DatabaseFileManager> mFileManager; 5019 ClientDirectoryLockHandle mDirectoryLockHandle; 5020 nsTArray<int64_t> mFileIds; 5021 State mState; 5022 DEBUGONLY(bool mDEBUGCountsAsPending = false); 5023 5024 static uint64_t sPendingRunnables; 5025 5026 public: 5027 DeleteFilesRunnable(SafeRefPtr<DatabaseFileManager> aFileManager, 5028 nsTArray<int64_t>&& aFileIds); 5029 5030 void RunImmediately(); 5031 5032 static bool IsDeletionPending() { return sPendingRunnables > 0; } 5033 5034 private: 5035 #ifdef DEBUG 5036 ~DeleteFilesRunnable(); 5037 #else 5038 ~DeleteFilesRunnable() = default; 5039 #endif 5040 5041 void Open(); 5042 5043 void DoDatabaseWork(); 5044 5045 void Finish(); 5046 5047 void UnblockOpen(); 5048 5049 NS_DECL_NSIRUNNABLE 5050 5051 void DirectoryLockAcquired(ClientDirectoryLockHandle aLockHandle); 5052 5053 void DirectoryLockFailed(); 5054 }; 5055 5056 class Maintenance final : public Runnable { 5057 struct DirectoryInfo final { 5058 InitializedOnce<const OriginMetadata> mOriginMetadata; 5059 InitializedOnce<const nsTArray<nsString>> mDatabasePaths; 5060 const PersistenceType mPersistenceType; 5061 5062 DirectoryInfo(PersistenceType aPersistenceType, 5063 OriginMetadata aOriginMetadata, 5064 nsTArray<nsString>&& aDatabasePaths); 5065 5066 DirectoryInfo(const DirectoryInfo& aOther) = delete; 5067 DirectoryInfo(DirectoryInfo&& aOther) = delete; 5068 5069 ~DirectoryInfo() { MOZ_COUNT_DTOR(Maintenance::DirectoryInfo); } 5070 }; 5071 5072 enum class State { 5073 // Newly created on the PBackground thread. Will proceed immediately or be 5074 // added to the maintenance queue. The next step is either 5075 // DirectoryOpenPending if IndexedDatabaseManager is running, or 5076 // CreateIndexedDatabaseManager if not. 5077 Initial = 0, 5078 5079 // Create IndexedDatabaseManager on the main thread. The next step is either 5080 // Finishing if IndexedDatabaseManager initialization fails, or 5081 // IndexedDatabaseManagerOpen if initialization succeeds. 5082 CreateIndexedDatabaseManager, 5083 5084 // Call OpenDirectory() on the PBackground thread. The next step is 5085 // DirectoryOpenPending. 5086 IndexedDatabaseManagerOpen, 5087 5088 // Waiting for directory open allowed on the PBackground thread. The next 5089 // step is either Finishing if directory lock failed to acquire, or 5090 // DirectoryWorkOpen if directory lock is acquired. 5091 DirectoryOpenPending, 5092 5093 // Waiting to do/doing work on the QuotaManager IO thread. The next step is 5094 // BeginDatabaseMaintenance. 5095 DirectoryWorkOpen, 5096 5097 // Dispatching a runnable for each database on the PBackground thread. The 5098 // next state is either WaitingForDatabaseMaintenancesToComplete if at least 5099 // one runnable has been dispatched, or Finishing otherwise. 5100 BeginDatabaseMaintenance, 5101 5102 // Waiting for DatabaseMaintenance to finish on maintenance thread pool. 5103 // The next state is Finishing if the last runnable has finished. 5104 WaitingForDatabaseMaintenancesToComplete, 5105 5106 // Waiting to finish/finishing on the PBackground thread. The next step is 5107 // Completed. 5108 Finishing, 5109 5110 // All done. 5111 Complete 5112 }; 5113 5114 RefPtr<QuotaClient> mQuotaClient; 5115 MozPromiseHolder<BoolPromise> mPromiseHolder; 5116 PRTime mStartTime; 5117 RefPtr<UniversalDirectoryLock> mPendingDirectoryLock; 5118 // The directory lock is normally dropped by BeginDatabaseMaintenance, but if 5119 // something fails (in any method), the Finish method will do the cleanup. 5120 RefPtr<UniversalDirectoryLock> mDirectoryLock; 5121 nsTArray<nsCOMPtr<nsIRunnable>> mCompleteCallbacks; 5122 nsTArray<DirectoryInfo> mDirectoryInfos; 5123 nsTHashMap<nsStringHashKey, DatabaseMaintenance*> mDatabaseMaintenances; 5124 nsresult mResultCode; 5125 Atomic<bool> mAborted; 5126 bool mOpenStorageForAllRepositoriesFailed; 5127 State mState; 5128 5129 public: 5130 explicit Maintenance(QuotaClient* aQuotaClient) 5131 : Runnable("dom::indexedDB::Maintenance"), 5132 mQuotaClient(aQuotaClient), 5133 mStartTime(PR_Now()), 5134 mResultCode(NS_OK), 5135 mAborted(false), 5136 mOpenStorageForAllRepositoriesFailed(false), 5137 mState(State::Initial) { 5138 AssertIsOnBackgroundThread(); 5139 MOZ_ASSERT(aQuotaClient); 5140 MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient); 5141 MOZ_ASSERT(mStartTime); 5142 } 5143 5144 nsIEventTarget* BackgroundThread() const { 5145 MOZ_ASSERT(mQuotaClient); 5146 return mQuotaClient->BackgroundThread(); 5147 } 5148 5149 PRTime StartTime() const { return mStartTime; } 5150 5151 bool IsAborted() const { return mAborted; } 5152 5153 void RunImmediately() { 5154 MOZ_ASSERT(mState == State::Initial); 5155 5156 (void)this->Run(); 5157 } 5158 5159 RefPtr<BoolPromise> OnResults() { 5160 AssertIsOnBackgroundThread(); 5161 5162 return mPromiseHolder.Ensure(__func__); 5163 } 5164 5165 void Abort(); 5166 5167 void RegisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance); 5168 5169 void UnregisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance); 5170 5171 bool HasDatabaseMaintenances() const { return mDatabaseMaintenances.Count(); } 5172 5173 RefPtr<DatabaseMaintenance> GetDatabaseMaintenance( 5174 const nsAString& aDatabasePath) const { 5175 AssertIsOnBackgroundThread(); 5176 5177 return mDatabaseMaintenances.Get(aDatabasePath); 5178 } 5179 5180 void WaitForCompletion(nsIRunnable* aCallback) { 5181 AssertIsOnBackgroundThread(); 5182 MOZ_ASSERT(mDatabaseMaintenances.Count()); 5183 5184 mCompleteCallbacks.AppendElement(aCallback); 5185 } 5186 5187 void Stringify(nsACString& aResult) const; 5188 5189 private: 5190 ~Maintenance() override { 5191 MOZ_ASSERT(mState == State::Complete); 5192 MOZ_ASSERT(!mDatabaseMaintenances.Count()); 5193 } 5194 5195 // Runs on the PBackground thread. Checks if IndexedDatabaseManager is 5196 // running. Calls OpenDirectory() or dispatches to the main thread on which 5197 // CreateIndexedDatabaseManager() is called. 5198 nsresult Start(); 5199 5200 // Runs on the main thread. Once IndexedDatabaseManager is created it will 5201 // dispatch to the PBackground thread on which OpenDirectory() is called. 5202 nsresult CreateIndexedDatabaseManager(); 5203 5204 RefPtr<UniversalDirectoryLockPromise> OpenStorageDirectory( 5205 const PersistenceScope& aPersistenceScope, bool aInitializeOrigins); 5206 5207 // Runs on the PBackground thread. Once QuotaManager has given a lock it will 5208 // call DirectoryOpen(). 5209 nsresult OpenDirectory(); 5210 5211 // Runs on the PBackground thread. Dispatches to the QuotaManager I/O thread. 5212 nsresult DirectoryOpen(); 5213 5214 // Runs on the QuotaManager I/O thread. Once it finds databases it will 5215 // dispatch to the PBackground thread on which BeginDatabaseMaintenance() 5216 // is called. 5217 nsresult DirectoryWork(); 5218 5219 // Runs on the PBackground thread. It dispatches a runnable for each database. 5220 nsresult BeginDatabaseMaintenance(); 5221 5222 // Runs on the PBackground thread. Called when the maintenance is finished or 5223 // if any of above methods fails. 5224 void Finish(); 5225 5226 NS_DECL_NSIRUNNABLE 5227 5228 void DirectoryLockAcquired(UniversalDirectoryLock* aLock); 5229 5230 void DirectoryLockFailed(); 5231 }; 5232 5233 Maintenance::DirectoryInfo::DirectoryInfo(PersistenceType aPersistenceType, 5234 OriginMetadata aOriginMetadata, 5235 nsTArray<nsString>&& aDatabasePaths) 5236 : mOriginMetadata(std::move(aOriginMetadata)), 5237 mDatabasePaths(std::move(aDatabasePaths)), 5238 mPersistenceType(aPersistenceType) { 5239 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID); 5240 MOZ_ASSERT(!mOriginMetadata->mGroup.IsEmpty()); 5241 MOZ_ASSERT(!mOriginMetadata->mOrigin.IsEmpty()); 5242 #ifdef DEBUG 5243 MOZ_ASSERT(!mDatabasePaths->IsEmpty()); 5244 for (const nsAString& databasePath : *mDatabasePaths) { 5245 MOZ_ASSERT(!databasePath.IsEmpty()); 5246 } 5247 #endif 5248 5249 MOZ_COUNT_CTOR(Maintenance::DirectoryInfo); 5250 } 5251 5252 class DatabaseMaintenance final : public Runnable { 5253 // The minimum amount of time that has passed since the last vacuum before we 5254 // will attempt to analyze the database for fragmentation. 5255 static const PRTime kMinVacuumAge = 5256 PRTime(PR_USEC_PER_SEC) * 60 * 60 * 24 * 7; 5257 5258 // If the percent of database pages that are not in contiguous order is higher 5259 // than this percentage we will attempt a vacuum. 5260 static const int32_t kPercentUnorderedThreshold = 30; 5261 5262 // If the percent of file size growth since the last vacuum is higher than 5263 // this percentage we will attempt a vacuum. 5264 static const int32_t kPercentFileSizeGrowthThreshold = 10; 5265 5266 // The number of freelist pages beyond which we will favor an incremental 5267 // vacuum over a full vacuum. 5268 static const int32_t kMaxFreelistThreshold = 5; 5269 5270 // If the percent of unused file bytes in the database exceeds this percentage 5271 // then we will attempt a full vacuum. 5272 static const int32_t kPercentUnusedThreshold = 20; 5273 5274 enum class MaintenanceAction { Nothing = 0, IncrementalVacuum, FullVacuum }; 5275 5276 RefPtr<Maintenance> mMaintenance; 5277 // The directory lock is dropped in RunOnOwningThread which serves as a 5278 // cleanup method and is always called. 5279 RefPtr<ClientDirectoryLock> mDirectoryLock; 5280 const OriginMetadata mOriginMetadata; 5281 const nsString mDatabasePath; 5282 int64_t mDirectoryLockId; 5283 nsCOMPtr<nsIRunnable> mCompleteCallback; 5284 const PersistenceType mPersistenceType; 5285 const Maybe<CipherKey> mMaybeKey; 5286 Atomic<bool> mAborted; 5287 DataMutex<nsCOMPtr<mozIStorageConnection>> mSharedStorageConnection; 5288 5289 public: 5290 DatabaseMaintenance(Maintenance* aMaintenance, 5291 RefPtr<ClientDirectoryLock> aDirectoryLock, 5292 PersistenceType aPersistenceType, 5293 const OriginMetadata& aOriginMetadata, 5294 const nsAString& aDatabasePath, 5295 const Maybe<CipherKey>& aMaybeKey) 5296 : Runnable("dom::indexedDB::DatabaseMaintenance"), 5297 mMaintenance(aMaintenance), 5298 mDirectoryLock(std::move(aDirectoryLock)), 5299 mOriginMetadata(aOriginMetadata), 5300 mDatabasePath(aDatabasePath), 5301 mPersistenceType(aPersistenceType), 5302 mMaybeKey{aMaybeKey}, 5303 mAborted(false), 5304 mSharedStorageConnection("sharedStorageConnection") { 5305 MOZ_ASSERT(mDirectoryLock); 5306 5307 MOZ_ASSERT(mDirectoryLock->Id() >= 0); 5308 mDirectoryLockId = mDirectoryLock->Id(); 5309 } 5310 5311 const nsAString& DatabasePath() const { return mDatabasePath; } 5312 5313 void WaitForCompletion(nsIRunnable* aCallback) { 5314 AssertIsOnBackgroundThread(); 5315 MOZ_ASSERT(!mCompleteCallback); 5316 5317 mCompleteCallback = aCallback; 5318 } 5319 5320 void Stringify(nsACString& aResult) const; 5321 5322 nsresult Abort(); 5323 5324 private: 5325 ~DatabaseMaintenance() override = default; 5326 5327 // Runs on maintenance thread pool. Does maintenance on the database. 5328 void PerformMaintenanceOnDatabase(); 5329 5330 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase. 5331 nsresult CheckIntegrity(mozIStorageConnection& aConnection, bool* aOk); 5332 5333 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase. 5334 nsresult DetermineMaintenanceAction(mozIStorageConnection& aConnection, 5335 nsIFile* aDatabaseFile, 5336 MaintenanceAction* aMaintenanceAction); 5337 5338 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase. 5339 void IncrementalVacuum(mozIStorageConnection& aConnection); 5340 5341 // Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase. 5342 void FullVacuum(mozIStorageConnection& aConnection, nsIFile* aDatabaseFile); 5343 5344 // Runs on the PBackground thread. It dispatches a complete callback and 5345 // unregisters from Maintenance. 5346 void RunOnOwningThread(); 5347 5348 // Runs on maintenance thread pool. Once it performs database maintenance 5349 // it will dispatch to the PBackground thread on which RunOnOwningThread() 5350 // is called. 5351 void RunOnConnectionThread(); 5352 5353 // TODO: Could QuotaClient::IsShuttingDownOnNonBackgroundThread() call 5354 // be part of mMaintenance::IsAborted() ? 5355 inline bool IsAborted() const { 5356 return mMaintenance->IsAborted() || mAborted || 5357 NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()); 5358 } 5359 5360 NS_DECL_NSIRUNNABLE 5361 }; 5362 5363 #ifdef DEBUG 5364 5365 class DEBUGThreadSlower final : public nsIThreadObserver { 5366 public: 5367 DEBUGThreadSlower() { 5368 AssertIsOnBackgroundThread(); 5369 MOZ_ASSERT(kDEBUGThreadSleepMS); 5370 } 5371 5372 NS_DECL_ISUPPORTS 5373 5374 private: 5375 ~DEBUGThreadSlower() { AssertIsOnBackgroundThread(); } 5376 5377 NS_DECL_NSITHREADOBSERVER 5378 }; 5379 5380 #endif // DEBUG 5381 5382 /******************************************************************************* 5383 * Helper classes 5384 ******************************************************************************/ 5385 5386 // XXX Get rid of FileHelper and move the functions into DatabaseFileManager. 5387 // Then, DatabaseFileManager::Get(Journal)Directory and 5388 // DatabaseFileManager::GetFileForId might eventually be made private. 5389 class MOZ_STACK_CLASS FileHelper final { 5390 const SafeRefPtr<DatabaseFileManager> mFileManager; 5391 5392 LazyInitializedOnce<const NotNull<nsCOMPtr<nsIFile>>> mFileDirectory; 5393 LazyInitializedOnce<const NotNull<nsCOMPtr<nsIFile>>> mJournalDirectory; 5394 5395 class ReadCallback; 5396 LazyInitializedOnce<const NotNull<RefPtr<ReadCallback>>> mReadCallback; 5397 5398 public: 5399 explicit FileHelper(SafeRefPtr<DatabaseFileManager>&& aFileManager) 5400 : mFileManager(std::move(aFileManager)) { 5401 MOZ_ASSERT(mFileManager); 5402 } 5403 5404 nsresult Init(); 5405 5406 [[nodiscard]] nsCOMPtr<nsIFile> GetFile(const DatabaseFileInfo& aFileInfo); 5407 5408 [[nodiscard]] nsCOMPtr<nsIFile> GetJournalFile( 5409 const DatabaseFileInfo& aFileInfo); 5410 5411 nsresult CreateFileFromStream(nsIFile& aFile, nsIFile& aJournalFile, 5412 nsIInputStream& aInputStream, bool aCompress, 5413 const Maybe<CipherKey>& aMaybeKey); 5414 5415 private: 5416 nsresult SyncCopy(nsIInputStream& aInputStream, 5417 nsIOutputStream& aOutputStream, char* aBuffer, 5418 uint32_t aBufferSize); 5419 5420 nsresult SyncRead(nsIInputStream& aInputStream, char* aBuffer, 5421 uint32_t aBufferSize, uint32_t* aRead); 5422 }; 5423 5424 /******************************************************************************* 5425 * Helper Functions 5426 ******************************************************************************/ 5427 5428 bool GetFilenameBase(const nsAString& aFilename, const nsAString& aSuffix, 5429 nsDependentSubstring& aFilenameBase) { 5430 MOZ_ASSERT(!aFilename.IsEmpty()); 5431 MOZ_ASSERT(aFilenameBase.IsEmpty()); 5432 5433 if (!StringEndsWith(aFilename, aSuffix) || 5434 aFilename.Length() == aSuffix.Length()) { 5435 return false; 5436 } 5437 5438 MOZ_ASSERT(aFilename.Length() > aSuffix.Length()); 5439 5440 aFilenameBase.Rebind(aFilename, 0, aFilename.Length() - aSuffix.Length()); 5441 return true; 5442 } 5443 5444 class EncryptedFileBlobImpl final : public FileBlobImpl { 5445 public: 5446 EncryptedFileBlobImpl(const nsCOMPtr<nsIFile>& aNativeFile, 5447 const DatabaseFileInfo::IdType aId, 5448 const CipherKey& aKey) 5449 : FileBlobImpl{aNativeFile}, mKey{aKey} { 5450 SetFileId(aId); 5451 } 5452 5453 uint64_t GetSize(ErrorResult& aRv) override { 5454 nsCOMPtr<nsIInputStream> inputStream; 5455 CreateInputStream(getter_AddRefs(inputStream), aRv); 5456 5457 if (aRv.Failed()) { 5458 return 0; 5459 } 5460 5461 MOZ_ASSERT(inputStream); 5462 5463 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(inputStream, Available), 0, 5464 [&aRv](const nsresult rv) { aRv = rv; }); 5465 } 5466 5467 void CreateInputStream(nsIInputStream** aInputStream, 5468 ErrorResult& aRv) const override { 5469 nsCOMPtr<nsIInputStream> baseInputStream; 5470 FileBlobImpl::CreateInputStream(getter_AddRefs(baseInputStream), aRv); 5471 if (NS_WARN_IF(aRv.Failed())) { 5472 return; 5473 } 5474 5475 *aInputStream = 5476 MakeAndAddRef<DecryptingInputStream<IndexedDBCipherStrategy>>( 5477 WrapNotNull(std::move(baseInputStream)), kEncryptedStreamBlockSize, 5478 mKey) 5479 .take(); 5480 } 5481 5482 void GetBlobImplType(nsAString& aBlobImplType) const override { 5483 aBlobImplType = u"EncryptedFileBlobImpl"_ns; 5484 } 5485 5486 already_AddRefed<BlobImpl> CreateSlice(uint64_t aStart, uint64_t aLength, 5487 const nsAString& aContentType, 5488 ErrorResult& aRv) const override { 5489 MOZ_CRASH("Not implemented because this should be unreachable."); 5490 } 5491 5492 private: 5493 const CipherKey mKey; 5494 }; 5495 5496 RefPtr<BlobImpl> CreateFileBlobImpl(const Database& aDatabase, 5497 const nsCOMPtr<nsIFile>& aNativeFile, 5498 const DatabaseFileInfo::IdType aId) { 5499 if (aDatabase.IsInPrivateBrowsing()) { 5500 nsCString keyId; 5501 keyId.AppendInt(aId); 5502 5503 const auto& key = 5504 aDatabase.GetFileManager().MutableCipherKeyManagerRef().Get(keyId); 5505 5506 MOZ_RELEASE_ASSERT(key.isSome()); 5507 return MakeRefPtr<EncryptedFileBlobImpl>(aNativeFile, aId, *key); 5508 } 5509 5510 auto impl = MakeRefPtr<FileBlobImpl>(aNativeFile); 5511 impl->SetFileId(aId); 5512 5513 return impl; 5514 } 5515 5516 Result<nsTArray<SerializedStructuredCloneFile>, nsresult> 5517 SerializeStructuredCloneFiles(const SafeRefPtr<Database>& aDatabase, 5518 const nsTArray<StructuredCloneFileParent>& aFiles, 5519 bool aForPreprocess) { 5520 AssertIsOnBackgroundThread(); 5521 MOZ_ASSERT(aDatabase); 5522 5523 if (aFiles.IsEmpty()) { 5524 return nsTArray<SerializedStructuredCloneFile>{}; 5525 } 5526 5527 const nsCOMPtr<nsIFile> directory = 5528 aDatabase->GetFileManager().GetCheckedDirectory(); 5529 QM_TRY(OkIf(directory), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), 5530 IDB_REPORT_INTERNAL_ERR_LAMBDA); 5531 5532 nsTArray<SerializedStructuredCloneFile> serializedStructuredCloneFiles; 5533 QM_TRY(OkIf(serializedStructuredCloneFiles.SetCapacity(aFiles.Length(), 5534 fallible)), 5535 Err(NS_ERROR_OUT_OF_MEMORY)); 5536 5537 QM_TRY(TransformIfAbortOnErr( 5538 aFiles, MakeBackInserter(serializedStructuredCloneFiles), 5539 [aForPreprocess](const auto& file) { 5540 return !aForPreprocess || 5541 file.Type() == StructuredCloneFileBase::eStructuredClone; 5542 }, 5543 [&directory, &aDatabase, aForPreprocess]( 5544 const auto& file) -> Result<SerializedStructuredCloneFile, nsresult> { 5545 const int64_t fileId = file.FileInfo().Id(); 5546 MOZ_ASSERT(fileId > 0); 5547 5548 const nsCOMPtr<nsIFile> nativeFile = 5549 mozilla::dom::indexedDB::DatabaseFileManager::GetCheckedFileForId( 5550 directory, fileId); 5551 QM_TRY(OkIf(nativeFile), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), 5552 IDB_REPORT_INTERNAL_ERR_LAMBDA); 5553 5554 switch (file.Type()) { 5555 case StructuredCloneFileBase::eStructuredClone: 5556 if (!aForPreprocess) { 5557 return SerializedStructuredCloneFile{ 5558 null_t(), StructuredCloneFileBase::eStructuredClone}; 5559 } 5560 5561 [[fallthrough]]; 5562 5563 case StructuredCloneFileBase::eBlob: { 5564 const auto impl = CreateFileBlobImpl(*aDatabase, nativeFile, 5565 file.FileInfo().Id()); 5566 5567 IPCBlob ipcBlob; 5568 5569 // This can only fail if the child has crashed. 5570 QM_TRY(MOZ_TO_RESULT(IPCBlobUtils::Serialize(impl, ipcBlob)), 5571 Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), 5572 IDB_REPORT_INTERNAL_ERR_LAMBDA); 5573 5574 aDatabase->MapBlob(ipcBlob, file.FileInfoPtr()); 5575 5576 return SerializedStructuredCloneFile{ipcBlob, file.Type()}; 5577 } 5578 5579 case StructuredCloneFileBase::eMutableFile: 5580 case StructuredCloneFileBase::eWasmBytecode: 5581 case StructuredCloneFileBase::eWasmCompiled: { 5582 // Set file() to null, support for storing WebAssembly.Modules has 5583 // been removed in bug 1469395. Support for de-serialization of 5584 // WebAssembly.Modules modules has been removed in bug 1561876. 5585 // Support for MutableFile has been removed in bug 1500343. Full 5586 // removal is tracked in bug 1487479. 5587 5588 return SerializedStructuredCloneFile{null_t(), file.Type()}; 5589 } 5590 5591 default: 5592 MOZ_CRASH("Should never get here!"); 5593 } 5594 })); 5595 5596 return std::move(serializedStructuredCloneFiles); 5597 } 5598 5599 bool IsFileNotFoundError(const nsresult aRv) { 5600 return aRv == NS_ERROR_FILE_NOT_FOUND; 5601 } 5602 5603 enum struct Idempotency { Yes, No }; 5604 5605 // Delete a file, decreasing the quota usage as appropriate. If the file no 5606 // longer exists but aIdempotency is Idempotency::Yes, success is returned, 5607 // although quota usage can't be decreased. (With the assumption being that the 5608 // file was already deleted prior to this logic running, and the non-existent 5609 // file was no longer tracked by quota because it didn't exist at 5610 // initialization time or a previous deletion call updated the usage.) 5611 nsresult DeleteFile(nsIFile& aFile, QuotaManager* const aQuotaManager, 5612 const PersistenceType aPersistenceType, 5613 const OriginMetadata& aOriginMetadata, 5614 const Idempotency aIdempotency) { 5615 MOZ_ASSERT(!NS_IsMainThread()); 5616 MOZ_ASSERT(!IsOnBackgroundThread()); 5617 5618 // Callers which pass Idempotency::Yes call this function without checking if 5619 // the file already exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used 5620 // here since we just want to log NS_ERROR_FILE_NOT_FOUND results and not spam 5621 // the reports. 5622 // Theoretically, there should be no QM_OR_ELSE_(WARN|LOG_VERBOSE)_IF when a 5623 // caller passes Idempotency::No, but it's simpler when the predicate just 5624 // always returns false in that case. 5625 5626 const auto isIgnorableError = [&aIdempotency]() -> bool (*)(nsresult) { 5627 if (aIdempotency == Idempotency::Yes) { 5628 return IsFileNotFoundError; 5629 } 5630 5631 return [](const nsresult rv) { return false; }; 5632 }(); 5633 5634 QM_TRY_INSPECT( 5635 const auto& fileSize, 5636 ([aQuotaManager, &aFile, 5637 isIgnorableError]() -> Result<Maybe<int64_t>, nsresult> { 5638 if (aQuotaManager) { 5639 QM_TRY_INSPECT( 5640 const Maybe<int64_t>& fileSize, 5641 QM_OR_ELSE_LOG_VERBOSE_IF( 5642 // Expression. 5643 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize) 5644 .map([](const int64_t val) { return Some(val); }), 5645 // Predicate. 5646 isIgnorableError, 5647 // Fallback. 5648 ErrToDefaultOk<Maybe<int64_t>>)); 5649 5650 // XXX Can we really assert that the file size is not 0 if 5651 // it existed? This might be violated by external 5652 // influences. 5653 MOZ_ASSERT(!fileSize || fileSize.value() >= 0); 5654 5655 return fileSize; 5656 } 5657 5658 return Some(int64_t(0)); 5659 }())); 5660 5661 if (!fileSize) { 5662 return NS_OK; 5663 } 5664 5665 QM_TRY_INSPECT(const auto& didExist, 5666 QM_OR_ELSE_LOG_VERBOSE_IF( 5667 // Expression. 5668 MOZ_TO_RESULT(aFile.Remove(false)).map(Some<Ok>), 5669 // Predicate. 5670 isIgnorableError, 5671 // Fallback. 5672 ErrToDefaultOk<Maybe<Ok>>)); 5673 5674 if (!didExist) { 5675 // XXX If we get here, this means that the file still existed when we 5676 // queried its size, but no longer when we tried to remove it. Not sure if 5677 // this should really be silently accepted in idempotent mode. 5678 return NS_OK; 5679 } 5680 5681 if (fileSize.value() > 0) { 5682 MOZ_ASSERT(aQuotaManager); 5683 5684 aQuotaManager->DecreaseUsageForClient( 5685 ClientMetadata{aOriginMetadata, Client::IDB}, fileSize.value()); 5686 } 5687 5688 return NS_OK; 5689 } 5690 5691 nsresult DeleteFile(nsIFile& aDirectory, const nsAString& aFilename, 5692 QuotaManager* const aQuotaManager, 5693 const PersistenceType aPersistenceType, 5694 const OriginMetadata& aOriginMetadata, 5695 const Idempotency aIdempotent) { 5696 AssertIsOnIOThread(); 5697 MOZ_ASSERT(!aFilename.IsEmpty()); 5698 5699 QM_TRY_INSPECT(const auto& file, CloneFileAndAppend(aDirectory, aFilename)); 5700 5701 return DeleteFile(*file, aQuotaManager, aPersistenceType, aOriginMetadata, 5702 aIdempotent); 5703 } 5704 5705 // Delete files in a directory that you think exists. If the directory doesn't 5706 // exist, an error will not be returned, but warning telemetry will be 5707 // generated! So only call this on directories that you know exist (idempotent 5708 // usage, but it's not recommended). 5709 nsresult DeleteFilesNoQuota(nsIFile& aFile) { 5710 AssertIsOnIOThread(); 5711 5712 QM_TRY_INSPECT(const auto& didExist, 5713 QM_OR_ELSE_WARN_IF( 5714 // Expression. 5715 MOZ_TO_RESULT(aFile.Remove(true)).map(Some<Ok>), 5716 // Predicate. 5717 IsFileNotFoundError, 5718 // Fallback. 5719 ErrToDefaultOk<Maybe<Ok>>)); 5720 5721 (void)didExist; 5722 5723 return NS_OK; 5724 } 5725 5726 nsresult DeleteFilesNoQuota(nsIFile* aDirectory, const nsAString& aFilename) { 5727 AssertIsOnIOThread(); 5728 MOZ_ASSERT(aDirectory); 5729 MOZ_ASSERT(!aFilename.IsEmpty()); 5730 5731 // The current using function hasn't initialized the origin, so in here we 5732 // don't update the size of origin. Adding this assertion for preventing from 5733 // misusing. 5734 DebugOnly<QuotaManager*> quotaManager = QuotaManager::Get(); 5735 MOZ_ASSERT(!quotaManager->IsTemporaryStorageInitializedInternal()); 5736 5737 QM_TRY_INSPECT(const auto& file, CloneFileAndAppend(*aDirectory, aFilename)); 5738 5739 QM_TRY(MOZ_TO_RESULT(DeleteFilesNoQuota(*file))); 5740 5741 return NS_OK; 5742 } 5743 5744 // CreateMarkerFile and RemoveMarkerFile are a pair of functions to indicate 5745 // whether having removed all the files successfully. The marker file should 5746 // be checked before executing the next operation or initialization. 5747 Result<nsCOMPtr<nsIFile>, nsresult> CreateMarkerFile( 5748 nsIFile& aBaseDirectory, const nsAString& aDatabaseNameBase) { 5749 AssertIsOnIOThread(); 5750 MOZ_ASSERT(!aDatabaseNameBase.IsEmpty()); 5751 5752 QM_TRY_INSPECT( 5753 const auto& markerFile, 5754 CloneFileAndAppend(aBaseDirectory, 5755 kIdbDeletionMarkerFilePrefix + aDatabaseNameBase)); 5756 5757 // Callers call this function without checking if the file already exists 5758 // (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we just want 5759 // to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the reports. 5760 // 5761 // TODO: In theory if this file exists, then RemoveDatabaseFilesAndDirectory 5762 // should have cleaned it up, but obviously we can crash and not clean it up, 5763 // which is the whole point of the marker file. In that case, we'll realize 5764 // the marker file exists in OpenDatabaseOp::DoDatabaseWork or 5765 // GetUsageForOriginInternal and resume the removal by calling 5766 // RemoveDatabaseFilesAndDirectory again, but we will also try to create the 5767 // marker file again, so if we see this marker file, it is part 5768 // of our standard operating procedure to redundantly try and create the 5769 // marker here. We currently treat this as idempotent usage, but we could 5770 // add an additional argument to RemoveDatabaseFilesAndDirectory which would 5771 // indicate that we are resuming an unfinished removal, so the marker already 5772 // exists and doesn't have to be created, and change 5773 // QM_OR_ELSE_LOG_VERBOSE_IF to QM_OR_ELSE_WARN_IF in the end. 5774 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF( 5775 // Expression. 5776 MOZ_TO_RESULT(markerFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)), 5777 // Predicate. 5778 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>, 5779 // Fallback. 5780 ErrToDefaultOk<>)); 5781 5782 return markerFile; 5783 } 5784 5785 nsresult RemoveMarkerFile(nsIFile* aMarkerFile) { 5786 AssertIsOnIOThread(); 5787 MOZ_ASSERT(aMarkerFile); 5788 5789 DebugOnly<bool> exists; 5790 MOZ_ASSERT(NS_SUCCEEDED(aMarkerFile->Exists(&exists))); 5791 MOZ_ASSERT(exists); 5792 5793 QM_TRY(MOZ_TO_RESULT(aMarkerFile->Remove(false))); 5794 5795 return NS_OK; 5796 } 5797 5798 Result<Ok, nsresult> DeleteFileManagerDirectory( 5799 nsIFile& aFileManagerDirectory, QuotaManager* aQuotaManager, 5800 const PersistenceType aPersistenceType, 5801 const OriginMetadata& aOriginMetadata) { 5802 GECKO_TRACE_SCOPE("dom::indexedDB", "DeleteFileManagerDirectory"); 5803 5804 // XXX In theory, deleting can continue for other files in case of a failure, 5805 // leaving only those files behind that cause the problem actually. However, 5806 // the current architecture doesn't allow having more databases (for the same 5807 // name) on disk, so trying to delete as much as possible won't help much 5808 // because we need to delete entire .files directory in the end anyway. 5809 QM_TRY(DatabaseFileManager::TraverseFiles( 5810 aFileManagerDirectory, 5811 // KnownDirEntryOp 5812 [&aQuotaManager, aPersistenceType, &aOriginMetadata]( 5813 nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> { 5814 if (isDirectory) { 5815 // The journal directory doesn't count towards quota. 5816 QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file))); 5817 } 5818 5819 // Stored files do count towards quota. 5820 QM_TRY_RETURN( 5821 MOZ_TO_RESULT(DeleteFile(file, aQuotaManager, aPersistenceType, 5822 aOriginMetadata, Idempotency::Yes))); 5823 }, 5824 // UnknownDirEntryOp 5825 [aPersistenceType, &aOriginMetadata]( 5826 nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> { 5827 // Unknown files and directories don't count towards quota. 5828 5829 if (isDirectory) { 5830 QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file))); 5831 } 5832 5833 QM_TRY_RETURN(MOZ_TO_RESULT( 5834 DeleteFile(file, /* doesn't count */ nullptr, aPersistenceType, 5835 aOriginMetadata, Idempotency::Yes))); 5836 })); 5837 5838 QM_TRY_RETURN(MOZ_TO_RESULT(aFileManagerDirectory.Remove(false))); 5839 } 5840 5841 // Idempotently delete all the parts of an IndexedDB database including its 5842 // SQLite database file, its WAL journal, it's shared-memory file, and its 5843 // Blob/Files sub-directory. A marker file is created prior to performing the 5844 // deletion so that in the event we crash or fail to successfully delete the 5845 // database and its files, we will re-attempt the deletion the next time the 5846 // origin is initialized using this method. Because this means the method may be 5847 // called on a partially deleted database, this method uses DeleteFile which 5848 // succeeds when the file we ask it to delete does not actually exist. The 5849 // marker file is removed once deletion has successfully completed. 5850 nsresult RemoveDatabaseFilesAndDirectory(nsIFile& aBaseDirectory, 5851 const nsAString& aDatabaseFilenameBase, 5852 QuotaManager* aQuotaManager, 5853 const PersistenceType aPersistenceType, 5854 const OriginMetadata& aOriginMetadata, 5855 const nsAString& aDatabaseName) { 5856 GECKO_TRACE_SCOPE("dom::indexedDB", "RemoveDatabaseFilesAndDirectory"); 5857 5858 AssertIsOnIOThread(); 5859 MOZ_ASSERT(!aDatabaseFilenameBase.IsEmpty()); 5860 5861 AUTO_PROFILER_LABEL("RemoveDatabaseFilesAndDirectory", DOM); 5862 5863 QM_TRY_UNWRAP(auto markerFile, 5864 CreateMarkerFile(aBaseDirectory, aDatabaseFilenameBase)); 5865 5866 // The database file counts towards quota. 5867 QM_TRY(MOZ_TO_RESULT(DeleteFile( 5868 aBaseDirectory, aDatabaseFilenameBase + kSQLiteSuffix, aQuotaManager, 5869 aPersistenceType, aOriginMetadata, Idempotency::Yes))); 5870 5871 // .sqlite-journal files don't count towards quota. 5872 QM_TRY(MOZ_TO_RESULT(DeleteFile(aBaseDirectory, 5873 aDatabaseFilenameBase + kSQLiteJournalSuffix, 5874 /* doesn't count */ nullptr, aPersistenceType, 5875 aOriginMetadata, Idempotency::Yes))); 5876 5877 // .sqlite-shm files don't count towards quota. 5878 QM_TRY(MOZ_TO_RESULT(DeleteFile(aBaseDirectory, 5879 aDatabaseFilenameBase + kSQLiteSHMSuffix, 5880 /* doesn't count */ nullptr, aPersistenceType, 5881 aOriginMetadata, Idempotency::Yes))); 5882 5883 // .sqlite-wal files do count towards quota. 5884 QM_TRY(MOZ_TO_RESULT(DeleteFile( 5885 aBaseDirectory, aDatabaseFilenameBase + kSQLiteWALSuffix, aQuotaManager, 5886 aPersistenceType, aOriginMetadata, Idempotency::Yes))); 5887 5888 // The files directory counts towards quota. 5889 QM_TRY_INSPECT( 5890 const auto& fmDirectory, 5891 CloneFileAndAppend(aBaseDirectory, aDatabaseFilenameBase + 5892 kFileManagerDirectoryNameSuffix)); 5893 5894 QM_TRY_INSPECT(const bool& exists, 5895 MOZ_TO_RESULT_INVOKE_MEMBER(fmDirectory, Exists)); 5896 5897 if (exists) { 5898 QM_TRY_INSPECT(const bool& isDirectory, 5899 MOZ_TO_RESULT_INVOKE_MEMBER(fmDirectory, IsDirectory)); 5900 5901 QM_TRY(OkIf(isDirectory), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); 5902 5903 QM_TRY(DeleteFileManagerDirectory(*fmDirectory, aQuotaManager, 5904 aPersistenceType, aOriginMetadata)); 5905 } 5906 5907 IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); 5908 MOZ_ASSERT_IF(aQuotaManager, mgr); 5909 5910 if (mgr) { 5911 mgr->InvalidateFileManager(aPersistenceType, aOriginMetadata.mOrigin, 5912 aDatabaseName); 5913 } 5914 5915 QM_TRY(MOZ_TO_RESULT(RemoveMarkerFile(markerFile))); 5916 5917 return NS_OK; 5918 } 5919 5920 /******************************************************************************* 5921 * Globals 5922 ******************************************************************************/ 5923 5924 // Counts the number of "live" Factory, FactoryOp and Database instances. 5925 uint64_t gBusyCount = 0; 5926 5927 // We don't use LinkedList<CheckedUnsafePtr<FactoryOp>> because 5928 // CheckedUnsafePtr is not suitable for use within LinkedList. While it's 5929 // theoretically possible to adapt LinkedList to support it, doing so would 5930 // introduce unnecessary overhead. Instead, we use a simpler and more 5931 // efficient approach. Each FactoryOp instance asserts !isInList() in its 5932 // destructor to catch dangling pointer issues. 5933 using FactoryOpArray = LinkedList<FactoryOp>; 5934 5935 StaticAutoPtr<FactoryOpArray> gFactoryOps; 5936 5937 // Maps a database id to information about live database actors. 5938 using DatabaseActorHashtable = 5939 nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>; 5940 5941 StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable; 5942 5943 StaticRefPtr<ConnectionPool> gConnectionPool; 5944 5945 using DatabaseLoggingInfoHashtable = 5946 nsTHashMap<nsIDHashKey, DatabaseLoggingInfo*>; 5947 5948 StaticAutoPtr<DatabaseLoggingInfoHashtable> gLoggingInfoHashtable; 5949 5950 using TelemetryIdHashtable = nsTHashMap<nsUint32HashKey, uint32_t>; 5951 5952 StaticAutoPtr<TelemetryIdHashtable> gTelemetryIdHashtable; 5953 5954 // Protects all reads and writes to gTelemetryIdHashtable. 5955 StaticAutoPtr<Mutex> gTelemetryIdMutex; 5956 5957 // For private browsing, maps the raw database names provided by content to a 5958 // replacement UUID in order to avoid exposing the name of the database on 5959 // disk or a directly derived value, such as the non-private-browsing 5960 // representation. This mapping will be the same for all databases with the 5961 // same name across all storage keys/origins for the lifetime of the IDB 5962 // QuotaClient. In tests, the QuotaClient may be created and destroyed multiple 5963 // times, but for normal browser use the QuotaClient will last until the 5964 // browser shuts down. Bug 1831835 will improve this implementation to avoid 5965 // using the same mapping across storage keys and to deal with the resulting 5966 // lifecycle issues of the additional memory use. 5967 using StorageDatabaseNameHashtable = nsTHashMap<nsString, nsString>; 5968 5969 StaticAutoPtr<StorageDatabaseNameHashtable> gStorageDatabaseNameHashtable; 5970 5971 // Protects all reads and writes to gStorageDatabaseNameHashtable. 5972 StaticAutoPtr<Mutex> gStorageDatabaseNameMutex; 5973 5974 #ifdef DEBUG 5975 5976 StaticRefPtr<DEBUGThreadSlower> gDEBUGThreadSlower; 5977 5978 #endif // DEBUG 5979 5980 void IncreaseBusyCount() { 5981 AssertIsOnBackgroundThread(); 5982 5983 // If this is the first instance then we need to do some initialization. 5984 if (!gBusyCount) { 5985 MOZ_ASSERT(!gFactoryOps); 5986 gFactoryOps = new FactoryOpArray(); 5987 5988 MOZ_ASSERT(!gLiveDatabaseHashtable); 5989 gLiveDatabaseHashtable = new DatabaseActorHashtable(); 5990 5991 MOZ_ASSERT(!gLoggingInfoHashtable); 5992 gLoggingInfoHashtable = new DatabaseLoggingInfoHashtable(); 5993 5994 #ifdef DEBUG 5995 if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) { 5996 NS_WARNING( 5997 "PBackground thread debugging enabled, priority has been " 5998 "modified!"); 5999 nsCOMPtr<nsISupportsPriority> thread = 6000 do_QueryInterface(NS_GetCurrentThread()); 6001 MOZ_ASSERT(thread); 6002 6003 MOZ_ALWAYS_SUCCEEDS(thread->SetPriority(kDEBUGThreadPriority)); 6004 } 6005 6006 if (kDEBUGThreadSleepMS) { 6007 NS_WARNING( 6008 "PBackground thread debugging enabled, sleeping after every " 6009 "event!"); 6010 nsCOMPtr<nsIThreadInternal> thread = 6011 do_QueryInterface(NS_GetCurrentThread()); 6012 MOZ_ASSERT(thread); 6013 6014 gDEBUGThreadSlower = new DEBUGThreadSlower(); 6015 6016 MOZ_ALWAYS_SUCCEEDS(thread->AddObserver(gDEBUGThreadSlower)); 6017 } 6018 #endif // DEBUG 6019 } 6020 6021 gBusyCount++; 6022 } 6023 6024 void DecreaseBusyCount() { 6025 AssertIsOnBackgroundThread(); 6026 MOZ_ASSERT(gBusyCount); 6027 6028 // Clean up if there are no more instances. 6029 if (--gBusyCount == 0) { 6030 MOZ_ASSERT(gLoggingInfoHashtable); 6031 gLoggingInfoHashtable = nullptr; 6032 6033 MOZ_ASSERT(gLiveDatabaseHashtable); 6034 MOZ_ASSERT(!gLiveDatabaseHashtable->Count()); 6035 gLiveDatabaseHashtable = nullptr; 6036 6037 MOZ_ASSERT(gFactoryOps); 6038 MOZ_ASSERT(gFactoryOps->isEmpty()); 6039 gFactoryOps = nullptr; 6040 6041 #ifdef DEBUG 6042 if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) { 6043 nsCOMPtr<nsISupportsPriority> thread = 6044 do_QueryInterface(NS_GetCurrentThread()); 6045 MOZ_ASSERT(thread); 6046 6047 MOZ_ALWAYS_SUCCEEDS( 6048 thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL)); 6049 } 6050 6051 if (kDEBUGThreadSleepMS) { 6052 MOZ_ASSERT(gDEBUGThreadSlower); 6053 6054 nsCOMPtr<nsIThreadInternal> thread = 6055 do_QueryInterface(NS_GetCurrentThread()); 6056 MOZ_ASSERT(thread); 6057 6058 MOZ_ALWAYS_SUCCEEDS(thread->RemoveObserver(gDEBUGThreadSlower)); 6059 6060 gDEBUGThreadSlower = nullptr; 6061 } 6062 #endif // DEBUG 6063 } 6064 } 6065 6066 template <typename Condition> 6067 void InvalidateLiveDatabasesMatching(const Condition& aCondition) { 6068 AssertIsOnBackgroundThread(); 6069 6070 if (!gLiveDatabaseHashtable) { 6071 return; 6072 } 6073 6074 // Invalidating a Database will cause it to be removed from the 6075 // gLiveDatabaseHashtable entries' mLiveDatabases, and, if it was the last 6076 // element in mLiveDatabases, to remove the whole hashtable entry. Therefore, 6077 // we need to make a temporary list of the databases to invalidate to avoid 6078 // iterator invalidation. 6079 6080 nsTArray<SafeRefPtr<Database>> databases; 6081 6082 for (const auto& liveDatabasesEntry : gLiveDatabaseHashtable->Values()) { 6083 for (Database* const database : liveDatabasesEntry->mLiveDatabases) { 6084 if (aCondition(*database)) { 6085 databases.AppendElement( 6086 SafeRefPtr{database, AcquireStrongRefFromRawPtr{}}); 6087 } 6088 } 6089 } 6090 6091 for (const auto& database : databases) { 6092 database->Invalidate(); 6093 } 6094 } 6095 6096 uint32_t TelemetryIdForFile(nsIFile* aFile) { 6097 // May be called on any thread! 6098 6099 MOZ_ASSERT(aFile); 6100 MOZ_ASSERT(gTelemetryIdMutex); 6101 6102 // The storage directory is structured like this: 6103 // 6104 // <profile>/storage/<persistence>/<origin>/idb/<filename>.sqlite 6105 // 6106 // For the purposes of this function we're only concerned with the 6107 // <persistence>, <origin>, and <filename> pieces. 6108 6109 nsString filename; 6110 MOZ_ALWAYS_SUCCEEDS(aFile->GetLeafName(filename)); 6111 6112 // Make sure we were given a database file. 6113 MOZ_ASSERT(StringEndsWith(filename, kSQLiteSuffix)); 6114 6115 filename.Truncate(filename.Length() - kSQLiteSuffix.Length()); 6116 6117 // Get the "idb" directory. 6118 nsCOMPtr<nsIFile> idbDirectory; 6119 MOZ_ALWAYS_SUCCEEDS(aFile->GetParent(getter_AddRefs(idbDirectory))); 6120 6121 DebugOnly<nsString> idbLeafName; 6122 MOZ_ASSERT(NS_SUCCEEDED(idbDirectory->GetLeafName(idbLeafName))); 6123 MOZ_ASSERT(static_cast<nsString&>(idbLeafName).EqualsLiteral("idb")); 6124 6125 // Get the <origin> directory. 6126 nsCOMPtr<nsIFile> originDirectory; 6127 MOZ_ALWAYS_SUCCEEDS(idbDirectory->GetParent(getter_AddRefs(originDirectory))); 6128 6129 nsString origin; 6130 MOZ_ALWAYS_SUCCEEDS(originDirectory->GetLeafName(origin)); 6131 6132 // Any databases in these directories are owned by the application and should 6133 // not have their filenames masked. Hopefully they also appear in the 6134 // Telemetry.cpp whitelist. 6135 if (origin.EqualsLiteral("chrome") || 6136 origin.EqualsLiteral("moz-safe-about+home")) { 6137 return 0; 6138 } 6139 6140 // Get the <persistence> directory. 6141 nsCOMPtr<nsIFile> persistenceDirectory; 6142 MOZ_ALWAYS_SUCCEEDS( 6143 originDirectory->GetParent(getter_AddRefs(persistenceDirectory))); 6144 6145 nsString persistence; 6146 MOZ_ALWAYS_SUCCEEDS(persistenceDirectory->GetLeafName(persistence)); 6147 6148 constexpr auto separator = u"*"_ns; 6149 6150 uint32_t hashValue = 6151 HashString(persistence + separator + origin + separator + filename); 6152 6153 MutexAutoLock lock(*gTelemetryIdMutex); 6154 6155 if (!gTelemetryIdHashtable) { 6156 gTelemetryIdHashtable = new TelemetryIdHashtable(); 6157 } 6158 6159 return gTelemetryIdHashtable->LookupOrInsertWith(hashValue, [] { 6160 static uint32_t sNextId = 1; 6161 6162 // We're locked, no need for atomics. 6163 return sNextId++; 6164 }); 6165 } 6166 6167 nsAutoString GetDatabaseFilenameBase(const nsAString& aDatabaseName, 6168 bool aIsPrivate) { 6169 nsAutoString databaseFilenameBase; 6170 6171 if (aIsPrivate) { 6172 MOZ_DIAGNOSTIC_ASSERT(gStorageDatabaseNameMutex); 6173 6174 MutexAutoLock lock(*gStorageDatabaseNameMutex); 6175 6176 if (!gStorageDatabaseNameHashtable) { 6177 gStorageDatabaseNameHashtable = new StorageDatabaseNameHashtable(); 6178 } 6179 6180 databaseFilenameBase.Append( 6181 gStorageDatabaseNameHashtable->LookupOrInsertWith(aDatabaseName, []() { 6182 return NSID_TrimBracketsUTF16(nsID::GenerateUUID()); 6183 })); 6184 6185 return databaseFilenameBase; 6186 } 6187 6188 // WARNING: do not change this hash function. See the comment in HashName() 6189 // for details. 6190 databaseFilenameBase.AppendInt(HashName(aDatabaseName)); 6191 6192 nsAutoCString escapedName; 6193 if (!NS_Escape(NS_ConvertUTF16toUTF8(aDatabaseName), escapedName, 6194 url_XPAlphas)) { 6195 MOZ_CRASH("Can't escape database name!"); 6196 } 6197 6198 const char* forwardIter = escapedName.BeginReading(); 6199 const char* backwardIter = escapedName.EndReading() - 1; 6200 6201 nsAutoCString substring; 6202 while (forwardIter <= backwardIter && substring.Length() < 21) { 6203 if (substring.Length() % 2) { 6204 substring.Append(*backwardIter--); 6205 } else { 6206 substring.Append(*forwardIter++); 6207 } 6208 } 6209 6210 databaseFilenameBase.AppendASCII(substring.get(), substring.Length()); 6211 6212 return databaseFilenameBase; 6213 } 6214 6215 const CommonIndexOpenCursorParams& GetCommonIndexOpenCursorParams( 6216 const OpenCursorParams& aParams) { 6217 switch (aParams.type()) { 6218 case OpenCursorParams::TIndexOpenCursorParams: 6219 return aParams.get_IndexOpenCursorParams().commonIndexParams(); 6220 case OpenCursorParams::TIndexOpenKeyCursorParams: 6221 return aParams.get_IndexOpenKeyCursorParams().commonIndexParams(); 6222 default: 6223 MOZ_CRASH("Should never get here!"); 6224 } 6225 } 6226 6227 const CommonOpenCursorParams& GetCommonOpenCursorParams( 6228 const OpenCursorParams& aParams) { 6229 switch (aParams.type()) { 6230 case OpenCursorParams::TObjectStoreOpenCursorParams: 6231 return aParams.get_ObjectStoreOpenCursorParams().commonParams(); 6232 case OpenCursorParams::TObjectStoreOpenKeyCursorParams: 6233 return aParams.get_ObjectStoreOpenKeyCursorParams().commonParams(); 6234 case OpenCursorParams::TIndexOpenCursorParams: 6235 case OpenCursorParams::TIndexOpenKeyCursorParams: 6236 return GetCommonIndexOpenCursorParams(aParams).commonParams(); 6237 default: 6238 MOZ_CRASH("Should never get here!"); 6239 } 6240 } 6241 6242 // TODO: Using nsCString as a return type here seems to lead to a dependency on 6243 // some temporaries, which I did not expect. Is it a good idea that the default 6244 // operator+ behaviour constructs such strings? It is certainly useful as an 6245 // optimization, but this should be better done via an appropriately named 6246 // function rather than an operator. 6247 nsAutoCString MakeColumnPairSelectionList( 6248 const nsLiteralCString& aPlainColumnName, 6249 const nsLiteralCString& aLocaleAwareColumnName, 6250 const nsLiteralCString& aSortColumnAlias, const bool aIsLocaleAware) { 6251 return aPlainColumnName + 6252 (aIsLocaleAware ? EmptyCString() : " as "_ns + aSortColumnAlias) + 6253 ", "_ns + aLocaleAwareColumnName + 6254 (aIsLocaleAware ? " as "_ns + aSortColumnAlias : EmptyCString()); 6255 } 6256 6257 constexpr bool IsIncreasingOrder(const IDBCursorDirection aDirection) { 6258 MOZ_ASSERT(aDirection == IDBCursorDirection::Next || 6259 aDirection == IDBCursorDirection::Nextunique || 6260 aDirection == IDBCursorDirection::Prev || 6261 aDirection == IDBCursorDirection::Prevunique); 6262 6263 return aDirection == IDBCursorDirection::Next || 6264 aDirection == IDBCursorDirection::Nextunique; 6265 } 6266 6267 constexpr bool IsUnique(const IDBCursorDirection aDirection) { 6268 MOZ_ASSERT(aDirection == IDBCursorDirection::Next || 6269 aDirection == IDBCursorDirection::Nextunique || 6270 aDirection == IDBCursorDirection::Prev || 6271 aDirection == IDBCursorDirection::Prevunique); 6272 6273 return aDirection == IDBCursorDirection::Nextunique || 6274 aDirection == IDBCursorDirection::Prevunique; 6275 } 6276 6277 // TODO: In principle, this could be constexpr, if operator+(nsLiteralCString, 6278 // nsLiteralCString) were constexpr and returned a literal type. 6279 nsAutoCString MakeDirectionClause(const IDBCursorDirection aDirection) { 6280 return " ORDER BY "_ns + kColumnNameKey + 6281 (IsIncreasingOrder(aDirection) ? " ASC"_ns : " DESC"_ns); 6282 } 6283 6284 enum struct ComparisonOperator { 6285 LessThan, 6286 LessOrEquals, 6287 Equals, 6288 GreaterThan, 6289 GreaterOrEquals, 6290 }; 6291 6292 constexpr nsLiteralCString GetComparisonOperatorString( 6293 const ComparisonOperator aComparisonOperator) { 6294 switch (aComparisonOperator) { 6295 case ComparisonOperator::LessThan: 6296 return "<"_ns; 6297 case ComparisonOperator::LessOrEquals: 6298 return "<="_ns; 6299 case ComparisonOperator::Equals: 6300 return "=="_ns; 6301 case ComparisonOperator::GreaterThan: 6302 return ">"_ns; 6303 case ComparisonOperator::GreaterOrEquals: 6304 return ">="_ns; 6305 } 6306 6307 // TODO: This is just to silence the "control reaches end of non-void 6308 // function" warning. Cannot use MOZ_CRASH in a constexpr function, 6309 // unfortunately. 6310 return ""_ns; 6311 } 6312 6313 nsAutoCString GetKeyClause(const nsACString& aColumnName, 6314 const ComparisonOperator aComparisonOperator, 6315 const nsLiteralCString& aStmtParamName) { 6316 return aColumnName + " "_ns + 6317 GetComparisonOperatorString(aComparisonOperator) + " :"_ns + 6318 aStmtParamName; 6319 } 6320 6321 nsAutoCString GetSortKeyClause(const ComparisonOperator aComparisonOperator, 6322 const nsLiteralCString& aStmtParamName) { 6323 return GetKeyClause(kColumnNameAliasSortKey, aComparisonOperator, 6324 aStmtParamName); 6325 } 6326 6327 template <IDBCursorType CursorType> 6328 struct PopulateResponseHelper; 6329 6330 struct CommonPopulateResponseHelper { 6331 explicit CommonPopulateResponseHelper( 6332 const TransactionDatabaseOperationBase& aOp) 6333 : mOp{aOp} {} 6334 6335 nsresult GetKeys(mozIStorageStatement* const aStmt, 6336 Key* const aOptOutSortKey) { 6337 QM_TRY(MOZ_TO_RESULT(GetCommonKeys(aStmt))); 6338 6339 if (aOptOutSortKey) { 6340 *aOptOutSortKey = mPosition; 6341 } 6342 6343 return NS_OK; 6344 } 6345 6346 nsresult GetCommonKeys(mozIStorageStatement* const aStmt) { 6347 MOZ_ASSERT(mPosition.IsUnset()); 6348 6349 QM_TRY(MOZ_TO_RESULT(mPosition.SetFromStatement(aStmt, 0))); 6350 6351 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST( 6352 "PRELOAD: Populating response with key %s", "Populating%.0s", 6353 IDB_LOG_ID_STRING(mOp.BackgroundChildLoggingId()), 6354 mOp.TransactionLoggingSerialNumber(), mOp.LoggingSerialNumber(), 6355 mPosition.GetBuffer().get()); 6356 6357 return NS_OK; 6358 } 6359 6360 template <typename Response> 6361 void FillKeys(Response& aResponse) { 6362 MOZ_ASSERT(!mPosition.IsUnset()); 6363 aResponse.key() = std::move(mPosition); 6364 } 6365 6366 template <typename Response> 6367 static size_t GetKeySize(const Response& aResponse) { 6368 return aResponse.key().GetBuffer().Length(); 6369 } 6370 6371 protected: 6372 const Key& GetPosition() const { return mPosition; } 6373 6374 private: 6375 const TransactionDatabaseOperationBase& mOp; 6376 Key mPosition; 6377 }; 6378 6379 struct IndexPopulateResponseHelper : CommonPopulateResponseHelper { 6380 using CommonPopulateResponseHelper::CommonPopulateResponseHelper; 6381 6382 nsresult GetKeys(mozIStorageStatement* const aStmt, 6383 Key* const aOptOutSortKey) { 6384 MOZ_ASSERT(mLocaleAwarePosition.IsUnset()); 6385 MOZ_ASSERT(mObjectStorePosition.IsUnset()); 6386 6387 QM_TRY(MOZ_TO_RESULT(CommonPopulateResponseHelper::GetCommonKeys(aStmt))); 6388 6389 QM_TRY(MOZ_TO_RESULT(mLocaleAwarePosition.SetFromStatement(aStmt, 1))); 6390 6391 QM_TRY(MOZ_TO_RESULT(mObjectStorePosition.SetFromStatement(aStmt, 2))); 6392 6393 if (aOptOutSortKey) { 6394 *aOptOutSortKey = 6395 mLocaleAwarePosition.IsUnset() ? GetPosition() : mLocaleAwarePosition; 6396 } 6397 6398 return NS_OK; 6399 } 6400 6401 template <typename Response> 6402 void FillKeys(Response& aResponse) { 6403 MOZ_ASSERT(!mLocaleAwarePosition.IsUnset()); 6404 MOZ_ASSERT(!mObjectStorePosition.IsUnset()); 6405 6406 CommonPopulateResponseHelper::FillKeys(aResponse); 6407 aResponse.sortKey() = std::move(mLocaleAwarePosition); 6408 aResponse.objectKey() = std::move(mObjectStorePosition); 6409 } 6410 6411 template <typename Response> 6412 static size_t GetKeySize(Response& aResponse) { 6413 return CommonPopulateResponseHelper::GetKeySize(aResponse) + 6414 aResponse.sortKey().GetBuffer().Length() + 6415 aResponse.objectKey().GetBuffer().Length(); 6416 } 6417 6418 private: 6419 Key mLocaleAwarePosition, mObjectStorePosition; 6420 }; 6421 6422 struct KeyPopulateResponseHelper { 6423 static constexpr nsresult MaybeGetCloneInfo( 6424 mozIStorageStatement* const /*aStmt*/, const CursorBase& /*aCursor*/) { 6425 return NS_OK; 6426 } 6427 6428 template <typename Response> 6429 static constexpr void MaybeFillCloneInfo(Response& /*aResponse*/, 6430 FilesArray* const /*aFiles*/) {} 6431 6432 template <typename Response> 6433 static constexpr size_t MaybeGetCloneInfoSize(const Response& /*aResponse*/) { 6434 return 0; 6435 } 6436 }; 6437 6438 template <bool StatementHasIndexKeyBindings> 6439 struct ValuePopulateResponseHelper { 6440 nsresult MaybeGetCloneInfo(mozIStorageStatement* const aStmt, 6441 const ValueCursorBase& aCursor) { 6442 constexpr auto offset = StatementHasIndexKeyBindings ? 2 : 0; 6443 6444 QM_TRY_UNWRAP(auto cloneInfo, 6445 GetStructuredCloneReadInfoFromStatement( 6446 aStmt, 2 + offset, 1 + offset, *aCursor.mFileManager)); 6447 6448 mCloneInfo.init(std::move(cloneInfo)); 6449 6450 if (mCloneInfo->HasPreprocessInfo()) { 6451 IDB_WARNING("Preprocessing for cursors not yet implemented!"); 6452 return NS_ERROR_NOT_IMPLEMENTED; 6453 } 6454 6455 return NS_OK; 6456 } 6457 6458 template <typename Response> 6459 void MaybeFillCloneInfo(Response& aResponse, FilesArray* const aFiles) { 6460 auto cloneInfo = mCloneInfo.release(); 6461 aResponse.cloneInfo().data().data = cloneInfo.ReleaseData(); 6462 aFiles->AppendElement(cloneInfo.ReleaseFiles()); 6463 } 6464 6465 template <typename Response> 6466 static size_t MaybeGetCloneInfoSize(const Response& aResponse) { 6467 return aResponse.cloneInfo().data().data.Size(); 6468 } 6469 6470 private: 6471 LazyInitializedOnceEarlyDestructible<const StructuredCloneReadInfoParent> 6472 mCloneInfo; 6473 }; 6474 6475 template <> 6476 struct PopulateResponseHelper<IDBCursorType::ObjectStore> 6477 : ValuePopulateResponseHelper<false>, CommonPopulateResponseHelper { 6478 using CommonPopulateResponseHelper::CommonPopulateResponseHelper; 6479 6480 static auto& GetTypedResponse(CursorResponse* const aResponse) { 6481 return aResponse->get_ArrayOfObjectStoreCursorResponse(); 6482 } 6483 }; 6484 6485 template <> 6486 struct PopulateResponseHelper<IDBCursorType::ObjectStoreKey> 6487 : KeyPopulateResponseHelper, CommonPopulateResponseHelper { 6488 using CommonPopulateResponseHelper::CommonPopulateResponseHelper; 6489 6490 static auto& GetTypedResponse(CursorResponse* const aResponse) { 6491 return aResponse->get_ArrayOfObjectStoreKeyCursorResponse(); 6492 } 6493 }; 6494 6495 template <> 6496 struct PopulateResponseHelper<IDBCursorType::Index> 6497 : ValuePopulateResponseHelper<true>, IndexPopulateResponseHelper { 6498 using IndexPopulateResponseHelper::IndexPopulateResponseHelper; 6499 6500 static auto& GetTypedResponse(CursorResponse* const aResponse) { 6501 return aResponse->get_ArrayOfIndexCursorResponse(); 6502 } 6503 }; 6504 6505 template <> 6506 struct PopulateResponseHelper<IDBCursorType::IndexKey> 6507 : KeyPopulateResponseHelper, IndexPopulateResponseHelper { 6508 using IndexPopulateResponseHelper::IndexPopulateResponseHelper; 6509 6510 static auto& GetTypedResponse(CursorResponse* const aResponse) { 6511 return aResponse->get_ArrayOfIndexKeyCursorResponse(); 6512 } 6513 }; 6514 6515 nsresult DispatchAndReturnFileReferences( 6516 PersistenceType aPersistenceType, const nsACString& aOrigin, 6517 const nsAString& aDatabaseName, const int64_t aFileId, 6518 int32_t* const aMemRefCnt, int32_t* const aDBRefCnt, bool* const aResult) { 6519 AssertIsOnBackgroundThread(); 6520 MOZ_ASSERT(aMemRefCnt); 6521 MOZ_ASSERT(aDBRefCnt); 6522 MOZ_ASSERT(aResult); 6523 6524 *aResult = false; 6525 *aMemRefCnt = -1; 6526 *aDBRefCnt = -1; 6527 6528 mozilla::Monitor monitor MOZ_ANNOTATED(__func__); 6529 bool waiting = true; 6530 6531 auto lambda = [&] { 6532 AssertIsOnIOThread(); 6533 6534 { 6535 IndexedDatabaseManager* const mgr = IndexedDatabaseManager::Get(); 6536 MOZ_ASSERT(mgr); 6537 6538 const SafeRefPtr<DatabaseFileManager> fileManager = 6539 mgr->GetFileManager(aPersistenceType, aOrigin, aDatabaseName); 6540 6541 if (fileManager) { 6542 const SafeRefPtr<DatabaseFileInfo> fileInfo = 6543 fileManager->GetFileInfo(aFileId); 6544 6545 if (fileInfo) { 6546 fileInfo->GetReferences(aMemRefCnt, aDBRefCnt); 6547 6548 if (*aMemRefCnt != -1) { 6549 // We added an extra temp ref, so account for that accordingly. 6550 (*aMemRefCnt)--; 6551 } 6552 6553 *aResult = true; 6554 } 6555 } 6556 } 6557 6558 mozilla::MonitorAutoLock lock(monitor); 6559 MOZ_ASSERT(waiting); 6560 6561 waiting = false; 6562 lock.Notify(); 6563 }; 6564 6565 QuotaManager* const quotaManager = QuotaManager::Get(); 6566 MOZ_ASSERT(quotaManager); 6567 6568 // XXX can't we simply use NS_DispatchAndSpinEventLoopUntilComplete instead of 6569 // using a monitor? 6570 QM_TRY(MOZ_TO_RESULT(quotaManager->IOThread()->Dispatch( 6571 NS_NewRunnableFunction("GetFileReferences", std::move(lambda)), 6572 NS_DISPATCH_NORMAL))); 6573 6574 mozilla::MonitorAutoLock autolock(monitor); 6575 while (waiting) { 6576 autolock.Wait(); 6577 } 6578 6579 return NS_OK; 6580 } 6581 6582 class DeserializeIndexValueHelper final : public Runnable { 6583 public: 6584 DeserializeIndexValueHelper(int64_t aIndexID, const KeyPath& aKeyPath, 6585 bool aMultiEntry, const nsACString& aLocale, 6586 StructuredCloneReadInfoParent& aCloneReadInfo, 6587 nsTArray<IndexUpdateInfo>& aUpdateInfoArray) 6588 : Runnable("DeserializeIndexValueHelper"), 6589 mMonitor("DeserializeIndexValueHelper::mMonitor"), 6590 mIndexID(aIndexID), 6591 mKeyPath(aKeyPath), 6592 mMultiEntry(aMultiEntry), 6593 mLocale(aLocale), 6594 mCloneReadInfo(aCloneReadInfo), 6595 mUpdateInfoArray(aUpdateInfoArray), 6596 mStatus(NS_ERROR_FAILURE) {} 6597 6598 nsresult DispatchAndWait() { 6599 // FIXME(Bug 1637530) Re-enable optimization using a non-system-principaled 6600 // JS context 6601 #if 0 6602 // We don't need to go to the main-thread and use the sandbox. Let's create 6603 // the updateInfo data here. 6604 if (!mCloneReadInfo.Data().Size()) { 6605 AutoJSAPI jsapi; 6606 jsapi.Init(); 6607 6608 JS::Rooted<JS::Value> value(jsapi.cx()); 6609 value.setUndefined(); 6610 6611 ErrorResult rv; 6612 IDBObjectStore::AppendIndexUpdateInfo( 6613 mIndexID, mKeyPath, mMultiEntry, &mUpdateInfoArray, 6614 /* aAutoIncrementedObjectStoreKeyPath */ VoidString(), &rv); 6615 return rv.Failed() ? rv.StealNSResult() : NS_OK; 6616 } 6617 #endif 6618 6619 // The operation will continue on the main-thread. 6620 6621 MOZ_ASSERT(!(mCloneReadInfo.Data().Size() % sizeof(uint64_t))); 6622 6623 MonitorAutoLock lock(mMonitor); 6624 6625 RefPtr<Runnable> self = this; 6626 QM_TRY(MOZ_TO_RESULT(SchedulerGroup::Dispatch(self.forget()))); 6627 6628 lock.Wait(); 6629 return mStatus; 6630 } 6631 6632 NS_IMETHOD 6633 Run() override { 6634 MOZ_ASSERT(NS_IsMainThread()); 6635 6636 AutoJSAPI jsapi; 6637 jsapi.Init(); 6638 JSContext* const cx = jsapi.cx(); 6639 6640 JS::Rooted<JSObject*> global(cx, GetSandbox(cx)); 6641 6642 QM_TRY(OkIf(global), NS_OK, 6643 [this](const NotOk) { OperationCompleted(NS_ERROR_FAILURE); }); 6644 6645 const JSAutoRealm ar(cx, global); 6646 6647 JS::Rooted<JS::Value> value(cx); 6648 QM_TRY(MOZ_TO_RESULT(DeserializeIndexValue(cx, &value)), NS_OK, 6649 [this](const nsresult rv) { OperationCompleted(rv); }); 6650 6651 ErrorResult errorResult; 6652 IDBObjectStore::AppendIndexUpdateInfo( 6653 mIndexID, mKeyPath, mMultiEntry, mLocale, cx, value, &mUpdateInfoArray, 6654 /* aAutoIncrementedObjectStoreKeyPath */ VoidString(), &errorResult); 6655 QM_TRY(OkIf(!errorResult.Failed()), NS_OK, 6656 ([this, &errorResult](const NotOk) { 6657 OperationCompleted(errorResult.StealNSResult()); 6658 })); 6659 6660 OperationCompleted(NS_OK); 6661 return NS_OK; 6662 } 6663 6664 private: 6665 nsresult DeserializeIndexValue(JSContext* aCx, 6666 JS::MutableHandle<JS::Value> aValue) { 6667 static const JSStructuredCloneCallbacks callbacks = { 6668 StructuredCloneReadCallback<StructuredCloneReadInfoParent>, 6669 nullptr, 6670 nullptr, 6671 nullptr, 6672 nullptr, 6673 nullptr, 6674 nullptr, 6675 nullptr}; 6676 6677 if (!JS_ReadStructuredClone( 6678 aCx, mCloneReadInfo.Data(), JS_STRUCTURED_CLONE_VERSION, 6679 JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue, 6680 JS::CloneDataPolicy(), &callbacks, &mCloneReadInfo)) { 6681 return NS_ERROR_DOM_DATA_CLONE_ERR; 6682 } 6683 6684 return NS_OK; 6685 } 6686 6687 void OperationCompleted(nsresult aStatus) { 6688 mStatus = aStatus; 6689 6690 MonitorAutoLock lock(mMonitor); 6691 lock.Notify(); 6692 } 6693 6694 Monitor mMonitor MOZ_UNANNOTATED; 6695 6696 const int64_t mIndexID; 6697 const KeyPath& mKeyPath; 6698 const bool mMultiEntry; 6699 const nsCString mLocale; 6700 StructuredCloneReadInfoParent& mCloneReadInfo; 6701 nsTArray<IndexUpdateInfo>& mUpdateInfoArray; 6702 nsresult mStatus; 6703 }; 6704 6705 auto DeserializeIndexValueToUpdateInfos( 6706 int64_t aIndexID, const KeyPath& aKeyPath, bool aMultiEntry, 6707 const nsACString& aLocale, StructuredCloneReadInfoParent& aCloneReadInfo) { 6708 MOZ_ASSERT(!NS_IsMainThread()); 6709 6710 using ArrayType = AutoTArray<IndexUpdateInfo, 32>; 6711 using ResultType = Result<ArrayType, nsresult>; 6712 6713 ArrayType updateInfoArray; 6714 const auto helper = MakeRefPtr<DeserializeIndexValueHelper>( 6715 aIndexID, aKeyPath, aMultiEntry, aLocale, aCloneReadInfo, 6716 updateInfoArray); 6717 const nsresult rv = helper->DispatchAndWait(); 6718 return NS_FAILED(rv) ? Err(rv) : ResultType{std::move(updateInfoArray)}; 6719 } 6720 6721 bool IsSome( 6722 const Maybe<CachingDatabaseConnection::BorrowedStatement>& aMaybeStmt) { 6723 return aMaybeStmt.isSome(); 6724 } 6725 6726 already_AddRefed<nsIThreadPool> MakeConnectionIOTarget() { 6727 nsCOMPtr<nsIThreadPool> threadPool = new nsThreadPool(); 6728 6729 MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(kMaxConnectionThreadCount)); 6730 6731 MOZ_ALWAYS_SUCCEEDS( 6732 threadPool->SetIdleThreadLimit(kMaxIdleConnectionThreadCount)); 6733 6734 MOZ_ALWAYS_SUCCEEDS( 6735 threadPool->SetIdleThreadMaximumTimeout(kConnectionThreadMaxIdleMS)); 6736 6737 MOZ_ALWAYS_SUCCEEDS( 6738 threadPool->SetIdleThreadGraceTimeout(kConnectionThreadGraceIdleMS)); 6739 6740 MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("IndexedDB IO"_ns)); 6741 6742 return threadPool.forget(); 6743 } 6744 6745 } // namespace 6746 6747 /******************************************************************************* 6748 * Exported functions 6749 ******************************************************************************/ 6750 6751 already_AddRefed<PBackgroundIDBFactoryParent> AllocPBackgroundIDBFactoryParent( 6752 const LoggingInfo& aLoggingInfo, const nsACString& aSystemLocale) { 6753 AssertIsOnBackgroundThread(); 6754 6755 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { 6756 return nullptr; 6757 } 6758 6759 if (NS_AUUF_OR_WARN_IF(!aLoggingInfo.nextTransactionSerialNumber()) || 6760 NS_AUUF_OR_WARN_IF( 6761 !aLoggingInfo.nextVersionChangeTransactionSerialNumber()) || 6762 NS_AUUF_OR_WARN_IF(!aLoggingInfo.nextRequestSerialNumber())) { 6763 return nullptr; 6764 } 6765 6766 SafeRefPtr<Factory> actor = Factory::Create(aLoggingInfo, aSystemLocale); 6767 MOZ_ASSERT(actor); 6768 6769 return actor.forget(); 6770 } 6771 6772 bool RecvPBackgroundIDBFactoryConstructor( 6773 PBackgroundIDBFactoryParent* aActor, const LoggingInfo& /* aLoggingInfo */, 6774 const nsACString& /* aSystemLocale */) { 6775 AssertIsOnBackgroundThread(); 6776 MOZ_ASSERT(aActor); 6777 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 6778 6779 return true; 6780 } 6781 6782 PBackgroundIndexedDBUtilsParent* AllocPBackgroundIndexedDBUtilsParent() { 6783 AssertIsOnBackgroundThread(); 6784 6785 RefPtr<Utils> actor = new Utils(); 6786 6787 return actor.forget().take(); 6788 } 6789 6790 bool DeallocPBackgroundIndexedDBUtilsParent( 6791 PBackgroundIndexedDBUtilsParent* aActor) { 6792 AssertIsOnBackgroundThread(); 6793 MOZ_ASSERT(aActor); 6794 6795 RefPtr<Utils> actor = dont_AddRef(static_cast<Utils*>(aActor)); 6796 return true; 6797 } 6798 6799 bool RecvFlushPendingFileDeletions() { 6800 AssertIsOnBackgroundThread(); 6801 6802 if (QuotaClient* quotaClient = QuotaClient::GetInstance()) { 6803 QM_WARNONLY_TRY(QM_TO_RESULT(quotaClient->FlushPendingFileDeletions())); 6804 } 6805 6806 return true; 6807 } 6808 6809 RefPtr<mozilla::dom::quota::Client> CreateQuotaClient() { 6810 AssertIsOnBackgroundThread(); 6811 6812 return MakeRefPtr<QuotaClient>(); 6813 } 6814 6815 nsresult DatabaseFileManager::AsyncDeleteFile(int64_t aFileId) { 6816 AssertIsOnBackgroundThread(); 6817 MOZ_ASSERT(!ContainsFileInfo(aFileId)); 6818 6819 QuotaClient* quotaClient = QuotaClient::GetInstance(); 6820 if (quotaClient) { 6821 QM_TRY(MOZ_TO_RESULT(quotaClient->AsyncDeleteFile(this, aFileId))); 6822 } 6823 6824 return NS_OK; 6825 } 6826 6827 /******************************************************************************* 6828 * DatabaseConnection implementation 6829 ******************************************************************************/ 6830 6831 DatabaseConnection::DatabaseConnection( 6832 MovingNotNull<nsCOMPtr<mozIStorageConnection>> aStorageConnection, 6833 MovingNotNull<SafeRefPtr<DatabaseFileManager>> aFileManager) 6834 : CachingDatabaseConnection(std::move(aStorageConnection)), 6835 mFileManager(std::move(aFileManager)), 6836 mLastDurability(IDBTransaction::Durability::Default), 6837 mInReadTransaction(false), 6838 mInWriteTransaction(false) 6839 #ifdef DEBUG 6840 , 6841 mDEBUGSavepointCount(0) 6842 #endif 6843 { 6844 AssertIsOnConnectionThread(); 6845 MOZ_ASSERT(mFileManager); 6846 } 6847 6848 DatabaseConnection::~DatabaseConnection() { 6849 MOZ_ASSERT(!mFileManager); 6850 MOZ_ASSERT(!mUpdateRefcountFunction); 6851 MOZ_DIAGNOSTIC_ASSERT(!mInWriteTransaction); 6852 MOZ_ASSERT(!mDEBUGSavepointCount); 6853 } 6854 6855 nsresult DatabaseConnection::Init() { 6856 AssertIsOnConnectionThread(); 6857 MOZ_ASSERT(!mInReadTransaction); 6858 MOZ_ASSERT(!mInWriteTransaction); 6859 6860 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN;"_ns))); 6861 6862 mInReadTransaction = true; 6863 6864 return NS_OK; 6865 } 6866 6867 nsresult DatabaseConnection::BeginWriteTransaction( 6868 const IDBTransaction::Durability aDurability) { 6869 AssertIsOnConnectionThread(); 6870 MOZ_ASSERT(HasStorageConnection()); 6871 MOZ_ASSERT(mInReadTransaction); 6872 MOZ_ASSERT(!mInWriteTransaction); 6873 6874 AUTO_PROFILER_LABEL("DatabaseConnection::BeginWriteTransaction", DOM); 6875 6876 // Release our read locks. 6877 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("ROLLBACK;"_ns))); 6878 6879 mInReadTransaction = false; 6880 6881 if (mLastDurability != aDurability) { 6882 auto synchronousMode = [aDurability]() -> nsLiteralCString { 6883 switch (aDurability) { 6884 case IDBTransaction::Durability::Default: 6885 return GetDefaultSynchronousMode(); 6886 6887 case IDBTransaction::Durability::Strict: 6888 return "EXTRA"_ns; 6889 6890 case IDBTransaction::Durability::Relaxed: 6891 return "OFF"_ns; 6892 6893 default: 6894 MOZ_CRASH("Unknown CheckpointMode!"); 6895 } 6896 }(); 6897 6898 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("PRAGMA synchronous = "_ns + 6899 synchronousMode + ";"_ns))); 6900 6901 mLastDurability = aDurability; 6902 } 6903 6904 if (!mUpdateRefcountFunction) { 6905 MOZ_ASSERT(mFileManager); 6906 6907 RefPtr<UpdateRefcountFunction> function = 6908 new UpdateRefcountFunction(this, **mFileManager); 6909 6910 QM_TRY(MOZ_TO_RESULT(MutableStorageConnection().CreateFunction( 6911 "update_refcount"_ns, 6912 /* aNumArguments */ 2, function))); 6913 6914 mUpdateRefcountFunction = std::move(function); 6915 } 6916 6917 // This one cannot obviously use ExecuteCachedStatement because of the custom 6918 // error handling for Execute only. If only Execute can produce 6919 // NS_ERROR_STORAGE_BUSY, we could actually use ExecuteCachedStatement and 6920 // simplify this. 6921 QM_TRY_INSPECT(const auto& beginStmt, 6922 BorrowCachedStatement("BEGIN IMMEDIATE;"_ns)); 6923 6924 QM_TRY(QM_OR_ELSE_WARN_IF( 6925 // Expression. 6926 MOZ_TO_RESULT(beginStmt->Execute()), 6927 // Predicate. 6928 IsSpecificError<NS_ERROR_STORAGE_BUSY>, 6929 // Fallback. 6930 ([&beginStmt](nsresult rv) { 6931 NS_WARNING( 6932 "Received NS_ERROR_STORAGE_BUSY when attempting to start write " 6933 "transaction, retrying for up to 10 seconds"); 6934 6935 // Another thread must be using the database. Wait up to 10 seconds 6936 // for that to complete. 6937 const TimeStamp start = TimeStamp::NowLoRes(); 6938 6939 while (true) { 6940 PR_Sleep(PR_MillisecondsToInterval(100)); 6941 6942 rv = beginStmt->Execute(); 6943 if (rv != NS_ERROR_STORAGE_BUSY || 6944 TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) { 6945 break; 6946 } 6947 } 6948 6949 return MOZ_TO_RESULT(rv); 6950 }))); 6951 6952 mInWriteTransaction = true; 6953 6954 return NS_OK; 6955 } 6956 6957 nsresult DatabaseConnection::CommitWriteTransaction() { 6958 AssertIsOnConnectionThread(); 6959 MOZ_ASSERT(HasStorageConnection()); 6960 MOZ_ASSERT(!mInReadTransaction); 6961 MOZ_ASSERT(mInWriteTransaction); 6962 6963 AUTO_PROFILER_LABEL("DatabaseConnection::CommitWriteTransaction", DOM); 6964 6965 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("COMMIT;"_ns))); 6966 6967 mInWriteTransaction = false; 6968 return NS_OK; 6969 } 6970 6971 void DatabaseConnection::RollbackWriteTransaction() { 6972 AssertIsOnConnectionThread(); 6973 MOZ_ASSERT(!mInReadTransaction); 6974 MOZ_DIAGNOSTIC_ASSERT(HasStorageConnection()); 6975 6976 AUTO_PROFILER_LABEL("DatabaseConnection::RollbackWriteTransaction", DOM); 6977 6978 if (!mInWriteTransaction) { 6979 return; 6980 } 6981 6982 QM_WARNONLY_TRY( 6983 BorrowCachedStatement("ROLLBACK;"_ns) 6984 .andThen([&self = *this](const auto& stmt) -> Result<Ok, nsresult> { 6985 // This may fail if SQLite already rolled back the transaction 6986 // so ignore any errors. 6987 6988 // XXX ROLLBACK can fail quite normmally if a previous statement 6989 // failed to execute successfully so SQLite rolled back the 6990 // transaction already. However, if it failed because of some other 6991 // reason, we could try to close the connection. 6992 (void)stmt->Execute(); 6993 6994 self.mInWriteTransaction = false; 6995 return Ok{}; 6996 })); 6997 } 6998 6999 void DatabaseConnection::FinishWriteTransaction() { 7000 AssertIsOnConnectionThread(); 7001 MOZ_ASSERT(HasStorageConnection()); 7002 MOZ_ASSERT(!mInReadTransaction); 7003 MOZ_ASSERT(!mInWriteTransaction); 7004 7005 AUTO_PROFILER_LABEL("DatabaseConnection::FinishWriteTransaction", DOM); 7006 7007 if (mUpdateRefcountFunction) { 7008 mUpdateRefcountFunction->Reset(); 7009 } 7010 7011 QM_WARNONLY_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN;"_ns)) 7012 .andThen([&](const auto) -> Result<Ok, nsresult> { 7013 mInReadTransaction = true; 7014 return Ok{}; 7015 })); 7016 } 7017 7018 nsresult DatabaseConnection::StartSavepoint() { 7019 AssertIsOnConnectionThread(); 7020 MOZ_ASSERT(HasStorageConnection()); 7021 MOZ_ASSERT(mUpdateRefcountFunction); 7022 MOZ_ASSERT(mInWriteTransaction); 7023 7024 AUTO_PROFILER_LABEL("DatabaseConnection::StartSavepoint", DOM); 7025 7026 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement(SAVEPOINT_CLAUSE))); 7027 7028 mUpdateRefcountFunction->StartSavepoint(); 7029 7030 #ifdef DEBUG 7031 MOZ_ASSERT(mDEBUGSavepointCount < UINT32_MAX); 7032 mDEBUGSavepointCount++; 7033 #endif 7034 7035 return NS_OK; 7036 } 7037 7038 nsresult DatabaseConnection::ReleaseSavepoint() { 7039 AssertIsOnConnectionThread(); 7040 MOZ_ASSERT(HasStorageConnection()); 7041 MOZ_ASSERT(mUpdateRefcountFunction); 7042 MOZ_ASSERT(mInWriteTransaction); 7043 7044 AUTO_PROFILER_LABEL("DatabaseConnection::ReleaseSavepoint", DOM); 7045 7046 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("RELEASE "_ns SAVEPOINT_CLAUSE))); 7047 7048 mUpdateRefcountFunction->ReleaseSavepoint(); 7049 7050 #ifdef DEBUG 7051 MOZ_ASSERT(mDEBUGSavepointCount); 7052 mDEBUGSavepointCount--; 7053 #endif 7054 7055 return NS_OK; 7056 } 7057 7058 nsresult DatabaseConnection::RollbackSavepoint() { 7059 AssertIsOnConnectionThread(); 7060 MOZ_ASSERT(HasStorageConnection()); 7061 MOZ_ASSERT(mUpdateRefcountFunction); 7062 MOZ_ASSERT(mInWriteTransaction); 7063 7064 AUTO_PROFILER_LABEL("DatabaseConnection::RollbackSavepoint", DOM); 7065 7066 #ifdef DEBUG 7067 MOZ_ASSERT(mDEBUGSavepointCount); 7068 mDEBUGSavepointCount--; 7069 #endif 7070 7071 mUpdateRefcountFunction->RollbackSavepoint(); 7072 7073 QM_TRY_INSPECT(const auto& stmt, 7074 BorrowCachedStatement("ROLLBACK TO "_ns SAVEPOINT_CLAUSE)); 7075 7076 // This may fail if SQLite already rolled back the savepoint so ignore any 7077 // errors. 7078 (void)stmt->Execute(); 7079 7080 return NS_OK; 7081 } 7082 7083 nsresult DatabaseConnection::CheckpointInternal(CheckpointMode aMode) { 7084 AssertIsOnConnectionThread(); 7085 MOZ_ASSERT(!mInReadTransaction); 7086 MOZ_ASSERT(!mInWriteTransaction); 7087 7088 AUTO_PROFILER_LABEL("DatabaseConnection::CheckpointInternal", DOM); 7089 7090 nsAutoCString stmtString; 7091 stmtString.AssignLiteral("PRAGMA wal_checkpoint("); 7092 7093 switch (aMode) { 7094 case CheckpointMode::Full: 7095 // Ensures that the database is completely checkpointed and flushed to 7096 // disk. 7097 stmtString.AppendLiteral("FULL"); 7098 break; 7099 7100 case CheckpointMode::Restart: 7101 // Like Full, but also ensures that the next write will start overwriting 7102 // the existing WAL file rather than letting the WAL file grow. 7103 stmtString.AppendLiteral("RESTART"); 7104 break; 7105 7106 case CheckpointMode::Truncate: 7107 // Like Restart but also truncates the existing WAL file. 7108 stmtString.AppendLiteral("TRUNCATE"); 7109 break; 7110 7111 default: 7112 MOZ_CRASH("Unknown CheckpointMode!"); 7113 } 7114 7115 stmtString.AppendLiteral(");"); 7116 7117 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement(stmtString))); 7118 7119 return NS_OK; 7120 } 7121 7122 void DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint, 7123 const Atomic<bool>& aInterrupted) { 7124 AssertIsOnConnectionThread(); 7125 MOZ_ASSERT(mInReadTransaction); 7126 MOZ_ASSERT(!mInWriteTransaction); 7127 7128 AUTO_PROFILER_LABEL("DatabaseConnection::DoIdleProcessing", DOM); 7129 7130 CachingDatabaseConnection::CachedStatement freelistStmt; 7131 const uint32_t freelistCount = [this, &freelistStmt] { 7132 QM_TRY_RETURN(GetFreelistCount(freelistStmt), 0u); 7133 }(); 7134 7135 CachedStatement rollbackStmt; 7136 CachedStatement beginStmt; 7137 if (aNeedsCheckpoint || freelistCount) { 7138 QM_TRY_UNWRAP(rollbackStmt, GetCachedStatement("ROLLBACK;"_ns), QM_VOID); 7139 QM_TRY_UNWRAP(beginStmt, GetCachedStatement("BEGIN;"_ns), QM_VOID); 7140 7141 // Release the connection's normal transaction. It's possible that it could 7142 // fail, but that isn't a problem here. 7143 (void)rollbackStmt.Borrow()->Execute(); 7144 7145 mInReadTransaction = false; 7146 } 7147 7148 const bool freedSomePages = 7149 freelistCount && [this, &freelistStmt, &rollbackStmt, freelistCount, 7150 aNeedsCheckpoint, &aInterrupted] { 7151 // Warn in case of an error, but do not propagate it. Just indicate we 7152 // didn't free any pages. 7153 QM_TRY_INSPECT( 7154 const bool& res, 7155 ReclaimFreePagesWhileIdle(freelistStmt, rollbackStmt, freelistCount, 7156 aNeedsCheckpoint, aInterrupted), 7157 false); 7158 7159 // Make sure we didn't leave a transaction running. 7160 MOZ_ASSERT(!mInReadTransaction); 7161 MOZ_ASSERT(!mInWriteTransaction); 7162 7163 return res; 7164 }(); 7165 7166 // Truncate the WAL if we were asked to or if we managed to free some space. 7167 if (aNeedsCheckpoint || freedSomePages) { 7168 QM_WARNONLY_TRY(QM_TO_RESULT(CheckpointInternal(CheckpointMode::Truncate))); 7169 } 7170 7171 // Finally try to restart the read transaction if we rolled it back earlier. 7172 if (beginStmt) { 7173 QM_WARNONLY_TRY( 7174 MOZ_TO_RESULT(beginStmt.Borrow()->Execute()) 7175 .andThen([&self = *this](const Ok) -> Result<Ok, nsresult> { 7176 self.mInReadTransaction = true; 7177 return Ok{}; 7178 })); 7179 } 7180 } 7181 7182 Result<bool, nsresult> DatabaseConnection::ReclaimFreePagesWhileIdle( 7183 CachedStatement& aFreelistStatement, CachedStatement& aRollbackStatement, 7184 uint32_t aFreelistCount, bool aNeedsCheckpoint, 7185 const Atomic<bool>& aInterrupted) { 7186 AssertIsOnConnectionThread(); 7187 MOZ_ASSERT(aFreelistStatement); 7188 MOZ_ASSERT(aRollbackStatement); 7189 MOZ_ASSERT(aFreelistCount); 7190 MOZ_ASSERT(!mInReadTransaction); 7191 MOZ_ASSERT(!mInWriteTransaction); 7192 7193 AUTO_PROFILER_LABEL("DatabaseConnection::ReclaimFreePagesWhileIdle", DOM); 7194 7195 uint32_t pauseOnConnectionThreadMs = StaticPrefs:: 7196 dom_indexedDB_connectionIdleMaintenance_pauseOnConnectionThreadMs(); 7197 if (pauseOnConnectionThreadMs > 0) { 7198 PR_Sleep(PR_MillisecondsToInterval(pauseOnConnectionThreadMs)); 7199 } 7200 7201 // Make sure we don't keep working if anything else needs this thread. 7202 if (aInterrupted) { 7203 return false; 7204 } 7205 7206 // Make all the statements we'll need up front. 7207 7208 // Only try to free 10% at a time so that we can bail out if this connection 7209 // suddenly becomes active or if the thread is needed otherwise. 7210 QM_TRY_INSPECT( 7211 const auto& incrementalVacuumStmt, 7212 GetCachedStatement( 7213 "PRAGMA incremental_vacuum("_ns + 7214 IntToCString(std::max(uint64_t(1), uint64_t(aFreelistCount / 10))) + 7215 ");"_ns)); 7216 7217 QM_TRY_INSPECT(const auto& beginImmediateStmt, 7218 GetCachedStatement("BEGIN IMMEDIATE;"_ns)); 7219 7220 QM_TRY_INSPECT(const auto& commitStmt, GetCachedStatement("COMMIT;"_ns)); 7221 7222 if (aNeedsCheckpoint) { 7223 // Freeing pages is a journaled operation, so it will require additional WAL 7224 // space. However, we're idle and are about to checkpoint anyway, so doing a 7225 // RESTART checkpoint here should allow us to reuse any existing space. 7226 QM_TRY(MOZ_TO_RESULT(CheckpointInternal(CheckpointMode::Restart))); 7227 } 7228 7229 // Start the write transaction. 7230 QM_TRY(MOZ_TO_RESULT(beginImmediateStmt.Borrow()->Execute())); 7231 7232 mInWriteTransaction = true; 7233 7234 bool freedSomePages = false; 7235 7236 const auto rollback = [&aRollbackStatement, this](const auto&) { 7237 MOZ_ASSERT(mInWriteTransaction); 7238 7239 // Something failed, make sure we roll everything back. 7240 (void)aRollbackStatement.Borrow()->Execute(); 7241 7242 // XXX Is rollback infallible? Shouldn't we check the result? 7243 7244 mInWriteTransaction = false; 7245 }; 7246 7247 uint64_t previousFreelistCount = (uint64_t)aFreelistCount + 1; 7248 7249 QM_TRY(CollectWhile( 7250 [&aFreelistCount, &previousFreelistCount, 7251 &aInterrupted]() -> Result<bool, nsresult> { 7252 if (aInterrupted) { 7253 // On interrupt, abort and roll back this transaction. It's ok 7254 // if we never make progress here because the idle service 7255 // should eventually reclaim this space. 7256 return false; 7257 } 7258 // If we were not able to free anything, we might either see 7259 // a DB that has no auto-vacuum support at all or some other 7260 // (hopefully temporary) condition that prevents vacuum from 7261 // working. Just carry on in non-DEBUG. 7262 bool madeProgress = previousFreelistCount != aFreelistCount; 7263 previousFreelistCount = aFreelistCount; 7264 MOZ_ASSERT(madeProgress); 7265 QM_WARNONLY_TRY(MOZ_TO_RESULT(madeProgress)); 7266 return madeProgress && (aFreelistCount != 0); 7267 }, 7268 [&aFreelistStatement, &aFreelistCount, &incrementalVacuumStmt, 7269 &freedSomePages, this]() -> mozilla::Result<Ok, nsresult> { 7270 QM_TRY(MOZ_TO_RESULT(incrementalVacuumStmt.Borrow()->Execute())); 7271 7272 freedSomePages = true; 7273 7274 QM_TRY_UNWRAP(aFreelistCount, 7275 GetFreelistCount(aFreelistStatement)); 7276 7277 return Ok{}; 7278 }) 7279 .andThen([&commitStmt, &freedSomePages, &aInterrupted, &rollback, 7280 this](Ok) -> Result<Ok, nsresult> { 7281 if (aInterrupted) { 7282 rollback(Ok{}); 7283 freedSomePages = false; 7284 } 7285 7286 if (freedSomePages) { 7287 // Commit the write transaction. 7288 QM_TRY(MOZ_TO_RESULT(commitStmt.Borrow()->Execute()), 7289 QM_PROPAGATE, 7290 [](const auto&) { NS_WARNING("Failed to commit!"); }); 7291 7292 mInWriteTransaction = false; 7293 } 7294 7295 return Ok{}; 7296 }), 7297 QM_PROPAGATE, rollback); 7298 7299 return freedSomePages; 7300 } 7301 7302 Result<uint32_t, nsresult> DatabaseConnection::GetFreelistCount( 7303 CachedStatement& aCachedStatement) { 7304 AssertIsOnConnectionThread(); 7305 7306 AUTO_PROFILER_LABEL("DatabaseConnection::GetFreelistCount", DOM); 7307 7308 if (!aCachedStatement) { 7309 QM_TRY_UNWRAP(aCachedStatement, 7310 GetCachedStatement("PRAGMA freelist_count;"_ns)); 7311 } 7312 7313 const auto borrowedStatement = aCachedStatement.Borrow(); 7314 7315 QM_TRY_UNWRAP(const DebugOnly<bool> hasResult, 7316 MOZ_TO_RESULT_INVOKE_MEMBER(&*borrowedStatement, ExecuteStep)); 7317 7318 MOZ_ASSERT(hasResult); 7319 7320 QM_TRY_INSPECT(const int32_t& freelistCount, 7321 MOZ_TO_RESULT_INVOKE_MEMBER(*borrowedStatement, GetInt32, 0)); 7322 7323 MOZ_ASSERT(freelistCount >= 0); 7324 7325 return uint32_t(freelistCount); 7326 } 7327 7328 void DatabaseConnection::Close() { 7329 AssertIsOnConnectionThread(); 7330 MOZ_ASSERT(!mDEBUGSavepointCount); 7331 MOZ_DIAGNOSTIC_ASSERT(!mInWriteTransaction); 7332 7333 AUTO_PROFILER_LABEL("DatabaseConnection::Close", DOM); 7334 7335 if (mUpdateRefcountFunction) { 7336 MOZ_ALWAYS_SUCCEEDS( 7337 MutableStorageConnection().RemoveFunction("update_refcount"_ns)); 7338 mUpdateRefcountFunction = nullptr; 7339 } 7340 7341 CachingDatabaseConnection::Close(); 7342 7343 mFileManager.destroy(); 7344 } 7345 7346 nsresult DatabaseConnection::DisableQuotaChecks() { 7347 AssertIsOnConnectionThread(); 7348 MOZ_ASSERT(HasStorageConnection()); 7349 7350 if (!mQuotaObject) { 7351 MOZ_ASSERT(!mJournalQuotaObject); 7352 7353 QM_TRY(MOZ_TO_RESULT(MutableStorageConnection().GetQuotaObjects( 7354 getter_AddRefs(mQuotaObject), getter_AddRefs(mJournalQuotaObject)))); 7355 7356 MOZ_ASSERT(mQuotaObject); 7357 MOZ_ASSERT(mJournalQuotaObject); 7358 } 7359 7360 mQuotaObject->DisableQuotaCheck(); 7361 mJournalQuotaObject->DisableQuotaCheck(); 7362 7363 return NS_OK; 7364 } 7365 7366 void DatabaseConnection::EnableQuotaChecks() { 7367 AssertIsOnConnectionThread(); 7368 if (!mQuotaObject) { 7369 MOZ_ASSERT(!mJournalQuotaObject); 7370 7371 // DisableQuotaChecks failed earlier, so we don't need to enable quota 7372 // checks again. 7373 return; 7374 } 7375 7376 MOZ_ASSERT(mJournalQuotaObject); 7377 7378 const RefPtr<QuotaObject> quotaObject = std::move(mQuotaObject); 7379 const RefPtr<QuotaObject> journalQuotaObject = std::move(mJournalQuotaObject); 7380 7381 quotaObject->EnableQuotaCheck(); 7382 journalQuotaObject->EnableQuotaCheck(); 7383 7384 QM_TRY_INSPECT(const int64_t& fileSize, GetFileSize(quotaObject->Path()), 7385 QM_VOID); 7386 QM_TRY_INSPECT(const int64_t& journalFileSize, 7387 GetFileSize(journalQuotaObject->Path()), QM_VOID); 7388 7389 DebugOnly<bool> result = journalQuotaObject->MaybeUpdateSize( 7390 journalFileSize, /* aTruncate */ true); 7391 MOZ_ASSERT(result); 7392 7393 result = quotaObject->MaybeUpdateSize(fileSize, /* aTruncate */ true); 7394 MOZ_ASSERT(result); 7395 } 7396 7397 Result<int64_t, nsresult> DatabaseConnection::GetFileSize( 7398 const nsAString& aPath) { 7399 MOZ_ASSERT(!aPath.IsEmpty()); 7400 7401 QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath)); 7402 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists)); 7403 7404 if (exists) { 7405 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(file, GetFileSize)); 7406 } 7407 7408 return 0; 7409 } 7410 7411 DatabaseConnection::AutoSavepoint::AutoSavepoint() 7412 : mConnection(nullptr) 7413 #ifdef DEBUG 7414 , 7415 mDEBUGTransaction(nullptr) 7416 #endif 7417 { 7418 MOZ_COUNT_CTOR(DatabaseConnection::AutoSavepoint); 7419 } 7420 7421 DatabaseConnection::AutoSavepoint::~AutoSavepoint() { 7422 MOZ_COUNT_DTOR(DatabaseConnection::AutoSavepoint); 7423 7424 if (mConnection) { 7425 mConnection->AssertIsOnConnectionThread(); 7426 MOZ_ASSERT(mDEBUGTransaction); 7427 MOZ_ASSERT( 7428 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::ReadWrite || 7429 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush || 7430 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::Cleanup || 7431 mDEBUGTransaction->GetMode() == IDBTransaction::Mode::VersionChange); 7432 7433 QM_WARNONLY_TRY(QM_TO_RESULT(mConnection->RollbackSavepoint())); 7434 } 7435 } 7436 7437 nsresult DatabaseConnection::AutoSavepoint::Start( 7438 const TransactionBase& aTransaction) { 7439 MOZ_ASSERT(aTransaction.GetMode() == IDBTransaction::Mode::ReadWrite || 7440 aTransaction.GetMode() == IDBTransaction::Mode::ReadWriteFlush || 7441 aTransaction.GetMode() == IDBTransaction::Mode::Cleanup || 7442 aTransaction.GetMode() == IDBTransaction::Mode::VersionChange); 7443 7444 DatabaseConnection* connection = aTransaction.GetDatabase().GetConnection(); 7445 MOZ_ASSERT(connection); 7446 connection->AssertIsOnConnectionThread(); 7447 7448 // The previous operation failed to begin a write transaction and the 7449 // following opertion jumped to the connection thread before the previous 7450 // operation has updated its failure to the transaction. 7451 if (!connection->GetUpdateRefcountFunction()) { 7452 NS_WARNING( 7453 "The connection was closed because the previous operation " 7454 "failed!"); 7455 return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR; 7456 } 7457 7458 MOZ_ASSERT(!mConnection); 7459 MOZ_ASSERT(!mDEBUGTransaction); 7460 7461 QM_TRY(MOZ_TO_RESULT(connection->StartSavepoint())); 7462 7463 mConnection = connection; 7464 #ifdef DEBUG 7465 mDEBUGTransaction = &aTransaction; 7466 #endif 7467 7468 return NS_OK; 7469 } 7470 7471 nsresult DatabaseConnection::AutoSavepoint::Commit() { 7472 MOZ_ASSERT(mConnection); 7473 mConnection->AssertIsOnConnectionThread(); 7474 MOZ_ASSERT(mDEBUGTransaction); 7475 7476 QM_TRY(MOZ_TO_RESULT(mConnection->ReleaseSavepoint())); 7477 7478 mConnection = nullptr; 7479 #ifdef DEBUG 7480 mDEBUGTransaction = nullptr; 7481 #endif 7482 7483 return NS_OK; 7484 } 7485 7486 DatabaseConnection::UpdateRefcountFunction::UpdateRefcountFunction( 7487 DatabaseConnection* const aConnection, DatabaseFileManager& aFileManager) 7488 : mConnection(aConnection), 7489 mFileManager(aFileManager), 7490 mInSavepoint(false) { 7491 MOZ_ASSERT(aConnection); 7492 aConnection->AssertIsOnConnectionThread(); 7493 } 7494 7495 nsresult DatabaseConnection::UpdateRefcountFunction::WillCommit() { 7496 MOZ_ASSERT(mConnection); 7497 mConnection->AssertIsOnConnectionThread(); 7498 MOZ_ASSERT(mConnection->HasStorageConnection()); 7499 7500 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::WillCommit", 7501 DOM); 7502 7503 // The parameter names are not used, parameters are bound by index 7504 // only locally in the same function. 7505 auto update = 7506 [updateStatement = LazyStatement{*mConnection, 7507 "UPDATE file " 7508 "SET refcount = refcount + :delta " 7509 "WHERE id = :id"_ns}, 7510 selectStatement = LazyStatement{*mConnection, 7511 "SELECT id " 7512 "FROM file " 7513 "WHERE id = :id"_ns}, 7514 insertStatement = 7515 LazyStatement{ 7516 *mConnection, 7517 "INSERT INTO file (id, refcount) VALUES(:id, :delta)"_ns}, 7518 this](int64_t aId, int32_t aDelta) mutable -> Result<Ok, nsresult> { 7519 AUTO_PROFILER_LABEL( 7520 "DatabaseConnection::UpdateRefcountFunction::WillCommit::Update", DOM); 7521 { 7522 QM_TRY_INSPECT(const auto& borrowedUpdateStatement, 7523 updateStatement.Borrow()); 7524 7525 QM_TRY( 7526 MOZ_TO_RESULT(borrowedUpdateStatement->BindInt32ByIndex(0, aDelta))); 7527 QM_TRY(MOZ_TO_RESULT(borrowedUpdateStatement->BindInt64ByIndex(1, aId))); 7528 QM_TRY(MOZ_TO_RESULT(borrowedUpdateStatement->Execute())); 7529 } 7530 7531 QM_TRY_INSPECT( 7532 const int32_t& rows, 7533 MOZ_TO_RESULT_INVOKE_MEMBER(mConnection->MutableStorageConnection(), 7534 GetAffectedRows)); 7535 7536 if (rows > 0) { 7537 QM_TRY_INSPECT( 7538 const bool& hasResult, 7539 selectStatement 7540 .BorrowAndExecuteSingleStep( 7541 [aId](auto& stmt) -> Result<Ok, nsresult> { 7542 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, aId))); 7543 return Ok{}; 7544 }) 7545 .map(IsSome)); 7546 7547 if (!hasResult) { 7548 // Don't have to create the journal here, we can create all at once, 7549 // just before commit 7550 mJournalsToCreateBeforeCommit.AppendElement(aId); 7551 } 7552 7553 return Ok{}; 7554 } 7555 7556 QM_TRY_INSPECT(const auto& borrowedInsertStatement, 7557 insertStatement.Borrow()); 7558 7559 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->BindInt64ByIndex(0, aId))); 7560 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->BindInt32ByIndex(1, aDelta))); 7561 QM_TRY(MOZ_TO_RESULT(borrowedInsertStatement->Execute())); 7562 7563 mJournalsToRemoveAfterCommit.AppendElement(aId); 7564 7565 return Ok{}; 7566 }; 7567 7568 QM_TRY(CollectEachInRange( 7569 mFileInfoEntries, [&update](const auto& entry) -> Result<Ok, nsresult> { 7570 const auto delta = entry.GetData()->Delta(); 7571 if (delta) { 7572 QM_TRY(update(entry.GetKey(), delta)); 7573 } 7574 7575 return Ok{}; 7576 })); 7577 7578 QM_TRY(MOZ_TO_RESULT(CreateJournals())); 7579 7580 return NS_OK; 7581 } 7582 7583 void DatabaseConnection::UpdateRefcountFunction::DidCommit() { 7584 MOZ_ASSERT(mConnection); 7585 mConnection->AssertIsOnConnectionThread(); 7586 7587 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::DidCommit", 7588 DOM); 7589 7590 for (const auto& entry : mFileInfoEntries.Values()) { 7591 entry->MaybeUpdateDBRefs(); 7592 } 7593 7594 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveJournals(mJournalsToRemoveAfterCommit))); 7595 } 7596 7597 void DatabaseConnection::UpdateRefcountFunction::DidAbort() { 7598 MOZ_ASSERT(mConnection); 7599 mConnection->AssertIsOnConnectionThread(); 7600 7601 AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::DidAbort", 7602 DOM); 7603 7604 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveJournals(mJournalsToRemoveAfterAbort))); 7605 } 7606 7607 void DatabaseConnection::UpdateRefcountFunction::StartSavepoint() { 7608 MOZ_ASSERT(mConnection); 7609 mConnection->AssertIsOnConnectionThread(); 7610 MOZ_ASSERT(!mInSavepoint); 7611 MOZ_ASSERT(!mSavepointEntriesIndex.Count()); 7612 7613 mInSavepoint = true; 7614 } 7615 7616 void DatabaseConnection::UpdateRefcountFunction::ReleaseSavepoint() { 7617 MOZ_ASSERT(mConnection); 7618 mConnection->AssertIsOnConnectionThread(); 7619 MOZ_ASSERT(mInSavepoint); 7620 7621 mSavepointEntriesIndex.Clear(); 7622 mInSavepoint = false; 7623 } 7624 7625 void DatabaseConnection::UpdateRefcountFunction::RollbackSavepoint() { 7626 MOZ_ASSERT(mConnection); 7627 mConnection->AssertIsOnConnectionThread(); 7628 MOZ_ASSERT(!IsOnBackgroundThread()); 7629 MOZ_ASSERT(mInSavepoint); 7630 7631 for (const auto& entry : mSavepointEntriesIndex.Values()) { 7632 entry->DecBySavepointDelta(); 7633 } 7634 7635 mInSavepoint = false; 7636 mSavepointEntriesIndex.Clear(); 7637 } 7638 7639 void DatabaseConnection::UpdateRefcountFunction::Reset() { 7640 MOZ_ASSERT(mConnection); 7641 mConnection->AssertIsOnConnectionThread(); 7642 MOZ_ASSERT(!mSavepointEntriesIndex.Count()); 7643 MOZ_ASSERT(!mInSavepoint); 7644 7645 mJournalsToCreateBeforeCommit.Clear(); 7646 mJournalsToRemoveAfterCommit.Clear(); 7647 mJournalsToRemoveAfterAbort.Clear(); 7648 7649 // DatabaseFileInfo implementation automatically removes unreferenced files, 7650 // but it's done asynchronously and with a delay. We want to remove them (and 7651 // decrease quota usage) before we fire the commit event. 7652 for (const auto& entry : mFileInfoEntries.Values()) { 7653 // We need to move mFileInfo into a raw pointer in order to release it 7654 // explicitly with aSyncDeleteFile == true. 7655 DatabaseFileInfo* const fileInfo = entry->ReleaseFileInfo().forget().take(); 7656 MOZ_ASSERT(fileInfo); 7657 7658 fileInfo->Release(/* aSyncDeleteFile */ true); 7659 } 7660 7661 mFileInfoEntries.Clear(); 7662 } 7663 7664 nsresult DatabaseConnection::UpdateRefcountFunction::ProcessValue( 7665 mozIStorageValueArray* aValues, int32_t aIndex, UpdateType aUpdateType) { 7666 MOZ_ASSERT(mConnection); 7667 mConnection->AssertIsOnConnectionThread(); 7668 MOZ_ASSERT(aValues); 7669 7670 AUTO_PROFILER_LABEL( 7671 "DatabaseConnection::UpdateRefcountFunction::ProcessValue", DOM); 7672 7673 QM_TRY_INSPECT(const int32_t& type, 7674 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, aIndex)); 7675 7676 if (type == mozIStorageValueArray::VALUE_TYPE_NULL) { 7677 return NS_OK; 7678 } 7679 7680 QM_TRY_INSPECT(const auto& ids, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 7681 nsString, aValues, GetString, aIndex)); 7682 7683 QM_TRY_INSPECT(const auto& files, 7684 DeserializeStructuredCloneFiles(mFileManager, ids)); 7685 7686 for (const StructuredCloneFileParent& file : files) { 7687 const int64_t id = file.FileInfo().Id(); 7688 MOZ_ASSERT(id > 0); 7689 7690 const auto entry = 7691 WrapNotNull(mFileInfoEntries.GetOrInsertNew(id, file.FileInfoPtr())); 7692 7693 if (mInSavepoint) { 7694 mSavepointEntriesIndex.InsertOrUpdate(id, entry); 7695 } 7696 7697 switch (aUpdateType) { 7698 case UpdateType::Increment: 7699 entry->IncDeltas(mInSavepoint); 7700 break; 7701 case UpdateType::Decrement: 7702 entry->DecDeltas(mInSavepoint); 7703 break; 7704 default: 7705 MOZ_CRASH("Unknown update type!"); 7706 } 7707 } 7708 7709 return NS_OK; 7710 } 7711 7712 nsresult DatabaseConnection::UpdateRefcountFunction::CreateJournals() { 7713 MOZ_ASSERT(mConnection); 7714 mConnection->AssertIsOnConnectionThread(); 7715 7716 AUTO_PROFILER_LABEL( 7717 "DatabaseConnection::UpdateRefcountFunction::CreateJournals", DOM); 7718 7719 const nsCOMPtr<nsIFile> journalDirectory = mFileManager.GetJournalDirectory(); 7720 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE); 7721 7722 for (const int64_t id : mJournalsToCreateBeforeCommit) { 7723 const nsCOMPtr<nsIFile> file = 7724 DatabaseFileManager::GetFileForId(journalDirectory, id); 7725 QM_TRY(OkIf(file), NS_ERROR_FAILURE); 7726 7727 QM_TRY(MOZ_TO_RESULT(file->Create(nsIFile::NORMAL_FILE_TYPE, 0644))); 7728 7729 mJournalsToRemoveAfterAbort.AppendElement(id); 7730 } 7731 7732 return NS_OK; 7733 } 7734 7735 nsresult DatabaseConnection::UpdateRefcountFunction::RemoveJournals( 7736 const nsTArray<int64_t>& aJournals) { 7737 MOZ_ASSERT(mConnection); 7738 mConnection->AssertIsOnConnectionThread(); 7739 7740 AUTO_PROFILER_LABEL( 7741 "DatabaseConnection::UpdateRefcountFunction::RemoveJournals", DOM); 7742 7743 nsCOMPtr<nsIFile> journalDirectory = mFileManager.GetJournalDirectory(); 7744 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE); 7745 7746 for (const auto& journal : aJournals) { 7747 nsCOMPtr<nsIFile> file = 7748 DatabaseFileManager::GetFileForId(journalDirectory, journal); 7749 QM_TRY(OkIf(file), NS_ERROR_FAILURE); 7750 7751 QM_WARNONLY_TRY(QM_TO_RESULT(file->Remove(false))); 7752 } 7753 7754 return NS_OK; 7755 } 7756 7757 NS_IMPL_ISUPPORTS(DatabaseConnection::UpdateRefcountFunction, 7758 mozIStorageFunction) 7759 7760 NS_IMETHODIMP 7761 DatabaseConnection::UpdateRefcountFunction::OnFunctionCall( 7762 mozIStorageValueArray* aValues, nsIVariant** _retval) { 7763 MOZ_ASSERT(aValues); 7764 MOZ_ASSERT(_retval); 7765 7766 AUTO_PROFILER_LABEL( 7767 "DatabaseConnection::UpdateRefcountFunction::OnFunctionCall", DOM); 7768 7769 #ifdef DEBUG 7770 { 7771 QM_TRY_INSPECT(const uint32_t& numEntries, 7772 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetNumEntries), 7773 QM_ASSERT_UNREACHABLE); 7774 7775 MOZ_ASSERT(numEntries == 2); 7776 7777 QM_TRY_INSPECT(const int32_t& type1, 7778 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 0), 7779 QM_ASSERT_UNREACHABLE); 7780 7781 QM_TRY_INSPECT(const int32_t& type2, 7782 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 1), 7783 QM_ASSERT_UNREACHABLE); 7784 7785 MOZ_ASSERT(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL && 7786 type2 == mozIStorageValueArray::VALUE_TYPE_NULL)); 7787 } 7788 #endif 7789 7790 QM_TRY(MOZ_TO_RESULT(ProcessValue(aValues, 0, UpdateType::Decrement))); 7791 7792 QM_TRY(MOZ_TO_RESULT(ProcessValue(aValues, 1, UpdateType::Increment))); 7793 7794 return NS_OK; 7795 } 7796 7797 /******************************************************************************* 7798 * ConnectionPool implementation 7799 ******************************************************************************/ 7800 7801 ConnectionPool::ConnectionPool() 7802 : mDatabasesMutex("ConnectionPool::mDatabasesMutex"), 7803 mIOTarget(MakeConnectionIOTarget()), 7804 mIdleTimer(NS_NewTimer()), 7805 mNextTransactionId(0) { 7806 AssertIsOnOwningThread(); 7807 AssertIsOnBackgroundThread(); 7808 MOZ_ASSERT(mIdleTimer); 7809 } 7810 7811 ConnectionPool::~ConnectionPool() { 7812 AssertIsOnOwningThread(); 7813 MOZ_ASSERT(mIdleDatabases.IsEmpty()); 7814 MOZ_ASSERT(!mIdleTimer); 7815 MOZ_ASSERT(mTargetIdleTime.IsNull()); 7816 MOZ_ASSERT(!mDatabases.Count()); 7817 MOZ_ASSERT(!mTransactions.Count()); 7818 MOZ_ASSERT(mQueuedTransactions.IsEmpty()); 7819 MOZ_ASSERT(mCompleteCallbacks.IsEmpty()); 7820 MOZ_ASSERT(mShutdownRequested); 7821 MOZ_ASSERT(mShutdownComplete); 7822 } 7823 7824 // static 7825 void ConnectionPool::IdleTimerCallback(nsITimer* aTimer, void* aClosure) { 7826 MOZ_ASSERT(aTimer); 7827 MOZ_ASSERT(aClosure); 7828 7829 AUTO_PROFILER_LABEL("ConnectionPool::IdleTimerCallback", DOM); 7830 7831 auto& self = *static_cast<ConnectionPool*>(aClosure); 7832 MOZ_ASSERT(self.mIdleTimer); 7833 MOZ_ASSERT(SameCOMIdentity(self.mIdleTimer, aTimer)); 7834 MOZ_ASSERT(!self.mTargetIdleTime.IsNull()); 7835 7836 self.mTargetIdleTime = TimeStamp(); 7837 7838 // Cheat a little. 7839 const TimeStamp now = 7840 TimeStamp::NowLoRes() + TimeDuration::FromMilliseconds(500); 7841 7842 // XXX Move this to ArrayAlgorithm.h? 7843 const auto removeUntil = [](auto& array, auto&& cond) { 7844 const auto begin = array.begin(), end = array.end(); 7845 array.RemoveElementsRange( 7846 begin, std::find_if(begin, end, std::forward<decltype(cond)>(cond))); 7847 }; 7848 7849 removeUntil(self.mIdleDatabases, [now, &self](const auto& info) { 7850 if (now >= info.mIdleTime) { 7851 if ((*info.mDatabaseInfo)->mIdle) { 7852 self.PerformIdleDatabaseMaintenance(*info.mDatabaseInfo.ref()); 7853 } else { 7854 self.CloseDatabase(*info.mDatabaseInfo.ref()); 7855 } 7856 7857 return false; 7858 } 7859 7860 return true; 7861 }); 7862 7863 self.AdjustIdleTimer(); 7864 } 7865 7866 Result<RefPtr<DatabaseConnection>, nsresult> 7867 ConnectionPool::GetOrCreateConnection(const Database& aDatabase) { 7868 MOZ_ASSERT(!NS_IsMainThread()); 7869 MOZ_ASSERT(!IsOnBackgroundThread()); 7870 7871 AUTO_PROFILER_LABEL("ConnectionPool::GetOrCreateConnection", DOM); 7872 7873 DatabaseInfo* dbInfo; 7874 { 7875 MutexAutoLock lock(mDatabasesMutex); 7876 7877 dbInfo = mDatabases.Get(aDatabase.Id()); 7878 } 7879 7880 MOZ_ASSERT(dbInfo); 7881 7882 if (dbInfo->mConnection) { 7883 dbInfo->AssertIsOnConnectionThread(); 7884 7885 return dbInfo->mConnection; 7886 } 7887 7888 MOZ_ASSERT(!dbInfo->mDEBUGConnectionEventTarget); 7889 7890 QM_TRY_UNWRAP( 7891 MovingNotNull<nsCOMPtr<mozIStorageConnection>> storageConnection, 7892 GetStorageConnection(aDatabase.FilePath(), aDatabase.DirectoryLockId(), 7893 aDatabase.TelemetryId(), aDatabase.MaybeKeyRef())); 7894 7895 RefPtr<DatabaseConnection> connection = new DatabaseConnection( 7896 std::move(storageConnection), aDatabase.GetFileManagerPtr()); 7897 7898 QM_TRY(MOZ_TO_RESULT(connection->Init())); 7899 7900 dbInfo->mConnection = connection; 7901 7902 IDB_DEBUG_LOG(("ConnectionPool created connection 0x%p for '%s'", 7903 dbInfo->mConnection.get(), 7904 NS_ConvertUTF16toUTF8(aDatabase.FilePath()).get())); 7905 7906 #ifdef DEBUG 7907 dbInfo->mDEBUGConnectionEventTarget = GetCurrentSerialEventTarget(); 7908 #endif 7909 7910 return connection; 7911 } 7912 7913 uint64_t ConnectionPool::Start( 7914 const nsID& aBackgroundChildLoggingId, const nsACString& aDatabaseId, 7915 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames, 7916 bool aIsWriteTransaction, 7917 TransactionDatabaseOperationBase* aTransactionOp) { 7918 AssertIsOnOwningThread(); 7919 MOZ_ASSERT(!aDatabaseId.IsEmpty()); 7920 MOZ_ASSERT(mNextTransactionId < UINT64_MAX); 7921 MOZ_ASSERT(!mShutdownRequested); 7922 7923 AUTO_PROFILER_LABEL("ConnectionPool::Start", DOM); 7924 7925 const uint64_t transactionId = ++mNextTransactionId; 7926 7927 // To avoid always acquiring a lock, we don't use WithEntryHandle here, which 7928 // would require a lock in any case. 7929 DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId); 7930 7931 const bool databaseInfoIsNew = !dbInfo; 7932 7933 if (databaseInfoIsNew) { 7934 MutexAutoLock lock(mDatabasesMutex); 7935 7936 dbInfo = mDatabases 7937 .InsertOrUpdate(aDatabaseId, 7938 MakeUnique<DatabaseInfo>(this, aDatabaseId)) 7939 .get(); 7940 } 7941 7942 MOZ_ASSERT(!mTransactions.Contains(transactionId)); 7943 auto& transactionInfo = *mTransactions.InsertOrUpdate( 7944 transactionId, MakeUnique<TransactionInfo>( 7945 *dbInfo, aBackgroundChildLoggingId, aDatabaseId, 7946 transactionId, aLoggingSerialNumber, aObjectStoreNames, 7947 aIsWriteTransaction, aTransactionOp)); 7948 7949 if (aIsWriteTransaction) { 7950 MOZ_ASSERT(dbInfo->mWriteTransactionCount < UINT32_MAX); 7951 dbInfo->mWriteTransactionCount++; 7952 } else { 7953 MOZ_ASSERT(dbInfo->mReadTransactionCount < UINT32_MAX); 7954 dbInfo->mReadTransactionCount++; 7955 } 7956 7957 auto& blockingTransactions = dbInfo->mBlockingTransactions; 7958 7959 for (const nsAString& objectStoreName : aObjectStoreNames) { 7960 TransactionInfoPair* blockInfo = 7961 blockingTransactions.GetOrInsertNew(objectStoreName); 7962 7963 // Mark what we are blocking on. 7964 if (const auto maybeBlockingRead = blockInfo->mLastBlockingReads) { 7965 transactionInfo.mBlockedOn.Insert(&maybeBlockingRead.ref()); 7966 maybeBlockingRead->AddBlockingTransaction(transactionInfo); 7967 } 7968 7969 if (aIsWriteTransaction) { 7970 for (const auto blockingWrite : blockInfo->mLastBlockingWrites) { 7971 transactionInfo.mBlockedOn.Insert(blockingWrite); 7972 blockingWrite->AddBlockingTransaction(transactionInfo); 7973 } 7974 7975 blockInfo->mLastBlockingReads = SomeRef(transactionInfo); 7976 blockInfo->mLastBlockingWrites.Clear(); 7977 } else { 7978 blockInfo->mLastBlockingWrites.AppendElement( 7979 WrapNotNullUnchecked(&transactionInfo)); 7980 } 7981 } 7982 7983 if (!transactionInfo.mBlockedOn.Count()) { 7984 (void)ScheduleTransaction(transactionInfo, 7985 /* aFromQueuedTransactions */ false); 7986 } 7987 7988 if (!databaseInfoIsNew && 7989 (mIdleDatabases.RemoveElement(dbInfo) || 7990 mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo))) { 7991 AdjustIdleTimer(); 7992 } 7993 7994 return transactionId; 7995 } 7996 7997 void ConnectionPool::StartOp(uint64_t aTransactionId, 7998 nsCOMPtr<nsIRunnable> aRunnable) { 7999 AssertIsOnOwningThread(); 8000 8001 AUTO_PROFILER_LABEL("ConnectionPool::StartOp", DOM); 8002 8003 auto* const transactionInfo = mTransactions.Get(aTransactionId); 8004 MOZ_ASSERT(transactionInfo); 8005 8006 transactionInfo->StartOp(std::move(aRunnable)); 8007 } 8008 8009 void ConnectionPool::FinishOp(uint64_t aTransactionId) { 8010 AssertIsOnOwningThread(); 8011 8012 AUTO_PROFILER_LABEL("ConnectionPool::FinishOp", DOM); 8013 8014 auto* const transactionInfo = mTransactions.Get(aTransactionId); 8015 MOZ_ASSERT(transactionInfo); 8016 8017 transactionInfo->FinishOp(); 8018 } 8019 8020 void ConnectionPool::Finish(uint64_t aTransactionId, 8021 FinishCallback* aCallback) { 8022 AssertIsOnOwningThread(); 8023 8024 #ifdef DEBUG 8025 auto* const transactionInfo = mTransactions.Get(aTransactionId); 8026 MOZ_ASSERT(transactionInfo); 8027 MOZ_ASSERT(!transactionInfo->mFinished); 8028 #endif 8029 8030 AUTO_PROFILER_LABEL("ConnectionPool::Finish", DOM); 8031 8032 nsCOMPtr<nsIRunnable> wrapper = 8033 new FinishCallbackWrapper(this, aTransactionId, aCallback); 8034 8035 StartOp(aTransactionId, std::move(wrapper)); 8036 8037 #ifdef DEBUG 8038 transactionInfo->mFinished.Flip(); 8039 #endif 8040 } 8041 8042 void ConnectionPool::WaitForDatabaseToComplete(const nsCString& aDatabaseId, 8043 nsIRunnable* aCallback) { 8044 AssertIsOnOwningThread(); 8045 MOZ_ASSERT(!aDatabaseId.IsEmpty()); 8046 MOZ_ASSERT(aCallback); 8047 8048 AUTO_PROFILER_LABEL("ConnectionPool::WaitForDatabaseToComplete", DOM); 8049 8050 if (!CloseDatabaseWhenIdleInternal(aDatabaseId)) { 8051 (void)aCallback->Run(); 8052 return; 8053 } 8054 8055 mCompleteCallbacks.EmplaceBack( 8056 MakeUnique<DatabaseCompleteCallback>(aDatabaseId, aCallback)); 8057 } 8058 8059 void ConnectionPool::Shutdown() { 8060 AssertIsOnOwningThread(); 8061 MOZ_ASSERT(!mShutdownComplete); 8062 8063 AUTO_PROFILER_LABEL("ConnectionPool::Shutdown", DOM); 8064 8065 mShutdownRequested.Flip(); 8066 8067 CancelIdleTimer(); 8068 MOZ_ASSERT(mTargetIdleTime.IsNull()); 8069 8070 mIdleTimer = nullptr; 8071 8072 CloseIdleDatabases(); 8073 8074 if (!mDatabases.Count()) { 8075 MOZ_ASSERT(!mTransactions.Count()); 8076 8077 Cleanup(); 8078 8079 MOZ_ASSERT(mShutdownComplete); 8080 8081 mIOTarget->Shutdown(); 8082 8083 return; 8084 } 8085 8086 MOZ_ALWAYS_TRUE(SpinEventLoopUntil("ConnectionPool::Shutdown"_ns, [&]() { 8087 return static_cast<bool>(mShutdownComplete); 8088 })); 8089 8090 mIOTarget->Shutdown(); 8091 } 8092 8093 void ConnectionPool::Cleanup() { 8094 AssertIsOnOwningThread(); 8095 MOZ_ASSERT(mShutdownRequested); 8096 MOZ_ASSERT(!mShutdownComplete); 8097 MOZ_ASSERT(!mDatabases.Count()); 8098 MOZ_ASSERT(!mTransactions.Count()); 8099 8100 AUTO_PROFILER_LABEL("ConnectionPool::Cleanup", DOM); 8101 8102 if (!mCompleteCallbacks.IsEmpty()) { 8103 // Run all callbacks manually now. 8104 8105 { 8106 auto completeCallbacks = std::move(mCompleteCallbacks); 8107 for (const auto& completeCallback : completeCallbacks) { 8108 MOZ_ASSERT(completeCallback); 8109 MOZ_ASSERT(completeCallback->mCallback); 8110 8111 (void)completeCallback->mCallback->Run(); 8112 } 8113 8114 // We expect no new callbacks being completed by running the existing 8115 // ones. 8116 MOZ_ASSERT(mCompleteCallbacks.IsEmpty()); 8117 } 8118 8119 // And make sure they get processed. 8120 nsIThread* currentThread = NS_GetCurrentThread(); 8121 MOZ_ASSERT(currentThread); 8122 8123 MOZ_ALWAYS_SUCCEEDS(NS_ProcessPendingEvents(currentThread)); 8124 } 8125 8126 mShutdownComplete.Flip(); 8127 } 8128 8129 void ConnectionPool::AdjustIdleTimer() { 8130 AssertIsOnOwningThread(); 8131 MOZ_ASSERT(mIdleTimer); 8132 8133 AUTO_PROFILER_LABEL("ConnectionPool::AdjustIdleTimer", DOM); 8134 8135 // Figure out the next time at which we should release idle resources. This 8136 // includes both databases and threads. 8137 TimeStamp newTargetIdleTime; 8138 MOZ_ASSERT(newTargetIdleTime.IsNull()); 8139 8140 if (!mIdleDatabases.IsEmpty()) { 8141 newTargetIdleTime = mIdleDatabases[0].mIdleTime; 8142 } 8143 8144 MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleDatabases.IsEmpty()); 8145 8146 // Cancel the timer if it was running and the new target time is different. 8147 if (!mTargetIdleTime.IsNull() && 8148 (newTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) { 8149 CancelIdleTimer(); 8150 8151 MOZ_ASSERT(mTargetIdleTime.IsNull()); 8152 } 8153 8154 // Schedule the timer if we have a target time different than before. 8155 if (!newTargetIdleTime.IsNull() && 8156 (mTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) { 8157 double delta = (newTargetIdleTime - TimeStamp::NowLoRes()).ToMilliseconds(); 8158 8159 uint32_t delay; 8160 if (delta > 0) { 8161 delay = uint32_t(std::min(delta, double(UINT32_MAX))); 8162 } else { 8163 delay = 0; 8164 } 8165 8166 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->InitWithNamedFuncCallback( 8167 IdleTimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT, 8168 "ConnectionPool::IdleTimerCallback"_ns)); 8169 8170 mTargetIdleTime = newTargetIdleTime; 8171 } 8172 } 8173 8174 void ConnectionPool::CancelIdleTimer() { 8175 AssertIsOnOwningThread(); 8176 MOZ_ASSERT(mIdleTimer); 8177 8178 if (!mTargetIdleTime.IsNull()) { 8179 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel()); 8180 8181 mTargetIdleTime = TimeStamp(); 8182 MOZ_ASSERT(mTargetIdleTime.IsNull()); 8183 } 8184 } 8185 8186 void ConnectionPool::CloseIdleDatabases() { 8187 AssertIsOnOwningThread(); 8188 MOZ_ASSERT(mShutdownRequested); 8189 8190 AUTO_PROFILER_LABEL("ConnectionPool::CloseIdleDatabases", DOM); 8191 8192 if (!mIdleDatabases.IsEmpty()) { 8193 for (IdleDatabaseInfo& idleInfo : mIdleDatabases) { 8194 CloseDatabase(*idleInfo.mDatabaseInfo.ref()); 8195 } 8196 mIdleDatabases.Clear(); 8197 } 8198 8199 if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) { 8200 for (PerformingIdleMaintenanceDatabaseInfo& performingIdleMaintenanceInfo : 8201 mDatabasesPerformingIdleMaintenance) { 8202 CloseDatabase(*performingIdleMaintenanceInfo.mDatabaseInfo); 8203 } 8204 mDatabasesPerformingIdleMaintenance.Clear(); 8205 } 8206 } 8207 8208 bool ConnectionPool::ScheduleTransaction(TransactionInfo& aTransactionInfo, 8209 bool aFromQueuedTransactions) { 8210 AssertIsOnOwningThread(); 8211 8212 AUTO_PROFILER_LABEL("ConnectionPool::ScheduleTransaction", DOM); 8213 8214 DatabaseInfo& dbInfo = aTransactionInfo.mDatabaseInfo; 8215 8216 dbInfo.mIdle = false; 8217 8218 if (dbInfo.mClosing) { 8219 MOZ_ASSERT(!mIdleDatabases.Contains(&dbInfo)); 8220 MOZ_ASSERT( 8221 !dbInfo.mTransactionsScheduledDuringClose.Contains(&aTransactionInfo)); 8222 8223 dbInfo.mTransactionsScheduledDuringClose.AppendElement( 8224 WrapNotNullUnchecked(&aTransactionInfo)); 8225 return true; 8226 } 8227 8228 if (!dbInfo.mEventTarget) { 8229 const uint32_t serialNumber = SerialNumber(); 8230 const nsCString serialName = 8231 nsPrintfCString("IndexedDB #%" PRIu32, serialNumber); 8232 8233 dbInfo.mEventTarget = 8234 TaskQueue::Create(do_AddRef(mIOTarget), serialName.get()); 8235 MOZ_ASSERT(dbInfo.mEventTarget); 8236 IDB_DEBUG_LOG(("ConnectionPool created task queue %" PRIu32, serialNumber)); 8237 } 8238 8239 // The number of active operations equals the number of databases minus idle 8240 // databases. The maximum number of database operations which can make 8241 // progress at the same time is kMaxConnectionThreadCount. If we are at this 8242 // limit, all idle processing is interrupted to make room for user 8243 // transactions. 8244 if (mDatabases.Count() >= 8245 (mIdleDatabases.Length() + kMaxConnectionThreadCount) && 8246 !mDatabasesPerformingIdleMaintenance.IsEmpty()) { 8247 const auto& busyDbs = mDatabasesPerformingIdleMaintenance; 8248 for (auto dbInfo = busyDbs.rbegin(); dbInfo != busyDbs.rend(); ++dbInfo) { 8249 (*dbInfo).mIdleConnectionRunnable->Interrupt(); 8250 } 8251 } 8252 8253 if (aTransactionInfo.mIsWriteTransaction) { 8254 if (dbInfo.mRunningWriteTransaction) { 8255 // SQLite only allows one write transaction at a time so queue this 8256 // transaction for later. 8257 MOZ_ASSERT( 8258 !dbInfo.mScheduledWriteTransactions.Contains(&aTransactionInfo)); 8259 8260 dbInfo.mScheduledWriteTransactions.AppendElement( 8261 WrapNotNullUnchecked(&aTransactionInfo)); 8262 return true; 8263 } 8264 8265 dbInfo.mRunningWriteTransaction = SomeRef(aTransactionInfo); 8266 dbInfo.mNeedsCheckpoint = true; 8267 } 8268 8269 aTransactionInfo.SetRunning(); 8270 8271 return true; 8272 } 8273 8274 void ConnectionPool::NoteFinishedTransaction(uint64_t aTransactionId) { 8275 AssertIsOnOwningThread(); 8276 8277 AUTO_PROFILER_LABEL("ConnectionPool::NoteFinishedTransaction", DOM); 8278 8279 auto* const transactionInfo = mTransactions.Get(aTransactionId); 8280 MOZ_ASSERT(transactionInfo); 8281 MOZ_ASSERT(transactionInfo->mRunning); 8282 MOZ_ASSERT(transactionInfo->mFinished); 8283 8284 transactionInfo->mRunning = false; 8285 8286 DatabaseInfo& dbInfo = transactionInfo->mDatabaseInfo; 8287 MOZ_ASSERT(mDatabases.Get(transactionInfo->mDatabaseId) == &dbInfo); 8288 MOZ_ASSERT(dbInfo.mEventTarget); 8289 8290 // Schedule the next write transaction if there are any queued. 8291 if (dbInfo.mRunningWriteTransaction && 8292 dbInfo.mRunningWriteTransaction.refEquals(*transactionInfo)) { 8293 MOZ_ASSERT(transactionInfo->mIsWriteTransaction); 8294 MOZ_ASSERT(dbInfo.mNeedsCheckpoint); 8295 8296 dbInfo.mRunningWriteTransaction = Nothing(); 8297 8298 if (!dbInfo.mScheduledWriteTransactions.IsEmpty()) { 8299 const auto nextWriteTransaction = dbInfo.mScheduledWriteTransactions[0]; 8300 8301 dbInfo.mScheduledWriteTransactions.RemoveElementAt(0); 8302 8303 MOZ_ALWAYS_TRUE(ScheduleTransaction(*nextWriteTransaction, 8304 /* aFromQueuedTransactions */ false)); 8305 } 8306 } 8307 8308 for (const auto& objectStoreName : transactionInfo->mObjectStoreNames) { 8309 TransactionInfoPair* blockInfo = 8310 dbInfo.mBlockingTransactions.Get(objectStoreName); 8311 MOZ_ASSERT(blockInfo); 8312 8313 if (transactionInfo->mIsWriteTransaction && blockInfo->mLastBlockingReads && 8314 blockInfo->mLastBlockingReads.refEquals(*transactionInfo)) { 8315 blockInfo->mLastBlockingReads = Nothing(); 8316 } 8317 8318 blockInfo->mLastBlockingWrites.RemoveElement(transactionInfo); 8319 } 8320 8321 transactionInfo->RemoveBlockingTransactions(); 8322 8323 if (transactionInfo->mIsWriteTransaction) { 8324 MOZ_ASSERT(dbInfo.mWriteTransactionCount); 8325 dbInfo.mWriteTransactionCount--; 8326 } else { 8327 MOZ_ASSERT(dbInfo.mReadTransactionCount); 8328 dbInfo.mReadTransactionCount--; 8329 } 8330 8331 mTransactions.Remove(aTransactionId); 8332 8333 if (!dbInfo.TotalTransactionCount()) { 8334 MOZ_ASSERT(!dbInfo.mIdle); 8335 dbInfo.mIdle = true; 8336 8337 NoteIdleDatabase(dbInfo); 8338 } 8339 } 8340 8341 void ConnectionPool::ScheduleQueuedTransactions() { 8342 AssertIsOnOwningThread(); 8343 MOZ_ASSERT(!mQueuedTransactions.IsEmpty()); 8344 8345 AUTO_PROFILER_LABEL("ConnectionPool::ScheduleQueuedTransactions", DOM); 8346 8347 const auto foundIt = std::find_if( 8348 mQueuedTransactions.begin(), mQueuedTransactions.end(), 8349 [&me = *this](const auto& queuedTransaction) { 8350 return !me.ScheduleTransaction(*queuedTransaction, 8351 /* aFromQueuedTransactions */ true); 8352 }); 8353 8354 mQueuedTransactions.RemoveElementsRange(mQueuedTransactions.begin(), foundIt); 8355 8356 AdjustIdleTimer(); 8357 } 8358 8359 void ConnectionPool::NoteIdleDatabase(DatabaseInfo& aDatabaseInfo) { 8360 AssertIsOnOwningThread(); 8361 MOZ_ASSERT(!aDatabaseInfo.TotalTransactionCount()); 8362 MOZ_ASSERT(aDatabaseInfo.mEventTarget); 8363 MOZ_ASSERT(!mIdleDatabases.Contains(&aDatabaseInfo)); 8364 8365 AUTO_PROFILER_LABEL("ConnectionPool::NoteIdleDatabase", DOM); 8366 8367 const bool otherDatabasesWaiting = !mQueuedTransactions.IsEmpty(); 8368 8369 // We check mShutdownRequested because when it is true, mIdleTimer is null. 8370 if (mShutdownRequested || otherDatabasesWaiting || 8371 aDatabaseInfo.mCloseOnIdle) { 8372 // Make sure we close the connection if we're shutting down or giving the 8373 // thread to another database. 8374 CloseDatabase(aDatabaseInfo); 8375 8376 if (otherDatabasesWaiting) { 8377 ScheduleQueuedTransactions(); 8378 } 8379 8380 return; 8381 } 8382 8383 mIdleDatabases.InsertElementSorted(IdleDatabaseInfo{aDatabaseInfo}); 8384 8385 AdjustIdleTimer(); 8386 } 8387 8388 void ConnectionPool::NoteClosedDatabase(DatabaseInfo& aDatabaseInfo) { 8389 AssertIsOnOwningThread(); 8390 MOZ_ASSERT(aDatabaseInfo.mClosing); 8391 MOZ_ASSERT(!mIdleDatabases.Contains(&aDatabaseInfo)); 8392 8393 AUTO_PROFILER_LABEL("ConnectionPool::NoteClosedDatabase", DOM); 8394 8395 aDatabaseInfo.mClosing = false; 8396 8397 // Schedule any transactions that were started while we were closing the 8398 // connection. 8399 if (!mQueuedTransactions.IsEmpty()) { 8400 ScheduleQueuedTransactions(); 8401 } else if (!aDatabaseInfo.TotalTransactionCount() && !mShutdownRequested) { 8402 AdjustIdleTimer(); 8403 } 8404 8405 // Schedule any transactions that were started while we were closing the 8406 // connection. 8407 if (aDatabaseInfo.TotalTransactionCount()) { 8408 auto& scheduledTransactions = 8409 aDatabaseInfo.mTransactionsScheduledDuringClose; 8410 8411 MOZ_ASSERT(!scheduledTransactions.IsEmpty()); 8412 8413 for (const auto& scheduledTransaction : scheduledTransactions) { 8414 (void)ScheduleTransaction(*scheduledTransaction, 8415 /* aFromQueuedTransactions */ false); 8416 } 8417 8418 scheduledTransactions.Clear(); 8419 8420 return; 8421 } 8422 8423 // There are no more transactions and the connection has been closed. We're 8424 // done with this database. 8425 { 8426 MutexAutoLock lock(mDatabasesMutex); 8427 8428 mDatabases.Remove(aDatabaseInfo.mDatabaseId); 8429 } 8430 8431 // That just deleted |aDatabaseInfo|, we must not access that below. 8432 8433 // See if we need to fire any complete callbacks now that the database is 8434 // finished. 8435 mCompleteCallbacks.RemoveLastElements( 8436 mCompleteCallbacks.end() - 8437 std::remove_if(mCompleteCallbacks.begin(), mCompleteCallbacks.end(), 8438 [&me = *this](const auto& completeCallback) { 8439 return me.MaybeFireCallback(completeCallback.get()); 8440 })); 8441 8442 // If that was the last database and we're supposed to be shutting down then 8443 // we are finished. 8444 if (mShutdownRequested && !mDatabases.Count()) { 8445 MOZ_ASSERT(!mTransactions.Count()); 8446 Cleanup(); 8447 } 8448 } 8449 8450 bool ConnectionPool::MaybeFireCallback(DatabaseCompleteCallback* aCallback) { 8451 AssertIsOnOwningThread(); 8452 MOZ_ASSERT(aCallback); 8453 MOZ_ASSERT(!aCallback->mDatabaseId.IsEmpty()); 8454 MOZ_ASSERT(aCallback->mCallback); 8455 8456 AUTO_PROFILER_LABEL("ConnectionPool::MaybeFireCallback", DOM); 8457 8458 if (mDatabases.Get(aCallback->mDatabaseId)) { 8459 return false; 8460 } 8461 8462 (void)aCallback->mCallback->Run(); 8463 return true; 8464 } 8465 8466 void ConnectionPool::PerformIdleDatabaseMaintenance( 8467 DatabaseInfo& aDatabaseInfo) { 8468 AssertIsOnOwningThread(); 8469 MOZ_ASSERT(!aDatabaseInfo.TotalTransactionCount()); 8470 MOZ_ASSERT(aDatabaseInfo.mEventTarget); 8471 MOZ_ASSERT(aDatabaseInfo.mIdle); 8472 MOZ_ASSERT(!aDatabaseInfo.mCloseOnIdle); 8473 MOZ_ASSERT(!aDatabaseInfo.mClosing); 8474 MOZ_ASSERT(mIdleDatabases.Contains(&aDatabaseInfo)); 8475 MOZ_ASSERT(!mDatabasesPerformingIdleMaintenance.Contains(&aDatabaseInfo)); 8476 8477 const bool neededCheckpoint = aDatabaseInfo.mNeedsCheckpoint; 8478 8479 aDatabaseInfo.mNeedsCheckpoint = false; 8480 aDatabaseInfo.mIdle = false; 8481 8482 auto idleConnectionRunnable = 8483 MakeRefPtr<IdleConnectionRunnable>(aDatabaseInfo, neededCheckpoint); 8484 8485 mDatabasesPerformingIdleMaintenance.AppendElement( 8486 PerformingIdleMaintenanceDatabaseInfo{aDatabaseInfo, 8487 idleConnectionRunnable}); 8488 8489 MOZ_ALWAYS_SUCCEEDS(aDatabaseInfo.mEventTarget->Dispatch( 8490 idleConnectionRunnable.forget(), NS_DISPATCH_NORMAL)); 8491 } 8492 8493 void ConnectionPool::CloseDatabase(DatabaseInfo& aDatabaseInfo) const { 8494 AssertIsOnOwningThread(); 8495 MOZ_DIAGNOSTIC_ASSERT(!aDatabaseInfo.TotalTransactionCount()); 8496 MOZ_ASSERT(aDatabaseInfo.mEventTarget); 8497 MOZ_ASSERT(!aDatabaseInfo.mClosing); 8498 8499 aDatabaseInfo.mIdle = false; 8500 aDatabaseInfo.mNeedsCheckpoint = false; 8501 aDatabaseInfo.mClosing = true; 8502 8503 MOZ_ALWAYS_SUCCEEDS(aDatabaseInfo.Dispatch( 8504 MakeAndAddRef<CloseConnectionRunnable>(aDatabaseInfo))); 8505 } 8506 8507 bool ConnectionPool::CloseDatabaseWhenIdleInternal( 8508 const nsACString& aDatabaseId) { 8509 AssertIsOnOwningThread(); 8510 MOZ_ASSERT(!aDatabaseId.IsEmpty()); 8511 8512 AUTO_PROFILER_LABEL("ConnectionPool::CloseDatabaseWhenIdleInternal", DOM); 8513 8514 if (DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId)) { 8515 if (mIdleDatabases.RemoveElement(dbInfo) || 8516 mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo)) { 8517 CloseDatabase(*dbInfo); 8518 AdjustIdleTimer(); 8519 } else { 8520 dbInfo->mCloseOnIdle.EnsureFlipped(); 8521 } 8522 8523 return true; 8524 } 8525 8526 return false; 8527 } 8528 8529 ConnectionPool::ConnectionRunnable::ConnectionRunnable( 8530 DatabaseInfo& aDatabaseInfo) 8531 : Runnable("dom::indexedDB::ConnectionPool::ConnectionRunnable"), 8532 mDatabaseInfo(aDatabaseInfo), 8533 mOwningEventTarget(GetCurrentSerialEventTarget()) { 8534 AssertIsOnBackgroundThread(); 8535 MOZ_ASSERT(aDatabaseInfo.mConnectionPool); 8536 aDatabaseInfo.mConnectionPool->AssertIsOnOwningThread(); 8537 MOZ_ASSERT(mOwningEventTarget); 8538 } 8539 8540 NS_IMETHODIMP 8541 ConnectionPool::IdleConnectionRunnable::Run() { 8542 MOZ_ASSERT(!mDatabaseInfo.mIdle); 8543 8544 const nsCOMPtr<nsIEventTarget> owningThread = std::move(mOwningEventTarget); 8545 8546 if (owningThread) { 8547 mDatabaseInfo.AssertIsOnConnectionThread(); 8548 8549 // The connection could be null if EnsureConnection() didn't run or was not 8550 // successful in TransactionDatabaseOperationBase::RunOnConnectionThread(). 8551 if (mDatabaseInfo.mConnection) { 8552 mDatabaseInfo.mConnection->DoIdleProcessing(mNeedsCheckpoint, 8553 mInterrupted); 8554 } 8555 8556 MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL)); 8557 return NS_OK; 8558 } 8559 8560 AssertIsOnBackgroundThread(); 8561 8562 RefPtr<ConnectionPool> connectionPool = mDatabaseInfo.mConnectionPool; 8563 MOZ_ASSERT(connectionPool); 8564 8565 if (mDatabaseInfo.mClosing || mDatabaseInfo.TotalTransactionCount()) { 8566 MOZ_ASSERT(!connectionPool->mDatabasesPerformingIdleMaintenance.Contains( 8567 &mDatabaseInfo)); 8568 } else { 8569 MOZ_ALWAYS_TRUE( 8570 connectionPool->mDatabasesPerformingIdleMaintenance.RemoveElement( 8571 &mDatabaseInfo)); 8572 8573 connectionPool->NoteIdleDatabase(mDatabaseInfo); 8574 } 8575 8576 return NS_OK; 8577 } 8578 8579 NS_IMETHODIMP 8580 ConnectionPool::CloseConnectionRunnable::Run() { 8581 AUTO_PROFILER_LABEL("ConnectionPool::CloseConnectionRunnable::Run", DOM); 8582 8583 if (mOwningEventTarget) { 8584 MOZ_ASSERT(mDatabaseInfo.mClosing); 8585 8586 const nsCOMPtr<nsIEventTarget> owningThread = std::move(mOwningEventTarget); 8587 8588 // The connection could be null if EnsureConnection() didn't run or was not 8589 // successful in TransactionDatabaseOperationBase::RunOnConnectionThread(). 8590 if (mDatabaseInfo.mConnection) { 8591 mDatabaseInfo.AssertIsOnConnectionThread(); 8592 8593 mDatabaseInfo.mConnection->Close(); 8594 8595 IDB_DEBUG_LOG(("ConnectionPool closed connection 0x%p", 8596 mDatabaseInfo.mConnection.get())); 8597 8598 mDatabaseInfo.mConnection = nullptr; 8599 8600 #ifdef DEBUG 8601 mDatabaseInfo.mDEBUGConnectionEventTarget = nullptr; 8602 #endif 8603 } 8604 8605 MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL)); 8606 return NS_OK; 8607 } 8608 8609 RefPtr<ConnectionPool> connectionPool = mDatabaseInfo.mConnectionPool; 8610 MOZ_ASSERT(connectionPool); 8611 8612 connectionPool->NoteClosedDatabase(mDatabaseInfo); 8613 return NS_OK; 8614 } 8615 8616 ConnectionPool::DatabaseInfo::DatabaseInfo(ConnectionPool* aConnectionPool, 8617 const nsACString& aDatabaseId) 8618 : mConnectionPool(aConnectionPool), 8619 mDatabaseId(aDatabaseId), 8620 mReadTransactionCount(0), 8621 mWriteTransactionCount(0), 8622 mNeedsCheckpoint(false), 8623 mIdle(false), 8624 mClosing(false) 8625 #ifdef DEBUG 8626 , 8627 mDEBUGConnectionEventTarget(nullptr) 8628 #endif 8629 { 8630 AssertIsOnBackgroundThread(); 8631 MOZ_ASSERT(aConnectionPool); 8632 aConnectionPool->AssertIsOnOwningThread(); 8633 MOZ_ASSERT(!aDatabaseId.IsEmpty()); 8634 8635 MOZ_COUNT_CTOR(ConnectionPool::DatabaseInfo); 8636 } 8637 8638 ConnectionPool::DatabaseInfo::~DatabaseInfo() { 8639 AssertIsOnBackgroundThread(); 8640 MOZ_ASSERT(!mConnection); 8641 MOZ_ASSERT(mScheduledWriteTransactions.IsEmpty()); 8642 MOZ_ASSERT(!mRunningWriteTransaction); 8643 MOZ_ASSERT(!TotalTransactionCount()); 8644 8645 MOZ_COUNT_DTOR(ConnectionPool::DatabaseInfo); 8646 } 8647 8648 nsresult ConnectionPool::DatabaseInfo::Dispatch( 8649 already_AddRefed<nsIRunnable> aRunnable) { 8650 nsCOMPtr<nsIRunnable> runnable = aRunnable; 8651 8652 #ifdef DEBUG 8653 if (kDEBUGTransactionThreadSleepMS) { 8654 runnable = MakeRefPtr<TransactionRunnable>(std::move(runnable)); 8655 } 8656 #endif 8657 8658 return mEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); 8659 } 8660 8661 ConnectionPool::DatabaseCompleteCallback::DatabaseCompleteCallback( 8662 const nsCString& aDatabaseId, nsIRunnable* aCallback) 8663 : mDatabaseId(aDatabaseId), mCallback(aCallback) { 8664 AssertIsOnBackgroundThread(); 8665 MOZ_ASSERT(!mDatabaseId.IsEmpty()); 8666 MOZ_ASSERT(aCallback); 8667 8668 MOZ_COUNT_CTOR(ConnectionPool::DatabaseCompleteCallback); 8669 } 8670 8671 ConnectionPool::DatabaseCompleteCallback::~DatabaseCompleteCallback() { 8672 AssertIsOnBackgroundThread(); 8673 8674 MOZ_COUNT_DTOR(ConnectionPool::DatabaseCompleteCallback); 8675 } 8676 8677 ConnectionPool::FinishCallbackWrapper::FinishCallbackWrapper( 8678 ConnectionPool* aConnectionPool, uint64_t aTransactionId, 8679 FinishCallback* aCallback) 8680 : Runnable("dom::indexedDB::ConnectionPool::FinishCallbackWrapper"), 8681 mConnectionPool(aConnectionPool), 8682 mCallback(aCallback), 8683 mOwningEventTarget(GetCurrentSerialEventTarget()), 8684 mTransactionId(aTransactionId), 8685 mHasRunOnce(false) { 8686 AssertIsOnBackgroundThread(); 8687 MOZ_ASSERT(aConnectionPool); 8688 MOZ_ASSERT(aCallback); 8689 MOZ_ASSERT(mOwningEventTarget); 8690 } 8691 8692 ConnectionPool::FinishCallbackWrapper::~FinishCallbackWrapper() { 8693 MOZ_ASSERT(!mConnectionPool); 8694 MOZ_ASSERT(!mCallback); 8695 } 8696 8697 nsresult ConnectionPool::FinishCallbackWrapper::Run() { 8698 MOZ_ASSERT(mConnectionPool); 8699 MOZ_ASSERT(mCallback); 8700 MOZ_ASSERT(mOwningEventTarget); 8701 8702 AUTO_PROFILER_LABEL("ConnectionPool::FinishCallbackWrapper::Run", DOM); 8703 8704 if (!mHasRunOnce) { 8705 MOZ_ASSERT(!IsOnBackgroundThread()); 8706 8707 mHasRunOnce = true; 8708 8709 (void)mCallback->Run(); 8710 8711 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); 8712 8713 return NS_OK; 8714 } 8715 8716 mConnectionPool->AssertIsOnOwningThread(); 8717 MOZ_ASSERT(mHasRunOnce); 8718 8719 RefPtr<ConnectionPool> connectionPool = std::move(mConnectionPool); 8720 8721 connectionPool->FinishOp(mTransactionId); 8722 8723 RefPtr<FinishCallback> callback = std::move(mCallback); 8724 8725 callback->TransactionFinishedBeforeUnblock(); 8726 8727 connectionPool->NoteFinishedTransaction(mTransactionId); 8728 8729 callback->TransactionFinishedAfterUnblock(); 8730 8731 return NS_OK; 8732 } 8733 8734 uint32_t ConnectionPool::sSerialNumber = 0u; 8735 8736 #ifdef DEBUG 8737 8738 ConnectionPool::TransactionRunnable::TransactionRunnable( 8739 nsCOMPtr<nsIRunnable> aRunnable) 8740 : Runnable("dom::indexedDB::ConnectionPool::TransactionRunnable"), 8741 mRunnable(std::move(aRunnable)) { 8742 AssertIsOnBackgroundThread(); 8743 MOZ_ASSERT(kDEBUGTransactionThreadSleepMS); 8744 } 8745 8746 nsresult ConnectionPool::TransactionRunnable::Run() { 8747 MOZ_ASSERT(!IsOnBackgroundThread()); 8748 8749 QM_TRY(MOZ_TO_RESULT(mRunnable->Run())); 8750 8751 MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval( 8752 kDEBUGTransactionThreadSleepMS)) == PR_SUCCESS); 8753 8754 return NS_OK; 8755 } 8756 8757 #endif 8758 8759 ConnectionPool::IdleResource::IdleResource(const TimeStamp& aIdleTime) 8760 : mIdleTime(aIdleTime) { 8761 AssertIsOnBackgroundThread(); 8762 MOZ_ASSERT(!aIdleTime.IsNull()); 8763 8764 MOZ_COUNT_CTOR(ConnectionPool::IdleResource); 8765 } 8766 8767 ConnectionPool::IdleResource::~IdleResource() { 8768 AssertIsOnBackgroundThread(); 8769 8770 MOZ_COUNT_DTOR(ConnectionPool::IdleResource); 8771 } 8772 8773 ConnectionPool::IdleDatabaseInfo::IdleDatabaseInfo(DatabaseInfo& aDatabaseInfo) 8774 : IdleResource( 8775 TimeStamp::NowLoRes() + 8776 (aDatabaseInfo.mIdle 8777 ? TimeDuration::FromMilliseconds(kConnectionIdleMaintenanceMS) 8778 : TimeDuration::FromMilliseconds(kConnectionIdleCloseMS))), 8779 mDatabaseInfo(WrapNotNullUnchecked(&aDatabaseInfo)) { 8780 AssertIsOnBackgroundThread(); 8781 8782 MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo); 8783 } 8784 8785 ConnectionPool::IdleDatabaseInfo::~IdleDatabaseInfo() { 8786 AssertIsOnBackgroundThread(); 8787 8788 MOZ_COUNT_DTOR(ConnectionPool::IdleDatabaseInfo); 8789 } 8790 8791 ConnectionPool::PerformingIdleMaintenanceDatabaseInfo:: 8792 PerformingIdleMaintenanceDatabaseInfo( 8793 DatabaseInfo& aDatabaseInfo, 8794 RefPtr<IdleConnectionRunnable> aIdleConnectionRunnable) 8795 : mDatabaseInfo(WrapNotNullUnchecked(&aDatabaseInfo)), 8796 mIdleConnectionRunnable(std::move(aIdleConnectionRunnable)) { 8797 AssertIsOnBackgroundThread(); 8798 MOZ_ASSERT(mIdleConnectionRunnable); 8799 8800 MOZ_COUNT_CTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo); 8801 } 8802 8803 ConnectionPool::PerformingIdleMaintenanceDatabaseInfo:: 8804 ~PerformingIdleMaintenanceDatabaseInfo() { 8805 AssertIsOnBackgroundThread(); 8806 8807 MOZ_COUNT_DTOR(ConnectionPool::PerformingIdleMaintenanceDatabaseInfo); 8808 } 8809 8810 ConnectionPool::TransactionInfo::TransactionInfo( 8811 DatabaseInfo& aDatabaseInfo, const nsID& aBackgroundChildLoggingId, 8812 const nsACString& aDatabaseId, uint64_t aTransactionId, 8813 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames, 8814 bool aIsWriteTransaction, TransactionDatabaseOperationBase* aTransactionOp) 8815 : mDatabaseInfo(aDatabaseInfo), 8816 mBackgroundChildLoggingId(aBackgroundChildLoggingId), 8817 mDatabaseId(aDatabaseId), 8818 mTransactionId(aTransactionId), 8819 mLoggingSerialNumber(aLoggingSerialNumber), 8820 mObjectStoreNames(aObjectStoreNames.Clone()), 8821 mIsWriteTransaction(aIsWriteTransaction), 8822 mRunning(false), 8823 mRunningOp(false) { 8824 AssertIsOnBackgroundThread(); 8825 aDatabaseInfo.mConnectionPool->AssertIsOnOwningThread(); 8826 8827 MOZ_COUNT_CTOR(ConnectionPool::TransactionInfo); 8828 8829 if (aTransactionOp) { 8830 mQueuedOps.Push(aTransactionOp); 8831 } 8832 } 8833 8834 ConnectionPool::TransactionInfo::~TransactionInfo() { 8835 AssertIsOnBackgroundThread(); 8836 MOZ_ASSERT(!mBlockedOn.Count()); 8837 MOZ_ASSERT(mQueuedOps.IsEmpty()); 8838 MOZ_ASSERT(!mRunning); 8839 MOZ_ASSERT(!mRunningOp); 8840 MOZ_ASSERT(mFinished); 8841 8842 MOZ_COUNT_DTOR(ConnectionPool::TransactionInfo); 8843 } 8844 8845 void ConnectionPool::TransactionInfo::AddBlockingTransaction( 8846 TransactionInfo& aTransactionInfo) { 8847 AssertIsOnBackgroundThread(); 8848 8849 // XXX Does it really make sense to have both mBlocking and mBlockingOrdered, 8850 // just to reduce the algorithmic complexity of this Contains check? This was 8851 // mentioned in the context of Bug 1290853, but no real justification was 8852 // given. There was the suggestion of encapsulating this in an 8853 // insertion-ordered hashtable implementation, which seems like a good idea. 8854 // If we had that, this would be the appropriate data structure to use here. 8855 if (mBlocking.EnsureInserted(&aTransactionInfo)) { 8856 mBlockingOrdered.AppendElement(WrapNotNullUnchecked(&aTransactionInfo)); 8857 } 8858 } 8859 8860 void ConnectionPool::TransactionInfo::RemoveBlockingTransactions() { 8861 AssertIsOnBackgroundThread(); 8862 8863 for (const auto blockedInfo : mBlockingOrdered) { 8864 blockedInfo->MaybeUnblock(*this); 8865 } 8866 8867 mBlocking.Clear(); 8868 mBlockingOrdered.Clear(); 8869 } 8870 8871 void ConnectionPool::TransactionInfo::SetRunning() { 8872 AssertIsOnBackgroundThread(); 8873 MOZ_ASSERT(!mRunning); 8874 8875 AUTO_PROFILER_LABEL("ConnectionPool::FinishOp", DOM); 8876 8877 mRunning = true; 8878 8879 if (!mQueuedOps.IsEmpty()) { 8880 mRunningOp = true; 8881 8882 nsCOMPtr<nsIRunnable> runnable = mQueuedOps.Pop(); 8883 8884 MOZ_ALWAYS_SUCCEEDS(mDatabaseInfo.Dispatch(runnable.forget())); 8885 } 8886 } 8887 8888 void ConnectionPool::TransactionInfo::StartOp(nsCOMPtr<nsIRunnable> aRunnable) { 8889 AssertIsOnBackgroundThread(); 8890 8891 if (mRunning) { 8892 MOZ_ASSERT(mDatabaseInfo.mEventTarget); 8893 MOZ_ASSERT(!mDatabaseInfo.mClosing); 8894 MOZ_ASSERT_IF(mIsWriteTransaction, 8895 mDatabaseInfo.mRunningWriteTransaction && 8896 mDatabaseInfo.mRunningWriteTransaction.refEquals(*this)); 8897 8898 if (!mRunningOp) { 8899 mRunningOp = true; 8900 8901 MOZ_ALWAYS_SUCCEEDS(mDatabaseInfo.Dispatch(aRunnable.forget())); 8902 } else { 8903 mQueuedOps.Push(std::move(aRunnable)); 8904 } 8905 } else { 8906 mQueuedOps.Push(std::move(aRunnable)); 8907 } 8908 } 8909 8910 void ConnectionPool::TransactionInfo::FinishOp() { 8911 AssertIsOnBackgroundThread(); 8912 MOZ_ASSERT(mRunning); 8913 MOZ_ASSERT(mRunningOp); 8914 8915 if (mQueuedOps.IsEmpty()) { 8916 mRunningOp = false; 8917 } else { 8918 nsCOMPtr<nsIRunnable> runnable = mQueuedOps.Pop(); 8919 8920 MOZ_ALWAYS_SUCCEEDS(mDatabaseInfo.Dispatch(runnable.forget())); 8921 } 8922 } 8923 8924 void ConnectionPool::TransactionInfo::MaybeUnblock( 8925 TransactionInfo& aTransactionInfo) { 8926 AssertIsOnBackgroundThread(); 8927 MOZ_ASSERT(mBlockedOn.Contains(&aTransactionInfo)); 8928 8929 mBlockedOn.Remove(&aTransactionInfo); 8930 if (mBlockedOn.IsEmpty()) { 8931 ConnectionPool* connectionPool = mDatabaseInfo.mConnectionPool; 8932 MOZ_ASSERT(connectionPool); 8933 connectionPool->AssertIsOnOwningThread(); 8934 8935 (void)connectionPool->ScheduleTransaction( 8936 *this, 8937 /* aFromQueuedTransactions */ false); 8938 } 8939 } 8940 8941 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) 8942 ConnectionPool::TransactionInfoPair::TransactionInfoPair() { 8943 AssertIsOnBackgroundThread(); 8944 8945 MOZ_COUNT_CTOR(ConnectionPool::TransactionInfoPair); 8946 } 8947 8948 ConnectionPool::TransactionInfoPair::~TransactionInfoPair() { 8949 AssertIsOnBackgroundThread(); 8950 8951 MOZ_COUNT_DTOR(ConnectionPool::TransactionInfoPair); 8952 } 8953 #endif 8954 8955 /******************************************************************************* 8956 * Metadata classes 8957 ******************************************************************************/ 8958 8959 bool FullObjectStoreMetadata::HasLiveIndexes() const { 8960 AssertIsOnBackgroundThread(); 8961 8962 return std::any_of(mIndexes.Values().cbegin(), mIndexes.Values().cend(), 8963 [](const auto& entry) { return !entry->mDeleted; }); 8964 } 8965 8966 SafeRefPtr<FullDatabaseMetadata> FullDatabaseMetadata::Duplicate() const { 8967 AssertIsOnBackgroundThread(); 8968 8969 // FullDatabaseMetadata contains two hash tables of pointers that we need to 8970 // duplicate so we can't just use the copy constructor. 8971 auto newMetadata = MakeSafeRefPtr<FullDatabaseMetadata>(mCommonMetadata); 8972 8973 newMetadata->mDatabaseId = mDatabaseId; 8974 newMetadata->mFilePath = mFilePath; 8975 newMetadata->mNextObjectStoreId = mNextObjectStoreId; 8976 newMetadata->mNextIndexId = mNextIndexId; 8977 8978 for (const auto& objectStoreEntry : mObjectStores) { 8979 const auto& objectStoreValue = objectStoreEntry.GetData(); 8980 8981 auto newOSMetadata = MakeSafeRefPtr<FullObjectStoreMetadata>( 8982 objectStoreValue->mCommonMetadata, [&objectStoreValue] { 8983 const auto&& srcLocked = objectStoreValue->mAutoIncrementIds.Lock(); 8984 return *srcLocked; 8985 }()); 8986 8987 for (const auto& indexEntry : objectStoreValue->mIndexes) { 8988 const auto& value = indexEntry.GetData(); 8989 8990 auto newIndexMetadata = MakeSafeRefPtr<FullIndexMetadata>(); 8991 8992 newIndexMetadata->mCommonMetadata = value->mCommonMetadata; 8993 8994 if (NS_WARN_IF(!newOSMetadata->mIndexes.InsertOrUpdate( 8995 indexEntry.GetKey(), std::move(newIndexMetadata), fallible))) { 8996 return nullptr; 8997 } 8998 } 8999 9000 MOZ_ASSERT(objectStoreValue->mIndexes.Count() == 9001 newOSMetadata->mIndexes.Count()); 9002 9003 if (NS_WARN_IF(!newMetadata->mObjectStores.InsertOrUpdate( 9004 objectStoreEntry.GetKey(), std::move(newOSMetadata), fallible))) { 9005 return nullptr; 9006 } 9007 } 9008 9009 MOZ_ASSERT(mObjectStores.Count() == newMetadata->mObjectStores.Count()); 9010 9011 return newMetadata; 9012 } 9013 9014 DatabaseLoggingInfo::~DatabaseLoggingInfo() { 9015 AssertIsOnBackgroundThread(); 9016 9017 if (gLoggingInfoHashtable) { 9018 const nsID& backgroundChildLoggingId = 9019 mLoggingInfo.backgroundChildLoggingId(); 9020 9021 MOZ_ASSERT(gLoggingInfoHashtable->Get(backgroundChildLoggingId) == this); 9022 9023 gLoggingInfoHashtable->Remove(backgroundChildLoggingId); 9024 } 9025 } 9026 9027 /******************************************************************************* 9028 * Factory 9029 ******************************************************************************/ 9030 9031 Factory::Factory(RefPtr<DatabaseLoggingInfo> aLoggingInfo, 9032 const nsACString& aSystemLocale) 9033 : mSystemLocale(aSystemLocale), 9034 mLoggingInfo(std::move(aLoggingInfo)) 9035 #ifdef DEBUG 9036 , 9037 mActorDestroyed(false) 9038 #endif 9039 { 9040 AssertIsOnBackgroundThread(); 9041 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 9042 } 9043 9044 Factory::~Factory() { MOZ_ASSERT(mActorDestroyed); } 9045 9046 // static 9047 SafeRefPtr<Factory> Factory::Create(const LoggingInfo& aLoggingInfo, 9048 const nsACString& aSystemLocale) { 9049 AssertIsOnBackgroundThread(); 9050 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 9051 9052 // Balanced in ActoryDestroy(). 9053 IncreaseBusyCount(); 9054 9055 MOZ_ASSERT(gLoggingInfoHashtable); 9056 RefPtr<DatabaseLoggingInfo> loggingInfo = 9057 gLoggingInfoHashtable->WithEntryHandle( 9058 aLoggingInfo.backgroundChildLoggingId(), [&](auto&& entry) { 9059 if (entry) { 9060 [[maybe_unused]] const auto& loggingInfo = entry.Data(); 9061 MOZ_ASSERT(aLoggingInfo.backgroundChildLoggingId() == 9062 loggingInfo->Id()); 9063 #if !FUZZING 9064 NS_WARNING_ASSERTION( 9065 aLoggingInfo.nextTransactionSerialNumber() == 9066 loggingInfo->mLoggingInfo.nextTransactionSerialNumber(), 9067 "NextTransactionSerialNumber doesn't match!"); 9068 NS_WARNING_ASSERTION( 9069 aLoggingInfo.nextVersionChangeTransactionSerialNumber() == 9070 loggingInfo->mLoggingInfo 9071 .nextVersionChangeTransactionSerialNumber(), 9072 "NextVersionChangeTransactionSerialNumber doesn't match!"); 9073 NS_WARNING_ASSERTION( 9074 aLoggingInfo.nextRequestSerialNumber() == 9075 loggingInfo->mLoggingInfo.nextRequestSerialNumber(), 9076 "NextRequestSerialNumber doesn't match!"); 9077 #endif // !FUZZING 9078 } else { 9079 entry.Insert(new DatabaseLoggingInfo(aLoggingInfo)); 9080 } 9081 9082 return do_AddRef(entry.Data()); 9083 }); 9084 9085 return MakeSafeRefPtr<Factory>(std::move(loggingInfo), aSystemLocale); 9086 } 9087 9088 void Factory::ActorDestroy(ActorDestroyReason aWhy) { 9089 AssertIsOnBackgroundThread(); 9090 MOZ_ASSERT(!mActorDestroyed); 9091 9092 #ifdef DEBUG 9093 mActorDestroyed = true; 9094 #endif 9095 9096 // Match the IncreaseBusyCount in Create(). 9097 DecreaseBusyCount(); 9098 } 9099 9100 mozilla::ipc::IPCResult Factory::RecvDeleteMe() { 9101 AssertIsOnBackgroundThread(); 9102 MOZ_ASSERT(!mActorDestroyed); 9103 9104 QM_WARNONLY_TRY(OkIf(PBackgroundIDBFactoryParent::Send__delete__(this))); 9105 9106 return IPC_OK(); 9107 } 9108 9109 PBackgroundIDBFactoryRequestParent* 9110 Factory::AllocPBackgroundIDBFactoryRequestParent( 9111 const FactoryRequestParams& aParams) { 9112 AssertIsOnBackgroundThread(); 9113 MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None); 9114 9115 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { 9116 return nullptr; 9117 } 9118 9119 const CommonFactoryRequestParams* commonParams; 9120 9121 switch (aParams.type()) { 9122 case FactoryRequestParams::TOpenDatabaseRequestParams: { 9123 const OpenDatabaseRequestParams& params = 9124 aParams.get_OpenDatabaseRequestParams(); 9125 commonParams = ¶ms.commonParams(); 9126 break; 9127 } 9128 9129 case FactoryRequestParams::TDeleteDatabaseRequestParams: { 9130 const DeleteDatabaseRequestParams& params = 9131 aParams.get_DeleteDatabaseRequestParams(); 9132 commonParams = ¶ms.commonParams(); 9133 break; 9134 } 9135 9136 default: 9137 MOZ_CRASH("Should never get here!"); 9138 } 9139 9140 MOZ_ASSERT(commonParams); 9141 9142 const DatabaseMetadata& metadata = commonParams->metadata(); 9143 9144 if (NS_AUUF_OR_WARN_IF(!IsValidPersistenceType(metadata.persistenceType()))) { 9145 return nullptr; 9146 } 9147 9148 const PrincipalInfo& principalInfo = commonParams->principalInfo(); 9149 9150 if (NS_AUUF_OR_WARN_IF(!quota::IsPrincipalInfoValid(principalInfo))) { 9151 IPC_FAIL(this, "Invalid principal!"); 9152 return nullptr; 9153 } 9154 9155 MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo || 9156 principalInfo.type() == PrincipalInfo::TContentPrincipalInfo); 9157 9158 if (NS_AUUF_OR_WARN_IF( 9159 principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo && 9160 metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) { 9161 return nullptr; 9162 } 9163 9164 if (NS_AUUF_OR_WARN_IF( 9165 principalInfo.type() == PrincipalInfo::TContentPrincipalInfo && 9166 QuotaManager::IsOriginInternal( 9167 principalInfo.get_ContentPrincipalInfo().originNoSuffix()) && 9168 metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) { 9169 return nullptr; 9170 } 9171 9172 Maybe<ContentParentId> contentParentId = GetContentParentId(); 9173 9174 auto actor = [&]() -> RefPtr<FactoryRequestOp> { 9175 if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) { 9176 return MakeRefPtr<OpenDatabaseOp>(SafeRefPtrFromThis(), contentParentId, 9177 *commonParams); 9178 } else { 9179 return MakeRefPtr<DeleteDatabaseOp>(SafeRefPtrFromThis(), contentParentId, 9180 *commonParams); 9181 } 9182 }(); 9183 9184 gFactoryOps->insertBack(actor); 9185 9186 // Balanced in CleanupMetadata() which is/must always called by SendResults(). 9187 IncreaseBusyCount(); 9188 9189 // Transfer ownership to IPDL. 9190 return actor.forget().take(); 9191 } 9192 9193 mozilla::ipc::IPCResult Factory::RecvPBackgroundIDBFactoryRequestConstructor( 9194 PBackgroundIDBFactoryRequestParent* aActor, 9195 const FactoryRequestParams& aParams) { 9196 AssertIsOnBackgroundThread(); 9197 MOZ_ASSERT(aActor); 9198 MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None); 9199 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 9200 9201 auto* op = static_cast<FactoryRequestOp*>(aActor); 9202 9203 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(op)); 9204 return IPC_OK(); 9205 } 9206 9207 bool Factory::DeallocPBackgroundIDBFactoryRequestParent( 9208 PBackgroundIDBFactoryRequestParent* aActor) { 9209 AssertIsOnBackgroundThread(); 9210 MOZ_ASSERT(aActor); 9211 9212 // Transfer ownership back from IPDL. 9213 RefPtr<FactoryRequestOp> op = 9214 dont_AddRef(static_cast<FactoryRequestOp*>(aActor)); 9215 return true; 9216 } 9217 9218 mozilla::ipc::IPCResult Factory::RecvGetDatabases( 9219 const PersistenceType& aPersistenceType, 9220 const PrincipalInfo& aPrincipalInfo, GetDatabasesResolver&& aResolve) { 9221 AssertIsOnBackgroundThread(); 9222 9223 auto ResolveGetDatabasesAndReturn = [&aResolve](const nsresult rv) { 9224 aResolve(rv); 9225 return IPC_OK(); 9226 }; 9227 9228 QM_TRY(MOZ_TO_RESULT(!QuotaClient::IsShuttingDownOnBackgroundThread()), 9229 ResolveGetDatabasesAndReturn); 9230 9231 QM_TRY(MOZ_TO_RESULT(IsValidPersistenceType(aPersistenceType)), 9232 QM_IPC_FAIL(this)); 9233 9234 QM_TRY(MOZ_TO_RESULT(quota::IsPrincipalInfoValid(aPrincipalInfo)), 9235 QM_IPC_FAIL(this)); 9236 9237 MOZ_ASSERT(aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo || 9238 aPrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo); 9239 9240 PersistenceType persistenceType = 9241 IDBFactory::GetPersistenceType(aPrincipalInfo); 9242 9243 QM_TRY(MOZ_TO_RESULT(aPersistenceType == persistenceType), QM_IPC_FAIL(this)); 9244 9245 Maybe<ContentParentId> contentParentId = GetContentParentId(); 9246 9247 auto op = MakeRefPtr<GetDatabasesOp>(SafeRefPtrFromThis(), contentParentId, 9248 aPersistenceType, aPrincipalInfo, 9249 std::move(aResolve)); 9250 9251 gFactoryOps->insertBack(op); 9252 9253 // Balanced in CleanupMetadata() which is/must always called by SendResults(). 9254 IncreaseBusyCount(); 9255 9256 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(op)); 9257 9258 return IPC_OK(); 9259 } 9260 9261 Maybe<ContentParentId> Factory::GetContentParentId() const { 9262 uint64_t childID = BackgroundParent::GetChildID(Manager()); 9263 if (childID) { 9264 // If childID is not zero we are dealing with an other-process actor. We 9265 // want to initialize OpenDatabaseOp/DeleteDatabaseOp here with the ID 9266 // (and later also Database) in that case, so Database::IsOwnedByProcess 9267 // can find Databases belonging to a particular content process when 9268 // QuotaClient::AbortOperationsForProcess is called which is currently used 9269 // to abort operations for content processes only. 9270 return Some(ContentParentId(childID)); 9271 } 9272 9273 return Nothing(); 9274 } 9275 9276 /******************************************************************************* 9277 * WaitForTransactionsHelper 9278 ******************************************************************************/ 9279 9280 void WaitForTransactionsHelper::WaitForTransactions() { 9281 MOZ_ASSERT(mState == State::Initial); 9282 9283 (void)this->Run(); 9284 } 9285 9286 void WaitForTransactionsHelper::MaybeWaitForTransactions() { 9287 AssertIsOnBackgroundThread(); 9288 MOZ_ASSERT(mState == State::Initial); 9289 9290 RefPtr<ConnectionPool> connectionPool = gConnectionPool.get(); 9291 if (connectionPool) { 9292 mState = State::WaitingForTransactions; 9293 9294 connectionPool->WaitForDatabaseToComplete(mDatabaseId, this); 9295 9296 return; 9297 } 9298 9299 CallCallback(); 9300 } 9301 9302 void WaitForTransactionsHelper::CallCallback() { 9303 AssertIsOnBackgroundThread(); 9304 MOZ_ASSERT(mState == State::Initial || 9305 mState == State::WaitingForTransactions); 9306 9307 const nsCOMPtr<nsIRunnable> callback = std::move(mCallback); 9308 9309 callback->Run(); 9310 9311 mState = State::Complete; 9312 } 9313 9314 NS_IMETHODIMP 9315 WaitForTransactionsHelper::Run() { 9316 MOZ_ASSERT(mState != State::Complete); 9317 MOZ_ASSERT(mCallback); 9318 9319 switch (mState) { 9320 case State::Initial: 9321 MaybeWaitForTransactions(); 9322 break; 9323 9324 case State::WaitingForTransactions: 9325 CallCallback(); 9326 break; 9327 9328 default: 9329 MOZ_CRASH("Should never get here!"); 9330 } 9331 9332 return NS_OK; 9333 } 9334 9335 /******************************************************************************* 9336 * Database 9337 ******************************************************************************/ 9338 9339 Database::Database(SafeRefPtr<Factory> aFactory, 9340 const PrincipalInfo& aPrincipalInfo, 9341 const Maybe<ContentParentId>& aOptionalContentParentId, 9342 const quota::OriginMetadata& aOriginMetadata, 9343 uint32_t aTelemetryId, 9344 SafeRefPtr<FullDatabaseMetadata> aMetadata, 9345 SafeRefPtr<DatabaseFileManager> aFileManager, 9346 ClientDirectoryLockHandle aDirectoryLockHandle, 9347 bool aInPrivateBrowsing, 9348 const Maybe<const CipherKey>& aMaybeKey) 9349 : mFactory(std::move(aFactory)), 9350 mMetadata(std::move(aMetadata)), 9351 mFileManager(std::move(aFileManager)), 9352 mDirectoryLockHandle(std::move(aDirectoryLockHandle)), 9353 mPrincipalInfo(aPrincipalInfo), 9354 mOptionalContentParentId(aOptionalContentParentId), 9355 mOriginMetadata(aOriginMetadata), 9356 mId(mMetadata->mDatabaseId), 9357 mFilePath(mMetadata->mFilePath), 9358 mKey(aMaybeKey), 9359 mTelemetryId(aTelemetryId), 9360 mPersistenceType(mMetadata->mCommonMetadata.persistenceType()), 9361 mInPrivateBrowsing(aInPrivateBrowsing), 9362 mBackgroundThread(GetCurrentSerialEventTarget()) 9363 #ifdef DEBUG 9364 , 9365 mAllBlobsUnmapped(false) 9366 #endif 9367 { 9368 AssertIsOnBackgroundThread(); 9369 MOZ_ASSERT(mFactory); 9370 MOZ_ASSERT(mMetadata); 9371 MOZ_ASSERT(mFileManager); 9372 9373 MOZ_ASSERT(mDirectoryLockHandle); 9374 MOZ_ASSERT(mDirectoryLockHandle->Id() >= 0); 9375 mDirectoryLockId = mDirectoryLockHandle->Id(); 9376 } 9377 9378 template <typename T> 9379 bool Database::InvalidateAll(const nsTBaseHashSet<nsPtrHashKey<T>>& aTable) { 9380 AssertIsOnBackgroundThread(); 9381 9382 const uint32_t count = aTable.Count(); 9383 if (!count) { 9384 return true; 9385 } 9386 9387 // XXX Does this really need to be fallible? 9388 QM_TRY_INSPECT(const auto& elementsToInvalidate, 9389 TransformIntoNewArray( 9390 aTable, [](const auto& entry) { return entry; }, fallible), 9391 false); 9392 9393 IDB_REPORT_INTERNAL_ERR(); 9394 9395 for (const auto& elementToInvalidate : elementsToInvalidate) { 9396 MOZ_ASSERT(elementToInvalidate); 9397 9398 elementToInvalidate->Invalidate(); 9399 } 9400 9401 return true; 9402 } 9403 9404 void Database::Invalidate() { 9405 AssertIsOnBackgroundThread(); 9406 9407 if (mInvalidated) { 9408 return; 9409 } 9410 9411 mInvalidated.Flip(); 9412 9413 if (mActorWasAlive && !mActorDestroyed) { 9414 (void)SendInvalidate(); 9415 } 9416 9417 QM_WARNONLY_TRY(OkIf(InvalidateAll(mTransactions))); 9418 9419 MOZ_ALWAYS_TRUE(CloseInternal()); 9420 } 9421 9422 nsresult Database::EnsureConnection() { 9423 MOZ_ASSERT(!NS_IsMainThread()); 9424 MOZ_ASSERT(!IsOnBackgroundThread()); 9425 9426 AUTO_PROFILER_LABEL("Database::EnsureConnection", DOM); 9427 9428 if (!mConnection || !mConnection->HasStorageConnection()) { 9429 QM_TRY_UNWRAP(mConnection, gConnectionPool->GetOrCreateConnection(*this)); 9430 } 9431 9432 AssertIsOnConnectionThread(); 9433 9434 return NS_OK; 9435 } 9436 9437 bool Database::RegisterTransaction(TransactionBase& aTransaction) { 9438 AssertIsOnBackgroundThread(); 9439 MOZ_ASSERT(!mTransactions.Contains(&aTransaction)); 9440 MOZ_ASSERT(mDirectoryLockHandle); 9441 MOZ_ASSERT(!mInvalidated); 9442 MOZ_ASSERT(!mClosed); 9443 9444 if (NS_WARN_IF(!mTransactions.Insert(&aTransaction, fallible))) { 9445 return false; 9446 } 9447 9448 return true; 9449 } 9450 9451 void Database::UnregisterTransaction(TransactionBase& aTransaction) { 9452 AssertIsOnBackgroundThread(); 9453 MOZ_ASSERT(mTransactions.Contains(&aTransaction)); 9454 9455 mTransactions.Remove(&aTransaction); 9456 9457 MaybeCloseConnection(); 9458 } 9459 9460 void Database::SetActorAlive() { 9461 AssertIsOnBackgroundThread(); 9462 MOZ_ASSERT(!mActorDestroyed); 9463 9464 mActorWasAlive.Flip(); 9465 } 9466 9467 void Database::MapBlob(const IPCBlob& aIPCBlob, 9468 SafeRefPtr<DatabaseFileInfo> aFileInfo) { 9469 AssertIsOnBackgroundThread(); 9470 9471 const RemoteLazyStream& stream = aIPCBlob.inputStream(); 9472 MOZ_ASSERT(stream.type() == RemoteLazyStream::TRemoteLazyInputStream); 9473 9474 nsID id{}; 9475 MOZ_ALWAYS_SUCCEEDS( 9476 stream.get_RemoteLazyInputStream()->GetInternalStreamID(id)); 9477 9478 MOZ_ASSERT(!mMappedBlobs.Contains(id)); 9479 mMappedBlobs.InsertOrUpdate(id, std::move(aFileInfo)); 9480 9481 RefPtr<UnmapBlobCallback> callback = 9482 new UnmapBlobCallback(SafeRefPtrFromThis()); 9483 9484 auto storage = RemoteLazyInputStreamStorage::Get(); 9485 MOZ_ASSERT(storage.isOk()); 9486 storage.inspect()->StoreCallback(id, callback); 9487 } 9488 9489 void Database::Stringify(nsACString& aResult) const { 9490 AssertIsOnBackgroundThread(); 9491 9492 constexpr auto kQuotaGenericDelimiterString = "|"_ns; 9493 9494 aResult.Append( 9495 "DirectoryLock:"_ns + IntToCString(!!mDirectoryLockHandle) + 9496 kQuotaGenericDelimiterString + 9497 // 9498 "Transactions:"_ns + IntToCString(mTransactions.Count()) + 9499 kQuotaGenericDelimiterString + 9500 // 9501 "OtherProcessActor:"_ns + 9502 IntToCString( 9503 BackgroundParent::IsOtherProcessActor(GetBackgroundParent())) + 9504 kQuotaGenericDelimiterString + 9505 // 9506 "Origin:"_ns + AnonymizedOriginString(mOriginMetadata.mOrigin) + 9507 kQuotaGenericDelimiterString + 9508 // 9509 "PersistenceType:"_ns + PersistenceTypeToString(mPersistenceType) + 9510 kQuotaGenericDelimiterString + 9511 // 9512 "Closed:"_ns + IntToCString(static_cast<bool>(mClosed)) + 9513 kQuotaGenericDelimiterString + 9514 // 9515 "Invalidated:"_ns + IntToCString(static_cast<bool>(mInvalidated)) + 9516 kQuotaGenericDelimiterString + 9517 // 9518 "ActorWasAlive:"_ns + IntToCString(static_cast<bool>(mActorWasAlive)) + 9519 kQuotaGenericDelimiterString + 9520 // 9521 "ActorDestroyed:"_ns + IntToCString(static_cast<bool>(mActorDestroyed))); 9522 } 9523 9524 SafeRefPtr<DatabaseFileInfo> Database::GetBlob(const IPCBlob& aIPCBlob) { 9525 AssertIsOnBackgroundThread(); 9526 9527 RefPtr<RemoteLazyInputStream> lazyStream; 9528 switch (aIPCBlob.inputStream().type()) { 9529 case RemoteLazyStream::TIPCStream: { 9530 const InputStreamParams& inputStreamParams = 9531 aIPCBlob.inputStream().get_IPCStream().stream(); 9532 if (inputStreamParams.type() != 9533 InputStreamParams::TRemoteLazyInputStreamParams) { 9534 return nullptr; 9535 } 9536 lazyStream = inputStreamParams.get_RemoteLazyInputStreamParams().stream(); 9537 break; 9538 } 9539 case RemoteLazyStream::TRemoteLazyInputStream: 9540 lazyStream = aIPCBlob.inputStream().get_RemoteLazyInputStream(); 9541 break; 9542 default: 9543 MOZ_ASSERT_UNREACHABLE("Unknown RemoteLazyStream type"); 9544 return nullptr; 9545 } 9546 9547 if (!lazyStream) { 9548 MOZ_ASSERT_UNREACHABLE("Unexpected null stream"); 9549 return nullptr; 9550 } 9551 9552 nsID id{}; 9553 nsresult rv = lazyStream->GetInternalStreamID(id); 9554 if (NS_FAILED(rv)) { 9555 MOZ_ASSERT_UNREACHABLE( 9556 "Received RemoteLazyInputStream doesn't have an actor connection"); 9557 return nullptr; 9558 } 9559 9560 const auto fileInfo = mMappedBlobs.Lookup(id); 9561 return fileInfo ? fileInfo->clonePtr() : nullptr; 9562 } 9563 9564 void Database::UnmapBlob(const nsID& aID) { 9565 AssertIsOnBackgroundThread(); 9566 9567 MOZ_ASSERT_IF(!mAllBlobsUnmapped, mMappedBlobs.Contains(aID)); 9568 mMappedBlobs.Remove(aID); 9569 } 9570 9571 void Database::UnmapAllBlobs() { 9572 AssertIsOnBackgroundThread(); 9573 9574 #ifdef DEBUG 9575 mAllBlobsUnmapped = true; 9576 #endif 9577 9578 mMappedBlobs.Clear(); 9579 } 9580 9581 bool Database::CloseInternal() { 9582 AssertIsOnBackgroundThread(); 9583 9584 if (mClosed) { 9585 if (NS_WARN_IF(!IsInvalidated())) { 9586 // Signal misbehaving child for sending the close message twice. 9587 return false; 9588 } 9589 9590 // Ignore harmless race when we just invalidated the database. 9591 return true; 9592 } 9593 9594 mClosed.Flip(); 9595 9596 if (gConnectionPool) { 9597 gConnectionPool->CloseDatabaseWhenIdle(Id()); 9598 } 9599 9600 DatabaseActorInfo* info; 9601 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info)); 9602 9603 MOZ_ASSERT(info->mLiveDatabases.contains(this)); 9604 9605 if (info->mWaitingFactoryOp) { 9606 info->mWaitingFactoryOp->NoteDatabaseClosed(this); 9607 } 9608 9609 MaybeCloseConnection(); 9610 9611 return true; 9612 } 9613 9614 void Database::MaybeCloseConnection() { 9615 AssertIsOnBackgroundThread(); 9616 9617 if (!mTransactions.Count() && IsClosed() && mDirectoryLockHandle) { 9618 nsCOMPtr<nsIRunnable> callback = 9619 NewRunnableMethod("dom::indexedDB::Database::ConnectionClosedCallback", 9620 this, &Database::ConnectionClosedCallback); 9621 9622 RefPtr<WaitForTransactionsHelper> helper = 9623 new WaitForTransactionsHelper(Id(), callback); 9624 helper->WaitForTransactions(); 9625 } 9626 } 9627 9628 void Database::ConnectionClosedCallback() { 9629 AssertIsOnBackgroundThread(); 9630 MOZ_ASSERT(mClosed); 9631 MOZ_ASSERT(!mTransactions.Count()); 9632 9633 { 9634 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 9635 } 9636 9637 CleanupMetadata(); 9638 9639 UnmapAllBlobs(); 9640 9641 if (IsInvalidated() && IsActorAlive()) { 9642 // Step 3 and 4 of "5.2 Closing a Database": 9643 // 1. Wait for all transactions to complete. 9644 // 2. Fire a close event if forced flag is set, i.e., IsInvalidated() in our 9645 // implementation. 9646 (void)SendCloseAfterInvalidationComplete(); 9647 } 9648 } 9649 9650 void Database::CleanupMetadata() { 9651 AssertIsOnBackgroundThread(); 9652 9653 DatabaseActorInfo* info; 9654 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info)); 9655 removeFrom(info->mLiveDatabases); 9656 9657 QuotaManager::MaybeRecordQuotaClientShutdownStep( 9658 quota::Client::IDB, "Live database entry removed"_ns); 9659 9660 if (info->mLiveDatabases.isEmpty()) { 9661 MOZ_ASSERT(!info->mWaitingFactoryOp || 9662 !info->mWaitingFactoryOp->HasBlockedDatabases()); 9663 gLiveDatabaseHashtable->Remove(Id()); 9664 9665 QuotaManager::MaybeRecordQuotaClientShutdownStep( 9666 quota::Client::IDB, "gLiveDatabaseHashtable entry removed"_ns); 9667 } 9668 9669 // Match the IncreaseBusyCount in OpenDatabaseOp::EnsureDatabaseActor(). 9670 DecreaseBusyCount(); 9671 } 9672 9673 void Database::ActorDestroy(ActorDestroyReason aWhy) { 9674 AssertIsOnBackgroundThread(); 9675 9676 mActorDestroyed.Flip(); 9677 9678 if (!IsInvalidated()) { 9679 Invalidate(); 9680 } 9681 } 9682 9683 PBackgroundIDBDatabaseFileParent* 9684 Database::AllocPBackgroundIDBDatabaseFileParent(const IPCBlob& aIPCBlob) { 9685 AssertIsOnBackgroundThread(); 9686 9687 SafeRefPtr<DatabaseFileInfo> fileInfo = GetBlob(aIPCBlob); 9688 RefPtr<DatabaseFile> actor; 9689 9690 if (fileInfo) { 9691 actor = new DatabaseFile(std::move(fileInfo)); 9692 } else { 9693 // This is a blob we haven't seen before. 9694 fileInfo = mFileManager->CreateFileInfo(); 9695 if (NS_WARN_IF(!fileInfo)) { 9696 return nullptr; 9697 } 9698 9699 actor = new DatabaseFile(IPCBlobUtils::Deserialize(aIPCBlob), 9700 std::move(fileInfo)); 9701 } 9702 9703 MOZ_ASSERT(actor); 9704 9705 return actor.forget().take(); 9706 } 9707 9708 bool Database::DeallocPBackgroundIDBDatabaseFileParent( 9709 PBackgroundIDBDatabaseFileParent* aActor) { 9710 AssertIsOnBackgroundThread(); 9711 MOZ_ASSERT(aActor); 9712 9713 RefPtr<DatabaseFile> actor = dont_AddRef(static_cast<DatabaseFile*>(aActor)); 9714 return true; 9715 } 9716 9717 already_AddRefed<PBackgroundIDBTransactionParent> 9718 Database::AllocPBackgroundIDBTransactionParent( 9719 const nsTArray<nsString>& aObjectStoreNames, const Mode& aMode, 9720 const Durability& aDurability) { 9721 AssertIsOnBackgroundThread(); 9722 9723 // Once a database is closed it must not try to open new transactions. 9724 if (NS_WARN_IF(mClosed)) { 9725 MOZ_ASSERT_UNLESS_FUZZING(mInvalidated); 9726 return nullptr; 9727 } 9728 9729 if (NS_AUUF_OR_WARN_IF(aObjectStoreNames.IsEmpty())) { 9730 return nullptr; 9731 } 9732 9733 if (NS_AUUF_OR_WARN_IF(aMode != IDBTransaction::Mode::ReadOnly && 9734 aMode != IDBTransaction::Mode::ReadWrite && 9735 aMode != IDBTransaction::Mode::ReadWriteFlush && 9736 aMode != IDBTransaction::Mode::Cleanup)) { 9737 return nullptr; 9738 } 9739 9740 if (NS_AUUF_OR_WARN_IF(aDurability != IDBTransaction::Durability::Default && 9741 aDurability != IDBTransaction::Durability::Strict && 9742 aDurability != IDBTransaction::Durability::Relaxed)) { 9743 return nullptr; 9744 } 9745 9746 const ObjectStoreTable& objectStores = mMetadata->mObjectStores; 9747 const uint32_t nameCount = aObjectStoreNames.Length(); 9748 9749 if (NS_AUUF_OR_WARN_IF(nameCount > objectStores.Count())) { 9750 return nullptr; 9751 } 9752 9753 QM_TRY_UNWRAP( 9754 auto objectStoreMetadatas, 9755 TransformIntoNewArrayAbortOnErr( 9756 aObjectStoreNames, 9757 [lastName = Maybe<const nsString&>{}, 9758 &objectStores](const nsString& name) mutable 9759 -> mozilla::Result<SafeRefPtr<FullObjectStoreMetadata>, 9760 nsresult> { 9761 if (lastName) { 9762 // Make sure that this name is sorted properly and not a 9763 // duplicate. 9764 if (NS_AUUF_OR_WARN_IF(name <= lastName.ref())) { 9765 return Err(NS_ERROR_FAILURE); 9766 } 9767 } 9768 lastName = SomeRef(name); 9769 9770 const auto foundIt = 9771 std::find_if(objectStores.cbegin(), objectStores.cend(), 9772 [&name](const auto& entry) { 9773 const auto& value = entry.GetData(); 9774 MOZ_ASSERT(entry.GetKey()); 9775 return name == value->mCommonMetadata.name() && 9776 !value->mDeleted; 9777 }); 9778 if (foundIt == objectStores.cend()) { 9779 MOZ_ASSERT_UNLESS_FUZZING(false, "ObjectStore not found."); 9780 return Err(NS_ERROR_FAILURE); 9781 } 9782 9783 return foundIt->GetData().clonePtr(); 9784 }, 9785 fallible), 9786 nullptr); 9787 9788 return MakeSafeRefPtr<NormalTransaction>(SafeRefPtrFromThis(), aMode, 9789 aDurability, 9790 std::move(objectStoreMetadatas)) 9791 .forget(); 9792 } 9793 9794 mozilla::ipc::IPCResult Database::RecvPBackgroundIDBTransactionConstructor( 9795 PBackgroundIDBTransactionParent* aActor, 9796 nsTArray<nsString>&& aObjectStoreNames, const Mode& aMode, 9797 const Durability& aDurability) { 9798 AssertIsOnBackgroundThread(); 9799 MOZ_ASSERT(aActor); 9800 MOZ_ASSERT(!aObjectStoreNames.IsEmpty()); 9801 MOZ_ASSERT(aMode == IDBTransaction::Mode::ReadOnly || 9802 aMode == IDBTransaction::Mode::ReadWrite || 9803 aMode == IDBTransaction::Mode::ReadWriteFlush || 9804 aMode == IDBTransaction::Mode::Cleanup); 9805 MOZ_ASSERT(aDurability == IDBTransaction::Durability::Default || 9806 aDurability == IDBTransaction::Durability::Strict || 9807 aDurability == IDBTransaction::Durability::Relaxed); 9808 MOZ_ASSERT(!mClosed); 9809 9810 if (IsInvalidated()) { 9811 // This is an expected race. We don't want the child to die here, just don't 9812 // actually do any work. 9813 return IPC_OK(); 9814 } 9815 9816 if (!gConnectionPool) { 9817 gConnectionPool = new ConnectionPool(); 9818 } 9819 9820 auto* transaction = static_cast<NormalTransaction*>(aActor); 9821 9822 RefPtr<StartTransactionOp> startOp = new StartTransactionOp( 9823 SafeRefPtr{transaction, AcquireStrongRefFromRawPtr{}}); 9824 9825 uint64_t transactionId = startOp->StartOnConnectionPool( 9826 GetLoggingInfo()->Id(), mMetadata->mDatabaseId, 9827 transaction->LoggingSerialNumber(), aObjectStoreNames, 9828 aMode != IDBTransaction::Mode::ReadOnly); 9829 9830 transaction->Init(transactionId); 9831 9832 if (NS_WARN_IF(!RegisterTransaction(*transaction))) { 9833 IDB_REPORT_INTERNAL_ERR(); 9834 transaction->Abort(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, /* aForce */ false); 9835 return IPC_OK(); 9836 } 9837 9838 return IPC_OK(); 9839 } 9840 9841 mozilla::ipc::IPCResult Database::RecvDeleteMe() { 9842 AssertIsOnBackgroundThread(); 9843 MOZ_ASSERT(!mActorDestroyed); 9844 9845 QM_WARNONLY_TRY(OkIf(PBackgroundIDBDatabaseParent::Send__delete__(this))); 9846 9847 return IPC_OK(); 9848 } 9849 9850 mozilla::ipc::IPCResult Database::RecvBlocked() { 9851 AssertIsOnBackgroundThread(); 9852 9853 if (NS_WARN_IF(mClosed)) { 9854 // Even though the sender checks the DB for not being closed, too, 9855 // there is a potential race with an ongoing origin clearing which 9856 // might have invalidated the DB in the meantime. Just ignore. 9857 return IPC_OK(); 9858 } 9859 9860 DatabaseActorInfo* info; 9861 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info)); 9862 MOZ_ASSERT(info->mLiveDatabases.contains(this)); 9863 9864 if (NS_WARN_IF(!info->mWaitingFactoryOp)) { 9865 return IPC_FAIL(this, "Database info has no mWaitingFactoryOp!"); 9866 } 9867 9868 info->mWaitingFactoryOp->NoteDatabaseBlocked(this); 9869 9870 return IPC_OK(); 9871 } 9872 9873 mozilla::ipc::IPCResult Database::RecvClose() { 9874 AssertIsOnBackgroundThread(); 9875 9876 if (NS_WARN_IF(!CloseInternal())) { 9877 return IPC_FAIL(this, "CloseInternal failed!"); 9878 } 9879 9880 return IPC_OK(); 9881 } 9882 9883 void Database::StartTransactionOp::RunOnConnectionThread() { 9884 MOZ_ASSERT(!IsOnBackgroundThread()); 9885 MOZ_ASSERT(!HasFailed()); 9886 9887 IDB_LOG_MARK_PARENT_TRANSACTION("Beginning database work", "DB Start", 9888 IDB_LOG_ID_STRING(mBackgroundChildLoggingId), 9889 mTransactionLoggingSerialNumber); 9890 9891 TransactionDatabaseOperationBase::RunOnConnectionThread(); 9892 } 9893 9894 nsresult Database::StartTransactionOp::DoDatabaseWork( 9895 DatabaseConnection* aConnection) { 9896 MOZ_ASSERT(aConnection); 9897 aConnection->AssertIsOnConnectionThread(); 9898 9899 Transaction().SetActiveOnConnectionThread(); 9900 9901 if (Transaction().GetMode() == IDBTransaction::Mode::Cleanup) { 9902 DebugOnly<nsresult> rv = aConnection->DisableQuotaChecks(); 9903 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 9904 "DisableQuotaChecks failed, trying to continue " 9905 "cleanup transaction with quota checks enabled"); 9906 } 9907 9908 if (Transaction().GetMode() != IDBTransaction::Mode::ReadOnly) { 9909 QM_TRY(MOZ_TO_RESULT( 9910 aConnection->BeginWriteTransaction(Transaction().GetDurability()))); 9911 } 9912 9913 return NS_OK; 9914 } 9915 9916 nsresult Database::StartTransactionOp::SendSuccessResult() { 9917 // We don't need to do anything here. 9918 return NS_OK; 9919 } 9920 9921 bool Database::StartTransactionOp::SendFailureResult( 9922 nsresult /* aResultCode */) { 9923 IDB_REPORT_INTERNAL_ERR(); 9924 9925 // Abort the transaction. 9926 return false; 9927 } 9928 9929 void Database::StartTransactionOp::Cleanup() { 9930 #ifdef DEBUG 9931 // StartTransactionOp is not a normal database operation that is tied to an 9932 // actor. Do this to make our assertions happy. 9933 NoteActorDestroyed(); 9934 #endif 9935 9936 TransactionDatabaseOperationBase::Cleanup(); 9937 } 9938 9939 /******************************************************************************* 9940 * TransactionBase 9941 ******************************************************************************/ 9942 9943 TransactionBase::TransactionBase(SafeRefPtr<Database> aDatabase, Mode aMode, 9944 Durability aDurability) 9945 : mDatabase(std::move(aDatabase)), 9946 mDatabaseId(mDatabase->Id()), 9947 mLoggingSerialNumber( 9948 mDatabase->GetLoggingInfo()->NextTransactionSN(aMode)), 9949 mActiveRequestCount(0), 9950 mInvalidatedOnAnyThread(false), 9951 mMode(aMode), 9952 mDurability(aDurability), 9953 mResultCode(NS_OK) { 9954 AssertIsOnBackgroundThread(); 9955 MOZ_ASSERT(mDatabase); 9956 MOZ_ASSERT(mLoggingSerialNumber); 9957 } 9958 9959 TransactionBase::~TransactionBase() { 9960 MOZ_ASSERT(!mActiveRequestCount); 9961 MOZ_ASSERT(mActorDestroyed); 9962 MOZ_ASSERT_IF(mInitialized, mCommittedOrAborted); 9963 } 9964 9965 void TransactionBase::Abort(nsresult aResultCode, bool aForce) { 9966 AssertIsOnBackgroundThread(); 9967 MOZ_ASSERT(NS_FAILED(aResultCode)); 9968 9969 if (NS_SUCCEEDED(mResultCode)) { 9970 mResultCode = aResultCode; 9971 } 9972 9973 if (aForce) { 9974 mForceAborted.EnsureFlipped(); 9975 } 9976 9977 MaybeCommitOrAbort(); 9978 } 9979 9980 mozilla::ipc::IPCResult TransactionBase::RecvCommit( 9981 IProtocol* aActor, const Maybe<int64_t> aLastRequest) { 9982 AssertIsOnBackgroundThread(); 9983 9984 if (NS_WARN_IF(mCommitOrAbortReceived)) { 9985 return IPC_FAIL( 9986 aActor, "Attempt to commit an already comitted/aborted transaction!"); 9987 } 9988 9989 mCommitOrAbortReceived.Flip(); 9990 mLastRequestBeforeCommit.init(aLastRequest); 9991 MaybeCommitOrAbort(); 9992 9993 return IPC_OK(); 9994 } 9995 9996 mozilla::ipc::IPCResult TransactionBase::RecvAbort(IProtocol* aActor, 9997 nsresult aResultCode) { 9998 AssertIsOnBackgroundThread(); 9999 10000 if (NS_WARN_IF(NS_SUCCEEDED(aResultCode))) { 10001 return IPC_FAIL(aActor, "aResultCode must not be a success code!"); 10002 } 10003 10004 if (NS_WARN_IF(NS_ERROR_GET_MODULE(aResultCode) != 10005 NS_ERROR_MODULE_DOM_INDEXEDDB)) { 10006 return IPC_FAIL(aActor, "aResultCode does not refer to IndexedDB!"); 10007 } 10008 10009 if (NS_WARN_IF(mCommitOrAbortReceived)) { 10010 return IPC_FAIL( 10011 aActor, "Attempt to abort an already comitted/aborted transaction!"); 10012 } 10013 10014 mCommitOrAbortReceived.Flip(); 10015 Abort(aResultCode, /* aForce */ false); 10016 10017 return IPC_OK(); 10018 } 10019 10020 void TransactionBase::CommitOrAbort() { 10021 AssertIsOnBackgroundThread(); 10022 10023 mCommittedOrAborted.Flip(); 10024 10025 if (!mInitialized) { 10026 return; 10027 } 10028 10029 // In case of a failed request and explicitly committed transaction, abort 10030 // (cf. https://w3c.github.io/IndexedDB/#async-execute-request step 5.3 10031 // vs. 5.4). It's worth emphasizing this can only happen here when we are 10032 // committing explicitly, otherwise the decision is made by the child. 10033 if (NS_SUCCEEDED(mResultCode) && mLastFailedRequest && 10034 *mLastRequestBeforeCommit && 10035 *mLastFailedRequest == **mLastRequestBeforeCommit) { 10036 mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR; 10037 } 10038 10039 RefPtr<CommitOp> commitOp = 10040 new CommitOp(SafeRefPtrFromThis(), ClampResultCode(mResultCode)); 10041 10042 gConnectionPool->Finish(TransactionId(), commitOp); 10043 } 10044 10045 SafeRefPtr<FullObjectStoreMetadata> 10046 TransactionBase::GetMetadataForObjectStoreId( 10047 IndexOrObjectStoreId aObjectStoreId) const { 10048 AssertIsOnBackgroundThread(); 10049 MOZ_ASSERT(aObjectStoreId); 10050 10051 if (!aObjectStoreId) { 10052 return nullptr; 10053 } 10054 10055 auto metadata = mDatabase->Metadata().mObjectStores.Lookup(aObjectStoreId); 10056 if (!metadata || (*metadata)->mDeleted) { 10057 return nullptr; 10058 } 10059 10060 MOZ_ASSERT((*metadata)->mCommonMetadata.id() == aObjectStoreId); 10061 10062 return metadata->clonePtr(); 10063 } 10064 10065 SafeRefPtr<FullIndexMetadata> TransactionBase::GetMetadataForIndexId( 10066 FullObjectStoreMetadata& aObjectStoreMetadata, 10067 IndexOrObjectStoreId aIndexId) const { 10068 AssertIsOnBackgroundThread(); 10069 MOZ_ASSERT(aIndexId); 10070 10071 if (!aIndexId) { 10072 return nullptr; 10073 } 10074 10075 auto metadata = aObjectStoreMetadata.mIndexes.Lookup(aIndexId); 10076 if (!metadata || (*metadata)->mDeleted) { 10077 return nullptr; 10078 } 10079 10080 MOZ_ASSERT((*metadata)->mCommonMetadata.id() == aIndexId); 10081 10082 return metadata->clonePtr(); 10083 } 10084 10085 void TransactionBase::NoteModifiedAutoIncrementObjectStore( 10086 const SafeRefPtr<FullObjectStoreMetadata>& aMetadata) { 10087 AssertIsOnConnectionThread(); 10088 10089 if (!mModifiedAutoIncrementObjectStoreMetadataArray.Contains(aMetadata)) { 10090 mModifiedAutoIncrementObjectStoreMetadataArray.AppendElement( 10091 aMetadata.clonePtr()); 10092 } 10093 } 10094 10095 void TransactionBase::ForgetModifiedAutoIncrementObjectStore( 10096 FullObjectStoreMetadata& aMetadata) { 10097 AssertIsOnConnectionThread(); 10098 10099 mModifiedAutoIncrementObjectStoreMetadataArray.RemoveElement(&aMetadata); 10100 } 10101 10102 bool TransactionBase::VerifyRequestParams(const RequestParams& aParams) const { 10103 AssertIsOnBackgroundThread(); 10104 MOZ_ASSERT(aParams.type() != RequestParams::T__None); 10105 10106 switch (aParams.type()) { 10107 case RequestParams::TObjectStoreAddParams: { 10108 const ObjectStoreAddPutParams& params = 10109 aParams.get_ObjectStoreAddParams().commonParams(); 10110 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params))) { 10111 return false; 10112 } 10113 break; 10114 } 10115 10116 case RequestParams::TObjectStorePutParams: { 10117 const ObjectStoreAddPutParams& params = 10118 aParams.get_ObjectStorePutParams().commonParams(); 10119 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params))) { 10120 return false; 10121 } 10122 break; 10123 } 10124 10125 case RequestParams::TObjectStoreGetParams: { 10126 const ObjectStoreGetParams& params = aParams.get_ObjectStoreGetParams(); 10127 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10128 GetMetadataForObjectStoreId(params.objectStoreId()); 10129 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10130 return false; 10131 } 10132 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) { 10133 return false; 10134 } 10135 break; 10136 } 10137 10138 case RequestParams::TObjectStoreGetKeyParams: { 10139 const ObjectStoreGetKeyParams& params = 10140 aParams.get_ObjectStoreGetKeyParams(); 10141 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10142 GetMetadataForObjectStoreId(params.objectStoreId()); 10143 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10144 return false; 10145 } 10146 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) { 10147 return false; 10148 } 10149 break; 10150 } 10151 10152 case RequestParams::TObjectStoreGetAllParams: { 10153 const ObjectStoreGetAllParams& params = 10154 aParams.get_ObjectStoreGetAllParams(); 10155 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10156 GetMetadataForObjectStoreId(params.objectStoreId()); 10157 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10158 return false; 10159 } 10160 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) { 10161 return false; 10162 } 10163 break; 10164 } 10165 10166 case RequestParams::TObjectStoreGetAllKeysParams: { 10167 const ObjectStoreGetAllKeysParams& params = 10168 aParams.get_ObjectStoreGetAllKeysParams(); 10169 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10170 GetMetadataForObjectStoreId(params.objectStoreId()); 10171 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10172 return false; 10173 } 10174 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) { 10175 return false; 10176 } 10177 break; 10178 } 10179 10180 case RequestParams::TObjectStoreDeleteParams: { 10181 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite && 10182 mMode != IDBTransaction::Mode::ReadWriteFlush && 10183 mMode != IDBTransaction::Mode::Cleanup && 10184 mMode != IDBTransaction::Mode::VersionChange)) { 10185 return false; 10186 } 10187 10188 const ObjectStoreDeleteParams& params = 10189 aParams.get_ObjectStoreDeleteParams(); 10190 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10191 GetMetadataForObjectStoreId(params.objectStoreId()); 10192 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10193 return false; 10194 } 10195 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) { 10196 return false; 10197 } 10198 break; 10199 } 10200 10201 case RequestParams::TObjectStoreClearParams: { 10202 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite && 10203 mMode != IDBTransaction::Mode::ReadWriteFlush && 10204 mMode != IDBTransaction::Mode::Cleanup && 10205 mMode != IDBTransaction::Mode::VersionChange)) { 10206 return false; 10207 } 10208 10209 const ObjectStoreClearParams& params = 10210 aParams.get_ObjectStoreClearParams(); 10211 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10212 GetMetadataForObjectStoreId(params.objectStoreId()); 10213 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10214 return false; 10215 } 10216 break; 10217 } 10218 10219 case RequestParams::TObjectStoreCountParams: { 10220 const ObjectStoreCountParams& params = 10221 aParams.get_ObjectStoreCountParams(); 10222 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10223 GetMetadataForObjectStoreId(params.objectStoreId()); 10224 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10225 return false; 10226 } 10227 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) { 10228 return false; 10229 } 10230 break; 10231 } 10232 10233 case RequestParams::TIndexGetParams: { 10234 const IndexGetParams& params = aParams.get_IndexGetParams(); 10235 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10236 GetMetadataForObjectStoreId(params.objectStoreId()); 10237 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10238 return false; 10239 } 10240 const SafeRefPtr<FullIndexMetadata> indexMetadata = 10241 GetMetadataForIndexId(*objectStoreMetadata, params.indexId()); 10242 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) { 10243 return false; 10244 } 10245 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) { 10246 return false; 10247 } 10248 break; 10249 } 10250 10251 case RequestParams::TIndexGetKeyParams: { 10252 const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams(); 10253 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10254 GetMetadataForObjectStoreId(params.objectStoreId()); 10255 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10256 return false; 10257 } 10258 const SafeRefPtr<FullIndexMetadata> indexMetadata = 10259 GetMetadataForIndexId(*objectStoreMetadata, params.indexId()); 10260 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) { 10261 return false; 10262 } 10263 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.keyRange()))) { 10264 return false; 10265 } 10266 break; 10267 } 10268 10269 case RequestParams::TIndexGetAllParams: { 10270 const IndexGetAllParams& params = aParams.get_IndexGetAllParams(); 10271 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10272 GetMetadataForObjectStoreId(params.objectStoreId()); 10273 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10274 return false; 10275 } 10276 const SafeRefPtr<FullIndexMetadata> indexMetadata = 10277 GetMetadataForIndexId(*objectStoreMetadata, params.indexId()); 10278 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) { 10279 return false; 10280 } 10281 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) { 10282 return false; 10283 } 10284 break; 10285 } 10286 10287 case RequestParams::TIndexGetAllKeysParams: { 10288 const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams(); 10289 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10290 GetMetadataForObjectStoreId(params.objectStoreId()); 10291 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10292 return false; 10293 } 10294 const SafeRefPtr<FullIndexMetadata> indexMetadata = 10295 GetMetadataForIndexId(*objectStoreMetadata, params.indexId()); 10296 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) { 10297 return false; 10298 } 10299 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) { 10300 return false; 10301 } 10302 break; 10303 } 10304 10305 case RequestParams::TIndexCountParams: { 10306 const IndexCountParams& params = aParams.get_IndexCountParams(); 10307 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 10308 GetMetadataForObjectStoreId(params.objectStoreId()); 10309 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10310 return false; 10311 } 10312 const SafeRefPtr<FullIndexMetadata> indexMetadata = 10313 GetMetadataForIndexId(*objectStoreMetadata, params.indexId()); 10314 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) { 10315 return false; 10316 } 10317 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) { 10318 return false; 10319 } 10320 break; 10321 } 10322 10323 default: 10324 MOZ_CRASH("Should never get here!"); 10325 } 10326 10327 return true; 10328 } 10329 10330 bool TransactionBase::VerifyRequestParams( 10331 const SerializedKeyRange& aParams) const { 10332 AssertIsOnBackgroundThread(); 10333 10334 // XXX Check more here? 10335 10336 if (aParams.isOnly()) { 10337 if (NS_AUUF_OR_WARN_IF(aParams.lower().IsUnset())) { 10338 return false; 10339 } 10340 if (NS_AUUF_OR_WARN_IF(!aParams.upper().IsUnset())) { 10341 return false; 10342 } 10343 if (NS_AUUF_OR_WARN_IF(aParams.lowerOpen())) { 10344 return false; 10345 } 10346 if (NS_AUUF_OR_WARN_IF(aParams.upperOpen())) { 10347 return false; 10348 } 10349 } else if (NS_AUUF_OR_WARN_IF(aParams.lower().IsUnset() && 10350 aParams.upper().IsUnset())) { 10351 return false; 10352 } 10353 10354 return true; 10355 } 10356 10357 bool TransactionBase::VerifyRequestParams( 10358 const ObjectStoreAddPutParams& aParams) const { 10359 AssertIsOnBackgroundThread(); 10360 10361 if (NS_AUUF_OR_WARN_IF(mMode != IDBTransaction::Mode::ReadWrite && 10362 mMode != IDBTransaction::Mode::ReadWriteFlush && 10363 mMode != IDBTransaction::Mode::VersionChange)) { 10364 return false; 10365 } 10366 10367 SafeRefPtr<FullObjectStoreMetadata> objMetadata = 10368 GetMetadataForObjectStoreId(aParams.objectStoreId()); 10369 if (NS_AUUF_OR_WARN_IF(!objMetadata)) { 10370 return false; 10371 } 10372 10373 if (NS_AUUF_OR_WARN_IF(!aParams.cloneInfo().data().data.Size())) { 10374 return false; 10375 } 10376 10377 if (objMetadata->mCommonMetadata.autoIncrement() && 10378 objMetadata->mCommonMetadata.keyPath().IsValid() && 10379 aParams.key().IsUnset()) { 10380 const SerializedStructuredCloneWriteInfo& cloneInfo = aParams.cloneInfo(); 10381 10382 if (NS_AUUF_OR_WARN_IF(!cloneInfo.offsetToKeyProp())) { 10383 return false; 10384 } 10385 10386 if (NS_AUUF_OR_WARN_IF(cloneInfo.data().data.Size() < sizeof(uint64_t))) { 10387 return false; 10388 } 10389 10390 if (NS_AUUF_OR_WARN_IF(cloneInfo.offsetToKeyProp() > 10391 (cloneInfo.data().data.Size() - sizeof(uint64_t)))) { 10392 return false; 10393 } 10394 } else if (NS_AUUF_OR_WARN_IF(aParams.cloneInfo().offsetToKeyProp())) { 10395 return false; 10396 } 10397 10398 for (const auto& updateInfo : aParams.indexUpdateInfos()) { 10399 SafeRefPtr<FullIndexMetadata> indexMetadata = 10400 GetMetadataForIndexId(*objMetadata, updateInfo.indexId()); 10401 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) { 10402 return false; 10403 } 10404 10405 if (NS_AUUF_OR_WARN_IF(updateInfo.value().IsUnset())) { 10406 return false; 10407 } 10408 10409 MOZ_ASSERT(!updateInfo.value().GetBuffer().IsEmpty()); 10410 } 10411 10412 for (const FileAddInfo& fileAddInfo : aParams.fileAddInfos()) { 10413 const PBackgroundIDBDatabaseFileParent* file = 10414 fileAddInfo.file().AsParent(); 10415 10416 switch (fileAddInfo.type()) { 10417 case StructuredCloneFileBase::eBlob: 10418 if (NS_AUUF_OR_WARN_IF(!file)) { 10419 return false; 10420 } 10421 break; 10422 10423 case StructuredCloneFileBase::eMutableFile: { 10424 return false; 10425 } 10426 10427 case StructuredCloneFileBase::eStructuredClone: 10428 case StructuredCloneFileBase::eWasmBytecode: 10429 case StructuredCloneFileBase::eWasmCompiled: 10430 case StructuredCloneFileBase::eEndGuard: 10431 MOZ_ASSERT_UNLESS_FUZZING(false, "Unsupported."); 10432 return false; 10433 10434 default: 10435 MOZ_CRASH("Should never get here!"); 10436 } 10437 } 10438 10439 return true; 10440 } 10441 10442 bool TransactionBase::VerifyRequestParams( 10443 const Maybe<SerializedKeyRange>& aParams) const { 10444 AssertIsOnBackgroundThread(); 10445 10446 if (aParams.isSome()) { 10447 if (NS_AUUF_OR_WARN_IF(!VerifyRequestParams(aParams.ref()))) { 10448 return false; 10449 } 10450 } 10451 10452 return true; 10453 } 10454 10455 void TransactionBase::NoteActiveRequest() { 10456 AssertIsOnBackgroundThread(); 10457 MOZ_ASSERT(mActiveRequestCount < UINT64_MAX); 10458 10459 mActiveRequestCount++; 10460 } 10461 10462 void TransactionBase::NoteFinishedRequest(const int64_t aRequestId, 10463 const nsresult aResultCode) { 10464 AssertIsOnBackgroundThread(); 10465 MOZ_ASSERT(mActiveRequestCount); 10466 10467 mActiveRequestCount--; 10468 10469 if (NS_FAILED(aResultCode)) { 10470 mLastFailedRequest = Some(aRequestId); 10471 } 10472 10473 MaybeCommitOrAbort(); 10474 } 10475 10476 void TransactionBase::Invalidate() { 10477 AssertIsOnBackgroundThread(); 10478 MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread); 10479 10480 if (!mInvalidated) { 10481 mInvalidated.Flip(); 10482 mInvalidatedOnAnyThread = true; 10483 10484 Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, /* aForce */ false); 10485 } 10486 } 10487 10488 PBackgroundIDBRequestParent* TransactionBase::AllocRequest( 10489 const int64_t aRequestId, RequestParams&& aParams, bool aTrustParams) { 10490 AssertIsOnBackgroundThread(); 10491 MOZ_ASSERT(aParams.type() != RequestParams::T__None); 10492 10493 #ifdef DEBUG 10494 // Always verify parameters in DEBUG builds! 10495 aTrustParams = false; 10496 #endif 10497 10498 if (NS_AUUF_OR_WARN_IF(!aTrustParams && !VerifyRequestParams(aParams))) { 10499 return nullptr; 10500 } 10501 10502 if (NS_AUUF_OR_WARN_IF(mCommitOrAbortReceived)) { 10503 return nullptr; 10504 } 10505 10506 RefPtr<NormalTransactionOp> actor; 10507 10508 switch (aParams.type()) { 10509 case RequestParams::TObjectStoreAddParams: 10510 case RequestParams::TObjectStorePutParams: 10511 actor = new ObjectStoreAddOrPutRequestOp(SafeRefPtrFromThis(), aRequestId, 10512 std::move(aParams)); 10513 break; 10514 10515 case RequestParams::TObjectStoreGetParams: 10516 actor = 10517 new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, 10518 /* aGetAll */ false); 10519 break; 10520 10521 case RequestParams::TObjectStoreGetAllParams: 10522 actor = 10523 new ObjectStoreGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, 10524 /* aGetAll */ true); 10525 break; 10526 10527 case RequestParams::TObjectStoreGetKeyParams: 10528 actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, 10529 aParams, 10530 /* aGetAll */ false); 10531 break; 10532 10533 case RequestParams::TObjectStoreGetAllKeysParams: 10534 actor = new ObjectStoreGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, 10535 aParams, 10536 /* aGetAll */ true); 10537 break; 10538 10539 case RequestParams::TObjectStoreDeleteParams: 10540 actor = 10541 new ObjectStoreDeleteRequestOp(SafeRefPtrFromThis(), aRequestId, 10542 aParams.get_ObjectStoreDeleteParams()); 10543 break; 10544 10545 case RequestParams::TObjectStoreClearParams: 10546 actor = 10547 new ObjectStoreClearRequestOp(SafeRefPtrFromThis(), aRequestId, 10548 aParams.get_ObjectStoreClearParams()); 10549 break; 10550 10551 case RequestParams::TObjectStoreCountParams: 10552 actor = 10553 new ObjectStoreCountRequestOp(SafeRefPtrFromThis(), aRequestId, 10554 aParams.get_ObjectStoreCountParams()); 10555 break; 10556 10557 case RequestParams::TIndexGetParams: 10558 actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, 10559 /* aGetAll */ false); 10560 break; 10561 10562 case RequestParams::TIndexGetKeyParams: 10563 actor = 10564 new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, 10565 /* aGetAll */ false); 10566 break; 10567 10568 case RequestParams::TIndexGetAllParams: 10569 actor = new IndexGetRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, 10570 /* aGetAll */ true); 10571 break; 10572 10573 case RequestParams::TIndexGetAllKeysParams: 10574 actor = 10575 new IndexGetKeyRequestOp(SafeRefPtrFromThis(), aRequestId, aParams, 10576 /* aGetAll */ true); 10577 break; 10578 10579 case RequestParams::TIndexCountParams: 10580 actor = 10581 new IndexCountRequestOp(SafeRefPtrFromThis(), aRequestId, aParams); 10582 break; 10583 10584 default: 10585 MOZ_CRASH("Should never get here!"); 10586 } 10587 10588 MOZ_ASSERT(actor); 10589 10590 // Transfer ownership to IPDL. 10591 return actor.forget().take(); 10592 } 10593 10594 bool TransactionBase::StartRequest(PBackgroundIDBRequestParent* aActor) { 10595 AssertIsOnBackgroundThread(); 10596 MOZ_ASSERT(aActor); 10597 10598 auto* op = static_cast<NormalTransactionOp*>(aActor); 10599 10600 if (NS_WARN_IF(!op->Init(*this))) { 10601 op->Cleanup(); 10602 return false; 10603 } 10604 10605 op->DispatchToConnectionPool(); 10606 return true; 10607 } 10608 10609 bool TransactionBase::DeallocRequest( 10610 PBackgroundIDBRequestParent* const aActor) { 10611 AssertIsOnBackgroundThread(); 10612 MOZ_ASSERT(aActor); 10613 10614 // Transfer ownership back from IPDL. 10615 const RefPtr<NormalTransactionOp> actor = 10616 dont_AddRef(static_cast<NormalTransactionOp*>(aActor)); 10617 return true; 10618 } 10619 10620 already_AddRefed<PBackgroundIDBCursorParent> TransactionBase::AllocCursor( 10621 const OpenCursorParams& aParams, bool aTrustParams) { 10622 AssertIsOnBackgroundThread(); 10623 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); 10624 10625 #ifdef DEBUG 10626 // Always verify parameters in DEBUG builds! 10627 aTrustParams = false; 10628 #endif 10629 10630 const OpenCursorParams::Type type = aParams.type(); 10631 SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata; 10632 SafeRefPtr<FullIndexMetadata> indexMetadata; 10633 CursorBase::Direction direction; 10634 10635 // First extract the parameters common to all open cursor variants. 10636 const auto& commonParams = GetCommonOpenCursorParams(aParams); 10637 objectStoreMetadata = 10638 GetMetadataForObjectStoreId(commonParams.objectStoreId()); 10639 if (NS_AUUF_OR_WARN_IF(!objectStoreMetadata)) { 10640 return nullptr; 10641 } 10642 if (aTrustParams && NS_AUUF_OR_WARN_IF(!VerifyRequestParams( 10643 commonParams.optionalKeyRange()))) { 10644 return nullptr; 10645 } 10646 direction = commonParams.direction(); 10647 10648 // Now, for the index open cursor variants, extract the additional parameter. 10649 if (type == OpenCursorParams::TIndexOpenCursorParams || 10650 type == OpenCursorParams::TIndexOpenKeyCursorParams) { 10651 const auto& commonIndexParams = GetCommonIndexOpenCursorParams(aParams); 10652 indexMetadata = GetMetadataForIndexId(*objectStoreMetadata, 10653 commonIndexParams.indexId()); 10654 if (NS_AUUF_OR_WARN_IF(!indexMetadata)) { 10655 return nullptr; 10656 } 10657 } 10658 10659 if (NS_AUUF_OR_WARN_IF(mCommitOrAbortReceived)) { 10660 return nullptr; 10661 } 10662 10663 // Create Cursor and transfer ownership to IPDL. 10664 switch (type) { 10665 case OpenCursorParams::TObjectStoreOpenCursorParams: 10666 MOZ_ASSERT(!indexMetadata); 10667 return MakeAndAddRef<Cursor<IDBCursorType::ObjectStore>>( 10668 SafeRefPtrFromThis(), std::move(objectStoreMetadata), direction, 10669 CursorBase::ConstructFromTransactionBase{}); 10670 case OpenCursorParams::TObjectStoreOpenKeyCursorParams: 10671 MOZ_ASSERT(!indexMetadata); 10672 return MakeAndAddRef<Cursor<IDBCursorType::ObjectStoreKey>>( 10673 SafeRefPtrFromThis(), std::move(objectStoreMetadata), direction, 10674 CursorBase::ConstructFromTransactionBase{}); 10675 case OpenCursorParams::TIndexOpenCursorParams: 10676 return MakeAndAddRef<Cursor<IDBCursorType::Index>>( 10677 SafeRefPtrFromThis(), std::move(objectStoreMetadata), 10678 std::move(indexMetadata), direction, 10679 CursorBase::ConstructFromTransactionBase{}); 10680 case OpenCursorParams::TIndexOpenKeyCursorParams: 10681 return MakeAndAddRef<Cursor<IDBCursorType::IndexKey>>( 10682 SafeRefPtrFromThis(), std::move(objectStoreMetadata), 10683 std::move(indexMetadata), direction, 10684 CursorBase::ConstructFromTransactionBase{}); 10685 default: 10686 MOZ_CRASH("Cannot get here."); 10687 } 10688 } 10689 10690 bool TransactionBase::StartCursor(PBackgroundIDBCursorParent* const aActor, 10691 const int64_t aRequestId, 10692 const OpenCursorParams& aParams) { 10693 AssertIsOnBackgroundThread(); 10694 MOZ_ASSERT(aActor); 10695 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); 10696 10697 auto* const op = static_cast<CursorBase*>(aActor); 10698 10699 if (NS_WARN_IF(!op->Start(aRequestId, aParams))) { 10700 return false; 10701 } 10702 10703 return true; 10704 } 10705 10706 /******************************************************************************* 10707 * NormalTransaction 10708 ******************************************************************************/ 10709 10710 NormalTransaction::NormalTransaction( 10711 SafeRefPtr<Database> aDatabase, TransactionBase::Mode aMode, 10712 TransactionBase::Durability aDurability, 10713 nsTArray<SafeRefPtr<FullObjectStoreMetadata>>&& aObjectStores) 10714 : TransactionBase(std::move(aDatabase), aMode, aDurability), 10715 mObjectStores{std::move(aObjectStores)} { 10716 AssertIsOnBackgroundThread(); 10717 MOZ_ASSERT(!mObjectStores.IsEmpty()); 10718 } 10719 10720 bool NormalTransaction::IsSameProcessActor() { 10721 AssertIsOnBackgroundThread(); 10722 10723 PBackgroundParent* const actor = Manager()->Manager()->Manager(); 10724 MOZ_ASSERT(actor); 10725 10726 return !BackgroundParent::IsOtherProcessActor(actor); 10727 } 10728 10729 void NormalTransaction::SendCompleteNotification(nsresult aResult) { 10730 AssertIsOnBackgroundThread(); 10731 10732 if (!IsActorDestroyed()) { 10733 (void)SendComplete(aResult); 10734 } 10735 } 10736 10737 void NormalTransaction::ActorDestroy(ActorDestroyReason aWhy) { 10738 AssertIsOnBackgroundThread(); 10739 10740 NoteActorDestroyed(); 10741 10742 if (!mCommittedOrAborted) { 10743 if (NS_SUCCEEDED(mResultCode)) { 10744 IDB_REPORT_INTERNAL_ERR(); 10745 mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 10746 } 10747 10748 mForceAborted.EnsureFlipped(); 10749 10750 MaybeCommitOrAbort(); 10751 } 10752 } 10753 10754 mozilla::ipc::IPCResult NormalTransaction::RecvDeleteMe() { 10755 AssertIsOnBackgroundThread(); 10756 MOZ_ASSERT(!IsActorDestroyed()); 10757 10758 QM_WARNONLY_TRY(OkIf(PBackgroundIDBTransactionParent::Send__delete__(this))); 10759 10760 return IPC_OK(); 10761 } 10762 10763 mozilla::ipc::IPCResult NormalTransaction::RecvCommit( 10764 const Maybe<int64_t>& aLastRequest) { 10765 AssertIsOnBackgroundThread(); 10766 10767 return TransactionBase::RecvCommit(this, aLastRequest); 10768 } 10769 10770 mozilla::ipc::IPCResult NormalTransaction::RecvAbort( 10771 const nsresult& aResultCode) { 10772 AssertIsOnBackgroundThread(); 10773 10774 return TransactionBase::RecvAbort(this, aResultCode); 10775 } 10776 10777 PBackgroundIDBRequestParent* 10778 NormalTransaction::AllocPBackgroundIDBRequestParent( 10779 const int64_t& aRequestId, const RequestParams& aParams) { 10780 AssertIsOnBackgroundThread(); 10781 MOZ_ASSERT(aParams.type() != RequestParams::T__None); 10782 10783 return AllocRequest(aRequestId, 10784 std::move(const_cast<RequestParams&>(aParams)), 10785 IsSameProcessActor()); 10786 } 10787 10788 mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBRequestConstructor( 10789 PBackgroundIDBRequestParent* const aActor, const int64_t& aRequestId, 10790 const RequestParams& aParams) { 10791 AssertIsOnBackgroundThread(); 10792 MOZ_ASSERT(aActor); 10793 MOZ_ASSERT(aParams.type() != RequestParams::T__None); 10794 10795 if (!StartRequest(aActor)) { 10796 return IPC_FAIL(this, "StartRequest failed!"); 10797 } 10798 return IPC_OK(); 10799 } 10800 10801 bool NormalTransaction::DeallocPBackgroundIDBRequestParent( 10802 PBackgroundIDBRequestParent* const aActor) { 10803 AssertIsOnBackgroundThread(); 10804 MOZ_ASSERT(aActor); 10805 10806 return DeallocRequest(aActor); 10807 } 10808 10809 already_AddRefed<PBackgroundIDBCursorParent> 10810 NormalTransaction::AllocPBackgroundIDBCursorParent( 10811 const int64_t& aRequestId, const OpenCursorParams& aParams) { 10812 AssertIsOnBackgroundThread(); 10813 10814 return AllocCursor(aParams, IsSameProcessActor()); 10815 } 10816 10817 mozilla::ipc::IPCResult NormalTransaction::RecvPBackgroundIDBCursorConstructor( 10818 PBackgroundIDBCursorParent* const aActor, const int64_t& aRequestId, 10819 const OpenCursorParams& aParams) { 10820 AssertIsOnBackgroundThread(); 10821 MOZ_ASSERT(aActor); 10822 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); 10823 10824 if (!StartCursor(aActor, aRequestId, aParams)) { 10825 return IPC_FAIL(this, "StartCursor failed!"); 10826 } 10827 return IPC_OK(); 10828 } 10829 10830 /******************************************************************************* 10831 * VersionChangeTransaction 10832 ******************************************************************************/ 10833 10834 VersionChangeTransaction::VersionChangeTransaction( 10835 OpenDatabaseOp* aOpenDatabaseOp) 10836 : TransactionBase(aOpenDatabaseOp->mDatabase.clonePtr(), 10837 IDBTransaction::Mode::VersionChange, 10838 // VersionChange must not change durability. 10839 IDBTransaction::Durability::Default), // Not used. 10840 mOpenDatabaseOp(aOpenDatabaseOp) { 10841 AssertIsOnBackgroundThread(); 10842 MOZ_ASSERT(aOpenDatabaseOp); 10843 } 10844 10845 VersionChangeTransaction::~VersionChangeTransaction() { 10846 #ifdef DEBUG 10847 // Silence the base class' destructor assertion if we never made this actor 10848 // live. 10849 FakeActorDestroyed(); 10850 #endif 10851 } 10852 10853 bool VersionChangeTransaction::IsSameProcessActor() { 10854 AssertIsOnBackgroundThread(); 10855 10856 PBackgroundParent* actor = Manager()->Manager()->Manager(); 10857 MOZ_ASSERT(actor); 10858 10859 return !BackgroundParent::IsOtherProcessActor(actor); 10860 } 10861 10862 void VersionChangeTransaction::SetActorAlive() { 10863 AssertIsOnBackgroundThread(); 10864 MOZ_ASSERT(!IsActorDestroyed()); 10865 10866 mActorWasAlive.Flip(); 10867 } 10868 10869 bool VersionChangeTransaction::CopyDatabaseMetadata() { 10870 AssertIsOnBackgroundThread(); 10871 MOZ_ASSERT(!mOldMetadata); 10872 10873 const auto& origMetadata = GetDatabase().Metadata(); 10874 10875 SafeRefPtr<FullDatabaseMetadata> newMetadata = origMetadata.Duplicate(); 10876 if (NS_WARN_IF(!newMetadata)) { 10877 return false; 10878 } 10879 10880 // Replace the live metadata with the new mutable copy. 10881 DatabaseActorInfo* info; 10882 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(origMetadata.mDatabaseId, &info)); 10883 MOZ_ASSERT(!info->mLiveDatabases.isEmpty()); 10884 MOZ_ASSERT(info->mMetadata == &origMetadata); 10885 10886 mOldMetadata = std::move(info->mMetadata); 10887 info->mMetadata = std::move(newMetadata); 10888 10889 // Replace metadata pointers for all live databases. 10890 for (Database* const liveDatabase : info->mLiveDatabases) { 10891 liveDatabase->mMetadata = info->mMetadata.clonePtr(); 10892 } 10893 10894 return true; 10895 } 10896 10897 void VersionChangeTransaction::UpdateMetadata(nsresult aResult) { 10898 AssertIsOnBackgroundThread(); 10899 MOZ_ASSERT(mOpenDatabaseOp); 10900 MOZ_ASSERT(!!mActorWasAlive == !!mOpenDatabaseOp->mDatabase); 10901 MOZ_ASSERT_IF(mActorWasAlive, !mOpenDatabaseOp->mDatabaseId.ref().IsEmpty()); 10902 10903 if (IsActorDestroyed() || !mActorWasAlive) { 10904 return; 10905 } 10906 10907 SafeRefPtr<FullDatabaseMetadata> oldMetadata = std::move(mOldMetadata); 10908 10909 DatabaseActorInfo* info; 10910 if (!gLiveDatabaseHashtable->Get(oldMetadata->mDatabaseId, &info)) { 10911 return; 10912 } 10913 10914 MOZ_ASSERT(!info->mLiveDatabases.isEmpty()); 10915 10916 if (NS_SUCCEEDED(aResult)) { 10917 // Remove all deleted objectStores and indexes, then mark immutable. 10918 info->mMetadata->mObjectStores.RemoveIf([](const auto& objectStoreIter) { 10919 MOZ_ASSERT(objectStoreIter.Key()); 10920 const SafeRefPtr<FullObjectStoreMetadata>& metadata = 10921 objectStoreIter.Data(); 10922 MOZ_ASSERT(metadata); 10923 10924 if (metadata->mDeleted) { 10925 return true; 10926 } 10927 10928 metadata->mIndexes.RemoveIf([](const auto& indexIter) -> bool { 10929 MOZ_ASSERT(indexIter.Key()); 10930 const SafeRefPtr<FullIndexMetadata>& index = indexIter.Data(); 10931 MOZ_ASSERT(index); 10932 10933 return index->mDeleted; 10934 }); 10935 metadata->mIndexes.MarkImmutable(); 10936 10937 return false; 10938 }); 10939 10940 info->mMetadata->mObjectStores.MarkImmutable(); 10941 } else { 10942 // Replace metadata pointers for all live databases. 10943 info->mMetadata = std::move(oldMetadata); 10944 10945 for (Database* const liveDatabase : info->mLiveDatabases) { 10946 liveDatabase->mMetadata = info->mMetadata.clonePtr(); 10947 } 10948 } 10949 } 10950 10951 void VersionChangeTransaction::SendCompleteNotification(nsresult aResult) { 10952 AssertIsOnBackgroundThread(); 10953 MOZ_ASSERT(mOpenDatabaseOp); 10954 MOZ_ASSERT(!mOpenDatabaseOp->mCompleteCallback); 10955 MOZ_ASSERT_IF(!mActorWasAlive, mOpenDatabaseOp->HasFailed()); 10956 MOZ_ASSERT_IF(!mActorWasAlive, mOpenDatabaseOp->mState > 10957 OpenDatabaseOp::State::SendingResults); 10958 10959 const RefPtr<OpenDatabaseOp> openDatabaseOp = std::move(mOpenDatabaseOp); 10960 10961 if (!mActorWasAlive) { 10962 return; 10963 } 10964 10965 openDatabaseOp->mCompleteCallback = 10966 [self = SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aResult]() { 10967 if (!self->IsActorDestroyed()) { 10968 (void)self->SendComplete(aResult); 10969 } 10970 }; 10971 10972 auto handleError = [openDatabaseOp](const nsresult rv) { 10973 openDatabaseOp->SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); 10974 10975 openDatabaseOp->mState = OpenDatabaseOp::State::SendingResults; 10976 10977 MOZ_ALWAYS_SUCCEEDS(openDatabaseOp->Run()); 10978 }; 10979 10980 if (NS_FAILED(aResult)) { 10981 // 3.3.1 Opening a database: 10982 // "If the upgrade transaction was aborted, run the steps for closing a 10983 // database connection with connection, create and return a new AbortError 10984 // exception and abort these steps." 10985 handleError(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); 10986 return; 10987 } 10988 10989 openDatabaseOp->mState = OpenDatabaseOp::State::DatabaseWorkVersionUpdate; 10990 10991 QuotaManager* const quotaManager = QuotaManager::Get(); 10992 MOZ_ASSERT(quotaManager); 10993 10994 QM_TRY(MOZ_TO_RESULT(quotaManager->IOThread()->Dispatch(openDatabaseOp, 10995 NS_DISPATCH_NORMAL)) 10996 .mapErr( 10997 [](const auto) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; }), 10998 QM_VOID, handleError); 10999 } 11000 11001 void VersionChangeTransaction::ActorDestroy(ActorDestroyReason aWhy) { 11002 AssertIsOnBackgroundThread(); 11003 11004 NoteActorDestroyed(); 11005 11006 if (!mCommittedOrAborted) { 11007 if (NS_SUCCEEDED(mResultCode)) { 11008 IDB_REPORT_INTERNAL_ERR(); 11009 mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 11010 } 11011 11012 mForceAborted.EnsureFlipped(); 11013 11014 MaybeCommitOrAbort(); 11015 } 11016 } 11017 11018 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteMe() { 11019 AssertIsOnBackgroundThread(); 11020 MOZ_ASSERT(!IsActorDestroyed()); 11021 11022 QM_WARNONLY_TRY( 11023 OkIf(PBackgroundIDBVersionChangeTransactionParent::Send__delete__(this))); 11024 11025 return IPC_OK(); 11026 } 11027 11028 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCommit( 11029 const Maybe<int64_t>& aLastRequest) { 11030 AssertIsOnBackgroundThread(); 11031 11032 return TransactionBase::RecvCommit(this, aLastRequest); 11033 } 11034 11035 mozilla::ipc::IPCResult VersionChangeTransaction::RecvAbort( 11036 const nsresult& aResultCode) { 11037 AssertIsOnBackgroundThread(); 11038 11039 return TransactionBase::RecvAbort(this, aResultCode); 11040 } 11041 11042 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCreateObjectStore( 11043 const ObjectStoreMetadata& aMetadata) { 11044 AssertIsOnBackgroundThread(); 11045 11046 if (NS_WARN_IF(!aMetadata.id())) { 11047 return IPC_FAIL(this, "No metadata ID!"); 11048 } 11049 11050 const SafeRefPtr<FullDatabaseMetadata> dbMetadata = 11051 GetDatabase().MetadataPtr(); 11052 11053 if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextObjectStoreId)) { 11054 return IPC_FAIL(this, "Requested metadata ID does not match next ID!"); 11055 } 11056 11057 if (NS_WARN_IF( 11058 MatchMetadataNameOrId(dbMetadata->mObjectStores, aMetadata.id(), 11059 SomeRef<const nsAString&>(aMetadata.name())) 11060 .isSome())) { 11061 return IPC_FAIL(this, "MatchMetadataNameOrId failed!"); 11062 } 11063 11064 if (NS_WARN_IF(mCommitOrAbortReceived)) { 11065 return IPC_FAIL(this, "Transaction is already committed/aborted!"); 11066 } 11067 11068 const int64_t initialAutoIncrementId = aMetadata.autoIncrement() ? 1 : 0; 11069 auto newMetadata = MakeSafeRefPtr<FullObjectStoreMetadata>( 11070 aMetadata, FullObjectStoreMetadata::AutoIncrementIds{ 11071 initialAutoIncrementId, initialAutoIncrementId}); 11072 11073 if (NS_WARN_IF(!dbMetadata->mObjectStores.InsertOrUpdate( 11074 aMetadata.id(), std::move(newMetadata), fallible))) { 11075 return IPC_FAIL(this, "mObjectStores.InsertOrUpdate failed!"); 11076 } 11077 11078 dbMetadata->mNextObjectStoreId++; 11079 11080 RefPtr<CreateObjectStoreOp> op = new CreateObjectStoreOp( 11081 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aMetadata); 11082 11083 if (NS_WARN_IF(!op->Init(*this))) { 11084 op->Cleanup(); 11085 return IPC_FAIL(this, "ObjectStoreOp initialization failed!"); 11086 } 11087 11088 op->DispatchToConnectionPool(); 11089 11090 return IPC_OK(); 11091 } 11092 11093 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteObjectStore( 11094 const IndexOrObjectStoreId& aObjectStoreId) { 11095 AssertIsOnBackgroundThread(); 11096 11097 if (NS_WARN_IF(!aObjectStoreId)) { 11098 return IPC_FAIL(this, "No ObjectStoreId!"); 11099 } 11100 11101 const auto& dbMetadata = GetDatabase().Metadata(); 11102 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0); 11103 11104 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) { 11105 return IPC_FAIL(this, "Invalid ObjectStoreId!"); 11106 } 11107 11108 SafeRefPtr<FullObjectStoreMetadata> foundMetadata = 11109 GetMetadataForObjectStoreId(aObjectStoreId); 11110 11111 if (NS_WARN_IF(!foundMetadata)) { 11112 return IPC_FAIL(this, "No metadata found for ObjectStoreId!"); 11113 } 11114 11115 if (NS_WARN_IF(mCommitOrAbortReceived)) { 11116 return IPC_FAIL(this, "Transaction is already committed/aborted!"); 11117 } 11118 11119 foundMetadata->mDeleted.Flip(); 11120 11121 DebugOnly<bool> foundTargetId = false; 11122 const bool isLastObjectStore = std::all_of( 11123 dbMetadata.mObjectStores.begin(), dbMetadata.mObjectStores.end(), 11124 [&foundTargetId, aObjectStoreId](const auto& objectStoreEntry) -> bool { 11125 if (uint64_t(aObjectStoreId) == objectStoreEntry.GetKey()) { 11126 foundTargetId = true; 11127 return true; 11128 } 11129 11130 return objectStoreEntry.GetData()->mDeleted; 11131 }); 11132 MOZ_ASSERT_IF(isLastObjectStore, foundTargetId); 11133 11134 RefPtr<DeleteObjectStoreOp> op = new DeleteObjectStoreOp( 11135 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), 11136 std::move(foundMetadata), isLastObjectStore); 11137 11138 if (NS_WARN_IF(!op->Init(*this))) { 11139 op->Cleanup(); 11140 return IPC_FAIL(this, "ObjectStoreOp initialization failed!"); 11141 } 11142 11143 op->DispatchToConnectionPool(); 11144 11145 return IPC_OK(); 11146 } 11147 11148 mozilla::ipc::IPCResult VersionChangeTransaction::RecvRenameObjectStore( 11149 const IndexOrObjectStoreId& aObjectStoreId, const nsAString& aName) { 11150 AssertIsOnBackgroundThread(); 11151 11152 if (NS_WARN_IF(!aObjectStoreId)) { 11153 return IPC_FAIL(this, "No ObjectStoreId!"); 11154 } 11155 11156 { 11157 const auto& dbMetadata = GetDatabase().Metadata(); 11158 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0); 11159 11160 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) { 11161 return IPC_FAIL(this, "Invalid ObjectStoreId!"); 11162 } 11163 } 11164 11165 SafeRefPtr<FullObjectStoreMetadata> foundMetadata = 11166 GetMetadataForObjectStoreId(aObjectStoreId); 11167 11168 if (NS_WARN_IF(!foundMetadata)) { 11169 return IPC_FAIL(this, "No metadata found for ObjectStoreId!"); 11170 } 11171 11172 if (NS_WARN_IF(mCommitOrAbortReceived)) { 11173 return IPC_FAIL(this, "Transaction is already committed/aborted!"); 11174 } 11175 11176 foundMetadata->mCommonMetadata.name() = aName; 11177 11178 RefPtr<RenameObjectStoreOp> renameOp = new RenameObjectStoreOp( 11179 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), 11180 *foundMetadata); 11181 11182 if (NS_WARN_IF(!renameOp->Init(*this))) { 11183 renameOp->Cleanup(); 11184 return IPC_FAIL(this, "ObjectStoreOp initialization failed!"); 11185 } 11186 11187 renameOp->DispatchToConnectionPool(); 11188 11189 return IPC_OK(); 11190 } 11191 11192 mozilla::ipc::IPCResult VersionChangeTransaction::RecvCreateIndex( 11193 const IndexOrObjectStoreId& aObjectStoreId, 11194 const IndexMetadata& aMetadata) { 11195 AssertIsOnBackgroundThread(); 11196 11197 if (NS_WARN_IF(!aObjectStoreId)) { 11198 return IPC_FAIL(this, "No ObjectStoreId!"); 11199 } 11200 11201 if (NS_WARN_IF(!aMetadata.id())) { 11202 return IPC_FAIL(this, "No Metadata id!"); 11203 } 11204 11205 const auto dbMetadata = GetDatabase().MetadataPtr(); 11206 11207 if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextIndexId)) { 11208 return IPC_FAIL(this, "Requested metadata ID does not match next ID!"); 11209 } 11210 11211 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata = 11212 GetMetadataForObjectStoreId(aObjectStoreId); 11213 11214 if (NS_WARN_IF(!foundObjectStoreMetadata)) { 11215 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!"); 11216 } 11217 11218 if (NS_WARN_IF(MatchMetadataNameOrId( 11219 foundObjectStoreMetadata->mIndexes, aMetadata.id(), 11220 SomeRef<const nsAString&>(aMetadata.name())) 11221 .isSome())) { 11222 return IPC_FAIL(this, "MatchMetadataNameOrId failed!"); 11223 } 11224 11225 if (NS_WARN_IF(mCommitOrAbortReceived)) { 11226 return IPC_FAIL(this, "Transaction is already committed/aborted!"); 11227 } 11228 11229 auto newMetadata = MakeSafeRefPtr<FullIndexMetadata>(); 11230 newMetadata->mCommonMetadata = aMetadata; 11231 11232 if (NS_WARN_IF(!foundObjectStoreMetadata->mIndexes.InsertOrUpdate( 11233 aMetadata.id(), std::move(newMetadata), fallible))) { 11234 return IPC_FAIL(this, "mIndexes.InsertOrUpdate failed!"); 11235 } 11236 11237 dbMetadata->mNextIndexId++; 11238 11239 RefPtr<CreateIndexOp> op = new CreateIndexOp( 11240 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aObjectStoreId, 11241 aMetadata); 11242 11243 if (NS_WARN_IF(!op->Init(*this))) { 11244 op->Cleanup(); 11245 return IPC_FAIL(this, "ObjectStoreOp initialization failed!"); 11246 } 11247 11248 op->DispatchToConnectionPool(); 11249 11250 return IPC_OK(); 11251 } 11252 11253 mozilla::ipc::IPCResult VersionChangeTransaction::RecvDeleteIndex( 11254 const IndexOrObjectStoreId& aObjectStoreId, 11255 const IndexOrObjectStoreId& aIndexId) { 11256 AssertIsOnBackgroundThread(); 11257 11258 if (NS_WARN_IF(!aObjectStoreId)) { 11259 return IPC_FAIL(this, "No ObjectStoreId!"); 11260 } 11261 11262 if (NS_WARN_IF(!aIndexId)) { 11263 return IPC_FAIL(this, "No Index id!"); 11264 } 11265 { 11266 const auto& dbMetadata = GetDatabase().Metadata(); 11267 MOZ_ASSERT(dbMetadata.mNextObjectStoreId > 0); 11268 MOZ_ASSERT(dbMetadata.mNextIndexId > 0); 11269 11270 if (NS_WARN_IF(aObjectStoreId >= dbMetadata.mNextObjectStoreId)) { 11271 return IPC_FAIL(this, "Requested ObjectStoreId does not match next ID!"); 11272 } 11273 11274 if (NS_WARN_IF(aIndexId >= dbMetadata.mNextIndexId)) { 11275 return IPC_FAIL(this, "Requested IndexId does not match next ID!"); 11276 } 11277 } 11278 11279 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata = 11280 GetMetadataForObjectStoreId(aObjectStoreId); 11281 11282 if (NS_WARN_IF(!foundObjectStoreMetadata)) { 11283 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!"); 11284 } 11285 11286 SafeRefPtr<FullIndexMetadata> foundIndexMetadata = 11287 GetMetadataForIndexId(*foundObjectStoreMetadata, aIndexId); 11288 11289 if (NS_WARN_IF(!foundIndexMetadata)) { 11290 return IPC_FAIL(this, "GetMetadataForIndexId failed!"); 11291 } 11292 11293 if (NS_WARN_IF(mCommitOrAbortReceived)) { 11294 return IPC_FAIL(this, "Transaction is already committed/aborted!"); 11295 } 11296 11297 foundIndexMetadata->mDeleted.Flip(); 11298 11299 DebugOnly<bool> foundTargetId = false; 11300 const bool isLastIndex = 11301 std::all_of(foundObjectStoreMetadata->mIndexes.cbegin(), 11302 foundObjectStoreMetadata->mIndexes.cend(), 11303 [&foundTargetId, aIndexId](const auto& indexEntry) -> bool { 11304 if (uint64_t(aIndexId) == indexEntry.GetKey()) { 11305 foundTargetId = true; 11306 return true; 11307 } 11308 11309 return indexEntry.GetData()->mDeleted; 11310 }); 11311 MOZ_ASSERT_IF(isLastIndex, foundTargetId); 11312 11313 RefPtr<DeleteIndexOp> op = new DeleteIndexOp( 11314 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), aObjectStoreId, 11315 aIndexId, foundIndexMetadata->mCommonMetadata.unique(), isLastIndex); 11316 11317 if (NS_WARN_IF(!op->Init(*this))) { 11318 op->Cleanup(); 11319 return IPC_FAIL(this, "ObjectStoreOp initialization failed!"); 11320 } 11321 11322 op->DispatchToConnectionPool(); 11323 11324 return IPC_OK(); 11325 } 11326 11327 mozilla::ipc::IPCResult VersionChangeTransaction::RecvRenameIndex( 11328 const IndexOrObjectStoreId& aObjectStoreId, 11329 const IndexOrObjectStoreId& aIndexId, const nsAString& aName) { 11330 AssertIsOnBackgroundThread(); 11331 11332 if (NS_WARN_IF(!aObjectStoreId)) { 11333 return IPC_FAIL(this, "No ObjectStoreId!"); 11334 } 11335 11336 if (NS_WARN_IF(!aIndexId)) { 11337 return IPC_FAIL(this, "No Index id!"); 11338 } 11339 11340 const SafeRefPtr<FullDatabaseMetadata> dbMetadata = 11341 GetDatabase().MetadataPtr(); 11342 MOZ_ASSERT(dbMetadata); 11343 MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0); 11344 MOZ_ASSERT(dbMetadata->mNextIndexId > 0); 11345 11346 if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) { 11347 return IPC_FAIL(this, "Requested ObjectStoreId does not match next ID!"); 11348 } 11349 11350 if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) { 11351 return IPC_FAIL(this, "Requested IndexId does not match next ID!"); 11352 } 11353 11354 SafeRefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata = 11355 GetMetadataForObjectStoreId(aObjectStoreId); 11356 11357 if (NS_WARN_IF(!foundObjectStoreMetadata)) { 11358 return IPC_FAIL(this, "GetMetadataForObjectStoreId failed!"); 11359 } 11360 11361 SafeRefPtr<FullIndexMetadata> foundIndexMetadata = 11362 GetMetadataForIndexId(*foundObjectStoreMetadata, aIndexId); 11363 11364 if (NS_WARN_IF(!foundIndexMetadata)) { 11365 return IPC_FAIL(this, "GetMetadataForIndexId failed!"); 11366 } 11367 11368 if (NS_WARN_IF(mCommitOrAbortReceived)) { 11369 return IPC_FAIL(this, "Transaction is already committed/aborted!"); 11370 } 11371 11372 foundIndexMetadata->mCommonMetadata.name() = aName; 11373 11374 RefPtr<RenameIndexOp> renameOp = new RenameIndexOp( 11375 SafeRefPtrFromThis().downcast<VersionChangeTransaction>(), 11376 *foundIndexMetadata, aObjectStoreId); 11377 11378 if (NS_WARN_IF(!renameOp->Init(*this))) { 11379 renameOp->Cleanup(); 11380 return IPC_FAIL(this, "ObjectStoreOp initialization failed!"); 11381 } 11382 11383 renameOp->DispatchToConnectionPool(); 11384 11385 return IPC_OK(); 11386 } 11387 11388 PBackgroundIDBRequestParent* 11389 VersionChangeTransaction::AllocPBackgroundIDBRequestParent( 11390 const int64_t& aRequestId, const RequestParams& aParams) { 11391 AssertIsOnBackgroundThread(); 11392 MOZ_ASSERT(aParams.type() != RequestParams::T__None); 11393 11394 return AllocRequest(aRequestId, 11395 std::move(const_cast<RequestParams&>(aParams)), 11396 IsSameProcessActor()); 11397 } 11398 11399 mozilla::ipc::IPCResult 11400 VersionChangeTransaction::RecvPBackgroundIDBRequestConstructor( 11401 PBackgroundIDBRequestParent* aActor, const int64_t& aRequestId, 11402 const RequestParams& aParams) { 11403 AssertIsOnBackgroundThread(); 11404 MOZ_ASSERT(aActor); 11405 MOZ_ASSERT(aParams.type() != RequestParams::T__None); 11406 11407 if (!StartRequest(aActor)) { 11408 return IPC_FAIL(this, "StartRequest failed!"); 11409 } 11410 return IPC_OK(); 11411 } 11412 11413 bool VersionChangeTransaction::DeallocPBackgroundIDBRequestParent( 11414 PBackgroundIDBRequestParent* aActor) { 11415 AssertIsOnBackgroundThread(); 11416 MOZ_ASSERT(aActor); 11417 11418 return DeallocRequest(aActor); 11419 } 11420 11421 already_AddRefed<PBackgroundIDBCursorParent> 11422 VersionChangeTransaction::AllocPBackgroundIDBCursorParent( 11423 const int64_t& aRequestId, const OpenCursorParams& aParams) { 11424 AssertIsOnBackgroundThread(); 11425 11426 return AllocCursor(aParams, IsSameProcessActor()); 11427 } 11428 11429 mozilla::ipc::IPCResult 11430 VersionChangeTransaction::RecvPBackgroundIDBCursorConstructor( 11431 PBackgroundIDBCursorParent* aActor, const int64_t& aRequestId, 11432 const OpenCursorParams& aParams) { 11433 AssertIsOnBackgroundThread(); 11434 MOZ_ASSERT(aActor); 11435 MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None); 11436 11437 if (!StartCursor(aActor, aRequestId, aParams)) { 11438 return IPC_FAIL(this, "StartCursor failed!"); 11439 } 11440 return IPC_OK(); 11441 } 11442 11443 /******************************************************************************* 11444 * CursorBase 11445 ******************************************************************************/ 11446 11447 CursorBase::CursorBase(SafeRefPtr<TransactionBase> aTransaction, 11448 SafeRefPtr<FullObjectStoreMetadata> aObjectStoreMetadata, 11449 const Direction aDirection, 11450 const ConstructFromTransactionBase /*aConstructionTag*/) 11451 : mTransaction(std::move(aTransaction)), 11452 mObjectStoreMetadata(WrapNotNull(std::move(aObjectStoreMetadata))), 11453 mObjectStoreId((*mObjectStoreMetadata)->mCommonMetadata.id()), 11454 mDirection(aDirection), 11455 mMaxExtraCount(IndexedDatabaseManager::MaxPreloadExtraRecords()), 11456 mIsSameProcessActor(!BackgroundParent::IsOtherProcessActor( 11457 mTransaction->GetBackgroundParent())) { 11458 AssertIsOnBackgroundThread(); 11459 MOZ_ASSERT(mTransaction); 11460 11461 static_assert( 11462 OpenCursorParams::T__None == 0 && OpenCursorParams::T__Last == 4, 11463 "Lots of code here assumes only four types of cursors!"); 11464 } 11465 11466 template <IDBCursorType CursorType> 11467 bool Cursor<CursorType>::VerifyRequestParams( 11468 const CursorRequestParams& aParams, 11469 const CursorPosition<CursorType>& aPosition) const { 11470 AssertIsOnBackgroundThread(); 11471 MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None); 11472 MOZ_ASSERT(this->mObjectStoreMetadata); 11473 if constexpr (IsIndexCursor) { 11474 MOZ_ASSERT(this->mIndexMetadata); 11475 } 11476 11477 #ifdef DEBUG 11478 { 11479 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 11480 mTransaction->GetMetadataForObjectStoreId(mObjectStoreId); 11481 if (objectStoreMetadata) { 11482 MOZ_ASSERT(objectStoreMetadata == (*this->mObjectStoreMetadata)); 11483 } else { 11484 MOZ_ASSERT((*this->mObjectStoreMetadata)->mDeleted); 11485 } 11486 11487 if constexpr (IsIndexCursor) { 11488 if (objectStoreMetadata) { 11489 const SafeRefPtr<FullIndexMetadata> indexMetadata = 11490 mTransaction->GetMetadataForIndexId(*objectStoreMetadata, 11491 this->mIndexId); 11492 if (indexMetadata) { 11493 MOZ_ASSERT(indexMetadata == *this->mIndexMetadata); 11494 } else { 11495 MOZ_ASSERT((*this->mIndexMetadata)->mDeleted); 11496 } 11497 } 11498 } 11499 } 11500 #endif 11501 11502 if (NS_AUUF_OR_WARN_IF((*this->mObjectStoreMetadata)->mDeleted)) { 11503 return false; 11504 } 11505 11506 if constexpr (IsIndexCursor) { 11507 if (NS_AUUF_OR_WARN_IF(this->mIndexMetadata && 11508 (*this->mIndexMetadata)->mDeleted)) { 11509 return false; 11510 } 11511 } 11512 11513 const Key& sortKey = aPosition.GetSortKey(this->IsLocaleAware()); 11514 11515 switch (aParams.type()) { 11516 case CursorRequestParams::TContinueParams: { 11517 const Key& key = aParams.get_ContinueParams().key(); 11518 if (!key.IsUnset()) { 11519 switch (mDirection) { 11520 case IDBCursorDirection::Next: 11521 case IDBCursorDirection::Nextunique: 11522 if (NS_AUUF_OR_WARN_IF(key <= sortKey)) { 11523 return false; 11524 } 11525 break; 11526 11527 case IDBCursorDirection::Prev: 11528 case IDBCursorDirection::Prevunique: 11529 if (NS_AUUF_OR_WARN_IF(key >= sortKey)) { 11530 return false; 11531 } 11532 break; 11533 11534 default: 11535 MOZ_CRASH("Should never get here!"); 11536 } 11537 } 11538 break; 11539 } 11540 11541 case CursorRequestParams::TContinuePrimaryKeyParams: { 11542 if constexpr (IsIndexCursor) { 11543 const Key& key = aParams.get_ContinuePrimaryKeyParams().key(); 11544 const Key& primaryKey = 11545 aParams.get_ContinuePrimaryKeyParams().primaryKey(); 11546 MOZ_ASSERT(!key.IsUnset()); 11547 MOZ_ASSERT(!primaryKey.IsUnset()); 11548 switch (mDirection) { 11549 case IDBCursorDirection::Next: 11550 if (NS_AUUF_OR_WARN_IF(key < sortKey || 11551 (key == sortKey && 11552 primaryKey <= aPosition.mObjectStoreKey))) { 11553 return false; 11554 } 11555 break; 11556 11557 case IDBCursorDirection::Prev: 11558 if (NS_AUUF_OR_WARN_IF(key > sortKey || 11559 (key == sortKey && 11560 primaryKey >= aPosition.mObjectStoreKey))) { 11561 return false; 11562 } 11563 break; 11564 11565 default: 11566 MOZ_CRASH("Should never get here!"); 11567 } 11568 } 11569 break; 11570 } 11571 11572 case CursorRequestParams::TAdvanceParams: 11573 if (NS_AUUF_OR_WARN_IF(!aParams.get_AdvanceParams().count())) { 11574 return false; 11575 } 11576 break; 11577 11578 default: 11579 MOZ_CRASH("Should never get here!"); 11580 } 11581 11582 return true; 11583 } 11584 11585 template <IDBCursorType CursorType> 11586 bool Cursor<CursorType>::Start(const int64_t aRequestId, 11587 const OpenCursorParams& aParams) { 11588 AssertIsOnBackgroundThread(); 11589 MOZ_ASSERT(aParams.type() == ToOpenCursorParamsType(CursorType)); 11590 MOZ_ASSERT(this->mObjectStoreMetadata); 11591 11592 if (NS_AUUF_OR_WARN_IF(mCurrentlyRunningOp)) { 11593 return false; 11594 } 11595 11596 const Maybe<SerializedKeyRange>& optionalKeyRange = 11597 GetCommonOpenCursorParams(aParams).optionalKeyRange(); 11598 11599 const RefPtr<OpenOp> openOp = new OpenOp(this, aRequestId, optionalKeyRange); 11600 11601 if (NS_WARN_IF(!openOp->Init(*mTransaction))) { 11602 openOp->Cleanup(); 11603 return false; 11604 } 11605 11606 openOp->DispatchToConnectionPool(); 11607 mCurrentlyRunningOp = openOp; 11608 11609 return true; 11610 } 11611 11612 void ValueCursorBase::ProcessFiles(CursorResponse& aResponse, 11613 const FilesArray& aFiles) { 11614 MOZ_ASSERT_IF( 11615 aResponse.type() == CursorResponse::Tnsresult || 11616 aResponse.type() == CursorResponse::Tvoid_t || 11617 aResponse.type() == 11618 CursorResponse::TArrayOfObjectStoreKeyCursorResponse || 11619 aResponse.type() == CursorResponse::TArrayOfIndexKeyCursorResponse, 11620 aFiles.IsEmpty()); 11621 11622 for (size_t i = 0; i < aFiles.Length(); ++i) { 11623 const auto& files = aFiles[i]; 11624 if (!files.IsEmpty()) { 11625 // TODO: Replace this assertion by one that checks if the response type 11626 // matches the cursor type, at a more generic location. 11627 MOZ_ASSERT(aResponse.type() == 11628 CursorResponse::TArrayOfObjectStoreCursorResponse || 11629 aResponse.type() == 11630 CursorResponse::TArrayOfIndexCursorResponse); 11631 11632 SerializedStructuredCloneReadInfo* serializedInfo = nullptr; 11633 switch (aResponse.type()) { 11634 case CursorResponse::TArrayOfObjectStoreCursorResponse: { 11635 auto& responses = aResponse.get_ArrayOfObjectStoreCursorResponse(); 11636 MOZ_ASSERT(i < responses.Length()); 11637 serializedInfo = &responses[i].cloneInfo(); 11638 break; 11639 } 11640 11641 case CursorResponse::TArrayOfIndexCursorResponse: { 11642 auto& responses = aResponse.get_ArrayOfIndexCursorResponse(); 11643 MOZ_ASSERT(i < responses.Length()); 11644 serializedInfo = &responses[i].cloneInfo(); 11645 break; 11646 } 11647 11648 default: 11649 MOZ_CRASH("Should never get here!"); 11650 } 11651 11652 MOZ_ASSERT(serializedInfo); 11653 MOZ_ASSERT(serializedInfo->files().IsEmpty()); 11654 MOZ_ASSERT(this->mDatabase); 11655 11656 QM_TRY_UNWRAP(serializedInfo->files(), 11657 SerializeStructuredCloneFiles(this->mDatabase, files, 11658 /* aForPreprocess */ false), 11659 QM_VOID, [&aResponse](const nsresult result) { 11660 aResponse = ClampResultCode(result); 11661 }); 11662 } 11663 } 11664 } 11665 11666 template <IDBCursorType CursorType> 11667 void Cursor<CursorType>::SendResponseInternal( 11668 CursorResponse& aResponse, const FilesArrayT<CursorType>& aFiles) { 11669 AssertIsOnBackgroundThread(); 11670 MOZ_ASSERT(aResponse.type() != CursorResponse::T__None); 11671 MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult, 11672 NS_FAILED(aResponse.get_nsresult())); 11673 MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult, 11674 NS_ERROR_GET_MODULE(aResponse.get_nsresult()) == 11675 NS_ERROR_MODULE_DOM_INDEXEDDB); 11676 MOZ_ASSERT(this->mObjectStoreMetadata); 11677 MOZ_ASSERT(mCurrentlyRunningOp); 11678 11679 KeyValueBase::ProcessFiles(aResponse, aFiles); 11680 11681 // Work around the deleted function by casting to the base class. 11682 QM_WARNONLY_TRY(OkIf( 11683 static_cast<PBackgroundIDBCursorParent*>(this)->SendResponse(aResponse))); 11684 11685 mCurrentlyRunningOp = nullptr; 11686 } 11687 11688 template <IDBCursorType CursorType> 11689 void Cursor<CursorType>::ActorDestroy(ActorDestroyReason aWhy) { 11690 AssertIsOnBackgroundThread(); 11691 11692 if (mCurrentlyRunningOp) { 11693 mCurrentlyRunningOp->NoteActorDestroyed(); 11694 } 11695 11696 if constexpr (IsValueCursor) { 11697 this->mBackgroundParent.destroy(); 11698 } 11699 this->mObjectStoreMetadata.destroy(); 11700 if constexpr (IsIndexCursor) { 11701 this->mIndexMetadata.destroy(); 11702 } 11703 } 11704 11705 template <IDBCursorType CursorType> 11706 mozilla::ipc::IPCResult Cursor<CursorType>::RecvDeleteMe() { 11707 AssertIsOnBackgroundThread(); 11708 MOZ_ASSERT(this->mObjectStoreMetadata); 11709 11710 if (NS_WARN_IF(mCurrentlyRunningOp)) { 11711 return IPC_FAIL( 11712 this, 11713 "Attempt to delete a cursor with a non-null mCurrentlyRunningOp!"); 11714 } 11715 11716 QM_WARNONLY_TRY(OkIf(PBackgroundIDBCursorParent::Send__delete__(this))); 11717 11718 return IPC_OK(); 11719 } 11720 11721 template <IDBCursorType CursorType> 11722 mozilla::ipc::IPCResult Cursor<CursorType>::RecvContinue( 11723 const int64_t& aRequestId, const CursorRequestParams& aParams, 11724 const Key& aCurrentKey, const Key& aCurrentObjectStoreKey) { 11725 AssertIsOnBackgroundThread(); 11726 MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None); 11727 MOZ_ASSERT(this->mObjectStoreMetadata); 11728 if constexpr (IsIndexCursor) { 11729 MOZ_ASSERT(this->mIndexMetadata); 11730 } 11731 11732 const bool trustParams = 11733 #ifdef DEBUG 11734 // Always verify parameters in DEBUG builds! 11735 false 11736 #else 11737 this->mIsSameProcessActor 11738 #endif 11739 ; 11740 11741 MOZ_ASSERT(!aCurrentKey.IsUnset()); 11742 11743 QM_TRY_UNWRAP( 11744 auto position, 11745 ([&]() -> Result<CursorPosition<CursorType>, mozilla::ipc::IPCResult> { 11746 if constexpr (IsIndexCursor) { 11747 auto localeAwarePosition = Key{}; 11748 if (this->IsLocaleAware()) { 11749 QM_TRY_UNWRAP( 11750 localeAwarePosition, 11751 aCurrentKey.ToLocaleAwareKey(this->mLocale), 11752 Err(IPC_FAIL(this, "aCurrentKey.ToLocaleAwareKey failed!"))); 11753 } 11754 return CursorPosition<CursorType>{aCurrentKey, localeAwarePosition, 11755 aCurrentObjectStoreKey}; 11756 } else { 11757 return CursorPosition<CursorType>{aCurrentKey}; 11758 } 11759 }())); 11760 11761 if (!trustParams && !VerifyRequestParams(aParams, position)) { 11762 return IPC_FAIL(this, "VerifyRequestParams failed!"); 11763 } 11764 11765 if (NS_WARN_IF(mCurrentlyRunningOp)) { 11766 return IPC_FAIL(this, "Cursor is CurrentlyRunningOp!"); 11767 } 11768 11769 if (NS_WARN_IF(mTransaction->mCommitOrAbortReceived)) { 11770 return IPC_FAIL(this, "Transaction is already committed/aborted!"); 11771 } 11772 11773 const RefPtr<ContinueOp> continueOp = 11774 new ContinueOp(this, aRequestId, aParams, std::move(position)); 11775 if (NS_WARN_IF(!continueOp->Init(*mTransaction))) { 11776 continueOp->Cleanup(); 11777 return IPC_FAIL(this, "ContinueOp initialization failed!"); 11778 } 11779 11780 continueOp->DispatchToConnectionPool(); 11781 mCurrentlyRunningOp = continueOp; 11782 11783 return IPC_OK(); 11784 } 11785 11786 /******************************************************************************* 11787 * DatabaseFileManager 11788 ******************************************************************************/ 11789 11790 DatabaseFileManager::MutexType DatabaseFileManager::sMutex; 11791 11792 DatabaseFileManager::DatabaseFileManager( 11793 PersistenceType aPersistenceType, 11794 const quota::OriginMetadata& aOriginMetadata, 11795 const nsAString& aDatabaseName, const nsCString& aDatabaseID, 11796 const nsAString& aDatabaseFilePath, bool aEnforcingQuota, 11797 bool aIsInPrivateBrowsingMode) 11798 : mPersistenceType(aPersistenceType), 11799 mOriginMetadata(aOriginMetadata), 11800 mDatabaseName(aDatabaseName), 11801 mDatabaseID(aDatabaseID), 11802 mDatabaseFilePath(aDatabaseFilePath), 11803 mCipherKeyManager( 11804 aIsInPrivateBrowsingMode 11805 ? new IndexedDBCipherKeyManager("IndexedDBCipherKeyManager") 11806 : nullptr), 11807 mDatabaseVersion(0), 11808 mEnforcingQuota(aEnforcingQuota), 11809 mIsInPrivateBrowsingMode(aIsInPrivateBrowsingMode) {} 11810 11811 uint64_t DatabaseFileManager::DatabaseVersion() const { 11812 AssertIsOnIOThread(); 11813 11814 return mDatabaseVersion; 11815 } 11816 11817 void DatabaseFileManager::UpdateDatabaseVersion(uint64_t aDatabaseVersion) { 11818 AssertIsOnIOThread(); 11819 11820 mDatabaseVersion = aDatabaseVersion; 11821 } 11822 11823 nsresult DatabaseFileManager::Init(nsIFile* aDirectory, 11824 const uint64_t aDatabaseVersion, 11825 mozIStorageConnection& aConnection) { 11826 AssertIsOnIOThread(); 11827 MOZ_ASSERT(aDirectory); 11828 11829 { 11830 QM_TRY_INSPECT(const bool& existsAsDirectory, 11831 ExistsAsDirectory(*aDirectory)); 11832 11833 if (!existsAsDirectory) { 11834 QM_TRY(MOZ_TO_RESULT(aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755))); 11835 } 11836 11837 QM_TRY_UNWRAP(auto path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 11838 nsString, aDirectory, GetPath)); 11839 11840 mDirectoryPath.init(std::move(path)); 11841 } 11842 11843 QM_TRY_INSPECT(const auto& journalDirectory, 11844 CloneFileAndAppend(*aDirectory, kJournalDirectoryName)); 11845 11846 // We don't care if it doesn't exist at all, but if it does exist, make sure 11847 // it's a directory. 11848 QM_TRY_INSPECT(const bool& existsAsDirectory, 11849 ExistsAsDirectory(*journalDirectory)); 11850 (void)existsAsDirectory; 11851 11852 { 11853 QM_TRY_UNWRAP(auto path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 11854 nsString, journalDirectory, GetPath)); 11855 11856 mJournalDirectoryPath.init(std::move(path)); 11857 } 11858 11859 mDatabaseVersion = aDatabaseVersion; 11860 11861 QM_TRY_INSPECT(const auto& stmt, 11862 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 11863 nsCOMPtr<mozIStorageStatement>, aConnection, 11864 CreateStatement, "SELECT id, refcount FROM file"_ns)); 11865 11866 QM_TRY( 11867 CollectWhileHasResult(*stmt, [this](auto& stmt) -> Result<Ok, nsresult> { 11868 QM_TRY_INSPECT(const int64_t& id, 11869 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0)); 11870 QM_TRY_INSPECT(const int32_t& dbRefCnt, 11871 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 1)); 11872 11873 // We put a raw pointer into the hash table, so the memory refcount will 11874 // be 0, but the dbRefCnt is non-zero, which will keep the 11875 // DatabaseFileInfo object alive. 11876 MOZ_ASSERT(dbRefCnt > 0); 11877 DebugOnly ok = static_cast<bool>(CreateFileInfo(Some(id), dbRefCnt)); 11878 MOZ_ASSERT(ok); 11879 11880 return Ok{}; 11881 })); 11882 11883 mInitialized.Flip(); 11884 11885 return NS_OK; 11886 } 11887 11888 nsCOMPtr<nsIFile> DatabaseFileManager::GetDirectory() { 11889 if (!this->AssertValid()) { 11890 return nullptr; 11891 } 11892 11893 return GetFileForPath(*mDirectoryPath); 11894 } 11895 11896 nsCOMPtr<nsIFile> DatabaseFileManager::GetCheckedDirectory() { 11897 auto directory = GetDirectory(); 11898 if (NS_WARN_IF(!directory)) { 11899 return nullptr; 11900 } 11901 11902 DebugOnly<bool> exists; 11903 MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists))); 11904 MOZ_ASSERT(exists); 11905 11906 DebugOnly<bool> isDirectory; 11907 MOZ_ASSERT(NS_SUCCEEDED(directory->IsDirectory(&isDirectory))); 11908 MOZ_ASSERT(isDirectory); 11909 11910 return directory; 11911 } 11912 11913 nsCOMPtr<nsIFile> DatabaseFileManager::GetJournalDirectory() { 11914 if (!this->AssertValid()) { 11915 return nullptr; 11916 } 11917 11918 return GetFileForPath(*mJournalDirectoryPath); 11919 } 11920 11921 nsCOMPtr<nsIFile> DatabaseFileManager::EnsureJournalDirectory() { 11922 // This can happen on the IO or on a transaction thread. 11923 MOZ_ASSERT(!NS_IsMainThread()); 11924 11925 auto journalDirectory = GetFileForPath(*mJournalDirectoryPath); 11926 QM_TRY(OkIf(journalDirectory), nullptr); 11927 11928 QM_TRY_INSPECT(const bool& exists, 11929 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, Exists), 11930 nullptr); 11931 11932 if (exists) { 11933 QM_TRY_INSPECT(const bool& isDirectory, 11934 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, IsDirectory), 11935 nullptr); 11936 11937 QM_TRY(OkIf(isDirectory), nullptr); 11938 } else { 11939 QM_TRY( 11940 MOZ_TO_RESULT(journalDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)), 11941 nullptr); 11942 } 11943 11944 return journalDirectory; 11945 } 11946 11947 // static 11948 nsCOMPtr<nsIFile> DatabaseFileManager::GetFileForId(nsIFile* aDirectory, 11949 int64_t aId) { 11950 MOZ_ASSERT(aDirectory); 11951 MOZ_ASSERT(aId > 0); 11952 11953 QM_TRY_RETURN(CloneFileAndAppend(*aDirectory, IntToString(aId)), nullptr); 11954 } 11955 11956 // static 11957 nsCOMPtr<nsIFile> DatabaseFileManager::GetCheckedFileForId(nsIFile* aDirectory, 11958 int64_t aId) { 11959 auto file = GetFileForId(aDirectory, aId); 11960 if (NS_WARN_IF(!file)) { 11961 return nullptr; 11962 } 11963 11964 DebugOnly<bool> exists; 11965 MOZ_ASSERT(NS_SUCCEEDED(file->Exists(&exists))); 11966 MOZ_ASSERT(exists); 11967 11968 DebugOnly<bool> isFile; 11969 MOZ_ASSERT(NS_SUCCEEDED(file->IsFile(&isFile))); 11970 MOZ_ASSERT(isFile); 11971 11972 return file; 11973 } 11974 11975 // static 11976 nsresult DatabaseFileManager::InitDirectory(nsIFile& aDirectory, 11977 nsIFile& aDatabaseFile, 11978 const nsACString& aOrigin, 11979 uint32_t aTelemetryId) { 11980 AssertIsOnIOThread(); 11981 11982 { 11983 QM_TRY_INSPECT(const bool& exists, 11984 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists)); 11985 11986 if (!exists) { 11987 return NS_OK; 11988 } 11989 11990 QM_TRY_INSPECT(const bool& isDirectory, 11991 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory)); 11992 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE); 11993 } 11994 11995 QM_TRY_INSPECT(const auto& journalDirectory, 11996 CloneFileAndAppend(aDirectory, kJournalDirectoryName)); 11997 11998 QM_TRY_INSPECT(const bool& exists, 11999 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, Exists)); 12000 12001 if (exists) { 12002 QM_TRY_INSPECT(const bool& isDirectory, 12003 MOZ_TO_RESULT_INVOKE_MEMBER(journalDirectory, IsDirectory)); 12004 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE); 12005 12006 bool hasJournals = false; 12007 12008 QM_TRY(CollectEachFile( 12009 *journalDirectory, 12010 [&hasJournals](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> { 12011 QM_TRY_INSPECT( 12012 const auto& leafName, 12013 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName)); 12014 12015 nsresult rv; 12016 leafName.ToInteger64(&rv); 12017 if (NS_SUCCEEDED(rv)) { 12018 hasJournals = true; 12019 } else { 12020 UNKNOWN_FILE_WARNING(leafName); 12021 } 12022 12023 return Ok{}; 12024 })); 12025 12026 if (hasJournals) { 12027 QM_TRY_UNWRAP(const NotNull<nsCOMPtr<mozIStorageConnection>> connection, 12028 CreateStorageConnection( 12029 aDatabaseFile, aDirectory, VoidString(), aOrigin, 12030 /* aDirectoryLockId */ -1, aTelemetryId, Nothing{})); 12031 12032 mozStorageTransaction transaction(connection.get(), false); 12033 12034 QM_TRY(MOZ_TO_RESULT(transaction.Start())) 12035 12036 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL( 12037 "CREATE VIRTUAL TABLE fs USING filesystem;"_ns))); 12038 12039 // The parameter names are not used, parameters are bound by index only 12040 // locally in the same function. 12041 QM_TRY_INSPECT( 12042 const auto& stmt, 12043 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 12044 nsCOMPtr<mozIStorageStatement>, *connection, CreateStatement, 12045 "SELECT name, (name IN (SELECT id FROM file)) FROM fs WHERE path = :path"_ns)); 12046 12047 QM_TRY_INSPECT(const auto& path, 12048 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 12049 nsString, journalDirectory, GetPath)); 12050 12051 QM_TRY(MOZ_TO_RESULT(stmt->BindStringByIndex(0, path))); 12052 12053 QM_TRY(CollectWhileHasResult( 12054 *stmt, 12055 [&aDirectory, &journalDirectory](auto& stmt) -> Result<Ok, nsresult> { 12056 nsString name; 12057 QM_TRY(MOZ_TO_RESULT(stmt.GetString(0, name))); 12058 12059 nsresult rv; 12060 name.ToInteger64(&rv); 12061 if (NS_FAILED(rv)) { 12062 return Ok{}; 12063 } 12064 12065 int32_t flag = stmt.AsInt32(1); 12066 12067 if (!flag) { 12068 QM_TRY_INSPECT(const auto& file, 12069 CloneFileAndAppend(aDirectory, name)); 12070 12071 if (NS_FAILED(file->Remove(false))) { 12072 NS_WARNING("Failed to remove orphaned file!"); 12073 } 12074 } 12075 12076 QM_TRY_INSPECT(const auto& journalFile, 12077 CloneFileAndAppend(*journalDirectory, name)); 12078 12079 if (NS_FAILED(journalFile->Remove(false))) { 12080 NS_WARNING("Failed to remove journal file!"); 12081 } 12082 12083 return Ok{}; 12084 })); 12085 12086 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("DROP TABLE fs;"_ns))); 12087 QM_TRY(MOZ_TO_RESULT(transaction.Commit())); 12088 } 12089 } 12090 12091 return NS_OK; 12092 } 12093 12094 // static 12095 Result<FileUsageType, nsresult> DatabaseFileManager::GetUsage( 12096 nsIFile* aDirectory) { 12097 AssertIsOnIOThread(); 12098 MOZ_ASSERT(aDirectory); 12099 12100 FileUsageType usage; 12101 12102 QM_TRY(TraverseFiles( 12103 *aDirectory, 12104 // KnownDirEntryOp 12105 [&usage](nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> { 12106 if (isDirectory) { 12107 return Ok{}; 12108 } 12109 12110 // Usually we only use QM_OR_ELSE_LOG_VERBOSE(_IF) with Remove and 12111 // NS_ERROR_FILE_NOT_FOUND check, but the file was found by a directory 12112 // traversal and ToInteger on the name succeeded, so it should be our 12113 // file and if the file disappears, the use of QM_OR_ELSE_WARN_IF is ok 12114 // here. 12115 QM_TRY_INSPECT(const auto& thisUsage, 12116 QM_OR_ELSE_WARN_IF( 12117 // Expression. 12118 MOZ_TO_RESULT_INVOKE_MEMBER(file, GetFileSize) 12119 .map([](const int64_t fileSize) { 12120 return FileUsageType(Some(uint64_t(fileSize))); 12121 }), 12122 // Predicate. 12123 ([](const nsresult rv) { 12124 return rv == NS_ERROR_FILE_NOT_FOUND; 12125 }), 12126 // Fallback. If the file does no longer exist, treat 12127 // it as 0-sized. 12128 ErrToDefaultOk<FileUsageType>)); 12129 12130 usage += thisUsage; 12131 12132 return Ok{}; 12133 }, 12134 // UnknownDirEntryOp 12135 [](nsIFile&, const bool) -> Result<Ok, nsresult> { return Ok{}; })); 12136 12137 return usage; 12138 } 12139 12140 nsresult DatabaseFileManager::SyncDeleteFile(const int64_t aId) { 12141 MOZ_ASSERT(!ContainsFileInfo(aId)); 12142 12143 if (!this->AssertValid()) { 12144 return NS_ERROR_UNEXPECTED; 12145 } 12146 12147 const auto directory = GetDirectory(); 12148 QM_TRY(OkIf(directory), NS_ERROR_FAILURE); 12149 12150 const auto journalDirectory = GetJournalDirectory(); 12151 QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE); 12152 12153 const nsCOMPtr<nsIFile> file = GetFileForId(directory, aId); 12154 QM_TRY(OkIf(file), NS_ERROR_FAILURE); 12155 12156 const nsCOMPtr<nsIFile> journalFile = GetFileForId(journalDirectory, aId); 12157 QM_TRY(OkIf(journalFile), NS_ERROR_FAILURE); 12158 12159 return SyncDeleteFile(*file, *journalFile); 12160 } 12161 12162 nsresult DatabaseFileManager::SyncDeleteFile(nsIFile& aFile, 12163 nsIFile& aJournalFile) const { 12164 QuotaManager* const quotaManager = 12165 EnforcingQuota() ? QuotaManager::Get() : nullptr; 12166 MOZ_ASSERT_IF(EnforcingQuota(), quotaManager); 12167 12168 QM_TRY(MOZ_TO_RESULT(DeleteFile(aFile, quotaManager, Type(), OriginMetadata(), 12169 Idempotency::No))); 12170 12171 QM_TRY(MOZ_TO_RESULT(aJournalFile.Remove(false))); 12172 12173 return NS_OK; 12174 } 12175 12176 nsresult DatabaseFileManager::Invalidate() { 12177 if (mCipherKeyManager) { 12178 mCipherKeyManager->Invalidate(); 12179 } 12180 12181 QM_TRY(MOZ_TO_RESULT(FileInfoManager::Invalidate())); 12182 12183 return NS_OK; 12184 } 12185 12186 /******************************************************************************* 12187 * QuotaClient 12188 ******************************************************************************/ 12189 12190 QuotaClient* QuotaClient::sInstance = nullptr; 12191 12192 QuotaClient::QuotaClient() : mDeleteTimer(NS_NewTimer()) { 12193 AssertIsOnBackgroundThread(); 12194 MOZ_ASSERT(!sInstance, "We expect this to be a singleton!"); 12195 MOZ_ASSERT(!gTelemetryIdMutex); 12196 12197 // Always create this so that later access to gTelemetryIdHashtable can be 12198 // properly synchronized. 12199 gTelemetryIdMutex = new Mutex("IndexedDB gTelemetryIdMutex"); 12200 12201 gStorageDatabaseNameMutex = new Mutex("IndexedDB gStorageDatabaseNameMutex"); 12202 12203 sInstance = this; 12204 } 12205 12206 QuotaClient::~QuotaClient() { 12207 AssertIsOnBackgroundThread(); 12208 MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!"); 12209 MOZ_ASSERT(gTelemetryIdMutex); 12210 MOZ_ASSERT(!mMaintenanceThreadPool); 12211 12212 // No one else should be able to touch gTelemetryIdHashtable now that the 12213 // QuotaClient has gone away. 12214 gTelemetryIdHashtable = nullptr; 12215 gTelemetryIdMutex = nullptr; 12216 12217 gStorageDatabaseNameHashtable = nullptr; 12218 gStorageDatabaseNameMutex = nullptr; 12219 12220 sInstance = nullptr; 12221 } 12222 12223 nsresult QuotaClient::AsyncDeleteFile(DatabaseFileManager* aFileManager, 12224 int64_t aFileId) { 12225 AssertIsOnBackgroundThread(); 12226 12227 if (IsShuttingDownOnBackgroundThread()) { 12228 // Whoops! We want to delete an IndexedDB disk-backed File but it's too late 12229 // to actually delete the file! This means we're going to "leak" the file 12230 // and leave it around when we shouldn't! (The file will stay around until 12231 // next storage initialization is triggered when the app is started again). 12232 // Fixing this is tracked by bug 1539377. 12233 12234 return NS_OK; 12235 } 12236 12237 MOZ_ASSERT(mDeleteTimer); 12238 MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel()); 12239 12240 QM_TRY(MOZ_TO_RESULT(mDeleteTimer->InitWithNamedFuncCallback( 12241 DeleteTimerCallback, this, kDeleteTimeoutMs, nsITimer::TYPE_ONE_SHOT, 12242 "dom::indexeddb::QuotaClient::AsyncDeleteFile"_ns))); 12243 12244 mPendingDeleteInfos.GetOrInsertNew(aFileManager)->AppendElement(aFileId); 12245 12246 return NS_OK; 12247 } 12248 12249 nsresult QuotaClient::FlushPendingFileDeletions() { 12250 AssertIsOnBackgroundThread(); 12251 12252 QM_TRY(MOZ_TO_RESULT(mDeleteTimer->Cancel())); 12253 12254 DeleteTimerCallback(mDeleteTimer, this); 12255 12256 return NS_OK; 12257 } 12258 12259 RefPtr<BoolPromise> QuotaClient::DoMaintenance() { 12260 AssertIsOnBackgroundThread(); 12261 MOZ_ASSERT(!IsShuttingDownOnBackgroundThread()); 12262 12263 if (!mBackgroundThread) { 12264 mBackgroundThread = GetCurrentSerialEventTarget(); 12265 } 12266 12267 auto maintenance = MakeRefPtr<Maintenance>(this); 12268 12269 mMaintenanceQueue.AppendElement(maintenance); 12270 ProcessMaintenanceQueue(); 12271 12272 return maintenance->OnResults(); 12273 } 12274 12275 nsThreadPool* QuotaClient::GetOrCreateThreadPool() { 12276 AssertIsOnBackgroundThread(); 12277 MOZ_ASSERT(!IsShuttingDownOnBackgroundThread()); 12278 12279 if (!mMaintenanceThreadPool) { 12280 RefPtr<nsThreadPool> threadPool = new nsThreadPool(); 12281 12282 // PR_GetNumberOfProcessors() can return -1 on error, so make sure we 12283 // don't set some huge number here. We add 2 in case some threads block on 12284 // the disk I/O. 12285 const uint32_t threadCount = 12286 std::max(int32_t(PR_GetNumberOfProcessors()), int32_t(1)) + 2; 12287 12288 MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(threadCount)); 12289 12290 // Don't keep more than one idle thread. 12291 MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadLimit(1)); 12292 12293 // Don't keep idle threads alive very long. 12294 MOZ_ALWAYS_SUCCEEDS( 12295 threadPool->SetIdleThreadMaximumTimeout(5 * PR_MSEC_PER_SEC)); 12296 12297 MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("IndexedDB Mnt"_ns)); 12298 12299 mMaintenanceThreadPool = std::move(threadPool); 12300 } 12301 12302 return mMaintenanceThreadPool; 12303 } 12304 12305 mozilla::dom::quota::Client::Type QuotaClient::GetType() { 12306 return QuotaClient::IDB; 12307 } 12308 12309 nsresult QuotaClient::UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) { 12310 AssertIsOnIOThread(); 12311 MOZ_ASSERT(aDirectory); 12312 12313 QM_TRY_INSPECT(const auto& databaseFilenamesInfo, 12314 GetDatabaseFilenames(*aDirectory, 12315 /* aCanceled */ AtomicBool{false})); 12316 // FIXME: use structural binding once we support c++20. 12317 const auto& subdirsToProcess = databaseFilenamesInfo.subdirsToProcess; 12318 const auto& databaseFilenames = databaseFilenamesInfo.databaseFilenames; 12319 12320 QM_TRY(CollectEachInRange( 12321 subdirsToProcess, 12322 [&databaseFilenames = databaseFilenames, 12323 aDirectory](const nsAString& subdirName) -> Result<Ok, nsresult> { 12324 // If the directory has the correct suffix then it should exist in 12325 // databaseFilenames. 12326 nsDependentSubstring subdirNameBase; 12327 if (GetFilenameBase(subdirName, kFileManagerDirectoryNameSuffix, 12328 subdirNameBase)) { 12329 QM_WARNONLY_TRY(OkIf(databaseFilenames.Contains(subdirNameBase))); 12330 return Ok{}; 12331 } 12332 12333 // The directory didn't have the right suffix but we might need to 12334 // rename it. Check to see if we have a database that references this 12335 // directory. 12336 QM_TRY_INSPECT( 12337 const auto& subdirNameWithSuffix, 12338 ([&databaseFilenames, 12339 &subdirName]() -> Result<nsAutoString, NotOk> { 12340 if (databaseFilenames.Contains(subdirName)) { 12341 return nsAutoString{subdirName + 12342 kFileManagerDirectoryNameSuffix}; 12343 } 12344 12345 // Windows doesn't allow a directory to end with a dot ('.'), so 12346 // we have to check that possibility here too. We do this on all 12347 // platforms, because the origin directory may have been created 12348 // on Windows and now accessed on different OS. 12349 const nsAutoString subdirNameWithDot = subdirName + u"."_ns; 12350 QM_TRY(OkIf(databaseFilenames.Contains(subdirNameWithDot)), 12351 Err(NotOk{})); 12352 12353 return nsAutoString{subdirNameWithDot + 12354 kFileManagerDirectoryNameSuffix}; 12355 }()), 12356 Ok{}); 12357 12358 // We do have a database that uses this subdir so we should rename it 12359 // now. 12360 QM_TRY_INSPECT(const auto& subdir, 12361 CloneFileAndAppend(*aDirectory, subdirName)); 12362 12363 DebugOnly<bool> isDirectory; 12364 MOZ_ASSERT(NS_SUCCEEDED(subdir->IsDirectory(&isDirectory))); 12365 MOZ_ASSERT(isDirectory); 12366 12367 // Check if the subdir with suffix already exists before renaming. 12368 QM_TRY_INSPECT(const auto& subdirWithSuffix, 12369 CloneFileAndAppend(*aDirectory, subdirNameWithSuffix)); 12370 12371 QM_TRY_INSPECT(const bool& exists, 12372 MOZ_TO_RESULT_INVOKE_MEMBER(subdirWithSuffix, Exists)); 12373 12374 if (exists) { 12375 IDB_WARNING("Deleting old %s files directory!", 12376 NS_ConvertUTF16toUTF8(subdirName).get()); 12377 12378 QM_TRY(MOZ_TO_RESULT(subdir->Remove(/* aRecursive */ true))); 12379 12380 return Ok{}; 12381 } 12382 12383 // Finally, rename the subdir. 12384 QM_TRY(MOZ_TO_RESULT(subdir->RenameTo(nullptr, subdirNameWithSuffix))); 12385 12386 return Ok{}; 12387 })); 12388 12389 return NS_OK; 12390 } 12391 12392 nsresult QuotaClient::UpgradeStorageFrom2_1To2_2(nsIFile* aDirectory) { 12393 AssertIsOnIOThread(); 12394 MOZ_ASSERT(aDirectory); 12395 12396 QM_TRY(CollectEachFile( 12397 *aDirectory, [](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> { 12398 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file)); 12399 12400 switch (dirEntryKind) { 12401 case nsIFileKind::ExistsAsDirectory: 12402 break; 12403 12404 case nsIFileKind::ExistsAsFile: { 12405 QM_TRY_INSPECT( 12406 const auto& leafName, 12407 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName)); 12408 12409 // It's reported that files ending with ".tmp" somehow live in the 12410 // indexedDB directories in Bug 1503883. Such files shouldn't exist 12411 // in the indexedDB directory so remove them in this upgrade. 12412 if (StringEndsWith(leafName, u".tmp"_ns)) { 12413 IDB_WARNING("Deleting unknown temporary file!"); 12414 12415 QM_TRY(MOZ_TO_RESULT(file->Remove(false))); 12416 } 12417 12418 break; 12419 } 12420 12421 case nsIFileKind::DoesNotExist: 12422 // Ignore files that got removed externally while iterating. 12423 break; 12424 } 12425 12426 return Ok{}; 12427 })); 12428 12429 return NS_OK; 12430 } 12431 12432 Result<UsageInfo, nsresult> QuotaClient::InitOrigin( 12433 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 12434 const AtomicBool& aCanceled) { 12435 AssertIsOnIOThread(); 12436 12437 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(this, GetUsageForOriginInternal, 12438 aPersistenceType, aOriginMetadata, 12439 aCanceled, 12440 /* aInitializing*/ true)); 12441 } 12442 12443 nsresult QuotaClient::InitOriginWithoutTracking( 12444 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 12445 const AtomicBool& aCanceled) { 12446 AssertIsOnIOThread(); 12447 12448 return GetUsageForOriginInternal(aPersistenceType, aOriginMetadata, aCanceled, 12449 /* aInitializing*/ true, nullptr); 12450 } 12451 12452 Result<UsageInfo, nsresult> QuotaClient::GetUsageForOrigin( 12453 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 12454 const AtomicBool& aCanceled) { 12455 AssertIsOnIOThread(); 12456 12457 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(this, GetUsageForOriginInternal, 12458 aPersistenceType, aOriginMetadata, 12459 aCanceled, 12460 /* aInitializing*/ false)); 12461 } 12462 12463 nsresult QuotaClient::GetUsageForOriginInternal( 12464 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 12465 const AtomicBool& aCanceled, const bool aInitializing, 12466 UsageInfo* aUsageInfo) { 12467 GECKO_TRACE_SCOPE("dom::indexedDB", "QuotaClient::GetUsageForOriginInternal"); 12468 12469 AssertIsOnIOThread(); 12470 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType); 12471 12472 QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& directory, 12473 GetDirectory(aOriginMetadata)); 12474 12475 // We need to see if there are any files in the directory already. If they 12476 // are database files then we need to cleanup stored files (if it's needed) 12477 // and also get the usage. 12478 12479 // XXX Can we avoid unwrapping into non-const variables here? (Only 12480 // databaseFilenames is currently modified below) 12481 QM_TRY_UNWRAP((auto [subdirsToProcess, databaseFilenames, obsoleteFilenames]), 12482 GetDatabaseFilenames<ObsoleteFilenamesHandling::Include>( 12483 *directory, aCanceled)); 12484 12485 if (aInitializing) { 12486 QM_TRY(CollectEachInRange( 12487 subdirsToProcess, 12488 [&directory, &obsoleteFilenames = obsoleteFilenames, 12489 &databaseFilenames = databaseFilenames, aPersistenceType, 12490 &aOriginMetadata]( 12491 const nsAString& subdirName) -> Result<Ok, nsresult> { 12492 // The directory must have the correct suffix. 12493 nsDependentSubstring subdirNameBase; 12494 QM_TRY(QM_OR_ELSE_WARN( 12495 // Expression. 12496 ([&subdirName, &subdirNameBase] { 12497 QM_TRY_RETURN(OkIf(GetFilenameBase( 12498 subdirName, kFileManagerDirectoryNameSuffix, 12499 subdirNameBase))); 12500 }()), 12501 // Fallback. 12502 ([&directory, 12503 &subdirName](const NotOk) -> Result<Ok, nsresult> { 12504 // If there is an unexpected directory in the idb 12505 // directory, trying to delete at first instead of 12506 // breaking the whole initialization. 12507 QM_TRY(MOZ_TO_RESULT( 12508 DeleteFilesNoQuota(directory, subdirName)), 12509 Err(NS_ERROR_UNEXPECTED)); 12510 12511 return Ok{}; 12512 })), 12513 Ok{}); 12514 12515 if (obsoleteFilenames.Contains(subdirNameBase)) { 12516 // If this fails, it probably means we are in a serious situation. 12517 // e.g. Filesystem corruption. Will handle this in bug 1521541. 12518 QM_TRY(MOZ_TO_RESULT(RemoveDatabaseFilesAndDirectory( 12519 *directory, subdirNameBase, /* aQuotaManager */ nullptr, 12520 aPersistenceType, aOriginMetadata, 12521 /* aDatabaseName */ u""_ns)), 12522 Err(NS_ERROR_UNEXPECTED)); 12523 12524 databaseFilenames.Remove(subdirNameBase); 12525 return Ok{}; 12526 } 12527 12528 // The directory base must exist in databaseFilenames. 12529 // If there is an unexpected directory in the idb directory, trying to 12530 // delete at first instead of breaking the whole initialization. 12531 12532 // XXX This is still somewhat quirky. It would be nice to make it 12533 // clear that the warning handler is infallible, which would also 12534 // remove the need for the error type conversion. 12535 QM_WARNONLY_TRY(QM_OR_ELSE_WARN( 12536 // Expression. 12537 OkIf(databaseFilenames.Contains(subdirNameBase)) 12538 .mapErr([](const NotOk) { return NS_ERROR_FAILURE; }), 12539 // Fallback. 12540 ([&directory, 12541 &subdirName](const nsresult) -> Result<Ok, nsresult> { 12542 // XXX It seems if we really got here, we can fail the 12543 // MOZ_ASSERT(!quotaManager->IsTemporaryStorageInitializedInternal()); 12544 // assertion in DeleteFilesNoQuota. 12545 QM_TRY(MOZ_TO_RESULT(DeleteFilesNoQuota(directory, subdirName)), 12546 Err(NS_ERROR_UNEXPECTED)); 12547 12548 return Ok{}; 12549 }))); 12550 12551 return Ok{}; 12552 })); 12553 } 12554 12555 for (const auto& databaseFilename : databaseFilenames) { 12556 if (aCanceled) { 12557 break; 12558 } 12559 12560 QM_TRY_INSPECT( 12561 const auto& fmDirectory, 12562 CloneFileAndAppend(*directory, 12563 databaseFilename + kFileManagerDirectoryNameSuffix)); 12564 12565 QM_TRY_INSPECT( 12566 const auto& databaseFile, 12567 CloneFileAndAppend(*directory, databaseFilename + kSQLiteSuffix)); 12568 12569 if (aInitializing) { 12570 QM_TRY(MOZ_TO_RESULT(DatabaseFileManager::InitDirectory( 12571 *fmDirectory, *databaseFile, aOriginMetadata.mOrigin, 12572 TelemetryIdForFile(databaseFile)))); 12573 } 12574 12575 if (aUsageInfo) { 12576 { 12577 QM_TRY_INSPECT(const int64_t& fileSize, 12578 MOZ_TO_RESULT_INVOKE_MEMBER(databaseFile, GetFileSize)); 12579 12580 MOZ_ASSERT(fileSize >= 0); 12581 12582 *aUsageInfo += DatabaseUsageType(Some(uint64_t(fileSize))); 12583 } 12584 12585 { 12586 QM_TRY_INSPECT(const auto& walFile, 12587 CloneFileAndAppend(*directory, 12588 databaseFilename + kSQLiteWALSuffix)); 12589 12590 // QM_OR_ELSE_WARN_IF is not used here since we just want to log 12591 // NS_ERROR_FILE_NOT_FOUND result and not spam the reports (the -wal 12592 // file doesn't have to exist). 12593 QM_TRY_INSPECT(const int64_t& walFileSize, 12594 QM_OR_ELSE_LOG_VERBOSE_IF( 12595 // Expression. 12596 MOZ_TO_RESULT_INVOKE_MEMBER(walFile, GetFileSize), 12597 // Predicate. 12598 ([](const nsresult rv) { 12599 return rv == NS_ERROR_FILE_NOT_FOUND; 12600 }), 12601 // Fallback. 12602 (ErrToOk<0, int64_t>))); 12603 MOZ_ASSERT(walFileSize >= 0); 12604 *aUsageInfo += DatabaseUsageType(Some(uint64_t(walFileSize))); 12605 } 12606 12607 { 12608 QM_TRY_INSPECT(const auto& fileUsage, 12609 DatabaseFileManager::GetUsage(fmDirectory)); 12610 12611 *aUsageInfo += fileUsage; 12612 } 12613 } 12614 } 12615 12616 return NS_OK; 12617 } 12618 12619 void QuotaClient::OnOriginClearCompleted( 12620 const OriginMetadata& aOriginMetadata) { 12621 AssertIsOnIOThread(); 12622 12623 if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) { 12624 mgr->InvalidateFileManagers(aOriginMetadata.mPersistenceType, 12625 aOriginMetadata.mOrigin); 12626 } 12627 } 12628 12629 void QuotaClient::OnRepositoryClearCompleted(PersistenceType aPersistenceType) { 12630 AssertIsOnIOThread(); 12631 12632 if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) { 12633 mgr->InvalidateFileManagers(aPersistenceType); 12634 } 12635 } 12636 12637 void QuotaClient::ReleaseIOThreadObjects() { 12638 AssertIsOnIOThread(); 12639 12640 if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) { 12641 mgr->InvalidateAllFileManagers(); 12642 } 12643 } 12644 12645 void QuotaClient::AbortOperationsForLocks( 12646 const DirectoryLockIdTable& aDirectoryLockIds) { 12647 AssertIsOnBackgroundThread(); 12648 12649 InvalidateLiveDatabasesMatching([&aDirectoryLockIds](const auto& database) { 12650 // If the database is registered in gLiveDatabaseHashtable then it must have 12651 // a directory lock. 12652 return IsLockForObjectContainedInLockTable(database, aDirectoryLockIds); 12653 }); 12654 } 12655 12656 void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) { 12657 AssertIsOnBackgroundThread(); 12658 12659 InvalidateLiveDatabasesMatching([&aContentParentId](const auto& database) { 12660 return database.IsOwnedByProcess(aContentParentId); 12661 }); 12662 } 12663 12664 void QuotaClient::AbortAllOperations() { 12665 AssertIsOnBackgroundThread(); 12666 12667 AbortAllMaintenances(); 12668 12669 InvalidateLiveDatabasesMatching([](const auto&) { return true; }); 12670 } 12671 12672 void QuotaClient::StartIdleMaintenance() { 12673 AssertIsOnBackgroundThread(); 12674 if (IsShuttingDownOnBackgroundThread()) { 12675 MOZ_ASSERT(false, "!IsShuttingDownOnBackgroundThread()"); 12676 return; 12677 } 12678 12679 DoMaintenance(); 12680 } 12681 12682 void QuotaClient::StopIdleMaintenance() { 12683 AssertIsOnBackgroundThread(); 12684 12685 AbortAllMaintenances(); 12686 } 12687 12688 void QuotaClient::InitiateShutdown() { 12689 AssertIsOnBackgroundThread(); 12690 MOZ_ASSERT(IsShuttingDownOnBackgroundThread()); 12691 12692 if (mDeleteTimer) { 12693 // QuotaClient::AsyncDeleteFile will not schedule new timers beyond 12694 // shutdown. And we expect all critical (PBM) deletions to have been 12695 // triggered before this point via ClearPrivateRepository (w/out using 12696 // DeleteFilesRunnable at all). 12697 mDeleteTimer->Cancel(); 12698 mDeleteTimer = nullptr; 12699 mPendingDeleteInfos.Clear(); 12700 } 12701 12702 AbortAllOperations(); 12703 } 12704 12705 bool QuotaClient::IsShutdownCompleted() const { 12706 return (!gFactoryOps || gFactoryOps->isEmpty()) && 12707 (!gLiveDatabaseHashtable || !gLiveDatabaseHashtable->Count()) && 12708 !mCurrentMaintenance && !DeleteFilesRunnable::IsDeletionPending(); 12709 } 12710 12711 void QuotaClient::ForceKillActors() { 12712 // Currently we don't implement force killing actors. 12713 } 12714 12715 nsCString QuotaClient::GetShutdownStatus() const { 12716 AssertIsOnBackgroundThread(); 12717 12718 nsCString data; 12719 12720 if (gFactoryOps && !gFactoryOps->isEmpty()) { 12721 data.Append("FactoryOperations: "_ns + 12722 IntToCString(static_cast<uint32_t>(gFactoryOps->length())) + 12723 " ("_ns); 12724 12725 // XXX It might be confusing to remove duplicates here, as the actual list 12726 // won't match the count then. 12727 nsTHashSet<nsCString> ids; 12728 12729 std::transform(gFactoryOps->begin(), gFactoryOps->end(), MakeInserter(ids), 12730 [](const FactoryOp* const factoryOp) { 12731 MOZ_ASSERT(factoryOp); 12732 12733 nsCString id; 12734 factoryOp->Stringify(id); 12735 return id; 12736 }); 12737 12738 StringJoinAppend(data, ", "_ns, ids); 12739 12740 data.Append(")\n"); 12741 } 12742 12743 if (gLiveDatabaseHashtable && gLiveDatabaseHashtable->Count()) { 12744 data.Append("LiveDatabases: "_ns + 12745 IntToCString(gLiveDatabaseHashtable->Count()) + " ("_ns); 12746 12747 // XXX It might be confusing to remove duplicates here, as the actual list 12748 // won't match the count then. 12749 nsTHashSet<nsCString> ids; 12750 12751 for (const auto& entry : gLiveDatabaseHashtable->Values()) { 12752 MOZ_ASSERT(entry); 12753 12754 std::transform(entry->mLiveDatabases.begin(), entry->mLiveDatabases.end(), 12755 MakeInserter(ids), [](const Database* const database) { 12756 nsCString id; 12757 database->Stringify(id); 12758 return id; 12759 }); 12760 } 12761 12762 StringJoinAppend(data, ", "_ns, ids); 12763 12764 data.Append(")\n"); 12765 } 12766 12767 if (mCurrentMaintenance) { 12768 data.Append("IdleMaintenance: 1 ("); 12769 mCurrentMaintenance->Stringify(data); 12770 data.Append(")\n"); 12771 } 12772 12773 return data; 12774 } 12775 12776 void QuotaClient::FinalizeShutdown() { 12777 RefPtr<ConnectionPool> connectionPool = gConnectionPool.get(); 12778 if (connectionPool) { 12779 connectionPool->Shutdown(); 12780 12781 gConnectionPool = nullptr; 12782 } 12783 12784 if (mMaintenanceThreadPool) { 12785 mMaintenanceThreadPool->Shutdown(); 12786 mMaintenanceThreadPool = nullptr; 12787 } 12788 } 12789 12790 void QuotaClient::DeleteTimerCallback(nsITimer* aTimer, void* aClosure) { 12791 AssertIsOnBackgroundThread(); 12792 MOZ_ASSERT(aTimer); 12793 12794 // Even though we do not schedule new timers after shutdown has started, 12795 // an already existing one might fire afterwards (actually we think it 12796 // shouldn't, but there is no reason to enforce this invariant). We can 12797 // just ignore it, the cleanup work is done in InitiateShutdown. 12798 if (NS_WARN_IF(IsShuttingDownOnBackgroundThread())) { 12799 return; 12800 } 12801 12802 auto* const self = static_cast<QuotaClient*>(aClosure); 12803 MOZ_ASSERT(self); 12804 MOZ_ASSERT(self->mDeleteTimer); 12805 MOZ_ASSERT(SameCOMIdentity(self->mDeleteTimer, aTimer)); 12806 12807 for (const auto& pendingDeleteInfoEntry : self->mPendingDeleteInfos) { 12808 const auto& key = pendingDeleteInfoEntry.GetKey(); 12809 const auto& value = pendingDeleteInfoEntry.GetData(); 12810 MOZ_ASSERT(!value->IsEmpty()); 12811 12812 RefPtr<DeleteFilesRunnable> runnable = new DeleteFilesRunnable( 12813 SafeRefPtr{key, AcquireStrongRefFromRawPtr{}}, std::move(*value)); 12814 12815 MOZ_ASSERT(value->IsEmpty()); 12816 12817 runnable->RunImmediately(); 12818 } 12819 12820 self->mPendingDeleteInfos.Clear(); 12821 } 12822 12823 void QuotaClient::AbortAllMaintenances() { 12824 if (mCurrentMaintenance) { 12825 mCurrentMaintenance->Abort(); 12826 } 12827 12828 for (const auto& maintenance : mMaintenanceQueue) { 12829 maintenance->Abort(); 12830 } 12831 } 12832 12833 Result<nsCOMPtr<nsIFile>, nsresult> QuotaClient::GetDirectory( 12834 const OriginMetadata& aOriginMetadata) { 12835 QuotaManager* const quotaManager = QuotaManager::Get(); 12836 NS_ASSERTION(quotaManager, "This should never fail!"); 12837 12838 QM_TRY_INSPECT(const auto& directory, 12839 quotaManager->GetOriginDirectory(aOriginMetadata)); 12840 12841 MOZ_ASSERT(directory); 12842 12843 QM_TRY(MOZ_TO_RESULT( 12844 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME)))); 12845 12846 return directory; 12847 } 12848 12849 template <QuotaClient::ObsoleteFilenamesHandling ObsoleteFilenames> 12850 Result<QuotaClient::GetDatabaseFilenamesResult<ObsoleteFilenames>, nsresult> 12851 QuotaClient::GetDatabaseFilenames(nsIFile& aDirectory, 12852 const AtomicBool& aCanceled) { 12853 AssertIsOnIOThread(); 12854 12855 GetDatabaseFilenamesResult<ObsoleteFilenames> result; 12856 12857 QM_TRY(CollectEachFileAtomicCancelable( 12858 aDirectory, aCanceled, 12859 [&result](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> { 12860 QM_TRY_INSPECT(const auto& leafName, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 12861 nsString, file, GetLeafName)); 12862 12863 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file)); 12864 12865 switch (dirEntryKind) { 12866 case nsIFileKind::ExistsAsDirectory: 12867 result.subdirsToProcess.AppendElement(leafName); 12868 break; 12869 12870 case nsIFileKind::ExistsAsFile: { 12871 if constexpr (ObsoleteFilenames == 12872 ObsoleteFilenamesHandling::Include) { 12873 if (StringBeginsWith(leafName, kIdbDeletionMarkerFilePrefix)) { 12874 result.obsoleteFilenames.Insert( 12875 Substring(leafName, kIdbDeletionMarkerFilePrefix.Length())); 12876 break; 12877 } 12878 } 12879 12880 // Skip OS metadata files. These files are only used in different 12881 // platforms, but the profile can be shared across different 12882 // operating systems, so we check it on all platforms. 12883 if (QuotaManager::IsOSMetadata(leafName)) { 12884 break; 12885 } 12886 12887 // Skip files starting with ".". 12888 if (QuotaManager::IsDotFile(leafName)) { 12889 break; 12890 } 12891 12892 // Skip SQLite temporary files. These files take up space on disk 12893 // but will be deleted as soon as the database is opened, so we 12894 // don't count them towards quota. 12895 if (StringEndsWith(leafName, kSQLiteJournalSuffix) || 12896 StringEndsWith(leafName, kSQLiteSHMSuffix)) { 12897 break; 12898 } 12899 12900 // The SQLite WAL file does count towards quota, but it is handled 12901 // below once we find the actual database file. 12902 if (StringEndsWith(leafName, kSQLiteWALSuffix)) { 12903 break; 12904 } 12905 12906 nsDependentSubstring leafNameBase; 12907 if (!GetFilenameBase(leafName, kSQLiteSuffix, leafNameBase)) { 12908 UNKNOWN_FILE_WARNING(leafName); 12909 break; 12910 } 12911 12912 result.databaseFilenames.Insert(leafNameBase); 12913 break; 12914 } 12915 12916 case nsIFileKind::DoesNotExist: 12917 // Ignore files that got removed externally while iterating. 12918 break; 12919 } 12920 12921 return Ok{}; 12922 })); 12923 12924 return result; 12925 } 12926 12927 void QuotaClient::ProcessMaintenanceQueue() { 12928 AssertIsOnBackgroundThread(); 12929 12930 if (mCurrentMaintenance || mMaintenanceQueue.IsEmpty()) { 12931 return; 12932 } 12933 12934 mCurrentMaintenance = mMaintenanceQueue[0]; 12935 mMaintenanceQueue.RemoveElementAt(0); 12936 12937 mCurrentMaintenance->RunImmediately(); 12938 } 12939 12940 /******************************************************************************* 12941 * DeleteFilesRunnable 12942 ******************************************************************************/ 12943 12944 uint64_t DeleteFilesRunnable::sPendingRunnables = 0; 12945 12946 DeleteFilesRunnable::DeleteFilesRunnable( 12947 SafeRefPtr<DatabaseFileManager> aFileManager, nsTArray<int64_t>&& aFileIds) 12948 : Runnable("dom::indexeddb::DeleteFilesRunnable"), 12949 mOwningEventTarget(GetCurrentSerialEventTarget()), 12950 mFileManager(std::move(aFileManager)), 12951 mFileIds(std::move(aFileIds)), 12952 mState(State_Initial) {} 12953 12954 #ifdef DEBUG 12955 DeleteFilesRunnable::~DeleteFilesRunnable() { 12956 MOZ_ASSERT(!mDEBUGCountsAsPending); 12957 } 12958 #endif 12959 12960 void DeleteFilesRunnable::RunImmediately() { 12961 AssertIsOnBackgroundThread(); 12962 MOZ_ASSERT(mState == State_Initial); 12963 12964 (void)this->Run(); 12965 } 12966 12967 void DeleteFilesRunnable::Open() { 12968 AssertIsOnBackgroundThread(); 12969 MOZ_ASSERT(mState == State_Initial); 12970 12971 MOZ_ASSERT(!mDEBUGCountsAsPending); 12972 sPendingRunnables++; 12973 DEBUGONLY(mDEBUGCountsAsPending = true); 12974 12975 QuotaManager* const quotaManager = QuotaManager::Get(); 12976 if (NS_WARN_IF(!quotaManager)) { 12977 Finish(); 12978 return; 12979 } 12980 12981 mState = State_DirectoryOpenPending; 12982 12983 quotaManager 12984 ->OpenClientDirectory( 12985 {mFileManager->OriginMetadata(), quota::Client::IDB}) 12986 ->Then( 12987 GetCurrentSerialEventTarget(), __func__, 12988 [self = RefPtr(this)](QuotaManager::ClientDirectoryLockHandlePromise:: 12989 ResolveOrRejectValue&& aValue) { 12990 if (aValue.IsResolve()) { 12991 self->DirectoryLockAcquired(std::move(aValue.ResolveValue())); 12992 } else { 12993 self->DirectoryLockFailed(); 12994 } 12995 }); 12996 } 12997 12998 void DeleteFilesRunnable::DoDatabaseWork() { 12999 AssertIsOnIOThread(); 13000 MOZ_ASSERT(mState == State_DatabaseWorkOpen); 13001 13002 if (!mFileManager->Invalidated()) { 13003 for (int64_t fileId : mFileIds) { 13004 if (NS_FAILED(mFileManager->SyncDeleteFile(fileId))) { 13005 NS_WARNING("Failed to delete file!"); 13006 } 13007 } 13008 } 13009 13010 Finish(); 13011 } 13012 13013 void DeleteFilesRunnable::Finish() { 13014 MOZ_ASSERT(mState != State_UnblockingOpen); 13015 13016 // Must set mState before dispatching otherwise we will race with the main 13017 // thread. 13018 mState = State_UnblockingOpen; 13019 13020 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); 13021 } 13022 13023 void DeleteFilesRunnable::UnblockOpen() { 13024 AssertIsOnBackgroundThread(); 13025 MOZ_ASSERT(mState == State_UnblockingOpen); 13026 13027 { 13028 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 13029 } 13030 13031 MOZ_ASSERT(mDEBUGCountsAsPending); 13032 sPendingRunnables--; 13033 DEBUGONLY(mDEBUGCountsAsPending = false); 13034 13035 mState = State_Completed; 13036 } 13037 13038 NS_IMETHODIMP 13039 DeleteFilesRunnable::Run() { 13040 switch (mState) { 13041 case State_Initial: 13042 Open(); 13043 break; 13044 13045 case State_DatabaseWorkOpen: 13046 DoDatabaseWork(); 13047 break; 13048 13049 case State_UnblockingOpen: 13050 UnblockOpen(); 13051 break; 13052 13053 case State_DirectoryOpenPending: 13054 default: 13055 MOZ_CRASH("Should never get here!"); 13056 } 13057 13058 return NS_OK; 13059 } 13060 13061 void DeleteFilesRunnable::DirectoryLockAcquired( 13062 ClientDirectoryLockHandle aLockHandle) { 13063 AssertIsOnBackgroundThread(); 13064 MOZ_ASSERT(mState == State_DirectoryOpenPending); 13065 MOZ_ASSERT(!mDirectoryLockHandle); 13066 13067 mDirectoryLockHandle = std::move(aLockHandle); 13068 13069 QuotaManager* const quotaManager = QuotaManager::Get(); 13070 MOZ_ASSERT(quotaManager); 13071 13072 // Must set this before dispatching otherwise we will race with the IO thread 13073 mState = State_DatabaseWorkOpen; 13074 13075 QM_TRY(MOZ_TO_RESULT( 13076 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)), 13077 QM_VOID, [this](const nsresult) { Finish(); }); 13078 } 13079 13080 void DeleteFilesRunnable::DirectoryLockFailed() { 13081 AssertIsOnBackgroundThread(); 13082 MOZ_ASSERT(mState == State_DirectoryOpenPending); 13083 MOZ_ASSERT(!mDirectoryLockHandle); 13084 13085 Finish(); 13086 } 13087 13088 void Maintenance::Abort() { 13089 AssertIsOnBackgroundThread(); 13090 13091 // Safe because mDatabaseMaintenances is modified 13092 // only in the background thread 13093 for (const auto& aDatabaseMaintenance : mDatabaseMaintenances) { 13094 aDatabaseMaintenance.GetData()->Abort(); 13095 } 13096 13097 mAborted = true; 13098 } 13099 13100 void Maintenance::RegisterDatabaseMaintenance( 13101 DatabaseMaintenance* aDatabaseMaintenance) { 13102 AssertIsOnBackgroundThread(); 13103 MOZ_ASSERT(aDatabaseMaintenance); 13104 MOZ_ASSERT(mState == State::BeginDatabaseMaintenance); 13105 MOZ_ASSERT( 13106 !mDatabaseMaintenances.Contains(aDatabaseMaintenance->DatabasePath())); 13107 13108 mDatabaseMaintenances.InsertOrUpdate(aDatabaseMaintenance->DatabasePath(), 13109 aDatabaseMaintenance); 13110 } 13111 13112 void Maintenance::UnregisterDatabaseMaintenance( 13113 DatabaseMaintenance* aDatabaseMaintenance) { 13114 AssertIsOnBackgroundThread(); 13115 MOZ_ASSERT(aDatabaseMaintenance); 13116 MOZ_ASSERT(mState == State::WaitingForDatabaseMaintenancesToComplete); 13117 MOZ_ASSERT(mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath())); 13118 13119 mDatabaseMaintenances.Remove(aDatabaseMaintenance->DatabasePath()); 13120 13121 if (mDatabaseMaintenances.Count()) { 13122 return; 13123 } 13124 13125 for (const auto& completeCallback : mCompleteCallbacks) { 13126 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(completeCallback)); 13127 } 13128 mCompleteCallbacks.Clear(); 13129 13130 mState = State::Finishing; 13131 Finish(); 13132 } 13133 13134 void Maintenance::Stringify(nsACString& aResult) const { 13135 AssertIsOnBackgroundThread(); 13136 13137 aResult.Append("DatabaseMaintenances: "_ns + 13138 IntToCString(mDatabaseMaintenances.Count()) + " ("_ns); 13139 13140 // XXX It might be confusing to remove duplicates here, as the actual list 13141 // won't match the count then. 13142 nsTHashSet<nsCString> ids; 13143 std::transform(mDatabaseMaintenances.Values().cbegin(), 13144 mDatabaseMaintenances.Values().cend(), MakeInserter(ids), 13145 [](const auto& entry) { 13146 MOZ_ASSERT(entry); 13147 13148 nsCString id; 13149 entry->Stringify(id); 13150 13151 return id; 13152 }); 13153 13154 StringJoinAppend(aResult, ", "_ns, ids); 13155 13156 aResult.Append(")"); 13157 } 13158 13159 nsresult Maintenance::Start() { 13160 AssertIsOnBackgroundThread(); 13161 MOZ_ASSERT(mState == State::Initial); 13162 13163 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 13164 IsAborted()) { 13165 return NS_ERROR_ABORT; 13166 } 13167 13168 // Make sure that the IndexedDatabaseManager is running so that we can check 13169 // for low disk space mode. 13170 13171 if (IndexedDatabaseManager::Get()) { 13172 OpenDirectory(); 13173 return NS_OK; 13174 } 13175 13176 mState = State::CreateIndexedDatabaseManager; 13177 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); 13178 13179 return NS_OK; 13180 } 13181 13182 nsresult Maintenance::CreateIndexedDatabaseManager() { 13183 MOZ_ASSERT(NS_IsMainThread()); 13184 MOZ_ASSERT(mState == State::CreateIndexedDatabaseManager); 13185 13186 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 13187 IsAborted()) { 13188 return NS_ERROR_ABORT; 13189 } 13190 13191 IndexedDatabaseManager* const mgr = IndexedDatabaseManager::GetOrCreate(); 13192 if (NS_WARN_IF(!mgr)) { 13193 return NS_ERROR_FAILURE; 13194 } 13195 13196 mState = State::IndexedDatabaseManagerOpen; 13197 MOZ_ALWAYS_SUCCEEDS( 13198 mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL)); 13199 13200 return NS_OK; 13201 } 13202 13203 RefPtr<UniversalDirectoryLockPromise> Maintenance::OpenStorageDirectory( 13204 const PersistenceScope& aPersistenceScope, bool aInitializeOrigins) { 13205 AssertIsOnBackgroundThread(); 13206 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 13207 MOZ_ASSERT(!mDirectoryLock); 13208 MOZ_ASSERT(!mAborted); 13209 MOZ_ASSERT(mState == State::DirectoryOpenPending); 13210 13211 QuotaManager* quotaManager = QuotaManager::Get(); 13212 MOZ_ASSERT(quotaManager); 13213 13214 // Return a shared lock for <profile>/storage/repo(aPersistenceScope)/*/idb 13215 return quotaManager->OpenStorageDirectory( 13216 aPersistenceScope, OriginScope::FromNull(), 13217 ClientStorageScope::CreateFromClient(Client::IDB), 13218 /* aExclusive */ false, aInitializeOrigins, DirectoryLockCategory::None, 13219 SomeRef(mPendingDirectoryLock)); 13220 } 13221 13222 nsresult Maintenance::OpenDirectory() { 13223 AssertIsOnBackgroundThread(); 13224 MOZ_ASSERT(mState == State::Initial || 13225 mState == State::IndexedDatabaseManagerOpen); 13226 MOZ_ASSERT(!mDirectoryLock); 13227 MOZ_ASSERT(QuotaManager::Get()); 13228 13229 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 13230 IsAborted()) { 13231 return NS_ERROR_ABORT; 13232 } 13233 13234 mState = State::DirectoryOpenPending; 13235 13236 // Since idle maintenance may occur before persistent or temporary storage is 13237 // initialized, make sure it's initialized here (all persistent and 13238 // non-persistent origins need to be cleaned up and quota info needs to be 13239 // loaded for non-persistent origins). 13240 13241 OpenStorageDirectory(PersistenceScope::CreateFromNull(), 13242 /* aInitializeOrigins */ true) 13243 ->Then( 13244 GetCurrentSerialEventTarget(), __func__, 13245 [self = RefPtr(this)]( 13246 const UniversalDirectoryLockPromise::ResolveOrRejectValue& 13247 aValue) { 13248 if (aValue.IsResolve()) { 13249 self->DirectoryLockAcquired(aValue.ResolveValue()); 13250 return; 13251 } 13252 13253 // Don't fail whole idle maintenance in case of an error, the 13254 // persistent repository can still be processed. 13255 13256 self->mPendingDirectoryLock = nullptr; 13257 self->mOpenStorageForAllRepositoriesFailed = true; 13258 13259 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 13260 self->IsAborted()) { 13261 self->DirectoryLockFailed(); 13262 return; 13263 } 13264 13265 self->OpenStorageDirectory(PersistenceScope::CreateFromValue( 13266 PERSISTENCE_TYPE_PERSISTENT), 13267 /* aInitializeOrigins */ true) 13268 ->Then(GetCurrentSerialEventTarget(), __func__, 13269 [self](const UniversalDirectoryLockPromise:: 13270 ResolveOrRejectValue& aValue) { 13271 if (aValue.IsResolve()) { 13272 self->DirectoryLockAcquired(aValue.ResolveValue()); 13273 } else { 13274 self->DirectoryLockFailed(); 13275 } 13276 }); 13277 }); 13278 13279 return NS_OK; 13280 } 13281 13282 nsresult Maintenance::DirectoryOpen() { 13283 AssertIsOnBackgroundThread(); 13284 MOZ_ASSERT(mState == State::DirectoryOpenPending); 13285 MOZ_ASSERT(mDirectoryLock); 13286 13287 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 13288 IsAborted()) { 13289 return NS_ERROR_ABORT; 13290 } 13291 13292 QuotaManager* const quotaManager = QuotaManager::Get(); 13293 MOZ_ASSERT(quotaManager); 13294 13295 mState = State::DirectoryWorkOpen; 13296 13297 QM_TRY(MOZ_TO_RESULT( 13298 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)), 13299 NS_ERROR_FAILURE); 13300 13301 return NS_OK; 13302 } 13303 13304 nsresult Maintenance::DirectoryWork() { 13305 AssertIsOnIOThread(); 13306 MOZ_ASSERT(mState == State::DirectoryWorkOpen); 13307 13308 // The storage directory is structured like this: 13309 // 13310 // <profile>/storage/<persistence>/<origin>/idb/*.sqlite 13311 // 13312 // We have to find all database files that match any persistence type and any 13313 // origin. We ignore anything out of the ordinary for now. 13314 13315 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 13316 IsAborted()) { 13317 return NS_ERROR_ABORT; 13318 } 13319 13320 QuotaManager* const quotaManager = QuotaManager::Get(); 13321 MOZ_ASSERT(quotaManager); 13322 13323 const nsCOMPtr<nsIFile> storageDir = 13324 GetFileForPath(quotaManager->GetStoragePath()); 13325 QM_TRY(OkIf(storageDir), NS_ERROR_FAILURE); 13326 13327 { 13328 QM_TRY_INSPECT(const bool& exists, 13329 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists)); 13330 13331 // XXX No warning here? 13332 if (!exists) { 13333 return NS_ERROR_NOT_AVAILABLE; 13334 } 13335 } 13336 13337 { 13338 QM_TRY_INSPECT(const bool& isDirectory, 13339 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, IsDirectory)); 13340 13341 QM_TRY(OkIf(isDirectory), NS_ERROR_FAILURE); 13342 } 13343 13344 // There are currently only 4 persistence types, and we want to iterate them 13345 // in this order: 13346 static const PersistenceType kPersistenceTypes[] = { 13347 PERSISTENCE_TYPE_PERSISTENT, PERSISTENCE_TYPE_DEFAULT, 13348 PERSISTENCE_TYPE_TEMPORARY, PERSISTENCE_TYPE_PRIVATE}; 13349 13350 static_assert( 13351 std::size(kPersistenceTypes) == size_t(PERSISTENCE_TYPE_INVALID), 13352 "Something changed with available persistence types!"); 13353 13354 constexpr auto idbDirName = 13355 NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME); 13356 13357 for (const PersistenceType persistenceType : kPersistenceTypes) { 13358 // Loop over "<persistence>" directories. 13359 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 13360 IsAborted()) { 13361 return NS_ERROR_ABORT; 13362 } 13363 13364 // Don't do any maintenance for private browsing databases, which are only 13365 // temporary. 13366 if (persistenceType == PERSISTENCE_TYPE_PRIVATE) { 13367 continue; 13368 } 13369 13370 const bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT; 13371 13372 if (!persistent && mOpenStorageForAllRepositoriesFailed) { 13373 // Non-persistent (best effort) repositories can't be processed if 13374 // temporary storage initialization failed. 13375 continue; 13376 } 13377 13378 // For all non-persistent types, temporary storage must already be 13379 // initialized 13380 MOZ_DIAGNOSTIC_ASSERT( 13381 persistent || quotaManager->IsTemporaryStorageInitializedInternal()); 13382 13383 // XXX persistenceType == PERSISTENCE_TYPE_PERSISTENT shouldn't be a special 13384 // case... 13385 const auto persistenceTypeString = 13386 persistenceType == PERSISTENCE_TYPE_PERSISTENT 13387 ? "permanent"_ns 13388 : PersistenceTypeToString(persistenceType); 13389 13390 QM_TRY_INSPECT(const auto& persistenceDir, 13391 CloneFileAndAppend(*storageDir, NS_ConvertASCIItoUTF16( 13392 persistenceTypeString))); 13393 13394 { 13395 QM_TRY_INSPECT(const bool& exists, 13396 MOZ_TO_RESULT_INVOKE_MEMBER(persistenceDir, Exists)); 13397 13398 if (!exists) { 13399 continue; 13400 } 13401 13402 QM_TRY_INSPECT(const bool& isDirectory, 13403 MOZ_TO_RESULT_INVOKE_MEMBER(persistenceDir, IsDirectory)); 13404 13405 if (NS_WARN_IF(!isDirectory)) { 13406 continue; 13407 } 13408 } 13409 13410 // Loop over "<origin>/idb" directories. 13411 QM_TRY(CollectEachFile( 13412 *persistenceDir, 13413 [this, "aManager, persistenceType, persistent, &idbDirName]( 13414 const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> { 13415 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 13416 IsAborted()) { 13417 return Err(NS_ERROR_ABORT); 13418 } 13419 13420 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir)); 13421 13422 switch (dirEntryKind) { 13423 case nsIFileKind::ExistsAsFile: 13424 break; 13425 13426 case nsIFileKind::ExistsAsDirectory: { 13427 // Get the necessary information about the origin 13428 // (GetOriginMetadata also checks if it's a valid origin). 13429 13430 QM_TRY_UNWRAP(auto metadata, 13431 quotaManager->GetOriginMetadata(originDir), 13432 // Not much we can do here... 13433 Ok{}); 13434 13435 if (!persistent && 13436 !quotaManager->IsTemporaryOriginInitializedInternal( 13437 metadata)) { 13438 glean::idb_maintenance::fallback_fullrestore_metadata.Add(); 13439 13440 // XXX GetOriginMetadata, which skips loading the metadata file 13441 // and instead relies on parsing the origin directory name and 13442 // reconstructing the principal, may produce a different origin 13443 // string than the one originally used to create the origin 13444 // directory. 13445 // 13446 // For now, if this mismatch occurs, we fall back to the slower 13447 // LoadFullOriginMetadataWithRestore. 13448 // 13449 // In the future, it would be useful to report anonymized 13450 // origin strings via telemetry to help investigate and 13451 // eventually fix this mismatch. 13452 13453 QM_TRY_INSPECT( 13454 const auto& fullmetadataAndStatus, 13455 quotaManager->LoadFullOriginMetadataWithRestoreAndStatus( 13456 originDir), 13457 Ok{}); 13458 13459 metadata = fullmetadataAndStatus.first; 13460 if (fullmetadataAndStatus.second) { 13461 /* metadata was restored */ 13462 glean::idb_maintenance::metadata_restored.Add(); 13463 } 13464 13465 QM_TRY(OkIf(quotaManager->IsTemporaryOriginInitializedInternal( 13466 metadata)), 13467 /* unexpected but still fine to just skip it */ Ok{}, 13468 /* increment telemetry in case of failure */ 13469 [](const auto&) { 13470 glean::idb_maintenance::unknown_metadata.Add(); 13471 }); 13472 } 13473 13474 // We now use a dedicated repository for private browsing 13475 // databases, but there could be some forgotten private browsing 13476 // databases in other repositories, so it's better to check for 13477 // that and don't do any maintenance for such databases. 13478 if (metadata.mIsPrivate) { 13479 return Ok{}; 13480 } 13481 13482 QM_TRY_INSPECT(const auto& idbDir, 13483 CloneFileAndAppend(*originDir, idbDirName)); 13484 13485 QM_TRY_INSPECT(const bool& exists, 13486 MOZ_TO_RESULT_INVOKE_MEMBER(idbDir, Exists)); 13487 13488 if (!exists) { 13489 return Ok{}; 13490 } 13491 13492 QM_TRY_INSPECT(const bool& isDirectory, 13493 MOZ_TO_RESULT_INVOKE_MEMBER(idbDir, IsDirectory)); 13494 13495 QM_TRY(OkIf(isDirectory), Ok{}); 13496 13497 nsTArray<nsString> databasePaths; 13498 13499 // Loop over files in the "idb" directory. 13500 QM_TRY(CollectEachFile( 13501 *idbDir, 13502 [this, &databasePaths](const nsCOMPtr<nsIFile>& idbDirFile) 13503 -> Result<Ok, nsresult> { 13504 if (NS_WARN_IF(QuotaClient:: 13505 IsShuttingDownOnNonBackgroundThread()) || 13506 IsAborted()) { 13507 return Err(NS_ERROR_ABORT); 13508 } 13509 13510 QM_TRY_UNWRAP(auto idbFilePath, 13511 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 13512 nsString, idbDirFile, GetPath)); 13513 13514 if (!StringEndsWith(idbFilePath, kSQLiteSuffix)) { 13515 return Ok{}; 13516 } 13517 13518 QM_TRY_INSPECT(const auto& dirEntryKind, 13519 GetDirEntryKind(*idbDirFile)); 13520 13521 switch (dirEntryKind) { 13522 case nsIFileKind::ExistsAsDirectory: 13523 break; 13524 13525 case nsIFileKind::ExistsAsFile: 13526 // Found a database. 13527 13528 MOZ_ASSERT(!databasePaths.Contains(idbFilePath)); 13529 13530 databasePaths.AppendElement(std::move(idbFilePath)); 13531 break; 13532 13533 case nsIFileKind::DoesNotExist: 13534 // Ignore files that got removed externally while 13535 // iterating. 13536 break; 13537 } 13538 13539 return Ok{}; 13540 })); 13541 13542 if (!databasePaths.IsEmpty()) { 13543 if (!persistent) { 13544 auto maybeOriginStateMetadata = 13545 quotaManager->GetOriginStateMetadata(metadata); 13546 auto originStateMetadata = maybeOriginStateMetadata.extract(); 13547 13548 // Skip origin maintenance if the origin hasn't been accessed 13549 // since its last recorded maintenance. This avoids 13550 // unnecessary I/O and prevents updating the accessed flag in 13551 // metadata, which helps preserve the effectiveness of the L2 13552 // quota info cache. 13553 // 13554 // This early-out is safe because maintenance is only needed 13555 // when something has changed (e.g., new access or activity). 13556 const Date accessDate = 13557 Date::FromTimestamp(originStateMetadata.mLastAccessTime); 13558 const Date maintenanceDate = 13559 Date::FromDays(originStateMetadata.mLastMaintenanceDate); 13560 13561 if (accessDate <= maintenanceDate) { 13562 return Ok{}; 13563 } 13564 13565 originStateMetadata.mLastMaintenanceDate = 13566 Date::Today().ToDays(); 13567 originStateMetadata.mAccessed = true; 13568 13569 QM_TRY(MOZ_TO_RESULT(SaveDirectoryMetadataHeader( 13570 *originDir, originStateMetadata))); 13571 13572 quotaManager->UpdateOriginMaintenanceDate( 13573 metadata, originStateMetadata.mLastMaintenanceDate); 13574 quotaManager->UpdateOriginAccessed(metadata); 13575 } 13576 13577 mDirectoryInfos.EmplaceBack(persistenceType, metadata, 13578 std::move(databasePaths)); 13579 } 13580 13581 break; 13582 } 13583 13584 case nsIFileKind::DoesNotExist: 13585 // Ignore files that got removed externally while iterating. 13586 break; 13587 } 13588 13589 return Ok{}; 13590 })); 13591 } 13592 13593 mState = State::BeginDatabaseMaintenance; 13594 13595 MOZ_ALWAYS_SUCCEEDS( 13596 mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL)); 13597 13598 return NS_OK; 13599 } 13600 13601 nsresult Maintenance::BeginDatabaseMaintenance() { 13602 AssertIsOnBackgroundThread(); 13603 MOZ_ASSERT(mState == State::BeginDatabaseMaintenance); 13604 13605 class MOZ_STACK_CLASS Helper final { 13606 public: 13607 static bool IsSafeToRunMaintenance(const nsAString& aDatabasePath) { 13608 if (gFactoryOps) { 13609 // XXX LinkedList should support reverse iteration via rbegin() and 13610 // rend(), see bug 1964967. 13611 for (const FactoryOp* existingOp = gFactoryOps->getLast(); existingOp; 13612 existingOp = existingOp->getPrevious()) { 13613 if (existingOp->DatabaseNameRef().isNothing()) { 13614 return false; 13615 } 13616 13617 if (!existingOp->DatabaseFilePathIsKnown()) { 13618 continue; 13619 } 13620 13621 if (existingOp->DatabaseFilePath() == aDatabasePath) { 13622 return false; 13623 } 13624 } 13625 } 13626 13627 if (gLiveDatabaseHashtable) { 13628 return std::all_of(gLiveDatabaseHashtable->Values().cbegin(), 13629 gLiveDatabaseHashtable->Values().cend(), 13630 [&aDatabasePath](const auto& liveDatabasesEntry) { 13631 // XXX std::all_of currently doesn't work with 13632 // LinkedList's iterator. See bug 1964969. 13633 for (const Database* const database : 13634 liveDatabasesEntry->mLiveDatabases) { 13635 if (database->FilePath() == aDatabasePath) { 13636 return false; 13637 } 13638 } 13639 return true; 13640 }); 13641 } 13642 13643 return true; 13644 } 13645 }; 13646 13647 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 13648 IsAborted()) { 13649 return NS_ERROR_ABORT; 13650 } 13651 13652 RefPtr<nsThreadPool> threadPool; 13653 13654 for (DirectoryInfo& directoryInfo : mDirectoryInfos) { 13655 for (const nsAString& databasePath : *directoryInfo.mDatabasePaths) { 13656 if (Helper::IsSafeToRunMaintenance(databasePath)) { 13657 RefPtr<ClientDirectoryLock> directoryLock = 13658 mDirectoryLock->SpecializeForClient(directoryInfo.mPersistenceType, 13659 *directoryInfo.mOriginMetadata, 13660 Client::IDB); 13661 MOZ_ASSERT(directoryLock); 13662 13663 // No key needs to be passed here, because we skip encrypted databases 13664 // in DoDirectoryWork as long as they are only used in private browsing 13665 // mode. 13666 const auto databaseMaintenance = MakeRefPtr<DatabaseMaintenance>( 13667 this, std::move(directoryLock), directoryInfo.mPersistenceType, 13668 *directoryInfo.mOriginMetadata, databasePath, Nothing{}); 13669 13670 if (!threadPool) { 13671 threadPool = mQuotaClient->GetOrCreateThreadPool(); 13672 MOZ_ASSERT(threadPool); 13673 } 13674 13675 // Perform database maintenance on a TaskQueue, as database connections 13676 // require a serial event target when being opened in order to allow 13677 // memory pressure notifications to clear caches (bug 1806751). 13678 const auto taskQueue = TaskQueue::Create( 13679 do_AddRef(threadPool), "IndexedDB Database Maintenance"); 13680 13681 MOZ_ALWAYS_SUCCEEDS( 13682 taskQueue->Dispatch(databaseMaintenance, NS_DISPATCH_NORMAL)); 13683 13684 RegisterDatabaseMaintenance(databaseMaintenance); 13685 } 13686 } 13687 } 13688 13689 mDirectoryInfos.Clear(); 13690 13691 DropDirectoryLock(mDirectoryLock); 13692 13693 if (mDatabaseMaintenances.Count()) { 13694 mState = State::WaitingForDatabaseMaintenancesToComplete; 13695 } else { 13696 mState = State::Finishing; 13697 Finish(); 13698 } 13699 13700 return NS_OK; 13701 } 13702 13703 void Maintenance::Finish() { 13704 AssertIsOnBackgroundThread(); 13705 MOZ_ASSERT(mState == State::Finishing); 13706 13707 if (NS_SUCCEEDED(mResultCode)) { 13708 mPromiseHolder.ResolveIfExists(true, __func__); 13709 } else { 13710 mPromiseHolder.RejectIfExists(mResultCode, __func__); 13711 13712 nsCString errorName; 13713 GetErrorName(mResultCode, errorName); 13714 13715 IDB_WARNING("Maintenance finished with error: %s", errorName.get()); 13716 } 13717 13718 SafeDropDirectoryLock(mDirectoryLock); 13719 13720 // It can happen that we are only referenced by mCurrentMaintenance which is 13721 // cleared in NoteFinishedMaintenance() 13722 const RefPtr<Maintenance> kungFuDeathGrip = this; 13723 13724 mQuotaClient->NoteFinishedMaintenance(this); 13725 13726 mState = State::Complete; 13727 } 13728 13729 NS_IMETHODIMP 13730 Maintenance::Run() { 13731 MOZ_ASSERT(mState != State::Complete); 13732 13733 const auto handleError = [this](const nsresult rv) { 13734 if (mState != State::Finishing) { 13735 if (NS_SUCCEEDED(mResultCode)) { 13736 mResultCode = rv; 13737 } 13738 13739 // Must set mState before dispatching otherwise we will race with the 13740 // owning thread. 13741 mState = State::Finishing; 13742 13743 if (IsOnBackgroundThread()) { 13744 Finish(); 13745 } else { 13746 MOZ_ALWAYS_SUCCEEDS(mQuotaClient->BackgroundThread()->Dispatch( 13747 this, NS_DISPATCH_NORMAL)); 13748 } 13749 } 13750 }; 13751 13752 switch (mState) { 13753 case State::Initial: 13754 QM_TRY(MOZ_TO_RESULT(Start()), NS_OK, handleError); 13755 break; 13756 13757 case State::CreateIndexedDatabaseManager: 13758 QM_TRY(MOZ_TO_RESULT(CreateIndexedDatabaseManager()), NS_OK, handleError); 13759 break; 13760 13761 case State::IndexedDatabaseManagerOpen: 13762 QM_TRY(MOZ_TO_RESULT(OpenDirectory()), NS_OK, handleError); 13763 break; 13764 13765 case State::DirectoryWorkOpen: 13766 QM_TRY(MOZ_TO_RESULT(DirectoryWork()), NS_OK, handleError); 13767 break; 13768 13769 case State::BeginDatabaseMaintenance: 13770 QM_TRY(MOZ_TO_RESULT(BeginDatabaseMaintenance()), NS_OK, handleError); 13771 break; 13772 13773 case State::Finishing: 13774 Finish(); 13775 break; 13776 13777 default: 13778 MOZ_CRASH("Bad state!"); 13779 } 13780 13781 return NS_OK; 13782 } 13783 13784 void Maintenance::DirectoryLockAcquired(UniversalDirectoryLock* aLock) { 13785 AssertIsOnBackgroundThread(); 13786 MOZ_ASSERT(mState == State::DirectoryOpenPending); 13787 MOZ_ASSERT(!mDirectoryLock); 13788 13789 mDirectoryLock = std::exchange(mPendingDirectoryLock, nullptr); 13790 13791 nsresult rv = DirectoryOpen(); 13792 if (NS_WARN_IF(NS_FAILED(rv))) { 13793 if (NS_SUCCEEDED(mResultCode)) { 13794 mResultCode = rv; 13795 } 13796 13797 mState = State::Finishing; 13798 Finish(); 13799 13800 return; 13801 } 13802 } 13803 13804 void Maintenance::DirectoryLockFailed() { 13805 AssertIsOnBackgroundThread(); 13806 MOZ_ASSERT(mState == State::DirectoryOpenPending); 13807 MOZ_ASSERT(!mDirectoryLock); 13808 13809 mPendingDirectoryLock = nullptr; 13810 13811 if (NS_SUCCEEDED(mResultCode)) { 13812 mResultCode = NS_ERROR_FAILURE; 13813 } 13814 13815 mState = State::Finishing; 13816 Finish(); 13817 } 13818 13819 void DatabaseMaintenance::Stringify(nsACString& aResult) const { 13820 AssertIsOnBackgroundThread(); 13821 13822 aResult.AppendLiteral("Origin:"); 13823 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin)); 13824 aResult.Append(kQuotaGenericDelimiter); 13825 13826 aResult.AppendLiteral("PersistenceType:"); 13827 aResult.Append(PersistenceTypeToString(mPersistenceType)); 13828 aResult.Append(kQuotaGenericDelimiter); 13829 13830 aResult.AppendLiteral("Duration:"); 13831 aResult.AppendInt((PR_Now() - mMaintenance->StartTime()) / PR_USEC_PER_MSEC); 13832 } 13833 13834 nsresult DatabaseMaintenance::Abort() { 13835 AssertIsOnBackgroundThread(); 13836 13837 // StopIdleMaintenance and AbortAllOperations may request abort independently 13838 if (!mAborted.compareExchange(false, true)) { 13839 return NS_OK; 13840 } 13841 13842 { 13843 auto shardStorageConnectionLocked = mSharedStorageConnection.Lock(); 13844 if (nsCOMPtr<mozIStorageConnection> connection = 13845 *shardStorageConnectionLocked) { 13846 QM_TRY(MOZ_TO_RESULT(connection->Interrupt())); 13847 } 13848 } 13849 13850 return NS_OK; 13851 } 13852 13853 void DatabaseMaintenance::PerformMaintenanceOnDatabase() { 13854 MOZ_ASSERT(!NS_IsMainThread()); 13855 MOZ_ASSERT(!IsOnBackgroundThread()); 13856 MOZ_ASSERT(mMaintenance); 13857 MOZ_ASSERT(mMaintenance->StartTime()); 13858 MOZ_ASSERT(mDirectoryLock); 13859 MOZ_ASSERT(!mDatabasePath.IsEmpty()); 13860 MOZ_ASSERT(!mOriginMetadata.mGroup.IsEmpty()); 13861 MOZ_ASSERT(!mOriginMetadata.mOrigin.IsEmpty()); 13862 13863 if (NS_WARN_IF(IsAborted())) { 13864 return; 13865 } 13866 13867 const nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath); 13868 MOZ_ASSERT(databaseFile); 13869 13870 QM_TRY_UNWRAP( 13871 const NotNull<nsCOMPtr<mozIStorageConnection>> connection, 13872 GetStorageConnection(*databaseFile, mDirectoryLockId, 13873 TelemetryIdForFile(databaseFile), mMaybeKey), 13874 QM_VOID); 13875 13876 auto autoClearConnection = MakeScopeExit([&]() { 13877 auto sharedStorageConnectionLocked = mSharedStorageConnection.Lock(); 13878 sharedStorageConnectionLocked.ref() = nullptr; 13879 connection->Close(); 13880 }); 13881 13882 { 13883 auto sharedStorageConnectionLocked = mSharedStorageConnection.Lock(); 13884 sharedStorageConnectionLocked.ref() = connection; 13885 } 13886 13887 auto databaseIsOk = false; 13888 QM_TRY(MOZ_TO_RESULT(CheckIntegrity(*connection, &databaseIsOk)), QM_VOID); 13889 13890 QM_TRY(OkIf(databaseIsOk), QM_VOID, [](auto result) { 13891 // XXX Handle this somehow! Probably need to clear all storage for the 13892 // origin. See Bug 1760612. 13893 MOZ_ASSERT(false, "Database corruption detected!"); 13894 }); 13895 13896 MaintenanceAction maintenanceAction; 13897 QM_TRY(MOZ_TO_RESULT(DetermineMaintenanceAction(*connection, databaseFile, 13898 &maintenanceAction)), 13899 QM_VOID); 13900 13901 switch (maintenanceAction) { 13902 case MaintenanceAction::Nothing: 13903 break; 13904 13905 case MaintenanceAction::IncrementalVacuum: 13906 IncrementalVacuum(*connection); 13907 break; 13908 13909 case MaintenanceAction::FullVacuum: 13910 FullVacuum(*connection, databaseFile); 13911 break; 13912 13913 default: 13914 MOZ_CRASH("Unknown MaintenanceAction!"); 13915 } 13916 } 13917 13918 nsresult DatabaseMaintenance::CheckIntegrity(mozIStorageConnection& aConnection, 13919 bool* aOk) { 13920 MOZ_ASSERT(!NS_IsMainThread()); 13921 MOZ_ASSERT(!IsOnBackgroundThread()); 13922 MOZ_ASSERT(aOk); 13923 13924 if (NS_WARN_IF(IsAborted())) { 13925 return NS_ERROR_ABORT; 13926 } 13927 13928 // First do a full integrity_check. Scope statements tightly here because 13929 // later operations require zero live statements. 13930 { 13931 QM_TRY_INSPECT(const auto& stmt, 13932 CreateAndExecuteSingleStepStatement( 13933 aConnection, "PRAGMA integrity_check(1);"_ns)); 13934 13935 QM_TRY_INSPECT(const auto& result, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 13936 nsString, *stmt, GetString, 0)); 13937 13938 QM_TRY(OkIf(result.EqualsLiteral("ok")), NS_OK, 13939 [&aOk](const auto) { *aOk = false; }); 13940 } 13941 13942 // Now enable and check for foreign key constraints. 13943 { 13944 QM_TRY_INSPECT( 13945 const int32_t& foreignKeysWereEnabled, 13946 ([&aConnection]() -> Result<int32_t, nsresult> { 13947 QM_TRY_INSPECT(const auto& stmt, 13948 CreateAndExecuteSingleStepStatement( 13949 aConnection, "PRAGMA foreign_keys;"_ns)); 13950 13951 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0)); 13952 }())); 13953 13954 if (!foreignKeysWereEnabled) { 13955 QM_TRY(MOZ_TO_RESULT( 13956 aConnection.ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); 13957 } 13958 13959 QM_TRY_INSPECT(const bool& foreignKeyError, 13960 CreateAndExecuteSingleStepStatement< 13961 SingleStepResult::ReturnNullIfNoResult>( 13962 aConnection, "PRAGMA foreign_key_check;"_ns)); 13963 13964 if (!foreignKeysWereEnabled) { 13965 QM_TRY(MOZ_TO_RESULT( 13966 aConnection.ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns))); 13967 } 13968 13969 if (foreignKeyError) { 13970 *aOk = false; 13971 return NS_OK; 13972 } 13973 } 13974 13975 *aOk = true; 13976 return NS_OK; 13977 } 13978 13979 nsresult DatabaseMaintenance::DetermineMaintenanceAction( 13980 mozIStorageConnection& aConnection, nsIFile* aDatabaseFile, 13981 MaintenanceAction* aMaintenanceAction) { 13982 MOZ_ASSERT(!NS_IsMainThread()); 13983 MOZ_ASSERT(!IsOnBackgroundThread()); 13984 MOZ_ASSERT(aDatabaseFile); 13985 MOZ_ASSERT(aMaintenanceAction); 13986 13987 if (NS_WARN_IF(IsAborted())) { 13988 return NS_ERROR_ABORT; 13989 } 13990 13991 QM_TRY_INSPECT(const int32_t& schemaVersion, 13992 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion)); 13993 13994 // Don't do anything if the schema version is less than 18; before that 13995 // version no databases had |auto_vacuum == INCREMENTAL| set and we didn't 13996 // track the values needed for the heuristics below. 13997 if (schemaVersion < MakeSchemaVersion(18, 0)) { 13998 *aMaintenanceAction = MaintenanceAction::Nothing; 13999 return NS_OK; 14000 } 14001 14002 // This method shouldn't make any permanent changes to the database, so make 14003 // sure everything gets rolled back when we leave. 14004 mozStorageTransaction transaction(&aConnection, 14005 /* aCommitOnComplete */ false); 14006 14007 QM_TRY(MOZ_TO_RESULT(transaction.Start())) 14008 14009 // Check to see when we last vacuumed this database. 14010 QM_TRY_INSPECT(const auto& stmt, 14011 CreateAndExecuteSingleStepStatement( 14012 aConnection, 14013 "SELECT last_vacuum_time, last_vacuum_size " 14014 "FROM database;"_ns)); 14015 14016 QM_TRY_INSPECT(const PRTime& lastVacuumTime, 14017 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 0)); 14018 14019 QM_TRY_INSPECT(const int64_t& lastVacuumSize, 14020 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 1)); 14021 14022 NS_ASSERTION(lastVacuumSize > 0, 14023 "Thy last vacuum size shall be greater than zero, less than " 14024 "zero shall thy last vacuum size not be. Zero is right out."); 14025 14026 const PRTime startTime = mMaintenance->StartTime(); 14027 14028 // This shouldn't really be possible... 14029 if (NS_WARN_IF(startTime <= lastVacuumTime)) { 14030 *aMaintenanceAction = MaintenanceAction::Nothing; 14031 return NS_OK; 14032 } 14033 14034 if (startTime - lastVacuumTime < kMinVacuumAge) { 14035 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum; 14036 return NS_OK; 14037 } 14038 14039 // It has been more than a week since the database was vacuumed, so gather 14040 // statistics on its usage to see if vacuuming is worthwhile. 14041 14042 // Create a temporary copy of the dbstat table to speed up the queries that 14043 // come later. 14044 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL( 14045 "CREATE VIRTUAL TABLE __stats__ USING dbstat;" 14046 "CREATE TEMP TABLE __temp_stats__ AS SELECT * FROM __stats__;"_ns))); 14047 14048 { // Calculate the percentage of the database pages that are not in 14049 // contiguous order. 14050 QM_TRY_INSPECT( 14051 const auto& stmt, 14052 CreateAndExecuteSingleStepStatement( 14053 aConnection, 14054 "SELECT SUM(__ts1__.pageno != __ts2__.pageno + 1) * 100.0 / " 14055 "COUNT(*) " 14056 "FROM __temp_stats__ AS __ts1__, __temp_stats__ AS __ts2__ " 14057 "WHERE __ts1__.name = __ts2__.name " 14058 "AND __ts1__.rowid = __ts2__.rowid + 1;"_ns)); 14059 14060 QM_TRY_INSPECT(const int32_t& percentUnordered, 14061 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0)); 14062 14063 MOZ_ASSERT(percentUnordered >= 0); 14064 MOZ_ASSERT(percentUnordered <= 100); 14065 14066 if (percentUnordered >= kPercentUnorderedThreshold) { 14067 *aMaintenanceAction = MaintenanceAction::FullVacuum; 14068 return NS_OK; 14069 } 14070 } 14071 14072 // Don't try a full vacuum if the file hasn't grown by 10%. 14073 QM_TRY_INSPECT(const int64_t& currentFileSize, 14074 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, GetFileSize)); 14075 14076 if (currentFileSize <= lastVacuumSize || 14077 (((currentFileSize - lastVacuumSize) * 100 / currentFileSize) < 14078 kPercentFileSizeGrowthThreshold)) { 14079 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum; 14080 return NS_OK; 14081 } 14082 14083 { // See if there are any free pages that we can reclaim. 14084 QM_TRY_INSPECT(const auto& stmt, 14085 CreateAndExecuteSingleStepStatement( 14086 aConnection, "PRAGMA freelist_count;"_ns)); 14087 14088 QM_TRY_INSPECT(const int32_t& freelistCount, 14089 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0)); 14090 14091 MOZ_ASSERT(freelistCount >= 0); 14092 14093 // If we have too many free pages then we should try an incremental 14094 // vacuum. If that causes too much fragmentation then we'll try a full 14095 // vacuum later. 14096 if (freelistCount > kMaxFreelistThreshold) { 14097 *aMaintenanceAction = MaintenanceAction::IncrementalVacuum; 14098 return NS_OK; 14099 } 14100 } 14101 14102 { // Calculate the percentage of unused bytes on pages in the database. 14103 QM_TRY_INSPECT( 14104 const auto& stmt, 14105 CreateAndExecuteSingleStepStatement( 14106 aConnection, 14107 "SELECT SUM(unused) * 100.0 / SUM(pgsize) FROM __temp_stats__;"_ns)); 14108 14109 QM_TRY_INSPECT(const int32_t& percentUnused, 14110 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0)); 14111 14112 MOZ_ASSERT(percentUnused >= 0); 14113 MOZ_ASSERT(percentUnused <= 100); 14114 14115 *aMaintenanceAction = percentUnused >= kPercentUnusedThreshold 14116 ? MaintenanceAction::FullVacuum 14117 : MaintenanceAction::IncrementalVacuum; 14118 } 14119 14120 return NS_OK; 14121 } 14122 14123 void DatabaseMaintenance::IncrementalVacuum( 14124 mozIStorageConnection& aConnection) { 14125 MOZ_ASSERT(!NS_IsMainThread()); 14126 MOZ_ASSERT(!IsOnBackgroundThread()); 14127 14128 if (NS_WARN_IF(IsAborted())) { 14129 return; 14130 } 14131 14132 nsresult rv = aConnection.ExecuteSimpleSQL("PRAGMA incremental_vacuum;"_ns); 14133 if (NS_WARN_IF(NS_FAILED(rv))) { 14134 return; 14135 } 14136 } 14137 14138 void DatabaseMaintenance::FullVacuum(mozIStorageConnection& aConnection, 14139 nsIFile* aDatabaseFile) { 14140 MOZ_ASSERT(!NS_IsMainThread()); 14141 MOZ_ASSERT(!IsOnBackgroundThread()); 14142 MOZ_ASSERT(aDatabaseFile); 14143 14144 if (NS_WARN_IF(IsAborted())) { 14145 return; 14146 } 14147 14148 QM_WARNONLY_TRY(([&]() -> Result<Ok, nsresult> { 14149 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("VACUUM;"_ns))); 14150 14151 const PRTime vacuumTime = PR_Now(); 14152 MOZ_ASSERT(vacuumTime > 0); 14153 14154 QM_TRY_INSPECT(const int64_t& fileSize, 14155 MOZ_TO_RESULT_INVOKE_MEMBER(aDatabaseFile, GetFileSize)); 14156 14157 MOZ_ASSERT(fileSize > 0); 14158 14159 // The parameter names are not used, parameters are bound by index only 14160 // locally in the same function. 14161 QM_TRY_INSPECT(const auto& stmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 14162 nsCOMPtr<mozIStorageStatement>, 14163 aConnection, CreateStatement, 14164 "UPDATE database " 14165 "SET last_vacuum_time = :time" 14166 ", last_vacuum_size = :size;"_ns)); 14167 14168 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByIndex(0, vacuumTime))); 14169 14170 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByIndex(1, fileSize))); 14171 14172 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 14173 return Ok{}; 14174 }())); 14175 } 14176 14177 void DatabaseMaintenance::RunOnOwningThread() { 14178 AssertIsOnBackgroundThread(); 14179 14180 DropDirectoryLock(mDirectoryLock); 14181 14182 if (mCompleteCallback) { 14183 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget())); 14184 } 14185 14186 mMaintenance->UnregisterDatabaseMaintenance(this); 14187 } 14188 14189 void DatabaseMaintenance::RunOnConnectionThread() { 14190 MOZ_ASSERT(!NS_IsMainThread()); 14191 MOZ_ASSERT(!IsOnBackgroundThread()); 14192 14193 PerformMaintenanceOnDatabase(); 14194 14195 MOZ_ALWAYS_SUCCEEDS( 14196 mMaintenance->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL)); 14197 } 14198 14199 NS_IMETHODIMP 14200 DatabaseMaintenance::Run() { 14201 if (IsOnBackgroundThread()) { 14202 RunOnOwningThread(); 14203 } else { 14204 RunOnConnectionThread(); 14205 } 14206 14207 return NS_OK; 14208 } 14209 14210 /******************************************************************************* 14211 * Local class implementations 14212 ******************************************************************************/ 14213 14214 // static 14215 nsAutoCString DatabaseOperationBase::MaybeGetBindingClauseForKeyRange( 14216 const Maybe<SerializedKeyRange>& aOptionalKeyRange, 14217 const nsACString& aKeyColumnName) { 14218 return aOptionalKeyRange.isSome() 14219 ? GetBindingClauseForKeyRange(aOptionalKeyRange.ref(), 14220 aKeyColumnName) 14221 : nsAutoCString{}; 14222 } 14223 14224 // static 14225 nsAutoCString DatabaseOperationBase::GetBindingClauseForKeyRange( 14226 const SerializedKeyRange& aKeyRange, const nsACString& aKeyColumnName) { 14227 MOZ_ASSERT(!IsOnBackgroundThread()); 14228 MOZ_ASSERT(!aKeyColumnName.IsEmpty()); 14229 14230 constexpr auto andStr = " AND "_ns; 14231 constexpr auto spacecolon = " :"_ns; 14232 14233 nsAutoCString result; 14234 if (aKeyRange.isOnly()) { 14235 // Both keys equal. 14236 result = 14237 andStr + aKeyColumnName + " ="_ns + spacecolon + kStmtParamNameLowerKey; 14238 } else { 14239 if (!aKeyRange.lower().IsUnset()) { 14240 // Lower key is set. 14241 result.Append(andStr + aKeyColumnName); 14242 result.AppendLiteral(" >"); 14243 if (!aKeyRange.lowerOpen()) { 14244 result.AppendLiteral("="); 14245 } 14246 result.Append(spacecolon + kStmtParamNameLowerKey); 14247 } 14248 14249 if (!aKeyRange.upper().IsUnset()) { 14250 // Upper key is set. 14251 result.Append(andStr + aKeyColumnName); 14252 result.AppendLiteral(" <"); 14253 if (!aKeyRange.upperOpen()) { 14254 result.AppendLiteral("="); 14255 } 14256 result.Append(spacecolon + kStmtParamNameUpperKey); 14257 } 14258 } 14259 14260 MOZ_ASSERT(!result.IsEmpty()); 14261 14262 return result; 14263 } 14264 14265 // static 14266 uint64_t DatabaseOperationBase::ReinterpretDoubleAsUInt64(double aDouble) { 14267 // This is a duplicate of the js engine's byte munging in StructuredClone.cpp 14268 return BitwiseCast<uint64_t>(aDouble); 14269 } 14270 14271 // static 14272 template <typename KeyTransformation> 14273 nsresult DatabaseOperationBase::MaybeBindKeyToStatement( 14274 const Key& aKey, mozIStorageStatement* const aStatement, 14275 const nsACString& aParameterName, 14276 const KeyTransformation& aKeyTransformation) { 14277 MOZ_ASSERT(!IsOnBackgroundThread()); 14278 MOZ_ASSERT(aStatement); 14279 14280 if (!aKey.IsUnset()) { 14281 // XXX This case distinction could be avoided if QM_TRY_INSPECT would also 14282 // work with a function not returning a Result<V, E> but simply a V (which 14283 // is const Key& here) and then assuming it is always a success. Or the 14284 // transformation could be changed to return Result<const V&, void> but I 14285 // don't think that Result supports that at the moment. 14286 if constexpr (std::is_reference_v< 14287 std::invoke_result_t<KeyTransformation, Key>>) { 14288 QM_TRY(MOZ_TO_RESULT(aKeyTransformation(aKey).BindToStatement( 14289 aStatement, aParameterName))); 14290 } else { 14291 QM_TRY_INSPECT(const auto& transformedKey, aKeyTransformation(aKey)); 14292 QM_TRY(MOZ_TO_RESULT( 14293 transformedKey.BindToStatement(aStatement, aParameterName))); 14294 } 14295 } 14296 14297 return NS_OK; 14298 } 14299 14300 // static 14301 template <typename KeyTransformation> 14302 nsresult DatabaseOperationBase::BindTransformedKeyRangeToStatement( 14303 const SerializedKeyRange& aKeyRange, mozIStorageStatement* const aStatement, 14304 const KeyTransformation& aKeyTransformation) { 14305 MOZ_ASSERT(!IsOnBackgroundThread()); 14306 MOZ_ASSERT(aStatement); 14307 14308 QM_TRY(MOZ_TO_RESULT(MaybeBindKeyToStatement(aKeyRange.lower(), aStatement, 14309 kStmtParamNameLowerKey, 14310 aKeyTransformation))); 14311 14312 if (aKeyRange.isOnly()) { 14313 return NS_OK; 14314 } 14315 14316 QM_TRY(MOZ_TO_RESULT(MaybeBindKeyToStatement(aKeyRange.upper(), aStatement, 14317 kStmtParamNameUpperKey, 14318 aKeyTransformation))); 14319 14320 return NS_OK; 14321 } 14322 14323 // static 14324 nsresult DatabaseOperationBase::BindKeyRangeToStatement( 14325 const SerializedKeyRange& aKeyRange, 14326 mozIStorageStatement* const aStatement) { 14327 return BindTransformedKeyRangeToStatement( 14328 aKeyRange, aStatement, [](const Key& key) -> const auto& { return key; }); 14329 } 14330 14331 // static 14332 nsresult DatabaseOperationBase::BindKeyRangeToStatement( 14333 const SerializedKeyRange& aKeyRange, mozIStorageStatement* const aStatement, 14334 const nsCString& aLocale) { 14335 MOZ_ASSERT(!aLocale.IsEmpty()); 14336 14337 return BindTransformedKeyRangeToStatement( 14338 aKeyRange, aStatement, 14339 [&aLocale](const Key& key) { return key.ToLocaleAwareKey(aLocale); }); 14340 } 14341 14342 // static 14343 void CommonOpenOpHelperBase::AppendConditionClause( 14344 const nsACString& aColumnName, const nsACString& aStatementParameterName, 14345 bool aLessThan, bool aEquals, nsCString& aResult) { 14346 aResult += " AND "_ns + aColumnName + " "_ns; 14347 14348 if (aLessThan) { 14349 aResult.Append('<'); 14350 } else { 14351 aResult.Append('>'); 14352 } 14353 14354 if (aEquals) { 14355 aResult.Append('='); 14356 } 14357 14358 aResult += " :"_ns + aStatementParameterName; 14359 } 14360 14361 // static 14362 Result<IndexDataValuesAutoArray, nsresult> 14363 DatabaseOperationBase::IndexDataValuesFromUpdateInfos( 14364 const nsTArray<IndexUpdateInfo>& aUpdateInfos, 14365 const UniqueIndexTable& aUniqueIndexTable) { 14366 MOZ_ASSERT_IF(!aUpdateInfos.IsEmpty(), aUniqueIndexTable.Count()); 14367 14368 AUTO_PROFILER_LABEL("DatabaseOperationBase::IndexDataValuesFromUpdateInfos", 14369 DOM); 14370 14371 // XXX We could use TransformIntoNewArray here if it allowed to specify that 14372 // an AutoArray should be created. 14373 IndexDataValuesAutoArray indexValues; 14374 14375 if (NS_WARN_IF(!indexValues.SetCapacity(aUpdateInfos.Length(), fallible))) { 14376 IDB_REPORT_INTERNAL_ERR(); 14377 return Err(NS_ERROR_OUT_OF_MEMORY); 14378 } 14379 14380 std::transform(aUpdateInfos.cbegin(), aUpdateInfos.cend(), 14381 MakeBackInserter(indexValues), 14382 [&aUniqueIndexTable](const IndexUpdateInfo& updateInfo) { 14383 const IndexOrObjectStoreId& indexId = updateInfo.indexId(); 14384 14385 bool unique = false; 14386 MOZ_ALWAYS_TRUE(aUniqueIndexTable.Get(indexId, &unique)); 14387 14388 return IndexDataValue{indexId, unique, updateInfo.value(), 14389 updateInfo.localizedValue()}; 14390 }); 14391 indexValues.Sort(); 14392 14393 return indexValues; 14394 } 14395 14396 // static 14397 nsresult DatabaseOperationBase::InsertIndexTableRows( 14398 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId, 14399 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues) { 14400 MOZ_ASSERT(aConnection); 14401 aConnection->AssertIsOnConnectionThread(); 14402 MOZ_ASSERT(!aObjectStoreKey.IsUnset()); 14403 14404 AUTO_PROFILER_LABEL("DatabaseOperationBase::InsertIndexTableRows", DOM); 14405 14406 const uint32_t count = aIndexValues.Length(); 14407 if (!count) { 14408 return NS_OK; 14409 } 14410 14411 auto insertUniqueStmt = DatabaseConnection::LazyStatement{ 14412 *aConnection, 14413 "INSERT INTO unique_index_data " 14414 "(index_id, value, object_store_id, " 14415 "object_data_key, value_locale) " 14416 "VALUES (:"_ns + 14417 kStmtParamNameIndexId + ", :"_ns + kStmtParamNameValue + ", :"_ns + 14418 kStmtParamNameObjectStoreId + ", :"_ns + kStmtParamNameObjectDataKey + 14419 ", :"_ns + kStmtParamNameValueLocale + ");"_ns}; 14420 auto insertStmt = DatabaseConnection::LazyStatement{ 14421 *aConnection, 14422 "INSERT OR IGNORE INTO index_data " 14423 "(index_id, value, object_data_key, " 14424 "object_store_id, value_locale) " 14425 "VALUES (:"_ns + 14426 kStmtParamNameIndexId + ", :"_ns + kStmtParamNameValue + ", :"_ns + 14427 kStmtParamNameObjectDataKey + ", :"_ns + kStmtParamNameObjectStoreId + 14428 ", :"_ns + kStmtParamNameValueLocale + ");"_ns}; 14429 14430 for (uint32_t index = 0; index < count; index++) { 14431 const IndexDataValue& info = aIndexValues[index]; 14432 14433 auto& stmt = info.mUnique ? insertUniqueStmt : insertStmt; 14434 14435 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow()); 14436 14437 QM_TRY(MOZ_TO_RESULT( 14438 borrowedStmt->BindInt64ByName(kStmtParamNameIndexId, info.mIndexId))); 14439 QM_TRY(MOZ_TO_RESULT( 14440 info.mPosition.BindToStatement(&*borrowedStmt, kStmtParamNameValue))); 14441 QM_TRY(MOZ_TO_RESULT(info.mLocaleAwarePosition.BindToStatement( 14442 &*borrowedStmt, kStmtParamNameValueLocale))); 14443 QM_TRY(MOZ_TO_RESULT(borrowedStmt->BindInt64ByName( 14444 kStmtParamNameObjectStoreId, aObjectStoreId))); 14445 QM_TRY(MOZ_TO_RESULT(aObjectStoreKey.BindToStatement( 14446 &*borrowedStmt, kStmtParamNameObjectDataKey))); 14447 14448 // QM_OR_ELSE_WARN_IF is not used here since we just want to log the 14449 // collision and not spam the reports. 14450 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF( 14451 // Expression. 14452 MOZ_TO_RESULT(borrowedStmt->Execute()), 14453 // Predicate. 14454 ([&info, index, &aIndexValues](nsresult rv) { 14455 if (rv == NS_ERROR_STORAGE_CONSTRAINT && info.mUnique) { 14456 // If we're inserting multiple entries for the same unique 14457 // index, then we might have failed to insert due to 14458 // colliding with another entry for the same index in which 14459 // case we should ignore it. 14460 for (int32_t index2 = int32_t(index) - 1; 14461 index2 >= 0 && aIndexValues[index2].mIndexId == info.mIndexId; 14462 --index2) { 14463 if (info.mPosition == aIndexValues[index2].mPosition) { 14464 // We found a key with the same value for the same 14465 // index. So we must have had a collision with a value 14466 // we just inserted. 14467 return true; 14468 } 14469 } 14470 } 14471 14472 return false; 14473 }), 14474 // Fallback. 14475 ErrToDefaultOk<>)); 14476 } 14477 14478 return NS_OK; 14479 } 14480 14481 // static 14482 nsresult DatabaseOperationBase::DeleteIndexDataTableRows( 14483 DatabaseConnection* aConnection, const Key& aObjectStoreKey, 14484 const nsTArray<IndexDataValue>& aIndexValues) { 14485 MOZ_ASSERT(aConnection); 14486 aConnection->AssertIsOnConnectionThread(); 14487 MOZ_ASSERT(!aObjectStoreKey.IsUnset()); 14488 14489 AUTO_PROFILER_LABEL("DatabaseOperationBase::DeleteIndexDataTableRows", DOM); 14490 14491 const uint32_t count = aIndexValues.Length(); 14492 if (!count) { 14493 return NS_OK; 14494 } 14495 14496 auto deleteUniqueStmt = DatabaseConnection::LazyStatement{ 14497 *aConnection, "DELETE FROM unique_index_data WHERE index_id = :"_ns + 14498 kStmtParamNameIndexId + " AND value = :"_ns + 14499 kStmtParamNameValue + ";"_ns}; 14500 auto deleteStmt = DatabaseConnection::LazyStatement{ 14501 *aConnection, "DELETE FROM index_data WHERE index_id = :"_ns + 14502 kStmtParamNameIndexId + " AND value = :"_ns + 14503 kStmtParamNameValue + " AND object_data_key = :"_ns + 14504 kStmtParamNameObjectDataKey + ";"_ns}; 14505 14506 for (uint32_t index = 0; index < count; index++) { 14507 const IndexDataValue& indexValue = aIndexValues[index]; 14508 14509 auto& stmt = indexValue.mUnique ? deleteUniqueStmt : deleteStmt; 14510 14511 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow()); 14512 14513 QM_TRY(MOZ_TO_RESULT(borrowedStmt->BindInt64ByName(kStmtParamNameIndexId, 14514 indexValue.mIndexId))); 14515 14516 QM_TRY(MOZ_TO_RESULT(indexValue.mPosition.BindToStatement( 14517 &*borrowedStmt, kStmtParamNameValue))); 14518 14519 if (!indexValue.mUnique) { 14520 QM_TRY(MOZ_TO_RESULT(aObjectStoreKey.BindToStatement( 14521 &*borrowedStmt, kStmtParamNameObjectDataKey))); 14522 } 14523 14524 QM_TRY(MOZ_TO_RESULT(borrowedStmt->Execute())); 14525 } 14526 14527 return NS_OK; 14528 } 14529 14530 // static 14531 nsresult DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes( 14532 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId, 14533 const Maybe<SerializedKeyRange>& aKeyRange) { 14534 MOZ_ASSERT(aConnection); 14535 aConnection->AssertIsOnConnectionThread(); 14536 MOZ_ASSERT(aObjectStoreId); 14537 14538 #ifdef DEBUG 14539 { 14540 QM_TRY_INSPECT(const bool& hasIndexes, 14541 ObjectStoreHasIndexes(*aConnection, aObjectStoreId), 14542 QM_PROPAGATE, [](const auto&) { MOZ_ASSERT(false); }); 14543 MOZ_ASSERT(hasIndexes, 14544 "Don't use this slow method if there are no indexes!"); 14545 } 14546 #endif 14547 14548 AUTO_PROFILER_LABEL( 14549 "DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes", DOM); 14550 14551 const bool singleRowOnly = aKeyRange.isSome() && aKeyRange.ref().isOnly(); 14552 14553 const auto keyRangeClause = 14554 MaybeGetBindingClauseForKeyRange(aKeyRange, kColumnNameKey); 14555 14556 Key objectStoreKey; 14557 QM_TRY_INSPECT( 14558 const auto& selectStmt, 14559 ([singleRowOnly, &aConnection, &objectStoreKey, &aKeyRange, 14560 &keyRangeClause]() 14561 -> Result<CachingDatabaseConnection::BorrowedStatement, nsresult> { 14562 if (singleRowOnly) { 14563 QM_TRY_UNWRAP(auto selectStmt, 14564 aConnection->BorrowCachedStatement( 14565 "SELECT index_data_values " 14566 "FROM object_data " 14567 "WHERE object_store_id = :"_ns + 14568 kStmtParamNameObjectStoreId + " AND key = :"_ns + 14569 kStmtParamNameKey + ";"_ns)); 14570 14571 objectStoreKey = aKeyRange.ref().lower(); 14572 14573 QM_TRY(MOZ_TO_RESULT( 14574 objectStoreKey.BindToStatement(&*selectStmt, kStmtParamNameKey))); 14575 14576 return selectStmt; 14577 } 14578 14579 QM_TRY_UNWRAP( 14580 auto selectStmt, 14581 aConnection->BorrowCachedStatement( 14582 "SELECT index_data_values, "_ns + kColumnNameKey + 14583 " FROM object_data WHERE object_store_id = :"_ns + 14584 kStmtParamNameObjectStoreId + keyRangeClause + ";"_ns)); 14585 14586 if (aKeyRange.isSome()) { 14587 QM_TRY(MOZ_TO_RESULT( 14588 BindKeyRangeToStatement(aKeyRange.ref(), &*selectStmt))); 14589 } 14590 14591 return selectStmt; 14592 }())); 14593 14594 QM_TRY(MOZ_TO_RESULT(selectStmt->BindInt64ByName(kStmtParamNameObjectStoreId, 14595 aObjectStoreId))); 14596 14597 DebugOnly<uint32_t> resultCountDEBUG = 0; 14598 14599 QM_TRY(CollectWhileHasResult( 14600 *selectStmt, 14601 [singleRowOnly, &objectStoreKey, &aConnection, &resultCountDEBUG, 14602 indexValues = IndexDataValuesAutoArray{}]( 14603 auto& selectStmt) mutable -> Result<Ok, nsresult> { 14604 if (!singleRowOnly) { 14605 QM_TRY( 14606 MOZ_TO_RESULT(objectStoreKey.SetFromStatement(&selectStmt, 1))); 14607 14608 indexValues.ClearAndRetainStorage(); 14609 } 14610 14611 QM_TRY(MOZ_TO_RESULT( 14612 ReadCompressedIndexDataValues(selectStmt, 0, indexValues))); 14613 QM_TRY(MOZ_TO_RESULT(DeleteIndexDataTableRows( 14614 aConnection, objectStoreKey, indexValues))); 14615 14616 resultCountDEBUG++; 14617 14618 return Ok{}; 14619 })); 14620 14621 MOZ_ASSERT_IF(singleRowOnly, resultCountDEBUG <= 1); 14622 14623 QM_TRY_UNWRAP( 14624 auto deleteManyStmt, 14625 aConnection->BorrowCachedStatement( 14626 "DELETE FROM object_data "_ns + "WHERE object_store_id = :"_ns + 14627 kStmtParamNameObjectStoreId + keyRangeClause + ";"_ns)); 14628 14629 QM_TRY(MOZ_TO_RESULT(deleteManyStmt->BindInt64ByName( 14630 kStmtParamNameObjectStoreId, aObjectStoreId))); 14631 14632 if (aKeyRange.isSome()) { 14633 QM_TRY(MOZ_TO_RESULT( 14634 BindKeyRangeToStatement(aKeyRange.ref(), &*deleteManyStmt))); 14635 } 14636 14637 QM_TRY(MOZ_TO_RESULT(deleteManyStmt->Execute())); 14638 14639 return NS_OK; 14640 } 14641 14642 // static 14643 nsresult DatabaseOperationBase::UpdateIndexValues( 14644 DatabaseConnection* aConnection, const IndexOrObjectStoreId aObjectStoreId, 14645 const Key& aObjectStoreKey, const nsTArray<IndexDataValue>& aIndexValues) { 14646 MOZ_ASSERT(aConnection); 14647 aConnection->AssertIsOnConnectionThread(); 14648 MOZ_ASSERT(!aObjectStoreKey.IsUnset()); 14649 14650 AUTO_PROFILER_LABEL("DatabaseOperationBase::UpdateIndexValues", DOM); 14651 14652 QM_TRY_UNWRAP((auto [indexDataValues, indexDataValuesLength]), 14653 MakeCompressedIndexDataValues(aIndexValues)); 14654 14655 MOZ_ASSERT(!indexDataValuesLength == !(indexDataValues.get())); 14656 14657 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 14658 "UPDATE object_data SET index_data_values = :"_ns + 14659 kStmtParamNameIndexDataValues + " WHERE object_store_id = :"_ns + 14660 kStmtParamNameObjectStoreId + " AND key = :"_ns + kStmtParamNameKey + 14661 ";"_ns, 14662 [&indexDataValues = indexDataValues, 14663 indexDataValuesLength = indexDataValuesLength, aObjectStoreId, 14664 &aObjectStoreKey]( 14665 mozIStorageStatement& updateStmt) -> Result<Ok, nsresult> { 14666 QM_TRY(MOZ_TO_RESULT( 14667 indexDataValues 14668 ? updateStmt.BindAdoptedBlobByName( 14669 kStmtParamNameIndexDataValues, indexDataValues.release(), 14670 indexDataValuesLength) 14671 : updateStmt.BindNullByName(kStmtParamNameIndexDataValues))); 14672 14673 QM_TRY(MOZ_TO_RESULT(updateStmt.BindInt64ByName( 14674 kStmtParamNameObjectStoreId, aObjectStoreId))); 14675 14676 QM_TRY(MOZ_TO_RESULT( 14677 aObjectStoreKey.BindToStatement(&updateStmt, kStmtParamNameKey))); 14678 14679 return Ok{}; 14680 }))); 14681 14682 return NS_OK; 14683 } 14684 14685 // static 14686 Result<bool, nsresult> DatabaseOperationBase::ObjectStoreHasIndexes( 14687 DatabaseConnection& aConnection, 14688 const IndexOrObjectStoreId aObjectStoreId) { 14689 aConnection.AssertIsOnConnectionThread(); 14690 MOZ_ASSERT(aObjectStoreId); 14691 14692 QM_TRY_RETURN(aConnection 14693 .BorrowAndExecuteSingleStepStatement( 14694 "SELECT id " 14695 "FROM object_store_index " 14696 "WHERE object_store_id = :"_ns + 14697 kStmtParamNameObjectStoreId + kOpenLimit + "1;"_ns, 14698 [aObjectStoreId](auto& stmt) -> Result<Ok, nsresult> { 14699 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName( 14700 kStmtParamNameObjectStoreId, aObjectStoreId))); 14701 return Ok{}; 14702 }) 14703 .map(IsSome)); 14704 } 14705 14706 NS_IMPL_ISUPPORTS_INHERITED(DatabaseOperationBase, Runnable, 14707 mozIStorageProgressHandler) 14708 14709 NS_IMETHODIMP 14710 DatabaseOperationBase::OnProgress(mozIStorageConnection* aConnection, 14711 bool* _retval) { 14712 MOZ_ASSERT(!IsOnBackgroundThread()); 14713 MOZ_ASSERT(_retval); 14714 14715 // This is intentionally racy. 14716 *_retval = QuotaClient::IsShuttingDownOnNonBackgroundThread() || 14717 !OperationMayProceed(); 14718 return NS_OK; 14719 } 14720 14721 DatabaseOperationBase::AutoSetProgressHandler::AutoSetProgressHandler() 14722 : mConnection(Nothing()) 14723 #ifdef DEBUG 14724 , 14725 mDEBUGDatabaseOp(nullptr) 14726 #endif 14727 { 14728 MOZ_ASSERT(!IsOnBackgroundThread()); 14729 } 14730 14731 DatabaseOperationBase::AutoSetProgressHandler::~AutoSetProgressHandler() { 14732 MOZ_ASSERT(!IsOnBackgroundThread()); 14733 14734 if (mConnection) { 14735 Unregister(); 14736 } 14737 } 14738 14739 nsresult DatabaseOperationBase::AutoSetProgressHandler::Register( 14740 mozIStorageConnection& aConnection, DatabaseOperationBase* aDatabaseOp) { 14741 MOZ_ASSERT(!IsOnBackgroundThread()); 14742 MOZ_ASSERT(aDatabaseOp); 14743 MOZ_ASSERT(!mConnection); 14744 14745 QM_TRY_UNWRAP( 14746 const DebugOnly oldProgressHandler, 14747 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 14748 nsCOMPtr<mozIStorageProgressHandler>, aConnection, SetProgressHandler, 14749 kStorageProgressGranularity, aDatabaseOp)); 14750 14751 MOZ_ASSERT(!oldProgressHandler.inspect()); 14752 14753 mConnection = SomeRef(aConnection); 14754 #ifdef DEBUG 14755 mDEBUGDatabaseOp = aDatabaseOp; 14756 #endif 14757 14758 return NS_OK; 14759 } 14760 14761 void DatabaseOperationBase::AutoSetProgressHandler::Unregister() { 14762 MOZ_ASSERT(!IsOnBackgroundThread()); 14763 MOZ_ASSERT(mConnection); 14764 14765 nsCOMPtr<mozIStorageProgressHandler> oldHandler; 14766 MOZ_ALWAYS_SUCCEEDS( 14767 mConnection->RemoveProgressHandler(getter_AddRefs(oldHandler))); 14768 MOZ_ASSERT(oldHandler == mDEBUGDatabaseOp); 14769 14770 mConnection = Nothing(); 14771 } 14772 14773 FactoryOp::FactoryOp(SafeRefPtr<Factory> aFactory, 14774 const Maybe<ContentParentId>& aContentParentId, 14775 const PersistenceType aPersistenceType, 14776 const PrincipalInfo& aPrincipalInfo, 14777 const Maybe<nsString>& aDatabaseName, bool aDeleting) 14778 : DatabaseOperationBase(aFactory->GetLoggingInfo()->Id(), 14779 aFactory->GetLoggingInfo()->NextRequestSN()), 14780 mFactory(std::move(aFactory)), 14781 mContentParentId(aContentParentId), 14782 mPrincipalInfo(aPrincipalInfo), 14783 mDatabaseName(aDatabaseName), 14784 mDirectoryLockId(-1), 14785 mPersistenceType(aPersistenceType), 14786 mState(State::Initial), 14787 mWaitingForPermissionRetry(false), 14788 mEnforcingQuota(true), 14789 mDeleting(aDeleting) { 14790 AssertIsOnBackgroundThread(); 14791 MOZ_ASSERT(mFactory); 14792 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 14793 } 14794 14795 nsresult FactoryOp::DispatchThisAfterProcessingCurrentEvent( 14796 nsCOMPtr<nsIEventTarget> aEventTarget) { 14797 QM_TRY(MOZ_TO_RESULT(RunAfterProcessingCurrentEvent( 14798 [eventTarget = std::move(aEventTarget), self = RefPtr(this)]() mutable { 14799 QM_WARNONLY_TRY(MOZ_TO_RESULT( 14800 eventTarget->Dispatch(self.forget(), NS_DISPATCH_NORMAL))); 14801 }))); 14802 14803 return NS_OK; 14804 } 14805 14806 void FactoryOp::NoteDatabaseBlocked(Database* aDatabase) { 14807 AssertIsOnOwningThread(); 14808 MOZ_ASSERT(aDatabase); 14809 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose); 14810 MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty()); 14811 MOZ_ASSERT(mMaybeBlockedDatabases.Contains(aDatabase)); 14812 14813 // Only send the blocked event if all databases have reported back. If the 14814 // database was closed then it will have been removed from the array. 14815 // Otherwise if it was blocked its |mBlocked| flag will be true. 14816 bool sendBlockedEvent = true; 14817 14818 for (auto& info : mMaybeBlockedDatabases) { 14819 if (info == aDatabase) { 14820 // This database was blocked, mark accordingly. 14821 info.mBlocked = true; 14822 } else if (!info.mBlocked) { 14823 // A database has not yet reported back yet, don't send the event yet. 14824 sendBlockedEvent = false; 14825 } 14826 } 14827 14828 if (sendBlockedEvent) { 14829 SendBlockedNotification(); 14830 } 14831 } 14832 14833 void FactoryOp::NoteDatabaseClosed(Database* const aDatabase) { 14834 AssertIsOnOwningThread(); 14835 MOZ_ASSERT(aDatabase); 14836 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose); 14837 MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty()); 14838 MOZ_ASSERT(mMaybeBlockedDatabases.Contains(aDatabase)); 14839 14840 mMaybeBlockedDatabases.RemoveElement(aDatabase); 14841 14842 if (!mMaybeBlockedDatabases.IsEmpty()) { 14843 return; 14844 } 14845 14846 DatabaseActorInfo* info; 14847 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)); 14848 MOZ_ASSERT(info->mWaitingFactoryOp == this); 14849 14850 if (AreActorsAlive()) { 14851 // The IPDL strong reference has not yet been released, so we can clear 14852 // mWaitingFactoryOp immediately. 14853 info->mWaitingFactoryOp = nullptr; 14854 14855 WaitForTransactions(); 14856 return; 14857 } 14858 14859 // The IPDL strong reference has been released, mWaitingFactoryOp holds the 14860 // last strong reference to us, so we need to move it to a stack variable 14861 // instead of clearing it immediately (We could clear it immediately if only 14862 // the other actor is destroyed, but we don't need to optimize for that, and 14863 // move it anyway). 14864 const RefPtr<FactoryOp> waitingFactoryOp = std::move(info->mWaitingFactoryOp); 14865 14866 IDB_REPORT_INTERNAL_ERR(); 14867 SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); 14868 14869 // We hold a strong ref in waitingFactoryOp, so it's safe to call Run() 14870 // directly. 14871 14872 mState = State::SendingResults; 14873 MOZ_ALWAYS_SUCCEEDS(Run()); 14874 } 14875 14876 void FactoryOp::StringifyState(nsACString& aResult) const { 14877 AssertIsOnOwningThread(); 14878 14879 switch (mState) { 14880 case State::Initial: 14881 aResult.AppendLiteral("Initial"); 14882 return; 14883 14884 case State::DirectoryOpenPending: 14885 aResult.AppendLiteral("DirectoryOpenPending"); 14886 return; 14887 14888 case State::DirectoryWorkOpen: 14889 aResult.AppendLiteral("DirectoryWorkOpen"); 14890 return; 14891 14892 case State::DirectoryWorkDone: 14893 aResult.AppendLiteral("DirectoryWorkDone"); 14894 return; 14895 14896 case State::DatabaseOpenPending: 14897 aResult.AppendLiteral("DatabaseOpenPending"); 14898 return; 14899 14900 case State::DatabaseWorkOpen: 14901 aResult.AppendLiteral("DatabaseWorkOpen"); 14902 return; 14903 14904 case State::BeginVersionChange: 14905 aResult.AppendLiteral("BeginVersionChange"); 14906 return; 14907 14908 case State::WaitingForOtherDatabasesToClose: 14909 aResult.AppendLiteral("WaitingForOtherDatabasesToClose"); 14910 return; 14911 14912 case State::WaitingForTransactionsToComplete: 14913 aResult.AppendLiteral("WaitingForTransactionsToComplete"); 14914 return; 14915 14916 case State::DatabaseWorkVersionChange: 14917 aResult.AppendLiteral("DatabaseWorkVersionChange"); 14918 return; 14919 14920 case State::SendingResults: 14921 aResult.AppendLiteral("SendingResults"); 14922 return; 14923 14924 case State::Completed: 14925 aResult.AppendLiteral("Completed"); 14926 return; 14927 14928 default: 14929 MOZ_CRASH("Bad state!"); 14930 } 14931 } 14932 14933 void FactoryOp::Stringify(nsACString& aResult) const { 14934 AssertIsOnOwningThread(); 14935 14936 aResult.AppendLiteral("PersistenceType:"); 14937 aResult.Append(PersistenceTypeToString(mPersistenceType)); 14938 aResult.Append(kQuotaGenericDelimiter); 14939 14940 aResult.AppendLiteral("Origin:"); 14941 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin)); 14942 aResult.Append(kQuotaGenericDelimiter); 14943 14944 aResult.AppendLiteral("State:"); 14945 StringifyState(aResult); 14946 } 14947 14948 nsresult FactoryOp::Open() { 14949 AssertIsOnOwningThread(); 14950 MOZ_ASSERT(mState == State::Initial); 14951 MOZ_ASSERT(mOriginMetadata.mOrigin.IsEmpty()); 14952 MOZ_ASSERT(!mDirectoryLockHandle); 14953 14954 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 14955 IsActorDestroyed()) { 14956 IDB_REPORT_INTERNAL_ERR(); 14957 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 14958 } 14959 14960 QM_TRY(QuotaManager::EnsureCreated()); 14961 14962 QuotaManager* const quotaManager = QuotaManager::Get(); 14963 MOZ_ASSERT(quotaManager); 14964 14965 QM_TRY_UNWRAP( 14966 auto principalMetadata, 14967 quota::GetInfoFromValidatedPrincipalInfo(*quotaManager, mPrincipalInfo)); 14968 14969 mOriginMetadata = {std::move(principalMetadata), mPersistenceType}; 14970 14971 if (mPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { 14972 MOZ_ASSERT(mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 14973 14974 mEnforcingQuota = false; 14975 } else if (mPrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo) { 14976 const ContentPrincipalInfo& contentPrincipalInfo = 14977 mPrincipalInfo.get_ContentPrincipalInfo(); 14978 14979 MOZ_ASSERT_IF( 14980 QuotaManager::IsOriginInternal(contentPrincipalInfo.originNoSuffix()), 14981 mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 14982 14983 mEnforcingQuota = mPersistenceType != PERSISTENCE_TYPE_PERSISTENT; 14984 14985 if (mOriginMetadata.mIsPrivate) { 14986 if (StaticPrefs::dom_indexedDB_privateBrowsing_enabled()) { 14987 // Explicitly disallow moz-extension urls from using the encrypted 14988 // indexedDB storage mode when the caller is an extension (see Bug 14989 // 1841806). 14990 if (StringBeginsWith(contentPrincipalInfo.originNoSuffix(), 14991 "moz-extension:"_ns)) { 14992 return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; 14993 } 14994 14995 mInPrivateBrowsing.Flip(); 14996 } else { 14997 return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR; 14998 } 14999 } 15000 } else { 15001 MOZ_ASSERT(false); 15002 } 15003 15004 if (mDatabaseName.isSome()) { 15005 nsCString databaseId; 15006 15007 QuotaManager::GetStorageId(mPersistenceType, mOriginMetadata.mOrigin, 15008 Client::IDB, databaseId); 15009 15010 databaseId.Append('*'); 15011 databaseId.Append(NS_ConvertUTF16toUTF8(mDatabaseName.ref())); 15012 15013 mDatabaseId = Some(std::move(databaseId)); 15014 15015 // Need to get database file path before opening the directory. 15016 // XXX: For what reason? 15017 QM_TRY_UNWRAP( 15018 auto databaseFilePath, 15019 ([this, quotaManager]() -> mozilla::Result<nsString, nsresult> { 15020 QM_TRY_INSPECT(const auto& dbFile, 15021 quotaManager->GetOriginDirectory(mOriginMetadata)); 15022 15023 QM_TRY(MOZ_TO_RESULT(dbFile->Append( 15024 NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME)))); 15025 15026 QM_TRY(MOZ_TO_RESULT(dbFile->Append( 15027 GetDatabaseFilenameBase(mDatabaseName.ref(), 15028 mOriginMetadata.mIsPrivate) + 15029 kSQLiteSuffix))); 15030 15031 QM_TRY_RETURN( 15032 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); 15033 }())); 15034 15035 mDatabaseFilePath = Some(std::move(databaseFilePath)); 15036 } 15037 15038 // Open directory 15039 mState = State::DirectoryOpenPending; 15040 15041 quotaManager->OpenClientDirectory({mOriginMetadata, Client::IDB}) 15042 ->Then( 15043 GetCurrentSerialEventTarget(), __func__, 15044 [self = RefPtr(this)](QuotaManager::ClientDirectoryLockHandlePromise:: 15045 ResolveOrRejectValue&& aValue) { 15046 if (aValue.IsResolve()) { 15047 self->DirectoryLockAcquired(std::move(aValue.ResolveValue())); 15048 } else { 15049 self->DirectoryLockFailed(); 15050 } 15051 }); 15052 15053 return NS_OK; 15054 } 15055 15056 nsresult FactoryOp::DirectoryOpen() { 15057 AssertIsOnOwningThread(); 15058 MOZ_ASSERT(mState == State::DirectoryOpenPending); 15059 MOZ_ASSERT(mDirectoryLockHandle); 15060 15061 if (mDatabaseName.isNothing()) { 15062 QuotaManager* const quotaManager = QuotaManager::Get(); 15063 MOZ_ASSERT(quotaManager); 15064 15065 // Must set this before dispatching otherwise we will race with the IO 15066 // thread. 15067 mState = State::DirectoryWorkOpen; 15068 15069 QM_TRY(MOZ_TO_RESULT( 15070 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)), 15071 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA); 15072 15073 return NS_OK; 15074 } 15075 15076 mState = State::DirectoryWorkDone; 15077 MOZ_ALWAYS_SUCCEEDS(Run()); 15078 15079 return NS_OK; 15080 } 15081 15082 nsresult FactoryOp::DirectoryWorkDone() { 15083 AssertIsOnOwningThread(); 15084 MOZ_ASSERT(mState == State::DirectoryWorkDone); 15085 MOZ_ASSERT(mDirectoryLockHandle); 15086 MOZ_ASSERT(gFactoryOps); 15087 15088 // See if this FactoryOp needs to wait. 15089 const bool blocked = [&self = *this] { 15090 bool foundThis = false; 15091 bool blocked = false; 15092 15093 // XXX LinkedList should support reverse iteration via rbegin() and rend(), 15094 // see bug 1964967. 15095 for (FactoryOp* existingOp = gFactoryOps->getLast(); existingOp; 15096 existingOp = existingOp->getPrevious()) { 15097 if (existingOp == &self) { 15098 foundThis = true; 15099 continue; 15100 } 15101 15102 if (foundThis && self.MustWaitFor(*existingOp)) { 15103 existingOp->AddBlockingOp(self); 15104 self.AddBlockedOnOp(*existingOp); 15105 blocked = true; 15106 } 15107 } 15108 15109 return blocked; 15110 }() || [&self = *this] { 15111 QuotaClient* quotaClient = QuotaClient::GetInstance(); 15112 MOZ_ASSERT(quotaClient); 15113 15114 if (RefPtr<Maintenance> currentMaintenance = 15115 quotaClient->GetCurrentMaintenance()) { 15116 if (self.mDatabaseName.isSome()) { 15117 if (RefPtr<DatabaseMaintenance> databaseMaintenance = 15118 currentMaintenance->GetDatabaseMaintenance( 15119 self.mDatabaseFilePath.ref())) { 15120 databaseMaintenance->WaitForCompletion(&self); 15121 return true; 15122 } 15123 } else if (currentMaintenance->HasDatabaseMaintenances()) { 15124 currentMaintenance->WaitForCompletion(&self); 15125 return true; 15126 } 15127 } 15128 15129 return false; 15130 }(); 15131 15132 mState = State::DatabaseOpenPending; 15133 if (!blocked) { 15134 QM_TRY(MOZ_TO_RESULT(DatabaseOpen())); 15135 } 15136 15137 return NS_OK; 15138 } 15139 15140 nsresult FactoryOp::SendToIOThread() { 15141 AssertIsOnOwningThread(); 15142 MOZ_ASSERT(mState == State::DatabaseOpenPending); 15143 15144 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 15145 !OperationMayProceed()) { 15146 IDB_REPORT_INTERNAL_ERR(); 15147 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 15148 } 15149 15150 QuotaManager* const quotaManager = QuotaManager::Get(); 15151 MOZ_ASSERT(quotaManager); 15152 15153 // Must set this before dispatching otherwise we will race with the IO thread. 15154 mState = State::DatabaseWorkOpen; 15155 15156 QM_TRY(MOZ_TO_RESULT( 15157 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)), 15158 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA); 15159 15160 NotifyDatabaseWorkStarted(); 15161 15162 return NS_OK; 15163 } 15164 15165 void FactoryOp::WaitForTransactions() { 15166 AssertIsOnOwningThread(); 15167 MOZ_ASSERT(mState == State::BeginVersionChange || 15168 mState == State::WaitingForOtherDatabasesToClose); 15169 MOZ_ASSERT(!mDatabaseId.ref().IsEmpty()); 15170 MOZ_ASSERT(!IsActorDestroyed()); 15171 15172 mState = State::WaitingForTransactionsToComplete; 15173 15174 RefPtr<WaitForTransactionsHelper> helper = 15175 new WaitForTransactionsHelper(mDatabaseId.ref(), this); 15176 helper->WaitForTransactions(); 15177 } 15178 15179 void FactoryOp::CleanupMetadata() { 15180 AssertIsOnOwningThread(); 15181 15182 for (const NotNull<RefPtr<FactoryOp>>& blockingOp : mBlocking) { 15183 blockingOp->MaybeUnblock(*this); 15184 } 15185 mBlocking.Clear(); 15186 15187 MOZ_ASSERT(gFactoryOps); 15188 removeFrom(*gFactoryOps); 15189 15190 // We might get here even after QuotaManagerOpen failed, so we need to check 15191 // if we have a quota manager. 15192 quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep( 15193 quota::Client::IDB, "An element was removed from gFactoryOps"_ns); 15194 15195 // Match the IncreaseBusyCount in AllocPBackgroundIDBFactoryRequestParent(). 15196 DecreaseBusyCount(); 15197 } 15198 15199 void FactoryOp::FinishSendResults() { 15200 AssertIsOnOwningThread(); 15201 MOZ_ASSERT(mState == State::SendingResults); 15202 MOZ_ASSERT(mFactory); 15203 15204 mState = State::Completed; 15205 15206 // Make sure to release the factory on this thread. 15207 mFactory = nullptr; 15208 } 15209 15210 nsresult FactoryOp::SendVersionChangeMessages( 15211 DatabaseActorInfo* aDatabaseActorInfo, Maybe<Database&> aOpeningDatabase, 15212 uint64_t aOldVersion, const Maybe<uint64_t>& aNewVersion) { 15213 AssertIsOnOwningThread(); 15214 MOZ_ASSERT(aDatabaseActorInfo); 15215 MOZ_ASSERT(mState == State::BeginVersionChange); 15216 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); 15217 MOZ_ASSERT(!IsActorDestroyed()); 15218 15219 const uint32_t expectedCount = mDeleting ? 0 : 1; 15220 const uint32_t liveCount = aDatabaseActorInfo->mLiveDatabases.length(); 15221 if (liveCount > expectedCount) { 15222 nsTArray<MaybeBlockedDatabaseInfo> maybeBlockedDatabases; 15223 for (Database* const database : aDatabaseActorInfo->mLiveDatabases) { 15224 if ((!aOpeningDatabase || database != &aOpeningDatabase.ref()) && 15225 !database->IsClosed() && 15226 NS_WARN_IF(!maybeBlockedDatabases.AppendElement( 15227 SafeRefPtr{database, AcquireStrongRefFromRawPtr{}}, fallible))) { 15228 return NS_ERROR_OUT_OF_MEMORY; 15229 } 15230 } 15231 15232 mMaybeBlockedDatabases = std::move(maybeBlockedDatabases); 15233 } 15234 15235 // We don't want to wait forever if we were not able to send the 15236 // message. 15237 mMaybeBlockedDatabases.RemoveLastElements( 15238 mMaybeBlockedDatabases.end() - 15239 std::remove_if(mMaybeBlockedDatabases.begin(), 15240 mMaybeBlockedDatabases.end(), 15241 [aOldVersion, &aNewVersion](auto& maybeBlockedDatabase) { 15242 return !maybeBlockedDatabase->SendVersionChange( 15243 aOldVersion, aNewVersion); 15244 })); 15245 15246 return NS_OK; 15247 } // namespace indexedDB 15248 15249 bool FactoryOp::MustWaitFor(const FactoryOp& aExistingOp) { 15250 AssertIsOnOwningThread(); 15251 15252 // If the persistence types don't overlap, the op can proceed. 15253 if (aExistingOp.mPersistenceType != mPersistenceType) { 15254 return false; 15255 } 15256 15257 // If the origins don't overlap, the op can proceed. 15258 if (aExistingOp.mOriginMetadata.mOrigin != mOriginMetadata.mOrigin) { 15259 return false; 15260 } 15261 15262 // If the database ids don't overlap, the op can proceed. 15263 if (!aExistingOp.mDatabaseId.isNothing() && !mDatabaseId.isNothing() && 15264 aExistingOp.mDatabaseId.ref() != mDatabaseId.ref()) { 15265 return false; 15266 } 15267 15268 return true; 15269 } 15270 15271 // Run() assumes that the caller holds a strong reference to the object that 15272 // can't be cleared while Run() is being executed. 15273 // So if you call Run() directly (as opposed to dispatching to an event queue) 15274 // you need to make sure there's such a reference. 15275 // See bug 1356824 for more details. 15276 NS_IMETHODIMP 15277 FactoryOp::Run() { 15278 const auto handleError = [this](const nsresult rv) { 15279 if (mState != State::SendingResults) { 15280 SetFailureCodeIfUnset(rv); 15281 15282 // Must set mState before dispatching otherwise we will race with the 15283 // owning thread. 15284 mState = State::SendingResults; 15285 15286 if (IsOnOwningThread()) { 15287 SendResults(); 15288 } else { 15289 MOZ_ALWAYS_SUCCEEDS( 15290 DispatchThisAfterProcessingCurrentEvent(mOwningEventTarget)); 15291 } 15292 } 15293 }; 15294 15295 switch (mState) { 15296 case State::Initial: 15297 QM_WARNONLY_TRY(MOZ_TO_RESULT(Open()), handleError); 15298 break; 15299 15300 case State::DirectoryWorkOpen: 15301 QM_WARNONLY_TRY(MOZ_TO_RESULT(DoDirectoryWork()), handleError); 15302 break; 15303 15304 case State::DirectoryWorkDone: 15305 QM_WARNONLY_TRY(MOZ_TO_RESULT(DirectoryWorkDone()), handleError); 15306 break; 15307 15308 case State::DatabaseOpenPending: 15309 QM_WARNONLY_TRY(MOZ_TO_RESULT(DatabaseOpen()), handleError); 15310 break; 15311 15312 case State::DatabaseWorkOpen: 15313 QM_WARNONLY_TRY(MOZ_TO_RESULT(DoDatabaseWork()), handleError); 15314 break; 15315 15316 case State::BeginVersionChange: 15317 QM_WARNONLY_TRY(MOZ_TO_RESULT(BeginVersionChange()), handleError); 15318 break; 15319 15320 case State::WaitingForTransactionsToComplete: 15321 QM_WARNONLY_TRY(MOZ_TO_RESULT(DispatchToWorkThread()), handleError); 15322 break; 15323 15324 case State::DatabaseWorkVersionUpdate: 15325 QM_WARNONLY_TRY(MOZ_TO_RESULT(DoVersionUpdate()), handleError); 15326 break; 15327 15328 case State::SendingResults: 15329 SendResults(); 15330 break; 15331 15332 default: 15333 MOZ_CRASH("Bad state!"); 15334 } 15335 15336 return NS_OK; 15337 } 15338 15339 void FactoryOp::DirectoryLockAcquired(ClientDirectoryLockHandle aLockHandle) { 15340 AssertIsOnOwningThread(); 15341 MOZ_ASSERT(aLockHandle); 15342 MOZ_ASSERT(mState == State::DirectoryOpenPending); 15343 MOZ_ASSERT(!mDirectoryLockHandle); 15344 15345 mDirectoryLockHandle = std::move(aLockHandle); 15346 15347 MOZ_ASSERT(mDirectoryLockHandle->Id() >= 0); 15348 mDirectoryLockId = mDirectoryLockHandle->Id(); 15349 15350 auto cleanupAndReturn = [self = RefPtr(this)](const nsresult rv) { 15351 self->SetFailureCodeIfUnset(rv); 15352 15353 // The caller holds a strong reference to us, no need for a self reference 15354 // before calling Run(). 15355 15356 self->mState = State::SendingResults; 15357 MOZ_ALWAYS_SUCCEEDS(self->Run()); 15358 }; 15359 15360 if (mDirectoryLockHandle->Invalidated()) { 15361 return cleanupAndReturn(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); 15362 } 15363 15364 QM_WARNONLY_TRY(MOZ_TO_RESULT(DirectoryOpen()), cleanupAndReturn); 15365 } 15366 15367 void FactoryOp::DirectoryLockFailed() { 15368 AssertIsOnOwningThread(); 15369 MOZ_ASSERT(mState == State::DirectoryOpenPending); 15370 MOZ_ASSERT(!mDirectoryLockHandle); 15371 15372 if (!HasFailed()) { 15373 IDB_REPORT_INTERNAL_ERR(); 15374 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); 15375 } 15376 15377 // The caller holds a strong reference to us, no need for a self reference 15378 // before calling Run(). 15379 15380 mState = State::SendingResults; 15381 MOZ_ALWAYS_SUCCEEDS(Run()); 15382 } 15383 15384 nsresult FactoryRequestOp::DoDirectoryWork() { 15385 MOZ_CRASH("Not implemented because this should be unreachable."); 15386 } 15387 15388 void FactoryRequestOp::ActorDestroy(ActorDestroyReason aWhy) { 15389 AssertIsOnBackgroundThread(); 15390 15391 NoteActorDestroyed(); 15392 } 15393 15394 OpenDatabaseOp::OpenDatabaseOp(SafeRefPtr<Factory> aFactory, 15395 const Maybe<ContentParentId>& aContentParentId, 15396 const CommonFactoryRequestParams& aParams) 15397 : FactoryRequestOp(std::move(aFactory), aContentParentId, aParams, 15398 /* aDeleting */ false), 15399 mMetadata(MakeSafeRefPtr<FullDatabaseMetadata>(aParams.metadata())), 15400 mRequestedVersion(aParams.metadata().version()), 15401 mVersionChangeOp(nullptr), 15402 mTelemetryId(0) {} 15403 15404 void OpenDatabaseOp::ActorDestroy(ActorDestroyReason aWhy) { 15405 AssertIsOnOwningThread(); 15406 15407 FactoryRequestOp::ActorDestroy(aWhy); 15408 15409 if (mVersionChangeOp) { 15410 mVersionChangeOp->NoteActorDestroyed(); 15411 } 15412 } 15413 15414 nsresult OpenDatabaseOp::DatabaseOpen() { 15415 AssertIsOnOwningThread(); 15416 MOZ_ASSERT(mState == State::DatabaseOpenPending); 15417 15418 nsresult rv = SendToIOThread(); 15419 if (NS_WARN_IF(NS_FAILED(rv))) { 15420 return rv; 15421 } 15422 15423 return NS_OK; 15424 } 15425 15426 nsresult OpenDatabaseOp::DoDatabaseWork() { 15427 AssertIsOnIOThread(); 15428 MOZ_ASSERT(mState == State::DatabaseWorkOpen); 15429 15430 AUTO_PROFILER_LABEL("OpenDatabaseOp::DoDatabaseWork", DOM); 15431 15432 QM_TRY(OkIf(!QuotaClient::IsShuttingDownOnNonBackgroundThread()), 15433 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA); 15434 15435 if (!OperationMayProceed()) { 15436 IDB_REPORT_INTERNAL_ERR(); 15437 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 15438 } 15439 15440 const nsAString& databaseName = mCommonParams.metadata().name(); 15441 const PersistenceType persistenceType = 15442 mCommonParams.metadata().persistenceType(); 15443 15444 QuotaManager* const quotaManager = QuotaManager::Get(); 15445 MOZ_ASSERT(quotaManager); 15446 15447 QM_TRY_INSPECT( 15448 const auto& dbDirectory, 15449 ([persistenceType, "aManager, 15450 this]() -> mozilla::Result<nsCOMPtr<nsIFile>, nsresult> { 15451 if (persistenceType == PERSISTENCE_TYPE_PERSISTENT) { 15452 QM_TRY_RETURN(quotaManager->GetOriginDirectory(mOriginMetadata)); 15453 } 15454 15455 QM_TRY_RETURN( 15456 quotaManager->GetOrCreateTemporaryOriginDirectory(mOriginMetadata)); 15457 }())); 15458 15459 QM_TRY(MOZ_TO_RESULT( 15460 dbDirectory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME)))); 15461 15462 { 15463 QM_TRY_INSPECT(const bool& exists, 15464 MOZ_TO_RESULT_INVOKE_MEMBER(dbDirectory, Exists)); 15465 15466 if (!exists) { 15467 QM_TRY(MOZ_TO_RESULT(dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755))); 15468 } 15469 #ifdef DEBUG 15470 else { 15471 bool isDirectory; 15472 MOZ_ASSERT(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory))); 15473 MOZ_ASSERT(isDirectory); 15474 } 15475 #endif 15476 } 15477 15478 const auto databaseFilenameBase = 15479 GetDatabaseFilenameBase(databaseName, mOriginMetadata.mIsPrivate); 15480 15481 QM_TRY_INSPECT(const auto& markerFile, 15482 CloneFileAndAppend(*dbDirectory, kIdbDeletionMarkerFilePrefix + 15483 databaseFilenameBase)); 15484 15485 QM_TRY_INSPECT(const bool& exists, 15486 MOZ_TO_RESULT_INVOKE_MEMBER(markerFile, Exists)); 15487 15488 if (exists) { 15489 // Delete the database and directroy since they should be deleted in 15490 // previous operation. 15491 // Note: only update usage to the QuotaManager when mEnforcingQuota == true 15492 QM_TRY(MOZ_TO_RESULT(RemoveDatabaseFilesAndDirectory( 15493 *dbDirectory, databaseFilenameBase, 15494 mEnforcingQuota ? quotaManager : nullptr, persistenceType, 15495 mOriginMetadata, databaseName))); 15496 } 15497 15498 QM_TRY_INSPECT( 15499 const auto& dbFile, 15500 CloneFileAndAppend(*dbDirectory, databaseFilenameBase + kSQLiteSuffix)); 15501 15502 mTelemetryId = TelemetryIdForFile(dbFile); 15503 15504 #ifdef DEBUG 15505 { 15506 QM_TRY_INSPECT( 15507 const auto& databaseFilePath, 15508 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); 15509 15510 MOZ_ASSERT(databaseFilePath == mDatabaseFilePath.ref()); 15511 } 15512 #endif 15513 15514 QM_TRY_INSPECT( 15515 const auto& fmDirectory, 15516 CloneFileAndAppend(*dbDirectory, databaseFilenameBase + 15517 kFileManagerDirectoryNameSuffix)); 15518 15519 IndexedDatabaseManager* const idm = IndexedDatabaseManager::Get(); 15520 MOZ_ASSERT(idm); 15521 15522 SafeRefPtr<DatabaseFileManager> fileManager = idm->GetFileManager( 15523 persistenceType, mOriginMetadata.mOrigin, databaseName); 15524 15525 if (!fileManager) { 15526 fileManager = MakeSafeRefPtr<DatabaseFileManager>( 15527 persistenceType, mOriginMetadata, databaseName, mDatabaseId.ref(), 15528 mDatabaseFilePath.ref(), mEnforcingQuota, mInPrivateBrowsing); 15529 } 15530 15531 Maybe<const CipherKey> maybeKey = 15532 mInPrivateBrowsing 15533 ? Some(fileManager->MutableCipherKeyManagerRef().Ensure()) 15534 : Nothing(); 15535 15536 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome()); 15537 15538 QM_TRY_UNWRAP( 15539 NotNull<nsCOMPtr<mozIStorageConnection>> connection, 15540 CreateStorageConnection(*dbFile, *fmDirectory, databaseName, 15541 mOriginMetadata.mOrigin, mDirectoryLockId, 15542 mTelemetryId, maybeKey)); 15543 15544 AutoSetProgressHandler asph; 15545 QM_TRY(MOZ_TO_RESULT(asph.Register(*connection, this))); 15546 15547 QM_TRY(MOZ_TO_RESULT(LoadDatabaseInformation(*connection))); 15548 15549 MOZ_ASSERT(mMetadata->mNextObjectStoreId > mMetadata->mObjectStores.Count()); 15550 MOZ_ASSERT(mMetadata->mNextIndexId > 0); 15551 15552 // See if we need to do a versionchange transaction 15553 15554 // Optional version semantics. 15555 if (!mRequestedVersion) { 15556 // If the requested version was not specified and the database was created, 15557 // treat it as if version 1 were requested. 15558 // Otherwise, treat it as if the current version were requested. 15559 mRequestedVersion = mMetadata->mCommonMetadata.version() == 0 15560 ? 1 15561 : mMetadata->mCommonMetadata.version(); 15562 } 15563 15564 QM_TRY(OkIf(mMetadata->mCommonMetadata.version() <= mRequestedVersion), 15565 NS_ERROR_DOM_INDEXEDDB_VERSION_ERR); 15566 15567 if (!fileManager->Initialized()) { 15568 QM_TRY(MOZ_TO_RESULT(fileManager->Init( 15569 fmDirectory, mMetadata->mCommonMetadata.version(), *connection))); 15570 15571 idm->AddFileManager(fileManager.clonePtr()); 15572 } 15573 15574 mFileManager = std::move(fileManager); 15575 15576 // Must close connection before dispatching otherwise we might race with the 15577 // connection thread which needs to open the same database. 15578 asph.Unregister(); 15579 15580 MOZ_ALWAYS_SUCCEEDS(connection->Close()); 15581 15582 SleepIfEnabled( 15583 StaticPrefs::dom_indexedDB_databaseInitialization_pauseOnIOThreadMs()); 15584 15585 // Must set mState before dispatching otherwise we will race with the owning 15586 // thread. 15587 mState = (mMetadata->mCommonMetadata.version() == mRequestedVersion) 15588 ? State::SendingResults 15589 : State::BeginVersionChange; 15590 15591 QM_TRY(MOZ_TO_RESULT( 15592 DispatchThisAfterProcessingCurrentEvent(mOwningEventTarget))); 15593 15594 return NS_OK; 15595 } 15596 15597 nsresult OpenDatabaseOp::LoadDatabaseInformation( 15598 mozIStorageConnection& aConnection) { 15599 AssertIsOnIOThread(); 15600 MOZ_ASSERT(mMetadata); 15601 15602 { 15603 // Load version information. 15604 QM_TRY_INSPECT( 15605 const auto& stmt, 15606 CreateAndExecuteSingleStepStatement< 15607 SingleStepResult::ReturnNullIfNoResult>( 15608 aConnection, "SELECT name, origin, version FROM database"_ns)); 15609 15610 QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED); 15611 15612 QM_TRY_INSPECT(const auto& databaseName, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 15613 nsString, stmt, GetString, 0)); 15614 15615 QM_TRY(OkIf(mCommonParams.metadata().name() == databaseName), 15616 NS_ERROR_FILE_CORRUPTED); 15617 15618 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 15619 nsCString, stmt, GetUTF8String, 1)); 15620 15621 // We can't just compare these strings directly. See bug 1339081 comment 69. 15622 QM_TRY(OkIf(QuotaManager::AreOriginsEqualOnDisk(mOriginMetadata.mOrigin, 15623 origin)), 15624 NS_ERROR_FILE_CORRUPTED); 15625 15626 QM_TRY_INSPECT(const int64_t& version, 15627 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 2)); 15628 15629 mMetadata->mCommonMetadata.version() = uint64_t(version); 15630 } 15631 15632 ObjectStoreTable& objectStores = mMetadata->mObjectStores; 15633 15634 QM_TRY_INSPECT( 15635 const auto& lastObjectStoreId, 15636 ([&aConnection, 15637 &objectStores]() -> mozilla::Result<IndexOrObjectStoreId, nsresult> { 15638 // Load object store names and ids. 15639 QM_TRY_INSPECT( 15640 const auto& stmt, 15641 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 15642 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement, 15643 "SELECT id, auto_increment, name, key_path " 15644 "FROM object_store"_ns)); 15645 15646 IndexOrObjectStoreId lastObjectStoreId = 0; 15647 15648 QM_TRY(CollectWhileHasResult( 15649 *stmt, 15650 [&lastObjectStoreId, &objectStores, 15651 usedIds = Maybe<nsTHashSet<uint64_t>>{}, 15652 usedNames = Maybe<nsTHashSet<nsString>>{}]( 15653 auto& stmt) mutable -> mozilla::Result<Ok, nsresult> { 15654 QM_TRY_INSPECT(const IndexOrObjectStoreId& objectStoreId, 15655 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0)); 15656 15657 if (!usedIds) { 15658 usedIds.emplace(); 15659 } 15660 15661 QM_TRY(OkIf(objectStoreId > 0), Err(NS_ERROR_FILE_CORRUPTED)); 15662 QM_TRY(OkIf(!usedIds.ref().Contains(objectStoreId)), 15663 Err(NS_ERROR_FILE_CORRUPTED)); 15664 15665 QM_TRY(OkIf(usedIds.ref().Insert(objectStoreId, fallible)), 15666 Err(NS_ERROR_OUT_OF_MEMORY)); 15667 15668 nsString name; 15669 QM_TRY(MOZ_TO_RESULT(stmt.GetString(2, name))); 15670 15671 if (!usedNames) { 15672 usedNames.emplace(); 15673 } 15674 15675 QM_TRY(OkIf(!usedNames.ref().Contains(name)), 15676 Err(NS_ERROR_FILE_CORRUPTED)); 15677 15678 QM_TRY(OkIf(usedNames.ref().Insert(name, fallible)), 15679 Err(NS_ERROR_OUT_OF_MEMORY)); 15680 15681 ObjectStoreMetadata commonMetadata; 15682 commonMetadata.id() = objectStoreId; 15683 commonMetadata.name() = std::move(name); 15684 15685 QM_TRY_INSPECT( 15686 const int32_t& columnType, 15687 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetTypeOfIndex, 3)); 15688 15689 if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) { 15690 commonMetadata.keyPath() = KeyPath(0); 15691 } else { 15692 MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_TEXT); 15693 15694 nsString keyPathSerialization; 15695 QM_TRY(MOZ_TO_RESULT(stmt.GetString(3, keyPathSerialization))); 15696 15697 commonMetadata.keyPath() = 15698 KeyPath::DeserializeFromString(keyPathSerialization); 15699 QM_TRY(OkIf(commonMetadata.keyPath().IsValid()), 15700 Err(NS_ERROR_FILE_CORRUPTED)); 15701 } 15702 15703 QM_TRY_INSPECT(const int64_t& nextAutoIncrementId, 15704 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1)); 15705 15706 commonMetadata.autoIncrement() = !!nextAutoIncrementId; 15707 15708 QM_TRY(OkIf(objectStores.InsertOrUpdate( 15709 objectStoreId, 15710 MakeSafeRefPtr<FullObjectStoreMetadata>( 15711 std::move(commonMetadata), 15712 FullObjectStoreMetadata::AutoIncrementIds{ 15713 nextAutoIncrementId, nextAutoIncrementId}), 15714 fallible)), 15715 Err(NS_ERROR_OUT_OF_MEMORY)); 15716 15717 lastObjectStoreId = std::max(lastObjectStoreId, objectStoreId); 15718 15719 return Ok{}; 15720 })); 15721 15722 return lastObjectStoreId; 15723 }())); 15724 15725 QM_TRY_INSPECT( 15726 const auto& lastIndexId, 15727 ([this, &aConnection, 15728 &objectStores]() -> mozilla::Result<IndexOrObjectStoreId, nsresult> { 15729 // Load index information 15730 QM_TRY_INSPECT( 15731 const auto& stmt, 15732 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 15733 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement, 15734 "SELECT " 15735 "id, object_store_id, name, key_path, " 15736 "unique_index, multientry, " 15737 "locale, is_auto_locale " 15738 "FROM object_store_index"_ns)); 15739 15740 IndexOrObjectStoreId lastIndexId = 0; 15741 15742 QM_TRY(CollectWhileHasResult( 15743 *stmt, 15744 [this, &lastIndexId, &objectStores, &aConnection, 15745 usedIds = Maybe<nsTHashSet<uint64_t>>{}, 15746 usedNames = Maybe<nsTHashSet<nsString>>{}]( 15747 auto& stmt) mutable -> mozilla::Result<Ok, nsresult> { 15748 QM_TRY_INSPECT(const IndexOrObjectStoreId& objectStoreId, 15749 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1)); 15750 15751 // XXX Why does this return NS_ERROR_OUT_OF_MEMORY if we don't 15752 // know the object store id? 15753 15754 auto objectStoreMetadata = objectStores.Lookup(objectStoreId); 15755 QM_TRY(OkIf(static_cast<bool>(objectStoreMetadata)), 15756 Err(NS_ERROR_OUT_OF_MEMORY)); 15757 15758 MOZ_ASSERT((*objectStoreMetadata)->mCommonMetadata.id() == 15759 objectStoreId); 15760 15761 IndexOrObjectStoreId indexId; 15762 QM_TRY(MOZ_TO_RESULT(stmt.GetInt64(0, &indexId))); 15763 15764 if (!usedIds) { 15765 usedIds.emplace(); 15766 } 15767 15768 QM_TRY(OkIf(indexId > 0), Err(NS_ERROR_FILE_CORRUPTED)); 15769 QM_TRY(OkIf(!usedIds.ref().Contains(indexId)), 15770 Err(NS_ERROR_FILE_CORRUPTED)); 15771 15772 QM_TRY(OkIf(usedIds.ref().Insert(indexId, fallible)), 15773 Err(NS_ERROR_OUT_OF_MEMORY)); 15774 15775 nsString name; 15776 QM_TRY(MOZ_TO_RESULT(stmt.GetString(2, name))); 15777 15778 const nsAutoString hashName = 15779 IntToString(indexId) + u":"_ns + name; 15780 15781 if (!usedNames) { 15782 usedNames.emplace(); 15783 } 15784 15785 QM_TRY(OkIf(!usedNames.ref().Contains(hashName)), 15786 Err(NS_ERROR_FILE_CORRUPTED)); 15787 15788 QM_TRY(OkIf(usedNames.ref().Insert(hashName, fallible)), 15789 Err(NS_ERROR_OUT_OF_MEMORY)); 15790 15791 auto indexMetadata = MakeSafeRefPtr<FullIndexMetadata>(); 15792 indexMetadata->mCommonMetadata.id() = indexId; 15793 indexMetadata->mCommonMetadata.name() = name; 15794 15795 #ifdef DEBUG 15796 { 15797 int32_t columnType; 15798 nsresult rv = stmt.GetTypeOfIndex(3, &columnType); 15799 MOZ_ASSERT(NS_SUCCEEDED(rv)); 15800 MOZ_ASSERT(columnType != mozIStorageStatement::VALUE_TYPE_NULL); 15801 } 15802 #endif 15803 15804 nsString keyPathSerialization; 15805 QM_TRY(MOZ_TO_RESULT(stmt.GetString(3, keyPathSerialization))); 15806 15807 indexMetadata->mCommonMetadata.keyPath() = 15808 KeyPath::DeserializeFromString(keyPathSerialization); 15809 QM_TRY(OkIf(indexMetadata->mCommonMetadata.keyPath().IsValid()), 15810 Err(NS_ERROR_FILE_CORRUPTED)); 15811 15812 int32_t scratch; 15813 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(4, &scratch))); 15814 15815 indexMetadata->mCommonMetadata.unique() = !!scratch; 15816 15817 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(5, &scratch))); 15818 15819 indexMetadata->mCommonMetadata.multiEntry() = !!scratch; 15820 15821 const bool localeAware = !stmt.IsNull(6); 15822 if (localeAware) { 15823 QM_TRY(MOZ_TO_RESULT(stmt.GetUTF8String( 15824 6, indexMetadata->mCommonMetadata.locale()))); 15825 15826 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(7, &scratch))); 15827 15828 indexMetadata->mCommonMetadata.autoLocale() = !!scratch; 15829 15830 // Update locale-aware indexes if necessary 15831 const nsCString& indexedLocale = 15832 indexMetadata->mCommonMetadata.locale(); 15833 const bool& isAutoLocale = 15834 indexMetadata->mCommonMetadata.autoLocale(); 15835 const nsCString& systemLocale = mFactory->GetSystemLocale(); 15836 if (!systemLocale.IsEmpty() && isAutoLocale && 15837 !indexedLocale.Equals(systemLocale)) { 15838 QM_TRY(MOZ_TO_RESULT(UpdateLocaleAwareIndex( 15839 aConnection, indexMetadata->mCommonMetadata, 15840 systemLocale))); 15841 } 15842 } 15843 15844 QM_TRY(OkIf((*objectStoreMetadata) 15845 ->mIndexes.InsertOrUpdate( 15846 indexId, std::move(indexMetadata), fallible)), 15847 Err(NS_ERROR_OUT_OF_MEMORY)); 15848 15849 lastIndexId = std::max(lastIndexId, indexId); 15850 15851 return Ok{}; 15852 })); 15853 15854 return lastIndexId; 15855 }())); 15856 15857 QM_TRY(OkIf(lastObjectStoreId != INT64_MAX), 15858 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA); 15859 QM_TRY(OkIf(lastIndexId != INT64_MAX), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, 15860 IDB_REPORT_INTERNAL_ERR_LAMBDA); 15861 15862 mMetadata->mNextObjectStoreId = lastObjectStoreId + 1; 15863 mMetadata->mNextIndexId = lastIndexId + 1; 15864 15865 return NS_OK; 15866 } 15867 15868 /* static */ 15869 nsresult OpenDatabaseOp::UpdateLocaleAwareIndex( 15870 mozIStorageConnection& aConnection, const IndexMetadata& aIndexMetadata, 15871 const nsCString& aLocale) { 15872 const auto indexTable = 15873 aIndexMetadata.unique() ? "unique_index_data"_ns : "index_data"_ns; 15874 15875 // The parameter names are not used, parameters are bound by index only 15876 // locally in the same function. 15877 const nsCString readQuery = "SELECT value, object_data_key FROM "_ns + 15878 indexTable + " WHERE index_id = :index_id"_ns; 15879 15880 QM_TRY_INSPECT(const auto& readStmt, 15881 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 15882 nsCOMPtr<mozIStorageStatement>, aConnection, 15883 CreateStatement, readQuery)); 15884 15885 QM_TRY(MOZ_TO_RESULT(readStmt->BindInt64ByIndex(0, aIndexMetadata.id()))); 15886 15887 QM_TRY(CollectWhileHasResult( 15888 *readStmt, 15889 [&aConnection, &indexTable, &aIndexMetadata, &aLocale, 15890 writeStmt = nsCOMPtr<mozIStorageStatement>{}]( 15891 auto& readStmt) mutable -> mozilla::Result<Ok, nsresult> { 15892 if (!writeStmt) { 15893 QM_TRY_UNWRAP( 15894 writeStmt, 15895 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 15896 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement, 15897 "UPDATE "_ns + indexTable + "SET value_locale = :"_ns + 15898 kStmtParamNameValueLocale + " WHERE index_id = :"_ns + 15899 kStmtParamNameIndexId + " AND value = :"_ns + 15900 kStmtParamNameValue + " AND object_data_key = :"_ns + 15901 kStmtParamNameObjectDataKey)); 15902 } 15903 15904 mozStorageStatementScoper scoper(writeStmt); 15905 QM_TRY(MOZ_TO_RESULT(writeStmt->BindInt64ByName(kStmtParamNameIndexId, 15906 aIndexMetadata.id()))); 15907 15908 Key oldKey, objectStorePosition; 15909 QM_TRY(MOZ_TO_RESULT(oldKey.SetFromStatement(&readStmt, 0))); 15910 QM_TRY(MOZ_TO_RESULT( 15911 oldKey.BindToStatement(writeStmt, kStmtParamNameValue))); 15912 15913 QM_TRY_INSPECT(const auto& newSortKey, 15914 oldKey.ToLocaleAwareKey(aLocale)); 15915 15916 QM_TRY(MOZ_TO_RESULT( 15917 newSortKey.BindToStatement(writeStmt, kStmtParamNameValueLocale))); 15918 QM_TRY( 15919 MOZ_TO_RESULT(objectStorePosition.SetFromStatement(&readStmt, 1))); 15920 QM_TRY(MOZ_TO_RESULT(objectStorePosition.BindToStatement( 15921 writeStmt, kStmtParamNameObjectDataKey))); 15922 15923 QM_TRY(MOZ_TO_RESULT(writeStmt->Execute())); 15924 15925 return Ok{}; 15926 })); 15927 15928 // The parameter names are not used, parameters are bound by index only 15929 // locally in the same function. 15930 static constexpr auto metaQuery = 15931 "UPDATE object_store_index SET " 15932 "locale = :locale WHERE id = :id"_ns; 15933 15934 QM_TRY_INSPECT(const auto& metaStmt, 15935 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 15936 nsCOMPtr<mozIStorageStatement>, aConnection, 15937 CreateStatement, metaQuery)); 15938 15939 QM_TRY(MOZ_TO_RESULT( 15940 metaStmt->BindStringByIndex(0, NS_ConvertASCIItoUTF16(aLocale)))); 15941 15942 QM_TRY(MOZ_TO_RESULT(metaStmt->BindInt64ByIndex(1, aIndexMetadata.id()))); 15943 15944 QM_TRY(MOZ_TO_RESULT(metaStmt->Execute())); 15945 15946 return NS_OK; 15947 } 15948 15949 nsresult OpenDatabaseOp::BeginVersionChange() { 15950 AssertIsOnOwningThread(); 15951 MOZ_ASSERT(mState == State::BeginVersionChange); 15952 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); 15953 MOZ_ASSERT(mMetadata->mCommonMetadata.version() <= mRequestedVersion); 15954 MOZ_ASSERT(!mDatabase); 15955 MOZ_ASSERT(!mVersionChangeTransaction); 15956 15957 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 15958 IsActorDestroyed()) { 15959 IDB_REPORT_INTERNAL_ERR(); 15960 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR)); 15961 } 15962 15963 EnsureDatabaseActor(); 15964 15965 if (mDatabase->IsInvalidated()) { 15966 IDB_REPORT_INTERNAL_ERR(); 15967 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR)); 15968 } 15969 15970 MOZ_ASSERT(!mDatabase->IsClosed()); 15971 15972 DatabaseActorInfo* info; 15973 MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)); 15974 15975 MOZ_ASSERT(info->mLiveDatabases.contains(mDatabase.unsafeGetRawPtr())); 15976 MOZ_ASSERT(!info->mWaitingFactoryOp); 15977 MOZ_ASSERT(info->mMetadata == mMetadata); 15978 15979 auto transaction = MakeSafeRefPtr<VersionChangeTransaction>(this); 15980 15981 if (NS_WARN_IF(!transaction->CopyDatabaseMetadata())) { 15982 return NS_ERROR_OUT_OF_MEMORY; 15983 } 15984 15985 MOZ_ASSERT(info->mMetadata != mMetadata); 15986 mMetadata = info->mMetadata.clonePtr(); 15987 15988 const Maybe<uint64_t> newVersion = Some(mRequestedVersion); 15989 15990 QM_TRY(MOZ_TO_RESULT(SendVersionChangeMessages( 15991 info, mDatabase.maybeDeref(), mMetadata->mCommonMetadata.version(), 15992 newVersion))); 15993 15994 mVersionChangeTransaction = std::move(transaction); 15995 15996 if (mMaybeBlockedDatabases.IsEmpty()) { 15997 // We don't need to wait on any databases, just jump to the transaction 15998 // pool. 15999 WaitForTransactions(); 16000 return NS_OK; 16001 } 16002 16003 // If the actor gets destroyed, mWaitingFactoryOp will hold the last strong 16004 // reference to us. 16005 info->mWaitingFactoryOp = this; 16006 16007 mState = State::WaitingForOtherDatabasesToClose; 16008 return NS_OK; 16009 } 16010 16011 bool OpenDatabaseOp::AreActorsAlive() { 16012 AssertIsOnOwningThread(); 16013 MOZ_ASSERT(mDatabase); 16014 16015 return !(IsActorDestroyed() || mDatabase->IsActorDestroyed()); 16016 } 16017 16018 void OpenDatabaseOp::SendBlockedNotification() { 16019 AssertIsOnOwningThread(); 16020 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose); 16021 16022 if (!IsActorDestroyed()) { 16023 (void)SendBlocked(mMetadata->mCommonMetadata.version()); 16024 } 16025 } 16026 16027 nsresult OpenDatabaseOp::DispatchToWorkThread() { 16028 AssertIsOnOwningThread(); 16029 MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete); 16030 MOZ_ASSERT(mVersionChangeTransaction); 16031 MOZ_ASSERT(mVersionChangeTransaction->GetMode() == 16032 IDBTransaction::Mode::VersionChange); 16033 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); 16034 16035 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 16036 IsActorDestroyed() || mDatabase->IsInvalidated()) { 16037 IDB_REPORT_INTERNAL_ERR(); 16038 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16039 } 16040 16041 mState = State::DatabaseWorkVersionChange; 16042 16043 // Intentionally empty. 16044 nsTArray<nsString> objectStoreNames; 16045 16046 const int64_t loggingSerialNumber = 16047 mVersionChangeTransaction->LoggingSerialNumber(); 16048 const nsID& backgroundChildLoggingId = 16049 mVersionChangeTransaction->GetLoggingInfo()->Id(); 16050 16051 if (NS_WARN_IF(!mDatabase->RegisterTransaction(*mVersionChangeTransaction))) { 16052 return NS_ERROR_OUT_OF_MEMORY; 16053 } 16054 16055 if (!gConnectionPool) { 16056 gConnectionPool = new ConnectionPool(); 16057 } 16058 16059 RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this); 16060 16061 uint64_t transactionId = versionChangeOp->StartOnConnectionPool( 16062 backgroundChildLoggingId, mVersionChangeTransaction->DatabaseId(), 16063 loggingSerialNumber, objectStoreNames, 16064 /* aIsWriteTransaction */ true); 16065 16066 mVersionChangeOp = versionChangeOp; 16067 16068 mVersionChangeTransaction->NoteActiveRequest(); 16069 mVersionChangeTransaction->Init(transactionId); 16070 16071 return NS_OK; 16072 } 16073 16074 nsresult OpenDatabaseOp::SendUpgradeNeeded() { 16075 AssertIsOnOwningThread(); 16076 MOZ_ASSERT(mState == State::DatabaseWorkVersionChange); 16077 MOZ_ASSERT(mVersionChangeTransaction); 16078 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); 16079 MOZ_ASSERT(!HasFailed()); 16080 MOZ_ASSERT_IF(!IsActorDestroyed(), mDatabase); 16081 16082 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 16083 IsActorDestroyed()) { 16084 IDB_REPORT_INTERNAL_ERR(); 16085 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16086 } 16087 16088 const SafeRefPtr<VersionChangeTransaction> transaction = 16089 std::move(mVersionChangeTransaction); 16090 16091 nsresult rv = EnsureDatabaseActorIsAlive(); 16092 if (NS_WARN_IF(NS_FAILED(rv))) { 16093 return rv; 16094 } 16095 16096 // Transfer ownership to IPDL. 16097 transaction->SetActorAlive(); 16098 16099 if (!mDatabase->SendPBackgroundIDBVersionChangeTransactionConstructor( 16100 transaction.unsafeGetRawPtr(), mMetadata->mCommonMetadata.version(), 16101 mRequestedVersion, mMetadata->mNextObjectStoreId, 16102 mMetadata->mNextIndexId)) { 16103 IDB_REPORT_INTERNAL_ERR(); 16104 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16105 } 16106 16107 return NS_OK; 16108 } 16109 16110 nsresult OpenDatabaseOp::DoVersionUpdate() { 16111 AssertIsOnIOThread(); 16112 MOZ_ASSERT(mState == State::DatabaseWorkVersionUpdate); 16113 MOZ_ASSERT(!HasFailed()); 16114 16115 AUTO_PROFILER_LABEL("OpenDatabaseOp::DoVersionUpdate", DOM); 16116 16117 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 16118 !OperationMayProceed()) { 16119 IDB_REPORT_INTERNAL_ERR(); 16120 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16121 } 16122 16123 mFileManager->UpdateDatabaseVersion(mRequestedVersion); 16124 16125 mState = State::SendingResults; 16126 16127 QM_TRY(MOZ_TO_RESULT( 16128 DispatchThisAfterProcessingCurrentEvent(mOwningEventTarget))); 16129 16130 return NS_OK; 16131 } 16132 16133 void OpenDatabaseOp::SendResults() { 16134 AssertIsOnOwningThread(); 16135 MOZ_ASSERT(mState == State::SendingResults); 16136 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); 16137 MOZ_ASSERT_IF(!HasFailed(), !mVersionChangeTransaction); 16138 16139 if (mCompleteCallback) { 16140 auto completeCallback = std::move(mCompleteCallback); 16141 completeCallback(); 16142 } 16143 16144 DebugOnly<DatabaseActorInfo*> info = nullptr; 16145 MOZ_ASSERT_IF(mDatabaseId.isSome() && gLiveDatabaseHashtable && 16146 gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info), 16147 !info->mWaitingFactoryOp); 16148 16149 if (mVersionChangeTransaction) { 16150 MOZ_ASSERT(HasFailed()); 16151 16152 mVersionChangeTransaction->Abort(ResultCode(), /* aForce */ true); 16153 mVersionChangeTransaction = nullptr; 16154 } 16155 16156 if (IsActorDestroyed()) { 16157 SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); 16158 } else { 16159 FactoryRequestResponse response; 16160 16161 if (!HasFailed()) { 16162 // If we just successfully completed a versionchange operation then we 16163 // need to update the version in our metadata. 16164 mMetadata->mCommonMetadata.version() = mRequestedVersion; 16165 16166 nsresult rv = EnsureDatabaseActorIsAlive(); 16167 if (NS_SUCCEEDED(rv)) { 16168 // We successfully opened a database so use its actor as the success 16169 // result for this request. 16170 16171 // XXX OpenDatabaseRequestResponse stores a raw pointer, can this be 16172 // avoided? 16173 response = OpenDatabaseRequestResponse{ 16174 WrapNotNull(mDatabase.unsafeGetRawPtr())}; 16175 } else { 16176 response = ClampResultCode(rv); 16177 #ifdef DEBUG 16178 SetFailureCode(response.get_nsresult()); 16179 #endif 16180 } 16181 } else { 16182 #ifdef DEBUG 16183 // If something failed then our metadata pointer is now bad. No one should 16184 // ever touch it again though so just null it out in DEBUG builds to make 16185 // sure we find such cases. 16186 mMetadata = nullptr; 16187 #endif 16188 response = ClampResultCode(ResultCode()); 16189 } 16190 16191 (void)PBackgroundIDBFactoryRequestParent::Send__delete__(this, response); 16192 } 16193 16194 if (mDatabase) { 16195 MOZ_ASSERT(!mDirectoryLockHandle); 16196 16197 if (HasFailed()) { 16198 mDatabase->Invalidate(); 16199 } 16200 16201 // Make sure to release the database on this thread. 16202 mDatabase = nullptr; 16203 16204 CleanupMetadata(); 16205 } else if (mDirectoryLockHandle) { 16206 // ConnectionClosedCallback will call CleanupMetadata(). 16207 nsCOMPtr<nsIRunnable> callback = NewRunnableMethod( 16208 "dom::indexedDB::OpenDatabaseOp::ConnectionClosedCallback", this, 16209 &OpenDatabaseOp::ConnectionClosedCallback); 16210 16211 RefPtr<WaitForTransactionsHelper> helper = 16212 new WaitForTransactionsHelper(mDatabaseId.ref(), callback); 16213 helper->WaitForTransactions(); 16214 } else { 16215 CleanupMetadata(); 16216 } 16217 16218 FinishSendResults(); 16219 } 16220 16221 void OpenDatabaseOp::ConnectionClosedCallback() { 16222 AssertIsOnOwningThread(); 16223 MOZ_ASSERT(HasFailed()); 16224 MOZ_ASSERT(mDirectoryLockHandle); 16225 16226 { 16227 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 16228 } 16229 16230 CleanupMetadata(); 16231 } 16232 16233 void OpenDatabaseOp::EnsureDatabaseActor() { 16234 AssertIsOnOwningThread(); 16235 MOZ_ASSERT(mState == State::BeginVersionChange || 16236 mState == State::DatabaseWorkVersionChange || 16237 mState == State::SendingResults); 16238 MOZ_ASSERT(!HasFailed()); 16239 MOZ_ASSERT(mDatabaseFilePath.isSome()); 16240 MOZ_ASSERT(!IsActorDestroyed()); 16241 16242 if (mDatabase) { 16243 return; 16244 } 16245 16246 MOZ_ASSERT(mMetadata->mDatabaseId.IsEmpty()); 16247 mMetadata->mDatabaseId = mDatabaseId.ref(); 16248 16249 MOZ_ASSERT(mMetadata->mFilePath.IsEmpty()); 16250 mMetadata->mFilePath = mDatabaseFilePath.ref(); 16251 16252 DatabaseActorInfo* info; 16253 if (gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)) { 16254 AssertMetadataConsistency(*info->mMetadata); 16255 mMetadata = info->mMetadata.clonePtr(); 16256 } 16257 16258 Maybe<const CipherKey> maybeKey = 16259 mInPrivateBrowsing ? mFileManager->MutableCipherKeyManagerRef().Get() 16260 : Nothing(); 16261 16262 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome()); 16263 16264 const bool directoryLockInvalidated = mDirectoryLockHandle->Invalidated(); 16265 16266 // XXX Shouldn't Manager() return already_AddRefed when 16267 // PBackgroundIDBFactoryParent is declared refcounted? 16268 mDatabase = MakeSafeRefPtr<Database>( 16269 SafeRefPtr{static_cast<Factory*>(Manager()), 16270 AcquireStrongRefFromRawPtr{}}, 16271 mCommonParams.principalInfo(), mContentParentId, mOriginMetadata, 16272 mTelemetryId, mMetadata.clonePtr(), mFileManager.clonePtr(), 16273 std::move(mDirectoryLockHandle), mInPrivateBrowsing, maybeKey); 16274 16275 if (info) { 16276 info->mLiveDatabases.insertBack(mDatabase.unsafeGetRawPtr()); 16277 } else { 16278 // XXX Maybe use LookupOrInsertWith above, to avoid a second lookup here? 16279 info = gLiveDatabaseHashtable 16280 ->InsertOrUpdate( 16281 mDatabaseId.ref(), 16282 MakeUnique<DatabaseActorInfo>( 16283 mMetadata.clonePtr(), 16284 WrapNotNullUnchecked(mDatabase.unsafeGetRawPtr()))) 16285 .get(); 16286 } 16287 16288 if (directoryLockInvalidated) { 16289 mDatabase->Invalidate(); 16290 } 16291 16292 // Balanced in Database::CleanupMetadata(). 16293 IncreaseBusyCount(); 16294 } 16295 16296 nsresult OpenDatabaseOp::EnsureDatabaseActorIsAlive() { 16297 AssertIsOnOwningThread(); 16298 MOZ_ASSERT(mState == State::DatabaseWorkVersionChange || 16299 mState == State::SendingResults); 16300 MOZ_ASSERT(!HasFailed()); 16301 MOZ_ASSERT(!IsActorDestroyed()); 16302 16303 EnsureDatabaseActor(); 16304 16305 if (mDatabase->IsActorAlive()) { 16306 return NS_OK; 16307 } 16308 16309 auto* const factory = static_cast<Factory*>(Manager()); 16310 16311 QM_TRY_INSPECT(const auto& spec, MetadataToSpec()); 16312 16313 mDatabase->SetActorAlive(); 16314 16315 if (!factory->SendPBackgroundIDBDatabaseConstructor( 16316 mDatabase.unsafeGetRawPtr(), spec, WrapNotNull(this))) { 16317 IDB_REPORT_INTERNAL_ERR(); 16318 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16319 } 16320 16321 if (mDatabase->IsInvalidated()) { 16322 (void)mDatabase->SendInvalidate(); 16323 } 16324 16325 return NS_OK; 16326 } 16327 16328 Result<DatabaseSpec, nsresult> OpenDatabaseOp::MetadataToSpec() const { 16329 AssertIsOnOwningThread(); 16330 MOZ_ASSERT(mMetadata); 16331 16332 DatabaseSpec spec; 16333 spec.metadata() = mMetadata->mCommonMetadata; 16334 16335 QM_TRY_UNWRAP(spec.objectStores(), 16336 TransformIntoNewArrayAbortOnErr( 16337 mMetadata->mObjectStores, 16338 [](const auto& objectStoreEntry) 16339 -> mozilla::Result<ObjectStoreSpec, nsresult> { 16340 FullObjectStoreMetadata* metadata = 16341 objectStoreEntry.GetWeak(); 16342 MOZ_ASSERT(objectStoreEntry.GetKey()); 16343 MOZ_ASSERT(metadata); 16344 16345 ObjectStoreSpec objectStoreSpec; 16346 objectStoreSpec.metadata() = metadata->mCommonMetadata; 16347 16348 QM_TRY_UNWRAP(auto indexes, 16349 TransformIntoNewArray( 16350 metadata->mIndexes, 16351 [](const auto& indexEntry) { 16352 FullIndexMetadata* indexMetadata = 16353 indexEntry.GetWeak(); 16354 MOZ_ASSERT(indexEntry.GetKey()); 16355 MOZ_ASSERT(indexMetadata); 16356 16357 return indexMetadata->mCommonMetadata; 16358 }, 16359 fallible)); 16360 16361 objectStoreSpec.indexes() = std::move(indexes); 16362 16363 return objectStoreSpec; 16364 }, 16365 fallible)); 16366 16367 return spec; 16368 } 16369 16370 #ifdef DEBUG 16371 16372 void OpenDatabaseOp::AssertMetadataConsistency( 16373 const FullDatabaseMetadata& aMetadata) { 16374 AssertIsOnBackgroundThread(); 16375 16376 const FullDatabaseMetadata& thisDB = *mMetadata; 16377 const FullDatabaseMetadata& otherDB = aMetadata; 16378 16379 MOZ_ASSERT(&thisDB != &otherDB); 16380 16381 MOZ_ASSERT(thisDB.mCommonMetadata.name() == otherDB.mCommonMetadata.name()); 16382 MOZ_ASSERT(thisDB.mCommonMetadata.version() == 16383 otherDB.mCommonMetadata.version()); 16384 MOZ_ASSERT(thisDB.mCommonMetadata.persistenceType() == 16385 otherDB.mCommonMetadata.persistenceType()); 16386 MOZ_ASSERT(thisDB.mDatabaseId == otherDB.mDatabaseId); 16387 MOZ_ASSERT(thisDB.mFilePath == otherDB.mFilePath); 16388 16389 // |thisDB| reflects the latest objectStore and index ids that have committed 16390 // to disk. The in-memory metadata |otherDB| keeps track of objectStores and 16391 // indexes that were created and then removed as well, so the next ids for 16392 // |otherDB| may be higher than for |thisDB|. 16393 MOZ_ASSERT(thisDB.mNextObjectStoreId <= otherDB.mNextObjectStoreId); 16394 MOZ_ASSERT(thisDB.mNextIndexId <= otherDB.mNextIndexId); 16395 16396 MOZ_ASSERT(thisDB.mObjectStores.Count() == otherDB.mObjectStores.Count()); 16397 16398 for (const auto& thisObjectStore : thisDB.mObjectStores.Values()) { 16399 MOZ_ASSERT(thisObjectStore); 16400 MOZ_ASSERT(!thisObjectStore->mDeleted); 16401 16402 auto otherObjectStore = MatchMetadataNameOrId( 16403 otherDB.mObjectStores, thisObjectStore->mCommonMetadata.id()); 16404 MOZ_ASSERT(otherObjectStore); 16405 16406 MOZ_ASSERT(thisObjectStore != &otherObjectStore.ref()); 16407 16408 MOZ_ASSERT(thisObjectStore->mCommonMetadata.id() == 16409 otherObjectStore->mCommonMetadata.id()); 16410 MOZ_ASSERT(thisObjectStore->mCommonMetadata.name() == 16411 otherObjectStore->mCommonMetadata.name()); 16412 MOZ_ASSERT(thisObjectStore->mCommonMetadata.autoIncrement() == 16413 otherObjectStore->mCommonMetadata.autoIncrement()); 16414 MOZ_ASSERT(thisObjectStore->mCommonMetadata.keyPath() == 16415 otherObjectStore->mCommonMetadata.keyPath()); 16416 // mNextAutoIncrementId and mCommittedAutoIncrementId may be modified 16417 // concurrently with this OpenOp, so it is not possible to assert equality 16418 // here. It's also possible that we've written the new ids to disk but not 16419 // yet updated the in-memory count. 16420 // TODO The first part of the comment should probably be rephrased. I think 16421 // it still applies but it sounds as if this were thread-unsafe like it was 16422 // before, which isn't true anymore. 16423 { 16424 const auto&& thisAutoIncrementIds = 16425 thisObjectStore->mAutoIncrementIds.Lock(); 16426 const auto&& otherAutoIncrementIds = 16427 otherObjectStore->mAutoIncrementIds.Lock(); 16428 16429 MOZ_ASSERT(thisAutoIncrementIds->next <= otherAutoIncrementIds->next); 16430 MOZ_ASSERT( 16431 thisAutoIncrementIds->committed <= otherAutoIncrementIds->committed || 16432 thisAutoIncrementIds->committed == otherAutoIncrementIds->next); 16433 } 16434 MOZ_ASSERT(!otherObjectStore->mDeleted); 16435 16436 MOZ_ASSERT(thisObjectStore->mIndexes.Count() == 16437 otherObjectStore->mIndexes.Count()); 16438 16439 for (const auto& thisIndex : thisObjectStore->mIndexes.Values()) { 16440 MOZ_ASSERT(thisIndex); 16441 MOZ_ASSERT(!thisIndex->mDeleted); 16442 16443 auto otherIndex = MatchMetadataNameOrId(otherObjectStore->mIndexes, 16444 thisIndex->mCommonMetadata.id()); 16445 MOZ_ASSERT(otherIndex); 16446 16447 MOZ_ASSERT(thisIndex != &otherIndex.ref()); 16448 16449 MOZ_ASSERT(thisIndex->mCommonMetadata.id() == 16450 otherIndex->mCommonMetadata.id()); 16451 MOZ_ASSERT(thisIndex->mCommonMetadata.name() == 16452 otherIndex->mCommonMetadata.name()); 16453 MOZ_ASSERT(thisIndex->mCommonMetadata.keyPath() == 16454 otherIndex->mCommonMetadata.keyPath()); 16455 MOZ_ASSERT(thisIndex->mCommonMetadata.unique() == 16456 otherIndex->mCommonMetadata.unique()); 16457 MOZ_ASSERT(thisIndex->mCommonMetadata.multiEntry() == 16458 otherIndex->mCommonMetadata.multiEntry()); 16459 MOZ_ASSERT(!otherIndex->mDeleted); 16460 } 16461 } 16462 } 16463 16464 #endif // DEBUG 16465 16466 nsresult OpenDatabaseOp::VersionChangeOp::DoDatabaseWork( 16467 DatabaseConnection* aConnection) { 16468 MOZ_ASSERT(aConnection); 16469 aConnection->AssertIsOnConnectionThread(); 16470 MOZ_ASSERT(mOpenDatabaseOp); 16471 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange); 16472 16473 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 16474 !OperationMayProceed()) { 16475 IDB_REPORT_INTERNAL_ERR(); 16476 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16477 } 16478 16479 AUTO_PROFILER_LABEL("OpenDatabaseOp::VersionChangeOp::DoDatabaseWork", DOM); 16480 16481 IDB_LOG_MARK_PARENT_TRANSACTION("Beginning database work", "DB Start", 16482 IDB_LOG_ID_STRING(mBackgroundChildLoggingId), 16483 mTransactionLoggingSerialNumber); 16484 16485 Transaction().SetActiveOnConnectionThread(); 16486 16487 QM_TRY(MOZ_TO_RESULT( 16488 aConnection->BeginWriteTransaction(Transaction().GetDurability()))); 16489 16490 // The parameter names are not used, parameters are bound by index only 16491 // locally in the same function. 16492 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 16493 "UPDATE database SET version = :version;"_ns, 16494 ([&self = *this]( 16495 mozIStorageStatement& updateStmt) -> mozilla::Result<Ok, nsresult> { 16496 QM_TRY(MOZ_TO_RESULT( 16497 updateStmt.BindInt64ByIndex(0, int64_t(self.mRequestedVersion)))); 16498 16499 return Ok{}; 16500 })))); 16501 16502 return NS_OK; 16503 } 16504 16505 nsresult OpenDatabaseOp::VersionChangeOp::SendSuccessResult() { 16506 AssertIsOnOwningThread(); 16507 MOZ_ASSERT(mOpenDatabaseOp); 16508 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange); 16509 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this); 16510 16511 nsresult rv = mOpenDatabaseOp->SendUpgradeNeeded(); 16512 if (NS_WARN_IF(NS_FAILED(rv))) { 16513 return rv; 16514 } 16515 16516 return NS_OK; 16517 } 16518 16519 bool OpenDatabaseOp::VersionChangeOp::SendFailureResult(nsresult aResultCode) { 16520 AssertIsOnOwningThread(); 16521 MOZ_ASSERT(mOpenDatabaseOp); 16522 MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange); 16523 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this); 16524 16525 mOpenDatabaseOp->SetFailureCode(aResultCode); 16526 mOpenDatabaseOp->mState = State::SendingResults; 16527 16528 MOZ_ALWAYS_SUCCEEDS(mOpenDatabaseOp->Run()); 16529 16530 return false; 16531 } 16532 16533 void OpenDatabaseOp::VersionChangeOp::Cleanup() { 16534 AssertIsOnOwningThread(); 16535 MOZ_ASSERT(mOpenDatabaseOp); 16536 MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this); 16537 16538 mOpenDatabaseOp->mVersionChangeOp = nullptr; 16539 mOpenDatabaseOp = nullptr; 16540 16541 #ifdef DEBUG 16542 // A bit hacky but the VersionChangeOp is not generated in response to a 16543 // child request like most other database operations. Do this to make our 16544 // assertions happy. 16545 // 16546 // XXX: Depending on timing, in most cases, NoteActorDestroyed will not have 16547 // been destroyed before, but in some cases it has. This should be reworked in 16548 // a way this hack is not necessary. There are also several similar cases in 16549 // other *Op classes. 16550 if (!IsActorDestroyed()) { 16551 NoteActorDestroyed(); 16552 } 16553 #endif 16554 16555 TransactionDatabaseOperationBase::Cleanup(); 16556 } 16557 16558 void DeleteDatabaseOp::LoadPreviousVersion(nsIFile& aDatabaseFile) { 16559 AssertIsOnIOThread(); 16560 MOZ_ASSERT(mState == State::DatabaseWorkOpen); 16561 MOZ_ASSERT(!mPreviousVersion); 16562 16563 AUTO_PROFILER_LABEL("DeleteDatabaseOp::LoadPreviousVersion", DOM); 16564 16565 nsresult rv; 16566 16567 nsCOMPtr<mozIStorageService> ss = 16568 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); 16569 if (NS_WARN_IF(NS_FAILED(rv))) { 16570 return; 16571 } 16572 16573 IndexedDatabaseManager* const idm = IndexedDatabaseManager::Get(); 16574 MOZ_ASSERT(idm); 16575 16576 const PersistenceType persistenceType = 16577 mCommonParams.metadata().persistenceType(); 16578 const nsAString& databaseName = mCommonParams.metadata().name(); 16579 16580 SafeRefPtr<DatabaseFileManager> fileManager = idm->GetFileManager( 16581 persistenceType, mOriginMetadata.mOrigin, databaseName); 16582 16583 if (!fileManager) { 16584 fileManager = MakeSafeRefPtr<DatabaseFileManager>( 16585 persistenceType, mOriginMetadata, databaseName, mDatabaseId.ref(), 16586 mDatabaseFilePath.ref(), mEnforcingQuota, mInPrivateBrowsing); 16587 } 16588 16589 const auto maybeKey = 16590 mInPrivateBrowsing 16591 ? Some(fileManager->MutableCipherKeyManagerRef().Ensure()) 16592 : Nothing(); 16593 16594 MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome()); 16595 16596 // Pass -1 as the directoryLockId to disable quota checking, since we might 16597 // temporarily exceed quota before deleting the database. 16598 QM_TRY_INSPECT(const auto& dbFileUrl, 16599 GetDatabaseFileURL(aDatabaseFile, -1, maybeKey), QM_VOID); 16600 16601 QM_TRY_UNWRAP(const NotNull<nsCOMPtr<mozIStorageConnection>> connection, 16602 OpenDatabaseAndHandleBusy(*ss, *dbFileUrl), QM_VOID); 16603 16604 #ifdef DEBUG 16605 { 16606 QM_TRY_INSPECT(const auto& stmt, 16607 CreateAndExecuteSingleStepStatement< 16608 SingleStepResult::ReturnNullIfNoResult>( 16609 *connection, "SELECT name FROM database"_ns), 16610 QM_VOID); 16611 16612 QM_TRY(OkIf(stmt), QM_VOID); 16613 16614 nsString databaseName; 16615 rv = stmt->GetString(0, databaseName); 16616 if (NS_WARN_IF(NS_FAILED(rv))) { 16617 return; 16618 } 16619 16620 MOZ_ASSERT(mCommonParams.metadata().name() == databaseName); 16621 } 16622 #endif 16623 16624 QM_TRY_INSPECT(const auto& stmt, 16625 CreateAndExecuteSingleStepStatement< 16626 SingleStepResult::ReturnNullIfNoResult>( 16627 *connection, "SELECT version FROM database"_ns), 16628 QM_VOID); 16629 16630 QM_TRY(OkIf(stmt), QM_VOID); 16631 16632 int64_t version; 16633 rv = stmt->GetInt64(0, &version); 16634 if (NS_WARN_IF(NS_FAILED(rv))) { 16635 return; 16636 } 16637 16638 mPreviousVersion = uint64_t(version); 16639 } 16640 16641 nsresult DeleteDatabaseOp::DatabaseOpen() { 16642 AssertIsOnOwningThread(); 16643 MOZ_ASSERT(mState == State::DatabaseOpenPending); 16644 16645 nsresult rv = SendToIOThread(); 16646 if (NS_WARN_IF(NS_FAILED(rv))) { 16647 return rv; 16648 } 16649 16650 return NS_OK; 16651 } 16652 16653 nsresult DeleteDatabaseOp::DoDatabaseWork() { 16654 AssertIsOnIOThread(); 16655 MOZ_ASSERT(mState == State::DatabaseWorkOpen); 16656 MOZ_ASSERT(mOriginMetadata.mPersistenceType == 16657 mCommonParams.metadata().persistenceType()); 16658 16659 AUTO_PROFILER_LABEL("DeleteDatabaseOp::DoDatabaseWork", DOM); 16660 16661 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 16662 !OperationMayProceed()) { 16663 IDB_REPORT_INTERNAL_ERR(); 16664 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16665 } 16666 16667 const nsAString& databaseName = mCommonParams.metadata().name(); 16668 16669 QuotaManager* const quotaManager = QuotaManager::Get(); 16670 MOZ_ASSERT(quotaManager); 16671 16672 QM_TRY_UNWRAP(auto directory, 16673 quotaManager->GetOriginDirectory(mOriginMetadata)); 16674 16675 QM_TRY(MOZ_TO_RESULT( 16676 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(IDB_DIRECTORY_NAME)))); 16677 16678 QM_TRY_UNWRAP(mDatabaseDirectoryPath, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 16679 nsString, directory, GetPath)); 16680 16681 mDatabaseFilenameBase = 16682 GetDatabaseFilenameBase(databaseName, mOriginMetadata.mIsPrivate); 16683 16684 QM_TRY_INSPECT( 16685 const auto& dbFile, 16686 CloneFileAndAppend(*directory, mDatabaseFilenameBase + kSQLiteSuffix)); 16687 16688 #ifdef DEBUG 16689 { 16690 QM_TRY_INSPECT( 16691 const auto& databaseFilePath, 16692 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, dbFile, GetPath)); 16693 16694 MOZ_ASSERT(databaseFilePath == mDatabaseFilePath.ref()); 16695 } 16696 #endif 16697 16698 QM_TRY_INSPECT(const bool& exists, 16699 MOZ_TO_RESULT_INVOKE_MEMBER(dbFile, Exists)); 16700 16701 if (exists) { 16702 // Parts of this function may fail but that shouldn't prevent us from 16703 // deleting the file eventually. 16704 LoadPreviousVersion(*dbFile); 16705 16706 mState = State::BeginVersionChange; 16707 } else { 16708 mState = State::SendingResults; 16709 } 16710 16711 QM_TRY(MOZ_TO_RESULT( 16712 DispatchThisAfterProcessingCurrentEvent(mOwningEventTarget))); 16713 16714 return NS_OK; 16715 } 16716 16717 nsresult DeleteDatabaseOp::BeginVersionChange() { 16718 AssertIsOnOwningThread(); 16719 MOZ_ASSERT(mState == State::BeginVersionChange); 16720 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); 16721 16722 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 16723 IsActorDestroyed()) { 16724 IDB_REPORT_INTERNAL_ERR(); 16725 QM_TRY(MOZ_TO_RESULT(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR)); 16726 } 16727 16728 DatabaseActorInfo* info; 16729 if (gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info)) { 16730 MOZ_ASSERT(!info->mWaitingFactoryOp); 16731 16732 nsresult rv = 16733 SendVersionChangeMessages(info, Nothing(), mPreviousVersion, Nothing()); 16734 if (NS_WARN_IF(NS_FAILED(rv))) { 16735 return rv; 16736 } 16737 16738 if (!mMaybeBlockedDatabases.IsEmpty()) { 16739 // If the actor gets destroyed, mWaitingFactoryOp will hold the last 16740 // strong reference to us. 16741 info->mWaitingFactoryOp = this; 16742 16743 mState = State::WaitingForOtherDatabasesToClose; 16744 return NS_OK; 16745 } 16746 } 16747 16748 // No other databases need to be notified, just make sure that all 16749 // transactions are complete. 16750 WaitForTransactions(); 16751 return NS_OK; 16752 } 16753 16754 bool DeleteDatabaseOp::AreActorsAlive() { 16755 AssertIsOnOwningThread(); 16756 16757 return !IsActorDestroyed(); 16758 } 16759 16760 nsresult DeleteDatabaseOp::DispatchToWorkThread() { 16761 AssertIsOnOwningThread(); 16762 MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete); 16763 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); 16764 16765 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 16766 IsActorDestroyed()) { 16767 IDB_REPORT_INTERNAL_ERR(); 16768 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16769 } 16770 16771 mState = State::DatabaseWorkVersionChange; 16772 16773 RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this); 16774 16775 QuotaManager* const quotaManager = QuotaManager::Get(); 16776 MOZ_ASSERT(quotaManager); 16777 16778 nsresult rv = quotaManager->IOThread()->Dispatch(versionChangeOp.forget(), 16779 NS_DISPATCH_NORMAL); 16780 if (NS_WARN_IF(NS_FAILED(rv))) { 16781 IDB_REPORT_INTERNAL_ERR(); 16782 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16783 } 16784 16785 return NS_OK; 16786 } 16787 16788 void DeleteDatabaseOp::SendBlockedNotification() { 16789 AssertIsOnOwningThread(); 16790 MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose); 16791 16792 if (!IsActorDestroyed()) { 16793 (void)SendBlocked(mPreviousVersion); 16794 } 16795 } 16796 16797 nsresult DeleteDatabaseOp::DoVersionUpdate() { 16798 MOZ_CRASH("Not implemented because this should be unreachable."); 16799 } 16800 16801 void DeleteDatabaseOp::SendResults() { 16802 AssertIsOnOwningThread(); 16803 MOZ_ASSERT(mState == State::SendingResults); 16804 MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); 16805 16806 DebugOnly<DatabaseActorInfo*> info = nullptr; 16807 MOZ_ASSERT_IF(mDatabaseId.isSome() && gLiveDatabaseHashtable && 16808 gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info), 16809 !info->mWaitingFactoryOp); 16810 16811 if (!IsActorDestroyed()) { 16812 FactoryRequestResponse response; 16813 16814 if (!HasFailed()) { 16815 response = DeleteDatabaseRequestResponse(mPreviousVersion); 16816 } else { 16817 response = ClampResultCode(ResultCode()); 16818 } 16819 16820 (void)PBackgroundIDBFactoryRequestParent::Send__delete__(this, response); 16821 } 16822 16823 { 16824 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 16825 } 16826 16827 CleanupMetadata(); 16828 16829 FinishSendResults(); 16830 } 16831 16832 nsresult DeleteDatabaseOp::VersionChangeOp::RunOnIOThread() { 16833 AssertIsOnIOThread(); 16834 MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange); 16835 16836 AUTO_PROFILER_LABEL("DeleteDatabaseOp::VersionChangeOp::RunOnIOThread", DOM); 16837 16838 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 16839 !OperationMayProceed()) { 16840 IDB_REPORT_INTERNAL_ERR(); 16841 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16842 } 16843 16844 const PersistenceType& persistenceType = 16845 mDeleteDatabaseOp->mCommonParams.metadata().persistenceType(); 16846 16847 QuotaManager* quotaManager = 16848 mDeleteDatabaseOp->mEnforcingQuota ? QuotaManager::Get() : nullptr; 16849 16850 MOZ_ASSERT_IF(mDeleteDatabaseOp->mEnforcingQuota, quotaManager); 16851 16852 nsCOMPtr<nsIFile> directory = 16853 GetFileForPath(mDeleteDatabaseOp->mDatabaseDirectoryPath); 16854 if (NS_WARN_IF(!directory)) { 16855 IDB_REPORT_INTERNAL_ERR(); 16856 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 16857 } 16858 16859 nsresult rv = RemoveDatabaseFilesAndDirectory( 16860 *directory, mDeleteDatabaseOp->mDatabaseFilenameBase, quotaManager, 16861 persistenceType, mDeleteDatabaseOp->mOriginMetadata, 16862 mDeleteDatabaseOp->mCommonParams.metadata().name()); 16863 if (NS_WARN_IF(NS_FAILED(rv))) { 16864 return rv; 16865 } 16866 16867 rv = mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); 16868 if (NS_WARN_IF(NS_FAILED(rv))) { 16869 return rv; 16870 } 16871 16872 return NS_OK; 16873 } 16874 16875 void DeleteDatabaseOp::VersionChangeOp::RunOnOwningThread() { 16876 AssertIsOnOwningThread(); 16877 MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange); 16878 16879 const RefPtr<DeleteDatabaseOp> deleteOp = std::move(mDeleteDatabaseOp); 16880 16881 if (deleteOp->IsActorDestroyed()) { 16882 IDB_REPORT_INTERNAL_ERR(); 16883 deleteOp->SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); 16884 } else if (HasFailed()) { 16885 deleteOp->SetFailureCodeIfUnset(ResultCode()); 16886 } else { 16887 DatabaseActorInfo* info; 16888 16889 // Inform all the other databases that they are now invalidated. That 16890 // should remove the previous metadata from our table. 16891 if (gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId.ref(), &info)) { 16892 MOZ_ASSERT(!info->mLiveDatabases.isEmpty()); 16893 MOZ_ASSERT(!info->mWaitingFactoryOp); 16894 16895 nsTArray<SafeRefPtr<Database>> liveDatabases; 16896 if (NS_WARN_IF(!liveDatabases.SetCapacity(info->mLiveDatabases.length(), 16897 fallible))) { 16898 deleteOp->SetFailureCode(NS_ERROR_OUT_OF_MEMORY); 16899 } else { 16900 std::transform(info->mLiveDatabases.begin(), info->mLiveDatabases.end(), 16901 MakeBackInserter(liveDatabases), 16902 [](Database* const aDatabase) -> SafeRefPtr<Database> { 16903 return {aDatabase, AcquireStrongRefFromRawPtr{}}; 16904 }); 16905 16906 #ifdef DEBUG 16907 // The code below should result in the deletion of |info|. Set to null 16908 // here to make sure we find invalid uses later. 16909 info = nullptr; 16910 #endif 16911 16912 for (const auto& database : liveDatabases) { 16913 database->Invalidate(); 16914 } 16915 16916 MOZ_ASSERT(!gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId.ref())); 16917 } 16918 } 16919 } 16920 16921 // We hold a strong ref to the deleteOp, so it's safe to call Run() directly. 16922 16923 deleteOp->mState = State::SendingResults; 16924 MOZ_ALWAYS_SUCCEEDS(deleteOp->Run()); 16925 16926 #ifdef DEBUG 16927 // A bit hacky but the DeleteDatabaseOp::VersionChangeOp is not really a 16928 // normal database operation that is tied to an actor. Do this to make our 16929 // assertions happy. 16930 NoteActorDestroyed(); 16931 #endif 16932 } 16933 16934 nsresult DeleteDatabaseOp::VersionChangeOp::Run() { 16935 nsresult rv; 16936 16937 if (IsOnIOThread()) { 16938 rv = RunOnIOThread(); 16939 } else { 16940 RunOnOwningThread(); 16941 rv = NS_OK; 16942 } 16943 16944 if (NS_WARN_IF(NS_FAILED(rv))) { 16945 SetFailureCodeIfUnset(rv); 16946 16947 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); 16948 } 16949 16950 return NS_OK; 16951 } 16952 16953 nsresult GetDatabasesOp::DatabasesNotAvailable() { 16954 AssertIsOnIOThread(); 16955 MOZ_ASSERT(mState == State::DatabaseWorkOpen); 16956 16957 mState = State::SendingResults; 16958 16959 QM_TRY(MOZ_TO_RESULT( 16960 DispatchThisAfterProcessingCurrentEvent(mOwningEventTarget))); 16961 16962 return NS_OK; 16963 } 16964 16965 nsresult GetDatabasesOp::DoDirectoryWork() { 16966 AssertIsOnIOThread(); 16967 MOZ_ASSERT(mState == State::DirectoryWorkOpen); 16968 16969 // This state (DirectoryWorkOpen) runs immediately on the I/O thread, before 16970 // waiting for existing factory operations to complete (at which point 16971 // DoDatabaseWork will be invoked). To match the spec, we must snapshot the 16972 // current state of any databases that are being created (version = 0) or 16973 // upgraded (version >= 1) now. If we only sampled these values in 16974 // DoDatabaseWork, we would only see their post-creation/post-upgrade 16975 // versions, which would be incorrect. 16976 16977 IndexedDatabaseManager* const idm = IndexedDatabaseManager::Get(); 16978 MOZ_ASSERT(idm); 16979 16980 const auto& fileManagers = 16981 idm->GetFileManagers(mPersistenceType, mOriginMetadata.mOrigin); 16982 16983 for (const auto& fileManager : fileManagers) { 16984 auto& metadata = 16985 mDatabaseMetadataTable.LookupOrInsert(fileManager->DatabaseFilePath()); 16986 metadata.name() = fileManager->DatabaseName(); 16987 metadata.version() = fileManager->DatabaseVersion(); 16988 } 16989 16990 // Must set this before dispatching otherwise we will race with the IO thread. 16991 mState = State::DirectoryWorkDone; 16992 16993 QM_TRY(MOZ_TO_RESULT(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL))); 16994 16995 return NS_OK; 16996 } 16997 16998 nsresult GetDatabasesOp::DatabaseOpen() { 16999 AssertIsOnOwningThread(); 17000 MOZ_ASSERT(mState == State::DatabaseOpenPending); 17001 17002 nsresult rv = SendToIOThread(); 17003 if (NS_WARN_IF(NS_FAILED(rv))) { 17004 return rv; 17005 } 17006 17007 return NS_OK; 17008 } 17009 17010 nsresult GetDatabasesOp::DoDatabaseWork() { 17011 AssertIsOnIOThread(); 17012 MOZ_ASSERT(mState == State::DatabaseWorkOpen); 17013 17014 AUTO_PROFILER_LABEL("GetDatabasesOp::DoDatabaseWork", DOM); 17015 17016 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 17017 !OperationMayProceed()) { 17018 IDB_REPORT_INTERNAL_ERR(); 17019 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 17020 } 17021 17022 QuotaManager* const quotaManager = QuotaManager::Get(); 17023 MOZ_ASSERT(quotaManager); 17024 17025 { 17026 QM_TRY_INSPECT(const bool& exists, 17027 quotaManager->DoesOriginDirectoryExist(mOriginMetadata)); 17028 if (!exists) { 17029 return DatabasesNotAvailable(); 17030 } 17031 } 17032 17033 // XXX Is this really needed ? 17034 QM_TRY((["aManager, 17035 this]() -> mozilla::Result<nsCOMPtr<nsIFile>, nsresult> { 17036 if (mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { 17037 QM_TRY_RETURN(quotaManager->GetOriginDirectory(mOriginMetadata)); 17038 } 17039 17040 QM_TRY_RETURN( 17041 quotaManager->GetOrCreateTemporaryOriginDirectory(mOriginMetadata)); 17042 }() 17043 .map([](const auto& res) { return Ok{}; }))); 17044 17045 { 17046 QM_TRY_INSPECT(const bool& exists, 17047 quotaManager->DoesClientDirectoryExist( 17048 ClientMetadata{mOriginMetadata, Client::IDB})); 17049 if (!exists) { 17050 return DatabasesNotAvailable(); 17051 } 17052 } 17053 17054 QM_TRY_INSPECT( 17055 const auto& clientDirectory, 17056 (["aManager, this]() 17057 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> { 17058 if (mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { 17059 QM_TRY_RETURN(quotaManager->EnsurePersistentClientIsInitialized( 17060 ClientMetadata{mOriginMetadata, Client::IDB})); 17061 } 17062 17063 QM_TRY_RETURN(quotaManager->EnsureTemporaryClientIsInitialized( 17064 ClientMetadata{mOriginMetadata, Client::IDB}, 17065 /* aCreateIfNonExistent */ true)); 17066 }() 17067 .map([](const auto& res) { return res.first; }))); 17068 17069 QM_TRY_INSPECT( 17070 (const auto& [subdirsToProcess, databaseFilenames]), 17071 QuotaClient::GetDatabaseFilenames(*clientDirectory, 17072 /* aCanceled */ Atomic<bool>{false})); 17073 17074 for (const auto& databaseFilename : databaseFilenames) { 17075 QM_TRY_INSPECT( 17076 const auto& databaseFile, 17077 CloneFileAndAppend(*clientDirectory, databaseFilename + kSQLiteSuffix)); 17078 17079 nsString path; 17080 databaseFile->GetPath(path); 17081 17082 // Use the snapshotted values from DoDirectoryWork which correctly 17083 // snapshotted the state of any pending creations/upgrades. This does mean 17084 // that we need to skip reporting databases that had a version of 0 at that 17085 // time because they were still being created. In the event that any other 17086 // creation or upgrade requests are made after our operation is created, 17087 // this operation will block those, so it's not possible for this set of 17088 // data to get out of sync. The snapshotting (using cached database name 17089 // and version in DatabaseFileManager) also guarantees that we are not 17090 // touching the SQLite database here on the QuotaManager I/O thread which 17091 // is already open on the connection thread. 17092 17093 auto metadata = mDatabaseMetadataTable.Lookup(path); 17094 if (metadata) { 17095 if (metadata->version() != 0) { 17096 mDatabaseMetadataArray.AppendElement(DatabaseMetadata( 17097 metadata->name(), metadata->version(), mPersistenceType)); 17098 } 17099 17100 continue; 17101 } 17102 17103 // Since the database is not already open (there was no DatabaseFileManager 17104 // for snapshotting in DoDirectoryWork which could provide us with the 17105 // database name and version without needing to open the SQLite database), 17106 // it is safe and necessary for us to open the database on this thread and 17107 // retrieve its name and version. We do not need to worry about racing a 17108 // database open because database opens can only be processed on this 17109 // thread and we are performing the steps below synchronously. 17110 17111 QM_TRY_INSPECT( 17112 const auto& fmDirectory, 17113 CloneFileAndAppend(*clientDirectory, 17114 databaseFilename + kFileManagerDirectoryNameSuffix)); 17115 17116 QM_TRY_UNWRAP( 17117 const NotNull<nsCOMPtr<mozIStorageConnection>> connection, 17118 CreateStorageConnection(*databaseFile, *fmDirectory, VoidString(), 17119 mOriginMetadata.mOrigin, mDirectoryLockId, 17120 TelemetryIdForFile(databaseFile), Nothing{})); 17121 17122 { 17123 // Load version information. 17124 QM_TRY_INSPECT(const auto& stmt, 17125 CreateAndExecuteSingleStepStatement< 17126 SingleStepResult::ReturnNullIfNoResult>( 17127 *connection, "SELECT name, version FROM database"_ns)); 17128 17129 QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED); 17130 17131 QM_TRY_INSPECT( 17132 const auto& databaseName, 17133 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, stmt, GetString, 0)); 17134 17135 QM_TRY_INSPECT(const int64_t& version, 17136 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 1)); 17137 17138 mDatabaseMetadataArray.AppendElement( 17139 DatabaseMetadata(databaseName, version, mPersistenceType)); 17140 } 17141 } 17142 17143 mState = State::SendingResults; 17144 17145 QM_TRY(MOZ_TO_RESULT( 17146 DispatchThisAfterProcessingCurrentEvent(mOwningEventTarget))); 17147 17148 return NS_OK; 17149 } 17150 17151 nsresult GetDatabasesOp::BeginVersionChange() { 17152 MOZ_CRASH("Not implemented because this should be unreachable."); 17153 } 17154 17155 bool GetDatabasesOp::AreActorsAlive() { 17156 MOZ_CRASH("Not implemented because this should be unreachable."); 17157 } 17158 17159 void GetDatabasesOp::SendBlockedNotification() { 17160 MOZ_CRASH("Not implemented because this should be unreachable."); 17161 } 17162 17163 nsresult GetDatabasesOp::DispatchToWorkThread() { 17164 MOZ_CRASH("Not implemented because this should be unreachable."); 17165 } 17166 17167 nsresult GetDatabasesOp::DoVersionUpdate() { 17168 MOZ_CRASH("Not implemented because this should be unreachable."); 17169 } 17170 17171 void GetDatabasesOp::SendResults() { 17172 AssertIsOnOwningThread(); 17173 MOZ_ASSERT(mState == State::SendingResults); 17174 17175 #ifdef DEBUG 17176 NoteActorDestroyed(); 17177 #endif 17178 17179 if (HasFailed()) { 17180 mResolver(ClampResultCode(ResultCode())); 17181 } else { 17182 mResolver(mDatabaseMetadataArray); 17183 } 17184 17185 { 17186 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 17187 } 17188 17189 CleanupMetadata(); 17190 17191 FinishSendResults(); 17192 } 17193 17194 TransactionDatabaseOperationBase::TransactionDatabaseOperationBase( 17195 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId) 17196 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(), 17197 aTransaction->GetLoggingInfo()->NextRequestSN()), 17198 mTransaction(WrapNotNull(std::move(aTransaction))), 17199 mRequestId(aRequestId), 17200 mTransactionIsAborted((*mTransaction)->IsAborted()), 17201 mTransactionLoggingSerialNumber((*mTransaction)->LoggingSerialNumber()) { 17202 MOZ_ASSERT(LoggingSerialNumber()); 17203 } 17204 17205 TransactionDatabaseOperationBase::TransactionDatabaseOperationBase( 17206 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, 17207 uint64_t aLoggingSerialNumber) 17208 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(), 17209 aLoggingSerialNumber), 17210 mTransaction(WrapNotNull(std::move(aTransaction))), 17211 mRequestId(aRequestId), 17212 mTransactionIsAborted((*mTransaction)->IsAborted()), 17213 mTransactionLoggingSerialNumber((*mTransaction)->LoggingSerialNumber()) {} 17214 17215 TransactionDatabaseOperationBase::~TransactionDatabaseOperationBase() { 17216 MOZ_ASSERT(mInternalState == InternalState::Completed); 17217 MOZ_ASSERT(!mTransaction, 17218 "TransactionDatabaseOperationBase::Cleanup() was not called by a " 17219 "subclass!"); 17220 } 17221 17222 #ifdef DEBUG 17223 17224 void TransactionDatabaseOperationBase::AssertIsOnConnectionThread() const { 17225 (*mTransaction)->AssertIsOnConnectionThread(); 17226 } 17227 17228 #endif // DEBUG 17229 17230 uint64_t TransactionDatabaseOperationBase::StartOnConnectionPool( 17231 const nsID& aBackgroundChildLoggingId, const nsACString& aDatabaseId, 17232 int64_t aLoggingSerialNumber, const nsTArray<nsString>& aObjectStoreNames, 17233 bool aIsWriteTransaction) { 17234 AssertIsOnOwningThread(); 17235 MOZ_ASSERT(mInternalState == InternalState::Initial); 17236 17237 // Must set mInternalState before dispatching otherwise we will race with the 17238 // connection thread. 17239 mInternalState = InternalState::DatabaseWork; 17240 17241 return gConnectionPool->Start(aBackgroundChildLoggingId, aDatabaseId, 17242 aLoggingSerialNumber, aObjectStoreNames, 17243 aIsWriteTransaction, this); 17244 } 17245 17246 void TransactionDatabaseOperationBase::DispatchToConnectionPool() { 17247 AssertIsOnOwningThread(); 17248 MOZ_ASSERT(mInternalState == InternalState::Initial); 17249 17250 (void)this->Run(); 17251 } 17252 17253 void TransactionDatabaseOperationBase::RunOnConnectionThread() { 17254 MOZ_ASSERT(!IsOnBackgroundThread()); 17255 MOZ_ASSERT(mInternalState == InternalState::DatabaseWork); 17256 MOZ_ASSERT(!HasFailed()); 17257 17258 AUTO_PROFILER_LABEL("TransactionDatabaseOperationBase::RunOnConnectionThread", 17259 DOM); 17260 17261 // There are several cases where we don't actually have to to any work here. 17262 17263 if (mTransactionIsAborted || (*mTransaction)->IsInvalidatedOnAnyThread()) { 17264 // This transaction is already set to be aborted or invalidated. 17265 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); 17266 } else if (!OperationMayProceed()) { 17267 // The operation was canceled in some way, likely because the child process 17268 // has crashed. 17269 IDB_REPORT_INTERNAL_ERR(); 17270 OverrideFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); 17271 } else { 17272 Database& database = (*mTransaction)->GetMutableDatabase(); 17273 17274 // Here we're actually going to perform the database operation. 17275 nsresult rv = database.EnsureConnection(); 17276 if (NS_WARN_IF(NS_FAILED(rv))) { 17277 SetFailureCode(rv); 17278 } else { 17279 DatabaseConnection* connection = database.GetConnection(); 17280 MOZ_ASSERT(connection); 17281 17282 auto& storageConnection = connection->MutableStorageConnection(); 17283 17284 AutoSetProgressHandler autoProgress; 17285 if (mLoggingSerialNumber) { 17286 rv = autoProgress.Register(storageConnection, this); 17287 if (NS_WARN_IF(NS_FAILED(rv))) { 17288 SetFailureCode(rv); 17289 } 17290 } 17291 17292 if (NS_SUCCEEDED(rv)) { 17293 if (mLoggingSerialNumber) { 17294 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST( 17295 "Beginning database work", "DB Start", 17296 IDB_LOG_ID_STRING(mBackgroundChildLoggingId), 17297 mTransactionLoggingSerialNumber, mLoggingSerialNumber); 17298 } 17299 17300 rv = DoDatabaseWork(connection); 17301 17302 if (mLoggingSerialNumber) { 17303 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST( 17304 "Finished database work", "DB End", 17305 IDB_LOG_ID_STRING(mBackgroundChildLoggingId), 17306 mTransactionLoggingSerialNumber, mLoggingSerialNumber); 17307 } 17308 17309 if (NS_FAILED(rv)) { 17310 SetFailureCode(rv); 17311 } 17312 } 17313 } 17314 } 17315 17316 // Must set mInternalState before dispatching otherwise we will race with the 17317 // owning thread. 17318 if (HasPreprocessInfo()) { 17319 mInternalState = InternalState::SendingPreprocess; 17320 } else { 17321 mInternalState = InternalState::SendingResults; 17322 } 17323 17324 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); 17325 } 17326 17327 bool TransactionDatabaseOperationBase::HasPreprocessInfo() { return false; } 17328 17329 nsresult TransactionDatabaseOperationBase::SendPreprocessInfo() { 17330 return NS_OK; 17331 } 17332 17333 void TransactionDatabaseOperationBase::NoteContinueReceived() { 17334 AssertIsOnOwningThread(); 17335 MOZ_ASSERT(mInternalState == InternalState::WaitingForContinue); 17336 17337 mWaitingForContinue = false; 17338 17339 mInternalState = InternalState::SendingResults; 17340 17341 // This TransactionDatabaseOperationBase can only be held alive by the IPDL. 17342 // Run() can end up with clearing that last reference. So we need to add 17343 // a self reference here. 17344 RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip = this; 17345 17346 (void)this->Run(); 17347 } 17348 17349 void TransactionDatabaseOperationBase::SendToConnectionPool() { 17350 AssertIsOnOwningThread(); 17351 MOZ_ASSERT(mInternalState == InternalState::Initial); 17352 17353 // Must set mInternalState before dispatching otherwise we will race with the 17354 // connection thread. 17355 mInternalState = InternalState::DatabaseWork; 17356 17357 gConnectionPool->StartOp((*mTransaction)->TransactionId(), this); 17358 17359 (*mTransaction)->NoteActiveRequest(); 17360 } 17361 17362 void TransactionDatabaseOperationBase::SendPreprocess() { 17363 AssertIsOnOwningThread(); 17364 MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess); 17365 17366 SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ true); 17367 } 17368 17369 void TransactionDatabaseOperationBase::SendResults() { 17370 AssertIsOnOwningThread(); 17371 MOZ_ASSERT(mInternalState == InternalState::SendingResults); 17372 17373 SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ false); 17374 } 17375 17376 void TransactionDatabaseOperationBase::SendPreprocessInfoOrResults( 17377 bool aSendPreprocessInfo) { 17378 AssertIsOnOwningThread(); 17379 MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess || 17380 mInternalState == InternalState::SendingResults); 17381 17382 // The flag is raised only when there is no mUpdateRefcountFunction for the 17383 // executing operation. It assume that is because the previous 17384 // StartTransactionOp was failed to begin a write transaction and it reported 17385 // when this operation has already jumped to the Connection thread. 17386 MOZ_DIAGNOSTIC_ASSERT_IF(mAssumingPreviousOperationFail, 17387 (*mTransaction)->IsAborted()); 17388 17389 if (NS_WARN_IF(IsActorDestroyed())) { 17390 // Normally we wouldn't need to send any notifications if the actor was 17391 // already destroyed, but this can be a VersionChangeOp which needs to 17392 // notify its parent operation (OpenDatabaseOp) about the failure. 17393 // So SendFailureResult needs to be called even when the actor was 17394 // destroyed. Normal operations redundantly check if the actor was 17395 // destroyed in SendSuccessResult and SendFailureResult, therefore it's 17396 // ok to call it in all cases here. 17397 if (!HasFailed()) { 17398 IDB_REPORT_INTERNAL_ERR(); 17399 SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); 17400 } 17401 } else if ((*mTransaction)->IsInvalidated() || (*mTransaction)->IsAborted()) { 17402 // Aborted transactions always see their requests fail with ABORT_ERR, 17403 // even if the request succeeded or failed with another error. 17404 OverrideFailureCode(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); 17405 } 17406 17407 const nsresult rv = [aSendPreprocessInfo, this] { 17408 if (HasFailed()) { 17409 return ResultCode(); 17410 } 17411 if (aSendPreprocessInfo) { 17412 // This should not release the IPDL reference. 17413 return SendPreprocessInfo(); 17414 } 17415 // This may release the IPDL reference. 17416 return SendSuccessResult(); 17417 }(); 17418 17419 if (NS_FAILED(rv)) { 17420 SetFailureCodeIfUnset(rv); 17421 17422 // This should definitely release the IPDL reference. 17423 if (!SendFailureResult(rv)) { 17424 // Abort the transaction. 17425 (*mTransaction)->Abort(rv, /* aForce */ false); 17426 } 17427 } 17428 17429 if (aSendPreprocessInfo && !HasFailed()) { 17430 mInternalState = InternalState::WaitingForContinue; 17431 17432 mWaitingForContinue = true; 17433 } else { 17434 if (mLoggingSerialNumber) { 17435 (*mTransaction)->NoteFinishedRequest(mRequestId, ResultCode()); 17436 } 17437 17438 gConnectionPool->FinishOp((*mTransaction)->TransactionId()); 17439 17440 Cleanup(); 17441 17442 mInternalState = InternalState::Completed; 17443 } 17444 } 17445 17446 bool TransactionDatabaseOperationBase::Init(TransactionBase& aTransaction) { 17447 AssertIsOnBackgroundThread(); 17448 MOZ_ASSERT(mInternalState == InternalState::Initial); 17449 17450 return true; 17451 } 17452 17453 void TransactionDatabaseOperationBase::Cleanup() { 17454 AssertIsOnOwningThread(); 17455 MOZ_ASSERT(mInternalState == InternalState::SendingResults); 17456 17457 mTransaction.destroy(); 17458 } 17459 17460 NS_IMETHODIMP 17461 TransactionDatabaseOperationBase::Run() { 17462 switch (mInternalState) { 17463 case InternalState::Initial: 17464 SendToConnectionPool(); 17465 return NS_OK; 17466 17467 case InternalState::DatabaseWork: 17468 RunOnConnectionThread(); 17469 return NS_OK; 17470 17471 case InternalState::SendingPreprocess: 17472 SendPreprocess(); 17473 return NS_OK; 17474 17475 case InternalState::SendingResults: 17476 SendResults(); 17477 return NS_OK; 17478 17479 default: 17480 MOZ_CRASH("Bad state!"); 17481 } 17482 } 17483 17484 TransactionBase::CommitOp::CommitOp(SafeRefPtr<TransactionBase> aTransaction, 17485 nsresult aResultCode) 17486 : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(), 17487 aTransaction->GetLoggingInfo()->NextRequestSN()), 17488 mTransaction(std::move(aTransaction)), 17489 mResultCode(aResultCode) { 17490 MOZ_ASSERT(mTransaction); 17491 MOZ_ASSERT(LoggingSerialNumber()); 17492 } 17493 17494 nsresult TransactionBase::CommitOp::WriteAutoIncrementCounts() { 17495 MOZ_ASSERT(mTransaction); 17496 mTransaction->AssertIsOnConnectionThread(); 17497 MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::Mode::ReadWrite || 17498 mTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush || 17499 mTransaction->GetMode() == IDBTransaction::Mode::Cleanup || 17500 mTransaction->GetMode() == IDBTransaction::Mode::VersionChange); 17501 17502 const nsTArray<SafeRefPtr<FullObjectStoreMetadata>>& metadataArray = 17503 mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray; 17504 17505 if (!metadataArray.IsEmpty()) { 17506 DatabaseConnection* connection = 17507 mTransaction->GetDatabase().GetConnection(); 17508 MOZ_ASSERT(connection); 17509 17510 // The parameter names are not used, parameters are bound by index only 17511 // locally in the same function. 17512 auto stmt = DatabaseConnection::LazyStatement( 17513 *connection, 17514 "UPDATE object_store " 17515 "SET auto_increment = :auto_increment WHERE id " 17516 "= :object_store_id;"_ns); 17517 17518 for (const auto& metadata : metadataArray) { 17519 MOZ_ASSERT(!metadata->mDeleted); 17520 17521 const int64_t nextAutoIncrementId = [&metadata] { 17522 const auto&& lockedAutoIncrementIds = 17523 metadata->mAutoIncrementIds.Lock(); 17524 return lockedAutoIncrementIds->next; 17525 }(); 17526 17527 MOZ_ASSERT(nextAutoIncrementId > 1); 17528 17529 QM_TRY_INSPECT(const auto& borrowedStmt, stmt.Borrow()); 17530 17531 QM_TRY(MOZ_TO_RESULT( 17532 borrowedStmt->BindInt64ByIndex(1, metadata->mCommonMetadata.id()))); 17533 17534 QM_TRY(MOZ_TO_RESULT( 17535 borrowedStmt->BindInt64ByIndex(0, nextAutoIncrementId))); 17536 17537 QM_TRY(MOZ_TO_RESULT(borrowedStmt->Execute())); 17538 } 17539 } 17540 17541 return NS_OK; 17542 } 17543 17544 void TransactionBase::CommitOp::CommitOrRollbackAutoIncrementCounts() { 17545 MOZ_ASSERT(mTransaction); 17546 mTransaction->AssertIsOnConnectionThread(); 17547 MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::Mode::ReadWrite || 17548 mTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush || 17549 mTransaction->GetMode() == IDBTransaction::Mode::Cleanup || 17550 mTransaction->GetMode() == IDBTransaction::Mode::VersionChange); 17551 17552 const auto& metadataArray = 17553 mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray; 17554 17555 if (!metadataArray.IsEmpty()) { 17556 bool committed = NS_SUCCEEDED(mResultCode); 17557 17558 for (const auto& metadata : metadataArray) { 17559 auto&& lockedAutoIncrementIds = metadata->mAutoIncrementIds.Lock(); 17560 17561 if (committed) { 17562 lockedAutoIncrementIds->committed = lockedAutoIncrementIds->next; 17563 } else { 17564 lockedAutoIncrementIds->next = lockedAutoIncrementIds->committed; 17565 } 17566 } 17567 } 17568 } 17569 17570 #ifdef DEBUG 17571 17572 void TransactionBase::CommitOp::AssertForeignKeyConsistency( 17573 DatabaseConnection* aConnection) { 17574 MOZ_ASSERT(aConnection); 17575 MOZ_ASSERT(mTransaction); 17576 mTransaction->AssertIsOnConnectionThread(); 17577 MOZ_ASSERT(mTransaction->GetMode() != IDBTransaction::Mode::ReadOnly); 17578 17579 { 17580 QM_TRY_INSPECT( 17581 const auto& pragmaStmt, 17582 CreateAndExecuteSingleStepStatement( 17583 aConnection->MutableStorageConnection(), "PRAGMA foreign_keys;"_ns), 17584 QM_ASSERT_UNREACHABLE_VOID); 17585 17586 int32_t foreignKeysEnabled; 17587 MOZ_ALWAYS_SUCCEEDS(pragmaStmt->GetInt32(0, &foreignKeysEnabled)); 17588 17589 MOZ_ASSERT(foreignKeysEnabled, 17590 "Database doesn't have foreign keys enabled!"); 17591 } 17592 17593 { 17594 QM_TRY_INSPECT(const bool& foreignKeyError, 17595 CreateAndExecuteSingleStepStatement< 17596 SingleStepResult::ReturnNullIfNoResult>( 17597 aConnection->MutableStorageConnection(), 17598 "PRAGMA foreign_key_check;"_ns), 17599 QM_ASSERT_UNREACHABLE_VOID); 17600 17601 MOZ_ASSERT(!foreignKeyError, "Database has inconsisistent foreign keys!"); 17602 } 17603 } 17604 17605 #endif // DEBUG 17606 17607 NS_IMPL_ISUPPORTS_INHERITED0(TransactionBase::CommitOp, DatabaseOperationBase) 17608 17609 NS_IMETHODIMP 17610 TransactionBase::CommitOp::Run() { 17611 MOZ_ASSERT(mTransaction); 17612 mTransaction->AssertIsOnConnectionThread(); 17613 17614 AUTO_PROFILER_LABEL("TransactionBase::CommitOp::Run", DOM); 17615 17616 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST( 17617 "Beginning database work", "DB Start", 17618 IDB_LOG_ID_STRING(mBackgroundChildLoggingId), 17619 mTransaction->LoggingSerialNumber(), mLoggingSerialNumber); 17620 17621 if (mTransaction->GetMode() != IDBTransaction::Mode::ReadOnly && 17622 mTransaction->mHasBeenActiveOnConnectionThread) { 17623 if (DatabaseConnection* connection = 17624 mTransaction->GetDatabase().GetConnection()) { 17625 // May be null if the VersionChangeOp was canceled. 17626 DatabaseConnection::UpdateRefcountFunction* fileRefcountFunction = 17627 connection->GetUpdateRefcountFunction(); 17628 17629 if (NS_SUCCEEDED(mResultCode)) { 17630 if (fileRefcountFunction) { 17631 mResultCode = fileRefcountFunction->WillCommit(); 17632 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode), 17633 "WillCommit() failed!"); 17634 } 17635 17636 if (NS_SUCCEEDED(mResultCode)) { 17637 mResultCode = WriteAutoIncrementCounts(); 17638 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode), 17639 "WriteAutoIncrementCounts() failed!"); 17640 17641 if (NS_SUCCEEDED(mResultCode)) { 17642 AssertForeignKeyConsistency(connection); 17643 17644 mResultCode = connection->CommitWriteTransaction(); 17645 NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode), "Commit failed!"); 17646 17647 if (NS_SUCCEEDED(mResultCode) && 17648 mTransaction->GetMode() == 17649 IDBTransaction::Mode::ReadWriteFlush) { 17650 mResultCode = connection->Checkpoint(); 17651 } 17652 17653 if (NS_SUCCEEDED(mResultCode) && fileRefcountFunction) { 17654 fileRefcountFunction->DidCommit(); 17655 } 17656 } 17657 } 17658 } 17659 17660 if (NS_FAILED(mResultCode)) { 17661 if (fileRefcountFunction) { 17662 fileRefcountFunction->DidAbort(); 17663 } 17664 17665 connection->RollbackWriteTransaction(); 17666 } 17667 17668 CommitOrRollbackAutoIncrementCounts(); 17669 17670 connection->FinishWriteTransaction(); 17671 17672 if (mTransaction->GetMode() == IDBTransaction::Mode::Cleanup) { 17673 connection->DoIdleProcessing(/* aNeedsCheckpoint */ true, 17674 /* aInterrupted */ Atomic<bool>(false)); 17675 17676 connection->EnableQuotaChecks(); 17677 } 17678 } 17679 } 17680 17681 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST( 17682 "Finished database work", "DB End", 17683 IDB_LOG_ID_STRING(mBackgroundChildLoggingId), 17684 mTransaction->LoggingSerialNumber(), mLoggingSerialNumber); 17685 17686 IDB_LOG_MARK_PARENT_TRANSACTION("Finished database work", "DB End", 17687 IDB_LOG_ID_STRING(mBackgroundChildLoggingId), 17688 mTransaction->LoggingSerialNumber()); 17689 17690 return NS_OK; 17691 } 17692 17693 void TransactionBase::CommitOp::TransactionFinishedBeforeUnblock() { 17694 AssertIsOnBackgroundThread(); 17695 MOZ_ASSERT(mTransaction); 17696 17697 AUTO_PROFILER_LABEL("CommitOp::TransactionFinishedBeforeUnblock", DOM); 17698 17699 if (!IsActorDestroyed()) { 17700 mTransaction->UpdateMetadata(mResultCode); 17701 } 17702 } 17703 17704 void TransactionBase::CommitOp::TransactionFinishedAfterUnblock() { 17705 AssertIsOnBackgroundThread(); 17706 MOZ_ASSERT(mTransaction); 17707 17708 IDB_LOG_MARK_PARENT_TRANSACTION( 17709 "Finished with result 0x%" PRIx32, "Transaction finished (0x%" PRIx32 ")", 17710 IDB_LOG_ID_STRING(mTransaction->GetLoggingInfo()->Id()), 17711 mTransaction->LoggingSerialNumber(), static_cast<uint32_t>(mResultCode)); 17712 17713 mTransaction->SendCompleteNotification(ClampResultCode(mResultCode)); 17714 17715 mTransaction->GetMutableDatabase().UnregisterTransaction(*mTransaction); 17716 17717 mTransaction = nullptr; 17718 17719 #ifdef DEBUG 17720 // A bit hacky but the CommitOp is not really a normal database operation 17721 // that is tied to an actor. Do this to make our assertions happy. 17722 NoteActorDestroyed(); 17723 #endif 17724 } 17725 17726 nsresult VersionChangeTransactionOp::SendSuccessResult() { 17727 AssertIsOnOwningThread(); 17728 17729 // Nothing to send here, the API assumes that this request always succeeds. 17730 return NS_OK; 17731 } 17732 17733 bool VersionChangeTransactionOp::SendFailureResult(nsresult aResultCode) { 17734 AssertIsOnOwningThread(); 17735 17736 // The only option here is to cause the transaction to abort. 17737 return false; 17738 } 17739 17740 void VersionChangeTransactionOp::Cleanup() { 17741 AssertIsOnOwningThread(); 17742 17743 #ifdef DEBUG 17744 // A bit hacky but the VersionChangeTransactionOp is not generated in response 17745 // to a child request like most other database operations. Do this to make our 17746 // assertions happy. 17747 NoteActorDestroyed(); 17748 #endif 17749 17750 TransactionDatabaseOperationBase::Cleanup(); 17751 } 17752 17753 nsresult CreateObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) { 17754 MOZ_ASSERT(aConnection); 17755 aConnection->AssertIsOnConnectionThread(); 17756 17757 AUTO_PROFILER_LABEL("CreateObjectStoreOp::DoDatabaseWork", DOM); 17758 17759 #ifdef DEBUG 17760 { 17761 // Make sure that we're not creating an object store with the same name as 17762 // another that already exists. This should be impossible because we should 17763 // have thrown an error long before now... 17764 // The parameter names are not used, parameters are bound by index only 17765 // locally in the same function. 17766 QM_TRY_INSPECT(const bool& hasResult, 17767 aConnection 17768 ->BorrowAndExecuteSingleStepStatement( 17769 "SELECT name " 17770 "FROM object_store " 17771 "WHERE name = :name;"_ns, 17772 [&self = *this](auto& stmt) -> Result<Ok, nsresult> { 17773 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex( 17774 0, self.mMetadata.name()))); 17775 return Ok{}; 17776 }) 17777 .map(IsSome), 17778 QM_ASSERT_UNREACHABLE); 17779 17780 MOZ_ASSERT(!hasResult); 17781 } 17782 #endif 17783 17784 DatabaseConnection::AutoSavepoint autoSave; 17785 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 17786 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 17787 , 17788 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 17789 #endif 17790 ); 17791 17792 // The parameter names are not used, parameters are bound by index only 17793 // locally in the same function. 17794 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 17795 "INSERT INTO object_store (id, auto_increment, name, key_path) " 17796 "VALUES (:id, :auto_increment, :name, :key_path);"_ns, 17797 [&metadata = 17798 mMetadata](mozIStorageStatement& stmt) -> Result<Ok, nsresult> { 17799 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, metadata.id()))); 17800 17801 QM_TRY(MOZ_TO_RESULT( 17802 stmt.BindInt32ByIndex(1, metadata.autoIncrement() ? 1 : 0))); 17803 17804 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(2, metadata.name()))); 17805 17806 if (metadata.keyPath().IsValid()) { 17807 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex( 17808 3, metadata.keyPath().SerializeToString()))); 17809 } else { 17810 QM_TRY(MOZ_TO_RESULT(stmt.BindNullByIndex(3))); 17811 } 17812 17813 return Ok{}; 17814 }))); 17815 17816 #ifdef DEBUG 17817 { 17818 int64_t id; 17819 MOZ_ALWAYS_SUCCEEDS( 17820 aConnection->MutableStorageConnection().GetLastInsertRowID(&id)); 17821 MOZ_ASSERT(mMetadata.id() == id); 17822 } 17823 #endif 17824 17825 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 17826 17827 return NS_OK; 17828 } 17829 17830 nsresult DeleteObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) { 17831 MOZ_ASSERT(aConnection); 17832 aConnection->AssertIsOnConnectionThread(); 17833 17834 AUTO_PROFILER_LABEL("DeleteObjectStoreOp::DoDatabaseWork", DOM); 17835 17836 #ifdef DEBUG 17837 { 17838 // Make sure |mIsLastObjectStore| is telling the truth. 17839 QM_TRY_INSPECT( 17840 const auto& stmt, 17841 aConnection->BorrowCachedStatement("SELECT id FROM object_store;"_ns), 17842 QM_ASSERT_UNREACHABLE); 17843 17844 bool foundThisObjectStore = false; 17845 bool foundOtherObjectStore = false; 17846 17847 while (true) { 17848 bool hasResult; 17849 MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult)); 17850 17851 if (!hasResult) { 17852 break; 17853 } 17854 17855 int64_t id; 17856 MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id)); 17857 17858 if (id == mMetadata->mCommonMetadata.id()) { 17859 foundThisObjectStore = true; 17860 } else { 17861 foundOtherObjectStore = true; 17862 } 17863 } 17864 17865 MOZ_ASSERT_IF(mIsLastObjectStore, 17866 foundThisObjectStore && !foundOtherObjectStore); 17867 MOZ_ASSERT_IF(!mIsLastObjectStore, 17868 foundThisObjectStore && foundOtherObjectStore); 17869 } 17870 #endif 17871 17872 DatabaseConnection::AutoSavepoint autoSave; 17873 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 17874 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 17875 , 17876 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 17877 #endif 17878 ); 17879 17880 if (mIsLastObjectStore) { 17881 // We can just delete everything if this is the last object store. 17882 QM_TRY(MOZ_TO_RESULT( 17883 aConnection->ExecuteCachedStatement("DELETE FROM index_data;"_ns))); 17884 17885 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 17886 "DELETE FROM unique_index_data;"_ns))); 17887 17888 QM_TRY(MOZ_TO_RESULT( 17889 aConnection->ExecuteCachedStatement("DELETE FROM object_data;"_ns))); 17890 17891 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 17892 "DELETE FROM object_store_index;"_ns))); 17893 17894 QM_TRY(MOZ_TO_RESULT( 17895 aConnection->ExecuteCachedStatement("DELETE FROM object_store;"_ns))); 17896 } else { 17897 QM_TRY_INSPECT( 17898 const bool& hasIndexes, 17899 ObjectStoreHasIndexes(*aConnection, mMetadata->mCommonMetadata.id())); 17900 17901 const auto bindObjectStoreIdToFirstParameter = 17902 [this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> { 17903 QM_TRY(MOZ_TO_RESULT( 17904 stmt.BindInt64ByIndex(0, mMetadata->mCommonMetadata.id()))); 17905 17906 return Ok{}; 17907 }; 17908 17909 // The parameter name :object_store_id in the SQL statements below is not 17910 // used for binding, parameters are bound by index only locally by 17911 // bindObjectStoreIdToFirstParameter. 17912 if (hasIndexes) { 17913 QM_TRY(MOZ_TO_RESULT(DeleteObjectStoreDataTableRowsWithIndexes( 17914 aConnection, mMetadata->mCommonMetadata.id(), Nothing()))); 17915 17916 // Now clean up the object store index table. 17917 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 17918 "DELETE FROM object_store_index " 17919 "WHERE object_store_id = :object_store_id;"_ns, 17920 bindObjectStoreIdToFirstParameter))); 17921 } else { 17922 // We only have to worry about object data if this object store has no 17923 // indexes. 17924 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 17925 "DELETE FROM object_data " 17926 "WHERE object_store_id = :object_store_id;"_ns, 17927 bindObjectStoreIdToFirstParameter))); 17928 } 17929 17930 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 17931 "DELETE FROM object_store " 17932 "WHERE id = :object_store_id;"_ns, 17933 bindObjectStoreIdToFirstParameter))); 17934 17935 #ifdef DEBUG 17936 { 17937 int32_t deletedRowCount; 17938 MOZ_ALWAYS_SUCCEEDS( 17939 aConnection->MutableStorageConnection().GetAffectedRows( 17940 &deletedRowCount)); 17941 MOZ_ASSERT(deletedRowCount == 1); 17942 } 17943 #endif 17944 } 17945 17946 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 17947 17948 if (mMetadata->mCommonMetadata.autoIncrement()) { 17949 Transaction().ForgetModifiedAutoIncrementObjectStore(*mMetadata); 17950 } 17951 17952 return NS_OK; 17953 } 17954 17955 nsresult RenameObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection) { 17956 MOZ_ASSERT(aConnection); 17957 aConnection->AssertIsOnConnectionThread(); 17958 17959 AUTO_PROFILER_LABEL("RenameObjectStoreOp::DoDatabaseWork", DOM); 17960 17961 #ifdef DEBUG 17962 { 17963 // Make sure that we're not renaming an object store with the same name as 17964 // another that already exists. This should be impossible because we should 17965 // have thrown an error long before now... 17966 // The parameter names are not used, parameters are bound by index only 17967 // locally in the same function. 17968 QM_TRY_INSPECT( 17969 const bool& hasResult, 17970 aConnection 17971 ->BorrowAndExecuteSingleStepStatement( 17972 "SELECT name " 17973 "FROM object_store " 17974 "WHERE name = :name AND id != :id;"_ns, 17975 [&self = *this](auto& stmt) -> Result<Ok, nsresult> { 17976 QM_TRY( 17977 MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName))); 17978 17979 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mId))); 17980 return Ok{}; 17981 }) 17982 .map(IsSome), 17983 QM_ASSERT_UNREACHABLE); 17984 17985 MOZ_ASSERT(!hasResult); 17986 } 17987 #endif 17988 17989 DatabaseConnection::AutoSavepoint autoSave; 17990 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 17991 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 17992 , 17993 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 17994 #endif 17995 ); 17996 17997 // The parameter names are not used, parameters are bound by index only 17998 // locally in the same function. 17999 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 18000 "UPDATE object_store " 18001 "SET name = :name " 18002 "WHERE id = :id;"_ns, 18003 [&self = *this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> { 18004 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName))); 18005 18006 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mId))); 18007 18008 return Ok{}; 18009 }))); 18010 18011 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 18012 18013 return NS_OK; 18014 } 18015 18016 CreateIndexOp::CreateIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction, 18017 const IndexOrObjectStoreId aObjectStoreId, 18018 const IndexMetadata& aMetadata) 18019 : VersionChangeTransactionOp(std::move(aTransaction)), 18020 mMetadata(aMetadata), 18021 mFileManager(Transaction().GetDatabase().GetFileManagerPtr()), 18022 mDatabaseId(Transaction().DatabaseId()), 18023 mObjectStoreId(aObjectStoreId) { 18024 MOZ_ASSERT(aObjectStoreId); 18025 MOZ_ASSERT(aMetadata.id()); 18026 MOZ_ASSERT(mFileManager); 18027 MOZ_ASSERT(!mDatabaseId.IsEmpty()); 18028 } 18029 18030 nsresult CreateIndexOp::InsertDataFromObjectStore( 18031 DatabaseConnection* aConnection) { 18032 MOZ_ASSERT(aConnection); 18033 aConnection->AssertIsOnConnectionThread(); 18034 MOZ_ASSERT(mMaybeUniqueIndexTable); 18035 18036 AUTO_PROFILER_LABEL("CreateIndexOp::InsertDataFromObjectStore", DOM); 18037 18038 auto& storageConnection = aConnection->MutableStorageConnection(); 18039 18040 RefPtr<UpdateIndexDataValuesFunction> updateFunction = 18041 new UpdateIndexDataValuesFunction(this, aConnection, 18042 Transaction().GetDatabasePtr()); 18043 18044 constexpr auto updateFunctionName = "update_index_data_values"_ns; 18045 18046 nsresult rv = 18047 storageConnection.CreateFunction(updateFunctionName, 4, updateFunction); 18048 if (NS_WARN_IF(NS_FAILED(rv))) { 18049 return rv; 18050 } 18051 18052 rv = InsertDataFromObjectStoreInternal(aConnection); 18053 18054 MOZ_ALWAYS_SUCCEEDS(storageConnection.RemoveFunction(updateFunctionName)); 18055 18056 if (NS_WARN_IF(NS_FAILED(rv))) { 18057 return rv; 18058 } 18059 18060 return NS_OK; 18061 } 18062 18063 nsresult CreateIndexOp::InsertDataFromObjectStoreInternal( 18064 DatabaseConnection* aConnection) const { 18065 MOZ_ASSERT(aConnection); 18066 aConnection->AssertIsOnConnectionThread(); 18067 MOZ_ASSERT(mMaybeUniqueIndexTable); 18068 18069 MOZ_ASSERT(aConnection->HasStorageConnection()); 18070 18071 // The parameter names are not used, parameters are bound by index only 18072 // locally in the same function. 18073 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 18074 "UPDATE object_data " 18075 "SET index_data_values = update_index_data_values " 18076 "(key, index_data_values, file_ids, data) " 18077 "WHERE object_store_id = :object_store_id;"_ns, 18078 [objectStoredId = 18079 mObjectStoreId](mozIStorageStatement& stmt) -> Result<Ok, nsresult> { 18080 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, objectStoredId))); 18081 18082 return Ok{}; 18083 }))); 18084 18085 return NS_OK; 18086 } 18087 18088 bool CreateIndexOp::Init(TransactionBase& aTransaction) { 18089 AssertIsOnBackgroundThread(); 18090 MOZ_ASSERT(mObjectStoreId); 18091 MOZ_ASSERT(mMaybeUniqueIndexTable.isNothing()); 18092 18093 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 18094 aTransaction.GetMetadataForObjectStoreId(mObjectStoreId); 18095 MOZ_ASSERT(objectStoreMetadata); 18096 18097 const uint32_t indexCount = objectStoreMetadata->mIndexes.Count(); 18098 if (!indexCount) { 18099 return true; 18100 } 18101 18102 auto uniqueIndexTable = UniqueIndexTable{indexCount}; 18103 18104 for (const auto& value : objectStoreMetadata->mIndexes.Values()) { 18105 MOZ_ASSERT(!uniqueIndexTable.Contains(value->mCommonMetadata.id())); 18106 18107 if (NS_WARN_IF(!uniqueIndexTable.InsertOrUpdate( 18108 value->mCommonMetadata.id(), value->mCommonMetadata.unique(), 18109 fallible))) { 18110 IDB_REPORT_INTERNAL_ERR(); 18111 NS_WARNING("out of memory"); 18112 return false; 18113 } 18114 } 18115 18116 uniqueIndexTable.MarkImmutable(); 18117 18118 mMaybeUniqueIndexTable.emplace(std::move(uniqueIndexTable)); 18119 18120 return true; 18121 } 18122 18123 nsresult CreateIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) { 18124 MOZ_ASSERT(aConnection); 18125 aConnection->AssertIsOnConnectionThread(); 18126 18127 AUTO_PROFILER_LABEL("CreateIndexOp::DoDatabaseWork", DOM); 18128 18129 #ifdef DEBUG 18130 { 18131 // Make sure that we're not creating an index with the same name and object 18132 // store as another that already exists. This should be impossible because 18133 // we should have thrown an error long before now... 18134 // The parameter names are not used, parameters are bound by index only 18135 // locally in the same function. 18136 QM_TRY_INSPECT( 18137 const bool& hasResult, 18138 aConnection 18139 ->BorrowAndExecuteSingleStepStatement( 18140 "SELECT name " 18141 "FROM object_store_index " 18142 "WHERE object_store_id = :object_store_id AND name = :name;"_ns, 18143 [&self = *this](auto& stmt) -> Result<Ok, nsresult> { 18144 QM_TRY(MOZ_TO_RESULT( 18145 stmt.BindInt64ByIndex(0, self.mObjectStoreId))); 18146 QM_TRY(MOZ_TO_RESULT( 18147 stmt.BindStringByIndex(1, self.mMetadata.name()))); 18148 return Ok{}; 18149 }) 18150 .map(IsSome), 18151 QM_ASSERT_UNREACHABLE); 18152 18153 MOZ_ASSERT(!hasResult); 18154 } 18155 #endif 18156 18157 DatabaseConnection::AutoSavepoint autoSave; 18158 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 18159 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 18160 , 18161 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 18162 #endif 18163 ); 18164 18165 // The parameter names are not used, parameters are bound by index only 18166 // locally in the same function. 18167 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 18168 "INSERT INTO object_store_index (id, name, key_path, unique_index, " 18169 "multientry, object_store_id, locale, " 18170 "is_auto_locale) " 18171 "VALUES (:id, :name, :key_path, :unique, :multientry, " 18172 ":object_store_id, :locale, :is_auto_locale)"_ns, 18173 [&metadata = mMetadata, objectStoreId = mObjectStoreId]( 18174 mozIStorageStatement& stmt) -> Result<Ok, nsresult> { 18175 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, metadata.id()))); 18176 18177 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(1, metadata.name()))); 18178 18179 QM_TRY(MOZ_TO_RESULT( 18180 stmt.BindStringByIndex(2, metadata.keyPath().SerializeToString()))); 18181 18182 QM_TRY( 18183 MOZ_TO_RESULT(stmt.BindInt32ByIndex(3, metadata.unique() ? 1 : 0))); 18184 18185 QM_TRY(MOZ_TO_RESULT( 18186 stmt.BindInt32ByIndex(4, metadata.multiEntry() ? 1 : 0))); 18187 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(5, objectStoreId))); 18188 18189 QM_TRY(MOZ_TO_RESULT( 18190 metadata.locale().IsEmpty() 18191 ? stmt.BindNullByIndex(6) 18192 : stmt.BindUTF8StringByIndex(6, metadata.locale()))); 18193 18194 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByIndex(7, metadata.autoLocale()))); 18195 18196 return Ok{}; 18197 }))); 18198 18199 #ifdef DEBUG 18200 { 18201 int64_t id; 18202 MOZ_ALWAYS_SUCCEEDS( 18203 aConnection->MutableStorageConnection().GetLastInsertRowID(&id)); 18204 MOZ_ASSERT(mMetadata.id() == id); 18205 } 18206 #endif 18207 18208 QM_TRY(MOZ_TO_RESULT(InsertDataFromObjectStore(aConnection))); 18209 18210 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 18211 18212 return NS_OK; 18213 } 18214 18215 NS_IMPL_ISUPPORTS(CreateIndexOp::UpdateIndexDataValuesFunction, 18216 mozIStorageFunction); 18217 18218 NS_IMETHODIMP 18219 CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall( 18220 mozIStorageValueArray* aValues, nsIVariant** _retval) { 18221 MOZ_ASSERT(aValues); 18222 MOZ_ASSERT(_retval); 18223 MOZ_ASSERT(mConnection); 18224 mConnection->AssertIsOnConnectionThread(); 18225 MOZ_ASSERT(mOp); 18226 MOZ_ASSERT(mOp->mFileManager); 18227 18228 AUTO_PROFILER_LABEL( 18229 "CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall", DOM); 18230 18231 #ifdef DEBUG 18232 { 18233 uint32_t argCount; 18234 MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount)); 18235 MOZ_ASSERT(argCount == 4); // key, index_data_values, file_ids, data 18236 18237 int32_t valueType; 18238 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType)); 18239 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); 18240 18241 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType)); 18242 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL || 18243 valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); 18244 18245 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType)); 18246 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL || 18247 valueType == mozIStorageValueArray::VALUE_TYPE_TEXT); 18248 18249 MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType)); 18250 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB || 18251 valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER); 18252 } 18253 #endif 18254 18255 QM_TRY_UNWRAP(auto cloneInfo, GetStructuredCloneReadInfoFromValueArray( 18256 aValues, 18257 /* aDataIndex */ 3, 18258 /* aFileIdsIndex */ 2, *mOp->mFileManager)); 18259 18260 const IndexMetadata& metadata = mOp->mMetadata; 18261 const IndexOrObjectStoreId& objectStoreId = mOp->mObjectStoreId; 18262 18263 // XXX does this really need a non-const cloneInfo? 18264 QM_TRY_INSPECT(const auto& updateInfos, 18265 DeserializeIndexValueToUpdateInfos( 18266 metadata.id(), metadata.keyPath(), metadata.multiEntry(), 18267 metadata.locale(), cloneInfo)); 18268 18269 if (updateInfos.IsEmpty()) { 18270 // XXX See if we can do this without copying... 18271 18272 nsCOMPtr<nsIVariant> unmodifiedValue; 18273 18274 // No changes needed, just return the original value. 18275 QM_TRY_INSPECT(const int32_t& valueType, 18276 MOZ_TO_RESULT_INVOKE_MEMBER(aValues, GetTypeOfIndex, 1)); 18277 18278 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL || 18279 valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); 18280 18281 if (valueType == mozIStorageValueArray::VALUE_TYPE_NULL) { 18282 unmodifiedValue = new storage::NullVariant(); 18283 unmodifiedValue.forget(_retval); 18284 return NS_OK; 18285 } 18286 18287 MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); 18288 18289 const uint8_t* blobData; 18290 uint32_t blobDataLength; 18291 QM_TRY( 18292 MOZ_TO_RESULT(aValues->GetSharedBlob(1, &blobDataLength, &blobData))); 18293 18294 const std::pair<uint8_t*, int> copiedBlobDataPair( 18295 static_cast<uint8_t*>(malloc(blobDataLength)), blobDataLength); 18296 18297 if (!copiedBlobDataPair.first) { 18298 IDB_REPORT_INTERNAL_ERR(); 18299 return NS_ERROR_OUT_OF_MEMORY; 18300 } 18301 18302 memcpy(copiedBlobDataPair.first, blobData, blobDataLength); 18303 18304 unmodifiedValue = new storage::AdoptedBlobVariant(copiedBlobDataPair); 18305 unmodifiedValue.forget(_retval); 18306 18307 return NS_OK; 18308 } 18309 18310 Key key; 18311 QM_TRY(MOZ_TO_RESULT(key.SetFromValueArray(aValues, 0))); 18312 18313 QM_TRY_UNWRAP(auto indexValues, ReadCompressedIndexDataValues(*aValues, 1)); 18314 18315 const bool hadPreviousIndexValues = !indexValues.IsEmpty(); 18316 18317 const uint32_t updateInfoCount = updateInfos.Length(); 18318 18319 QM_TRY(OkIf(indexValues.SetCapacity(indexValues.Length() + updateInfoCount, 18320 fallible)), 18321 NS_ERROR_OUT_OF_MEMORY, IDB_REPORT_INTERNAL_ERR_LAMBDA); 18322 18323 // First construct the full list to update the index_data_values row. 18324 for (const IndexUpdateInfo& info : updateInfos) { 18325 MOZ_ALWAYS_TRUE(indexValues.InsertElementSorted( 18326 IndexDataValue(metadata.id(), metadata.unique(), info.value(), 18327 info.localizedValue()), 18328 fallible)); 18329 } 18330 18331 QM_TRY_UNWRAP((auto [indexValuesBlob, indexValuesBlobLength]), 18332 MakeCompressedIndexDataValues(indexValues)); 18333 18334 MOZ_ASSERT(!indexValuesBlobLength == !(indexValuesBlob.get())); 18335 18336 nsCOMPtr<nsIVariant> value; 18337 18338 if (!indexValuesBlob) { 18339 value = new storage::NullVariant(); 18340 18341 value.forget(_retval); 18342 return NS_OK; 18343 } 18344 18345 // Now insert the new table rows. We only need to construct a new list if 18346 // the full list is different. 18347 if (hadPreviousIndexValues) { 18348 indexValues.ClearAndRetainStorage(); 18349 18350 MOZ_ASSERT(indexValues.Capacity() >= updateInfoCount); 18351 18352 for (const IndexUpdateInfo& info : updateInfos) { 18353 MOZ_ALWAYS_TRUE(indexValues.InsertElementSorted( 18354 IndexDataValue(metadata.id(), metadata.unique(), info.value(), 18355 info.localizedValue()), 18356 fallible)); 18357 } 18358 } 18359 18360 QM_TRY(MOZ_TO_RESULT( 18361 InsertIndexTableRows(mConnection, objectStoreId, key, indexValues))); 18362 18363 value = new storage::AdoptedBlobVariant( 18364 std::pair(indexValuesBlob.release(), indexValuesBlobLength)); 18365 18366 value.forget(_retval); 18367 return NS_OK; 18368 } 18369 18370 DeleteIndexOp::DeleteIndexOp(SafeRefPtr<VersionChangeTransaction> aTransaction, 18371 const IndexOrObjectStoreId aObjectStoreId, 18372 const IndexOrObjectStoreId aIndexId, 18373 const bool aUnique, const bool aIsLastIndex) 18374 : VersionChangeTransactionOp(std::move(aTransaction)), 18375 mObjectStoreId(aObjectStoreId), 18376 mIndexId(aIndexId), 18377 mUnique(aUnique), 18378 mIsLastIndex(aIsLastIndex) { 18379 MOZ_ASSERT(aObjectStoreId); 18380 MOZ_ASSERT(aIndexId); 18381 } 18382 18383 nsresult DeleteIndexOp::RemoveReferencesToIndex( 18384 DatabaseConnection* aConnection, const Key& aObjectStoreKey, 18385 nsTArray<IndexDataValue>& aIndexValues) const { 18386 MOZ_ASSERT(!NS_IsMainThread()); 18387 MOZ_ASSERT(!IsOnBackgroundThread()); 18388 MOZ_ASSERT(aConnection); 18389 MOZ_ASSERT(!aObjectStoreKey.IsUnset()); 18390 MOZ_ASSERT_IF(!mIsLastIndex, !aIndexValues.IsEmpty()); 18391 18392 AUTO_PROFILER_LABEL("DeleteIndexOp::RemoveReferencesToIndex", DOM); 18393 18394 if (mIsLastIndex) { 18395 // There is no need to parse the previous entry in the index_data_values 18396 // column if this is the last index. Simply set it to NULL. 18397 QM_TRY_INSPECT(const auto& stmt, 18398 aConnection->BorrowCachedStatement( 18399 "UPDATE object_data " 18400 "SET index_data_values = NULL " 18401 "WHERE object_store_id = :"_ns + 18402 kStmtParamNameObjectStoreId + " AND key = :"_ns + 18403 kStmtParamNameKey + ";"_ns)); 18404 18405 QM_TRY(MOZ_TO_RESULT( 18406 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId))); 18407 18408 QM_TRY(MOZ_TO_RESULT( 18409 aObjectStoreKey.BindToStatement(&*stmt, kStmtParamNameKey))); 18410 18411 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 18412 18413 return NS_OK; 18414 } 18415 18416 { 18417 IndexDataValue search; 18418 search.mIndexId = mIndexId; 18419 18420 // Use raw pointers for search to avoid redundant index validity checks. 18421 // Maybe this should better be encapsulated in nsTArray. 18422 const auto* const begin = aIndexValues.Elements(); 18423 const auto* const end = aIndexValues.Elements() + aIndexValues.Length(); 18424 18425 const auto indexIdComparator = [](const IndexDataValue& aA, 18426 const IndexDataValue& aB) { 18427 return aA.mIndexId < aB.mIndexId; 18428 }; 18429 18430 MOZ_ASSERT(std::is_sorted(begin, end, indexIdComparator)); 18431 18432 const auto [beginRange, endRange] = 18433 std::equal_range(begin, end, search, indexIdComparator); 18434 if (beginRange == end) { 18435 IDB_REPORT_INTERNAL_ERR(); 18436 return NS_ERROR_FILE_CORRUPTED; 18437 } 18438 18439 aIndexValues.RemoveElementsAt(beginRange - begin, endRange - beginRange); 18440 } 18441 18442 QM_TRY(MOZ_TO_RESULT(UpdateIndexValues(aConnection, mObjectStoreId, 18443 aObjectStoreKey, aIndexValues))); 18444 18445 return NS_OK; 18446 } 18447 18448 nsresult DeleteIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) { 18449 MOZ_ASSERT(aConnection); 18450 aConnection->AssertIsOnConnectionThread(); 18451 18452 #ifdef DEBUG 18453 { 18454 // Make sure |mIsLastIndex| is telling the truth. 18455 // The parameter names are not used, parameters are bound by index only 18456 // locally in the same function. 18457 QM_TRY_INSPECT(const auto& stmt, 18458 aConnection->BorrowCachedStatement( 18459 "SELECT id " 18460 "FROM object_store_index " 18461 "WHERE object_store_id = :object_store_id;"_ns), 18462 QM_ASSERT_UNREACHABLE); 18463 18464 MOZ_ALWAYS_SUCCEEDS(stmt->BindInt64ByIndex(0, mObjectStoreId)); 18465 18466 bool foundThisIndex = false; 18467 bool foundOtherIndex = false; 18468 18469 while (true) { 18470 bool hasResult; 18471 MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult)); 18472 18473 if (!hasResult) { 18474 break; 18475 } 18476 18477 int64_t id; 18478 MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id)); 18479 18480 if (id == mIndexId) { 18481 foundThisIndex = true; 18482 } else { 18483 foundOtherIndex = true; 18484 } 18485 } 18486 18487 MOZ_ASSERT_IF(mIsLastIndex, foundThisIndex && !foundOtherIndex); 18488 MOZ_ASSERT_IF(!mIsLastIndex, foundThisIndex && foundOtherIndex); 18489 } 18490 #endif 18491 18492 AUTO_PROFILER_LABEL("DeleteIndexOp::DoDatabaseWork", DOM); 18493 18494 DatabaseConnection::AutoSavepoint autoSave; 18495 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 18496 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 18497 , 18498 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 18499 #endif 18500 ); 18501 18502 // mozStorage warns that these statements trigger a sort operation but we 18503 // don't care because this is a very rare call and we expect it to be slow. 18504 // The cost of having an index on this field is too high. 18505 QM_TRY_INSPECT( 18506 const auto& selectStmt, 18507 aConnection->BorrowCachedStatement( 18508 mUnique 18509 ? (mIsLastIndex 18510 ? "/* do not warn (bug someone else) */ " 18511 "SELECT value, object_data_key " 18512 "FROM unique_index_data " 18513 "WHERE index_id = :"_ns + 18514 kStmtParamNameIndexId + 18515 " ORDER BY object_data_key ASC;"_ns 18516 : "/* do not warn (bug out) */ " 18517 "SELECT unique_index_data.value, " 18518 "unique_index_data.object_data_key, " 18519 "object_data.index_data_values " 18520 "FROM unique_index_data " 18521 "JOIN object_data " 18522 "ON unique_index_data.object_data_key = object_data.key " 18523 "WHERE unique_index_data.index_id = :"_ns + 18524 kStmtParamNameIndexId + 18525 " AND object_data.object_store_id = :"_ns + 18526 kStmtParamNameObjectStoreId + 18527 " ORDER BY unique_index_data.object_data_key ASC;"_ns) 18528 : (mIsLastIndex 18529 ? "/* do not warn (bug me not) */ " 18530 "SELECT value, object_data_key " 18531 "FROM index_data " 18532 "WHERE index_id = :"_ns + 18533 kStmtParamNameIndexId + 18534 " AND object_store_id = :"_ns + 18535 kStmtParamNameObjectStoreId + 18536 " ORDER BY object_data_key ASC;"_ns 18537 : "/* do not warn (bug off) */ " 18538 "SELECT index_data.value, " 18539 "index_data.object_data_key, " 18540 "object_data.index_data_values " 18541 "FROM index_data " 18542 "JOIN object_data " 18543 "ON index_data.object_data_key = object_data.key " 18544 "WHERE index_data.index_id = :"_ns + 18545 kStmtParamNameIndexId + 18546 " AND object_data.object_store_id = :"_ns + 18547 kStmtParamNameObjectStoreId + 18548 " ORDER BY index_data.object_data_key ASC;"_ns))); 18549 18550 QM_TRY(MOZ_TO_RESULT( 18551 selectStmt->BindInt64ByName(kStmtParamNameIndexId, mIndexId))); 18552 18553 if (!mUnique || !mIsLastIndex) { 18554 QM_TRY(MOZ_TO_RESULT(selectStmt->BindInt64ByName( 18555 kStmtParamNameObjectStoreId, mObjectStoreId))); 18556 } 18557 18558 Key lastObjectStoreKey; 18559 IndexDataValuesAutoArray lastIndexValues; 18560 18561 QM_TRY(CollectWhileHasResult( 18562 *selectStmt, 18563 [this, &aConnection, &lastObjectStoreKey, &lastIndexValues, 18564 deleteIndexRowStmt = 18565 DatabaseConnection::LazyStatement{ 18566 *aConnection, 18567 mUnique 18568 ? "DELETE FROM unique_index_data " 18569 "WHERE index_id = :"_ns + 18570 kStmtParamNameIndexId + " AND value = :"_ns + 18571 kStmtParamNameValue + ";"_ns 18572 : "DELETE FROM index_data " 18573 "WHERE index_id = :"_ns + 18574 kStmtParamNameIndexId + " AND value = :"_ns + 18575 kStmtParamNameValue + " AND object_data_key = :"_ns + 18576 kStmtParamNameObjectDataKey + ";"_ns}]( 18577 auto& selectStmt) mutable -> Result<Ok, nsresult> { 18578 // We always need the index key to delete the index row. 18579 Key indexKey; 18580 QM_TRY(MOZ_TO_RESULT(indexKey.SetFromStatement(&selectStmt, 0))); 18581 18582 QM_TRY(OkIf(!indexKey.IsUnset()), Err(NS_ERROR_FILE_CORRUPTED), 18583 IDB_REPORT_INTERNAL_ERR_LAMBDA); 18584 18585 // Don't call |lastObjectStoreKey.BindToStatement()| directly because we 18586 // don't want to copy the same key multiple times. 18587 const uint8_t* objectStoreKeyData; 18588 uint32_t objectStoreKeyDataLength; 18589 QM_TRY(MOZ_TO_RESULT(selectStmt.GetSharedBlob( 18590 1, &objectStoreKeyDataLength, &objectStoreKeyData))); 18591 18592 QM_TRY(OkIf(objectStoreKeyDataLength), Err(NS_ERROR_FILE_CORRUPTED), 18593 IDB_REPORT_INTERNAL_ERR_LAMBDA); 18594 18595 const nsDependentCString currentObjectStoreKeyBuffer( 18596 reinterpret_cast<const char*>(objectStoreKeyData), 18597 objectStoreKeyDataLength); 18598 if (currentObjectStoreKeyBuffer != lastObjectStoreKey.GetBuffer()) { 18599 // We just walked to the next object store key. 18600 if (!lastObjectStoreKey.IsUnset()) { 18601 // Before we move on to the next key we need to update the previous 18602 // key's index_data_values column. 18603 QM_TRY(MOZ_TO_RESULT(RemoveReferencesToIndex( 18604 aConnection, lastObjectStoreKey, lastIndexValues))); 18605 } 18606 18607 // Save the object store key. 18608 lastObjectStoreKey = Key(currentObjectStoreKeyBuffer); 18609 18610 // And the |index_data_values| row if this isn't the only index. 18611 if (!mIsLastIndex) { 18612 lastIndexValues.ClearAndRetainStorage(); 18613 QM_TRY(MOZ_TO_RESULT( 18614 ReadCompressedIndexDataValues(selectStmt, 2, lastIndexValues))); 18615 18616 QM_TRY(OkIf(!lastIndexValues.IsEmpty()), 18617 Err(NS_ERROR_FILE_CORRUPTED), 18618 IDB_REPORT_INTERNAL_ERR_LAMBDA); 18619 } 18620 } 18621 18622 // Now delete the index row. 18623 { 18624 QM_TRY_INSPECT(const auto& borrowedDeleteIndexRowStmt, 18625 deleteIndexRowStmt.Borrow()); 18626 18627 QM_TRY(MOZ_TO_RESULT(borrowedDeleteIndexRowStmt->BindInt64ByName( 18628 kStmtParamNameIndexId, mIndexId))); 18629 18630 QM_TRY(MOZ_TO_RESULT(indexKey.BindToStatement( 18631 &*borrowedDeleteIndexRowStmt, kStmtParamNameValue))); 18632 18633 if (!mUnique) { 18634 QM_TRY(MOZ_TO_RESULT(lastObjectStoreKey.BindToStatement( 18635 &*borrowedDeleteIndexRowStmt, kStmtParamNameObjectDataKey))); 18636 } 18637 18638 QM_TRY(MOZ_TO_RESULT(borrowedDeleteIndexRowStmt->Execute())); 18639 } 18640 18641 return Ok{}; 18642 })); 18643 18644 // Take care of the last key. 18645 if (!lastObjectStoreKey.IsUnset()) { 18646 MOZ_ASSERT_IF(!mIsLastIndex, !lastIndexValues.IsEmpty()); 18647 18648 QM_TRY(MOZ_TO_RESULT(RemoveReferencesToIndex( 18649 aConnection, lastObjectStoreKey, lastIndexValues))); 18650 } 18651 18652 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 18653 "DELETE FROM object_store_index " 18654 "WHERE id = :index_id;"_ns, 18655 [indexId = 18656 mIndexId](mozIStorageStatement& deleteStmt) -> Result<Ok, nsresult> { 18657 QM_TRY(MOZ_TO_RESULT(deleteStmt.BindInt64ByIndex(0, indexId))); 18658 18659 return Ok{}; 18660 }))); 18661 18662 #ifdef DEBUG 18663 { 18664 int32_t deletedRowCount; 18665 MOZ_ALWAYS_SUCCEEDS(aConnection->MutableStorageConnection().GetAffectedRows( 18666 &deletedRowCount)); 18667 MOZ_ASSERT(deletedRowCount == 1); 18668 } 18669 #endif 18670 18671 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 18672 18673 return NS_OK; 18674 } 18675 18676 nsresult RenameIndexOp::DoDatabaseWork(DatabaseConnection* aConnection) { 18677 MOZ_ASSERT(aConnection); 18678 aConnection->AssertIsOnConnectionThread(); 18679 18680 AUTO_PROFILER_LABEL("RenameIndexOp::DoDatabaseWork", DOM); 18681 18682 #ifdef DEBUG 18683 { 18684 // Make sure that we're not renaming an index with the same name as another 18685 // that already exists. This should be impossible because we should have 18686 // thrown an error long before now... 18687 // The parameter names are not used, parameters are bound by index only 18688 // locally in the same function. 18689 QM_TRY_INSPECT(const bool& hasResult, 18690 aConnection 18691 ->BorrowAndExecuteSingleStepStatement( 18692 "SELECT name " 18693 "FROM object_store_index " 18694 "WHERE object_store_id = :object_store_id " 18695 "AND name = :name " 18696 "AND id != :id;"_ns, 18697 [&self = *this](auto& stmt) -> Result<Ok, nsresult> { 18698 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex( 18699 0, self.mObjectStoreId))); 18700 QM_TRY(MOZ_TO_RESULT( 18701 stmt.BindStringByIndex(1, self.mNewName))); 18702 QM_TRY(MOZ_TO_RESULT( 18703 stmt.BindInt64ByIndex(2, self.mIndexId))); 18704 18705 return Ok{}; 18706 }) 18707 .map(IsSome), 18708 QM_ASSERT_UNREACHABLE); 18709 18710 MOZ_ASSERT(!hasResult); 18711 } 18712 #else 18713 (void)mObjectStoreId; 18714 #endif 18715 18716 DatabaseConnection::AutoSavepoint autoSave; 18717 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 18718 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 18719 , 18720 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 18721 #endif 18722 ); 18723 18724 // The parameter names are not used, parameters are bound by index only 18725 // locally in the same function. 18726 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 18727 "UPDATE object_store_index " 18728 "SET name = :name " 18729 "WHERE id = :id;"_ns, 18730 [&self = *this](mozIStorageStatement& stmt) -> Result<Ok, nsresult> { 18731 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByIndex(0, self.mNewName))); 18732 18733 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByIndex(1, self.mIndexId))); 18734 18735 return Ok{}; 18736 }))); 18737 18738 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 18739 18740 return NS_OK; 18741 } 18742 18743 Result<bool, nsresult> NormalTransactionOp::ObjectStoreHasIndexes( 18744 DatabaseConnection& aConnection, const IndexOrObjectStoreId aObjectStoreId, 18745 const bool aMayHaveIndexes) { 18746 aConnection.AssertIsOnConnectionThread(); 18747 MOZ_ASSERT(aObjectStoreId); 18748 18749 if (Transaction().GetMode() == IDBTransaction::Mode::VersionChange && 18750 aMayHaveIndexes) { 18751 // If this is a version change transaction then mObjectStoreMayHaveIndexes 18752 // could be wrong (e.g. if a unique index failed to be created due to a 18753 // constraint error). We have to check on this thread by asking the database 18754 // directly. 18755 QM_TRY_RETURN(DatabaseOperationBase::ObjectStoreHasIndexes(aConnection, 18756 aObjectStoreId)); 18757 } 18758 18759 #ifdef DEBUG 18760 QM_TRY_INSPECT( 18761 const bool& hasIndexes, 18762 DatabaseOperationBase::ObjectStoreHasIndexes(aConnection, aObjectStoreId), 18763 QM_ASSERT_UNREACHABLE); 18764 MOZ_ASSERT(aMayHaveIndexes == hasIndexes); 18765 #endif 18766 18767 return aMayHaveIndexes; 18768 } 18769 18770 Result<PreprocessParams, nsresult> NormalTransactionOp::GetPreprocessParams() { 18771 return PreprocessParams{}; 18772 } 18773 18774 nsresult NormalTransactionOp::SendPreprocessInfo() { 18775 AssertIsOnOwningThread(); 18776 MOZ_ASSERT(!IsActorDestroyed()); 18777 18778 QM_TRY_INSPECT(const auto& params, GetPreprocessParams()); 18779 18780 MOZ_ASSERT(params.type() != PreprocessParams::T__None); 18781 18782 if (NS_WARN_IF(!PBackgroundIDBRequestParent::SendPreprocess(params))) { 18783 IDB_REPORT_INTERNAL_ERR(); 18784 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 18785 } 18786 18787 return NS_OK; 18788 } 18789 18790 nsresult NormalTransactionOp::SendSuccessResult() { 18791 AssertIsOnOwningThread(); 18792 18793 if (!IsActorDestroyed()) { 18794 static const size_t kMaxIDBMsgOverhead = 1024 * 1024 * 10; // 10MB 18795 const uint32_t maximalSizeFromPref = 18796 IndexedDatabaseManager::MaxSerializedMsgSize(); 18797 MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead); 18798 const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead; 18799 18800 RequestResponse response; 18801 size_t responseSize = kMaxMessageSize; 18802 GetResponse(response, &responseSize); 18803 18804 // TODO: Adjust the calculation of the response size in relevant 18805 // GetResponse methods to account for the fallback to shared memory during 18806 // serialization of the primary key and index keys if their size exceeds 18807 // IPC::kMessageBufferShmemThreshold. This ensures the calculated size 18808 // accurately reflects the actual IPC message size. 18809 // See also bug 1945040. 18810 18811 if (responseSize >= kMaxMessageSize) { 18812 nsPrintfCString warning( 18813 "The serialized value is too large" 18814 " (size=%zu bytes, max=%zu bytes).", 18815 responseSize, kMaxMessageSize); 18816 NS_WARNING(warning.get()); 18817 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 18818 } 18819 18820 MOZ_ASSERT(response.type() != RequestResponse::T__None); 18821 18822 if (response.type() == RequestResponse::Tnsresult) { 18823 MOZ_ASSERT(NS_FAILED(response.get_nsresult())); 18824 18825 return response.get_nsresult(); 18826 } 18827 18828 if (NS_WARN_IF( 18829 !PBackgroundIDBRequestParent::Send__delete__(this, response))) { 18830 IDB_REPORT_INTERNAL_ERR(); 18831 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 18832 } 18833 } 18834 18835 #ifdef DEBUG 18836 mResponseSent = true; 18837 #endif 18838 18839 return NS_OK; 18840 } 18841 18842 bool NormalTransactionOp::SendFailureResult(nsresult aResultCode) { 18843 AssertIsOnOwningThread(); 18844 MOZ_ASSERT(NS_FAILED(aResultCode)); 18845 18846 bool result = false; 18847 18848 if (!IsActorDestroyed()) { 18849 result = PBackgroundIDBRequestParent::Send__delete__( 18850 this, ClampResultCode(aResultCode)); 18851 } 18852 18853 #ifdef DEBUG 18854 mResponseSent = true; 18855 #endif 18856 18857 return result; 18858 } 18859 18860 void NormalTransactionOp::Cleanup() { 18861 AssertIsOnOwningThread(); 18862 MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent); 18863 18864 TransactionDatabaseOperationBase::Cleanup(); 18865 } 18866 18867 void NormalTransactionOp::ActorDestroy(ActorDestroyReason aWhy) { 18868 AssertIsOnOwningThread(); 18869 18870 NoteActorDestroyed(); 18871 18872 // Assume ActorDestroy can happen at any time, so we can't probe the current 18873 // state since mInternalState can be modified on any thread (only one thread 18874 // at a time based on the state machine). 18875 // However we can use mWaitingForContinue which is only touched on the owning 18876 // thread. If mWaitingForContinue is true, we can also modify mInternalState 18877 // since we are guaranteed that there are no pending runnables which would 18878 // probe mInternalState to decide what code needs to run (there shouldn't be 18879 // any running runnables on other threads either). 18880 18881 if (IsWaitingForContinue()) { 18882 NoteContinueReceived(); 18883 } 18884 18885 // We don't have to handle the case when mWaitingForContinue is not true since 18886 // it means that either nothing has been initialized yet, so nothing to 18887 // cleanup or there are pending runnables that will detect that the actor has 18888 // been destroyed and cleanup accordingly. 18889 } 18890 18891 mozilla::ipc::IPCResult NormalTransactionOp::RecvContinue( 18892 const PreprocessResponse& aResponse) { 18893 AssertIsOnOwningThread(); 18894 18895 switch (aResponse.type()) { 18896 case PreprocessResponse::Tnsresult: 18897 SetFailureCode(aResponse.get_nsresult()); 18898 break; 18899 18900 case PreprocessResponse::TObjectStoreGetPreprocessResponse: 18901 case PreprocessResponse::TObjectStoreGetAllPreprocessResponse: 18902 break; 18903 18904 default: 18905 MOZ_CRASH("Should never get here!"); 18906 } 18907 18908 NoteContinueReceived(); 18909 18910 return IPC_OK(); 18911 } 18912 18913 ObjectStoreAddOrPutRequestOp::ObjectStoreAddOrPutRequestOp( 18914 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, 18915 RequestParams&& aParams) 18916 : NormalTransactionOp(std::move(aTransaction), aRequestId), 18917 mParams( 18918 std::move(aParams.type() == RequestParams::TObjectStoreAddParams 18919 ? aParams.get_ObjectStoreAddParams().commonParams() 18920 : aParams.get_ObjectStorePutParams().commonParams())), 18921 mOriginMetadata(Transaction().GetDatabase().OriginMetadata()), 18922 mPersistenceType(Transaction().GetDatabase().Type()), 18923 mOverwrite(aParams.type() == RequestParams::TObjectStorePutParams), 18924 mObjectStoreMayHaveIndexes(false) { 18925 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreAddParams || 18926 aParams.type() == RequestParams::TObjectStorePutParams); 18927 18928 mMetadata = 18929 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId()); 18930 MOZ_ASSERT(mMetadata); 18931 18932 mObjectStoreMayHaveIndexes = mMetadata->HasLiveIndexes(); 18933 18934 mDataOverThreshold = 18935 snappy::MaxCompressedLength(mParams.cloneInfo().data().data.Size()) > 18936 IndexedDatabaseManager::DataThreshold(); 18937 } 18938 18939 nsresult ObjectStoreAddOrPutRequestOp::RemoveOldIndexDataValues( 18940 DatabaseConnection* aConnection) { 18941 AssertIsOnConnectionThread(); 18942 MOZ_ASSERT(aConnection); 18943 MOZ_ASSERT(mOverwrite); 18944 MOZ_ASSERT(!mResponse.IsUnset()); 18945 18946 #ifdef DEBUG 18947 { 18948 QM_TRY_INSPECT(const bool& hasIndexes, 18949 DatabaseOperationBase::ObjectStoreHasIndexes( 18950 *aConnection, mParams.objectStoreId()), 18951 QM_ASSERT_UNREACHABLE); 18952 18953 MOZ_ASSERT(hasIndexes, 18954 "Don't use this slow method if there are no indexes!"); 18955 } 18956 #endif 18957 18958 QM_TRY_INSPECT( 18959 const auto& indexValuesStmt, 18960 aConnection->BorrowAndExecuteSingleStepStatement( 18961 "SELECT index_data_values " 18962 "FROM object_data " 18963 "WHERE object_store_id = :"_ns + 18964 kStmtParamNameObjectStoreId + " AND key = :"_ns + 18965 kStmtParamNameKey + ";"_ns, 18966 [&self = *this](auto& stmt) -> mozilla::Result<Ok, nsresult> { 18967 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName( 18968 kStmtParamNameObjectStoreId, self.mParams.objectStoreId()))); 18969 18970 QM_TRY(MOZ_TO_RESULT( 18971 self.mResponse.BindToStatement(&stmt, kStmtParamNameKey))); 18972 18973 return Ok{}; 18974 })); 18975 18976 if (indexValuesStmt) { 18977 QM_TRY_INSPECT(const auto& existingIndexValues, 18978 ReadCompressedIndexDataValues(**indexValuesStmt, 0)); 18979 18980 QM_TRY(MOZ_TO_RESULT( 18981 DeleteIndexDataTableRows(aConnection, mResponse, existingIndexValues))); 18982 } 18983 18984 return NS_OK; 18985 } 18986 18987 bool ObjectStoreAddOrPutRequestOp::Init(TransactionBase& aTransaction) { 18988 AssertIsOnOwningThread(); 18989 18990 const nsTArray<IndexUpdateInfo>& indexUpdateInfos = 18991 mParams.indexUpdateInfos(); 18992 18993 if (!indexUpdateInfos.IsEmpty()) { 18994 mUniqueIndexTable.emplace(); 18995 18996 for (const auto& updateInfo : indexUpdateInfos) { 18997 auto indexMetadata = mMetadata->mIndexes.Lookup(updateInfo.indexId()); 18998 MOZ_ALWAYS_TRUE(indexMetadata); 18999 19000 MOZ_ASSERT(!(*indexMetadata)->mDeleted); 19001 19002 const IndexOrObjectStoreId& indexId = 19003 (*indexMetadata)->mCommonMetadata.id(); 19004 const bool& unique = (*indexMetadata)->mCommonMetadata.unique(); 19005 19006 MOZ_ASSERT(indexId == updateInfo.indexId()); 19007 MOZ_ASSERT_IF(!(*indexMetadata)->mCommonMetadata.multiEntry(), 19008 !mUniqueIndexTable.ref().Contains(indexId)); 19009 19010 if (NS_WARN_IF(!mUniqueIndexTable.ref().InsertOrUpdate(indexId, unique, 19011 fallible))) { 19012 return false; 19013 } 19014 } 19015 } else if (mOverwrite) { 19016 mUniqueIndexTable.emplace(); 19017 } 19018 19019 if (mUniqueIndexTable.isSome()) { 19020 mUniqueIndexTable.ref().MarkImmutable(); 19021 } 19022 19023 QM_TRY_UNWRAP( 19024 mStoredFileInfos, 19025 TransformIntoNewArray( 19026 mParams.fileAddInfos(), 19027 [](const auto& fileAddInfo) { 19028 MOZ_ASSERT(fileAddInfo.type() == StructuredCloneFileBase::eBlob || 19029 fileAddInfo.type() == 19030 StructuredCloneFileBase::eMutableFile); 19031 19032 switch (fileAddInfo.type()) { 19033 case StructuredCloneFileBase::eBlob: { 19034 PBackgroundIDBDatabaseFileParent* file = 19035 fileAddInfo.file().AsParent(); 19036 MOZ_ASSERT(file); 19037 19038 auto* const fileActor = static_cast<DatabaseFile*>(file); 19039 MOZ_ASSERT(fileActor); 19040 19041 return StoredFileInfo::CreateForBlob( 19042 fileActor->GetFileInfoPtr(), fileActor); 19043 } 19044 19045 default: 19046 MOZ_CRASH("Should never get here!"); 19047 } 19048 }, 19049 fallible), 19050 false); 19051 19052 if (mDataOverThreshold) { 19053 auto fileInfo = 19054 aTransaction.GetDatabase().GetFileManager().CreateFileInfo(); 19055 if (NS_WARN_IF(!fileInfo)) { 19056 return false; 19057 } 19058 19059 mStoredFileInfos.EmplaceBack(StoredFileInfo::CreateForStructuredClone( 19060 std::move(fileInfo), 19061 MakeRefPtr<SCInputStream>(mParams.cloneInfo().data().data))); 19062 } 19063 19064 return true; 19065 } 19066 19067 nsresult ObjectStoreAddOrPutRequestOp::DoDatabaseWork( 19068 DatabaseConnection* aConnection) { 19069 MOZ_ASSERT(aConnection); 19070 aConnection->AssertIsOnConnectionThread(); 19071 MOZ_ASSERT(aConnection->HasStorageConnection()); 19072 19073 AUTO_PROFILER_LABEL("ObjectStoreAddOrPutRequestOp::DoDatabaseWork", DOM); 19074 19075 DatabaseConnection::AutoSavepoint autoSave; 19076 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 19077 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 19078 , 19079 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 19080 #endif 19081 ); 19082 19083 QM_TRY_INSPECT(const bool& objectStoreHasIndexes, 19084 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(), 19085 mObjectStoreMayHaveIndexes)); 19086 19087 // This will be the final key we use. 19088 Key& key = mResponse; 19089 key = mParams.key(); 19090 19091 const bool keyUnset = key.IsUnset(); 19092 const IndexOrObjectStoreId osid = mParams.objectStoreId(); 19093 19094 // First delete old index_data_values if we're overwriting something and we 19095 // have indexes. 19096 if (mOverwrite && !keyUnset && objectStoreHasIndexes) { 19097 QM_TRY(MOZ_TO_RESULT(RemoveOldIndexDataValues(aConnection))); 19098 } 19099 19100 int64_t autoIncrementNum = 0; 19101 19102 { 19103 // The "|| keyUnset" here is mostly a debugging tool. If a key isn't 19104 // specified we should never have a collision and so it shouldn't matter 19105 // if we allow overwrite or not. By not allowing overwrite we raise 19106 // detectable errors rather than corrupting data. 19107 const auto optReplaceDirective = 19108 (!mOverwrite || keyUnset) ? ""_ns : "OR REPLACE "_ns; 19109 QM_TRY_INSPECT(const auto& stmt, 19110 aConnection->BorrowCachedStatement( 19111 "INSERT "_ns + optReplaceDirective + 19112 "INTO object_data " 19113 "(object_store_id, key, file_ids, data) " 19114 "VALUES (:"_ns + 19115 kStmtParamNameObjectStoreId + ", :"_ns + 19116 kStmtParamNameKey + ", :"_ns + kStmtParamNameFileIds + 19117 ", :"_ns + kStmtParamNameData + ");"_ns)); 19118 19119 QM_TRY(MOZ_TO_RESULT( 19120 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, osid))); 19121 19122 const SerializedStructuredCloneWriteInfo& cloneInfo = mParams.cloneInfo(); 19123 const JSStructuredCloneData& cloneData = cloneInfo.data().data; 19124 const size_t cloneDataSize = cloneData.Size(); 19125 19126 MOZ_ASSERT(!keyUnset || mMetadata->mCommonMetadata.autoIncrement(), 19127 "Should have key unless autoIncrement"); 19128 19129 if (mMetadata->mCommonMetadata.autoIncrement()) { 19130 if (keyUnset) { 19131 { 19132 const auto&& lockedAutoIncrementIds = 19133 mMetadata->mAutoIncrementIds.Lock(); 19134 19135 autoIncrementNum = lockedAutoIncrementIds->next; 19136 } 19137 19138 MOZ_ASSERT(autoIncrementNum > 0); 19139 19140 if (autoIncrementNum > (1LL << 53)) { 19141 return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; 19142 } 19143 19144 QM_TRY(key.SetFromInteger(autoIncrementNum)); 19145 19146 // Update index keys if primary key is preserved in child. 19147 for (auto& updateInfo : mParams.indexUpdateInfos()) { 19148 updateInfo.value().MaybeUpdateAutoIncrementKey(autoIncrementNum); 19149 } 19150 } else if (key.IsFloat()) { 19151 double numericKey = key.ToFloat(); 19152 numericKey = std::min(numericKey, double(1LL << 53)); 19153 numericKey = floor(numericKey); 19154 19155 const auto&& lockedAutoIncrementIds = 19156 mMetadata->mAutoIncrementIds.Lock(); 19157 if (numericKey >= lockedAutoIncrementIds->next) { 19158 autoIncrementNum = numericKey; 19159 } 19160 } 19161 19162 if (keyUnset && mMetadata->mCommonMetadata.keyPath().IsValid()) { 19163 const SerializedStructuredCloneWriteInfo& cloneInfo = 19164 mParams.cloneInfo(); 19165 MOZ_ASSERT(cloneInfo.offsetToKeyProp()); 19166 MOZ_ASSERT(cloneDataSize > sizeof(uint64_t)); 19167 MOZ_ASSERT(cloneInfo.offsetToKeyProp() <= 19168 (cloneDataSize - sizeof(uint64_t))); 19169 19170 // Special case where someone put an object into an autoIncrement'ing 19171 // objectStore with no key in its keyPath set. We needed to figure out 19172 // which row id we would get above before we could set that properly. 19173 uint64_t keyPropValue = 19174 ReinterpretDoubleAsUInt64(static_cast<double>(autoIncrementNum)); 19175 19176 static const size_t keyPropSize = sizeof(uint64_t); 19177 19178 char keyPropBuffer[keyPropSize]; 19179 LittleEndian::writeUint64(keyPropBuffer, keyPropValue); 19180 19181 auto iter = cloneData.Start(); 19182 MOZ_ALWAYS_TRUE(cloneData.Advance(iter, cloneInfo.offsetToKeyProp())); 19183 MOZ_ALWAYS_TRUE( 19184 cloneData.UpdateBytes(iter, keyPropBuffer, keyPropSize)); 19185 } 19186 } 19187 19188 key.BindToStatement(&*stmt, kStmtParamNameKey); 19189 19190 if (mDataOverThreshold) { 19191 // The data we store in the SQLite database is a (signed) 64-bit integer. 19192 // The flags are left-shifted 32 bits so the max value is 0xFFFFFFFF. 19193 // The file_ids index occupies the lower 32 bits and its max is 19194 // 0xFFFFFFFF. 19195 static const uint32_t kCompressedFlag = (1 << 0); 19196 19197 uint32_t flags = 0; 19198 flags |= kCompressedFlag; 19199 19200 const uint32_t index = mStoredFileInfos.Length() - 1; 19201 19202 const int64_t data = (uint64_t(flags) << 32) | index; 19203 19204 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameData, data))); 19205 } else { 19206 AutoTArray<char, 4096> flatCloneData; // 4096 from JSStructuredCloneData 19207 QM_TRY(OkIf(flatCloneData.SetLength(cloneDataSize, fallible)), 19208 Err(NS_ERROR_OUT_OF_MEMORY)); 19209 19210 { 19211 auto iter = cloneData.Start(); 19212 MOZ_ALWAYS_TRUE( 19213 cloneData.ReadBytes(iter, flatCloneData.Elements(), cloneDataSize)); 19214 } 19215 19216 // Compress the bytes before adding into the database. 19217 const char* const uncompressed = flatCloneData.Elements(); 19218 const size_t uncompressedLength = cloneDataSize; 19219 19220 size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength); 19221 19222 UniqueFreePtr<char> compressed( 19223 static_cast<char*>(malloc(compressedLength))); 19224 if (NS_WARN_IF(!compressed)) { 19225 return NS_ERROR_OUT_OF_MEMORY; 19226 } 19227 19228 snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(), 19229 &compressedLength); 19230 19231 uint8_t* const dataBuffer = 19232 reinterpret_cast<uint8_t*>(compressed.release()); 19233 const size_t dataBufferLength = compressedLength; 19234 19235 QM_TRY(MOZ_TO_RESULT(stmt->BindAdoptedBlobByName( 19236 kStmtParamNameData, dataBuffer, dataBufferLength))); 19237 } 19238 19239 if (!mStoredFileInfos.IsEmpty()) { 19240 // Moved outside the loop to allow it to be cached when demanded by the 19241 // first write. (We may have mStoredFileInfos without any required 19242 // writes.) 19243 Maybe<FileHelper> fileHelper; 19244 nsAutoString fileIds; 19245 19246 for (auto& storedFileInfo : mStoredFileInfos) { 19247 MOZ_ASSERT(storedFileInfo.IsValid()); 19248 19249 QM_TRY_INSPECT(const auto& inputStream, 19250 storedFileInfo.GetInputStream()); 19251 19252 if (inputStream) { 19253 if (fileHelper.isNothing()) { 19254 fileHelper.emplace(Transaction().GetDatabase().GetFileManagerPtr()); 19255 QM_TRY(MOZ_TO_RESULT(fileHelper->Init()), 19256 NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, 19257 IDB_REPORT_INTERNAL_ERR_LAMBDA); 19258 } 19259 19260 const DatabaseFileInfo& fileInfo = storedFileInfo.GetFileInfo(); 19261 const DatabaseFileManager& fileManager = fileInfo.Manager(); 19262 19263 const auto file = fileHelper->GetFile(fileInfo); 19264 QM_TRY(OkIf(file), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, 19265 IDB_REPORT_INTERNAL_ERR_LAMBDA); 19266 19267 const auto journalFile = fileHelper->GetJournalFile(fileInfo); 19268 QM_TRY(OkIf(journalFile), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, 19269 IDB_REPORT_INTERNAL_ERR_LAMBDA); 19270 19271 nsCString fileKeyId; 19272 fileKeyId.AppendInt(fileInfo.Id()); 19273 19274 const auto maybeKey = 19275 fileManager.IsInPrivateBrowsingMode() 19276 ? fileManager.MutableCipherKeyManagerRef().Get(fileKeyId) 19277 : Nothing(); 19278 19279 QM_TRY(MOZ_TO_RESULT(fileHelper->CreateFileFromStream( 19280 *file, *journalFile, *inputStream, 19281 storedFileInfo.ShouldCompress(), maybeKey)) 19282 .mapErr([](const nsresult rv) { 19283 if (NS_ERROR_GET_MODULE(rv) != 19284 NS_ERROR_MODULE_DOM_INDEXEDDB) { 19285 IDB_REPORT_INTERNAL_ERR(); 19286 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; 19287 } 19288 return rv; 19289 }), 19290 QM_PROPAGATE, 19291 ([&fileManager, &file = *file, 19292 &journalFile = *journalFile](const auto) { 19293 // Try to remove the file if the copy failed. 19294 QM_TRY(MOZ_TO_RESULT( 19295 fileManager.SyncDeleteFile(file, journalFile)), 19296 QM_VOID); 19297 })); 19298 19299 storedFileInfo.NotifyWriteSucceeded(); 19300 } 19301 19302 if (!fileIds.IsEmpty()) { 19303 fileIds.Append(' '); 19304 } 19305 storedFileInfo.Serialize(fileIds); 19306 } 19307 19308 QM_TRY(MOZ_TO_RESULT( 19309 stmt->BindStringByName(kStmtParamNameFileIds, fileIds))); 19310 } else { 19311 QM_TRY(MOZ_TO_RESULT(stmt->BindNullByName(kStmtParamNameFileIds))); 19312 } 19313 19314 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_PROPAGATE, 19315 [keyUnset = DebugOnly{keyUnset}](const nsresult rv) { 19316 if (rv == NS_ERROR_STORAGE_CONSTRAINT) { 19317 MOZ_ASSERT(!keyUnset, "Generated key had a collision!"); 19318 } 19319 }); 19320 } 19321 19322 // Update our indexes if needed. 19323 if (!mParams.indexUpdateInfos().IsEmpty()) { 19324 MOZ_ASSERT(mUniqueIndexTable.isSome()); 19325 19326 // Write the index_data_values column. 19327 QM_TRY_INSPECT(const auto& indexValues, 19328 IndexDataValuesFromUpdateInfos(mParams.indexUpdateInfos(), 19329 mUniqueIndexTable.ref())); 19330 19331 QM_TRY( 19332 MOZ_TO_RESULT(UpdateIndexValues(aConnection, osid, key, indexValues))); 19333 19334 QM_TRY(MOZ_TO_RESULT( 19335 InsertIndexTableRows(aConnection, osid, key, indexValues))); 19336 } 19337 19338 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 19339 19340 if (autoIncrementNum) { 19341 { 19342 auto&& lockedAutoIncrementIds = mMetadata->mAutoIncrementIds.Lock(); 19343 19344 lockedAutoIncrementIds->next = autoIncrementNum + 1; 19345 } 19346 19347 Transaction().NoteModifiedAutoIncrementObjectStore(mMetadata); 19348 } 19349 19350 return NS_OK; 19351 } 19352 19353 void ObjectStoreAddOrPutRequestOp::GetResponse(RequestResponse& aResponse, 19354 size_t* aResponseSize) { 19355 AssertIsOnOwningThread(); 19356 19357 if (mOverwrite) { 19358 aResponse = ObjectStorePutResponse(mResponse); 19359 *aResponseSize = mResponse.GetBuffer().Length(); 19360 } else { 19361 aResponse = ObjectStoreAddResponse(mResponse); 19362 *aResponseSize = mResponse.GetBuffer().Length(); 19363 } 19364 } 19365 19366 void ObjectStoreAddOrPutRequestOp::Cleanup() { 19367 AssertIsOnOwningThread(); 19368 19369 mStoredFileInfos.Clear(); 19370 19371 NormalTransactionOp::Cleanup(); 19372 } 19373 19374 NS_IMPL_ISUPPORTS(ObjectStoreAddOrPutRequestOp::SCInputStream, nsIInputStream) 19375 19376 NS_IMETHODIMP 19377 ObjectStoreAddOrPutRequestOp::SCInputStream::Close() { return NS_OK; } 19378 19379 NS_IMETHODIMP 19380 ObjectStoreAddOrPutRequestOp::SCInputStream::Available(uint64_t* _retval) { 19381 return NS_ERROR_NOT_IMPLEMENTED; 19382 } 19383 19384 NS_IMETHODIMP 19385 ObjectStoreAddOrPutRequestOp::SCInputStream::StreamStatus() { return NS_OK; } 19386 19387 NS_IMETHODIMP 19388 ObjectStoreAddOrPutRequestOp::SCInputStream::Read(char* aBuf, uint32_t aCount, 19389 uint32_t* _retval) { 19390 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); 19391 } 19392 19393 NS_IMETHODIMP 19394 ObjectStoreAddOrPutRequestOp::SCInputStream::ReadSegments( 19395 nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, 19396 uint32_t* _retval) { 19397 *_retval = 0; 19398 19399 while (aCount) { 19400 uint32_t count = std::min(uint32_t(mIter.RemainingInSegment()), aCount); 19401 if (!count) { 19402 // We've run out of data in the last segment. 19403 break; 19404 } 19405 19406 uint32_t written; 19407 nsresult rv = 19408 aWriter(this, aClosure, mIter.Data(), *_retval, count, &written); 19409 if (NS_WARN_IF(NS_FAILED(rv))) { 19410 // InputStreams do not propagate errors to caller. 19411 return NS_OK; 19412 } 19413 19414 // Writer should write what we asked it to write. 19415 MOZ_ASSERT(written == count); 19416 19417 *_retval += count; 19418 aCount -= count; 19419 19420 if (NS_WARN_IF(!mData.Advance(mIter, count))) { 19421 // InputStreams do not propagate errors to caller. 19422 return NS_OK; 19423 } 19424 } 19425 19426 return NS_OK; 19427 } 19428 19429 NS_IMETHODIMP 19430 ObjectStoreAddOrPutRequestOp::SCInputStream::IsNonBlocking(bool* _retval) { 19431 *_retval = false; 19432 return NS_OK; 19433 } 19434 19435 ObjectStoreGetRequestOp::ObjectStoreGetRequestOp( 19436 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, 19437 const RequestParams& aParams, bool aGetAll) 19438 : NormalTransactionOp(std::move(aTransaction), aRequestId), 19439 mObjectStoreId(aGetAll 19440 ? aParams.get_ObjectStoreGetAllParams().objectStoreId() 19441 : aParams.get_ObjectStoreGetParams().objectStoreId()), 19442 mDatabase(Transaction().GetDatabasePtr()), 19443 mOptionalKeyRange( 19444 aGetAll ? aParams.get_ObjectStoreGetAllParams().optionalKeyRange() 19445 : Some(aParams.get_ObjectStoreGetParams().keyRange())), 19446 mBackgroundParent(Transaction().GetBackgroundParent()), 19447 mPreprocessInfoCount(0), 19448 mLimit(aGetAll ? aParams.get_ObjectStoreGetAllParams().limit() : 1), 19449 mGetAll(aGetAll) { 19450 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetParams || 19451 aParams.type() == RequestParams::TObjectStoreGetAllParams); 19452 MOZ_ASSERT(mObjectStoreId); 19453 MOZ_ASSERT(mDatabase); 19454 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome()); 19455 MOZ_ASSERT(mBackgroundParent); 19456 } 19457 19458 template <typename T> 19459 Result<T, nsresult> ObjectStoreGetRequestOp::ConvertResponse( 19460 StructuredCloneReadInfoParent&& aInfo) { 19461 T result; 19462 19463 static_assert(std::is_same_v<T, SerializedStructuredCloneReadInfo> || 19464 std::is_same_v<T, PreprocessInfo>); 19465 19466 if constexpr (std::is_same_v<T, SerializedStructuredCloneReadInfo>) { 19467 result.data().data = aInfo.ReleaseData(); 19468 result.hasPreprocessInfo() = aInfo.HasPreprocessInfo(); 19469 } 19470 19471 QM_TRY_UNWRAP(result.files(), SerializeStructuredCloneFiles( 19472 mDatabase, aInfo.Files(), 19473 std::is_same_v<T, PreprocessInfo>)); 19474 19475 return result; 19476 } 19477 19478 nsresult ObjectStoreGetRequestOp::DoDatabaseWork( 19479 DatabaseConnection* aConnection) { 19480 MOZ_ASSERT(aConnection); 19481 aConnection->AssertIsOnConnectionThread(); 19482 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome()); 19483 MOZ_ASSERT_IF(!mGetAll, mLimit == 1); 19484 19485 AUTO_PROFILER_LABEL("ObjectStoreGetRequestOp::DoDatabaseWork", DOM); 19486 19487 const nsCString query = 19488 "SELECT file_ids, data " 19489 "FROM object_data " 19490 "WHERE object_store_id = :"_ns + 19491 kStmtParamNameObjectStoreId + 19492 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameKey) + 19493 " ORDER BY key ASC"_ns + 19494 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString()); 19495 19496 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query)); 19497 19498 QM_TRY(MOZ_TO_RESULT( 19499 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId))); 19500 19501 if (mOptionalKeyRange.isSome()) { 19502 QM_TRY(MOZ_TO_RESULT( 19503 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt))); 19504 } 19505 19506 QM_TRY(CollectWhileHasResult( 19507 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> { 19508 QM_TRY_UNWRAP(auto cloneInfo, 19509 GetStructuredCloneReadInfoFromStatement( 19510 &stmt, 1, 0, mDatabase->GetFileManager())); 19511 19512 if (cloneInfo.HasPreprocessInfo()) { 19513 mPreprocessInfoCount++; 19514 } 19515 19516 QM_TRY(OkIf(mResponse.EmplaceBack(fallible, std::move(cloneInfo))), 19517 Err(NS_ERROR_OUT_OF_MEMORY)); 19518 19519 return Ok{}; 19520 })); 19521 19522 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1); 19523 19524 return NS_OK; 19525 } 19526 19527 bool ObjectStoreGetRequestOp::HasPreprocessInfo() { 19528 return mPreprocessInfoCount > 0; 19529 } 19530 19531 Result<PreprocessParams, nsresult> 19532 ObjectStoreGetRequestOp::GetPreprocessParams() { 19533 AssertIsOnOwningThread(); 19534 MOZ_ASSERT(!mResponse.IsEmpty()); 19535 19536 if (mGetAll) { 19537 auto params = ObjectStoreGetAllPreprocessParams(); 19538 19539 auto& preprocessInfos = params.preprocessInfos(); 19540 if (NS_WARN_IF( 19541 !preprocessInfos.SetCapacity(mPreprocessInfoCount, fallible))) { 19542 return Err(NS_ERROR_OUT_OF_MEMORY); 19543 } 19544 19545 QM_TRY(TransformIfAbortOnErr( 19546 std::make_move_iterator(mResponse.begin()), 19547 std::make_move_iterator(mResponse.end()), 19548 MakeBackInserter(preprocessInfos), 19549 [](const auto& info) { return info.HasPreprocessInfo(); }, 19550 [&self = *this](StructuredCloneReadInfoParent&& info) { 19551 return self.ConvertResponse<PreprocessInfo>(std::move(info)); 19552 })); 19553 19554 return PreprocessParams{std::move(params)}; 19555 } 19556 19557 auto params = ObjectStoreGetPreprocessParams(); 19558 19559 QM_TRY_UNWRAP(params.preprocessInfo(), 19560 ConvertResponse<PreprocessInfo>(std::move(mResponse[0]))); 19561 19562 return PreprocessParams{std::move(params)}; 19563 } 19564 19565 void ObjectStoreGetRequestOp::GetResponse(RequestResponse& aResponse, 19566 size_t* aResponseSize) { 19567 MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit); 19568 19569 if (mGetAll) { 19570 aResponse = ObjectStoreGetAllResponse(); 19571 *aResponseSize = 0; 19572 19573 if (!mResponse.IsEmpty()) { 19574 QM_TRY_UNWRAP( 19575 aResponse.get_ObjectStoreGetAllResponse().cloneInfos(), 19576 TransformIntoNewArrayAbortOnErr( 19577 std::make_move_iterator(mResponse.begin()), 19578 std::make_move_iterator(mResponse.end()), 19579 [this, &aResponseSize](StructuredCloneReadInfoParent&& info) { 19580 *aResponseSize += info.Size(); 19581 return ConvertResponse<SerializedStructuredCloneReadInfo>( 19582 std::move(info)); 19583 }, 19584 fallible), 19585 QM_VOID, [&aResponse](const nsresult result) { aResponse = result; }); 19586 } 19587 19588 return; 19589 } 19590 19591 aResponse = ObjectStoreGetResponse(); 19592 *aResponseSize = 0; 19593 19594 if (!mResponse.IsEmpty()) { 19595 SerializedStructuredCloneReadInfo& serializedInfo = 19596 aResponse.get_ObjectStoreGetResponse().cloneInfo(); 19597 19598 *aResponseSize += mResponse[0].Size(); 19599 QM_TRY_UNWRAP(serializedInfo, 19600 ConvertResponse<SerializedStructuredCloneReadInfo>( 19601 std::move(mResponse[0])), 19602 QM_VOID, 19603 [&aResponse](const nsresult result) { aResponse = result; }); 19604 } 19605 } 19606 19607 ObjectStoreGetKeyRequestOp::ObjectStoreGetKeyRequestOp( 19608 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, 19609 const RequestParams& aParams, bool aGetAll) 19610 : NormalTransactionOp(std::move(aTransaction), aRequestId), 19611 mObjectStoreId( 19612 aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().objectStoreId() 19613 : aParams.get_ObjectStoreGetKeyParams().objectStoreId()), 19614 mOptionalKeyRange( 19615 aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().optionalKeyRange() 19616 : Some(aParams.get_ObjectStoreGetKeyParams().keyRange())), 19617 mLimit(aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().limit() : 1), 19618 mGetAll(aGetAll) { 19619 MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetKeyParams || 19620 aParams.type() == RequestParams::TObjectStoreGetAllKeysParams); 19621 MOZ_ASSERT(mObjectStoreId); 19622 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome()); 19623 } 19624 19625 nsresult ObjectStoreGetKeyRequestOp::DoDatabaseWork( 19626 DatabaseConnection* aConnection) { 19627 MOZ_ASSERT(aConnection); 19628 aConnection->AssertIsOnConnectionThread(); 19629 19630 AUTO_PROFILER_LABEL("ObjectStoreGetKeyRequestOp::DoDatabaseWork", DOM); 19631 19632 const nsCString query = 19633 "SELECT key " 19634 "FROM object_data " 19635 "WHERE object_store_id = :"_ns + 19636 kStmtParamNameObjectStoreId + 19637 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameKey) + 19638 " ORDER BY key ASC"_ns + 19639 (mLimit ? " LIMIT "_ns + IntToCString(mLimit) : EmptyCString()); 19640 19641 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query)); 19642 19643 nsresult rv = 19644 stmt->BindInt64ByName(kStmtParamNameObjectStoreId, mObjectStoreId); 19645 if (NS_WARN_IF(NS_FAILED(rv))) { 19646 return rv; 19647 } 19648 19649 if (mOptionalKeyRange.isSome()) { 19650 rv = BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt); 19651 if (NS_WARN_IF(NS_FAILED(rv))) { 19652 return rv; 19653 } 19654 } 19655 19656 QM_TRY(CollectWhileHasResult( 19657 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> { 19658 Key* const key = mResponse.AppendElement(fallible); 19659 QM_TRY(OkIf(key), Err(NS_ERROR_OUT_OF_MEMORY)); 19660 QM_TRY(MOZ_TO_RESULT(key->SetFromStatement(&stmt, 0))); 19661 19662 return Ok{}; 19663 })); 19664 19665 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1); 19666 19667 return NS_OK; 19668 } 19669 19670 void ObjectStoreGetKeyRequestOp::GetResponse(RequestResponse& aResponse, 19671 size_t* aResponseSize) { 19672 MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit); 19673 19674 if (mGetAll) { 19675 aResponse = ObjectStoreGetAllKeysResponse(); 19676 *aResponseSize = std::accumulate(mResponse.begin(), mResponse.end(), 0u, 19677 [](size_t old, const auto& entry) { 19678 return old + entry.GetBuffer().Length(); 19679 }); 19680 19681 aResponse.get_ObjectStoreGetAllKeysResponse().keys() = std::move(mResponse); 19682 19683 return; 19684 } 19685 19686 aResponse = ObjectStoreGetKeyResponse(); 19687 *aResponseSize = 0; 19688 19689 if (!mResponse.IsEmpty()) { 19690 *aResponseSize = mResponse[0].GetBuffer().Length(); 19691 aResponse.get_ObjectStoreGetKeyResponse().key() = std::move(mResponse[0]); 19692 } 19693 } 19694 19695 ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp( 19696 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, 19697 const ObjectStoreDeleteParams& aParams) 19698 : NormalTransactionOp(std::move(aTransaction), aRequestId), 19699 mParams(aParams), 19700 mObjectStoreMayHaveIndexes(false) { 19701 AssertIsOnBackgroundThread(); 19702 19703 SafeRefPtr<FullObjectStoreMetadata> metadata = 19704 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId()); 19705 MOZ_ASSERT(metadata); 19706 19707 mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes(); 19708 } 19709 19710 nsresult ObjectStoreDeleteRequestOp::DoDatabaseWork( 19711 DatabaseConnection* aConnection) { 19712 MOZ_ASSERT(aConnection); 19713 aConnection->AssertIsOnConnectionThread(); 19714 AUTO_PROFILER_LABEL("ObjectStoreDeleteRequestOp::DoDatabaseWork", DOM); 19715 19716 DatabaseConnection::AutoSavepoint autoSave; 19717 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 19718 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 19719 , 19720 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 19721 #endif 19722 ); 19723 19724 QM_TRY_INSPECT(const bool& objectStoreHasIndexes, 19725 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(), 19726 mObjectStoreMayHaveIndexes)); 19727 19728 if (objectStoreHasIndexes) { 19729 QM_TRY(MOZ_TO_RESULT(DeleteObjectStoreDataTableRowsWithIndexes( 19730 aConnection, mParams.objectStoreId(), Some(mParams.keyRange())))); 19731 } else { 19732 const auto keyRangeClause = 19733 GetBindingClauseForKeyRange(mParams.keyRange(), kColumnNameKey); 19734 19735 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 19736 "DELETE FROM object_data " 19737 "WHERE object_store_id = :"_ns + 19738 kStmtParamNameObjectStoreId + keyRangeClause + ";"_ns, 19739 [¶ms = mParams]( 19740 mozIStorageStatement& stmt) -> mozilla::Result<Ok, nsresult> { 19741 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName(kStmtParamNameObjectStoreId, 19742 params.objectStoreId()))); 19743 19744 QM_TRY( 19745 MOZ_TO_RESULT(BindKeyRangeToStatement(params.keyRange(), &stmt))); 19746 19747 return Ok{}; 19748 }))); 19749 } 19750 19751 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 19752 19753 return NS_OK; 19754 } 19755 19756 ObjectStoreClearRequestOp::ObjectStoreClearRequestOp( 19757 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, 19758 const ObjectStoreClearParams& aParams) 19759 : NormalTransactionOp(std::move(aTransaction), aRequestId), 19760 mParams(aParams), 19761 mObjectStoreMayHaveIndexes(false) { 19762 AssertIsOnBackgroundThread(); 19763 19764 SafeRefPtr<FullObjectStoreMetadata> metadata = 19765 Transaction().GetMetadataForObjectStoreId(mParams.objectStoreId()); 19766 MOZ_ASSERT(metadata); 19767 19768 mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes(); 19769 } 19770 19771 nsresult ObjectStoreClearRequestOp::DoDatabaseWork( 19772 DatabaseConnection* aConnection) { 19773 MOZ_ASSERT(aConnection); 19774 aConnection->AssertIsOnConnectionThread(); 19775 19776 AUTO_PROFILER_LABEL("ObjectStoreClearRequestOp::DoDatabaseWork", DOM); 19777 19778 DatabaseConnection::AutoSavepoint autoSave; 19779 QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction())) 19780 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 19781 , 19782 QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection) 19783 #endif 19784 ); 19785 19786 QM_TRY_INSPECT(const bool& objectStoreHasIndexes, 19787 ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(), 19788 mObjectStoreMayHaveIndexes)); 19789 19790 // The parameter names are not used, parameters are bound by index only 19791 // locally in the same function. 19792 QM_TRY(MOZ_TO_RESULT( 19793 objectStoreHasIndexes 19794 ? DeleteObjectStoreDataTableRowsWithIndexes( 19795 aConnection, mParams.objectStoreId(), Nothing()) 19796 : aConnection->ExecuteCachedStatement( 19797 "DELETE FROM object_data " 19798 "WHERE object_store_id = :object_store_id;"_ns, 19799 [objectStoreId = 19800 mParams.objectStoreId()](mozIStorageStatement& stmt) 19801 -> mozilla::Result<Ok, nsresult> { 19802 QM_TRY( 19803 MOZ_TO_RESULT(stmt.BindInt64ByIndex(0, objectStoreId))); 19804 19805 return Ok{}; 19806 }))); 19807 19808 QM_TRY(MOZ_TO_RESULT(autoSave.Commit())); 19809 19810 return NS_OK; 19811 } 19812 19813 nsresult ObjectStoreCountRequestOp::DoDatabaseWork( 19814 DatabaseConnection* aConnection) { 19815 MOZ_ASSERT(aConnection); 19816 aConnection->AssertIsOnConnectionThread(); 19817 19818 AUTO_PROFILER_LABEL("ObjectStoreCountRequestOp::DoDatabaseWork", DOM); 19819 19820 const auto keyRangeClause = MaybeGetBindingClauseForKeyRange( 19821 mParams.optionalKeyRange(), kColumnNameKey); 19822 19823 QM_TRY_INSPECT( 19824 const auto& maybeStmt, 19825 aConnection->BorrowAndExecuteSingleStepStatement( 19826 "SELECT count(*) " 19827 "FROM object_data " 19828 "WHERE object_store_id = :"_ns + 19829 kStmtParamNameObjectStoreId + keyRangeClause, 19830 [¶ms = mParams](auto& stmt) -> mozilla::Result<Ok, nsresult> { 19831 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName( 19832 kStmtParamNameObjectStoreId, params.objectStoreId()))); 19833 19834 if (params.optionalKeyRange().isSome()) { 19835 QM_TRY(MOZ_TO_RESULT(BindKeyRangeToStatement( 19836 params.optionalKeyRange().ref(), &stmt))); 19837 } 19838 19839 return Ok{}; 19840 })); 19841 19842 QM_TRY(OkIf(maybeStmt.isSome()), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, 19843 [](const auto) { 19844 // XXX Why do we have an assertion here, but not at most other 19845 // places using IDB_REPORT_INTERNAL_ERR(_LAMBDA)? 19846 MOZ_ASSERT(false, "This should never be possible!"); 19847 IDB_REPORT_INTERNAL_ERR(); 19848 }); 19849 19850 const auto& stmt = *maybeStmt; 19851 19852 const int64_t count = stmt->AsInt64(0); 19853 QM_TRY(OkIf(count >= 0), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, [](const auto) { 19854 // XXX Why do we have an assertion here, but not at most other places using 19855 // IDB_REPORT_INTERNAL_ERR(_LAMBDA)? 19856 MOZ_ASSERT(false, "This should never be possible!"); 19857 IDB_REPORT_INTERNAL_ERR(); 19858 }); 19859 19860 mResponse.count() = count; 19861 19862 return NS_OK; 19863 } 19864 19865 // static 19866 SafeRefPtr<FullIndexMetadata> IndexRequestOpBase::IndexMetadataForParams( 19867 const TransactionBase& aTransaction, const RequestParams& aParams) { 19868 AssertIsOnBackgroundThread(); 19869 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams || 19870 aParams.type() == RequestParams::TIndexGetKeyParams || 19871 aParams.type() == RequestParams::TIndexGetAllParams || 19872 aParams.type() == RequestParams::TIndexGetAllKeysParams || 19873 aParams.type() == RequestParams::TIndexCountParams); 19874 19875 IndexOrObjectStoreId objectStoreId; 19876 IndexOrObjectStoreId indexId; 19877 19878 switch (aParams.type()) { 19879 case RequestParams::TIndexGetParams: { 19880 const IndexGetParams& params = aParams.get_IndexGetParams(); 19881 objectStoreId = params.objectStoreId(); 19882 indexId = params.indexId(); 19883 break; 19884 } 19885 19886 case RequestParams::TIndexGetKeyParams: { 19887 const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams(); 19888 objectStoreId = params.objectStoreId(); 19889 indexId = params.indexId(); 19890 break; 19891 } 19892 19893 case RequestParams::TIndexGetAllParams: { 19894 const IndexGetAllParams& params = aParams.get_IndexGetAllParams(); 19895 objectStoreId = params.objectStoreId(); 19896 indexId = params.indexId(); 19897 break; 19898 } 19899 19900 case RequestParams::TIndexGetAllKeysParams: { 19901 const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams(); 19902 objectStoreId = params.objectStoreId(); 19903 indexId = params.indexId(); 19904 break; 19905 } 19906 19907 case RequestParams::TIndexCountParams: { 19908 const IndexCountParams& params = aParams.get_IndexCountParams(); 19909 objectStoreId = params.objectStoreId(); 19910 indexId = params.indexId(); 19911 break; 19912 } 19913 19914 default: 19915 MOZ_CRASH("Should never get here!"); 19916 } 19917 19918 const SafeRefPtr<FullObjectStoreMetadata> objectStoreMetadata = 19919 aTransaction.GetMetadataForObjectStoreId(objectStoreId); 19920 MOZ_ASSERT(objectStoreMetadata); 19921 19922 SafeRefPtr<FullIndexMetadata> indexMetadata = 19923 aTransaction.GetMetadataForIndexId(*objectStoreMetadata, indexId); 19924 MOZ_ASSERT(indexMetadata); 19925 19926 return indexMetadata; 19927 } 19928 19929 IndexGetRequestOp::IndexGetRequestOp(SafeRefPtr<TransactionBase> aTransaction, 19930 const int64_t aRequestId, 19931 const RequestParams& aParams, bool aGetAll) 19932 : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams), 19933 mDatabase(Transaction().GetDatabasePtr()), 19934 mOptionalKeyRange(aGetAll 19935 ? aParams.get_IndexGetAllParams().optionalKeyRange() 19936 : Some(aParams.get_IndexGetParams().keyRange())), 19937 mBackgroundParent(Transaction().GetBackgroundParent()), 19938 mLimit(aGetAll ? aParams.get_IndexGetAllParams().limit() : 1), 19939 mGetAll(aGetAll) { 19940 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams || 19941 aParams.type() == RequestParams::TIndexGetAllParams); 19942 MOZ_ASSERT(mDatabase); 19943 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome()); 19944 MOZ_ASSERT(mBackgroundParent); 19945 } 19946 19947 nsresult IndexGetRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) { 19948 MOZ_ASSERT(aConnection); 19949 aConnection->AssertIsOnConnectionThread(); 19950 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome()); 19951 MOZ_ASSERT_IF(!mGetAll, mLimit == 1); 19952 19953 AUTO_PROFILER_LABEL("IndexGetRequestOp::DoDatabaseWork", DOM); 19954 19955 const auto indexTable = mMetadata->mCommonMetadata.unique() 19956 ? "unique_index_data "_ns 19957 : "index_data "_ns; 19958 19959 QM_TRY_INSPECT( 19960 const auto& stmt, 19961 aConnection->BorrowCachedStatement( 19962 "SELECT file_ids, data " 19963 "FROM object_data " 19964 "INNER JOIN "_ns + 19965 indexTable + 19966 "AS index_table " 19967 "ON object_data.object_store_id = " 19968 "index_table.object_store_id " 19969 "AND object_data.key = " 19970 "index_table.object_data_key " 19971 "WHERE index_id = :"_ns + 19972 kStmtParamNameIndexId + 19973 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, 19974 kColumnNameValue) + 19975 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString()))); 19976 19977 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameIndexId, 19978 mMetadata->mCommonMetadata.id()))); 19979 19980 if (mOptionalKeyRange.isSome()) { 19981 QM_TRY(MOZ_TO_RESULT( 19982 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt))); 19983 } 19984 19985 QM_TRY(CollectWhileHasResult( 19986 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> { 19987 QM_TRY_UNWRAP(auto cloneInfo, 19988 GetStructuredCloneReadInfoFromStatement( 19989 &stmt, 1, 0, mDatabase->GetFileManager())); 19990 19991 if (cloneInfo.HasPreprocessInfo()) { 19992 IDB_WARNING("Preprocessing for indexes not yet implemented!"); 19993 return Err(NS_ERROR_NOT_IMPLEMENTED); 19994 } 19995 19996 QM_TRY(OkIf(mResponse.EmplaceBack(fallible, std::move(cloneInfo))), 19997 Err(NS_ERROR_OUT_OF_MEMORY)); 19998 19999 return Ok{}; 20000 })); 20001 20002 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1); 20003 20004 return NS_OK; 20005 } 20006 20007 // XXX This is more or less a duplicate of ObjectStoreGetRequestOp::GetResponse 20008 void IndexGetRequestOp::GetResponse(RequestResponse& aResponse, 20009 size_t* aResponseSize) { 20010 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1); 20011 20012 auto convertResponse = [this](StructuredCloneReadInfoParent&& info) 20013 -> mozilla::Result<SerializedStructuredCloneReadInfo, nsresult> { 20014 SerializedStructuredCloneReadInfo result; 20015 20016 result.data().data = info.ReleaseData(); 20017 20018 QM_TRY_UNWRAP(result.files(), SerializeStructuredCloneFiles( 20019 mDatabase, info.Files(), false)); 20020 20021 return result; 20022 }; 20023 20024 if (mGetAll) { 20025 aResponse = IndexGetAllResponse(); 20026 *aResponseSize = 0; 20027 20028 if (!mResponse.IsEmpty()) { 20029 QM_TRY_UNWRAP( 20030 aResponse.get_IndexGetAllResponse().cloneInfos(), 20031 TransformIntoNewArrayAbortOnErr( 20032 std::make_move_iterator(mResponse.begin()), 20033 std::make_move_iterator(mResponse.end()), 20034 [convertResponse, 20035 &aResponseSize](StructuredCloneReadInfoParent&& info) { 20036 *aResponseSize += info.Size(); 20037 return convertResponse(std::move(info)); 20038 }, 20039 fallible), 20040 QM_VOID, [&aResponse](const nsresult result) { aResponse = result; }); 20041 } 20042 20043 return; 20044 } 20045 20046 aResponse = IndexGetResponse(); 20047 *aResponseSize = 0; 20048 20049 if (!mResponse.IsEmpty()) { 20050 SerializedStructuredCloneReadInfo& serializedInfo = 20051 aResponse.get_IndexGetResponse().cloneInfo(); 20052 20053 *aResponseSize += mResponse[0].Size(); 20054 QM_TRY_UNWRAP(serializedInfo, convertResponse(std::move(mResponse[0])), 20055 QM_VOID, 20056 [&aResponse](const nsresult result) { aResponse = result; }); 20057 } 20058 } 20059 20060 IndexGetKeyRequestOp::IndexGetKeyRequestOp( 20061 SafeRefPtr<TransactionBase> aTransaction, const int64_t aRequestId, 20062 const RequestParams& aParams, bool aGetAll) 20063 : IndexRequestOpBase(std::move(aTransaction), aRequestId, aParams), 20064 mOptionalKeyRange( 20065 aGetAll ? aParams.get_IndexGetAllKeysParams().optionalKeyRange() 20066 : Some(aParams.get_IndexGetKeyParams().keyRange())), 20067 mLimit(aGetAll ? aParams.get_IndexGetAllKeysParams().limit() : 1), 20068 mGetAll(aGetAll) { 20069 MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetKeyParams || 20070 aParams.type() == RequestParams::TIndexGetAllKeysParams); 20071 MOZ_ASSERT_IF(!aGetAll, mOptionalKeyRange.isSome()); 20072 } 20073 20074 nsresult IndexGetKeyRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) { 20075 MOZ_ASSERT(aConnection); 20076 aConnection->AssertIsOnConnectionThread(); 20077 MOZ_ASSERT_IF(!mGetAll, mOptionalKeyRange.isSome()); 20078 MOZ_ASSERT_IF(!mGetAll, mLimit == 1); 20079 20080 AUTO_PROFILER_LABEL("IndexGetKeyRequestOp::DoDatabaseWork", DOM); 20081 20082 const bool hasKeyRange = mOptionalKeyRange.isSome(); 20083 20084 const auto indexTable = mMetadata->mCommonMetadata.unique() 20085 ? "unique_index_data "_ns 20086 : "index_data "_ns; 20087 20088 const nsCString query = 20089 "SELECT object_data_key " 20090 "FROM "_ns + 20091 indexTable + "WHERE index_id = :"_ns + kStmtParamNameIndexId + 20092 MaybeGetBindingClauseForKeyRange(mOptionalKeyRange, kColumnNameValue) + 20093 (mLimit ? kOpenLimit + IntToCString(mLimit) : EmptyCString()); 20094 20095 QM_TRY_INSPECT(const auto& stmt, aConnection->BorrowCachedStatement(query)); 20096 20097 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameIndexId, 20098 mMetadata->mCommonMetadata.id()))); 20099 20100 if (hasKeyRange) { 20101 QM_TRY(MOZ_TO_RESULT( 20102 BindKeyRangeToStatement(mOptionalKeyRange.ref(), &*stmt))); 20103 } 20104 20105 QM_TRY(CollectWhileHasResult( 20106 *stmt, [this](auto& stmt) mutable -> mozilla::Result<Ok, nsresult> { 20107 Key* const key = mResponse.AppendElement(fallible); 20108 QM_TRY(OkIf(key), Err(NS_ERROR_OUT_OF_MEMORY)); 20109 QM_TRY(MOZ_TO_RESULT(key->SetFromStatement(&stmt, 0))); 20110 20111 return Ok{}; 20112 })); 20113 20114 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1); 20115 20116 return NS_OK; 20117 } 20118 20119 void IndexGetKeyRequestOp::GetResponse(RequestResponse& aResponse, 20120 size_t* aResponseSize) { 20121 MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1); 20122 20123 if (mGetAll) { 20124 aResponse = IndexGetAllKeysResponse(); 20125 *aResponseSize = std::accumulate(mResponse.begin(), mResponse.end(), 0u, 20126 [](size_t old, const auto& entry) { 20127 return old + entry.GetBuffer().Length(); 20128 }); 20129 20130 aResponse.get_IndexGetAllKeysResponse().keys() = std::move(mResponse); 20131 20132 return; 20133 } 20134 20135 aResponse = IndexGetKeyResponse(); 20136 *aResponseSize = 0; 20137 20138 if (!mResponse.IsEmpty()) { 20139 *aResponseSize = mResponse[0].GetBuffer().Length(); 20140 aResponse.get_IndexGetKeyResponse().key() = std::move(mResponse[0]); 20141 } 20142 } 20143 20144 nsresult IndexCountRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) { 20145 MOZ_ASSERT(aConnection); 20146 aConnection->AssertIsOnConnectionThread(); 20147 20148 AUTO_PROFILER_LABEL("IndexCountRequestOp::DoDatabaseWork", DOM); 20149 20150 const auto indexTable = mMetadata->mCommonMetadata.unique() 20151 ? "unique_index_data "_ns 20152 : "index_data "_ns; 20153 20154 const auto keyRangeClause = MaybeGetBindingClauseForKeyRange( 20155 mParams.optionalKeyRange(), kColumnNameValue); 20156 20157 QM_TRY_INSPECT( 20158 const auto& maybeStmt, 20159 aConnection->BorrowAndExecuteSingleStepStatement( 20160 "SELECT count(*) " 20161 "FROM "_ns + 20162 indexTable + "WHERE index_id = :"_ns + kStmtParamNameIndexId + 20163 keyRangeClause, 20164 [&self = *this](auto& stmt) -> mozilla::Result<Ok, nsresult> { 20165 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName( 20166 kStmtParamNameIndexId, self.mMetadata->mCommonMetadata.id()))); 20167 20168 if (self.mParams.optionalKeyRange().isSome()) { 20169 QM_TRY(MOZ_TO_RESULT(BindKeyRangeToStatement( 20170 self.mParams.optionalKeyRange().ref(), &stmt))); 20171 } 20172 20173 return Ok{}; 20174 })); 20175 20176 QM_TRY(OkIf(maybeStmt.isSome()), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, 20177 [](const auto) { 20178 // XXX Why do we have an assertion here, but not at most other 20179 // places using IDB_REPORT_INTERNAL_ERR(_LAMBDA)? 20180 MOZ_ASSERT(false, "This should never be possible!"); 20181 IDB_REPORT_INTERNAL_ERR(); 20182 }); 20183 20184 const auto& stmt = *maybeStmt; 20185 20186 const int64_t count = stmt->AsInt64(0); 20187 QM_TRY(OkIf(count >= 0), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, [](const auto) { 20188 // XXX Why do we have an assertion here, but not at most other places using 20189 // IDB_REPORT_INTERNAL_ERR(_LAMBDA)? 20190 MOZ_ASSERT(false, "This should never be possible!"); 20191 IDB_REPORT_INTERNAL_ERR(); 20192 }); 20193 20194 mResponse.count() = count; 20195 20196 return NS_OK; 20197 } 20198 20199 template <IDBCursorType CursorType> 20200 bool Cursor<CursorType>::CursorOpBase::SendFailureResult(nsresult aResultCode) { 20201 AssertIsOnOwningThread(); 20202 MOZ_ASSERT(NS_FAILED(aResultCode)); 20203 MOZ_ASSERT(mCursor); 20204 MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this); 20205 MOZ_ASSERT(!mResponseSent); 20206 20207 if (!IsActorDestroyed()) { 20208 mResponse = ClampResultCode(aResultCode); 20209 20210 // This is an expected race when the transaction is invalidated after 20211 // data is retrieved from database. 20212 // 20213 // TODO: There seem to be other cases when mFiles is non-empty here, which 20214 // have been present before adding cursor preloading, but with cursor 20215 // preloading they have become more frequent (also during startup). One 20216 // possible cause with cursor preloading is to be addressed by Bug 1597191. 20217 NS_WARNING_ASSERTION( 20218 !mFiles.IsEmpty() && !Transaction().IsInvalidated(), 20219 "Expected empty mFiles when transaction has not been invalidated"); 20220 20221 // SendResponseInternal will assert when mResponse.type() is 20222 // CursorResponse::Tnsresult and mFiles is non-empty, so we clear mFiles 20223 // here. 20224 mFiles.Clear(); 20225 20226 mCursor->SendResponseInternal(mResponse, mFiles); 20227 } 20228 20229 #ifdef DEBUG 20230 mResponseSent = true; 20231 #endif 20232 return false; 20233 } 20234 20235 template <IDBCursorType CursorType> 20236 void Cursor<CursorType>::CursorOpBase::Cleanup() { 20237 AssertIsOnOwningThread(); 20238 MOZ_ASSERT(mCursor); 20239 MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent); 20240 20241 mCursor = nullptr; 20242 20243 #ifdef DEBUG 20244 // A bit hacky but the CursorOp request is not generated in response to a 20245 // child request like most other database operations. Do this to make our 20246 // assertions happy. 20247 NoteActorDestroyed(); 20248 #endif 20249 20250 TransactionDatabaseOperationBase::Cleanup(); 20251 } 20252 20253 template <IDBCursorType CursorType> 20254 ResponseSizeOrError 20255 CursorOpBaseHelperBase<CursorType>::PopulateResponseFromStatement( 20256 mozIStorageStatement* const aStmt, const bool aInitializeResponse, 20257 Key* const aOptOutSortKey) { 20258 mOp.Transaction().AssertIsOnConnectionThread(); 20259 MOZ_ASSERT_IF(aInitializeResponse, 20260 mOp.mResponse.type() == CursorResponse::T__None); 20261 MOZ_ASSERT_IF(!aInitializeResponse, 20262 mOp.mResponse.type() != CursorResponse::T__None); 20263 MOZ_ASSERT_IF( 20264 mOp.mFiles.IsEmpty() && 20265 (mOp.mResponse.type() == 20266 CursorResponse::TArrayOfObjectStoreCursorResponse || 20267 mOp.mResponse.type() == CursorResponse::TArrayOfIndexCursorResponse), 20268 aInitializeResponse); 20269 20270 auto populateResponseHelper = PopulateResponseHelper<CursorType>{mOp}; 20271 auto previousKey = aOptOutSortKey ? std::move(*aOptOutSortKey) : Key{}; 20272 20273 QM_TRY(MOZ_TO_RESULT(populateResponseHelper.GetKeys(aStmt, aOptOutSortKey))); 20274 20275 // aOptOutSortKey must be set iff the cursor is a unique cursor. For unique 20276 // cursors, we need to skip records with the same key. The SQL queries 20277 // currently do not filter these out. 20278 if (aOptOutSortKey && !previousKey.IsUnset() && 20279 previousKey == *aOptOutSortKey) { 20280 return 0; 20281 } 20282 20283 QM_TRY(MOZ_TO_RESULT( 20284 populateResponseHelper.MaybeGetCloneInfo(aStmt, GetCursor()))); 20285 20286 // CAUTION: It is important that only the part of the function above this 20287 // comment may fail, and modifications to the data structure (in particular 20288 // mResponse and mFiles) may only be made below. This is necessary to allow to 20289 // discard entries that were attempted to be preloaded without causing an 20290 // inconsistent state. 20291 20292 if (aInitializeResponse) { 20293 mOp.mResponse = std::remove_reference_t< 20294 decltype(populateResponseHelper.GetTypedResponse(&mOp.mResponse))>(); 20295 } 20296 20297 auto& responses = populateResponseHelper.GetTypedResponse(&mOp.mResponse); 20298 auto& response = *responses.AppendElement(); 20299 20300 populateResponseHelper.FillKeys(response); 20301 if constexpr (!CursorTypeTraits<CursorType>::IsKeyOnlyCursor) { 20302 populateResponseHelper.MaybeFillCloneInfo(response, &mOp.mFiles); 20303 } 20304 20305 return populateResponseHelper.GetKeySize(response) + 20306 populateResponseHelper.MaybeGetCloneInfoSize(response); 20307 } 20308 20309 template <IDBCursorType CursorType> 20310 void CursorOpBaseHelperBase<CursorType>::PopulateExtraResponses( 20311 mozIStorageStatement* const aStmt, const uint32_t aMaxExtraCount, 20312 const size_t aInitialResponseSize, const nsACString& aOperation, 20313 Key* const aOptPreviousSortKey) { 20314 mOp.AssertIsOnConnectionThread(); 20315 20316 const auto extraCount = [&]() -> uint32_t { 20317 auto accumulatedResponseSize = aInitialResponseSize; 20318 uint32_t extraCount = 0; 20319 20320 do { 20321 bool hasResult; 20322 nsresult rv = aStmt->ExecuteStep(&hasResult); 20323 if (NS_WARN_IF(NS_FAILED(rv))) { 20324 // In case of a failure on one step, do not attempt to execute further 20325 // steps, but use the results already populated. 20326 20327 break; 20328 } 20329 20330 if (!hasResult) { 20331 break; 20332 } 20333 20334 // PopulateResponseFromStatement does not modify the data in case of 20335 // failure, so we can just use the results already populated, and discard 20336 // any remaining entries, and signal overall success. Probably, future 20337 // attempts to access the same entry will fail as well, but it might never 20338 // be accessed by the application. 20339 QM_TRY_INSPECT( 20340 const auto& responseSize, 20341 PopulateResponseFromStatement(aStmt, false, aOptPreviousSortKey), 20342 extraCount, [](const auto&) { 20343 // TODO: Maybe disable preloading for this cursor? The problem will 20344 // probably reoccur on the next attempt, and disabling preloading 20345 // will reduce latency. However, if some problematic entry will be 20346 // skipped over, after that it might be fine again. To judge this, 20347 // the causes for such failures would need to be analyzed more 20348 // thoroughly. Since this seems to be rare, maybe no further action 20349 // is necessary at all. 20350 }); 20351 20352 // Check accumulated size of individual responses and maybe break early. 20353 accumulatedResponseSize += responseSize; 20354 if (accumulatedResponseSize > IPC::Channel::kMaximumMessageSize / 2) { 20355 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST( 20356 "PRELOAD: %s: Dropping entries because maximum message size is " 20357 "exceeded: %" PRIu32 "/%zu bytes", 20358 "%.0s Dropping too large (%" PRIu32 "/%zu)", 20359 IDB_LOG_ID_STRING(mOp.mBackgroundChildLoggingId), 20360 mOp.mTransactionLoggingSerialNumber, mOp.mLoggingSerialNumber, 20361 PromiseFlatCString(aOperation).get(), extraCount, 20362 accumulatedResponseSize); 20363 20364 break; 20365 } 20366 20367 // TODO: Do not count entries skipped for unique cursors. 20368 ++extraCount; 20369 } while (true); 20370 20371 return extraCount; 20372 }(); 20373 20374 IDB_LOG_MARK_PARENT_TRANSACTION_REQUEST( 20375 "PRELOAD: %s: Number of extra results populated: %" PRIu32 "/%" PRIu32, 20376 "%.0s Populated (%" PRIu32 "/%" PRIu32 ")", 20377 IDB_LOG_ID_STRING(mOp.mBackgroundChildLoggingId), 20378 mOp.mTransactionLoggingSerialNumber, mOp.mLoggingSerialNumber, 20379 PromiseFlatCString(aOperation).get(), extraCount, aMaxExtraCount); 20380 } 20381 20382 template <IDBCursorType CursorType> 20383 void Cursor<CursorType>::SetOptionalKeyRange( 20384 const Maybe<SerializedKeyRange>& aOptionalKeyRange, bool* const aOpen) { 20385 MOZ_ASSERT(aOpen); 20386 20387 Key localeAwareRangeBound; 20388 20389 if (aOptionalKeyRange.isSome()) { 20390 const SerializedKeyRange& range = aOptionalKeyRange.ref(); 20391 20392 const bool lowerBound = !IsIncreasingOrder(mDirection); 20393 *aOpen = 20394 !range.isOnly() && (lowerBound ? range.lowerOpen() : range.upperOpen()); 20395 20396 const auto& bound = 20397 (range.isOnly() || lowerBound) ? range.lower() : range.upper(); 20398 if constexpr (IsIndexCursor) { 20399 if (this->IsLocaleAware()) { 20400 // XXX Don't we need to propagate the error? 20401 QM_TRY_UNWRAP(localeAwareRangeBound, 20402 bound.ToLocaleAwareKey(this->mLocale), QM_VOID); 20403 } else { 20404 localeAwareRangeBound = bound; 20405 } 20406 } else { 20407 localeAwareRangeBound = bound; 20408 } 20409 } else { 20410 *aOpen = false; 20411 } 20412 20413 this->mLocaleAwareRangeBound.init(std::move(localeAwareRangeBound)); 20414 } 20415 20416 template <IDBCursorType CursorType> 20417 void ObjectStoreOpenOpHelper<CursorType>::PrepareKeyConditionClauses( 20418 const nsACString& aDirectionClause, const nsACString& aQueryStart) { 20419 const bool isIncreasingOrder = IsIncreasingOrder(GetCursor().mDirection); 20420 20421 nsAutoCString keyRangeClause; 20422 nsAutoCString continueToKeyRangeClause; 20423 AppendConditionClause(kStmtParamNameKey, kStmtParamNameCurrentKey, 20424 !isIncreasingOrder, false, keyRangeClause); 20425 AppendConditionClause(kStmtParamNameKey, kStmtParamNameCurrentKey, 20426 !isIncreasingOrder, true, continueToKeyRangeClause); 20427 20428 { 20429 bool open; 20430 GetCursor().SetOptionalKeyRange(GetOptionalKeyRange(), &open); 20431 20432 if (GetOptionalKeyRange().isSome() && 20433 !GetCursor().mLocaleAwareRangeBound->IsUnset()) { 20434 AppendConditionClause(kStmtParamNameKey, kStmtParamNameRangeBound, 20435 isIncreasingOrder, !open, keyRangeClause); 20436 AppendConditionClause(kStmtParamNameKey, kStmtParamNameRangeBound, 20437 isIncreasingOrder, !open, continueToKeyRangeClause); 20438 } 20439 } 20440 20441 const nsAutoCString suffix = 20442 aDirectionClause + kOpenLimit + ":"_ns + kStmtParamNameLimit; 20443 20444 GetCursor().mContinueQueries.init( 20445 aQueryStart + keyRangeClause + suffix, 20446 aQueryStart + continueToKeyRangeClause + suffix); 20447 } 20448 20449 template <IDBCursorType CursorType> 20450 void IndexOpenOpHelper<CursorType>::PrepareIndexKeyConditionClause( 20451 const nsACString& aDirectionClause, 20452 const nsLiteralCString& aObjectDataKeyPrefix, nsAutoCString aQueryStart) { 20453 const bool isIncreasingOrder = IsIncreasingOrder(GetCursor().mDirection); 20454 20455 { 20456 bool open; 20457 GetCursor().SetOptionalKeyRange(GetOptionalKeyRange(), &open); 20458 if (GetOptionalKeyRange().isSome() && 20459 !GetCursor().mLocaleAwareRangeBound->IsUnset()) { 20460 AppendConditionClause(kColumnNameAliasSortKey, kStmtParamNameRangeBound, 20461 isIncreasingOrder, !open, aQueryStart); 20462 } 20463 } 20464 20465 nsCString continueQuery, continueToQuery, continuePrimaryKeyQuery; 20466 20467 continueToQuery = 20468 aQueryStart + " AND "_ns + 20469 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterOrEquals 20470 : ComparisonOperator::LessOrEquals, 20471 kStmtParamNameCurrentKey); 20472 20473 switch (GetCursor().mDirection) { 20474 case IDBCursorDirection::Next: 20475 case IDBCursorDirection::Prev: 20476 continueQuery = 20477 aQueryStart + " AND "_ns + 20478 GetSortKeyClause(isIncreasingOrder 20479 ? ComparisonOperator::GreaterOrEquals 20480 : ComparisonOperator::LessOrEquals, 20481 kStmtParamNameCurrentKey) + 20482 " AND ( "_ns + 20483 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan 20484 : ComparisonOperator::LessThan, 20485 kStmtParamNameCurrentKey) + 20486 " OR "_ns + 20487 GetKeyClause(aObjectDataKeyPrefix + "object_data_key"_ns, 20488 isIncreasingOrder ? ComparisonOperator::GreaterThan 20489 : ComparisonOperator::LessThan, 20490 kStmtParamNameObjectStorePosition) + 20491 " ) "_ns; 20492 20493 continuePrimaryKeyQuery = 20494 aQueryStart + 20495 " AND (" 20496 "("_ns + 20497 GetSortKeyClause(ComparisonOperator::Equals, 20498 kStmtParamNameCurrentKey) + 20499 " AND "_ns + 20500 GetKeyClause(aObjectDataKeyPrefix + "object_data_key"_ns, 20501 isIncreasingOrder ? ComparisonOperator::GreaterOrEquals 20502 : ComparisonOperator::LessOrEquals, 20503 kStmtParamNameObjectStorePosition) + 20504 ") OR "_ns + 20505 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan 20506 : ComparisonOperator::LessThan, 20507 kStmtParamNameCurrentKey) + 20508 ")"_ns; 20509 break; 20510 20511 case IDBCursorDirection::Nextunique: 20512 case IDBCursorDirection::Prevunique: 20513 continueQuery = 20514 aQueryStart + " AND "_ns + 20515 GetSortKeyClause(isIncreasingOrder ? ComparisonOperator::GreaterThan 20516 : ComparisonOperator::LessThan, 20517 kStmtParamNameCurrentKey); 20518 break; 20519 20520 default: 20521 MOZ_CRASH("Should never get here!"); 20522 } 20523 20524 const nsAutoCString suffix = 20525 aDirectionClause + kOpenLimit + ":"_ns + kStmtParamNameLimit; 20526 continueQuery += suffix; 20527 continueToQuery += suffix; 20528 if (!continuePrimaryKeyQuery.IsEmpty()) { 20529 continuePrimaryKeyQuery += suffix; 20530 } 20531 20532 GetCursor().mContinueQueries.init(std::move(continueQuery), 20533 std::move(continueToQuery), 20534 std::move(continuePrimaryKeyQuery)); 20535 } 20536 20537 template <IDBCursorType CursorType> 20538 nsresult CommonOpenOpHelper<CursorType>::ProcessStatementSteps( 20539 mozIStorageStatement* const aStmt) { 20540 QM_TRY_INSPECT(const bool& hasResult, 20541 MOZ_TO_RESULT_INVOKE_MEMBER(aStmt, ExecuteStep)); 20542 20543 if (!hasResult) { 20544 SetResponse(void_t{}); 20545 return NS_OK; 20546 } 20547 20548 Key previousKey; 20549 auto* optPreviousKey = 20550 IsUnique(GetCursor().mDirection) ? &previousKey : nullptr; 20551 20552 QM_TRY_INSPECT(const auto& responseSize, 20553 PopulateResponseFromStatement(aStmt, true, optPreviousKey)); 20554 20555 // The degree to which extra responses on OpenOp can actually be used depends 20556 // on the parameters of subsequent ContinueOp operations, see also comment in 20557 // ContinueOp::DoDatabaseWork. 20558 // 20559 // TODO: We should somehow evaluate the effects of this. Maybe use a smaller 20560 // extra count than for ContinueOp? 20561 PopulateExtraResponses(aStmt, GetCursor().mMaxExtraCount, responseSize, 20562 "OpenOp"_ns, optPreviousKey); 20563 20564 return NS_OK; 20565 } 20566 20567 nsresult OpenOpHelper<IDBCursorType::ObjectStore>::DoDatabaseWork( 20568 DatabaseConnection* aConnection) { 20569 MOZ_ASSERT(aConnection); 20570 aConnection->AssertIsOnConnectionThread(); 20571 MOZ_ASSERT(GetCursor().mObjectStoreId); 20572 20573 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoObjectStoreDatabaseWork", DOM); 20574 20575 const bool usingKeyRange = GetOptionalKeyRange().isSome(); 20576 20577 const nsCString queryStart = "SELECT "_ns + kColumnNameKey + 20578 ", file_ids, data " 20579 "FROM object_data " 20580 "WHERE object_store_id = :"_ns + 20581 kStmtParamNameId; 20582 20583 const auto keyRangeClause = 20584 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange( 20585 GetOptionalKeyRange(), kColumnNameKey); 20586 20587 const auto& directionClause = MakeDirectionClause(GetCursor().mDirection); 20588 20589 // Note: Changing the number or order of SELECT columns in the query will 20590 // require changes to CursorOpBase::PopulateResponseFromStatement. 20591 const nsCString firstQuery = queryStart + keyRangeClause + directionClause + 20592 kOpenLimit + 20593 IntToCString(1 + GetCursor().mMaxExtraCount); 20594 20595 QM_TRY_INSPECT(const auto& stmt, 20596 aConnection->BorrowCachedStatement(firstQuery)); 20597 20598 QM_TRY(MOZ_TO_RESULT( 20599 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mObjectStoreId))); 20600 20601 if (usingKeyRange) { 20602 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement( 20603 GetOptionalKeyRange().ref(), &*stmt))); 20604 } 20605 20606 // Now we need to make the query for ContinueOp. 20607 PrepareKeyConditionClauses(directionClause, queryStart); 20608 20609 return ProcessStatementSteps(&*stmt); 20610 } 20611 20612 nsresult OpenOpHelper<IDBCursorType::ObjectStoreKey>::DoDatabaseWork( 20613 DatabaseConnection* aConnection) { 20614 MOZ_ASSERT(aConnection); 20615 aConnection->AssertIsOnConnectionThread(); 20616 MOZ_ASSERT(GetCursor().mObjectStoreId); 20617 20618 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoObjectStoreKeyDatabaseWork", DOM); 20619 20620 const bool usingKeyRange = GetOptionalKeyRange().isSome(); 20621 20622 const nsCString queryStart = "SELECT "_ns + kColumnNameKey + 20623 " FROM object_data " 20624 "WHERE object_store_id = :"_ns + 20625 kStmtParamNameId; 20626 20627 const auto keyRangeClause = 20628 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange( 20629 GetOptionalKeyRange(), kColumnNameKey); 20630 20631 const auto& directionClause = MakeDirectionClause(GetCursor().mDirection); 20632 20633 // Note: Changing the number or order of SELECT columns in the query will 20634 // require changes to CursorOpBase::PopulateResponseFromStatement. 20635 const nsCString firstQuery = 20636 queryStart + keyRangeClause + directionClause + kOpenLimit + "1"_ns; 20637 20638 QM_TRY_INSPECT(const auto& stmt, 20639 aConnection->BorrowCachedStatement(firstQuery)); 20640 20641 QM_TRY(MOZ_TO_RESULT( 20642 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mObjectStoreId))); 20643 20644 if (usingKeyRange) { 20645 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement( 20646 GetOptionalKeyRange().ref(), &*stmt))); 20647 } 20648 20649 // Now we need to make the query to get the next match. 20650 PrepareKeyConditionClauses(directionClause, queryStart); 20651 20652 return ProcessStatementSteps(&*stmt); 20653 } 20654 20655 nsresult OpenOpHelper<IDBCursorType::Index>::DoDatabaseWork( 20656 DatabaseConnection* aConnection) { 20657 MOZ_ASSERT(aConnection); 20658 aConnection->AssertIsOnConnectionThread(); 20659 MOZ_ASSERT(GetCursor().mObjectStoreId); 20660 MOZ_ASSERT(GetCursor().mIndexId); 20661 20662 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoIndexDatabaseWork", DOM); 20663 20664 const bool usingKeyRange = GetOptionalKeyRange().isSome(); 20665 20666 const auto indexTable = 20667 GetCursor().mUniqueIndex ? "unique_index_data"_ns : "index_data"_ns; 20668 20669 // The result of MakeColumnPairSelectionList is stored in a local variable, 20670 // since inlining it into the next statement causes a crash on some Mac OS X 20671 // builds (see https://bugzilla.mozilla.org/show_bug.cgi?id=1168606#c110). 20672 const auto columnPairSelectionList = MakeColumnPairSelectionList( 20673 "index_table.value"_ns, "index_table.value_locale"_ns, 20674 kColumnNameAliasSortKey, GetCursor().IsLocaleAware()); 20675 const nsCString sortColumnAlias = 20676 "SELECT "_ns + columnPairSelectionList + ", "_ns; 20677 20678 const nsAutoCString queryStart = sortColumnAlias + 20679 "index_table.object_data_key, " 20680 "object_data.file_ids, " 20681 "object_data.data " 20682 "FROM "_ns + 20683 indexTable + 20684 " AS index_table " 20685 "JOIN object_data " 20686 "ON index_table.object_store_id = " 20687 "object_data.object_store_id " 20688 "AND index_table.object_data_key = " 20689 "object_data.key " 20690 "WHERE index_table.index_id = :"_ns + 20691 kStmtParamNameId; 20692 20693 const auto keyRangeClause = 20694 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange( 20695 GetOptionalKeyRange(), kColumnNameAliasSortKey); 20696 20697 nsAutoCString directionClause = " ORDER BY "_ns + kColumnNameAliasSortKey; 20698 20699 switch (GetCursor().mDirection) { 20700 case IDBCursorDirection::Next: 20701 case IDBCursorDirection::Nextunique: 20702 directionClause.AppendLiteral(" ASC, index_table.object_data_key ASC"); 20703 break; 20704 20705 case IDBCursorDirection::Prev: 20706 directionClause.AppendLiteral(" DESC, index_table.object_data_key DESC"); 20707 break; 20708 20709 case IDBCursorDirection::Prevunique: 20710 directionClause.AppendLiteral(" DESC, index_table.object_data_key ASC"); 20711 break; 20712 20713 default: 20714 MOZ_CRASH("Should never get here!"); 20715 } 20716 20717 // Note: Changing the number or order of SELECT columns in the query will 20718 // require changes to CursorOpBase::PopulateResponseFromStatement. 20719 const nsCString firstQuery = queryStart + keyRangeClause + directionClause + 20720 kOpenLimit + 20721 IntToCString(1 + GetCursor().mMaxExtraCount); 20722 20723 QM_TRY_INSPECT(const auto& stmt, 20724 aConnection->BorrowCachedStatement(firstQuery)); 20725 20726 QM_TRY(MOZ_TO_RESULT( 20727 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mIndexId))); 20728 20729 if (usingKeyRange) { 20730 if (GetCursor().IsLocaleAware()) { 20731 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement( 20732 GetOptionalKeyRange().ref(), &*stmt, GetCursor().mLocale))); 20733 } else { 20734 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement( 20735 GetOptionalKeyRange().ref(), &*stmt))); 20736 } 20737 } 20738 20739 // TODO: At least the last two statements are almost the same in all 20740 // DoDatabaseWork variants, consider removing this duplication. 20741 20742 // Now we need to make the query to get the next match. 20743 PrepareKeyConditionClauses(directionClause, std::move(queryStart)); 20744 20745 return ProcessStatementSteps(&*stmt); 20746 } 20747 20748 nsresult OpenOpHelper<IDBCursorType::IndexKey>::DoDatabaseWork( 20749 DatabaseConnection* aConnection) { 20750 MOZ_ASSERT(aConnection); 20751 aConnection->AssertIsOnConnectionThread(); 20752 MOZ_ASSERT(GetCursor().mObjectStoreId); 20753 MOZ_ASSERT(GetCursor().mIndexId); 20754 20755 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoIndexKeyDatabaseWork", DOM); 20756 20757 const bool usingKeyRange = GetOptionalKeyRange().isSome(); 20758 20759 const auto table = 20760 GetCursor().mUniqueIndex ? "unique_index_data"_ns : "index_data"_ns; 20761 20762 // The result of MakeColumnPairSelectionList is stored in a local variable, 20763 // since inlining it into the next statement causes a crash on some Mac OS X 20764 // builds (see https://bugzilla.mozilla.org/show_bug.cgi?id=1168606#c110). 20765 const auto columnPairSelectionList = MakeColumnPairSelectionList( 20766 "value"_ns, "value_locale"_ns, kColumnNameAliasSortKey, 20767 GetCursor().IsLocaleAware()); 20768 const nsCString sortColumnAlias = 20769 "SELECT "_ns + columnPairSelectionList + ", "_ns; 20770 20771 const nsAutoCString queryStart = sortColumnAlias + 20772 "object_data_key " 20773 " FROM "_ns + 20774 table + " WHERE index_id = :"_ns + 20775 kStmtParamNameId; 20776 20777 const auto keyRangeClause = 20778 DatabaseOperationBase::MaybeGetBindingClauseForKeyRange( 20779 GetOptionalKeyRange(), kColumnNameAliasSortKey); 20780 20781 nsAutoCString directionClause = " ORDER BY "_ns + kColumnNameAliasSortKey; 20782 20783 switch (GetCursor().mDirection) { 20784 case IDBCursorDirection::Next: 20785 case IDBCursorDirection::Nextunique: 20786 directionClause.AppendLiteral(" ASC, object_data_key ASC"); 20787 break; 20788 20789 case IDBCursorDirection::Prev: 20790 directionClause.AppendLiteral(" DESC, object_data_key DESC"); 20791 break; 20792 20793 case IDBCursorDirection::Prevunique: 20794 directionClause.AppendLiteral(" DESC, object_data_key ASC"); 20795 break; 20796 20797 default: 20798 MOZ_CRASH("Should never get here!"); 20799 } 20800 20801 // Note: Changing the number or order of SELECT columns in the query will 20802 // require changes to CursorOpBase::PopulateResponseFromStatement. 20803 const nsCString firstQuery = 20804 queryStart + keyRangeClause + directionClause + kOpenLimit + "1"_ns; 20805 20806 QM_TRY_INSPECT(const auto& stmt, 20807 aConnection->BorrowCachedStatement(firstQuery)); 20808 20809 QM_TRY(MOZ_TO_RESULT( 20810 stmt->BindInt64ByName(kStmtParamNameId, GetCursor().mIndexId))); 20811 20812 if (usingKeyRange) { 20813 if (GetCursor().IsLocaleAware()) { 20814 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement( 20815 GetOptionalKeyRange().ref(), &*stmt, GetCursor().mLocale))); 20816 } else { 20817 QM_TRY(MOZ_TO_RESULT(DatabaseOperationBase::BindKeyRangeToStatement( 20818 GetOptionalKeyRange().ref(), &*stmt))); 20819 } 20820 } 20821 20822 // Now we need to make the query to get the next match. 20823 PrepareKeyConditionClauses(directionClause, std::move(queryStart)); 20824 20825 return ProcessStatementSteps(&*stmt); 20826 } 20827 20828 template <IDBCursorType CursorType> 20829 nsresult Cursor<CursorType>::OpenOp::DoDatabaseWork( 20830 DatabaseConnection* aConnection) { 20831 MOZ_ASSERT(aConnection); 20832 aConnection->AssertIsOnConnectionThread(); 20833 MOZ_ASSERT(mCursor); 20834 MOZ_ASSERT(!mCursor->mContinueQueries); 20835 20836 AUTO_PROFILER_LABEL("Cursor::OpenOp::DoDatabaseWork", DOM); 20837 20838 auto helper = OpenOpHelper<CursorType>{*this}; 20839 const auto rv = helper.DoDatabaseWork(aConnection); 20840 if (NS_WARN_IF(NS_FAILED(rv))) { 20841 return rv; 20842 } 20843 20844 return NS_OK; 20845 } 20846 20847 template <IDBCursorType CursorType> 20848 nsresult Cursor<CursorType>::CursorOpBase::SendSuccessResult() { 20849 AssertIsOnOwningThread(); 20850 MOZ_ASSERT(mCursor); 20851 MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this); 20852 MOZ_ASSERT(mResponse.type() != CursorResponse::T__None); 20853 20854 if (IsActorDestroyed()) { 20855 return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR; 20856 } 20857 20858 mCursor->SendResponseInternal(mResponse, mFiles); 20859 20860 #ifdef DEBUG 20861 mResponseSent = true; 20862 #endif 20863 return NS_OK; 20864 } 20865 20866 template <IDBCursorType CursorType> 20867 nsresult Cursor<CursorType>::ContinueOp::DoDatabaseWork( 20868 DatabaseConnection* aConnection) { 20869 MOZ_ASSERT(aConnection); 20870 aConnection->AssertIsOnConnectionThread(); 20871 MOZ_ASSERT(mCursor); 20872 MOZ_ASSERT(mCursor->mObjectStoreId); 20873 MOZ_ASSERT(!mCursor->mContinueQueries->mContinueQuery.IsEmpty()); 20874 MOZ_ASSERT(!mCursor->mContinueQueries->mContinueToQuery.IsEmpty()); 20875 MOZ_ASSERT(!mCurrentPosition.mKey.IsUnset()); 20876 20877 if constexpr (IsIndexCursor) { 20878 MOZ_ASSERT_IF( 20879 mCursor->mDirection == IDBCursorDirection::Next || 20880 mCursor->mDirection == IDBCursorDirection::Prev, 20881 !mCursor->mContinueQueries->mContinuePrimaryKeyQuery.IsEmpty()); 20882 MOZ_ASSERT(mCursor->mIndexId); 20883 MOZ_ASSERT(!mCurrentPosition.mObjectStoreKey.IsUnset()); 20884 } 20885 20886 AUTO_PROFILER_LABEL("Cursor::ContinueOp::DoDatabaseWork", DOM); 20887 20888 // We need to pick a query based on whether or not a key was passed to the 20889 // continue function. If not we'll grab the next item in the database that 20890 // is greater than (or less than, if we're running a PREV cursor) the current 20891 // key. If a key was passed we'll grab the next item in the database that is 20892 // greater than (or less than, if we're running a PREV cursor) or equal to the 20893 // key that was specified. 20894 // 20895 // TODO: The description above is not complete, it does not take account of 20896 // ContinuePrimaryKey nor Advance. 20897 // 20898 // Note: Changing the number or order of SELECT columns in the query will 20899 // require changes to CursorOpBase::PopulateResponseFromStatement. 20900 20901 const uint32_t advanceCount = 20902 mParams.type() == CursorRequestParams::TAdvanceParams 20903 ? mParams.get_AdvanceParams().count() 20904 : 1; 20905 MOZ_ASSERT(advanceCount > 0); 20906 20907 bool hasContinueKey = false; 20908 bool hasContinuePrimaryKey = false; 20909 20910 auto explicitContinueKey = Key{}; 20911 20912 switch (mParams.type()) { 20913 case CursorRequestParams::TContinueParams: 20914 if (!mParams.get_ContinueParams().key().IsUnset()) { 20915 hasContinueKey = true; 20916 explicitContinueKey = mParams.get_ContinueParams().key(); 20917 } 20918 break; 20919 case CursorRequestParams::TContinuePrimaryKeyParams: 20920 MOZ_ASSERT(!mParams.get_ContinuePrimaryKeyParams().key().IsUnset()); 20921 MOZ_ASSERT( 20922 !mParams.get_ContinuePrimaryKeyParams().primaryKey().IsUnset()); 20923 MOZ_ASSERT(mCursor->mDirection == IDBCursorDirection::Next || 20924 mCursor->mDirection == IDBCursorDirection::Prev); 20925 hasContinueKey = true; 20926 hasContinuePrimaryKey = true; 20927 explicitContinueKey = mParams.get_ContinuePrimaryKeyParams().key(); 20928 break; 20929 case CursorRequestParams::TAdvanceParams: 20930 break; 20931 default: 20932 MOZ_CRASH("Should never get here!"); 20933 } 20934 20935 // TODO: Whether it makes sense to preload depends on the kind of the 20936 // subsequent operations, not of the current operation. We could assume that 20937 // the subsequent operations are: 20938 // - the same as the current operation (with the same parameter values) 20939 // - as above, except for Advance, where we assume the count will be 1 on the 20940 // next call 20941 // - basic operations (Advance with count 1 or Continue-without-key) 20942 // 20943 // For now, we implement the second option for now (which correspond to 20944 // !hasContinueKey). 20945 // 20946 // Based on that, we could in both cases either preload for any assumed 20947 // subsequent operations, or only for the basic operations. For now, we 20948 // preload only for an assumed basic operation. Other operations would require 20949 // more work on the client side for invalidation, and may not make any sense 20950 // at all. 20951 const uint32_t maxExtraCount = hasContinueKey ? 0 : mCursor->mMaxExtraCount; 20952 20953 QM_TRY_INSPECT(const auto& stmt, 20954 aConnection->BorrowCachedStatement( 20955 mCursor->mContinueQueries->GetContinueQuery( 20956 hasContinueKey, hasContinuePrimaryKey))); 20957 20958 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName( 20959 kStmtParamNameLimit, 20960 IntToCString(advanceCount + mCursor->mMaxExtraCount)))); 20961 20962 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName(kStmtParamNameId, mCursor->Id()))); 20963 20964 // Bind current key. 20965 const auto& continueKey = 20966 hasContinueKey ? explicitContinueKey 20967 : mCurrentPosition.GetSortKey(mCursor->IsLocaleAware()); 20968 QM_TRY(MOZ_TO_RESULT( 20969 continueKey.BindToStatement(&*stmt, kStmtParamNameCurrentKey))); 20970 20971 // Bind range bound if it is specified. 20972 if (!mCursor->mLocaleAwareRangeBound->IsUnset()) { 20973 QM_TRY(MOZ_TO_RESULT(mCursor->mLocaleAwareRangeBound->BindToStatement( 20974 &*stmt, kStmtParamNameRangeBound))); 20975 } 20976 20977 // Bind object store position if duplicates are allowed and we're not 20978 // continuing to a specific key. 20979 if constexpr (IsIndexCursor) { 20980 if (!hasContinueKey && (mCursor->mDirection == IDBCursorDirection::Next || 20981 mCursor->mDirection == IDBCursorDirection::Prev)) { 20982 QM_TRY(MOZ_TO_RESULT(mCurrentPosition.mObjectStoreKey.BindToStatement( 20983 &*stmt, kStmtParamNameObjectStorePosition))); 20984 } else if (hasContinuePrimaryKey) { 20985 QM_TRY(MOZ_TO_RESULT( 20986 mParams.get_ContinuePrimaryKeyParams().primaryKey().BindToStatement( 20987 &*stmt, kStmtParamNameObjectStorePosition))); 20988 } 20989 } 20990 20991 // TODO: Why do we query the records we don't need and skip them here, rather 20992 // than using a OFFSET clause in the query? 20993 for (uint32_t index = 0; index < advanceCount; index++) { 20994 QM_TRY_INSPECT(const bool& hasResult, 20995 MOZ_TO_RESULT_INVOKE_MEMBER(&*stmt, ExecuteStep)); 20996 20997 if (!hasResult) { 20998 mResponse = void_t(); 20999 return NS_OK; 21000 } 21001 } 21002 21003 Key previousKey; 21004 auto* const optPreviousKey = 21005 IsUnique(mCursor->mDirection) ? &previousKey : nullptr; 21006 21007 auto helper = CursorOpBaseHelperBase<CursorType>{*this}; 21008 QM_TRY_INSPECT(const auto& responseSize, helper.PopulateResponseFromStatement( 21009 &*stmt, true, optPreviousKey)); 21010 21011 helper.PopulateExtraResponses(&*stmt, maxExtraCount, responseSize, 21012 "ContinueOp"_ns, optPreviousKey); 21013 21014 return NS_OK; 21015 } 21016 21017 Utils::Utils() 21018 #ifdef DEBUG 21019 : mActorDestroyed(false) 21020 #endif 21021 { 21022 AssertIsOnBackgroundThread(); 21023 } 21024 21025 Utils::~Utils() { MOZ_ASSERT(mActorDestroyed); } 21026 21027 void Utils::ActorDestroy(ActorDestroyReason aWhy) { 21028 AssertIsOnBackgroundThread(); 21029 MOZ_ASSERT(!mActorDestroyed); 21030 21031 #ifdef DEBUG 21032 mActorDestroyed = true; 21033 #endif 21034 } 21035 21036 mozilla::ipc::IPCResult Utils::RecvDeleteMe() { 21037 AssertIsOnBackgroundThread(); 21038 MOZ_ASSERT(!mActorDestroyed); 21039 21040 QM_WARNONLY_TRY(OkIf(PBackgroundIndexedDBUtilsParent::Send__delete__(this))); 21041 21042 return IPC_OK(); 21043 } 21044 21045 mozilla::ipc::IPCResult Utils::RecvGetFileReferences( 21046 const PersistenceType& aPersistenceType, const nsACString& aOrigin, 21047 const nsAString& aDatabaseName, const int64_t& aFileId, int32_t* aRefCnt, 21048 int32_t* aDBRefCnt, bool* aResult) { 21049 AssertIsOnBackgroundThread(); 21050 MOZ_ASSERT(aRefCnt); 21051 MOZ_ASSERT(aDBRefCnt); 21052 MOZ_ASSERT(aResult); 21053 MOZ_ASSERT(!mActorDestroyed); 21054 21055 if (NS_WARN_IF(!IndexedDatabaseManager::Get())) { 21056 return IPC_FAIL(this, "No IndexedDatabaseManager active!"); 21057 } 21058 21059 if (NS_WARN_IF(!QuotaManager::Get())) { 21060 return IPC_FAIL(this, "No QuotaManager active!"); 21061 } 21062 21063 if (NS_WARN_IF(!StaticPrefs::dom_indexedDB_testing())) { 21064 return IPC_FAIL(this, "IndexedDB is not in testing mode!"); 21065 } 21066 21067 if (NS_WARN_IF(!IsValidPersistenceType(aPersistenceType))) { 21068 return IPC_FAIL(this, "PersistenceType is not valid!"); 21069 } 21070 21071 if (NS_WARN_IF(aOrigin.IsEmpty())) { 21072 return IPC_FAIL(this, "Origin is empty!"); 21073 } 21074 21075 if (NS_WARN_IF(aDatabaseName.IsEmpty())) { 21076 return IPC_FAIL(this, "DatabaseName is empty!"); 21077 } 21078 21079 if (NS_WARN_IF(aFileId == 0)) { 21080 return IPC_FAIL(this, "No FileId!"); 21081 } 21082 21083 nsresult rv = 21084 DispatchAndReturnFileReferences(aPersistenceType, aOrigin, aDatabaseName, 21085 aFileId, aRefCnt, aDBRefCnt, aResult); 21086 if (NS_WARN_IF(NS_FAILED(rv))) { 21087 return IPC_FAIL(this, "DispatchAndReturnFileReferences failed!"); 21088 } 21089 21090 return IPC_OK(); 21091 } 21092 21093 mozilla::ipc::IPCResult Utils::RecvDoMaintenance( 21094 DoMaintenanceResolver&& aResolver) { 21095 AssertIsOnBackgroundThread(); 21096 21097 QM_TRY(MOZ_TO_RESULT(!QuotaManager::IsShuttingDown()), 21098 ResolveNSResultAndReturn(aResolver)); 21099 21100 QM_TRY(QuotaManager::EnsureCreated(), ResolveNSResultAndReturn(aResolver)); 21101 21102 QuotaClient* quotaClient = QuotaClient::GetInstance(); 21103 QM_TRY(MOZ_TO_RESULT(quotaClient), QM_IPC_FAIL(this)); 21104 21105 quotaClient->DoMaintenance()->Then( 21106 GetCurrentSerialEventTarget(), __func__, 21107 [self = RefPtr(this), resolver = std::move(aResolver)]( 21108 const BoolPromise::ResolveOrRejectValue& aValue) { 21109 if (!self->CanSend()) { 21110 return; 21111 } 21112 21113 if (aValue.IsResolve()) { 21114 resolver(NS_OK); 21115 } else { 21116 resolver(aValue.RejectValue()); 21117 } 21118 }); 21119 21120 return IPC_OK(); 21121 } 21122 21123 #ifdef DEBUG 21124 21125 NS_IMPL_ISUPPORTS(DEBUGThreadSlower, nsIThreadObserver) 21126 21127 NS_IMETHODIMP 21128 DEBUGThreadSlower::OnDispatchedEvent() { MOZ_CRASH("Should never be called!"); } 21129 21130 NS_IMETHODIMP 21131 DEBUGThreadSlower::OnProcessNextEvent(nsIThreadInternal* /* aThread */, 21132 bool /* aMayWait */) { 21133 return NS_OK; 21134 } 21135 21136 NS_IMETHODIMP 21137 DEBUGThreadSlower::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, 21138 bool /* aEventWasProcessed */) { 21139 MOZ_ASSERT(kDEBUGThreadSleepMS); 21140 21141 MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(kDEBUGThreadSleepMS)) == 21142 PR_SUCCESS); 21143 return NS_OK; 21144 } 21145 21146 #endif // DEBUG 21147 21148 nsresult FileHelper::Init() { 21149 MOZ_ASSERT(!IsOnBackgroundThread()); 21150 21151 auto fileDirectory = mFileManager->GetCheckedDirectory(); 21152 if (NS_WARN_IF(!fileDirectory)) { 21153 return NS_ERROR_FAILURE; 21154 } 21155 21156 auto journalDirectory = mFileManager->EnsureJournalDirectory(); 21157 if (NS_WARN_IF(!journalDirectory)) { 21158 return NS_ERROR_FAILURE; 21159 } 21160 21161 DebugOnly<bool> exists; 21162 MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists))); 21163 MOZ_ASSERT(exists); 21164 21165 DebugOnly<bool> isDirectory; 21166 MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory))); 21167 MOZ_ASSERT(isDirectory); 21168 21169 mFileDirectory.init(WrapNotNullUnchecked(std::move(fileDirectory))); 21170 mJournalDirectory.init(WrapNotNullUnchecked(std::move(journalDirectory))); 21171 21172 return NS_OK; 21173 } 21174 21175 nsCOMPtr<nsIFile> FileHelper::GetFile(const DatabaseFileInfo& aFileInfo) { 21176 MOZ_ASSERT(!IsOnBackgroundThread()); 21177 21178 return mFileManager->GetFileForId(mFileDirectory->get(), aFileInfo.Id()); 21179 } 21180 21181 nsCOMPtr<nsIFile> FileHelper::GetJournalFile( 21182 const DatabaseFileInfo& aFileInfo) { 21183 MOZ_ASSERT(!IsOnBackgroundThread()); 21184 21185 return mFileManager->GetFileForId(mJournalDirectory->get(), aFileInfo.Id()); 21186 } 21187 21188 nsresult FileHelper::CreateFileFromStream(nsIFile& aFile, nsIFile& aJournalFile, 21189 nsIInputStream& aInputStream, 21190 bool aCompress, 21191 const Maybe<CipherKey>& aMaybeKey) { 21192 MOZ_ASSERT(!IsOnBackgroundThread()); 21193 21194 QM_TRY_INSPECT(const auto& exists, 21195 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists)); 21196 21197 // DOM blobs that are being stored in IDB are cached by calling 21198 // IDBDatabase::GetOrCreateFileActorForBlob. So if the same DOM blob is stored 21199 // again under a different key or in a different object store, we just add 21200 // a new reference instead of creating a new copy (all such stored blobs share 21201 // the same id). 21202 // However, it can happen that CreateFileFromStream failed due to quota 21203 // exceeded error and for some reason the orphaned file couldn't be deleted 21204 // immediately. Now, if the operation is being repeated, the DOM blob is 21205 // already cached, so it has the same file id which clashes with the orphaned 21206 // file. We could do some tricks to restore previous copy loop, but it's safer 21207 // to just delete the orphaned file and start from scratch. 21208 // This corner case is partially simulated in test_file_copy_failure.js 21209 if (exists) { 21210 QM_TRY_INSPECT(const auto& isFile, 21211 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, IsFile)); 21212 21213 QM_TRY(OkIf(isFile), NS_ERROR_FAILURE); 21214 21215 QM_TRY_INSPECT(const auto& journalExists, 21216 MOZ_TO_RESULT_INVOKE_MEMBER(aJournalFile, Exists)); 21217 21218 QM_TRY(OkIf(journalExists), NS_ERROR_FAILURE); 21219 21220 QM_TRY_INSPECT(const auto& journalIsFile, 21221 MOZ_TO_RESULT_INVOKE_MEMBER(aJournalFile, IsFile)); 21222 21223 QM_TRY(OkIf(journalIsFile), NS_ERROR_FAILURE); 21224 21225 IDB_WARNING("Deleting orphaned file!"); 21226 21227 QM_TRY(MOZ_TO_RESULT(mFileManager->SyncDeleteFile(aFile, aJournalFile))); 21228 } 21229 21230 // Create a journal file first. 21231 QM_TRY(MOZ_TO_RESULT(aJournalFile.Create(nsIFile::NORMAL_FILE_TYPE, 0644))); 21232 21233 // Now try to copy the stream. 21234 QM_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileOutputStream, 21235 CreateFileOutputStream(mFileManager->Type(), 21236 mFileManager->OriginMetadata(), 21237 Client::IDB, &aFile)); 21238 21239 AutoTArray<char, kFileCopyBufferSize> buffer; 21240 const auto actualOutputStream = 21241 [aCompress, &aMaybeKey, &buffer, 21242 baseOutputStream = 21243 std::move(fileOutputStream)]() mutable -> nsCOMPtr<nsIOutputStream> { 21244 if (aMaybeKey) { 21245 baseOutputStream = 21246 MakeRefPtr<EncryptingOutputStream<IndexedDBCipherStrategy>>( 21247 std::move(baseOutputStream), kEncryptedStreamBlockSize, 21248 *aMaybeKey); 21249 } 21250 21251 if (aCompress) { 21252 auto snappyOutputStream = 21253 MakeRefPtr<SnappyCompressOutputStream>(baseOutputStream); 21254 21255 buffer.SetLength(snappyOutputStream->BlockSize()); 21256 21257 return snappyOutputStream; 21258 } 21259 21260 buffer.SetLength(kFileCopyBufferSize); 21261 return std::move(baseOutputStream); 21262 }(); 21263 21264 QM_TRY(MOZ_TO_RESULT(SyncCopy(aInputStream, *actualOutputStream, 21265 buffer.Elements(), buffer.Length()))); 21266 21267 return NS_OK; 21268 } 21269 21270 class FileHelper::ReadCallback final : public nsIInputStreamCallback { 21271 public: 21272 NS_DECL_THREADSAFE_ISUPPORTS 21273 21274 ReadCallback() 21275 : mMutex("ReadCallback::mMutex"), 21276 mCondVar(mMutex, "ReadCallback::mCondVar"), 21277 mInputAvailable(false) {} 21278 21279 NS_IMETHOD 21280 OnInputStreamReady(nsIAsyncInputStream* aStream) override { 21281 mozilla::MutexAutoLock autolock(mMutex); 21282 21283 mInputAvailable = true; 21284 mCondVar.Notify(); 21285 21286 return NS_OK; 21287 } 21288 21289 nsresult AsyncWait(nsIAsyncInputStream* aStream, uint32_t aBufferSize, 21290 nsIEventTarget* aTarget) { 21291 MOZ_ASSERT(aStream); 21292 mozilla::MutexAutoLock autolock(mMutex); 21293 21294 nsresult rv = aStream->AsyncWait(this, 0, aBufferSize, aTarget); 21295 if (NS_WARN_IF(NS_FAILED(rv))) { 21296 return rv; 21297 } 21298 21299 mInputAvailable = false; 21300 while (!mInputAvailable) { 21301 mCondVar.Wait(); 21302 } 21303 21304 return NS_OK; 21305 } 21306 21307 private: 21308 ~ReadCallback() = default; 21309 21310 mozilla::Mutex mMutex MOZ_UNANNOTATED; 21311 mozilla::CondVar mCondVar; 21312 bool mInputAvailable; 21313 }; 21314 21315 NS_IMPL_ADDREF(FileHelper::ReadCallback); 21316 NS_IMPL_RELEASE(FileHelper::ReadCallback); 21317 21318 NS_INTERFACE_MAP_BEGIN(FileHelper::ReadCallback) 21319 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) 21320 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamCallback) 21321 NS_INTERFACE_MAP_END 21322 21323 nsresult FileHelper::SyncRead(nsIInputStream& aInputStream, char* const aBuffer, 21324 const uint32_t aBufferSize, 21325 uint32_t* const aRead) { 21326 MOZ_ASSERT(!IsOnBackgroundThread()); 21327 21328 // Let's try to read, directly. 21329 nsresult rv = aInputStream.Read(aBuffer, aBufferSize, aRead); 21330 if (NS_SUCCEEDED(rv) || rv != NS_BASE_STREAM_WOULD_BLOCK) { 21331 return rv; 21332 } 21333 21334 // We need to proceed async. 21335 nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(&aInputStream); 21336 if (!asyncStream) { 21337 return rv; 21338 } 21339 21340 if (!mReadCallback) { 21341 mReadCallback.init(MakeNotNull<RefPtr<ReadCallback>>()); 21342 } 21343 21344 // We just need any thread with an event loop for receiving the 21345 // OnInputStreamReady callback. Let's use the I/O thread. 21346 nsCOMPtr<nsIEventTarget> target = 21347 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 21348 MOZ_ASSERT(target); 21349 21350 rv = (*mReadCallback)->AsyncWait(asyncStream, aBufferSize, target); 21351 if (NS_WARN_IF(NS_FAILED(rv))) { 21352 return rv; 21353 } 21354 21355 return SyncRead(aInputStream, aBuffer, aBufferSize, aRead); 21356 } 21357 21358 nsresult FileHelper::SyncCopy(nsIInputStream& aInputStream, 21359 nsIOutputStream& aOutputStream, 21360 char* const aBuffer, const uint32_t aBufferSize) { 21361 MOZ_ASSERT(!IsOnBackgroundThread()); 21362 21363 AUTO_PROFILER_LABEL("FileHelper::SyncCopy", DOM); 21364 21365 nsresult rv; 21366 21367 do { 21368 uint32_t numRead; 21369 rv = SyncRead(aInputStream, aBuffer, aBufferSize, &numRead); 21370 if (NS_WARN_IF(NS_FAILED(rv))) { 21371 break; 21372 } 21373 21374 if (!numRead) { 21375 break; 21376 } 21377 21378 uint32_t numWrite; 21379 rv = aOutputStream.Write(aBuffer, numRead, &numWrite); 21380 if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { 21381 rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; 21382 } 21383 if (NS_WARN_IF(NS_FAILED(rv))) { 21384 break; 21385 } 21386 21387 if (NS_WARN_IF(numWrite != numRead)) { 21388 rv = NS_ERROR_FAILURE; 21389 break; 21390 } 21391 } while (true); 21392 21393 if (NS_SUCCEEDED(rv)) { 21394 rv = aOutputStream.Flush(); 21395 if (NS_WARN_IF(NS_FAILED(rv))) { 21396 return rv; 21397 } 21398 } 21399 21400 nsresult rv2 = aOutputStream.Close(); 21401 if (NS_WARN_IF(NS_FAILED(rv2))) { 21402 return NS_SUCCEEDED(rv) ? rv2 : rv; 21403 } 21404 21405 return rv; 21406 } 21407 21408 } // namespace dom::indexedDB 21409 } // namespace mozilla 21410 21411 #undef IDB_MOBILE 21412 #undef IDB_DEBUG_LOG