ActorsParent.cpp (282955B)
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 // Local includes 10 #include "LSInitializationTypes.h" 11 #include "LSObject.h" 12 #include "ReportInternalError.h" 13 14 // Global includes 15 #include <cinttypes> 16 #include <cstdlib> 17 #include <cstring> 18 #include <new> 19 #include <type_traits> 20 #include <utility> 21 22 #include "ErrorList.h" 23 #include "MainThreadUtils.h" 24 #include "NotifyUtils.h" 25 #include "mozIStorageAsyncConnection.h" 26 #include "mozIStorageConnection.h" 27 #include "mozIStorageFunction.h" 28 #include "mozIStorageService.h" 29 #include "mozIStorageStatement.h" 30 #include "mozIStorageValueArray.h" 31 #include "mozStorageCID.h" 32 #include "mozStorageHelper.h" 33 #include "mozilla/Assertions.h" 34 #include "mozilla/Atomics.h" 35 #include "mozilla/Attributes.h" 36 #include "mozilla/DebugOnly.h" 37 #include "mozilla/GeckoTrace.h" 38 #include "mozilla/Logging.h" 39 #include "mozilla/Maybe.h" 40 #include "mozilla/Monitor.h" 41 #include "mozilla/Mutex.h" 42 #include "mozilla/NotNull.h" 43 #include "mozilla/OriginAttributes.h" 44 #include "mozilla/Preferences.h" 45 #include "mozilla/RefPtr.h" 46 #include "mozilla/Result.h" 47 #include "mozilla/ResultExtensions.h" 48 #include "mozilla/ScopeExit.h" 49 #include "mozilla/Services.h" 50 #include "mozilla/StaticPrefs_dom.h" 51 #include "mozilla/StaticPtr.h" 52 #include "mozilla/StoragePrincipalHelper.h" 53 #include "mozilla/UniquePtr.h" 54 #include "mozilla/Variant.h" 55 #include "mozilla/dom/ClientManagerService.h" 56 #include "mozilla/dom/FlippedOnce.h" 57 #include "mozilla/dom/LSSnapshot.h" 58 #include "mozilla/dom/LSValue.h" 59 #include "mozilla/dom/LSWriteOptimizer.h" 60 #include "mozilla/dom/LSWriteOptimizerImpl.h" 61 #include "mozilla/dom/LocalStorageCommon.h" 62 #include "mozilla/dom/Nullable.h" 63 #include "mozilla/dom/PBackgroundLSDatabase.h" 64 #include "mozilla/dom/PBackgroundLSDatabaseParent.h" 65 #include "mozilla/dom/PBackgroundLSObserverParent.h" 66 #include "mozilla/dom/PBackgroundLSRequestParent.h" 67 #include "mozilla/dom/PBackgroundLSSharedTypes.h" 68 #include "mozilla/dom/PBackgroundLSSimpleRequestParent.h" 69 #include "mozilla/dom/PBackgroundLSSnapshotParent.h" 70 #include "mozilla/dom/SnappyUtils.h" 71 #include "mozilla/dom/StorageDBUpdater.h" 72 #include "mozilla/dom/StorageUtils.h" 73 #include "mozilla/dom/ipc/IdType.h" 74 #include "mozilla/dom/quota/CachingDatabaseConnection.h" 75 #include "mozilla/dom/quota/CheckedUnsafePtr.h" 76 #include "mozilla/dom/quota/Client.h" 77 #include "mozilla/dom/quota/ClientDirectoryLock.h" 78 #include "mozilla/dom/quota/ClientDirectoryLockHandle.h" 79 #include "mozilla/dom/quota/ClientImpl.h" 80 #include "mozilla/dom/quota/FirstInitializationAttemptsImpl.h" 81 #include "mozilla/dom/quota/HashKeys.h" 82 #include "mozilla/dom/quota/OriginScope.h" 83 #include "mozilla/dom/quota/PersistenceScope.h" 84 #include "mozilla/dom/quota/PersistenceType.h" 85 #include "mozilla/dom/quota/PrincipalUtils.h" 86 #include "mozilla/dom/quota/QuotaCommon.h" 87 #include "mozilla/dom/quota/QuotaManager.h" 88 #include "mozilla/dom/quota/QuotaObject.h" 89 #include "mozilla/dom/quota/ResultExtensions.h" 90 #include "mozilla/dom/quota/StorageHelpers.h" 91 #include "mozilla/dom/quota/ThreadUtils.h" 92 #include "mozilla/dom/quota/UsageInfo.h" 93 #include "mozilla/glean/DomLocalstorageMetrics.h" 94 #include "mozilla/ipc/BackgroundChild.h" 95 #include "mozilla/ipc/BackgroundParent.h" 96 #include "mozilla/ipc/PBackgroundChild.h" 97 #include "mozilla/ipc/PBackgroundParent.h" 98 #include "mozilla/ipc/PBackgroundSharedTypes.h" 99 #include "mozilla/ipc/ProtocolUtils.h" 100 #include "mozilla/storage/Variant.h" 101 #include "nsBaseHashtable.h" 102 #include "nsCOMPtr.h" 103 #include "nsClassHashtable.h" 104 #include "nsDebug.h" 105 #include "nsError.h" 106 #include "nsHashKeys.h" 107 #include "nsIBinaryInputStream.h" 108 #include "nsIBinaryOutputStream.h" 109 #include "nsIDirectoryEnumerator.h" 110 #include "nsIEventTarget.h" 111 #include "nsIFile.h" 112 #include "nsIInputStream.h" 113 #include "nsIObjectInputStream.h" 114 #include "nsIObjectOutputStream.h" 115 #include "nsIObserver.h" 116 #include "nsIObserverService.h" 117 #include "nsIOutputStream.h" 118 #include "nsIRunnable.h" 119 #include "nsISerialEventTarget.h" 120 #include "nsISupports.h" 121 #include "nsIThread.h" 122 #include "nsITimer.h" 123 #include "nsIVariant.h" 124 #include "nsInterfaceHashtable.h" 125 #include "nsLiteralString.h" 126 #include "nsNetUtil.h" 127 #include "nsPointerHashKeys.h" 128 #include "nsPrintfCString.h" 129 #include "nsRefPtrHashtable.h" 130 #include "nsServiceManagerUtils.h" 131 #include "nsString.h" 132 #include "nsStringFlags.h" 133 #include "nsStringFwd.h" 134 #include "nsTArray.h" 135 #include "nsTHashMap.h" 136 #include "nsTHashSet.h" 137 #include "nsTLiteralString.h" 138 #include "nsTStringRepr.h" 139 #include "nsThreadUtils.h" 140 #include "nsVariant.h" 141 #include "nsXPCOM.h" 142 #include "nsXULAppAPI.h" 143 #include "nscore.h" 144 #include "prenv.h" 145 #include "prtime.h" 146 147 #define LS_LOG_TEST() MOZ_LOG_TEST(GetLocalStorageLogger(), LogLevel::Info) 148 #define LS_LOG(_args) MOZ_LOG(GetLocalStorageLogger(), LogLevel::Info, _args) 149 150 #if defined(MOZ_WIDGET_ANDROID) 151 # define LS_MOBILE 152 #endif 153 154 namespace mozilla::dom { 155 156 using namespace mozilla::dom::quota; 157 using namespace mozilla::dom::StorageUtils; 158 using namespace mozilla::ipc; 159 160 namespace { 161 162 struct ArchivedOriginInfo; 163 class ArchivedOriginScope; 164 class Connection; 165 class ConnectionThread; 166 class Database; 167 class Observer; 168 class PrepareDatastoreOp; 169 class PreparedDatastore; 170 class QuotaClient; 171 class Snapshot; 172 173 using ArchivedOriginHashtable = 174 nsClassHashtable<nsCStringHashKey, ArchivedOriginInfo>; 175 176 /******************************************************************************* 177 * Constants 178 ******************************************************************************/ 179 180 // Major schema version. Bump for almost everything. 181 const uint32_t kMajorSchemaVersion = 5; 182 183 // Minor schema version. Should almost always be 0 (maybe bump on release 184 // branches if we have to). 185 const uint32_t kMinorSchemaVersion = 0; 186 187 // The schema version we store in the SQLite database is a (signed) 32-bit 188 // integer. The major version is left-shifted 4 bits so the max value is 189 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF. 190 static_assert(kMajorSchemaVersion <= 0xFFFFFFF, 191 "Major version needs to fit in 28 bits."); 192 static_assert(kMinorSchemaVersion <= 0xF, 193 "Minor version needs to fit in 4 bits."); 194 195 const int32_t kSQLiteSchemaVersion = 196 int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion); 197 198 // Changing the value here will override the page size of new databases only. 199 // A journal mode change and VACUUM are needed to change existing databases, so 200 // the best way to do that is to use the schema version upgrade mechanism. 201 const uint32_t kSQLitePageSizeOverride = 202 #ifdef LS_MOBILE 203 512; 204 #else 205 1024; 206 #endif 207 208 static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 || 209 (kSQLitePageSizeOverride % 2 == 0 && 210 kSQLitePageSizeOverride >= 512 && 211 kSQLitePageSizeOverride <= 65536), 212 "Must be 0 (disabled) or a power of 2 between 512 and 65536!"); 213 214 // Set to some multiple of the page size to grow the database in larger chunks. 215 const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2; 216 217 static_assert(kSQLiteGrowthIncrement >= 0 && 218 kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 && 219 kSQLiteGrowthIncrement < uint32_t(INT32_MAX), 220 "Must be 0 (disabled) or a positive multiple of the page size!"); 221 222 /** 223 * The database name for LocalStorage data in a per-origin directory. 224 */ 225 constexpr auto kDataFileName = u"data.sqlite"_ns; 226 227 /** 228 * The journal corresponding to kDataFileName. (We don't use WAL mode.) 229 * Currently only needed in QuotaClient::InitOrigin and only in DEBUG builds. 230 * See the corresponding comment in QuotaClient::InitOrigin. 231 */ 232 #ifdef DEBUG 233 constexpr auto kJournalFileName = u"data.sqlite-journal"_ns; 234 #endif 235 236 /** 237 * This file contains the current usage of the LocalStorage database as defined 238 * by the mozLength totals of all keys and values for the database, which 239 * differs from the actual size on disk. We store this value in a separate 240 * file as a cache so that we can initialize the QuotaClient faster. 241 * In the future, this file will be eliminated and the information will be 242 * stored in PROFILE/storage.sqlite or similar QuotaManager-wide storage. 243 * 244 * The file contains a binary verification cookie (32-bits) followed by the 245 * actual usage (64-bits). 246 */ 247 constexpr auto kUsageFileName = u"usage"_ns; 248 249 /** 250 * Following a QuotaManager idiom, this journal file's existence is a marker 251 * that the usage file was in the process of being updated and is currently 252 * invalid. This file is created prior to updating the usage file and only 253 * deleted after the usage file has been written and closed and any pending 254 * database transactions have been committed. Note that this idiom is expected 255 * to work if Gecko crashes in the middle of a write, but is not expected to be 256 * foolproof in the face of a system crash, as we do not explicitly attempt to 257 * fsync the directory containing the journal file. 258 * 259 * If the journal file is found to exist at origin initialization time, the 260 * usage will be re-computed from the current state of DATA_FILE_NAME. 261 */ 262 constexpr auto kUsageJournalFileName = u"usage-journal"_ns; 263 264 static const uint32_t kUsageFileSize = 12; 265 static const uint32_t kUsageFileCookie = 0x420a420a; 266 267 /** 268 * How long between the first moment we know we have data to be written on a 269 * `Connection` and when we should actually perform the write. This helps 270 * limit disk churn under silly usage patterns and is historically consistent 271 * with the previous, legacy implementation. 272 * 273 * Note that flushing happens downstream of Snapshot checkpointing and its 274 * batch mechanism which helps avoid wasteful IPC in the case of silly content 275 * code. 276 */ 277 const uint32_t kFlushTimeoutMs = 5000; 278 279 const bool kDefaultShadowWrites = false; 280 const uint32_t kDefaultSnapshotPrefill = 16384; 281 const uint32_t kDefaultSnapshotGradualPrefill = 4096; 282 const bool kDefaultClientValidation = true; 283 /** 284 * Should all mutations also be reflected in the "shadow" database, which is 285 * the legacy webappsstore.sqlite database. When this is enabled, users can 286 * downgrade their version of Firefox and/or otherwise fall back to the legacy 287 * implementation without loss of data. (Older versions of Firefox will 288 * recognize the presence of ls-archive.sqlite and purge it and the other 289 * LocalStorage directories so privacy is maintained.) 290 */ 291 const char kShadowWritesPref[] = "dom.storage.shadow_writes"; 292 /** 293 * Byte budget for sending data down to the LSSnapshot instance when it is first 294 * created. If there is less data than this (measured by tallying the string 295 * length of the keys and values), all data is sent, otherwise partial data is 296 * sent. See `Snapshot`. 297 */ 298 const char kSnapshotPrefillPref[] = "dom.storage.snapshot_prefill"; 299 /** 300 * When a specific value is requested by an LSSnapshot that is not already fully 301 * populated, gradual prefill is used. This preference specifies the number of 302 * bytes to be used to send values beyond the specific value that is requested. 303 * (The size of the explicitly requested value does not impact this preference.) 304 * Setting the value to 0 disables gradual prefill. Tests may set this value to 305 * -1 which is converted to INT_MAX in order to cause gradual prefill to send 306 * all values not previously sent. 307 */ 308 const char kSnapshotGradualPrefillPref[] = 309 "dom.storage.snapshot_gradual_prefill"; 310 311 const char kClientValidationPref[] = "dom.storage.client_validation"; 312 313 /** 314 * The amount of time a PreparedDatastore instance should stick around after a 315 * preload is triggered in order to give time for the page to use LocalStorage 316 * without triggering worst-case synchronous jank. 317 */ 318 const uint32_t kPreparedDatastoreTimeoutMs = 20000; 319 320 /** 321 * Cold storage for LocalStorage data extracted from webappsstore.sqlite at 322 * LSNG first-run that has not yet been migrated to its own per-origin directory 323 * by use. 324 * 325 * In other words, at first run, LSNG copies the contents of webappsstore.sqlite 326 * into this database. As requests are made for that LocalStorage data, the 327 * contents are removed from this database and placed into per-origin QM 328 * storage. So the contents of this database are always old, unused 329 * LocalStorage data that we can potentially get rid of at some point in the 330 * future. 331 */ 332 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite" 333 /** 334 * The legacy LocalStorage database. Its contents are maintained as our 335 * "shadow" database so that LSNG can be disabled without loss of user data. 336 */ 337 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite" 338 339 // Shadow database Write Ahead Log's maximum size is 512KB 340 const uint32_t kShadowMaxWALSize = 512 * 1024; 341 342 bool IsOnGlobalConnectionThread(); 343 344 void AssertIsOnGlobalConnectionThread(); 345 346 /******************************************************************************* 347 * SQLite functions 348 ******************************************************************************/ 349 350 int32_t MakeSchemaVersion(uint32_t aMajorSchemaVersion, 351 uint32_t aMinorSchemaVersion) { 352 return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion); 353 } 354 355 nsCString GetArchivedOriginHashKey(const nsACString& aOriginSuffix, 356 const nsACString& aOriginNoSuffix) { 357 return aOriginSuffix + ":"_ns + aOriginNoSuffix; 358 } 359 360 nsresult CreateDataTable(mozIStorageConnection* aConnection) { 361 return aConnection->ExecuteSimpleSQL( 362 "CREATE TABLE data" 363 "( key TEXT PRIMARY KEY" 364 ", utf16_length INTEGER NOT NULL" 365 ", conversion_type INTEGER NOT NULL" 366 ", compression_type INTEGER NOT NULL" 367 ", last_access_time INTEGER NOT NULL DEFAULT 0" 368 ", value BLOB NOT NULL" 369 ");"_ns); 370 } 371 372 nsresult CreateTables(mozIStorageConnection* aConnection) { 373 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 374 MOZ_ASSERT(aConnection); 375 376 // Table `database` 377 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 378 "CREATE TABLE database" 379 "( origin TEXT NOT NULL" 380 ", usage INTEGER NOT NULL DEFAULT 0" 381 ", last_vacuum_time INTEGER NOT NULL DEFAULT 0" 382 ", last_analyze_time INTEGER NOT NULL DEFAULT 0" 383 ", last_vacuum_size INTEGER NOT NULL DEFAULT 0" 384 ");"_ns))); 385 386 // Table `data` 387 QM_TRY(MOZ_TO_RESULT(CreateDataTable(aConnection))); 388 389 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kSQLiteSchemaVersion))); 390 391 return NS_OK; 392 } 393 394 nsresult UpgradeSchemaFrom1_0To2_0(mozIStorageConnection* aConnection) { 395 AssertIsOnIOThread(); 396 MOZ_ASSERT(aConnection); 397 398 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 399 "ALTER TABLE database ADD COLUMN usage INTEGER NOT NULL DEFAULT 0;"_ns))); 400 401 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 402 "UPDATE database " 403 "SET usage = (SELECT total(utf16Length(key) + utf16Length(value)) " 404 "FROM data);"_ns))); 405 406 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0)))); 407 408 return NS_OK; 409 } 410 411 nsresult UpgradeSchemaFrom2_0To3_0(mozIStorageConnection* aConnection) { 412 AssertIsOnIOThread(); 413 MOZ_ASSERT(aConnection); 414 415 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 416 "ALTER TABLE data ADD COLUMN utf16Length INTEGER NOT NULL DEFAULT 0;"_ns))); 417 418 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 419 "UPDATE data SET utf16Length = utf16Length(value);"_ns))); 420 421 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(3, 0)))); 422 423 return NS_OK; 424 } 425 426 nsresult UpgradeSchemaFrom3_0To4_0(mozIStorageConnection* aConnection) { 427 AssertIsOnIOThread(); 428 MOZ_ASSERT(aConnection); 429 430 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(4, 0)))); 431 432 return NS_OK; 433 } 434 435 nsresult UpgradeSchemaFrom4_0To5_0(mozIStorageConnection* aConnection) { 436 AssertIsOnIOThread(); 437 MOZ_ASSERT(aConnection); 438 439 // Recreate data table in new format following steps at 440 // https://www.sqlite.org/lang_altertable.html 441 // section "Making Other Kinds Of Table Schema Changes" 442 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 443 "CREATE TABLE migrated_data" 444 "( key TEXT PRIMARY KEY" 445 ", utf16_length INTEGER NOT NULL" 446 ", conversion_type INTEGER NOT NULL" 447 ", compression_type INTEGER NOT NULL" 448 ", last_access_time INTEGER NOT NULL DEFAULT 0" 449 ", value BLOB NOT NULL" 450 ");"_ns))); 451 452 // Reinsert old data, all legacy data is UTF8 453 static_assert(1u == 454 static_cast<uint8_t>(LSValue::ConversionType::UTF16_UTF8)); 455 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 456 "INSERT INTO migrated_data (key, utf16_length, conversion_type, " 457 "compression_type, last_access_time, value) " 458 "SELECT key, utf16Length, 1, compressed, lastAccessTime, value " 459 "FROM data;"_ns))); 460 461 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL("DROP TABLE data;"_ns))); 462 463 // Rename to data 464 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 465 "ALTER TABLE migrated_data RENAME TO data;"_ns))); 466 467 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(5, 0)))); 468 469 return NS_OK; 470 } 471 472 nsresult SetDefaultPragmas(mozIStorageConnection* aConnection) { 473 MOZ_ASSERT(!NS_IsMainThread()); 474 MOZ_ASSERT(aConnection); 475 476 QM_TRY(MOZ_TO_RESULT( 477 aConnection->ExecuteSimpleSQL("PRAGMA synchronous = FULL;"_ns))); 478 479 #ifndef LS_MOBILE 480 if (kSQLiteGrowthIncrement) { 481 // This is just an optimization so ignore the failure if the disk is 482 // currently too full. 483 QM_TRY(QM_OR_ELSE_WARN_IF( 484 // Expression. 485 MOZ_TO_RESULT( 486 aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement, ""_ns)), 487 // Predicate. 488 IsSpecificError<NS_ERROR_FILE_TOO_BIG>, 489 // Fallback. 490 ErrToDefaultOk<>)); 491 } 492 #endif // LS_MOBILE 493 494 return NS_OK; 495 } 496 497 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateStorageConnection( 498 nsIFile& aDBFile, nsIFile& aUsageFile, const nsACString& aOrigin) { 499 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 500 501 // XXX Common logic should be refactored out of this method and 502 // cache::DBAction::OpenDBConnection, and maybe other similar functions. 503 504 QM_TRY_INSPECT(const auto& storageService, 505 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 506 MOZ_SELECT_OVERLOAD(do_GetService), 507 MOZ_STORAGE_SERVICE_CONTRACTID)); 508 509 QM_TRY_UNWRAP(auto connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 510 nsCOMPtr<mozIStorageConnection>, 511 storageService, OpenDatabase, &aDBFile, 512 mozIStorageService::CONNECTION_DEFAULT)); 513 514 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(connection))); 515 516 // Check to make sure that the database schema is correct. 517 // XXX Try to make schemaVersion const. 518 QM_TRY_UNWRAP(int32_t schemaVersion, 519 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion)); 520 521 QM_TRY(OkIf(schemaVersion <= kSQLiteSchemaVersion), Err(NS_ERROR_FAILURE)); 522 523 if (schemaVersion != kSQLiteSchemaVersion) { 524 const bool newDatabase = !schemaVersion; 525 526 if (newDatabase) { 527 // Set the page size first. 528 if (kSQLitePageSizeOverride) { 529 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString( 530 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride)))); 531 } 532 533 // We have to set the auto_vacuum mode before opening a transaction. 534 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL( 535 #ifdef LS_MOBILE 536 // Turn on full auto_vacuum mode to reclaim disk space on mobile 537 // devices (at the cost of some COMMIT speed). 538 "PRAGMA auto_vacuum = FULL;"_ns 539 #else 540 // Turn on incremental auto_vacuum mode on desktop builds. 541 "PRAGMA auto_vacuum = INCREMENTAL;"_ns 542 #endif 543 ))); 544 } 545 546 bool vacuumNeeded = false; 547 548 if (newDatabase) { 549 mozStorageTransaction transaction( 550 connection, 551 /* aCommitOnComplete */ false, 552 mozIStorageConnection::TRANSACTION_IMMEDIATE); 553 554 QM_TRY(MOZ_TO_RESULT(transaction.Start())); 555 556 QM_TRY(MOZ_TO_RESULT(CreateTables(connection))); 557 558 #ifdef DEBUG 559 { 560 QM_TRY_INSPECT( 561 const int32_t& schemaVersion, 562 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion), 563 QM_ASSERT_UNREACHABLE); 564 565 MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion); 566 } 567 #endif 568 569 QM_TRY_INSPECT( 570 const auto& stmt, 571 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 572 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement, 573 "INSERT INTO database (origin) VALUES (:origin)"_ns)); 574 575 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("origin"_ns, aOrigin))); 576 577 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 578 579 QM_TRY(MOZ_TO_RESULT(transaction.Commit())); 580 } else { 581 // This logic needs to change next time we change the schema! 582 static_assert(kSQLiteSchemaVersion == int32_t((5 << 4) + 0), 583 "Upgrade function needed due to schema version increase."); 584 585 while (schemaVersion != kSQLiteSchemaVersion) { 586 mozStorageTransaction transaction( 587 connection, 588 /* aCommitOnComplete */ false, 589 mozIStorageConnection::TRANSACTION_IMMEDIATE); 590 591 QM_TRY(MOZ_TO_RESULT(transaction.Start())); 592 593 if (schemaVersion == MakeSchemaVersion(1, 0)) { 594 QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom1_0To2_0(connection))); 595 } else if (schemaVersion == MakeSchemaVersion(2, 0)) { 596 QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom2_0To3_0(connection))); 597 } else if (schemaVersion == MakeSchemaVersion(3, 0)) { 598 QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom3_0To4_0(connection))); 599 } else if (schemaVersion == MakeSchemaVersion(4, 0)) { 600 QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom4_0To5_0(connection))); 601 vacuumNeeded = true; 602 } else { 603 LS_WARNING( 604 "Unable to open LocalStorage database, no upgrade path is " 605 "available!"); 606 return Err(NS_ERROR_FAILURE); 607 } 608 609 QM_TRY(MOZ_TO_RESULT(transaction.Commit())); 610 611 QM_TRY_UNWRAP(schemaVersion, MOZ_TO_RESULT_INVOKE_MEMBER( 612 connection, GetSchemaVersion)); 613 } 614 615 MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion); 616 } 617 618 if (vacuumNeeded) { 619 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("VACUUM;"_ns))); 620 } 621 622 if (newDatabase) { 623 // Windows caches the file size, let's force it to stat the file again. 624 QM_TRY_INSPECT(const bool& exists, 625 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, Exists)); 626 (void)exists; 627 628 QM_TRY_INSPECT(const int64_t& fileSize, 629 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, GetFileSize)); 630 631 MOZ_ASSERT(fileSize > 0); 632 633 const PRTime vacuumTime = PR_Now(); 634 MOZ_ASSERT(vacuumTime); 635 636 QM_TRY_INSPECT( 637 const auto& vacuumTimeStmt, 638 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<mozIStorageStatement>, 639 connection, CreateStatement, 640 "UPDATE database " 641 "SET last_vacuum_time = :time" 642 ", last_vacuum_size = :size;"_ns)); 643 644 QM_TRY(MOZ_TO_RESULT( 645 vacuumTimeStmt->BindInt64ByName("time"_ns, vacuumTime))); 646 647 QM_TRY( 648 MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByName("size"_ns, fileSize))); 649 650 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->Execute())); 651 } 652 } 653 654 return connection; 655 } 656 657 template <typename CorruptedFileHandler> 658 Result<nsCOMPtr<mozIStorageConnection>, nsresult> 659 CreateStorageConnectionWithRecovery( 660 nsIFile& aDBFile, nsIFile& aUsageFile, const nsACString& aOrigin, 661 CorruptedFileHandler&& aCorruptedFileHandler) { 662 QM_TRY_RETURN(QM_OR_ELSE_WARN_IF( 663 // Expression. 664 CreateStorageConnection(aDBFile, aUsageFile, aOrigin), 665 // Predicate. 666 IsDatabaseCorruptionError, 667 // Fallback. 668 ([&aDBFile, &aUsageFile, &aOrigin, 669 &aCorruptedFileHandler](const nsresult rv) 670 -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> { 671 // Remove the usage file first (it might not exist at all due 672 // to corrupted state, which is ignored here). 673 674 // Usually we only use QM_OR_ELSE_LOG_VERBOSE(_IF) with Remove and 675 // NS_ERROR_FILE_NOT_FOUND check, but we're already in the rare case 676 // of corruption here, so the use of QM_OR_ELSE_WARN_IF is ok here. 677 QM_TRY(QM_OR_ELSE_WARN_IF( 678 // Expression. 679 MOZ_TO_RESULT(aUsageFile.Remove(false)), 680 // Predicate. 681 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }), 682 // Fallback. 683 ErrToDefaultOk<>)); 684 685 // Call the corrupted file handler before trying to remove the 686 // database file, which might fail. 687 aCorruptedFileHandler(); 688 689 // Nuke the database file. 690 QM_TRY(MOZ_TO_RESULT(aDBFile.Remove(false))); 691 692 QM_TRY_RETURN(CreateStorageConnection(aDBFile, aUsageFile, aOrigin)); 693 }))); 694 } 695 696 Result<nsCOMPtr<mozIStorageConnection>, nsresult> GetStorageConnection( 697 const nsAString& aDatabaseFilePath) { 698 AssertIsOnGlobalConnectionThread(); 699 MOZ_ASSERT(!aDatabaseFilePath.IsEmpty()); 700 MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, u".sqlite"_ns)); 701 702 QM_TRY_INSPECT(const auto& databaseFile, QM_NewLocalFile(aDatabaseFilePath)); 703 704 QM_TRY_INSPECT(const bool& exists, 705 MOZ_TO_RESULT_INVOKE_MEMBER(databaseFile, Exists)); 706 707 QM_TRY(OkIf(exists), Err(NS_ERROR_FAILURE)); 708 709 QM_TRY_INSPECT(const auto& ss, 710 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 711 MOZ_SELECT_OVERLOAD(do_GetService), 712 MOZ_STORAGE_SERVICE_CONTRACTID)); 713 714 QM_TRY_UNWRAP(auto connection, 715 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 716 nsCOMPtr<mozIStorageConnection>, ss, OpenDatabase, 717 databaseFile, mozIStorageService::CONNECTION_DEFAULT)); 718 719 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(connection))); 720 721 return connection; 722 } 723 724 Result<nsCOMPtr<nsIFile>, nsresult> GetArchiveFile( 725 const nsAString& aStoragePath) { 726 AssertIsOnIOThread(); 727 MOZ_ASSERT(!aStoragePath.IsEmpty()); 728 729 QM_TRY_UNWRAP(auto archiveFile, QM_NewLocalFile(aStoragePath)); 730 731 QM_TRY(MOZ_TO_RESULT( 732 archiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME)))); 733 734 return archiveFile; 735 } 736 737 Result<nsCOMPtr<mozIStorageConnection>, nsresult> 738 CreateArchiveStorageConnection(const nsAString& aStoragePath) { 739 GECKO_TRACE_SCOPE("dom::localstorage", "CreateArchiveStorageConnection"); 740 741 AssertIsOnIOThread(); 742 MOZ_ASSERT(!aStoragePath.IsEmpty()); 743 744 QM_TRY_INSPECT(const auto& archiveFile, GetArchiveFile(aStoragePath)); 745 746 // QuotaManager ensures this file always exists. 747 DebugOnly<bool> exists; 748 MOZ_ASSERT(NS_SUCCEEDED(archiveFile->Exists(&exists))); 749 MOZ_ASSERT(exists); 750 751 QM_TRY_INSPECT(const bool& isDirectory, 752 MOZ_TO_RESULT_INVOKE_MEMBER(archiveFile, IsDirectory)); 753 754 if (isDirectory) { 755 LS_WARNING("ls-archive is not a file!"); 756 return nsCOMPtr<mozIStorageConnection>{}; 757 } 758 759 QM_TRY_INSPECT(const auto& ss, 760 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 761 MOZ_SELECT_OVERLOAD(do_GetService), 762 MOZ_STORAGE_SERVICE_CONTRACTID)); 763 764 QM_TRY_UNWRAP( 765 auto connection, 766 QM_OR_ELSE_WARN_IF( 767 // Expression. 768 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 769 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, 770 archiveFile, mozIStorageService::CONNECTION_DEFAULT), 771 // Predicate. 772 IsDatabaseCorruptionError, 773 // Fallback. Don't throw an error, leave a corrupted ls-archive 774 // database as it is. 775 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>)); 776 777 if (connection) { 778 const nsresult rv = StorageDBUpdater::Update(connection); 779 if (NS_FAILED(rv)) { 780 // Don't throw an error, leave a non-updateable ls-archive database as 781 // it is. 782 return nsCOMPtr<mozIStorageConnection>{}; 783 } 784 } 785 786 return connection; 787 } 788 789 Result<nsCOMPtr<nsIFile>, nsresult> GetShadowFile(const nsAString& aBasePath) { 790 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 791 MOZ_ASSERT(!aBasePath.IsEmpty()); 792 793 QM_TRY_UNWRAP(auto archiveFile, QM_NewLocalFile(aBasePath)); 794 795 QM_TRY(MOZ_TO_RESULT( 796 archiveFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME)))); 797 798 return archiveFile; 799 } 800 801 nsresult SetShadowJournalMode(mozIStorageConnection* aConnection) { 802 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 803 MOZ_ASSERT(aConnection); 804 805 // Try enabling WAL mode. This can fail in various circumstances so we have to 806 // check the results here. 807 constexpr auto journalModeQueryStart = "PRAGMA journal_mode = "_ns; 808 constexpr auto journalModeWAL = "wal"_ns; 809 810 QM_TRY_INSPECT(const auto& stmt, 811 CreateAndExecuteSingleStepStatement( 812 *aConnection, journalModeQueryStart + journalModeWAL)); 813 814 QM_TRY_INSPECT(const auto& journalMode, 815 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt, 816 GetUTF8String, 0)); 817 818 if (journalMode.Equals(journalModeWAL)) { 819 // WAL mode successfully enabled. Set limits on its size here. 820 821 // Set the threshold for auto-checkpointing the WAL. We don't want giant 822 // logs slowing down us. 823 QM_TRY_INSPECT(const auto& stmt, CreateAndExecuteSingleStepStatement( 824 *aConnection, "PRAGMA page_size;"_ns)); 825 826 QM_TRY_INSPECT(const int32_t& pageSize, 827 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0)); 828 829 MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536); 830 831 // Note there is a default journal_size_limit set by mozStorage. 832 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 833 "PRAGMA wal_autocheckpoint = "_ns + 834 IntToCString(static_cast<int32_t>(kShadowMaxWALSize / pageSize))))); 835 } else { 836 QM_TRY(MOZ_TO_RESULT( 837 aConnection->ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns))); 838 } 839 840 return NS_OK; 841 } 842 843 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateShadowStorageConnection( 844 const nsAString& aBasePath) { 845 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 846 MOZ_ASSERT(!aBasePath.IsEmpty()); 847 848 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath)); 849 850 QM_TRY_INSPECT(const auto& ss, 851 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 852 MOZ_SELECT_OVERLOAD(do_GetService), 853 MOZ_STORAGE_SERVICE_CONTRACTID)); 854 855 QM_TRY_UNWRAP( 856 auto connection, 857 QM_OR_ELSE_WARN_IF( 858 // Expression. 859 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 860 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, 861 shadowFile, mozIStorageService::CONNECTION_DEFAULT), 862 // Predicate. 863 IsDatabaseCorruptionError, 864 // Fallback. 865 ([&shadowFile, &ss](const nsresult rv) 866 -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> { 867 QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false))); 868 869 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 870 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, 871 shadowFile, mozIStorageService::CONNECTION_DEFAULT)); 872 }))); 873 874 QM_TRY(MOZ_TO_RESULT(SetShadowJournalMode(connection))); 875 876 // XXX Depending on whether the *first* call to OpenUnsharedDatabase above 877 // failed, we (a) might or (b) might not be dealing with a fresh database 878 // here. This is confusing, since in a failure of case (a) we would do the 879 // same thing again. Probably, the control flow should be changed here so that 880 // it's clear we only delete & create a fresh database once. If we still have 881 // a failure then, we better give up. Or, if we really want to handle that, 882 // the number of 2 retries seems arbitrary, and we should better do this in 883 // some loop until a maximum number of retries is reached. 884 // 885 // Compare this with QuotaManager::CreateLocalStorageArchiveConnection, which 886 // actually tracks if the file was removed before, but it's also more 887 // complicated than it should be. Maybe these two methods can be merged (which 888 // would mean that a parameter must be added that indicates whether it's 889 // handling the shadow file or not). 890 QM_TRY(QM_OR_ELSE_WARN( 891 // Expression. 892 MOZ_TO_RESULT(StorageDBUpdater::Update(connection)), 893 // Fallback. 894 ([&connection, &shadowFile, &ss](const nsresult) -> Result<Ok, nsresult> { 895 QM_TRY(MOZ_TO_RESULT(connection->Close())); 896 QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false))); 897 898 QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 899 nsCOMPtr<mozIStorageConnection>, ss, 900 OpenUnsharedDatabase, shadowFile, 901 mozIStorageService::CONNECTION_DEFAULT)); 902 903 QM_TRY(MOZ_TO_RESULT(SetShadowJournalMode(connection))); 904 905 QM_TRY( 906 MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection))); 907 908 return Ok{}; 909 }))); 910 911 return connection; 912 } 913 914 Result<nsCOMPtr<mozIStorageConnection>, nsresult> GetShadowStorageConnection( 915 const nsAString& aBasePath) { 916 AssertIsOnIOThread(); 917 MOZ_ASSERT(!aBasePath.IsEmpty()); 918 919 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath)); 920 921 QM_TRY_INSPECT(const bool& exists, 922 MOZ_TO_RESULT_INVOKE_MEMBER(shadowFile, Exists)); 923 924 QM_TRY(OkIf(exists), Err(NS_ERROR_FAILURE)); 925 926 QM_TRY_INSPECT(const auto& ss, 927 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 928 MOZ_SELECT_OVERLOAD(do_GetService), 929 MOZ_STORAGE_SERVICE_CONTRACTID)); 930 931 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 932 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, shadowFile, 933 mozIStorageService::CONNECTION_DEFAULT)); 934 } 935 936 nsresult AttachShadowDatabase(const nsAString& aBasePath, 937 mozIStorageConnection* aConnection) { 938 AssertIsOnGlobalConnectionThread(); 939 MOZ_ASSERT(!aBasePath.IsEmpty()); 940 MOZ_ASSERT(aConnection); 941 942 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath)); 943 944 #ifdef DEBUG 945 { 946 QM_TRY_INSPECT(const bool& exists, 947 MOZ_TO_RESULT_INVOKE_MEMBER(shadowFile, Exists)); 948 949 MOZ_ASSERT(exists); 950 } 951 #endif 952 953 QM_TRY_INSPECT(const auto& path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 954 nsString, shadowFile, GetPath)); 955 956 QM_TRY_INSPECT(const auto& stmt, 957 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 958 nsCOMPtr<mozIStorageStatement>, aConnection, 959 CreateStatement, "ATTACH DATABASE :path AS shadow;"_ns)); 960 961 QM_TRY(MOZ_TO_RESULT(stmt->BindStringByName("path"_ns, path))); 962 963 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 964 965 return NS_OK; 966 } 967 968 nsresult DetachShadowDatabase(mozIStorageConnection* aConnection) { 969 AssertIsOnGlobalConnectionThread(); 970 MOZ_ASSERT(aConnection); 971 972 QM_TRY(MOZ_TO_RESULT( 973 aConnection->ExecuteSimpleSQL("DETACH DATABASE shadow"_ns))); 974 975 return NS_OK; 976 } 977 978 Result<nsCOMPtr<nsIFile>, nsresult> GetUsageFile( 979 const nsAString& aDirectoryPath) { 980 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 981 MOZ_ASSERT(!aDirectoryPath.IsEmpty()); 982 983 QM_TRY_UNWRAP(auto usageFile, QM_NewLocalFile(aDirectoryPath)); 984 985 QM_TRY(MOZ_TO_RESULT(usageFile->Append(kUsageFileName))); 986 987 return usageFile; 988 } 989 990 Result<nsCOMPtr<nsIFile>, nsresult> GetUsageJournalFile( 991 const nsAString& aDirectoryPath) { 992 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 993 MOZ_ASSERT(!aDirectoryPath.IsEmpty()); 994 995 QM_TRY_UNWRAP(auto usageJournalFile, QM_NewLocalFile(aDirectoryPath)); 996 997 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Append(kUsageJournalFileName))); 998 999 return usageJournalFile; 1000 } 1001 1002 // Checks if aFile exists and is a file. Returns true if it exists and is a 1003 // file, false if it doesn't exist, and an error if it exists but isn't a file. 1004 Result<bool, nsresult> ExistsAsFile(nsIFile& aFile) { 1005 enum class ExistsAsFileResult { DoesNotExist, IsDirectory, IsFile }; 1006 1007 // This is an optimization to check both properties in one OS case, rather 1008 // than calling Exists first, and then IsDirectory. IsDirectory also checks 1009 // if the path exists. QM_OR_ELSE_WARN_IF is not used here since we just want 1010 // to log NS_ERROR_FILE_NOT_FOUND result and not spam the reports. 1011 QM_TRY_INSPECT( 1012 const auto& res, 1013 QM_OR_ELSE_LOG_VERBOSE_IF( 1014 // Expression. 1015 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, IsDirectory) 1016 .map([](const bool isDirectory) { 1017 return isDirectory ? ExistsAsFileResult::IsDirectory 1018 : ExistsAsFileResult::IsFile; 1019 }), 1020 // Predicate. 1021 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }), 1022 // Fallback. 1023 ErrToOk<ExistsAsFileResult::DoesNotExist>)); 1024 1025 QM_TRY(OkIf(res != ExistsAsFileResult::IsDirectory), Err(NS_ERROR_FAILURE)); 1026 1027 return res == ExistsAsFileResult::IsFile; 1028 } 1029 1030 nsresult UpdateUsageFile(nsIFile* aUsageFile, nsIFile* aUsageJournalFile, 1031 int64_t aUsage) { 1032 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 1033 MOZ_ASSERT(aUsageFile); 1034 MOZ_ASSERT(aUsageJournalFile); 1035 MOZ_ASSERT(aUsage >= 0); 1036 1037 QM_TRY_INSPECT(const bool& usageJournalFileExists, 1038 ExistsAsFile(*aUsageJournalFile)); 1039 if (!usageJournalFileExists) { 1040 QM_TRY(MOZ_TO_RESULT( 1041 aUsageJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644))); 1042 } 1043 1044 QM_TRY_INSPECT(const auto& stream, NS_NewLocalFileOutputStream(aUsageFile)); 1045 1046 nsCOMPtr<nsIBinaryOutputStream> binaryStream = 1047 NS_NewObjectOutputStream(stream); 1048 1049 QM_TRY(MOZ_TO_RESULT(binaryStream->Write32(kUsageFileCookie))); 1050 1051 QM_TRY(MOZ_TO_RESULT(binaryStream->Write64(aUsage))); 1052 1053 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG) 1054 QM_TRY(MOZ_TO_RESULT(stream->Flush())); 1055 #endif 1056 1057 QM_TRY(MOZ_TO_RESULT(stream->Close())); 1058 1059 return NS_OK; 1060 } 1061 1062 Result<UsageInfo, nsresult> LoadUsageFile(nsIFile& aUsageFile) { 1063 GECKO_TRACE_SCOPE("dom::localstorage", "LoadUsageFile"); 1064 1065 AssertIsOnIOThread(); 1066 1067 QM_TRY_INSPECT(const int64_t& fileSize, 1068 MOZ_TO_RESULT_INVOKE_MEMBER(aUsageFile, GetFileSize)); 1069 1070 QM_TRY(OkIf(fileSize == kUsageFileSize), Err(NS_ERROR_FILE_CORRUPTED)); 1071 1072 QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(&aUsageFile)); 1073 1074 QM_TRY_INSPECT(const auto& bufferedStream, 1075 NS_NewBufferedInputStream(stream.forget(), 16)); 1076 1077 const nsCOMPtr<nsIBinaryInputStream> binaryStream = 1078 NS_NewObjectInputStream(bufferedStream); 1079 1080 QM_TRY_INSPECT(const uint32_t& cookie, 1081 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32)); 1082 1083 QM_TRY(OkIf(cookie == kUsageFileCookie), Err(NS_ERROR_FILE_CORRUPTED)); 1084 1085 QM_TRY_INSPECT(const uint64_t& usage, 1086 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64)); 1087 1088 return UsageInfo{DatabaseUsageType(Some(usage))}; 1089 } 1090 1091 /******************************************************************************* 1092 * Non-actor class declarations 1093 ******************************************************************************/ 1094 1095 /** 1096 * Coalescing manipulation queue used by `Datastore`. Used by `Datastore` to 1097 * update `Datastore::mOrderedItems` efficiently/for code simplification. 1098 * (Datastore does not actually depend on the coalescing, as mutations are 1099 * applied atomically when a Snapshot Checkpoints, and with `Datastore::mValues` 1100 * being updated at the same time the mutations are applied to Datastore's 1101 * mWriteOptimizer.) 1102 */ 1103 class DatastoreWriteOptimizer final : public LSWriteOptimizer<LSValue> { 1104 public: 1105 void ApplyAndReset(nsTArray<LSItemInfo>& aOrderedItems); 1106 }; 1107 1108 /** 1109 * Coalescing manipulation queue used by `Connection`. Used by `Connection` to 1110 * buffer and coalesce manipulations applied to the Datastore in batches by 1111 * Snapshot Checkpointing until flushed to disk. 1112 */ 1113 class ConnectionWriteOptimizer final : public LSWriteOptimizer<LSValue> { 1114 public: 1115 // Returns the usage as the success value. 1116 Result<int64_t, nsresult> Perform(Connection* aConnection, 1117 bool aShadowWrites); 1118 1119 private: 1120 /** 1121 * Handlers for specific mutations. Each method knows how to `Perform` the 1122 * manipulation against a `Connection` and the "shadow" database (legacy 1123 * webappsstore.sqlite database that exists so LSNG can be disabled/safely 1124 * downgraded from.) 1125 */ 1126 nsresult PerformInsertOrUpdate(Connection* aConnection, bool aShadowWrites, 1127 const nsAString& aKey, const LSValue& aValue); 1128 1129 nsresult PerformDelete(Connection* aConnection, bool aShadowWrites, 1130 const nsAString& aKey); 1131 1132 nsresult PerformTruncate(Connection* aConnection, bool aShadowWrites); 1133 }; 1134 1135 class DatastoreOperationBase : public Runnable { 1136 nsCOMPtr<nsIEventTarget> mOwningEventTarget; 1137 nsresult mResultCode; 1138 Atomic<bool> mMayProceedOnNonOwningThread; 1139 bool mMayProceed; 1140 1141 public: 1142 nsIEventTarget* OwningEventTarget() const { 1143 MOZ_ASSERT(mOwningEventTarget); 1144 1145 return mOwningEventTarget; 1146 } 1147 1148 bool IsOnOwningThread() const { 1149 MOZ_ASSERT(mOwningEventTarget); 1150 1151 bool current; 1152 return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) && 1153 current; 1154 } 1155 1156 void AssertIsOnOwningThread() const { 1157 MOZ_ASSERT(IsOnBackgroundThread()); 1158 MOZ_ASSERT(IsOnOwningThread()); 1159 } 1160 1161 nsresult ResultCode() const { return mResultCode; } 1162 1163 void SetFailureCode(nsresult aErrorCode) { 1164 MOZ_ASSERT(NS_SUCCEEDED(mResultCode)); 1165 MOZ_ASSERT(NS_FAILED(aErrorCode)); 1166 1167 mResultCode = aErrorCode; 1168 } 1169 1170 void MaybeSetFailureCode(nsresult aErrorCode) { 1171 MOZ_ASSERT(NS_FAILED(aErrorCode)); 1172 1173 if (NS_SUCCEEDED(mResultCode)) { 1174 mResultCode = aErrorCode; 1175 } 1176 } 1177 1178 void NoteComplete() { 1179 AssertIsOnOwningThread(); 1180 1181 mMayProceed = false; 1182 mMayProceedOnNonOwningThread = false; 1183 } 1184 1185 bool MayProceed() const { 1186 AssertIsOnOwningThread(); 1187 1188 return mMayProceed; 1189 } 1190 1191 // May be called on any thread, but you should call MayProceed() if you know 1192 // you're on the background thread because it is slightly faster. 1193 bool MayProceedOnNonOwningThread() const { 1194 return mMayProceedOnNonOwningThread; 1195 } 1196 1197 protected: 1198 DatastoreOperationBase() 1199 : Runnable("dom::DatastoreOperationBase"), 1200 mOwningEventTarget(GetCurrentSerialEventTarget()), 1201 mResultCode(NS_OK), 1202 mMayProceedOnNonOwningThread(true), 1203 mMayProceed(true) {} 1204 1205 ~DatastoreOperationBase() override { MOZ_ASSERT(!mMayProceed); } 1206 }; 1207 1208 class ConnectionDatastoreOperationBase : public DatastoreOperationBase { 1209 protected: 1210 RefPtr<Connection> mConnection; 1211 /** 1212 * This boolean flag is used by the CloseOp to avoid creating empty databases. 1213 */ 1214 const bool mEnsureStorageConnection; 1215 1216 public: 1217 // This callback will be called on the background thread before releasing the 1218 // final reference to this request object. Subclasses may perform any 1219 // additional cleanup here but must always call the base class implementation. 1220 virtual void Cleanup(); 1221 1222 protected: 1223 ConnectionDatastoreOperationBase(Connection* aConnection, 1224 bool aEnsureStorageConnection = true); 1225 1226 ~ConnectionDatastoreOperationBase(); 1227 1228 // Must be overridden in subclasses. Called on the target thread to allow the 1229 // subclass to perform necessary datastore operations. A successful return 1230 // value will trigger an OnSuccess callback on the background thread while 1231 // while a failure value will trigger an OnFailure callback. 1232 virtual nsresult DoDatastoreWork() = 0; 1233 1234 // Methods that subclasses may implement. 1235 virtual void OnSuccess(); 1236 1237 virtual void OnFailure(nsresult aResultCode); 1238 1239 private: 1240 void RunOnConnectionThread(); 1241 1242 void RunOnOwningThread(); 1243 1244 // Not to be overridden by subclasses. 1245 NS_DECL_NSIRUNNABLE 1246 }; 1247 1248 class Connection final : public CachingDatabaseConnection { 1249 friend class ConnectionThread; 1250 1251 class GetOrCreateTemporaryOriginDirectoryHelper; 1252 1253 class FlushOp; 1254 class CloseOp; 1255 1256 RefPtr<ConnectionThread> mConnectionThread; 1257 RefPtr<QuotaClient> mQuotaClient; 1258 nsCOMPtr<nsITimer> mFlushTimer; 1259 UniquePtr<ArchivedOriginScope> mArchivedOriginScope; 1260 ConnectionWriteOptimizer mWriteOptimizer; 1261 // XXX Consider changing this to ClientMetadata. 1262 const OriginMetadata mOriginMetadata; 1263 nsString mDirectoryPath; 1264 /** 1265 * Propagated from PrepareDatastoreOp. PrepareDatastoreOp may defer the 1266 * creation of the localstorage client directory and database on the 1267 * QuotaManager IO thread in its DatabaseWork method to 1268 * Connection::EnsureStorageConnection, in which case the method needs to know 1269 * it is responsible for taking those actions (without redundantly performing 1270 * the existence checks). 1271 */ 1272 const bool mDatabaseWasNotAvailable; 1273 bool mHasCreatedDatabase; 1274 bool mFlushScheduled; 1275 #ifdef DEBUG 1276 bool mInUpdateBatch; 1277 bool mFinished; 1278 #endif 1279 1280 public: 1281 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection) 1282 1283 void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); } 1284 1285 QuotaClient* GetQuotaClient() const { 1286 MOZ_ASSERT(mQuotaClient); 1287 1288 return mQuotaClient; 1289 } 1290 1291 ArchivedOriginScope* GetArchivedOriginScope() const { 1292 return mArchivedOriginScope.get(); 1293 } 1294 1295 const nsCString& Origin() const { return mOriginMetadata.mOrigin; } 1296 1297 const nsString& DirectoryPath() const { return mDirectoryPath; } 1298 1299 void GetFinishInfo(bool& aDatabaseWasNotAvailable, 1300 bool& aHasCreatedDatabase) const { 1301 AssertIsOnOwningThread(); 1302 MOZ_ASSERT(mFinished); 1303 1304 aDatabaseWasNotAvailable = mDatabaseWasNotAvailable; 1305 aHasCreatedDatabase = mHasCreatedDatabase; 1306 } 1307 1308 ////////////////////////////////////////////////////////////////////////////// 1309 // Methods which can only be called on the owning thread. 1310 1311 // This method is used to asynchronously execute a connection datastore 1312 // operation on the connection thread. 1313 void Dispatch(ConnectionDatastoreOperationBase* aOp); 1314 1315 // This method is used to asynchronously close the storage connection on the 1316 // connection thread. 1317 void Close(nsIRunnable* aCallback); 1318 1319 void SetItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta, 1320 bool aIsNewItem); 1321 1322 void RemoveItem(const nsString& aKey, int64_t aDelta); 1323 1324 void Clear(int64_t aDelta); 1325 1326 void BeginUpdateBatch(); 1327 1328 void EndUpdateBatch(); 1329 1330 ////////////////////////////////////////////////////////////////////////////// 1331 // Methods which can only be called on the connection thread. 1332 1333 nsresult EnsureStorageConnection(); 1334 1335 mozIStorageConnection* StorageConnection() const { 1336 AssertIsOnGlobalConnectionThread(); 1337 1338 return &MutableStorageConnection(); 1339 } 1340 1341 void CloseStorageConnection(); 1342 1343 nsresult BeginWriteTransaction(); 1344 1345 nsresult CommitWriteTransaction(); 1346 1347 nsresult RollbackWriteTransaction(); 1348 1349 private: 1350 // Only created by ConnectionThread. 1351 Connection(ConnectionThread* aConnectionThread, 1352 const OriginMetadata& aOriginMetadata, 1353 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope, 1354 bool aDatabaseWasNotAvailable); 1355 1356 ~Connection(); 1357 1358 void ScheduleFlush(); 1359 1360 void Flush(); 1361 1362 static void FlushTimerCallback(nsITimer* aTimer, void* aClosure); 1363 }; 1364 1365 /** 1366 * Helper to invoke GetOrCreateTemporaryOriginDirectory on the QuotaManager IO 1367 * thread from the LocalStorage connection thread when creating a database 1368 * connection on demand. This is necessary because we attempt to defer the 1369 * creation of the origin directory and the database until absolutely needed, 1370 * but the directory creation must happen on the QM IO thread for invariant 1371 * reasons. (We can't just use a mutex because there could be logic on the IO 1372 * thread that also wants to deal with the same origin, so we need to queue a 1373 * runnable and wait our turn.) 1374 */ 1375 class Connection::GetOrCreateTemporaryOriginDirectoryHelper final 1376 : public Runnable { 1377 mozilla::Monitor mMonitor MOZ_UNANNOTATED; 1378 const OriginMetadata mOriginMetadata; 1379 nsString mOriginDirectoryPath; 1380 nsresult mIOThreadResultCode; 1381 bool mWaiting; 1382 1383 public: 1384 explicit GetOrCreateTemporaryOriginDirectoryHelper( 1385 const OriginMetadata& aOriginMetadata) 1386 : Runnable( 1387 "dom::localstorage::Connection::" 1388 "GetOrCreateTemporaryOriginDirectoryHelper"), 1389 mMonitor("GetOrCreateTemporaryOriginDirectoryHelper::mMonitor"), 1390 mOriginMetadata(aOriginMetadata), 1391 mIOThreadResultCode(NS_OK), 1392 mWaiting(true) { 1393 AssertIsOnGlobalConnectionThread(); 1394 } 1395 1396 Result<nsString, nsresult> BlockAndReturnOriginDirectoryPath(); 1397 1398 private: 1399 ~GetOrCreateTemporaryOriginDirectoryHelper() = default; 1400 1401 nsresult RunOnIOThread(); 1402 1403 NS_DECL_NSIRUNNABLE 1404 }; 1405 1406 class Connection::FlushOp final : public ConnectionDatastoreOperationBase { 1407 ConnectionWriteOptimizer mWriteOptimizer; 1408 bool mShadowWrites; 1409 1410 public: 1411 FlushOp(Connection* aConnection, ConnectionWriteOptimizer&& aWriteOptimizer); 1412 1413 private: 1414 nsresult DoDatastoreWork() override; 1415 1416 void Cleanup() override; 1417 }; 1418 1419 class Connection::CloseOp final : public ConnectionDatastoreOperationBase { 1420 nsCOMPtr<nsIRunnable> mCallback; 1421 1422 public: 1423 CloseOp(Connection* aConnection, nsIRunnable* aCallback) 1424 : ConnectionDatastoreOperationBase(aConnection, 1425 /* aEnsureStorageConnection */ false), 1426 mCallback(aCallback) {} 1427 1428 private: 1429 nsresult DoDatastoreWork() override; 1430 1431 void Cleanup() override; 1432 }; 1433 1434 class ConnectionThread final { 1435 friend class Connection; 1436 1437 nsCOMPtr<nsIThread> mThread; 1438 nsRefPtrHashtable<nsCStringHashKey, Connection> mConnections; 1439 1440 public: 1441 ConnectionThread(); 1442 1443 void AssertIsOnOwningThread() const { 1444 NS_ASSERT_OWNINGTHREAD(ConnectionThread); 1445 } 1446 1447 bool IsOnConnectionThread(); 1448 1449 void AssertIsOnConnectionThread(); 1450 1451 already_AddRefed<Connection> CreateConnection( 1452 const OriginMetadata& aOriginMetadata, 1453 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope, 1454 bool aDatabaseWasNotAvailable); 1455 1456 void Shutdown(); 1457 1458 NS_INLINE_DECL_REFCOUNTING(ConnectionThread) 1459 1460 private: 1461 ~ConnectionThread(); 1462 }; 1463 1464 /** 1465 * Canonical state of Storage for an origin, containing all keys and their 1466 * values in the parent process. Specifically, this is the state that will 1467 * be handed out to freshly created Snapshots and that will be persisted to disk 1468 * when the Connection's flush completes. State is mutated in batches as 1469 * Snapshot instances Checkpoint their mutations locally accumulated in the 1470 * child LSSnapshots. 1471 */ 1472 class Datastore final 1473 : public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> { 1474 ClientDirectoryLockHandle mDirectoryLockHandle; 1475 RefPtr<Connection> mConnection; 1476 RefPtr<QuotaObject> mQuotaObject; 1477 nsCOMPtr<nsIRunnable> mCompleteCallback; 1478 /** 1479 * PrepareDatastoreOps register themselves with the Datastore at 1480 * and unregister in PrepareDatastoreOp::Cleanup. 1481 */ 1482 nsTHashSet<PrepareDatastoreOp*> mPrepareDatastoreOps; 1483 /** 1484 * PreparedDatastore instances register themselves with their associated 1485 * Datastore at construction time and unregister at destruction time. They 1486 * hang around for kPreparedDatastoreTimeoutMs in order to keep the Datastore 1487 * from closing itself via MaybeClose(), thereby giving the document enough 1488 * time to load and access LocalStorage. 1489 */ 1490 nsTHashSet<PreparedDatastore*> mPreparedDatastores; 1491 /** 1492 * A database is live (and in this hashtable) if it has a live LSDatabase 1493 * actor. There is at most one Database per origin per content process. Each 1494 * Database corresponds to an LSDatabase in its associated content process. 1495 */ 1496 nsTHashSet<Database*> mDatabases; 1497 /** 1498 * A database is active if it has a non-null `mSnapshot`. As long as there 1499 * are any active databases final deltas can't be calculated and 1500 * `UpdateUsage()` can't be invoked. 1501 */ 1502 nsTHashSet<Database*> mActiveDatabases; 1503 /** 1504 * Non-authoritative hashtable representation of mOrderedItems for efficient 1505 * lookup. 1506 */ 1507 nsTHashMap<nsStringHashKey, LSValue> mValues; 1508 /** 1509 * The authoritative ordered state of the Datastore; mValue also exists as an 1510 * unordered hashtable for efficient lookup. 1511 */ 1512 nsTArray<LSItemInfo> mOrderedItems; 1513 nsTArray<int64_t> mPendingUsageDeltas; 1514 DatastoreWriteOptimizer mWriteOptimizer; 1515 const OriginMetadata mOriginMetadata; 1516 const uint32_t mPrivateBrowsingId; 1517 int64_t mUsage; 1518 int64_t mUpdateBatchUsage; 1519 int64_t mSizeOfKeys; 1520 int64_t mSizeOfItems; 1521 bool mClosed; 1522 bool mInUpdateBatch; 1523 bool mHasLivePrivateDatastore; 1524 1525 public: 1526 // Created by PrepareDatastoreOp. 1527 Datastore(const OriginMetadata& aOriginMetadata, uint32_t aPrivateBrowsingId, 1528 int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems, 1529 ClientDirectoryLockHandle&& aDirectoryLockHandle, 1530 RefPtr<Connection>&& aConnection, 1531 RefPtr<QuotaObject>&& aQuotaObject, 1532 nsTHashMap<nsStringHashKey, LSValue>& aValues, 1533 nsTArray<LSItemInfo>&& aOrderedItems); 1534 1535 Maybe<ClientDirectoryLock&> MaybeDirectoryLockRef() const { 1536 AssertIsOnBackgroundThread(); 1537 1538 return ToMaybeRef(mDirectoryLockHandle.get()); 1539 } 1540 1541 const nsCString& Origin() const { return mOriginMetadata.mOrigin; } 1542 1543 uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; } 1544 1545 bool IsPersistent() const { 1546 // Private-browsing is forbidden from touching disk. 1547 return mPrivateBrowsingId == 0; 1548 } 1549 1550 void Close(); 1551 1552 bool IsClosed() const { 1553 AssertIsOnBackgroundThread(); 1554 1555 return mClosed; 1556 } 1557 1558 void WaitForConnectionToComplete(nsIRunnable* aCallback); 1559 1560 void NoteLivePrepareDatastoreOp(PrepareDatastoreOp* aPrepareDatastoreOp); 1561 1562 void NoteFinishedPrepareDatastoreOp(PrepareDatastoreOp* aPrepareDatastoreOp); 1563 1564 void NoteLivePrivateDatastore(); 1565 1566 void NoteFinishedPrivateDatastore(); 1567 1568 void NoteLivePreparedDatastore(PreparedDatastore* aPreparedDatastore); 1569 1570 void NoteFinishedPreparedDatastore(PreparedDatastore* aPreparedDatastore); 1571 1572 bool HasOtherProcessDatabases(Database* aDatabase); 1573 1574 void NoteLiveDatabase(Database* aDatabase); 1575 1576 void NoteFinishedDatabase(Database* aDatabase); 1577 1578 void NoteActiveDatabase(Database* aDatabase); 1579 1580 void NoteInactiveDatabase(Database* aDatabase); 1581 1582 void GetSnapshotLoadInfo(const nsAString& aKey, bool& aAddKeyToUnknownItems, 1583 nsTHashtable<nsStringHashKey>& aLoadedItems, 1584 nsTArray<LSItemInfo>& aItemInfos, 1585 uint32_t& aNextLoadIndex, 1586 LSSnapshot::LoadState& aLoadState); 1587 1588 uint32_t GetLength() const { return mValues.Count(); } 1589 1590 const nsTArray<LSItemInfo>& GetOrderedItems() const { return mOrderedItems; } 1591 1592 void GetItem(const nsAString& aKey, LSValue& aValue) const; 1593 1594 void GetKeys(nsTArray<nsString>& aKeys) const; 1595 1596 ////////////////////////////////////////////////////////////////////////////// 1597 // Mutation Methods 1598 // 1599 // These are only called during Snapshot::Checkpoint 1600 1601 /** 1602 * Used by Snapshot::Checkpoint to set a key/value pair as part of an 1603 * explicit batch. 1604 */ 1605 void SetItem(Database* aDatabase, const nsString& aKey, 1606 const LSValue& aValue); 1607 1608 void RemoveItem(Database* aDatabase, const nsString& aKey); 1609 1610 void Clear(Database* aDatabase); 1611 1612 void BeginUpdateBatch(int64_t aSnapshotUsage); 1613 1614 int64_t EndUpdateBatch(int64_t aSnapshotPeakUsage); 1615 1616 int64_t GetUsage() const { return mUsage; } 1617 1618 int64_t AttemptToUpdateUsage(int64_t aMinSize, bool aInitial); 1619 1620 bool HasOtherProcessObservers(Database* aDatabase); 1621 1622 void NotifyOtherProcessObservers(Database* aDatabase, 1623 const nsString& aDocumentURI, 1624 const nsString& aKey, 1625 const LSValue& aOldValue, 1626 const LSValue& aNewValue); 1627 1628 void NoteChangedObserverArray(const nsTArray<NotNull<Observer*>>& aObservers); 1629 1630 void Stringify(nsACString& aResult) const; 1631 1632 NS_INLINE_DECL_REFCOUNTING(Datastore) 1633 1634 private: 1635 // Reference counted. 1636 ~Datastore(); 1637 1638 bool UpdateUsage(int64_t aDelta); 1639 1640 void MaybeClose(); 1641 1642 void ConnectionClosedCallback(); 1643 1644 void CleanupMetadata(); 1645 1646 void NotifySnapshots(Database* aDatabase, const nsAString& aKey, 1647 const LSValue& aOldValue, bool aAffectsOrder); 1648 1649 void NoteChangedDatabaseMap(); 1650 }; 1651 1652 class PrivateDatastore { 1653 const NotNull<RefPtr<Datastore>> mDatastore; 1654 1655 public: 1656 explicit PrivateDatastore(MovingNotNull<RefPtr<Datastore>> aDatastore) 1657 : mDatastore(std::move(aDatastore)) { 1658 AssertIsOnBackgroundThread(); 1659 1660 mDatastore->NoteLivePrivateDatastore(); 1661 } 1662 1663 ~PrivateDatastore() { mDatastore->NoteFinishedPrivateDatastore(); } 1664 1665 const Datastore& DatastoreRef() const { 1666 AssertIsOnBackgroundThread(); 1667 1668 return *mDatastore; 1669 } 1670 1671 Datastore& MutableDatastoreRef() const { 1672 AssertIsOnBackgroundThread(); 1673 1674 return *mDatastore; 1675 } 1676 }; 1677 1678 class PreparedDatastore { 1679 RefPtr<Datastore> mDatastore; 1680 nsCOMPtr<nsITimer> mTimer; 1681 const Maybe<ContentParentId> mContentParentId; 1682 // Strings share buffers if possible, so it's not a problem to duplicate the 1683 // origin here. 1684 const nsCString mOrigin; 1685 uint64_t mDatastoreId; 1686 bool mForPreload; 1687 bool mInvalidated; 1688 1689 public: 1690 PreparedDatastore(Datastore* aDatastore, 1691 const Maybe<ContentParentId>& aContentParentId, 1692 const nsACString& aOrigin, uint64_t aDatastoreId, 1693 bool aForPreload) 1694 : mDatastore(aDatastore), 1695 mTimer(NS_NewTimer()), 1696 mContentParentId(aContentParentId), 1697 mOrigin(aOrigin), 1698 mDatastoreId(aDatastoreId), 1699 mForPreload(aForPreload), 1700 mInvalidated(false) { 1701 AssertIsOnBackgroundThread(); 1702 MOZ_ASSERT(aDatastore); 1703 MOZ_ASSERT(mTimer); 1704 1705 aDatastore->NoteLivePreparedDatastore(this); 1706 1707 MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback( 1708 TimerCallback, this, kPreparedDatastoreTimeoutMs, 1709 nsITimer::TYPE_ONE_SHOT, "PreparedDatastore::TimerCallback"_ns)); 1710 } 1711 1712 ~PreparedDatastore() { 1713 MOZ_ASSERT(mDatastore); 1714 MOZ_ASSERT(mTimer); 1715 1716 mTimer->Cancel(); 1717 1718 mDatastore->NoteFinishedPreparedDatastore(this); 1719 } 1720 1721 const Datastore& DatastoreRef() const { 1722 AssertIsOnBackgroundThread(); 1723 MOZ_ASSERT(mDatastore); 1724 1725 return *mDatastore; 1726 } 1727 1728 Datastore& MutableDatastoreRef() const { 1729 AssertIsOnBackgroundThread(); 1730 MOZ_ASSERT(mDatastore); 1731 1732 return *mDatastore; 1733 } 1734 1735 const Maybe<ContentParentId>& GetContentParentId() const { 1736 return mContentParentId; 1737 } 1738 1739 const nsCString& Origin() const { return mOrigin; } 1740 1741 void Invalidate() { 1742 AssertIsOnBackgroundThread(); 1743 1744 mInvalidated = true; 1745 1746 if (mForPreload) { 1747 mTimer->Cancel(); 1748 1749 MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback( 1750 TimerCallback, this, 0, nsITimer::TYPE_ONE_SHOT, 1751 "PreparedDatastore::TimerCallback"_ns)); 1752 } 1753 } 1754 1755 bool IsInvalidated() const { 1756 AssertIsOnBackgroundThread(); 1757 1758 return mInvalidated; 1759 } 1760 1761 private: 1762 void Destroy(); 1763 1764 static void TimerCallback(nsITimer* aTimer, void* aClosure); 1765 }; 1766 1767 /******************************************************************************* 1768 * Actor class declarations 1769 ******************************************************************************/ 1770 1771 class Database final 1772 : public PBackgroundLSDatabaseParent, 1773 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> { 1774 RefPtr<Datastore> mDatastore; 1775 Snapshot* mSnapshot; 1776 const PrincipalInfo mPrincipalInfo; 1777 const Maybe<ContentParentId> mContentParentId; 1778 // Strings share buffers if possible, so it's not a problem to duplicate the 1779 // origin here. 1780 nsCString mOrigin; 1781 mozilla::glean::TimerId mRequestAllowToCloseTimerId; 1782 uint32_t mPrivateBrowsingId; 1783 bool mAllowedToClose; 1784 bool mActorDestroyed; 1785 bool mRequestedAllowToClose; 1786 #ifdef DEBUG 1787 bool mActorWasAlive; 1788 #endif 1789 1790 public: 1791 // Created in AllocPBackgroundLSDatabaseParent. 1792 Database(const PrincipalInfo& aPrincipalInfo, 1793 const Maybe<ContentParentId>& aContentParentId, 1794 const nsACString& aOrigin, uint32_t aPrivateBrowsingId); 1795 1796 void AssertIsOnOwningThread() const { 1797 AssertIsOnBackgroundThread(); 1798 NS_ASSERT_OWNINGTHREAD(mozilla::dom::Database); 1799 } 1800 1801 Datastore* GetDatastore() const { 1802 AssertIsOnOwningThread(); 1803 return mDatastore; 1804 } 1805 1806 Maybe<Datastore&> MaybeDatastoreRef() const { 1807 AssertIsOnOwningThread(); 1808 1809 return ToMaybeRef(mDatastore.get()); 1810 } 1811 1812 const PrincipalInfo& GetPrincipalInfo() const { return mPrincipalInfo; } 1813 1814 const Maybe<ContentParentId>& ContentParentIdRef() const { 1815 return mContentParentId; 1816 } 1817 1818 uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; } 1819 1820 const nsCString& Origin() const { return mOrigin; } 1821 1822 void SetActorAlive(Datastore* aDatastore); 1823 1824 void RegisterSnapshot(Snapshot* aSnapshot); 1825 1826 void UnregisterSnapshot(Snapshot* aSnapshot); 1827 1828 Snapshot* GetSnapshot() const { 1829 AssertIsOnOwningThread(); 1830 return mSnapshot; 1831 } 1832 1833 void RequestAllowToClose(); 1834 1835 void ForceKill(); 1836 1837 void Stringify(nsACString& aResult) const; 1838 1839 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Database, override) 1840 1841 private: 1842 // Reference counted. 1843 ~Database(); 1844 1845 void AllowToClose(); 1846 1847 // IPDL methods are only called by IPDL. 1848 void ActorDestroy(ActorDestroyReason aWhy) override; 1849 1850 mozilla::ipc::IPCResult RecvAllowToClose() override; 1851 1852 PBackgroundLSSnapshotParent* AllocPBackgroundLSSnapshotParent( 1853 const nsAString& aDocumentURI, const nsAString& aKey, 1854 const bool& aIncreasePeakUsage, const int64_t& aMinSize, 1855 LSSnapshotInitInfo* aInitInfo) override; 1856 1857 mozilla::ipc::IPCResult RecvPBackgroundLSSnapshotConstructor( 1858 PBackgroundLSSnapshotParent* aActor, const nsAString& aDocumentURI, 1859 const nsAString& aKey, const bool& aIncreasePeakUsage, 1860 const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override; 1861 1862 bool DeallocPBackgroundLSSnapshotParent( 1863 PBackgroundLSSnapshotParent* aActor) override; 1864 }; 1865 1866 /** 1867 * Attempts to capture the state of the underlying Datastore at the time of its 1868 * creation so run-to-completion semantics can be honored. 1869 * 1870 * Rather than simply duplicate the contents of `DataStore::mValues` and 1871 * `Datastore::mOrderedItems` at the time of their creation, the Snapshot tracks 1872 * mutations to the Datastore as they happen, saving off the state of values as 1873 * they existed when the Snapshot was created. In other words, given an initial 1874 * Datastore state of { foo: 'bar', bar: 'baz' }, the Snapshot won't store those 1875 * values until it hears via `SaveItem` that "foo" is being over-written. At 1876 * that time, it will save off foo='bar' in mValues. 1877 * 1878 * ## Quota Allocation ## 1879 * 1880 * ## States ## 1881 * 1882 */ 1883 class Snapshot final : public PBackgroundLSSnapshotParent { 1884 /** 1885 * The Database that owns this snapshot. There is a 1:1 relationship between 1886 * snapshots and databases. 1887 */ 1888 RefPtr<Database> mDatabase; 1889 RefPtr<Datastore> mDatastore; 1890 /** 1891 * The set of keys for which values have been sent to the child LSSnapshot. 1892 * Cleared once all values have been sent as indicated by 1893 * mLoadedItems.Count()==mTotalLength and therefore mLoadedAllItems should be 1894 * true. No requests should be received for keys already in this set, and 1895 * this is enforced by fatal IPC error (unless fuzzing). 1896 */ 1897 nsTHashtable<nsStringHashKey> mLoadedItems; 1898 /** 1899 * The set of keys for which a RecvLoadValueAndMoreItems request was received 1900 * but there was no such key, and so null was returned. The child LSSnapshot 1901 * will also cache these values, so redundant requests are also handled with 1902 * fatal process termination just like for mLoadedItems. Also cleared when 1903 * mLoadedAllItems becomes true because then the child can infer that all 1904 * other values must be null. (Note: this could also be done when 1905 * mLoadKeysReceived is true as a further optimization, but is not.) 1906 */ 1907 nsTHashSet<nsString> mUnknownItems; 1908 /** 1909 * Values that have changed in mDatastore as reported by SaveItem 1910 * notifications that are not yet known to the child LSSnapshot. 1911 * 1912 * The naive way to snapshot the state of mDatastore would be to duplicate its 1913 * internal mValues at the time of our creation, but that is wasteful if few 1914 * changes are made to the Datastore's state. So we only track values that 1915 * are changed/evicted from the Datastore as they happen, as reported to us by 1916 * SaveItem notifications. 1917 */ 1918 nsTHashMap<nsStringHashKey, LSValue> mValues; 1919 /** 1920 * Latched state of mDatastore's keys during a SaveItem notification with 1921 * aAffectsOrder=true. The ordered keys needed to be saved off so that a 1922 * consistent ordering could be presented to the child LSSnapshot when it asks 1923 * for them via RecvLoadKeys. 1924 */ 1925 nsTArray<nsString> mKeys; 1926 nsString mDocumentURI; 1927 /** 1928 * The index used for restoring iteration over not yet sent key/value pairs to 1929 * the child LSSnapshot. 1930 */ 1931 uint32_t mNextLoadIndex; 1932 /** 1933 * The number of key/value pairs that were present in the Datastore at the 1934 * time the snapshot was created. Once we have sent this many values to the 1935 * child LSSnapshot, we can infer that it has received all of the keys/values 1936 * and set mLoadedAllItems to true and clear mLoadedItems and mUnknownItems. 1937 * Note that knowing the keys/values is not the same as knowing their ordering 1938 * and so mKeys may be retained. 1939 */ 1940 uint32_t mTotalLength; 1941 int64_t mUsage; 1942 int64_t mPeakUsage; 1943 /** 1944 * True if SaveItem has saved mDatastore's keys into mKeys because a SaveItem 1945 * notification with aAffectsOrder=true was received. 1946 */ 1947 bool mSavedKeys; 1948 bool mActorDestroyed; 1949 bool mFinishReceived; 1950 bool mLoadedReceived; 1951 /** 1952 * True if LSSnapshot's mLoadState should be LoadState::AllOrderedItems or 1953 * LoadState::AllUnorderedItems. It will be AllOrderedItems if the initial 1954 * snapshot contained all the data or if the state was AllOrderedKeys and 1955 * successive RecvLoadValueAndMoreItems requests have resulted in the 1956 * LSSnapshot being told all of the key/value pairs. It will be 1957 * AllUnorderedItems if the state was LoadState::Partial and successive 1958 * RecvLoadValueAndMoreItem requests got all the keys/values but the key 1959 * ordering was not retrieved. 1960 */ 1961 bool mLoadedAllItems; 1962 /** 1963 * True if LSSnapshot's mLoadState should be LoadState::AllOrderedItems or 1964 * AllOrderedKeys. This can occur because of the initial snapshot, or because 1965 * a RecvLoadKeys request was received. 1966 */ 1967 bool mLoadKeysReceived; 1968 bool mSentMarkDirty; 1969 1970 /** 1971 * True if there are Database objects in other content processes. The value 1972 * never gets updated, we instead mark snapshots as dirty when Database 1973 * objects are added or removed. Marking snapshots as dirty forces creation 1974 * of new snapshots for new tasks. 1975 */ 1976 bool mHasOtherProcessDatabases; 1977 bool mHasOtherProcessObservers; 1978 1979 public: 1980 // Created in AllocPBackgroundLSSnapshotParent. 1981 Snapshot(Database* aDatabase, const nsAString& aDocumentURI); 1982 1983 void Init(nsTHashtable<nsStringHashKey>& aLoadedItems, 1984 nsTHashSet<nsString>&& aUnknownItems, uint32_t aNextLoadIndex, 1985 uint32_t aTotalLength, int64_t aUsage, int64_t aPeakUsage, 1986 LSSnapshot::LoadState aLoadState, bool aHasOtherProcessDatabases, 1987 bool aHasOtherProcessObservers) { 1988 AssertIsOnBackgroundThread(); 1989 MOZ_ASSERT(aUsage >= 0); 1990 MOZ_ASSERT(aPeakUsage >= aUsage); 1991 MOZ_ASSERT_IF(aLoadState != LSSnapshot::LoadState::AllOrderedItems, 1992 aNextLoadIndex < aTotalLength); 1993 MOZ_ASSERT(mTotalLength == 0); 1994 MOZ_ASSERT(mUsage == -1); 1995 MOZ_ASSERT(mPeakUsage == -1); 1996 1997 mLoadedItems.SwapElements(aLoadedItems); 1998 mUnknownItems = std::move(aUnknownItems); 1999 mNextLoadIndex = aNextLoadIndex; 2000 mTotalLength = aTotalLength; 2001 mUsage = aUsage; 2002 mPeakUsage = aPeakUsage; 2003 if (aLoadState == LSSnapshot::LoadState::AllOrderedKeys) { 2004 MOZ_ASSERT(mUnknownItems.Count() == 0); 2005 mLoadKeysReceived = true; 2006 } else if (aLoadState == LSSnapshot::LoadState::AllOrderedItems) { 2007 MOZ_ASSERT(mLoadedItems.Count() == 0); 2008 MOZ_ASSERT(mUnknownItems.Count() == 0); 2009 MOZ_ASSERT(mNextLoadIndex == mTotalLength); 2010 mLoadedReceived = true; 2011 mLoadedAllItems = true; 2012 mLoadKeysReceived = true; 2013 } 2014 mHasOtherProcessDatabases = aHasOtherProcessDatabases; 2015 mHasOtherProcessObservers = aHasOtherProcessObservers; 2016 } 2017 2018 /** 2019 * Called via NotifySnapshots by Datastore whenever it is updating its 2020 * internal state so that snapshots can save off the state of a value at the 2021 * time of their creation. 2022 */ 2023 void SaveItem(const nsAString& aKey, const LSValue& aOldValue, 2024 bool aAffectsOrder); 2025 2026 void MarkDirty(); 2027 2028 bool IsDirty() const { 2029 AssertIsOnBackgroundThread(); 2030 2031 return mSentMarkDirty; 2032 } 2033 2034 bool HasOtherProcessDatabases() const { 2035 AssertIsOnBackgroundThread(); 2036 2037 return mHasOtherProcessDatabases; 2038 } 2039 2040 bool HasOtherProcessObservers() const { 2041 AssertIsOnBackgroundThread(); 2042 2043 return mHasOtherProcessObservers; 2044 } 2045 2046 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Snapshot) 2047 2048 private: 2049 // Reference counted. 2050 ~Snapshot(); 2051 2052 mozilla::ipc::IPCResult Checkpoint(nsTArray<LSWriteInfo>&& aWriteInfos); 2053 2054 mozilla::ipc::IPCResult CheckpointAndNotify( 2055 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos); 2056 2057 void Finish(); 2058 2059 // IPDL methods are only called by IPDL. 2060 void ActorDestroy(ActorDestroyReason aWhy) override; 2061 2062 mozilla::ipc::IPCResult RecvDeleteMe() override; 2063 2064 mozilla::ipc::IPCResult RecvAsyncCheckpoint( 2065 nsTArray<LSWriteInfo>&& aWriteInfos) override; 2066 2067 mozilla::ipc::IPCResult RecvAsyncCheckpointAndNotify( 2068 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) override; 2069 2070 mozilla::ipc::IPCResult RecvSyncCheckpoint( 2071 nsTArray<LSWriteInfo>&& aWriteInfos) override; 2072 2073 mozilla::ipc::IPCResult RecvSyncCheckpointAndNotify( 2074 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) override; 2075 2076 mozilla::ipc::IPCResult RecvAsyncFinish() override; 2077 2078 mozilla::ipc::IPCResult RecvSyncFinish() override; 2079 2080 mozilla::ipc::IPCResult RecvLoaded() override; 2081 2082 mozilla::ipc::IPCResult RecvLoadValueAndMoreItems( 2083 const nsAString& aKey, LSValue* aValue, 2084 nsTArray<LSItemInfo>* aItemInfos) override; 2085 2086 mozilla::ipc::IPCResult RecvLoadKeys(nsTArray<nsString>* aKeys) override; 2087 2088 mozilla::ipc::IPCResult RecvIncreasePeakUsage(const int64_t& aMinSize, 2089 int64_t* aSize) override; 2090 }; 2091 2092 class Observer final : public PBackgroundLSObserverParent { 2093 const Maybe<ContentParentId> mContentParentId; 2094 nsCString mOrigin; 2095 bool mActorDestroyed; 2096 2097 public: 2098 // Created in AllocPBackgroundLSObserverParent. 2099 Observer(const Maybe<ContentParentId>& aContentParentId, 2100 const nsACString& aOrigin); 2101 2102 const Maybe<ContentParentId>& ContentParentIdRef() const { 2103 return mContentParentId; 2104 } 2105 2106 const nsCString& Origin() const { return mOrigin; } 2107 2108 void Observe(Database* aDatabase, const nsString& aDocumentURI, 2109 const nsString& aKey, const LSValue& aOldValue, 2110 const LSValue& aNewValue); 2111 2112 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Observer) 2113 2114 private: 2115 // Reference counted. 2116 ~Observer(); 2117 2118 // IPDL methods are only called by IPDL. 2119 void ActorDestroy(ActorDestroyReason aWhy) override; 2120 2121 mozilla::ipc::IPCResult RecvDeleteMe() override; 2122 }; 2123 2124 class LSRequestBase : public DatastoreOperationBase, 2125 public PBackgroundLSRequestParent { 2126 protected: 2127 enum class State { 2128 // Just created on the PBackground thread. Next step is StartingRequest. 2129 Initial, 2130 2131 // Waiting to start/starting request on the PBackground thread. Next step is 2132 // either Nesting if a subclass needs to process more nested states or 2133 // SendingReadyMessage if a subclass doesn't need any nested processing. 2134 StartingRequest, 2135 2136 // Doing nested processing. 2137 Nesting, 2138 2139 // Waiting to send/sending the ready message on the PBackground thread. Next 2140 // step is WaitingForFinish. 2141 SendingReadyMessage, 2142 2143 // Waiting for the finish message on the PBackground thread. Next step is 2144 // SendingResults. 2145 WaitingForFinish, 2146 2147 // Waiting to send/sending results on the PBackground thread. Next step is 2148 // Completed. 2149 SendingResults, 2150 2151 // All done. 2152 Completed 2153 }; 2154 2155 const LSRequestParams mParams; 2156 Maybe<ContentParentId> mContentParentId; 2157 State mState; 2158 bool mWaitingForFinish; 2159 2160 public: 2161 LSRequestBase(const LSRequestParams& aParams, 2162 const Maybe<ContentParentId>& aContentParentId); 2163 2164 void Dispatch(); 2165 2166 void StringifyState(nsACString& aResult) const; 2167 2168 virtual void Stringify(nsACString& aResult) const; 2169 2170 virtual void Log(); 2171 2172 protected: 2173 ~LSRequestBase() override; 2174 2175 virtual nsresult Start() = 0; 2176 2177 virtual nsresult NestedRun(); 2178 2179 virtual void GetResponse(LSRequestResponse& aResponse) = 0; 2180 2181 virtual void Cleanup() {} 2182 2183 private: 2184 bool VerifyRequestParams(); 2185 2186 nsresult StartRequest(); 2187 2188 void SendReadyMessage(); 2189 2190 nsresult SendReadyMessageInternal(); 2191 2192 void Finish(); 2193 2194 void FinishInternal(); 2195 2196 void SendResults(); 2197 2198 protected: 2199 // Common nsIRunnable implementation that subclasses may not override. 2200 NS_IMETHOD 2201 Run() final; 2202 2203 // IPDL methods. 2204 void ActorDestroy(ActorDestroyReason aWhy) override; 2205 2206 private: 2207 mozilla::ipc::IPCResult RecvCancel() final; 2208 2209 mozilla::ipc::IPCResult RecvFinish() final; 2210 }; 2211 2212 template <typename T> 2213 class SerializedOp { 2214 protected: 2215 void AddBlockingOp(T& aOp) { mBlocking.AppendElement(WrapNotNull(&aOp)); } 2216 2217 void AddBlockedOnOp(T& aOp) { mBlockedOn.AppendElement(WrapNotNull(&aOp)); } 2218 2219 void MaybeUnblock(T& aOp) { 2220 mBlockedOn.RemoveElement(&aOp); 2221 if (mBlockedOn.IsEmpty()) { 2222 Unblock(); 2223 } 2224 } 2225 2226 virtual void Unblock() = 0; 2227 2228 nsTArray<NotNull<RefPtr<T>>> mBlocking; 2229 nsTArray<NotNull<RefPtr<T>>> mBlockedOn; 2230 }; 2231 2232 class PrepareDatastoreOp 2233 : public LSRequestBase, 2234 public SerializedOp<PrepareDatastoreOp>, 2235 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> { 2236 class LoadDataOp; 2237 2238 class CompressFunction; 2239 class CompressionTypeFunction; 2240 2241 enum class NestedState { 2242 // The nesting has not yet taken place. Next step is 2243 // CheckExistingOperations. 2244 BeforeNesting, 2245 2246 // Starting directory opening on the PBackground thread. The next step is 2247 // DirectoryOpenPending. 2248 OpenDirectory, 2249 2250 // Waiting for directory open allowed on the PBackground thread. The next 2251 // step is either SendingReadyMessage if directory lock failed to acquire, 2252 // or CheckExistingOperations if directory lock is acquired. 2253 DirectoryOpenPending, 2254 2255 // Checking if a prepare datastore operation is already running for given 2256 // origin on the PBackground thread. Next step is CheckClosingDatastore. 2257 CheckExistingOperations, 2258 2259 // Checking if a datastore is closing the connection for given origin on 2260 // the PBackground thread. Next step is PreparationPending. 2261 CheckClosingDatastore, 2262 2263 // Ensuring quota manager is created and opening directory on the 2264 // PBackground thread. Next step is either SendingResults if quota manager 2265 // is not available or DatabaseWorkOpen if quota manager is available. If 2266 // a datastore already exists for given origin then the next state is 2267 // SendingReadyMessage. 2268 PreparationPending, 2269 2270 // Waiting to do/doing work on the QuotaManager IO thread. Its next step is 2271 // BeginLoadData. 2272 DatabaseWorkOpen, 2273 2274 // Starting a load data operation on the PBackground thread. Next step is 2275 // DatabaseWorkLoadData. 2276 BeginLoadData, 2277 2278 // Waiting to do/doing work on the connection thread. This involves waiting 2279 // for the LoadDataOp to do its work. Eventually the state will transition 2280 // to SendingReadyMessage. 2281 DatabaseWorkLoadData, 2282 2283 // The nesting has completed. 2284 AfterNesting 2285 }; 2286 2287 mozilla::glean::TimerId mProcessingTimerId; 2288 RefPtr<ClientDirectoryLock> mPendingDirectoryLock; 2289 ClientDirectoryLockHandle mDirectoryLockHandle; 2290 ClientDirectoryLockHandle mExtraDirectoryLockHandle; 2291 RefPtr<Connection> mConnection; 2292 RefPtr<Datastore> mDatastore; 2293 UniquePtr<ArchivedOriginScope> mArchivedOriginScope; 2294 LoadDataOp* mLoadDataOp; 2295 nsTHashMap<nsStringHashKey, LSValue> mValues; 2296 nsTArray<LSItemInfo> mOrderedItems; 2297 OriginMetadata mOriginMetadata; 2298 nsCString mMainThreadOrigin; 2299 nsString mDatabaseFilePath; 2300 uint32_t mPrivateBrowsingId; 2301 int64_t mUsage; 2302 int64_t mSizeOfKeys; 2303 int64_t mSizeOfItems; 2304 uint64_t mDatastoreId; 2305 NestedState mNestedState; 2306 const bool mForPreload; 2307 const bool mEnableMigration; 2308 bool mDatabaseNotAvailable; 2309 // Set when the Datastore has been registered with gPrivateDatastores so that 2310 // it can be unregistered if an error is encountered in PrepareDatastoreOp. 2311 FlippedOnce<false> mPrivateDatastoreRegistered; 2312 // Set when the Datastore has been registered with gPreparedDatastores so 2313 // that it can be unregistered if an error is encountered in 2314 // PrepareDatastoreOp. 2315 FlippedOnce<false> mPreparedDatastoreRegistered; 2316 bool mInvalidated; 2317 2318 #ifdef DEBUG 2319 int64_t mDEBUGUsage; 2320 #endif 2321 2322 public: 2323 PrepareDatastoreOp(const LSRequestParams& aParams, 2324 const Maybe<ContentParentId>& aContentParentId); 2325 2326 Maybe<ClientDirectoryLock&> MaybeDirectoryLockRef() const { 2327 AssertIsOnBackgroundThread(); 2328 2329 if (mDirectoryLockHandle) { 2330 return SomeRef(*mDirectoryLockHandle); 2331 } 2332 if (mExtraDirectoryLockHandle) { 2333 return SomeRef(*mExtraDirectoryLockHandle); 2334 } 2335 return Nothing(); 2336 } 2337 2338 bool OriginIsKnown() const { 2339 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread()); 2340 2341 return !mOriginMetadata.mOrigin.IsEmpty(); 2342 } 2343 2344 const nsCString& Origin() const { 2345 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread()); 2346 MOZ_ASSERT(OriginIsKnown()); 2347 2348 return mOriginMetadata.mOrigin; 2349 } 2350 2351 void Invalidate() { 2352 AssertIsOnOwningThread(); 2353 2354 mInvalidated = true; 2355 } 2356 2357 void StringifyNestedState(nsACString& aResult) const; 2358 2359 void Stringify(nsACString& aResult) const override; 2360 2361 void Log() override; 2362 2363 private: 2364 ~PrepareDatastoreOp() override; 2365 2366 nsresult Start() override; 2367 2368 nsresult OpenDirectory(); 2369 2370 void Unblock() override { 2371 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this)); 2372 } 2373 2374 nsresult CheckExistingOperations(); 2375 2376 nsresult CheckClosingDatastoreInternal(); 2377 2378 nsresult CheckClosingDatastore(); 2379 2380 nsresult BeginDatastorePreparationInternal(); 2381 2382 nsresult BeginDatastorePreparation(); 2383 2384 void SendToIOThread(); 2385 2386 nsresult DatabaseWork(); 2387 2388 nsresult DatabaseNotAvailable(); 2389 2390 nsresult EnsureDirectoryEntry(nsIFile* aEntry, bool aCreateIfNotExists, 2391 bool aDirectory, 2392 bool* aAlreadyExisted = nullptr); 2393 2394 nsresult VerifyDatabaseInformation(mozIStorageConnection* aConnection); 2395 2396 already_AddRefed<QuotaObject> GetQuotaObject(); 2397 2398 nsresult BeginLoadData(); 2399 2400 void FinishNesting(); 2401 2402 nsresult FinishNestingOnNonOwningThread(); 2403 2404 nsresult NestedRun() override; 2405 2406 void GetResponse(LSRequestResponse& aResponse) override; 2407 2408 void Cleanup() override; 2409 2410 void ConnectionClosedCallback(); 2411 2412 void CleanupMetadata(); 2413 2414 // IPDL overrides. 2415 void ActorDestroy(ActorDestroyReason aWhy) override; 2416 2417 void DirectoryLockAcquired(ClientDirectoryLockHandle aLockHandle); 2418 2419 void DirectoryLockFailed(); 2420 }; 2421 2422 class PrepareDatastoreOp::LoadDataOp final 2423 : public ConnectionDatastoreOperationBase { 2424 RefPtr<PrepareDatastoreOp> mPrepareDatastoreOp; 2425 2426 public: 2427 explicit LoadDataOp(PrepareDatastoreOp* aPrepareDatastoreOp) 2428 : ConnectionDatastoreOperationBase(aPrepareDatastoreOp->mConnection), 2429 mPrepareDatastoreOp(aPrepareDatastoreOp) {} 2430 2431 private: 2432 ~LoadDataOp() = default; 2433 2434 nsresult DoDatastoreWork() override; 2435 2436 void OnSuccess() override; 2437 2438 void OnFailure(nsresult aResultCode) override; 2439 2440 void Cleanup() override; 2441 }; 2442 2443 class PrepareDatastoreOp::CompressFunction final : public mozIStorageFunction { 2444 private: 2445 ~CompressFunction() = default; 2446 2447 NS_DECL_ISUPPORTS 2448 NS_DECL_MOZISTORAGEFUNCTION 2449 }; 2450 2451 class PrepareDatastoreOp::CompressionTypeFunction final 2452 : public mozIStorageFunction { 2453 private: 2454 ~CompressionTypeFunction() = default; 2455 2456 NS_DECL_ISUPPORTS 2457 NS_DECL_MOZISTORAGEFUNCTION 2458 }; 2459 2460 class PrepareObserverOp : public LSRequestBase { 2461 nsCString mOrigin; 2462 2463 public: 2464 PrepareObserverOp(const LSRequestParams& aParams, 2465 const Maybe<ContentParentId>& aContentParentId); 2466 2467 private: 2468 nsresult Start() override; 2469 2470 void GetResponse(LSRequestResponse& aResponse) override; 2471 }; 2472 2473 class LSSimpleRequestBase : public DatastoreOperationBase, 2474 public PBackgroundLSSimpleRequestParent { 2475 protected: 2476 enum class State { 2477 // Just created on the PBackground thread. Next step is StartingRequest. 2478 Initial, 2479 2480 // Waiting to start/starting request on the PBackground thread. Next step is 2481 // SendingResults. 2482 StartingRequest, 2483 2484 // Waiting to send/sending results on the PBackground thread. Next step is 2485 // Completed. 2486 SendingResults, 2487 2488 // All done. 2489 Completed 2490 }; 2491 2492 const LSSimpleRequestParams mParams; 2493 Maybe<ContentParentId> mContentParentId; 2494 State mState; 2495 2496 public: 2497 LSSimpleRequestBase(const LSSimpleRequestParams& aParams, 2498 const Maybe<ContentParentId>& aContentParentId); 2499 2500 void Dispatch(); 2501 2502 protected: 2503 ~LSSimpleRequestBase() override; 2504 2505 virtual nsresult Start() = 0; 2506 2507 virtual void GetResponse(LSSimpleRequestResponse& aResponse) = 0; 2508 2509 private: 2510 bool VerifyRequestParams(); 2511 2512 nsresult StartRequest(); 2513 2514 void SendResults(); 2515 2516 // Common nsIRunnable implementation that subclasses may not override. 2517 NS_IMETHOD 2518 Run() final; 2519 2520 // IPDL methods. 2521 void ActorDestroy(ActorDestroyReason aWhy) override; 2522 }; 2523 2524 class PreloadedOp : public LSSimpleRequestBase { 2525 nsCString mOrigin; 2526 2527 public: 2528 PreloadedOp(const LSSimpleRequestParams& aParams, 2529 const Maybe<ContentParentId>& aContentParentId); 2530 2531 private: 2532 nsresult Start() override; 2533 2534 void GetResponse(LSSimpleRequestResponse& aResponse) override; 2535 }; 2536 2537 class GetStateOp : public LSSimpleRequestBase { 2538 nsCString mOrigin; 2539 2540 public: 2541 GetStateOp(const LSSimpleRequestParams& aParams, 2542 const Maybe<ContentParentId>& aContentParentId); 2543 2544 private: 2545 nsresult Start() override; 2546 2547 void GetResponse(LSSimpleRequestResponse& aResponse) override; 2548 }; 2549 2550 /******************************************************************************* 2551 * Other class declarations 2552 ******************************************************************************/ 2553 2554 struct ArchivedOriginInfo { 2555 OriginAttributes mOriginAttributes; 2556 nsCString mOriginNoSuffix; 2557 2558 ArchivedOriginInfo(const OriginAttributes& aOriginAttributes, 2559 const nsACString& aOriginNoSuffix) 2560 : mOriginAttributes(aOriginAttributes), 2561 mOriginNoSuffix(aOriginNoSuffix) {} 2562 }; 2563 2564 class ArchivedOriginScope { 2565 struct Origin { 2566 nsCString mOriginSuffix; 2567 nsCString mOriginNoSuffix; 2568 2569 Origin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) 2570 : mOriginSuffix(aOriginSuffix), mOriginNoSuffix(aOriginNoSuffix) {} 2571 2572 const nsACString& OriginSuffix() const { return mOriginSuffix; } 2573 2574 const nsACString& OriginNoSuffix() const { return mOriginNoSuffix; } 2575 }; 2576 2577 struct Prefix { 2578 nsCString mOriginNoSuffix; 2579 2580 explicit Prefix(const nsACString& aOriginNoSuffix) 2581 : mOriginNoSuffix(aOriginNoSuffix) {} 2582 2583 const nsACString& OriginNoSuffix() const { return mOriginNoSuffix; } 2584 }; 2585 2586 struct Pattern { 2587 UniquePtr<OriginAttributesPattern> mPattern; 2588 2589 explicit Pattern(const OriginAttributesPattern& aPattern) 2590 : mPattern(MakeUnique<OriginAttributesPattern>(aPattern)) {} 2591 2592 Pattern(const Pattern& aOther) 2593 : mPattern(MakeUnique<OriginAttributesPattern>(*aOther.mPattern)) {} 2594 2595 Pattern(Pattern&& aOther) = default; 2596 2597 const OriginAttributesPattern& GetPattern() const { 2598 MOZ_ASSERT(mPattern); 2599 return *mPattern; 2600 } 2601 }; 2602 2603 struct Null {}; 2604 2605 using DataType = Variant<Origin, Pattern, Prefix, Null>; 2606 2607 DataType mData; 2608 2609 public: 2610 static UniquePtr<ArchivedOriginScope> CreateFromOrigin( 2611 const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey); 2612 2613 static UniquePtr<ArchivedOriginScope> CreateFromPrefix( 2614 const nsACString& aOriginKey); 2615 2616 static UniquePtr<ArchivedOriginScope> CreateFromPattern( 2617 const OriginAttributesPattern& aPattern); 2618 2619 static UniquePtr<ArchivedOriginScope> CreateFromNull(); 2620 2621 bool IsOrigin() const { return mData.is<Origin>(); } 2622 2623 bool IsPrefix() const { return mData.is<Prefix>(); } 2624 2625 bool IsPattern() const { return mData.is<Pattern>(); } 2626 2627 bool IsNull() const { return mData.is<Null>(); } 2628 2629 const nsACString& OriginSuffix() const { 2630 MOZ_ASSERT(IsOrigin()); 2631 2632 return mData.as<Origin>().OriginSuffix(); 2633 } 2634 2635 const nsACString& OriginNoSuffix() const { 2636 MOZ_ASSERT(IsOrigin() || IsPrefix()); 2637 2638 if (IsOrigin()) { 2639 return mData.as<Origin>().OriginNoSuffix(); 2640 } 2641 return mData.as<Prefix>().OriginNoSuffix(); 2642 } 2643 2644 const OriginAttributesPattern& GetPattern() const { 2645 MOZ_ASSERT(IsPattern()); 2646 2647 return mData.as<Pattern>().GetPattern(); 2648 } 2649 2650 nsLiteralCString GetBindingClause() const; 2651 2652 nsresult BindToStatement(mozIStorageStatement* aStatement) const; 2653 2654 bool HasMatches(ArchivedOriginHashtable* aHashtable) const; 2655 2656 void RemoveMatches(ArchivedOriginHashtable* aHashtable) const; 2657 2658 private: 2659 // Move constructors 2660 explicit ArchivedOriginScope(const Origin&& aOrigin) : mData(aOrigin) {} 2661 2662 explicit ArchivedOriginScope(const Pattern&& aPattern) : mData(aPattern) {} 2663 2664 explicit ArchivedOriginScope(const Prefix&& aPrefix) : mData(aPrefix) {} 2665 2666 explicit ArchivedOriginScope(const Null&& aNull) : mData(aNull) {} 2667 }; 2668 2669 class QuotaClient final : public mozilla::dom::quota::Client { 2670 class MatchFunction; 2671 2672 static QuotaClient* sInstance; 2673 2674 Mutex mShadowDatabaseMutex MOZ_UNANNOTATED; 2675 2676 public: 2677 QuotaClient(); 2678 2679 static QuotaClient* GetInstance() { 2680 AssertIsOnBackgroundThread(); 2681 2682 return sInstance; 2683 } 2684 2685 mozilla::Mutex& ShadowDatabaseMutex() { 2686 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 2687 2688 return mShadowDatabaseMutex; 2689 } 2690 2691 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::QuotaClient, override) 2692 2693 Type GetType() override; 2694 2695 Result<UsageInfo, nsresult> InitOrigin(PersistenceType aPersistenceType, 2696 const OriginMetadata& aOriginMetadata, 2697 const AtomicBool& aCanceled) override; 2698 2699 nsresult InitOriginWithoutTracking(PersistenceType aPersistenceType, 2700 const OriginMetadata& aOriginMetadata, 2701 const AtomicBool& aCanceled) override; 2702 2703 Result<UsageInfo, nsresult> GetUsageForOrigin( 2704 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 2705 const AtomicBool& aCanceled) override; 2706 2707 nsresult AboutToClearOrigins(const PersistenceScope& aPersistenceScope, 2708 const OriginScope& aOriginScope) override; 2709 2710 void OnOriginClearCompleted(const OriginMetadata& aOriginMetadata) override; 2711 2712 void OnRepositoryClearCompleted(PersistenceType aPersistenceType) override; 2713 2714 void ReleaseIOThreadObjects() override; 2715 2716 void AbortOperationsForLocks( 2717 const DirectoryLockIdTable& aDirectoryLockIds) override; 2718 2719 void AbortOperationsForProcess(ContentParentId aContentParentId) override; 2720 2721 void AbortAllOperations() override; 2722 2723 void StartIdleMaintenance() override; 2724 2725 void StopIdleMaintenance() override; 2726 2727 private: 2728 ~QuotaClient() override; 2729 2730 void InitiateShutdown() override; 2731 bool IsShutdownCompleted() const override; 2732 nsCString GetShutdownStatus() const override; 2733 void ForceKillActors() override; 2734 void FinalizeShutdown() override; 2735 2736 Result<UniquePtr<ArchivedOriginScope>, nsresult> CreateArchivedOriginScope( 2737 const OriginScope& aOriginScope); 2738 2739 nsresult PerformDelete(mozIStorageConnection* aConnection, 2740 const nsACString& aSchemaName, 2741 ArchivedOriginScope* aArchivedOriginScope) const; 2742 }; 2743 2744 class QuotaClient::MatchFunction final : public mozIStorageFunction { 2745 OriginAttributesPattern mPattern; 2746 2747 public: 2748 explicit MatchFunction(const OriginAttributesPattern& aPattern) 2749 : mPattern(aPattern) {} 2750 2751 private: 2752 ~MatchFunction() = default; 2753 2754 NS_DECL_ISUPPORTS 2755 NS_DECL_MOZISTORAGEFUNCTION 2756 }; 2757 2758 /******************************************************************************* 2759 * Helper classes 2760 ******************************************************************************/ 2761 2762 class MOZ_STACK_CLASS AutoWriteTransaction final { 2763 Connection* mConnection; 2764 Maybe<MutexAutoLock> mShadowDatabaseLock; 2765 bool mShadowWrites; 2766 2767 public: 2768 explicit AutoWriteTransaction(bool aShadowWrites); 2769 2770 ~AutoWriteTransaction(); 2771 2772 nsresult Start(Connection* aConnection); 2773 2774 nsresult Commit(); 2775 2776 private: 2777 nsresult LockAndAttachShadowDatabase(Connection* aConnection); 2778 2779 nsresult DetachShadowDatabaseAndUnlock(); 2780 }; 2781 2782 /******************************************************************************* 2783 * Globals 2784 ******************************************************************************/ 2785 2786 #ifdef DEBUG 2787 bool gLocalStorageInitialized = false; 2788 #endif 2789 2790 using PrepareDatastoreOpArray = 2791 nsTArray<NotNull<CheckedUnsafePtr<PrepareDatastoreOp>>>; 2792 2793 StaticAutoPtr<PrepareDatastoreOpArray> gPrepareDatastoreOps; 2794 2795 // When CheckedUnsafePtr's checking is enabled, it's necessary to ensure that 2796 // the hashtable uses the copy constructor instead of memmove for moving entries 2797 // since memmove will break CheckedUnsafePtr in a memory-corrupting way. 2798 using DatastoreHashKey = std::conditional<DiagnosticAssertEnabled::value, 2799 nsCStringHashKeyWithDisabledMemmove, 2800 nsCStringHashKey>::type; 2801 2802 using DatastoreHashtable = 2803 nsBaseHashtable<DatastoreHashKey, NotNull<CheckedUnsafePtr<Datastore>>, 2804 MovingNotNull<CheckedUnsafePtr<Datastore>>>; 2805 2806 StaticAutoPtr<DatastoreHashtable> gDatastores; 2807 2808 uint64_t gLastDatastoreId = 0; 2809 2810 using PreparedDatastoreHashtable = 2811 nsClassHashtable<nsUint64HashKey, PreparedDatastore>; 2812 2813 StaticAutoPtr<PreparedDatastoreHashtable> gPreparedDatastores; 2814 2815 using PrivateDatastoreHashtable = 2816 nsClassHashtable<nsCStringHashKey, PrivateDatastore>; 2817 2818 // Keeps Private Browsing Datastores alive until the private browsing session 2819 // is closed. This is necessary because LocalStorage Private Browsing data is 2820 // (currently) not written to disk and therefore needs to explicitly be kept 2821 // alive in memory so that if a user browses away from a site during a session 2822 // and then back to it that they will still have their data. 2823 // 2824 // The entries are wrapped by PrivateDatastore instances which call 2825 // NoteLivePrivateDatastore and NoteFinishedPrivateDatastore which set and 2826 // clear mHasLivePrivateDatastore which inhibits MaybeClose() from closing the 2827 // datastore (which would discard the data) when there are no active windows 2828 // using LocalStorage for the origin. 2829 // 2830 // The table is cleared when the Private Browsing session is closed, which will 2831 // cause NoteFinishedPrivateDatastore to be called on each Datastore which will 2832 // in turn call MaybeClose which should then discard the Datastore. Or in the 2833 // event of an (unlikely) race where the private browsing windows are still 2834 // being torn down, will cause the Datastore to be discarded when the last 2835 // window actually goes away. 2836 constinit UniquePtr<PrivateDatastoreHashtable> gPrivateDatastores; 2837 2838 using DatabaseArray = nsTArray<Database*>; 2839 2840 StaticAutoPtr<DatabaseArray> gDatabases; 2841 2842 using LiveDatabaseArray = nsTArray<NotNull<CheckedUnsafePtr<Database>>>; 2843 2844 StaticAutoPtr<LiveDatabaseArray> gLiveDatabases; 2845 2846 StaticRefPtr<ConnectionThread> gConnectionThread; 2847 2848 uint64_t gLastObserverId = 0; 2849 2850 using PreparedObserverHashtable = nsRefPtrHashtable<nsUint64HashKey, Observer>; 2851 2852 StaticAutoPtr<PreparedObserverHashtable> gPreparedObsevers; 2853 2854 using ObserverHashtable = 2855 nsClassHashtable<nsCStringHashKey, nsTArray<NotNull<Observer*>>>; 2856 2857 StaticAutoPtr<ObserverHashtable> gObservers; 2858 2859 Atomic<bool> gShadowWrites(kDefaultShadowWrites); 2860 Atomic<int32_t, Relaxed> gSnapshotPrefill(kDefaultSnapshotPrefill); 2861 Atomic<int32_t, Relaxed> gSnapshotGradualPrefill( 2862 kDefaultSnapshotGradualPrefill); 2863 Atomic<bool> gClientValidation(kDefaultClientValidation); 2864 2865 using UsageHashtable = nsTHashMap<nsCStringHashKey, int64_t>; 2866 2867 StaticAutoPtr<ArchivedOriginHashtable> gArchivedOrigins; 2868 2869 // Can only be touched on the Quota Manager I/O thread. 2870 bool gInitializedShadowStorage = false; 2871 2872 StaticAutoPtr<LSInitializationInfo> gInitializationInfo; 2873 2874 bool IsOnGlobalConnectionThread() { 2875 MOZ_ASSERT(gConnectionThread); 2876 return gConnectionThread->IsOnConnectionThread(); 2877 } 2878 2879 void AssertIsOnGlobalConnectionThread() { 2880 MOZ_ASSERT(gConnectionThread); 2881 gConnectionThread->AssertIsOnConnectionThread(); 2882 } 2883 2884 already_AddRefed<Datastore> GetDatastore(const nsACString& aOrigin) { 2885 AssertIsOnBackgroundThread(); 2886 2887 if (gDatastores) { 2888 auto maybeDatastore = gDatastores->MaybeGet(aOrigin); 2889 if (maybeDatastore) { 2890 RefPtr<Datastore> result(std::move(*maybeDatastore).unwrapBasePtr()); 2891 return result.forget(); 2892 } 2893 } 2894 2895 return nullptr; 2896 } 2897 2898 nsresult LoadArchivedOrigins() { 2899 GECKO_TRACE_SCOPE("dom::localstorage", "LoadArchivedOrigins"); 2900 2901 AssertIsOnIOThread(); 2902 MOZ_ASSERT(!gArchivedOrigins); 2903 2904 QuotaManager* quotaManager = QuotaManager::Get(); 2905 MOZ_ASSERT(quotaManager); 2906 2907 QM_TRY_INSPECT(const auto& connection, CreateArchiveStorageConnection( 2908 quotaManager->GetStoragePath())); 2909 2910 if (!connection) { 2911 gArchivedOrigins = new ArchivedOriginHashtable(); 2912 return NS_OK; 2913 } 2914 2915 QM_TRY_INSPECT( 2916 const auto& stmt, 2917 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2918 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement, 2919 "SELECT DISTINCT originAttributes, originKey " 2920 "FROM webappsstore2;"_ns)); 2921 2922 auto archivedOrigins = MakeUnique<ArchivedOriginHashtable>(); 2923 2924 // XXX Actually, this could use a hashtable variant of 2925 // CollectElementsWhileHasResult 2926 QM_TRY(quota::CollectWhileHasResult( 2927 *stmt, [&archivedOrigins](auto& stmt) -> Result<Ok, nsresult> { 2928 QM_TRY_INSPECT(const auto& originSuffix, 2929 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt, 2930 GetUTF8String, 0)); 2931 QM_TRY_INSPECT(const auto& originNoSuffix, 2932 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt, 2933 GetUTF8String, 1)); 2934 2935 const nsCString hashKey = 2936 GetArchivedOriginHashKey(originSuffix, originNoSuffix); 2937 2938 OriginAttributes originAttributes; 2939 QM_TRY(OkIf(originAttributes.PopulateFromSuffix(originSuffix)), 2940 Err(NS_ERROR_FAILURE)); 2941 2942 archivedOrigins->InsertOrUpdate( 2943 hashKey, 2944 MakeUnique<ArchivedOriginInfo>(originAttributes, originNoSuffix)); 2945 2946 return Ok{}; 2947 })); 2948 2949 gArchivedOrigins = archivedOrigins.release(); 2950 return NS_OK; 2951 } 2952 2953 Result<int64_t, nsresult> GetUsage(mozIStorageConnection& aConnection, 2954 ArchivedOriginScope* aArchivedOriginScope) { 2955 AssertIsOnIOThread(); 2956 2957 QM_TRY_INSPECT( 2958 const auto& stmt, 2959 ([aArchivedOriginScope, 2960 &aConnection]() -> Result<nsCOMPtr<mozIStorageStatement>, nsresult> { 2961 if (aArchivedOriginScope) { 2962 QM_TRY_RETURN(CreateAndExecuteSingleStepStatement< 2963 SingleStepResult::ReturnNullIfNoResult>( 2964 aConnection, 2965 "SELECT " 2966 "total(utf16Length(key) + utf16Length(value)) " 2967 "FROM webappsstore2 " 2968 "WHERE originKey = :originKey " 2969 "AND originAttributes = :originAttributes;"_ns, 2970 [aArchivedOriginScope](auto& stmt) -> Result<Ok, nsresult> { 2971 QM_TRY(MOZ_TO_RESULT( 2972 aArchivedOriginScope->BindToStatement(&stmt))); 2973 return Ok{}; 2974 })); 2975 } 2976 2977 QM_TRY_RETURN(CreateAndExecuteSingleStepStatement< 2978 SingleStepResult::ReturnNullIfNoResult>( 2979 aConnection, "SELECT usage FROM database"_ns)); 2980 }())); 2981 2982 QM_TRY(OkIf(stmt), Err(NS_ERROR_FAILURE)); 2983 2984 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0)); 2985 } 2986 2987 void ShadowWritesPrefChangedCallback(const char* aPrefName, void* aClosure) { 2988 MOZ_ASSERT(NS_IsMainThread()); 2989 MOZ_ASSERT(!strcmp(aPrefName, kShadowWritesPref)); 2990 MOZ_ASSERT(!aClosure); 2991 2992 gShadowWrites = Preferences::GetBool(aPrefName, kDefaultShadowWrites); 2993 } 2994 2995 void SnapshotPrefillPrefChangedCallback(const char* aPrefName, void* aClosure) { 2996 MOZ_ASSERT(NS_IsMainThread()); 2997 MOZ_ASSERT(!strcmp(aPrefName, kSnapshotPrefillPref)); 2998 MOZ_ASSERT(!aClosure); 2999 3000 int32_t snapshotPrefill = 3001 Preferences::GetInt(aPrefName, kDefaultSnapshotPrefill); 3002 3003 // The magic -1 is for use only by tests. 3004 if (snapshotPrefill == -1) { 3005 snapshotPrefill = INT32_MAX; 3006 } 3007 3008 gSnapshotPrefill = snapshotPrefill; 3009 } 3010 3011 void SnapshotGradualPrefillPrefChangedCallback(const char* aPrefName, 3012 void* aClosure) { 3013 MOZ_ASSERT(NS_IsMainThread()); 3014 MOZ_ASSERT(!strcmp(aPrefName, kSnapshotGradualPrefillPref)); 3015 MOZ_ASSERT(!aClosure); 3016 3017 int32_t snapshotGradualPrefill = 3018 Preferences::GetInt(aPrefName, kDefaultSnapshotGradualPrefill); 3019 3020 // The magic -1 is for use only by tests. 3021 if (snapshotGradualPrefill == -1) { 3022 snapshotGradualPrefill = INT32_MAX; 3023 } 3024 3025 gSnapshotGradualPrefill = snapshotGradualPrefill; 3026 } 3027 3028 int64_t GetSnapshotPeakUsagePreincrement(bool aInitial) { 3029 return aInitial ? StaticPrefs:: 3030 dom_storage_snapshot_peak_usage_initial_preincrement() 3031 : StaticPrefs:: 3032 dom_storage_snapshot_peak_usage_gradual_preincrement(); 3033 } 3034 3035 int64_t GetSnapshotPeakUsageReducedPreincrement(bool aInitial) { 3036 return aInitial 3037 ? StaticPrefs:: 3038 dom_storage_snapshot_peak_usage_reduced_initial_preincrement() 3039 : StaticPrefs:: 3040 dom_storage_snapshot_peak_usage_reduced_gradual_preincrement(); 3041 } 3042 3043 void ClientValidationPrefChangedCallback(const char* aPrefName, 3044 void* aClosure) { 3045 MOZ_ASSERT(NS_IsMainThread()); 3046 MOZ_ASSERT(!strcmp(aPrefName, kClientValidationPref)); 3047 MOZ_ASSERT(!aClosure); 3048 3049 gClientValidation = Preferences::GetBool(aPrefName, kDefaultClientValidation); 3050 } 3051 3052 template <typename Condition> 3053 void InvalidatePrepareDatastoreOpsMatching(const Condition& aCondition) { 3054 if (!gPrepareDatastoreOps) { 3055 return; 3056 } 3057 3058 for (const auto& prepareDatastoreOp : *gPrepareDatastoreOps) { 3059 if (aCondition(*prepareDatastoreOp)) { 3060 prepareDatastoreOp->Invalidate(); 3061 } 3062 } 3063 } 3064 3065 template <typename Condition> 3066 void InvalidatePreparedDatastoresMatching(const Condition& aCondition) { 3067 if (!gPreparedDatastores) { 3068 return; 3069 } 3070 3071 for (const auto& preparedDatastore : gPreparedDatastores->Values()) { 3072 MOZ_ASSERT(preparedDatastore); 3073 3074 if (aCondition(*preparedDatastore)) { 3075 preparedDatastore->Invalidate(); 3076 } 3077 } 3078 } 3079 3080 template <typename Condition> 3081 nsTArray<RefPtr<Database>> CollectDatabasesMatching(Condition aCondition) { 3082 AssertIsOnBackgroundThread(); 3083 3084 if (!gLiveDatabases) { 3085 return nsTArray<RefPtr<Database>>{}; 3086 } 3087 3088 nsTArray<RefPtr<Database>> databases; 3089 3090 for (const auto& database : *gLiveDatabases) { 3091 if (aCondition(*database)) { 3092 databases.AppendElement(database.get()); 3093 } 3094 } 3095 3096 return databases; 3097 } 3098 3099 template <typename Condition> 3100 void RequestAllowToCloseDatabasesMatching(Condition aCondition) { 3101 AssertIsOnBackgroundThread(); 3102 3103 nsTArray<RefPtr<Database>> databases = CollectDatabasesMatching(aCondition); 3104 3105 for (const auto& database : databases) { 3106 MOZ_ASSERT(database); 3107 3108 database->RequestAllowToClose(); 3109 } 3110 } 3111 3112 void ForceKillAllDatabases() { 3113 AssertIsOnBackgroundThread(); 3114 3115 nsTArray<RefPtr<Database>> databases = 3116 CollectDatabasesMatching([](const auto&) { return true; }); 3117 3118 for (const auto& database : databases) { 3119 MOZ_ASSERT(database); 3120 3121 database->ForceKill(); 3122 } 3123 } 3124 3125 bool VerifyPrincipalInfo(const PrincipalInfo& aPrincipalInfo, 3126 const PrincipalInfo& aStoragePrincipalInfo, 3127 bool aCheckClientPrincipal) { 3128 AssertIsOnBackgroundThread(); 3129 3130 if (NS_WARN_IF(!quota::IsPrincipalInfoValid(aPrincipalInfo))) { 3131 return false; 3132 } 3133 3134 // Note that the client prinicpal could have a different spec than the node 3135 // principal but they should have the same origin. It's because the client 3136 // could be initialized when opening the initial about:blank document and pass 3137 // to the newly opened window and reuse over there if the new window has the 3138 // same origin as the initial about:blank document. But, the FilePath could be 3139 // different. Therefore, we have to ignore comparing the Spec of the 3140 // principals if we are verifying clinet principal here. Also, when 3141 // document.domain is set, client principal won't get it. So, we don't compare 3142 // domain for client princpal too. 3143 bool result = aCheckClientPrincipal 3144 ? StoragePrincipalHelper:: 3145 VerifyValidClientPrincipalInfoForPrincipalInfo( 3146 aStoragePrincipalInfo, aPrincipalInfo) 3147 : StoragePrincipalHelper:: 3148 VerifyValidStoragePrincipalInfoForPrincipalInfo( 3149 aStoragePrincipalInfo, aPrincipalInfo); 3150 if (NS_WARN_IF(!result)) { 3151 return false; 3152 } 3153 3154 return true; 3155 } 3156 3157 bool VerifyClientId(const Maybe<ContentParentId>& aContentParentId, 3158 const Maybe<PrincipalInfo>& aPrincipalInfo, 3159 const Maybe<nsID>& aClientId) { 3160 AssertIsOnBackgroundThread(); 3161 3162 if (gClientValidation) { 3163 if (NS_WARN_IF(aClientId.isNothing())) { 3164 return false; 3165 } 3166 3167 if (NS_WARN_IF(aPrincipalInfo.isNothing())) { 3168 return false; 3169 } 3170 3171 RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance(); 3172 if (svc && NS_WARN_IF(!svc->HasWindow( 3173 aContentParentId, aPrincipalInfo.ref(), aClientId.ref()))) { 3174 return false; 3175 } 3176 } 3177 3178 return true; 3179 } 3180 3181 bool VerifyOriginKey(const nsACString& aOriginKey, 3182 const PrincipalInfo& aPrincipalInfo) { 3183 AssertIsOnBackgroundThread(); 3184 3185 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]), 3186 GenerateOriginKey2(aPrincipalInfo), false); 3187 3188 (void)originAttrSuffix; 3189 3190 QM_TRY(OkIf(originKey == aOriginKey), false, 3191 ([&originKey = originKey, &aOriginKey](const auto) { 3192 LS_WARNING("originKey (%s) doesn't match passed one (%s)!", 3193 originKey.get(), nsCString(aOriginKey).get()); 3194 })); 3195 3196 return true; 3197 } 3198 3199 LSInitializationInfo& MutableInitializationInfoRef(const CreateIfNonExistent&) { 3200 if (!gInitializationInfo) { 3201 gInitializationInfo = new LSInitializationInfo(); 3202 } 3203 return *gInitializationInfo; 3204 } 3205 3206 template <typename Func> 3207 auto ExecuteOriginInitialization(const nsACString& aOrigin, 3208 const LSOriginInitialization aInitialization, 3209 const nsACString& aContext, Func&& aFunc) 3210 -> std::invoke_result_t<Func, const FirstInitializationAttempt< 3211 LSOriginInitialization, Nothing>&> { 3212 return ExecuteInitialization( 3213 MutableInitializationInfoRef(CreateIfNonExistent{}) 3214 .MutableOriginInitializationInfoRef(aOrigin, CreateIfNonExistent{}), 3215 aInitialization, aContext, std::forward<Func>(aFunc)); 3216 } 3217 3218 } // namespace 3219 3220 /******************************************************************************* 3221 * Exported functions 3222 ******************************************************************************/ 3223 3224 void InitializeLocalStorage() { 3225 MOZ_ASSERT(XRE_IsParentProcess()); 3226 MOZ_ASSERT(NS_IsMainThread()); 3227 MOZ_ASSERT(!gLocalStorageInitialized); 3228 3229 // XXX Isn't this redundant? It's already done in InitializeQuotaManager. 3230 if (!QuotaManager::IsRunningGTests()) { 3231 // This service has to be started on the main thread currently. 3232 const nsCOMPtr<mozIStorageService> ss = 3233 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); 3234 3235 QM_WARNONLY_TRY(OkIf(ss)); 3236 } 3237 3238 Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback, 3239 kShadowWritesPref); 3240 3241 Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback, 3242 kSnapshotPrefillPref); 3243 3244 Preferences::RegisterCallbackAndCall( 3245 SnapshotGradualPrefillPrefChangedCallback, kSnapshotGradualPrefillPref); 3246 3247 Preferences::RegisterCallbackAndCall(ClientValidationPrefChangedCallback, 3248 kClientValidationPref); 3249 3250 #ifdef DEBUG 3251 gLocalStorageInitialized = true; 3252 #endif 3253 } 3254 3255 namespace { 3256 3257 // XXX Merge these three methods into a new factory method Database::Create 3258 3259 already_AddRefed<PBackgroundLSDatabaseParent> AllocPBackgroundLSDatabaseParent( 3260 const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId, 3261 const uint64_t& aDatastoreId) { 3262 AssertIsOnBackgroundThread(); 3263 3264 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { 3265 return nullptr; 3266 } 3267 3268 if (NS_WARN_IF(!gPreparedDatastores)) { 3269 MOZ_ASSERT_UNLESS_FUZZING(false); 3270 return nullptr; 3271 } 3272 3273 PreparedDatastore* preparedDatastore = gPreparedDatastores->Get(aDatastoreId); 3274 if (NS_WARN_IF(!preparedDatastore)) { 3275 MOZ_ASSERT_UNLESS_FUZZING(false); 3276 return nullptr; 3277 } 3278 3279 // If we ever decide to return null from this point on, we need to make sure 3280 // that the datastore is closed and the prepared datastore is removed from the 3281 // gPreparedDatastores hashtable. 3282 // We also assume that RecvCreateBackgroundLSDatabaseParent must call 3283 // RecvPBackgroundLSDatabaseConstructor once we return a valid actor in this 3284 // method. 3285 3286 RefPtr<Database> database = 3287 new Database(aPrincipalInfo, preparedDatastore->GetContentParentId(), 3288 preparedDatastore->Origin(), aPrivateBrowsingId); 3289 3290 return database.forget(); 3291 } 3292 3293 bool RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor, 3294 const PrincipalInfo& aPrincipalInfo, 3295 const uint32_t& aPrivateBrowsingId, 3296 const uint64_t& aDatastoreId) { 3297 AssertIsOnBackgroundThread(); 3298 MOZ_ASSERT(aActor); 3299 MOZ_ASSERT(gPreparedDatastores); 3300 MOZ_ASSERT(gPreparedDatastores->Get(aDatastoreId)); 3301 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 3302 3303 // The actor is now completely built (it has a channel and it's registered 3304 // as a top level protocol). 3305 // ActorDestroy will be called if we fail here. 3306 3307 mozilla::UniquePtr<PreparedDatastore> preparedDatastore; 3308 gPreparedDatastores->Remove(aDatastoreId, &preparedDatastore); 3309 MOZ_ASSERT(preparedDatastore); 3310 3311 auto* database = static_cast<Database*>(aActor); 3312 3313 database->SetActorAlive(&preparedDatastore->MutableDatastoreRef()); 3314 3315 // It's possible that AbortOperationsForLocks was called before the database 3316 // actor was created and became live. Let the child know that the database is 3317 // no longer valid. 3318 if (preparedDatastore->IsInvalidated()) { 3319 database->RequestAllowToClose(); 3320 } 3321 3322 return true; 3323 } 3324 3325 bool RecvCreateBackgroundLSDatabaseParent( 3326 const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId, 3327 const uint64_t& aDatastoreId, 3328 Endpoint<PBackgroundLSDatabaseParent>&& aParentEndpoint) { 3329 RefPtr<PBackgroundLSDatabaseParent> parent = AllocPBackgroundLSDatabaseParent( 3330 aPrincipalInfo, aPrivateBrowsingId, aDatastoreId); 3331 if (!parent) { 3332 return false; 3333 } 3334 3335 // Transfer ownership to IPDL. 3336 MOZ_ALWAYS_TRUE(aParentEndpoint.Bind(parent)); 3337 3338 return RecvPBackgroundLSDatabaseConstructor(parent, aPrincipalInfo, 3339 aPrivateBrowsingId, aDatastoreId); 3340 } 3341 3342 } // namespace 3343 3344 PBackgroundLSObserverParent* AllocPBackgroundLSObserverParent( 3345 const uint64_t& aObserverId) { 3346 AssertIsOnBackgroundThread(); 3347 3348 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { 3349 return nullptr; 3350 } 3351 3352 if (NS_WARN_IF(!gPreparedObsevers)) { 3353 MOZ_ASSERT_UNLESS_FUZZING(false); 3354 return nullptr; 3355 } 3356 3357 RefPtr<Observer> observer = gPreparedObsevers->Get(aObserverId); 3358 if (NS_WARN_IF(!observer)) { 3359 MOZ_ASSERT_UNLESS_FUZZING(false); 3360 return nullptr; 3361 } 3362 3363 // observer->SetObject(this); 3364 3365 // Transfer ownership to IPDL. 3366 return observer.forget().take(); 3367 } 3368 3369 bool RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent* aActor, 3370 const uint64_t& aObserverId) { 3371 AssertIsOnBackgroundThread(); 3372 MOZ_ASSERT(aActor); 3373 MOZ_ASSERT(gPreparedObsevers); 3374 MOZ_ASSERT(gPreparedObsevers->GetWeak(aObserverId)); 3375 3376 RefPtr<Observer> observer; 3377 gPreparedObsevers->Remove(aObserverId, observer.StartAssignment()); 3378 3379 if (!gPreparedObsevers->Count()) { 3380 gPreparedObsevers = nullptr; 3381 } 3382 3383 if (!gObservers) { 3384 gObservers = new ObserverHashtable(); 3385 } 3386 3387 const auto notNullObserver = WrapNotNull(observer.get()); 3388 3389 nsTArray<NotNull<Observer*>>* const array = 3390 gObservers->GetOrInsertNew(notNullObserver->Origin()); 3391 array->AppendElement(notNullObserver); 3392 3393 if (RefPtr<Datastore> datastore = GetDatastore(observer->Origin())) { 3394 datastore->NoteChangedObserverArray(*array); 3395 } 3396 3397 return true; 3398 } 3399 3400 bool DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor) { 3401 AssertIsOnBackgroundThread(); 3402 MOZ_ASSERT(aActor); 3403 3404 // Transfer ownership back from IPDL. 3405 RefPtr<Observer> actor = dont_AddRef(static_cast<Observer*>(aActor)); 3406 3407 return true; 3408 } 3409 3410 PBackgroundLSRequestParent* AllocPBackgroundLSRequestParent( 3411 PBackgroundParent* aBackgroundActor, const LSRequestParams& aParams) { 3412 AssertIsOnBackgroundThread(); 3413 MOZ_ASSERT(aParams.type() != LSRequestParams::T__None); 3414 3415 if (NS_WARN_IF(!NextGenLocalStorageEnabled())) { 3416 return nullptr; 3417 } 3418 3419 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { 3420 return nullptr; 3421 } 3422 3423 Maybe<ContentParentId> contentParentId; 3424 3425 uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor); 3426 if (childID) { 3427 contentParentId = Some(ContentParentId(childID)); 3428 } 3429 3430 RefPtr<LSRequestBase> actor; 3431 3432 switch (aParams.type()) { 3433 case LSRequestParams::TLSRequestPreloadDatastoreParams: 3434 case LSRequestParams::TLSRequestPrepareDatastoreParams: { 3435 RefPtr<PrepareDatastoreOp> prepareDatastoreOp = 3436 new PrepareDatastoreOp(aParams, contentParentId); 3437 3438 if (!gPrepareDatastoreOps) { 3439 gPrepareDatastoreOps = new PrepareDatastoreOpArray(); 3440 } 3441 gPrepareDatastoreOps->AppendElement( 3442 WrapNotNullUnchecked(prepareDatastoreOp.get())); 3443 3444 actor = std::move(prepareDatastoreOp); 3445 3446 break; 3447 } 3448 3449 case LSRequestParams::TLSRequestPrepareObserverParams: { 3450 RefPtr<PrepareObserverOp> prepareObserverOp = 3451 new PrepareObserverOp(aParams, contentParentId); 3452 3453 actor = std::move(prepareObserverOp); 3454 3455 break; 3456 } 3457 3458 default: 3459 MOZ_CRASH("Should never get here!"); 3460 } 3461 3462 // Transfer ownership to IPDL. 3463 return actor.forget().take(); 3464 } 3465 3466 bool RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor, 3467 const LSRequestParams& aParams) { 3468 AssertIsOnBackgroundThread(); 3469 MOZ_ASSERT(aActor); 3470 MOZ_ASSERT(aParams.type() != LSRequestParams::T__None); 3471 MOZ_ASSERT(NextGenLocalStorageEnabled()); 3472 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 3473 3474 // The actor is now completely built. 3475 3476 auto* op = static_cast<LSRequestBase*>(aActor); 3477 3478 op->Dispatch(); 3479 3480 return true; 3481 } 3482 3483 bool DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent* aActor) { 3484 AssertIsOnBackgroundThread(); 3485 3486 // Transfer ownership back from IPDL. 3487 RefPtr<LSRequestBase> actor = 3488 dont_AddRef(static_cast<LSRequestBase*>(aActor)); 3489 3490 return true; 3491 } 3492 3493 PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent( 3494 PBackgroundParent* aBackgroundActor, const LSSimpleRequestParams& aParams) { 3495 AssertIsOnBackgroundThread(); 3496 MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None); 3497 3498 if (NS_WARN_IF(!NextGenLocalStorageEnabled())) { 3499 return nullptr; 3500 } 3501 3502 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { 3503 return nullptr; 3504 } 3505 3506 Maybe<ContentParentId> contentParentId; 3507 3508 uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor); 3509 if (childID) { 3510 contentParentId = Some(ContentParentId(childID)); 3511 } 3512 3513 RefPtr<LSSimpleRequestBase> actor; 3514 3515 switch (aParams.type()) { 3516 case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: { 3517 RefPtr<PreloadedOp> preloadedOp = 3518 new PreloadedOp(aParams, contentParentId); 3519 3520 actor = std::move(preloadedOp); 3521 3522 break; 3523 } 3524 3525 case LSSimpleRequestParams::TLSSimpleRequestGetStateParams: { 3526 RefPtr<GetStateOp> getStateOp = new GetStateOp(aParams, contentParentId); 3527 3528 actor = std::move(getStateOp); 3529 3530 break; 3531 } 3532 3533 default: 3534 MOZ_CRASH("Should never get here!"); 3535 } 3536 3537 // Transfer ownership to IPDL. 3538 return actor.forget().take(); 3539 } 3540 3541 bool RecvPBackgroundLSSimpleRequestConstructor( 3542 PBackgroundLSSimpleRequestParent* aActor, 3543 const LSSimpleRequestParams& aParams) { 3544 AssertIsOnBackgroundThread(); 3545 MOZ_ASSERT(aActor); 3546 MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None); 3547 MOZ_ASSERT(NextGenLocalStorageEnabled()); 3548 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 3549 3550 // The actor is now completely built. 3551 3552 auto* op = static_cast<LSSimpleRequestBase*>(aActor); 3553 3554 op->Dispatch(); 3555 3556 return true; 3557 } 3558 3559 bool DeallocPBackgroundLSSimpleRequestParent( 3560 PBackgroundLSSimpleRequestParent* aActor) { 3561 AssertIsOnBackgroundThread(); 3562 3563 // Transfer ownership back from IPDL. 3564 RefPtr<LSSimpleRequestBase> actor = 3565 dont_AddRef(static_cast<LSSimpleRequestBase*>(aActor)); 3566 3567 return true; 3568 } 3569 3570 namespace localstorage { 3571 3572 already_AddRefed<mozilla::dom::quota::Client> CreateQuotaClient() { 3573 AssertIsOnBackgroundThread(); 3574 MOZ_ASSERT(CachedNextGenLocalStorageEnabled()); 3575 3576 RefPtr<QuotaClient> client = new QuotaClient(); 3577 return client.forget(); 3578 } 3579 3580 } // namespace localstorage 3581 3582 /******************************************************************************* 3583 * DatastoreWriteOptimizer 3584 ******************************************************************************/ 3585 3586 void DatastoreWriteOptimizer::ApplyAndReset( 3587 nsTArray<LSItemInfo>& aOrderedItems) { 3588 AssertIsOnOwningThread(); 3589 3590 // The mWriteInfos hash table contains all write infos, but it keeps them in 3591 // an arbitrary order, which means write infos need to be sorted before being 3592 // processed. However, the order is not important for deletions and normal 3593 // updates. Usually, filtering out deletions and updates would require extra 3594 // work, but we have to check the hash table for each ordered item anyway, so 3595 // we can remove the write info if it is a deletion or update without adding 3596 // extra overhead. In the end, only insertions need to be sorted before being 3597 // processed. 3598 3599 if (mTruncateInfo) { 3600 aOrderedItems.Clear(); 3601 mTruncateInfo = nullptr; 3602 } 3603 3604 for (int32_t index = aOrderedItems.Length() - 1; index >= 0; index--) { 3605 LSItemInfo& item = aOrderedItems[index]; 3606 3607 if (auto entry = mWriteInfos.Lookup(item.key())) { 3608 WriteInfo* writeInfo = entry->get(); 3609 3610 switch (writeInfo->GetType()) { 3611 case WriteInfo::DeleteItem: 3612 aOrderedItems.RemoveElementAt(index); 3613 entry.Remove(); 3614 break; 3615 3616 case WriteInfo::UpdateItem: { 3617 auto updateItemInfo = static_cast<UpdateItemInfo*>(writeInfo); 3618 if (updateItemInfo->UpdateWithMove()) { 3619 // See the comment in LSWriteOptimizer::InsertItem for more details 3620 // about the UpdateWithMove flag. 3621 3622 aOrderedItems.RemoveElementAt(index); 3623 entry.Data() = MakeUnique<InsertItemInfo>( 3624 updateItemInfo->SerialNumber(), updateItemInfo->GetKey(), 3625 updateItemInfo->GetValue()); 3626 } else { 3627 item.value() = updateItemInfo->GetValue(); 3628 entry.Remove(); 3629 } 3630 break; 3631 } 3632 3633 case WriteInfo::InsertItem: 3634 break; 3635 3636 default: 3637 MOZ_CRASH("Bad type!"); 3638 } 3639 } 3640 } 3641 3642 nsTArray<NotNull<WriteInfo*>> writeInfos; 3643 GetSortedWriteInfos(writeInfos); 3644 3645 for (WriteInfo* writeInfo : writeInfos) { 3646 MOZ_ASSERT(writeInfo->GetType() == WriteInfo::InsertItem); 3647 3648 auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo); 3649 3650 LSItemInfo* itemInfo = aOrderedItems.AppendElement(); 3651 itemInfo->key() = insertItemInfo->GetKey(); 3652 itemInfo->value() = insertItemInfo->GetValue(); 3653 } 3654 3655 mWriteInfos.Clear(); 3656 } 3657 3658 /******************************************************************************* 3659 * ConnectionWriteOptimizer 3660 ******************************************************************************/ 3661 3662 Result<int64_t, nsresult> ConnectionWriteOptimizer::Perform( 3663 Connection* aConnection, bool aShadowWrites) { 3664 AssertIsOnGlobalConnectionThread(); 3665 MOZ_ASSERT(aConnection); 3666 3667 // The order of elements is not stored in the database, so write infos don't 3668 // need to be sorted before being processed. 3669 3670 if (mTruncateInfo) { 3671 QM_TRY(MOZ_TO_RESULT(PerformTruncate(aConnection, aShadowWrites))); 3672 } 3673 3674 for (const auto& entry : mWriteInfos) { 3675 const WriteInfo* const writeInfo = entry.GetWeak(); 3676 3677 switch (writeInfo->GetType()) { 3678 case WriteInfo::InsertItem: 3679 case WriteInfo::UpdateItem: { 3680 const auto* const insertItemInfo = 3681 static_cast<const InsertItemInfo*>(writeInfo); 3682 3683 QM_TRY(MOZ_TO_RESULT(PerformInsertOrUpdate( 3684 aConnection, aShadowWrites, insertItemInfo->GetKey(), 3685 insertItemInfo->GetValue()))); 3686 3687 break; 3688 } 3689 3690 case WriteInfo::DeleteItem: { 3691 const auto* const deleteItemInfo = 3692 static_cast<const DeleteItemInfo*>(writeInfo); 3693 3694 QM_TRY(MOZ_TO_RESULT(PerformDelete(aConnection, aShadowWrites, 3695 deleteItemInfo->GetKey()))); 3696 3697 break; 3698 } 3699 3700 default: 3701 MOZ_CRASH("Bad type!"); 3702 } 3703 } 3704 3705 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 3706 "UPDATE database " 3707 "SET usage = usage + :delta"_ns, 3708 [this](auto& stmt) -> Result<Ok, nsresult> { 3709 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName("delta"_ns, mTotalDelta))); 3710 3711 return Ok{}; 3712 }))); 3713 3714 QM_TRY_INSPECT(const auto& stmt, CreateAndExecuteSingleStepStatement< 3715 SingleStepResult::ReturnNullIfNoResult>( 3716 aConnection->MutableStorageConnection(), 3717 "SELECT usage FROM database"_ns)); 3718 3719 QM_TRY(OkIf(stmt), Err(NS_ERROR_FAILURE)); 3720 3721 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 0)); 3722 } 3723 3724 nsresult ConnectionWriteOptimizer::PerformInsertOrUpdate( 3725 Connection* aConnection, bool aShadowWrites, const nsAString& aKey, 3726 const LSValue& aValue) { 3727 AssertIsOnGlobalConnectionThread(); 3728 MOZ_ASSERT(aConnection); 3729 3730 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 3731 "INSERT OR REPLACE INTO data (key, utf16_length, conversion_type, " 3732 "compression_type, value) " 3733 "VALUES(:key, :utf16_length, :conversion_type, :compression_type, :value)"_ns, 3734 [&aKey, &aValue](auto& stmt) -> Result<Ok, nsresult> { 3735 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey))); 3736 QM_TRY(MOZ_TO_RESULT( 3737 stmt.BindInt32ByName("utf16_length"_ns, aValue.UTF16Length()))); 3738 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByName( 3739 "conversion_type"_ns, 3740 static_cast<int32_t>(aValue.GetConversionType())))); 3741 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByName( 3742 "compression_type"_ns, 3743 static_cast<int32_t>(aValue.GetCompressionType())))); 3744 3745 if (0u == aValue.Length()) { // Otherwise empty string becomes null 3746 QM_TRY(MOZ_TO_RESULT( 3747 stmt.BindUTF8StringByName("value"_ns, aValue.AsCString()))); 3748 } else { 3749 QM_TRY(MOZ_TO_RESULT( 3750 stmt.BindUTF8StringAsBlobByName("value"_ns, aValue.AsCString()))); 3751 } 3752 3753 return Ok{}; 3754 }))); 3755 3756 if (!aShadowWrites) { 3757 return NS_OK; 3758 } 3759 3760 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 3761 "INSERT OR REPLACE INTO shadow.webappsstore2 " 3762 "(originAttributes, originKey, scope, key, value) " 3763 "VALUES (:originAttributes, :originKey, :scope, :key, :value) "_ns, 3764 [&aConnection, &aKey, &aValue](auto& stmt) -> Result<Ok, nsresult> { 3765 using ConversionType = LSValue::ConversionType; 3766 using CompressionType = LSValue::CompressionType; 3767 3768 const ArchivedOriginScope* const archivedOriginScope = 3769 aConnection->GetArchivedOriginScope(); 3770 3771 QM_TRY(MOZ_TO_RESULT(archivedOriginScope->BindToStatement(&stmt))); 3772 3773 QM_TRY(MOZ_TO_RESULT(stmt.BindUTF8StringByName( 3774 "scope"_ns, Scheme0Scope(archivedOriginScope->OriginSuffix(), 3775 archivedOriginScope->OriginNoSuffix())))); 3776 3777 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey))); 3778 3779 bool isCompressed = 3780 CompressionType::UNCOMPRESSED != aValue.GetCompressionType(); 3781 bool isAlreadyConverted = 3782 ConversionType::NONE != aValue.GetConversionType(); 3783 3784 nsCString buffer; 3785 const nsCString& valueBlob = aValue.AsCString(); 3786 if (isCompressed) { 3787 QM_TRY(OkIf(SnappyUncompress(valueBlob, buffer)), 3788 Err(NS_ERROR_FAILURE)); 3789 } 3790 const nsCString& value = isCompressed ? buffer : valueBlob; 3791 3792 // For shadow writes, we undo buffer swap and convert destructively 3793 nsCString unconverted; 3794 if (!isAlreadyConverted) { 3795 nsString converted; 3796 QM_TRY(OkIf(PutCStringBytesToString(value, converted)), 3797 Err(NS_ERROR_OUT_OF_MEMORY)); 3798 QM_TRY(OkIf(CopyUTF16toUTF8(converted, unconverted, fallible)), 3799 Err(NS_ERROR_OUT_OF_MEMORY)); // Corrupt invalid data 3800 } 3801 const nsCString& untransformed = 3802 (!isAlreadyConverted) ? unconverted : value; 3803 3804 QM_TRY(MOZ_TO_RESULT( 3805 stmt.BindUTF8StringByName("value"_ns, untransformed))); 3806 3807 return Ok{}; 3808 }))); 3809 3810 return NS_OK; 3811 } 3812 3813 nsresult ConnectionWriteOptimizer::PerformDelete(Connection* aConnection, 3814 bool aShadowWrites, 3815 const nsAString& aKey) { 3816 AssertIsOnGlobalConnectionThread(); 3817 MOZ_ASSERT(aConnection); 3818 3819 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 3820 "DELETE FROM data " 3821 "WHERE key = :key;"_ns, 3822 [&aKey](auto& stmt) -> Result<Ok, nsresult> { 3823 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey))); 3824 3825 return Ok{}; 3826 }))); 3827 3828 if (!aShadowWrites) { 3829 return NS_OK; 3830 } 3831 3832 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 3833 "DELETE FROM shadow.webappsstore2 " 3834 "WHERE originAttributes = :originAttributes " 3835 "AND originKey = :originKey " 3836 "AND key = :key;"_ns, 3837 [&aConnection, &aKey](auto& stmt) -> Result<Ok, nsresult> { 3838 QM_TRY(MOZ_TO_RESULT( 3839 aConnection->GetArchivedOriginScope()->BindToStatement(&stmt))); 3840 3841 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey))); 3842 3843 return Ok{}; 3844 }))); 3845 3846 return NS_OK; 3847 } 3848 3849 nsresult ConnectionWriteOptimizer::PerformTruncate(Connection* aConnection, 3850 bool aShadowWrites) { 3851 AssertIsOnGlobalConnectionThread(); 3852 MOZ_ASSERT(aConnection); 3853 3854 QM_TRY(MOZ_TO_RESULT( 3855 aConnection->ExecuteCachedStatement("DELETE FROM data;"_ns))); 3856 3857 if (!aShadowWrites) { 3858 return NS_OK; 3859 } 3860 3861 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement( 3862 "DELETE FROM shadow.webappsstore2 " 3863 "WHERE originAttributes = :originAttributes " 3864 "AND originKey = :originKey;"_ns, 3865 [&aConnection](auto& stmt) -> Result<Ok, nsresult> { 3866 QM_TRY(MOZ_TO_RESULT( 3867 aConnection->GetArchivedOriginScope()->BindToStatement(&stmt))); 3868 3869 return Ok{}; 3870 }))); 3871 3872 return NS_OK; 3873 } 3874 3875 /******************************************************************************* 3876 * DatastoreOperationBase 3877 ******************************************************************************/ 3878 3879 /******************************************************************************* 3880 * ConnectionDatastoreOperationBase 3881 ******************************************************************************/ 3882 3883 ConnectionDatastoreOperationBase::ConnectionDatastoreOperationBase( 3884 Connection* aConnection, bool aEnsureStorageConnection) 3885 : mConnection(aConnection), 3886 mEnsureStorageConnection(aEnsureStorageConnection) { 3887 MOZ_ASSERT(aConnection); 3888 } 3889 3890 ConnectionDatastoreOperationBase::~ConnectionDatastoreOperationBase() { 3891 MOZ_ASSERT(!mConnection, 3892 "ConnectionDatabaseOperationBase::Cleanup() was not called by a " 3893 "subclass!"); 3894 } 3895 3896 void ConnectionDatastoreOperationBase::Cleanup() { 3897 AssertIsOnOwningThread(); 3898 MOZ_ASSERT(mConnection); 3899 3900 mConnection = nullptr; 3901 3902 NoteComplete(); 3903 } 3904 3905 void ConnectionDatastoreOperationBase::OnSuccess() { AssertIsOnOwningThread(); } 3906 3907 void ConnectionDatastoreOperationBase::OnFailure(nsresult aResultCode) { 3908 AssertIsOnOwningThread(); 3909 MOZ_ASSERT(NS_FAILED(aResultCode)); 3910 } 3911 3912 void ConnectionDatastoreOperationBase::RunOnConnectionThread() { 3913 AssertIsOnGlobalConnectionThread(); 3914 MOZ_ASSERT(mConnection); 3915 MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); 3916 3917 if (!MayProceedOnNonOwningThread()) { 3918 SetFailureCode(NS_ERROR_ABORT); 3919 } else { 3920 nsresult rv = NS_OK; 3921 3922 // The boolean flag is only used by the CloseOp to avoid creating empty 3923 // databases. 3924 if (mEnsureStorageConnection) { 3925 rv = mConnection->EnsureStorageConnection(); 3926 if (NS_WARN_IF(NS_FAILED(rv))) { 3927 SetFailureCode(rv); 3928 } else { 3929 MOZ_ASSERT(mConnection->HasStorageConnection()); 3930 } 3931 } 3932 3933 if (NS_SUCCEEDED(rv)) { 3934 rv = DoDatastoreWork(); 3935 if (NS_FAILED(rv)) { 3936 SetFailureCode(rv); 3937 } 3938 } 3939 } 3940 3941 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); 3942 } 3943 3944 void ConnectionDatastoreOperationBase::RunOnOwningThread() { 3945 AssertIsOnOwningThread(); 3946 MOZ_ASSERT(mConnection); 3947 3948 if (!MayProceed()) { 3949 MaybeSetFailureCode(NS_ERROR_ABORT); 3950 } 3951 3952 if (NS_SUCCEEDED(ResultCode())) { 3953 OnSuccess(); 3954 } else { 3955 OnFailure(ResultCode()); 3956 } 3957 3958 Cleanup(); 3959 } 3960 3961 NS_IMETHODIMP 3962 ConnectionDatastoreOperationBase::Run() { 3963 if (IsOnGlobalConnectionThread()) { 3964 RunOnConnectionThread(); 3965 } else { 3966 RunOnOwningThread(); 3967 } 3968 3969 return NS_OK; 3970 } 3971 3972 /******************************************************************************* 3973 * Connection implementation 3974 ******************************************************************************/ 3975 3976 Connection::Connection(ConnectionThread* aConnectionThread, 3977 const OriginMetadata& aOriginMetadata, 3978 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope, 3979 bool aDatabaseWasNotAvailable) 3980 : mConnectionThread(aConnectionThread), 3981 mQuotaClient(QuotaClient::GetInstance()), 3982 mArchivedOriginScope(std::move(aArchivedOriginScope)), 3983 mOriginMetadata(aOriginMetadata), 3984 mDatabaseWasNotAvailable(aDatabaseWasNotAvailable), 3985 mHasCreatedDatabase(false), 3986 mFlushScheduled(false) 3987 #ifdef DEBUG 3988 , 3989 mInUpdateBatch(false), 3990 mFinished(false) 3991 #endif 3992 { 3993 AssertIsOnOwningThread(); 3994 MOZ_ASSERT(!aOriginMetadata.mGroup.IsEmpty()); 3995 MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty()); 3996 } 3997 3998 Connection::~Connection() { 3999 AssertIsOnOwningThread(); 4000 MOZ_ASSERT(!mFlushScheduled); 4001 MOZ_ASSERT(!mInUpdateBatch); 4002 MOZ_ASSERT(mFinished); 4003 } 4004 4005 void Connection::Dispatch(ConnectionDatastoreOperationBase* aOp) { 4006 AssertIsOnOwningThread(); 4007 MOZ_ASSERT(mConnectionThread); 4008 4009 MOZ_ALWAYS_SUCCEEDS( 4010 mConnectionThread->mThread->Dispatch(aOp, NS_DISPATCH_NORMAL)); 4011 } 4012 4013 void Connection::Close(nsIRunnable* aCallback) { 4014 AssertIsOnOwningThread(); 4015 MOZ_ASSERT(aCallback); 4016 4017 if (mFlushScheduled) { 4018 MOZ_ASSERT(mFlushTimer); 4019 MOZ_ALWAYS_SUCCEEDS(mFlushTimer->Cancel()); 4020 4021 Flush(); 4022 4023 mFlushTimer = nullptr; 4024 } 4025 4026 RefPtr<CloseOp> op = new CloseOp(this, aCallback); 4027 4028 Dispatch(op); 4029 } 4030 4031 void Connection::SetItem(const nsString& aKey, const LSValue& aValue, 4032 int64_t aDelta, bool aIsNewItem) { 4033 AssertIsOnOwningThread(); 4034 MOZ_ASSERT(mInUpdateBatch); 4035 4036 if (aIsNewItem) { 4037 mWriteOptimizer.InsertItem(aKey, aValue, aDelta); 4038 } else { 4039 mWriteOptimizer.UpdateItem(aKey, aValue, aDelta); 4040 } 4041 } 4042 4043 void Connection::RemoveItem(const nsString& aKey, int64_t aDelta) { 4044 AssertIsOnOwningThread(); 4045 MOZ_ASSERT(mInUpdateBatch); 4046 4047 mWriteOptimizer.DeleteItem(aKey, aDelta); 4048 } 4049 4050 void Connection::Clear(int64_t aDelta) { 4051 AssertIsOnOwningThread(); 4052 MOZ_ASSERT(mInUpdateBatch); 4053 4054 mWriteOptimizer.Truncate(aDelta); 4055 } 4056 4057 void Connection::BeginUpdateBatch() { 4058 AssertIsOnOwningThread(); 4059 MOZ_ASSERT(!mInUpdateBatch); 4060 4061 #ifdef DEBUG 4062 mInUpdateBatch = true; 4063 #endif 4064 } 4065 4066 void Connection::EndUpdateBatch() { 4067 AssertIsOnOwningThread(); 4068 MOZ_ASSERT(mInUpdateBatch); 4069 4070 if (mWriteOptimizer.HasWrites() && !mFlushScheduled) { 4071 ScheduleFlush(); 4072 } 4073 4074 #ifdef DEBUG 4075 mInUpdateBatch = false; 4076 #endif 4077 } 4078 4079 nsresult Connection::EnsureStorageConnection() { 4080 AssertIsOnGlobalConnectionThread(); 4081 4082 if (HasStorageConnection()) { 4083 return NS_OK; 4084 } 4085 4086 QuotaManager* quotaManager = QuotaManager::Get(); 4087 MOZ_ASSERT(quotaManager); 4088 4089 if (!mDatabaseWasNotAvailable || mHasCreatedDatabase) { 4090 MOZ_ASSERT(mOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_DEFAULT); 4091 4092 QM_TRY_INSPECT(const auto& directoryEntry, 4093 quotaManager->GetOriginDirectory(mOriginMetadata)); 4094 4095 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append( 4096 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME)))); 4097 4098 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDirectoryPath))); 4099 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName))); 4100 4101 QM_TRY_INSPECT( 4102 const auto& databaseFilePath, 4103 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directoryEntry, GetPath)); 4104 4105 QM_TRY_UNWRAP(auto storageConnection, 4106 GetStorageConnection(databaseFilePath)); 4107 LazyInit(WrapMovingNotNull(std::move(storageConnection))); 4108 4109 return NS_OK; 4110 } 4111 4112 auto helper = 4113 MakeRefPtr<GetOrCreateTemporaryOriginDirectoryHelper>(mOriginMetadata); 4114 4115 QM_TRY_INSPECT(const auto& originDirectoryPath, 4116 helper->BlockAndReturnOriginDirectoryPath()); 4117 4118 QM_TRY_INSPECT(const auto& directoryEntry, 4119 QM_NewLocalFile(originDirectoryPath)); 4120 4121 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append( 4122 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME)))); 4123 4124 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDirectoryPath))); 4125 4126 QM_TRY_INSPECT(const bool& exists, 4127 MOZ_TO_RESULT_INVOKE_MEMBER(directoryEntry, Exists)); 4128 4129 if (!exists) { 4130 QM_TRY( 4131 MOZ_TO_RESULT(directoryEntry->Create(nsIFile::DIRECTORY_TYPE, 0755))); 4132 } 4133 4134 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName))); 4135 4136 #ifdef DEBUG 4137 { 4138 QM_TRY_INSPECT(const bool& exists, 4139 MOZ_TO_RESULT_INVOKE_MEMBER(directoryEntry, Exists)); 4140 4141 MOZ_ASSERT(!exists); 4142 } 4143 #endif 4144 4145 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(mDirectoryPath)); 4146 4147 nsCOMPtr<mozIStorageConnection> storageConnection; 4148 4149 auto autoRemove = MakeScopeExit([&storageConnection, &directoryEntry] { 4150 if (storageConnection) { 4151 MOZ_ALWAYS_SUCCEEDS(storageConnection->Close()); 4152 } 4153 4154 nsresult rv = directoryEntry->Remove(false); 4155 if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { 4156 NS_WARNING("Failed to remove database file!"); 4157 } 4158 }); 4159 4160 QM_TRY_UNWRAP(storageConnection, CreateStorageConnectionWithRecovery( 4161 *directoryEntry, *usageFile, Origin(), 4162 [] { MOZ_ASSERT_UNREACHABLE(); })); 4163 4164 MOZ_ASSERT(mQuotaClient); 4165 4166 MutexAutoLock shadowDatabaseLock(mQuotaClient->ShadowDatabaseMutex()); 4167 4168 nsCOMPtr<mozIStorageConnection> shadowConnection; 4169 if (!gInitializedShadowStorage) { 4170 QM_TRY_UNWRAP(shadowConnection, 4171 CreateShadowStorageConnection(quotaManager->GetBasePath())); 4172 4173 gInitializedShadowStorage = true; 4174 } 4175 4176 autoRemove.release(); 4177 4178 if (!mHasCreatedDatabase) { 4179 mHasCreatedDatabase = true; 4180 } 4181 4182 LazyInit(WrapMovingNotNull(std::move(storageConnection))); 4183 4184 return NS_OK; 4185 } 4186 4187 void Connection::CloseStorageConnection() { 4188 AssertIsOnGlobalConnectionThread(); 4189 4190 CachingDatabaseConnection::Close(); 4191 } 4192 4193 nsresult Connection::BeginWriteTransaction() { 4194 AssertIsOnGlobalConnectionThread(); 4195 MOZ_ASSERT(HasStorageConnection()); 4196 4197 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN IMMEDIATE;"_ns))); 4198 4199 return NS_OK; 4200 } 4201 4202 nsresult Connection::CommitWriteTransaction() { 4203 AssertIsOnGlobalConnectionThread(); 4204 MOZ_ASSERT(HasStorageConnection()); 4205 4206 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("COMMIT;"_ns))); 4207 4208 return NS_OK; 4209 } 4210 4211 nsresult Connection::RollbackWriteTransaction() { 4212 AssertIsOnGlobalConnectionThread(); 4213 MOZ_ASSERT(HasStorageConnection()); 4214 4215 QM_TRY_INSPECT(const auto& stmt, BorrowCachedStatement("ROLLBACK;"_ns)); 4216 4217 // This may fail if SQLite already rolled back the transaction so ignore any 4218 // errors. 4219 (void)stmt->Execute(); 4220 4221 return NS_OK; 4222 } 4223 4224 void Connection::ScheduleFlush() { 4225 AssertIsOnOwningThread(); 4226 MOZ_ASSERT(mWriteOptimizer.HasWrites()); 4227 MOZ_ASSERT(!mFlushScheduled); 4228 4229 if (!mFlushTimer) { 4230 mFlushTimer = NS_NewTimer(); 4231 MOZ_ASSERT(mFlushTimer); 4232 } 4233 4234 MOZ_ALWAYS_SUCCEEDS(mFlushTimer->InitWithNamedFuncCallback( 4235 FlushTimerCallback, this, kFlushTimeoutMs, nsITimer::TYPE_ONE_SHOT, 4236 "Connection::FlushTimerCallback"_ns)); 4237 4238 mFlushScheduled = true; 4239 } 4240 4241 void Connection::Flush() { 4242 AssertIsOnOwningThread(); 4243 MOZ_ASSERT(mFlushScheduled); 4244 4245 if (mWriteOptimizer.HasWrites()) { 4246 RefPtr<FlushOp> op = new FlushOp(this, std::move(mWriteOptimizer)); 4247 4248 Dispatch(op); 4249 } 4250 4251 mFlushScheduled = false; 4252 } 4253 4254 // static 4255 void Connection::FlushTimerCallback(nsITimer* aTimer, void* aClosure) { 4256 MOZ_ASSERT(aClosure); 4257 4258 auto* self = static_cast<Connection*>(aClosure); 4259 MOZ_ASSERT(self); 4260 MOZ_ASSERT(self->mFlushScheduled); 4261 4262 self->Flush(); 4263 } 4264 4265 Result<nsString, nsresult> 4266 Connection::GetOrCreateTemporaryOriginDirectoryHelper:: 4267 BlockAndReturnOriginDirectoryPath() { 4268 AssertIsOnGlobalConnectionThread(); 4269 4270 QuotaManager* quotaManager = QuotaManager::Get(); 4271 MOZ_ASSERT(quotaManager); 4272 4273 MOZ_ALWAYS_SUCCEEDS( 4274 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)); 4275 4276 mozilla::MonitorAutoLock lock(mMonitor); 4277 while (mWaiting) { 4278 lock.Wait(); 4279 } 4280 4281 QM_TRY(MOZ_TO_RESULT(mIOThreadResultCode)); 4282 4283 return mOriginDirectoryPath; 4284 } 4285 4286 nsresult 4287 Connection::GetOrCreateTemporaryOriginDirectoryHelper::RunOnIOThread() { 4288 AssertIsOnIOThread(); 4289 4290 QuotaManager* quotaManager = QuotaManager::Get(); 4291 MOZ_ASSERT(quotaManager); 4292 4293 QM_TRY_INSPECT( 4294 const auto& directoryEntry, 4295 quotaManager->GetOrCreateTemporaryOriginDirectory(mOriginMetadata)); 4296 4297 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mOriginDirectoryPath))); 4298 4299 return NS_OK; 4300 } 4301 4302 NS_IMETHODIMP 4303 Connection::GetOrCreateTemporaryOriginDirectoryHelper::Run() { 4304 AssertIsOnIOThread(); 4305 4306 nsresult rv = RunOnIOThread(); 4307 if (NS_WARN_IF(NS_FAILED(rv))) { 4308 mIOThreadResultCode = rv; 4309 } 4310 4311 mozilla::MonitorAutoLock lock(mMonitor); 4312 MOZ_ASSERT(mWaiting); 4313 4314 mWaiting = false; 4315 lock.Notify(); 4316 4317 return NS_OK; 4318 } 4319 4320 Connection::FlushOp::FlushOp(Connection* aConnection, 4321 ConnectionWriteOptimizer&& aWriteOptimizer) 4322 : ConnectionDatastoreOperationBase(aConnection), 4323 mWriteOptimizer(std::move(aWriteOptimizer)), 4324 mShadowWrites(gShadowWrites) {} 4325 4326 nsresult Connection::FlushOp::DoDatastoreWork() { 4327 AssertIsOnGlobalConnectionThread(); 4328 MOZ_ASSERT(mConnection); 4329 4330 AutoWriteTransaction autoWriteTransaction(mShadowWrites); 4331 4332 QM_TRY(MOZ_TO_RESULT(autoWriteTransaction.Start(mConnection))); 4333 4334 QM_TRY_INSPECT(const int64_t& usage, 4335 mWriteOptimizer.Perform(mConnection, mShadowWrites)); 4336 4337 QM_TRY_INSPECT(const auto& usageFile, 4338 GetUsageFile(mConnection->DirectoryPath())); 4339 4340 QM_TRY_INSPECT(const auto& usageJournalFile, 4341 GetUsageJournalFile(mConnection->DirectoryPath())); 4342 4343 QM_TRY(MOZ_TO_RESULT(UpdateUsageFile(usageFile, usageJournalFile, usage))); 4344 4345 QM_TRY(MOZ_TO_RESULT(autoWriteTransaction.Commit())); 4346 4347 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false))); 4348 4349 return NS_OK; 4350 } 4351 4352 void Connection::FlushOp::Cleanup() { 4353 AssertIsOnOwningThread(); 4354 4355 mWriteOptimizer.Reset(); 4356 4357 MOZ_ASSERT(!mWriteOptimizer.HasWrites()); 4358 4359 ConnectionDatastoreOperationBase::Cleanup(); 4360 } 4361 4362 nsresult Connection::CloseOp::DoDatastoreWork() { 4363 AssertIsOnGlobalConnectionThread(); 4364 MOZ_ASSERT(mConnection); 4365 4366 if (mConnection->HasStorageConnection()) { 4367 mConnection->CloseStorageConnection(); 4368 } 4369 4370 return NS_OK; 4371 } 4372 4373 void Connection::CloseOp::Cleanup() { 4374 AssertIsOnOwningThread(); 4375 MOZ_ASSERT(mConnection); 4376 4377 mConnection->mConnectionThread->mConnections.Remove(mConnection->Origin()); 4378 4379 #ifdef DEBUG 4380 MOZ_ASSERT(!mConnection->mFinished); 4381 mConnection->mFinished = true; 4382 #endif 4383 4384 nsCOMPtr<nsIRunnable> callback; 4385 mCallback.swap(callback); 4386 4387 callback->Run(); 4388 4389 ConnectionDatastoreOperationBase::Cleanup(); 4390 } 4391 4392 /******************************************************************************* 4393 * ConnectionThread implementation 4394 ******************************************************************************/ 4395 4396 ConnectionThread::ConnectionThread() { 4397 AssertIsOnOwningThread(); 4398 AssertIsOnBackgroundThread(); 4399 4400 MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread("LS Thread", getter_AddRefs(mThread))); 4401 } 4402 4403 ConnectionThread::~ConnectionThread() { 4404 AssertIsOnOwningThread(); 4405 MOZ_ASSERT(!mConnections.Count()); 4406 } 4407 4408 bool ConnectionThread::IsOnConnectionThread() { 4409 MOZ_ASSERT(mThread); 4410 4411 bool current; 4412 return NS_SUCCEEDED(mThread->IsOnCurrentThread(¤t)) && current; 4413 } 4414 4415 void ConnectionThread::AssertIsOnConnectionThread() { 4416 MOZ_ASSERT(IsOnConnectionThread()); 4417 } 4418 4419 already_AddRefed<Connection> ConnectionThread::CreateConnection( 4420 const OriginMetadata& aOriginMetadata, 4421 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope, 4422 bool aDatabaseWasNotAvailable) { 4423 AssertIsOnOwningThread(); 4424 MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty()); 4425 MOZ_DIAGNOSTIC_ASSERT(!mConnections.Contains(aOriginMetadata.mOrigin)); 4426 4427 RefPtr<Connection> connection = 4428 new Connection(this, aOriginMetadata, std::move(aArchivedOriginScope), 4429 aDatabaseWasNotAvailable); 4430 mConnections.InsertOrUpdate(aOriginMetadata.mOrigin, RefPtr{connection}); 4431 4432 return connection.forget(); 4433 } 4434 4435 void ConnectionThread::Shutdown() { 4436 AssertIsOnOwningThread(); 4437 MOZ_ASSERT(mThread); 4438 4439 mThread->Shutdown(); 4440 } 4441 4442 /******************************************************************************* 4443 * Datastore 4444 ******************************************************************************/ 4445 4446 Datastore::Datastore(const OriginMetadata& aOriginMetadata, 4447 uint32_t aPrivateBrowsingId, int64_t aUsage, 4448 int64_t aSizeOfKeys, int64_t aSizeOfItems, 4449 ClientDirectoryLockHandle&& aDirectoryLockHandle, 4450 RefPtr<Connection>&& aConnection, 4451 RefPtr<QuotaObject>&& aQuotaObject, 4452 nsTHashMap<nsStringHashKey, LSValue>& aValues, 4453 nsTArray<LSItemInfo>&& aOrderedItems) 4454 : mDirectoryLockHandle(std::move(aDirectoryLockHandle)), 4455 mConnection(std::move(aConnection)), 4456 mQuotaObject(std::move(aQuotaObject)), 4457 mOrderedItems(std::move(aOrderedItems)), 4458 mOriginMetadata(aOriginMetadata), 4459 mPrivateBrowsingId(aPrivateBrowsingId), 4460 mUsage(aUsage), 4461 mUpdateBatchUsage(-1), 4462 mSizeOfKeys(aSizeOfKeys), 4463 mSizeOfItems(aSizeOfItems), 4464 mClosed(false), 4465 mInUpdateBatch(false), 4466 mHasLivePrivateDatastore(false) { 4467 AssertIsOnBackgroundThread(); 4468 4469 mValues.SwapElements(aValues); 4470 } 4471 4472 Datastore::~Datastore() { 4473 AssertIsOnBackgroundThread(); 4474 MOZ_ASSERT(mClosed); 4475 } 4476 4477 void Datastore::Close() { 4478 AssertIsOnBackgroundThread(); 4479 MOZ_DIAGNOSTIC_ASSERT(!mClosed); 4480 MOZ_ASSERT(!mPrepareDatastoreOps.Count()); 4481 MOZ_ASSERT(!mPreparedDatastores.Count()); 4482 MOZ_ASSERT(!mDatabases.Count()); 4483 MOZ_ASSERT(mDirectoryLockHandle); 4484 4485 mClosed = true; 4486 4487 if (IsPersistent()) { 4488 MOZ_ASSERT(mConnection); 4489 MOZ_ASSERT(mQuotaObject); 4490 4491 // We can't release the directory lock and unregister itself from the 4492 // hashtable until the connection is fully closed. 4493 nsCOMPtr<nsIRunnable> callback = 4494 NewRunnableMethod("dom::Datastore::ConnectionClosedCallback", this, 4495 &Datastore::ConnectionClosedCallback); 4496 mConnection->Close(callback); 4497 } else { 4498 MOZ_ASSERT(!mConnection); 4499 MOZ_ASSERT(!mQuotaObject); 4500 4501 // There's no connection, so it's safe to release the directory lock and 4502 // unregister itself from the hashtable. 4503 4504 { 4505 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 4506 } 4507 4508 CleanupMetadata(); 4509 } 4510 } 4511 4512 void Datastore::WaitForConnectionToComplete(nsIRunnable* aCallback) { 4513 AssertIsOnBackgroundThread(); 4514 MOZ_ASSERT(aCallback); 4515 MOZ_ASSERT(!mCompleteCallback); 4516 MOZ_ASSERT(mClosed); 4517 4518 mCompleteCallback = aCallback; 4519 } 4520 4521 void Datastore::NoteLivePrepareDatastoreOp( 4522 PrepareDatastoreOp* aPrepareDatastoreOp) { 4523 AssertIsOnBackgroundThread(); 4524 MOZ_ASSERT(aPrepareDatastoreOp); 4525 MOZ_ASSERT(!mPrepareDatastoreOps.Contains(aPrepareDatastoreOp)); 4526 MOZ_ASSERT(mDirectoryLockHandle); 4527 MOZ_ASSERT(!mClosed); 4528 4529 mPrepareDatastoreOps.Insert(aPrepareDatastoreOp); 4530 } 4531 4532 void Datastore::NoteFinishedPrepareDatastoreOp( 4533 PrepareDatastoreOp* aPrepareDatastoreOp) { 4534 AssertIsOnBackgroundThread(); 4535 MOZ_ASSERT(aPrepareDatastoreOp); 4536 MOZ_ASSERT(mPrepareDatastoreOps.Contains(aPrepareDatastoreOp)); 4537 MOZ_ASSERT(mDirectoryLockHandle); 4538 MOZ_ASSERT(!mClosed); 4539 4540 mPrepareDatastoreOps.Remove(aPrepareDatastoreOp); 4541 4542 QuotaManager::MaybeRecordQuotaClientShutdownStep( 4543 quota::Client::LS, "PrepareDatastoreOp finished"_ns); 4544 4545 MaybeClose(); 4546 } 4547 4548 void Datastore::NoteLivePrivateDatastore() { 4549 AssertIsOnBackgroundThread(); 4550 MOZ_ASSERT(!mHasLivePrivateDatastore); 4551 MOZ_ASSERT(mDirectoryLockHandle); 4552 MOZ_ASSERT(!mClosed); 4553 4554 mHasLivePrivateDatastore = true; 4555 } 4556 4557 void Datastore::NoteFinishedPrivateDatastore() { 4558 AssertIsOnBackgroundThread(); 4559 MOZ_ASSERT(mHasLivePrivateDatastore); 4560 MOZ_ASSERT(mDirectoryLockHandle); 4561 MOZ_ASSERT(!mClosed); 4562 4563 mHasLivePrivateDatastore = false; 4564 4565 QuotaManager::MaybeRecordQuotaClientShutdownStep( 4566 quota::Client::LS, "PrivateDatastore finished"_ns); 4567 4568 MaybeClose(); 4569 } 4570 4571 void Datastore::NoteLivePreparedDatastore( 4572 PreparedDatastore* aPreparedDatastore) { 4573 AssertIsOnBackgroundThread(); 4574 MOZ_ASSERT(aPreparedDatastore); 4575 MOZ_ASSERT(!mPreparedDatastores.Contains(aPreparedDatastore)); 4576 MOZ_ASSERT(mDirectoryLockHandle); 4577 MOZ_ASSERT(!mClosed); 4578 4579 mPreparedDatastores.Insert(aPreparedDatastore); 4580 } 4581 4582 void Datastore::NoteFinishedPreparedDatastore( 4583 PreparedDatastore* aPreparedDatastore) { 4584 AssertIsOnBackgroundThread(); 4585 MOZ_ASSERT(aPreparedDatastore); 4586 MOZ_ASSERT(mPreparedDatastores.Contains(aPreparedDatastore)); 4587 MOZ_ASSERT(mDirectoryLockHandle); 4588 MOZ_ASSERT(!mClosed); 4589 4590 mPreparedDatastores.Remove(aPreparedDatastore); 4591 4592 QuotaManager::MaybeRecordQuotaClientShutdownStep( 4593 quota::Client::LS, "PreparedDatastore finished"_ns); 4594 4595 MaybeClose(); 4596 } 4597 4598 bool Datastore::HasOtherProcessDatabases(Database* aDatabase) { 4599 AssertIsOnBackgroundThread(); 4600 4601 for (Database* database : mDatabases) { 4602 if (database->ContentParentIdRef() != aDatabase->ContentParentIdRef()) { 4603 return true; 4604 } 4605 } 4606 4607 return false; 4608 } 4609 4610 void Datastore::NoteLiveDatabase(Database* aDatabase) { 4611 AssertIsOnBackgroundThread(); 4612 MOZ_ASSERT(aDatabase); 4613 MOZ_ASSERT(!mDatabases.Contains(aDatabase)); 4614 MOZ_ASSERT(mDirectoryLockHandle); 4615 MOZ_ASSERT(!mClosed); 4616 4617 mDatabases.Insert(aDatabase); 4618 4619 NoteChangedDatabaseMap(); 4620 } 4621 4622 void Datastore::NoteFinishedDatabase(Database* aDatabase) { 4623 AssertIsOnBackgroundThread(); 4624 MOZ_ASSERT(aDatabase); 4625 MOZ_ASSERT(mDatabases.Contains(aDatabase)); 4626 MOZ_ASSERT(!mActiveDatabases.Contains(aDatabase)); 4627 MOZ_ASSERT(mDirectoryLockHandle); 4628 MOZ_ASSERT(!mClosed); 4629 4630 mDatabases.Remove(aDatabase); 4631 4632 NoteChangedDatabaseMap(); 4633 4634 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS, 4635 "Database finished"_ns); 4636 4637 MaybeClose(); 4638 } 4639 4640 void Datastore::NoteActiveDatabase(Database* aDatabase) { 4641 AssertIsOnBackgroundThread(); 4642 MOZ_ASSERT(aDatabase); 4643 MOZ_ASSERT(mDatabases.Contains(aDatabase)); 4644 MOZ_ASSERT(!mActiveDatabases.Contains(aDatabase)); 4645 MOZ_ASSERT(!mClosed); 4646 4647 mActiveDatabases.Insert(aDatabase); 4648 } 4649 4650 void Datastore::NoteInactiveDatabase(Database* aDatabase) { 4651 AssertIsOnBackgroundThread(); 4652 MOZ_ASSERT(aDatabase); 4653 MOZ_ASSERT(mDatabases.Contains(aDatabase)); 4654 MOZ_ASSERT(mActiveDatabases.Contains(aDatabase)); 4655 MOZ_ASSERT(!mClosed); 4656 4657 mActiveDatabases.Remove(aDatabase); 4658 4659 if (!mActiveDatabases.Count() && mPendingUsageDeltas.Length()) { 4660 int64_t finalDelta = 0; 4661 4662 for (auto delta : mPendingUsageDeltas) { 4663 finalDelta += delta; 4664 } 4665 4666 MOZ_ASSERT(finalDelta <= 0); 4667 4668 if (finalDelta != 0) { 4669 DebugOnly<bool> ok = UpdateUsage(finalDelta); 4670 MOZ_ASSERT(ok); 4671 } 4672 4673 mPendingUsageDeltas.Clear(); 4674 } 4675 } 4676 4677 void Datastore::GetSnapshotLoadInfo(const nsAString& aKey, 4678 bool& aAddKeyToUnknownItems, 4679 nsTHashtable<nsStringHashKey>& aLoadedItems, 4680 nsTArray<LSItemInfo>& aItemInfos, 4681 uint32_t& aNextLoadIndex, 4682 LSSnapshot::LoadState& aLoadState) { 4683 AssertIsOnBackgroundThread(); 4684 MOZ_ASSERT(!mClosed); 4685 MOZ_ASSERT(!mInUpdateBatch); 4686 4687 #ifdef DEBUG 4688 int64_t sizeOfKeys = 0; 4689 int64_t sizeOfItems = 0; 4690 for (auto item : mOrderedItems) { 4691 int64_t sizeOfKey = static_cast<int64_t>(item.key().Length()); 4692 sizeOfKeys += sizeOfKey; 4693 sizeOfItems += sizeOfKey + static_cast<int64_t>(item.value().Length()); 4694 } 4695 MOZ_ASSERT(mSizeOfKeys == sizeOfKeys); 4696 MOZ_ASSERT(mSizeOfItems == sizeOfItems); 4697 #endif 4698 4699 // Computes load state optimized for current size of keys and items. 4700 // Zero key length and value can be passed to do a quick initial estimation. 4701 // If computed load state is already AllOrderedItems then excluded key length 4702 // and value length can't make it any better. 4703 auto GetLoadState = [&](int64_t aKeyLength, int64_t aValueLength) { 4704 if (mSizeOfKeys - aKeyLength <= gSnapshotPrefill) { 4705 if (mSizeOfItems - aKeyLength - aValueLength <= gSnapshotPrefill) { 4706 return LSSnapshot::LoadState::AllOrderedItems; 4707 } 4708 4709 return LSSnapshot::LoadState::AllOrderedKeys; 4710 } 4711 4712 return LSSnapshot::LoadState::Partial; 4713 }; 4714 4715 // Value for given aKey if aKey is not void (can be void too if value doesn't 4716 // exist for given aKey). 4717 LSValue value; 4718 // If aKey and value are not void, checkKey will be set to true. Once we find 4719 // an item for given aKey in one of the loops below, checkKey is set to false 4720 // to prevent additional comparison of strings (string implementation compares 4721 // string lengths first to avoid char by char comparison if possible). 4722 bool checkKey = false; 4723 4724 // Avoid additional hash lookup if all ordered items fit into initial prefill 4725 // already. 4726 LSSnapshot::LoadState loadState = GetLoadState(/* aKeyLength */ 0, 4727 /* aValueLength */ 0); 4728 if (loadState != LSSnapshot::LoadState::AllOrderedItems && !aKey.IsVoid()) { 4729 GetItem(aKey, value); 4730 if (!value.IsVoid()) { 4731 // Ok, we have a non void aKey and value. 4732 4733 // We have to watch for aKey during one of the loops below to exclude it 4734 // from the size computation. The super fast mode (AllOrderedItems) 4735 // doesn't have to do that though. 4736 checkKey = true; 4737 4738 // We have to compute load state again because aKey length and value 4739 // length is excluded from the size in this case. 4740 loadState = GetLoadState(aKey.Length(), value.Length()); 4741 } 4742 } 4743 4744 switch (loadState) { 4745 case LSSnapshot::LoadState::AllOrderedItems: { 4746 // We're sending all ordered items, we don't need to check keys because 4747 // mOrderedItems must contain a value for aKey if checkKey is true. 4748 4749 aItemInfos.AppendElements(mOrderedItems); 4750 4751 MOZ_ASSERT(aItemInfos.Length() == mValues.Count()); 4752 aNextLoadIndex = mValues.Count(); 4753 4754 aAddKeyToUnknownItems = false; 4755 4756 break; 4757 } 4758 4759 case LSSnapshot::LoadState::AllOrderedKeys: { 4760 // We don't have enough snapshot budget to send all items, but we do have 4761 // enough to send all of the keys and to make a best effort to populate as 4762 // many values as possible. We send void string values once we run out of 4763 // budget. A complicating factor is that we want to make sure that we send 4764 // the value for aKey which is a localStorage read that's triggering this 4765 // request. Since that key can happen anywhere in the list of items, we 4766 // need to handle it specially. 4767 // 4768 // The loop is effectively doing 2 things in parallel: 4769 // 4770 // 1. Looking for the `aKey` to send. This is tracked by `checkKey` 4771 // which is true if there was an `aKey` specified and until we 4772 // populate its value, and false thereafter. 4773 // 2. Sending values until we run out of `size` budget and switch to 4774 // sending void values. `doneSendingValues` tracks when we've run out 4775 // of size budget, with `setVoidValue` tracking whether a value 4776 // should be sent for each turn of the event loop but can be 4777 // overridden when `aKey` is found. 4778 4779 int64_t size = mSizeOfKeys; 4780 bool setVoidValue = false; 4781 bool doneSendingValues = false; 4782 for (uint32_t index = 0; index < mOrderedItems.Length(); index++) { 4783 const LSItemInfo& item = mOrderedItems[index]; 4784 4785 const nsString& key = item.key(); 4786 const LSValue& value = item.value(); 4787 4788 if (checkKey && key == aKey) { 4789 checkKey = false; 4790 setVoidValue = false; 4791 } else if (!setVoidValue) { 4792 if (doneSendingValues) { 4793 setVoidValue = true; 4794 } else { 4795 size += static_cast<int64_t>(value.Length()); 4796 4797 if (size > gSnapshotPrefill) { 4798 setVoidValue = true; 4799 doneSendingValues = true; 4800 4801 // We set doneSendingValues to true and that will guard against 4802 // entering this branch during next iterations. So aNextLoadIndex 4803 // is set only once. 4804 aNextLoadIndex = index; 4805 } 4806 } 4807 } 4808 4809 LSItemInfo* itemInfo = aItemInfos.AppendElement(); 4810 itemInfo->key() = key; 4811 if (setVoidValue) { 4812 itemInfo->value().SetIsVoid(true); 4813 } else { 4814 aLoadedItems.PutEntry(key); 4815 itemInfo->value() = value; 4816 } 4817 } 4818 4819 aAddKeyToUnknownItems = false; 4820 4821 break; 4822 } 4823 4824 case LSSnapshot::LoadState::Partial: { 4825 int64_t size = 0; 4826 for (uint32_t index = 0; index < mOrderedItems.Length(); index++) { 4827 const LSItemInfo& item = mOrderedItems[index]; 4828 4829 const nsString& key = item.key(); 4830 const LSValue& value = item.value(); 4831 4832 if (checkKey && key == aKey) { 4833 checkKey = false; 4834 } else { 4835 size += static_cast<int64_t>(key.Length()) + 4836 static_cast<int64_t>(value.Length()); 4837 4838 if (size > gSnapshotPrefill) { 4839 aNextLoadIndex = index; 4840 break; 4841 } 4842 } 4843 4844 aLoadedItems.PutEntry(key); 4845 4846 LSItemInfo* itemInfo = aItemInfos.AppendElement(); 4847 itemInfo->key() = key; 4848 itemInfo->value() = value; 4849 } 4850 4851 aAddKeyToUnknownItems = false; 4852 4853 if (!aKey.IsVoid()) { 4854 if (value.IsVoid()) { 4855 aAddKeyToUnknownItems = true; 4856 } else if (checkKey) { 4857 // The item wasn't added in the loop above, add it here. 4858 4859 LSItemInfo* itemInfo = aItemInfos.AppendElement(); 4860 itemInfo->key() = aKey; 4861 itemInfo->value() = value; 4862 } 4863 } 4864 4865 MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length()); 4866 4867 break; 4868 } 4869 4870 default: 4871 MOZ_CRASH("Bad load state value!"); 4872 } 4873 4874 aLoadState = loadState; 4875 } 4876 4877 void Datastore::GetItem(const nsAString& aKey, LSValue& aValue) const { 4878 AssertIsOnBackgroundThread(); 4879 MOZ_ASSERT(!mClosed); 4880 4881 if (!mValues.Get(aKey, &aValue)) { 4882 aValue.SetIsVoid(true); 4883 } 4884 } 4885 4886 void Datastore::GetKeys(nsTArray<nsString>& aKeys) const { 4887 AssertIsOnBackgroundThread(); 4888 MOZ_ASSERT(!mClosed); 4889 4890 for (auto item : mOrderedItems) { 4891 aKeys.AppendElement(item.key()); 4892 } 4893 } 4894 4895 void Datastore::SetItem(Database* aDatabase, const nsString& aKey, 4896 const LSValue& aValue) { 4897 AssertIsOnBackgroundThread(); 4898 MOZ_ASSERT(aDatabase); 4899 MOZ_ASSERT(!mClosed); 4900 MOZ_ASSERT(mInUpdateBatch); 4901 4902 LSValue oldValue; 4903 GetItem(aKey, oldValue); 4904 4905 if (oldValue != aValue) { 4906 bool isNewItem = oldValue.IsVoid(); 4907 4908 NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem); 4909 4910 mValues.InsertOrUpdate(aKey, aValue); 4911 4912 int64_t delta; 4913 4914 if (isNewItem) { 4915 mWriteOptimizer.InsertItem(aKey, aValue); 4916 4917 int64_t sizeOfKey = static_cast<int64_t>(aKey.Length()); 4918 4919 delta = sizeOfKey + static_cast<int64_t>(aValue.UTF16Length()); 4920 4921 mUpdateBatchUsage += delta; 4922 4923 mSizeOfKeys += sizeOfKey; 4924 mSizeOfItems += sizeOfKey + static_cast<int64_t>(aValue.Length()); 4925 } else { 4926 mWriteOptimizer.UpdateItem(aKey, aValue); 4927 4928 delta = static_cast<int64_t>(aValue.UTF16Length()) - 4929 static_cast<int64_t>(oldValue.UTF16Length()); 4930 4931 mUpdateBatchUsage += delta; 4932 4933 mSizeOfItems += static_cast<int64_t>(aValue.Length()) - 4934 static_cast<int64_t>(oldValue.Length()); 4935 } 4936 4937 if (IsPersistent()) { 4938 mConnection->SetItem(aKey, aValue, delta, isNewItem); 4939 } 4940 } 4941 } 4942 4943 void Datastore::RemoveItem(Database* aDatabase, const nsString& aKey) { 4944 AssertIsOnBackgroundThread(); 4945 MOZ_ASSERT(aDatabase); 4946 MOZ_ASSERT(!mClosed); 4947 MOZ_ASSERT(mInUpdateBatch); 4948 4949 LSValue oldValue; 4950 GetItem(aKey, oldValue); 4951 4952 if (!oldValue.IsVoid()) { 4953 NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true); 4954 4955 mValues.Remove(aKey); 4956 4957 mWriteOptimizer.DeleteItem(aKey); 4958 4959 int64_t sizeOfKey = static_cast<int64_t>(aKey.Length()); 4960 4961 int64_t delta = -sizeOfKey - static_cast<int64_t>(oldValue.UTF16Length()); 4962 4963 mUpdateBatchUsage += delta; 4964 4965 mSizeOfKeys -= sizeOfKey; 4966 mSizeOfItems -= sizeOfKey + static_cast<int64_t>(oldValue.Length()); 4967 4968 if (IsPersistent()) { 4969 mConnection->RemoveItem(aKey, delta); 4970 } 4971 } 4972 } 4973 4974 void Datastore::Clear(Database* aDatabase) { 4975 AssertIsOnBackgroundThread(); 4976 MOZ_ASSERT(!mClosed); 4977 4978 if (mValues.Count()) { 4979 int64_t delta = 0; 4980 for (const auto& entry : mValues) { 4981 const nsAString& key = entry.GetKey(); 4982 const LSValue& value = entry.GetData(); 4983 4984 delta += -static_cast<int64_t>(key.Length()) - 4985 static_cast<int64_t>(value.UTF16Length()); 4986 4987 NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true); 4988 } 4989 4990 mValues.Clear(); 4991 4992 if (mInUpdateBatch) { 4993 mWriteOptimizer.Truncate(); 4994 4995 mUpdateBatchUsage += delta; 4996 } else { 4997 mOrderedItems.Clear(); 4998 4999 DebugOnly<bool> ok = UpdateUsage(delta); 5000 MOZ_ASSERT(ok); 5001 } 5002 5003 mSizeOfKeys = 0; 5004 mSizeOfItems = 0; 5005 5006 if (IsPersistent()) { 5007 mConnection->Clear(delta); 5008 } 5009 } 5010 } 5011 5012 void Datastore::BeginUpdateBatch(int64_t aSnapshotUsage) { 5013 AssertIsOnBackgroundThread(); 5014 // Don't assert `aSnapshotUsage >= 0`, it can be negative when multiple 5015 // snapshots are operating in parallel. 5016 MOZ_ASSERT(!mClosed); 5017 MOZ_ASSERT(mUpdateBatchUsage == -1); 5018 MOZ_ASSERT(!mInUpdateBatch); 5019 5020 mUpdateBatchUsage = aSnapshotUsage; 5021 5022 if (IsPersistent()) { 5023 mConnection->BeginUpdateBatch(); 5024 } 5025 5026 mInUpdateBatch = true; 5027 } 5028 5029 int64_t Datastore::EndUpdateBatch(int64_t aSnapshotPeakUsage) { 5030 AssertIsOnBackgroundThread(); 5031 MOZ_ASSERT(!mClosed); 5032 MOZ_ASSERT(mInUpdateBatch); 5033 5034 mWriteOptimizer.ApplyAndReset(mOrderedItems); 5035 5036 MOZ_ASSERT(!mWriteOptimizer.HasWrites()); 5037 5038 if (aSnapshotPeakUsage >= 0) { 5039 int64_t delta = mUpdateBatchUsage - aSnapshotPeakUsage; 5040 5041 if (mActiveDatabases.Count()) { 5042 // We can't apply deltas while other databases are still active. 5043 // The final delta must be zero or negative, but individual deltas can 5044 // be positive. A positive delta can't be applied asynchronously since 5045 // there's no way to fire the quota exceeded error event. 5046 5047 mPendingUsageDeltas.AppendElement(delta); 5048 } else { 5049 MOZ_ASSERT(delta <= 0); 5050 if (delta != 0) { 5051 DebugOnly<bool> ok = UpdateUsage(delta); 5052 MOZ_ASSERT(ok); 5053 } 5054 } 5055 } 5056 5057 int64_t result = mUpdateBatchUsage; 5058 mUpdateBatchUsage = -1; 5059 5060 if (IsPersistent()) { 5061 mConnection->EndUpdateBatch(); 5062 } 5063 5064 mInUpdateBatch = false; 5065 5066 return result; 5067 } 5068 5069 int64_t Datastore::AttemptToUpdateUsage(int64_t aMinSize, bool aInitial) { 5070 AssertIsOnBackgroundThread(); 5071 MOZ_ASSERT_IF(aInitial, aMinSize >= 0); 5072 MOZ_ASSERT_IF(!aInitial, aMinSize > 0); 5073 5074 const int64_t size = aMinSize + GetSnapshotPeakUsagePreincrement(aInitial); 5075 5076 if (size && UpdateUsage(size)) { 5077 return size; 5078 } 5079 5080 const int64_t reducedSize = 5081 aMinSize + GetSnapshotPeakUsageReducedPreincrement(aInitial); 5082 5083 if (reducedSize && UpdateUsage(reducedSize)) { 5084 return reducedSize; 5085 } 5086 5087 if (aMinSize > 0 && UpdateUsage(aMinSize)) { 5088 return aMinSize; 5089 } 5090 5091 return 0; 5092 } 5093 5094 bool Datastore::HasOtherProcessObservers(Database* aDatabase) { 5095 AssertIsOnBackgroundThread(); 5096 MOZ_ASSERT(aDatabase); 5097 5098 if (!gObservers) { 5099 return false; 5100 } 5101 5102 nsTArray<NotNull<Observer*>>* array; 5103 if (!gObservers->Get(mOriginMetadata.mOrigin, &array)) { 5104 return false; 5105 } 5106 5107 MOZ_ASSERT(array); 5108 5109 for (Observer* observer : *array) { 5110 if (observer->ContentParentIdRef() != aDatabase->ContentParentIdRef()) { 5111 return true; 5112 } 5113 } 5114 5115 return false; 5116 } 5117 5118 void Datastore::NotifyOtherProcessObservers(Database* aDatabase, 5119 const nsString& aDocumentURI, 5120 const nsString& aKey, 5121 const LSValue& aOldValue, 5122 const LSValue& aNewValue) { 5123 AssertIsOnBackgroundThread(); 5124 MOZ_ASSERT(aDatabase); 5125 5126 if (!gObservers) { 5127 return; 5128 } 5129 5130 nsTArray<NotNull<Observer*>>* array; 5131 if (!gObservers->Get(mOriginMetadata.mOrigin, &array)) { 5132 return; 5133 } 5134 5135 MOZ_ASSERT(array); 5136 5137 // We do not want to send information about events back to the content process 5138 // that caused the change. 5139 5140 for (Observer* observer : *array) { 5141 if (observer->ContentParentIdRef() != aDatabase->ContentParentIdRef()) { 5142 observer->Observe(aDatabase, aDocumentURI, aKey, aOldValue, aNewValue); 5143 } 5144 } 5145 } 5146 5147 void Datastore::NoteChangedObserverArray( 5148 const nsTArray<NotNull<Observer*>>& aObservers) { 5149 AssertIsOnBackgroundThread(); 5150 5151 for (Database* database : mActiveDatabases) { 5152 Snapshot* snapshot = database->GetSnapshot(); 5153 MOZ_ASSERT(snapshot); 5154 5155 if (snapshot->IsDirty()) { 5156 continue; 5157 } 5158 5159 bool hasOtherProcessObservers = false; 5160 5161 for (Observer* observer : aObservers) { 5162 if (observer->ContentParentIdRef() != database->ContentParentIdRef()) { 5163 hasOtherProcessObservers = true; 5164 break; 5165 } 5166 } 5167 5168 if (snapshot->HasOtherProcessObservers() != hasOtherProcessObservers) { 5169 snapshot->MarkDirty(); 5170 } 5171 } 5172 } 5173 5174 void Datastore::Stringify(nsACString& aResult) const { 5175 AssertIsOnBackgroundThread(); 5176 5177 aResult.AppendLiteral("DirectoryLock:"); 5178 aResult.AppendInt(!!mDirectoryLockHandle); 5179 aResult.Append(kQuotaGenericDelimiter); 5180 5181 aResult.AppendLiteral("Connection:"); 5182 aResult.AppendInt(!!mConnection); 5183 aResult.Append(kQuotaGenericDelimiter); 5184 5185 aResult.AppendLiteral("QuotaObject:"); 5186 aResult.AppendInt(!!mQuotaObject); 5187 aResult.Append(kQuotaGenericDelimiter); 5188 5189 aResult.AppendLiteral("PrepareDatastoreOps:"); 5190 aResult.AppendInt(mPrepareDatastoreOps.Count()); 5191 aResult.Append(kQuotaGenericDelimiter); 5192 5193 aResult.AppendLiteral("PreparedDatastores:"); 5194 aResult.AppendInt(mPreparedDatastores.Count()); 5195 aResult.Append(kQuotaGenericDelimiter); 5196 5197 aResult.AppendLiteral("Databases:"); 5198 aResult.AppendInt(mDatabases.Count()); 5199 aResult.Append(kQuotaGenericDelimiter); 5200 5201 aResult.AppendLiteral("ActiveDatabases:"); 5202 aResult.AppendInt(mActiveDatabases.Count()); 5203 aResult.Append(kQuotaGenericDelimiter); 5204 5205 aResult.AppendLiteral("Origin:"); 5206 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin)); 5207 aResult.Append(kQuotaGenericDelimiter); 5208 5209 aResult.AppendLiteral("PrivateBrowsingId:"); 5210 aResult.AppendInt(mPrivateBrowsingId); 5211 aResult.Append(kQuotaGenericDelimiter); 5212 5213 aResult.AppendLiteral("Closed:"); 5214 aResult.AppendInt(mClosed); 5215 } 5216 5217 bool Datastore::UpdateUsage(int64_t aDelta) { 5218 AssertIsOnBackgroundThread(); 5219 5220 // Check internal LocalStorage origin limit. 5221 int64_t newUsage = mUsage + aDelta; 5222 5223 MOZ_ASSERT(newUsage >= 0); 5224 5225 if (newUsage > StaticPrefs::dom_storage_default_quota() * 1024) { 5226 return false; 5227 } 5228 5229 // Check QuotaManager limits (group and global limit). 5230 if (IsPersistent()) { 5231 MOZ_ASSERT(mQuotaObject); 5232 5233 if (!mQuotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) { 5234 return false; 5235 } 5236 } 5237 5238 // Quota checks passed, set new usage. 5239 mUsage = newUsage; 5240 5241 return true; 5242 } 5243 5244 void Datastore::MaybeClose() { 5245 AssertIsOnBackgroundThread(); 5246 5247 if (!mPrepareDatastoreOps.Count() && !mHasLivePrivateDatastore && 5248 !mPreparedDatastores.Count() && !mDatabases.Count()) { 5249 Close(); 5250 } 5251 } 5252 5253 void Datastore::ConnectionClosedCallback() { 5254 AssertIsOnBackgroundThread(); 5255 MOZ_ASSERT(mDirectoryLockHandle); 5256 MOZ_ASSERT(mConnection); 5257 MOZ_ASSERT(mQuotaObject); 5258 MOZ_ASSERT(mClosed); 5259 5260 // Release the quota object first. 5261 mQuotaObject = nullptr; 5262 5263 bool databaseWasNotAvailable; 5264 bool hasCreatedDatabase; 5265 mConnection->GetFinishInfo(databaseWasNotAvailable, hasCreatedDatabase); 5266 5267 if (databaseWasNotAvailable && !hasCreatedDatabase) { 5268 MOZ_ASSERT(mUsage == 0); 5269 5270 QuotaManager* quotaManager = QuotaManager::Get(); 5271 MOZ_ASSERT(quotaManager); 5272 5273 quotaManager->ResetUsageForClient( 5274 ClientMetadata{mOriginMetadata, mozilla::dom::quota::Client::LS}); 5275 } 5276 5277 mConnection = nullptr; 5278 5279 // Now it's safe to release the directory lock and unregister itself from 5280 // the hashtable. 5281 5282 { 5283 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 5284 } 5285 5286 CleanupMetadata(); 5287 5288 if (mCompleteCallback) { 5289 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget())); 5290 } 5291 } 5292 5293 void Datastore::CleanupMetadata() { 5294 AssertIsOnBackgroundThread(); 5295 5296 MOZ_ASSERT(gDatastores); 5297 const DebugOnly<bool> removed = gDatastores->Remove(mOriginMetadata.mOrigin); 5298 MOZ_ASSERT(removed); 5299 5300 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS, 5301 "Datastore removed"_ns); 5302 5303 if (!gDatastores->Count()) { 5304 gDatastores = nullptr; 5305 } 5306 } 5307 5308 void Datastore::NotifySnapshots(Database* aDatabase, const nsAString& aKey, 5309 const LSValue& aOldValue, bool aAffectsOrder) { 5310 AssertIsOnBackgroundThread(); 5311 5312 for (Database* database : mDatabases) { 5313 MOZ_ASSERT(database); 5314 5315 if (database == aDatabase) { 5316 continue; 5317 } 5318 5319 Snapshot* snapshot = database->GetSnapshot(); 5320 if (snapshot) { 5321 snapshot->SaveItem(aKey, aOldValue, aAffectsOrder); 5322 } 5323 } 5324 } 5325 5326 void Datastore::NoteChangedDatabaseMap() { 5327 AssertIsOnBackgroundThread(); 5328 5329 for (Database* database : mActiveDatabases) { 5330 Snapshot* snapshot = database->GetSnapshot(); 5331 MOZ_ASSERT(snapshot); 5332 5333 if (snapshot->IsDirty()) { 5334 continue; 5335 } 5336 5337 if (snapshot->HasOtherProcessDatabases() != 5338 HasOtherProcessDatabases(database)) { 5339 snapshot->MarkDirty(); 5340 } 5341 } 5342 } 5343 5344 /******************************************************************************* 5345 * PreparedDatastore 5346 ******************************************************************************/ 5347 5348 void PreparedDatastore::Destroy() { 5349 AssertIsOnBackgroundThread(); 5350 MOZ_ASSERT(gPreparedDatastores); 5351 DebugOnly<bool> removed = gPreparedDatastores->Remove(mDatastoreId); 5352 MOZ_ASSERT(removed); 5353 } 5354 5355 // static 5356 void PreparedDatastore::TimerCallback(nsITimer* aTimer, void* aClosure) { 5357 AssertIsOnBackgroundThread(); 5358 5359 auto* self = static_cast<PreparedDatastore*>(aClosure); 5360 MOZ_ASSERT(self); 5361 5362 self->Destroy(); 5363 } 5364 5365 /******************************************************************************* 5366 * Database 5367 ******************************************************************************/ 5368 5369 Database::Database(const PrincipalInfo& aPrincipalInfo, 5370 const Maybe<ContentParentId>& aContentParentId, 5371 const nsACString& aOrigin, uint32_t aPrivateBrowsingId) 5372 : mSnapshot(nullptr), 5373 mPrincipalInfo(aPrincipalInfo), 5374 mContentParentId(aContentParentId), 5375 mOrigin(aOrigin), 5376 mRequestAllowToCloseTimerId(0), 5377 mPrivateBrowsingId(aPrivateBrowsingId), 5378 mAllowedToClose(false), 5379 mActorDestroyed(false), 5380 mRequestedAllowToClose(false) 5381 #ifdef DEBUG 5382 , 5383 mActorWasAlive(false) 5384 #endif 5385 { 5386 AssertIsOnOwningThread(); 5387 5388 if (!gDatabases) { 5389 gDatabases = new DatabaseArray(); 5390 } 5391 5392 gDatabases->AppendElement(this); 5393 } 5394 5395 Database::~Database() { 5396 AssertIsOnOwningThread(); 5397 MOZ_ASSERT_IF(mActorWasAlive, mAllowedToClose); 5398 MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed); 5399 5400 MOZ_ASSERT(gDatabases); 5401 gDatabases->RemoveElement(this); 5402 5403 if (gDatabases->IsEmpty()) { 5404 gDatabases = nullptr; 5405 } 5406 } 5407 5408 void Database::SetActorAlive(Datastore* aDatastore) { 5409 AssertIsOnOwningThread(); 5410 MOZ_ASSERT(!mActorWasAlive); 5411 MOZ_ASSERT(!mActorDestroyed); 5412 5413 #ifdef DEBUG 5414 mActorWasAlive = true; 5415 #endif 5416 5417 mDatastore = aDatastore; 5418 5419 mDatastore->NoteLiveDatabase(this); 5420 5421 if (!gLiveDatabases) { 5422 gLiveDatabases = new LiveDatabaseArray(); 5423 } 5424 5425 gLiveDatabases->AppendElement(WrapNotNullUnchecked(this)); 5426 } 5427 5428 void Database::RegisterSnapshot(Snapshot* aSnapshot) { 5429 AssertIsOnOwningThread(); 5430 MOZ_ASSERT(aSnapshot); 5431 MOZ_ASSERT(!mSnapshot); 5432 MOZ_ASSERT(!mAllowedToClose); 5433 5434 // Only one snapshot at a time is currently supported. 5435 mSnapshot = aSnapshot; 5436 5437 mDatastore->NoteActiveDatabase(this); 5438 } 5439 5440 void Database::UnregisterSnapshot(Snapshot* aSnapshot) { 5441 MOZ_ASSERT(aSnapshot); 5442 MOZ_ASSERT(mSnapshot == aSnapshot); 5443 5444 mSnapshot = nullptr; 5445 5446 mDatastore->NoteInactiveDatabase(this); 5447 } 5448 5449 void Database::RequestAllowToClose() { 5450 AssertIsOnOwningThread(); 5451 5452 if (mRequestedAllowToClose) { 5453 return; 5454 } 5455 5456 mRequestedAllowToClose = true; 5457 5458 // Send the RequestAllowToClose message to the child to avoid racing with the 5459 // child actor. Except the case when the actor was already destroyed. 5460 if (mActorDestroyed) { 5461 MOZ_ASSERT(mAllowedToClose); 5462 return; 5463 } 5464 5465 if (NS_WARN_IF(!SendRequestAllowToClose())) { 5466 if (!mSnapshot) { 5467 // This is not necessary, because there should be a runnable scheduled 5468 // that will call ActorDestroy which calls AllowToClose. However we can 5469 // speedup the shutdown a bit if we do it here directly, but only if 5470 // there's no registered snapshot. 5471 AllowToClose(); 5472 } 5473 } else { 5474 mRequestAllowToCloseTimerId = 5475 glean::localstorage_database::request_allow_to_close_response_time 5476 .Start(); 5477 } 5478 } 5479 5480 void Database::ForceKill() { 5481 AssertIsOnOwningThread(); 5482 5483 if (mActorDestroyed) { 5484 MOZ_ASSERT(mAllowedToClose); 5485 return; 5486 } 5487 5488 Close(); 5489 } 5490 5491 void Database::Stringify(nsACString& aResult) const { 5492 AssertIsOnOwningThread(); 5493 5494 aResult.AppendLiteral("SnapshotRegistered:"); 5495 aResult.AppendInt(!!mSnapshot); 5496 aResult.Append(kQuotaGenericDelimiter); 5497 5498 aResult.AppendLiteral("OtherProcessActor:"); 5499 aResult.AppendInt(mContentParentId.isSome()); 5500 aResult.Append(kQuotaGenericDelimiter); 5501 5502 aResult.AppendLiteral("Origin:"); 5503 aResult.Append(AnonymizedOriginString(mOrigin)); 5504 aResult.Append(kQuotaGenericDelimiter); 5505 5506 aResult.AppendLiteral("PrivateBrowsingId:"); 5507 aResult.AppendInt(mPrivateBrowsingId); 5508 aResult.Append(kQuotaGenericDelimiter); 5509 5510 aResult.AppendLiteral("AllowedToClose:"); 5511 aResult.AppendInt(mAllowedToClose); 5512 aResult.Append(kQuotaGenericDelimiter); 5513 5514 aResult.AppendLiteral("ActorDestroyed:"); 5515 aResult.AppendInt(mActorDestroyed); 5516 aResult.Append(kQuotaGenericDelimiter); 5517 5518 aResult.AppendLiteral("RequestedAllowToClose:"); 5519 aResult.AppendInt(mRequestedAllowToClose); 5520 } 5521 5522 void Database::AllowToClose() { 5523 AssertIsOnOwningThread(); 5524 MOZ_ASSERT(!mAllowedToClose); 5525 MOZ_ASSERT(mDatastore); 5526 MOZ_ASSERT(!mSnapshot); 5527 5528 mAllowedToClose = true; 5529 5530 mDatastore->NoteFinishedDatabase(this); 5531 5532 mDatastore = nullptr; 5533 5534 MOZ_ASSERT(gLiveDatabases); 5535 gLiveDatabases->RemoveElement(this); 5536 5537 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS, 5538 "Live database removed"_ns); 5539 5540 if (gLiveDatabases->IsEmpty()) { 5541 gLiveDatabases = nullptr; 5542 } 5543 } 5544 5545 void Database::ActorDestroy(ActorDestroyReason aWhy) { 5546 AssertIsOnOwningThread(); 5547 MOZ_ASSERT(!mActorDestroyed); 5548 5549 mActorDestroyed = true; 5550 5551 if (!mAllowedToClose) { 5552 AllowToClose(); 5553 } 5554 } 5555 5556 mozilla::ipc::IPCResult Database::RecvAllowToClose() { 5557 AssertIsOnOwningThread(); 5558 5559 if (NS_WARN_IF(mAllowedToClose)) { 5560 return IPC_FAIL(this, "mAllowedToClose already set!"); 5561 } 5562 5563 // AllowToClose can be sent from content without a prior request from the 5564 // parent (RequestAllowToClose). Therefore, we need to check for this; 5565 // otherwise, we would receive warnings from Glean about timing not running. 5566 if (mRequestedAllowToClose) { 5567 // mRequestAllowToCloseTimerId is initialized to zero in the constructor, 5568 // and the Start() method always returns id that is greater than zero, so 5569 // we can assert that here. 5570 MOZ_ASSERT(mRequestAllowToCloseTimerId); 5571 5572 glean::localstorage_database::request_allow_to_close_response_time 5573 .StopAndAccumulate(std::move(mRequestAllowToCloseTimerId)); 5574 } 5575 5576 AllowToClose(); 5577 5578 return IPC_OK(); 5579 } 5580 5581 PBackgroundLSSnapshotParent* Database::AllocPBackgroundLSSnapshotParent( 5582 const nsAString& aDocumentURI, const nsAString& aKey, 5583 const bool& aIncreasePeakUsage, const int64_t& aMinSize, 5584 LSSnapshotInitInfo* aInitInfo) { 5585 AssertIsOnOwningThread(); 5586 5587 if (NS_WARN_IF(aIncreasePeakUsage && aMinSize < 0)) { 5588 MOZ_ASSERT_UNLESS_FUZZING(false); 5589 return nullptr; 5590 } 5591 5592 if (NS_WARN_IF(mAllowedToClose)) { 5593 MOZ_ASSERT_UNLESS_FUZZING(false); 5594 return nullptr; 5595 } 5596 5597 RefPtr<Snapshot> snapshot = new Snapshot(this, aDocumentURI); 5598 5599 // Transfer ownership to IPDL. 5600 return snapshot.forget().take(); 5601 } 5602 5603 mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor( 5604 PBackgroundLSSnapshotParent* aActor, const nsAString& aDocumentURI, 5605 const nsAString& aKey, const bool& aIncreasePeakUsage, 5606 const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) { 5607 AssertIsOnOwningThread(); 5608 MOZ_ASSERT_IF(aIncreasePeakUsage, aMinSize >= 0); 5609 MOZ_ASSERT(aInitInfo); 5610 MOZ_ASSERT(!mAllowedToClose); 5611 5612 auto* snapshot = static_cast<Snapshot*>(aActor); 5613 5614 bool addKeyToUnknownItems; 5615 nsTHashtable<nsStringHashKey> loadedItems; 5616 nsTArray<LSItemInfo> itemInfos; 5617 uint32_t nextLoadIndex; 5618 LSSnapshot::LoadState loadState; 5619 mDatastore->GetSnapshotLoadInfo(aKey, addKeyToUnknownItems, loadedItems, 5620 itemInfos, nextLoadIndex, loadState); 5621 5622 nsTHashSet<nsString> unknownItems; 5623 if (addKeyToUnknownItems) { 5624 unknownItems.Insert(aKey); 5625 } 5626 5627 uint32_t totalLength = mDatastore->GetLength(); 5628 5629 int64_t usage = mDatastore->GetUsage(); 5630 5631 int64_t peakUsage = usage; 5632 5633 if (aIncreasePeakUsage) { 5634 int64_t size = 5635 mDatastore->AttemptToUpdateUsage(aMinSize, /* aInitial */ true); 5636 5637 peakUsage += size; 5638 } 5639 5640 bool hasOtherProcessDatabases = mDatastore->HasOtherProcessDatabases(this); 5641 bool hasOtherProcessObservers = mDatastore->HasOtherProcessObservers(this); 5642 5643 snapshot->Init(loadedItems, std::move(unknownItems), nextLoadIndex, 5644 totalLength, usage, peakUsage, loadState, 5645 hasOtherProcessDatabases, hasOtherProcessObservers); 5646 5647 RegisterSnapshot(snapshot); 5648 5649 aInitInfo->addKeyToUnknownItems() = addKeyToUnknownItems; 5650 aInitInfo->itemInfos() = std::move(itemInfos); 5651 aInitInfo->totalLength() = totalLength; 5652 aInitInfo->usage() = usage; 5653 aInitInfo->peakUsage() = peakUsage; 5654 aInitInfo->loadState() = loadState; 5655 aInitInfo->hasOtherProcessDatabases() = hasOtherProcessDatabases; 5656 aInitInfo->hasOtherProcessObservers() = hasOtherProcessObservers; 5657 5658 return IPC_OK(); 5659 } 5660 5661 bool Database::DeallocPBackgroundLSSnapshotParent( 5662 PBackgroundLSSnapshotParent* aActor) { 5663 AssertIsOnOwningThread(); 5664 MOZ_ASSERT(aActor); 5665 5666 // Transfer ownership back from IPDL. 5667 RefPtr<Snapshot> actor = dont_AddRef(static_cast<Snapshot*>(aActor)); 5668 5669 return true; 5670 } 5671 5672 /******************************************************************************* 5673 * Snapshot 5674 ******************************************************************************/ 5675 5676 Snapshot::Snapshot(Database* aDatabase, const nsAString& aDocumentURI) 5677 : mDatabase(aDatabase), 5678 mDatastore(aDatabase->GetDatastore()), 5679 mDocumentURI(aDocumentURI), 5680 mTotalLength(0), 5681 mUsage(-1), 5682 mPeakUsage(-1), 5683 mSavedKeys(false), 5684 mActorDestroyed(false), 5685 mFinishReceived(false), 5686 mLoadedReceived(false), 5687 mLoadedAllItems(false), 5688 mLoadKeysReceived(false), 5689 mSentMarkDirty(false) { 5690 AssertIsOnBackgroundThread(); 5691 MOZ_ASSERT(aDatabase); 5692 } 5693 5694 Snapshot::~Snapshot() { 5695 MOZ_ASSERT(mActorDestroyed); 5696 MOZ_ASSERT(mFinishReceived); 5697 } 5698 5699 void Snapshot::SaveItem(const nsAString& aKey, const LSValue& aOldValue, 5700 bool aAffectsOrder) { 5701 AssertIsOnBackgroundThread(); 5702 5703 MarkDirty(); 5704 5705 if (mLoadedAllItems) { 5706 return; 5707 } 5708 5709 if (!mLoadedItems.Contains(aKey) && !mUnknownItems.Contains(aKey)) { 5710 mValues.LookupOrInsert(aKey, aOldValue); 5711 } 5712 5713 if (aAffectsOrder && !mSavedKeys) { 5714 mDatastore->GetKeys(mKeys); 5715 mSavedKeys = true; 5716 } 5717 } 5718 5719 void Snapshot::MarkDirty() { 5720 AssertIsOnBackgroundThread(); 5721 5722 if (!mSentMarkDirty) { 5723 (void)SendMarkDirty(); 5724 mSentMarkDirty = true; 5725 } 5726 } 5727 5728 void Snapshot::Finish() { 5729 AssertIsOnBackgroundThread(); 5730 MOZ_ASSERT(mDatabase); 5731 MOZ_ASSERT(mDatastore); 5732 MOZ_ASSERT(!mFinishReceived); 5733 5734 mDatastore->BeginUpdateBatch(mUsage); 5735 5736 mDatastore->EndUpdateBatch(mPeakUsage); 5737 5738 mDatabase->UnregisterSnapshot(this); 5739 5740 mFinishReceived = true; 5741 } 5742 5743 void Snapshot::ActorDestroy(ActorDestroyReason aWhy) { 5744 AssertIsOnBackgroundThread(); 5745 MOZ_ASSERT(!mActorDestroyed); 5746 5747 mActorDestroyed = true; 5748 5749 if (!mFinishReceived) { 5750 Finish(); 5751 } 5752 } 5753 5754 mozilla::ipc::IPCResult Snapshot::RecvDeleteMe() { 5755 AssertIsOnBackgroundThread(); 5756 MOZ_ASSERT(!mActorDestroyed); 5757 5758 IProtocol* mgr = Manager(); 5759 if (!PBackgroundLSSnapshotParent::Send__delete__(this)) { 5760 return IPC_FAIL(mgr, "Send__delete__ failed!"); 5761 } 5762 return IPC_OK(); 5763 } 5764 5765 mozilla::ipc::IPCResult Snapshot::Checkpoint( 5766 nsTArray<LSWriteInfo>&& aWriteInfos) { 5767 AssertIsOnBackgroundThread(); 5768 // Don't assert `mUsage >= 0`, it can be negative when multiple snapshots are 5769 // operating in parallel. 5770 MOZ_ASSERT(mPeakUsage >= mUsage); 5771 5772 if (NS_WARN_IF(aWriteInfos.IsEmpty())) { 5773 return IPC_FAIL(this, "aWriteInfos is empty!"); 5774 } 5775 5776 if (NS_WARN_IF(mHasOtherProcessObservers)) { 5777 return IPC_FAIL(this, "mHasOtherProcessObservers already set!"); 5778 } 5779 5780 mDatastore->BeginUpdateBatch(mUsage); 5781 5782 for (uint32_t index = 0; index < aWriteInfos.Length(); index++) { 5783 const LSWriteInfo& writeInfo = aWriteInfos[index]; 5784 5785 switch (writeInfo.type()) { 5786 case LSWriteInfo::TLSSetItemInfo: { 5787 const LSSetItemInfo& info = writeInfo.get_LSSetItemInfo(); 5788 5789 mDatastore->SetItem(mDatabase, info.key(), info.value()); 5790 5791 break; 5792 } 5793 5794 case LSWriteInfo::TLSRemoveItemInfo: { 5795 const LSRemoveItemInfo& info = writeInfo.get_LSRemoveItemInfo(); 5796 5797 mDatastore->RemoveItem(mDatabase, info.key()); 5798 5799 break; 5800 } 5801 5802 case LSWriteInfo::TLSClearInfo: { 5803 mDatastore->Clear(mDatabase); 5804 5805 break; 5806 } 5807 5808 default: 5809 MOZ_CRASH("Should never get here!"); 5810 } 5811 } 5812 5813 mUsage = mDatastore->EndUpdateBatch(-1); 5814 5815 return IPC_OK(); 5816 } 5817 5818 mozilla::ipc::IPCResult Snapshot::CheckpointAndNotify( 5819 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) { 5820 AssertIsOnBackgroundThread(); 5821 // Don't assert `mUsage >= 0`, it can be negative when multiple snapshots are 5822 // operating in parallel. 5823 MOZ_ASSERT(mPeakUsage >= mUsage); 5824 5825 if (NS_WARN_IF(aWriteAndNotifyInfos.IsEmpty())) { 5826 return IPC_FAIL(this, "aWriteAndNotifyInfos is empty!"); 5827 } 5828 5829 if (NS_WARN_IF(!mHasOtherProcessObservers)) { 5830 return IPC_FAIL(this, "mHasOtherProcessObservers is not set!"); 5831 } 5832 5833 mDatastore->BeginUpdateBatch(mUsage); 5834 5835 for (uint32_t index = 0; index < aWriteAndNotifyInfos.Length(); index++) { 5836 const LSWriteAndNotifyInfo& writeAndNotifyInfo = 5837 aWriteAndNotifyInfos[index]; 5838 5839 switch (writeAndNotifyInfo.type()) { 5840 case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: { 5841 const LSSetItemAndNotifyInfo& info = 5842 writeAndNotifyInfo.get_LSSetItemAndNotifyInfo(); 5843 5844 mDatastore->SetItem(mDatabase, info.key(), info.value()); 5845 5846 mDatastore->NotifyOtherProcessObservers( 5847 mDatabase, mDocumentURI, info.key(), info.oldValue(), info.value()); 5848 5849 break; 5850 } 5851 5852 case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: { 5853 const LSRemoveItemAndNotifyInfo& info = 5854 writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo(); 5855 5856 mDatastore->RemoveItem(mDatabase, info.key()); 5857 5858 mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI, 5859 info.key(), info.oldValue(), 5860 VoidLSValue()); 5861 5862 break; 5863 } 5864 5865 case LSWriteAndNotifyInfo::TLSClearInfo: { 5866 mDatastore->Clear(mDatabase); 5867 5868 mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI, 5869 VoidString(), VoidLSValue(), 5870 VoidLSValue()); 5871 5872 break; 5873 } 5874 5875 default: 5876 MOZ_CRASH("Should never get here!"); 5877 } 5878 } 5879 5880 mUsage = mDatastore->EndUpdateBatch(-1); 5881 5882 return IPC_OK(); 5883 } 5884 5885 mozilla::ipc::IPCResult Snapshot::RecvAsyncCheckpoint( 5886 nsTArray<LSWriteInfo>&& aWriteInfos) { 5887 return Checkpoint(std::move(aWriteInfos)); 5888 } 5889 5890 mozilla::ipc::IPCResult Snapshot::RecvAsyncCheckpointAndNotify( 5891 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) { 5892 return CheckpointAndNotify(std::move(aWriteAndNotifyInfos)); 5893 } 5894 5895 mozilla::ipc::IPCResult Snapshot::RecvSyncCheckpoint( 5896 nsTArray<LSWriteInfo>&& aWriteInfos) { 5897 return Checkpoint(std::move(aWriteInfos)); 5898 } 5899 5900 mozilla::ipc::IPCResult Snapshot::RecvSyncCheckpointAndNotify( 5901 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) { 5902 return CheckpointAndNotify(std::move(aWriteAndNotifyInfos)); 5903 } 5904 5905 mozilla::ipc::IPCResult Snapshot::RecvAsyncFinish() { 5906 AssertIsOnBackgroundThread(); 5907 5908 if (NS_WARN_IF(mFinishReceived)) { 5909 MOZ_ASSERT_UNLESS_FUZZING(false); 5910 return IPC_FAIL(this, "Already finished"); 5911 } 5912 5913 Finish(); 5914 5915 return IPC_OK(); 5916 } 5917 5918 mozilla::ipc::IPCResult Snapshot::RecvSyncFinish() { 5919 AssertIsOnBackgroundThread(); 5920 5921 if (NS_WARN_IF(mFinishReceived)) { 5922 MOZ_ASSERT_UNLESS_FUZZING(false); 5923 return IPC_FAIL(this, "Already finished"); 5924 } 5925 5926 Finish(); 5927 5928 return IPC_OK(); 5929 } 5930 5931 mozilla::ipc::IPCResult Snapshot::RecvLoaded() { 5932 AssertIsOnBackgroundThread(); 5933 5934 if (NS_WARN_IF(mFinishReceived)) { 5935 return IPC_FAIL(this, "mFinishReceived already set!"); 5936 } 5937 5938 if (NS_WARN_IF(mLoadedReceived)) { 5939 return IPC_FAIL(this, "mLoadedReceived already set!"); 5940 } 5941 5942 if (NS_WARN_IF(mLoadedAllItems)) { 5943 return IPC_FAIL(this, "mLoadedAllItems already set!"); 5944 } 5945 5946 if (NS_WARN_IF(mLoadKeysReceived)) { 5947 return IPC_FAIL(this, "mLoadKeysReceived already set!"); 5948 } 5949 5950 mLoadedReceived = true; 5951 5952 mLoadedItems.Clear(); 5953 mUnknownItems.Clear(); 5954 mValues.Clear(); 5955 mKeys.Clear(); 5956 mLoadedAllItems = true; 5957 mLoadKeysReceived = true; 5958 5959 return IPC_OK(); 5960 } 5961 5962 mozilla::ipc::IPCResult Snapshot::RecvLoadValueAndMoreItems( 5963 const nsAString& aKey, LSValue* aValue, nsTArray<LSItemInfo>* aItemInfos) { 5964 AssertIsOnBackgroundThread(); 5965 MOZ_ASSERT(aValue); 5966 MOZ_ASSERT(aItemInfos); 5967 MOZ_ASSERT(mDatastore); 5968 5969 if (NS_WARN_IF(mFinishReceived)) { 5970 return IPC_FAIL(this, "mFinishReceived already set!"); 5971 } 5972 5973 if (NS_WARN_IF(mLoadedReceived)) { 5974 return IPC_FAIL(this, "mLoadedReceived already set!"); 5975 } 5976 5977 if (NS_WARN_IF(mLoadedAllItems)) { 5978 return IPC_FAIL(this, "mLoadedAllItems already set!"); 5979 } 5980 5981 if (mLoadedItems.Contains(aKey)) { 5982 return IPC_FAIL(this, "mLoadedItems already contains aKey!"); 5983 } 5984 5985 if (mUnknownItems.Contains(aKey)) { 5986 return IPC_FAIL(this, "mUnknownItems already contains aKey!"); 5987 } 5988 5989 if (auto entry = mValues.Lookup(aKey)) { 5990 *aValue = entry.Data(); 5991 entry.Remove(); 5992 } else { 5993 mDatastore->GetItem(aKey, *aValue); 5994 } 5995 5996 if (aValue->IsVoid()) { 5997 mUnknownItems.Insert(aKey); 5998 } else { 5999 mLoadedItems.PutEntry(aKey); 6000 6001 // mLoadedItems.Count()==mTotalLength is checked below. 6002 } 6003 6004 // Load some more key/value pairs (as many as the snapshot gradual prefill 6005 // byte budget allows). 6006 6007 if (gSnapshotGradualPrefill > 0) { 6008 const nsTArray<LSItemInfo>& orderedItems = mDatastore->GetOrderedItems(); 6009 6010 uint32_t length; 6011 if (mSavedKeys) { 6012 length = mKeys.Length(); 6013 } else { 6014 length = orderedItems.Length(); 6015 } 6016 6017 int64_t size = 0; 6018 while (mNextLoadIndex < length) { 6019 // If the datastore's ordering has changed, mSavedKeys will be true and 6020 // mKeys contains an ordered list of the keys. Otherwise we can use the 6021 // datastore's key ordering which is still the same as when the snapshot 6022 // was created. 6023 6024 nsString key; 6025 if (mSavedKeys) { 6026 key = mKeys[mNextLoadIndex]; 6027 } else { 6028 key = orderedItems[mNextLoadIndex].key(); 6029 } 6030 6031 // Normally we would do this: 6032 // if (!mLoadedItems.GetEntry(key)) { 6033 // ... 6034 // mLoadedItems.PutEntry(key); 6035 // } 6036 // but that requires two hash lookups. We can reduce that to just one 6037 // hash lookup if we always call PutEntry and check the number of entries 6038 // before and after the put (which is very cheap). However, if we reach 6039 // the prefill limit, we need to call RemoveEntry, but that is also cheap 6040 // because we pass the entry (not the key). 6041 6042 uint32_t countBeforePut = mLoadedItems.Count(); 6043 auto loadedItemEntry = mLoadedItems.PutEntry(key); 6044 if (countBeforePut != mLoadedItems.Count()) { 6045 // Check mValues first since that contains values as they existed when 6046 // our snapshot was created, but have since been changed/removed in the 6047 // datastore. If it's not there, then the datastore has the 6048 // still-current value. However, if the datastore's key ordering has 6049 // changed, we need to do a hash lookup rather than being able to do an 6050 // optimized direct access to the index. 6051 6052 LSValue value; 6053 auto valueEntry = mValues.Lookup(key); 6054 if (valueEntry) { 6055 value = valueEntry.Data(); 6056 } else if (mSavedKeys) { 6057 mDatastore->GetItem(nsString(key), value); 6058 } else { 6059 value = orderedItems[mNextLoadIndex].value(); 6060 } 6061 6062 // All not loaded keys must have a value. 6063 MOZ_ASSERT(!value.IsVoid()); 6064 6065 size += static_cast<int64_t>(key.Length()) + 6066 static_cast<int64_t>(value.Length()); 6067 6068 if (size > gSnapshotGradualPrefill) { 6069 mLoadedItems.RemoveEntry(loadedItemEntry); 6070 6071 // mNextLoadIndex is not incremented, so we will resume at the same 6072 // position next time. 6073 break; 6074 } 6075 6076 if (valueEntry) { 6077 valueEntry.Remove(); 6078 } 6079 6080 LSItemInfo* itemInfo = aItemInfos->AppendElement(); 6081 itemInfo->key() = key; 6082 itemInfo->value() = value; 6083 } 6084 6085 mNextLoadIndex++; 6086 } 6087 } 6088 6089 if (mLoadedItems.Count() == mTotalLength) { 6090 mLoadedItems.Clear(); 6091 mUnknownItems.Clear(); 6092 #ifdef DEBUG 6093 const bool allValuesVoid = 6094 std::all_of(mValues.Values().cbegin(), mValues.Values().cend(), 6095 [](const auto& entry) { return entry.IsVoid(); }); 6096 MOZ_ASSERT(allValuesVoid); 6097 #endif 6098 mValues.Clear(); 6099 mLoadedAllItems = true; 6100 } 6101 6102 return IPC_OK(); 6103 } 6104 6105 mozilla::ipc::IPCResult Snapshot::RecvLoadKeys(nsTArray<nsString>* aKeys) { 6106 AssertIsOnBackgroundThread(); 6107 MOZ_ASSERT(aKeys); 6108 MOZ_ASSERT(mDatastore); 6109 6110 if (NS_WARN_IF(mFinishReceived)) { 6111 return IPC_FAIL(this, "mFinishReceived already set!"); 6112 } 6113 6114 if (NS_WARN_IF(mLoadedReceived)) { 6115 return IPC_FAIL(this, "mLoadedReceived already set!"); 6116 } 6117 6118 if (NS_WARN_IF(mLoadKeysReceived)) { 6119 return IPC_FAIL(this, "mLoadKeysReceived already set!"); 6120 } 6121 6122 mLoadKeysReceived = true; 6123 6124 if (mSavedKeys) { 6125 aKeys->AppendElements(std::move(mKeys)); 6126 } else { 6127 mDatastore->GetKeys(*aKeys); 6128 } 6129 6130 return IPC_OK(); 6131 } 6132 6133 mozilla::ipc::IPCResult Snapshot::RecvIncreasePeakUsage(const int64_t& aMinSize, 6134 int64_t* aSize) { 6135 AssertIsOnBackgroundThread(); 6136 MOZ_ASSERT(aSize); 6137 6138 if (NS_WARN_IF(aMinSize <= 0)) { 6139 return IPC_FAIL(this, "aMinSize not valid!"); 6140 } 6141 6142 if (NS_WARN_IF(mFinishReceived)) { 6143 return IPC_FAIL(this, "mFinishReceived already set!"); 6144 } 6145 6146 int64_t size = 6147 mDatastore->AttemptToUpdateUsage(aMinSize, /* aInitial */ false); 6148 6149 mPeakUsage += size; 6150 6151 *aSize = size; 6152 6153 return IPC_OK(); 6154 } 6155 6156 /******************************************************************************* 6157 * Observer 6158 ******************************************************************************/ 6159 6160 Observer::Observer(const Maybe<ContentParentId>& aContentParentId, 6161 const nsACString& aOrigin) 6162 : mContentParentId(aContentParentId), 6163 mOrigin(aOrigin), 6164 mActorDestroyed(false) { 6165 AssertIsOnBackgroundThread(); 6166 } 6167 6168 Observer::~Observer() { MOZ_ASSERT(mActorDestroyed); } 6169 6170 void Observer::Observe(Database* aDatabase, const nsString& aDocumentURI, 6171 const nsString& aKey, const LSValue& aOldValue, 6172 const LSValue& aNewValue) { 6173 AssertIsOnBackgroundThread(); 6174 MOZ_ASSERT(aDatabase); 6175 6176 (void)SendObserve(aDatabase->GetPrincipalInfo(), 6177 aDatabase->PrivateBrowsingId(), aDocumentURI, aKey, 6178 aOldValue, aNewValue); 6179 } 6180 6181 void Observer::ActorDestroy(ActorDestroyReason aWhy) { 6182 AssertIsOnBackgroundThread(); 6183 MOZ_ASSERT(!mActorDestroyed); 6184 6185 mActorDestroyed = true; 6186 6187 MOZ_ASSERT(gObservers); 6188 6189 nsTArray<NotNull<Observer*>>* array; 6190 gObservers->Get(mOrigin, &array); 6191 MOZ_ASSERT(array); 6192 6193 array->RemoveElement(this); 6194 6195 if (RefPtr<Datastore> datastore = GetDatastore(mOrigin)) { 6196 datastore->NoteChangedObserverArray(*array); 6197 } 6198 6199 if (array->IsEmpty()) { 6200 gObservers->Remove(mOrigin); 6201 } 6202 6203 if (!gObservers->Count()) { 6204 gObservers = nullptr; 6205 } 6206 } 6207 6208 mozilla::ipc::IPCResult Observer::RecvDeleteMe() { 6209 AssertIsOnBackgroundThread(); 6210 MOZ_ASSERT(!mActorDestroyed); 6211 6212 IProtocol* mgr = Manager(); 6213 if (!PBackgroundLSObserverParent::Send__delete__(this)) { 6214 return IPC_FAIL(mgr, "Send__delete__ failed!"); 6215 } 6216 return IPC_OK(); 6217 } 6218 6219 /******************************************************************************* 6220 * LSRequestBase 6221 ******************************************************************************/ 6222 6223 LSRequestBase::LSRequestBase(const LSRequestParams& aParams, 6224 const Maybe<ContentParentId>& aContentParentId) 6225 : mParams(aParams), 6226 mContentParentId(aContentParentId), 6227 mState(State::Initial), 6228 mWaitingForFinish(false) {} 6229 6230 LSRequestBase::~LSRequestBase() { 6231 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(), 6232 mState == State::Initial || mState == State::Completed); 6233 } 6234 6235 void LSRequestBase::Dispatch() { 6236 AssertIsOnOwningThread(); 6237 6238 mState = State::StartingRequest; 6239 6240 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this)); 6241 } 6242 6243 void LSRequestBase::StringifyState(nsACString& aResult) const { 6244 AssertIsOnOwningThread(); 6245 6246 switch (mState) { 6247 case State::Initial: 6248 aResult.AppendLiteral("Initial"); 6249 return; 6250 6251 case State::StartingRequest: 6252 aResult.AppendLiteral("StartingRequest"); 6253 return; 6254 6255 case State::Nesting: 6256 aResult.AppendLiteral("Nesting"); 6257 return; 6258 6259 case State::SendingReadyMessage: 6260 aResult.AppendLiteral("SendingReadyMessage"); 6261 return; 6262 6263 case State::WaitingForFinish: 6264 aResult.AppendLiteral("WaitingForFinish"); 6265 return; 6266 6267 case State::SendingResults: 6268 aResult.AppendLiteral("SendingResults"); 6269 return; 6270 6271 case State::Completed: 6272 aResult.AppendLiteral("Completed"); 6273 return; 6274 6275 default: 6276 MOZ_CRASH("Bad state!"); 6277 } 6278 } 6279 6280 void LSRequestBase::Stringify(nsACString& aResult) const { 6281 AssertIsOnOwningThread(); 6282 6283 aResult.AppendLiteral("State:"); 6284 StringifyState(aResult); 6285 } 6286 6287 void LSRequestBase::Log() { 6288 AssertIsOnOwningThread(); 6289 6290 if (!LS_LOG_TEST()) { 6291 return; 6292 } 6293 6294 LS_LOG(("LSRequestBase [%p]", this)); 6295 6296 nsCString state; 6297 StringifyState(state); 6298 6299 LS_LOG((" mState: %s", state.get())); 6300 } 6301 6302 nsresult LSRequestBase::NestedRun() { return NS_OK; } 6303 6304 bool LSRequestBase::VerifyRequestParams() { 6305 AssertIsOnBackgroundThread(); 6306 6307 MOZ_ASSERT(mParams.type() != LSRequestParams::T__None); 6308 6309 switch (mParams.type()) { 6310 case LSRequestParams::TLSRequestPreloadDatastoreParams: { 6311 const LSRequestCommonParams& params = 6312 mParams.get_LSRequestPreloadDatastoreParams().commonParams(); 6313 6314 if (NS_WARN_IF(!VerifyPrincipalInfo( 6315 params.principalInfo(), params.storagePrincipalInfo(), false))) { 6316 return false; 6317 } 6318 6319 if (NS_WARN_IF( 6320 !VerifyOriginKey(params.originKey(), params.principalInfo()))) { 6321 return false; 6322 } 6323 6324 break; 6325 } 6326 6327 case LSRequestParams::TLSRequestPrepareDatastoreParams: { 6328 const LSRequestPrepareDatastoreParams& params = 6329 mParams.get_LSRequestPrepareDatastoreParams(); 6330 6331 const LSRequestCommonParams& commonParams = params.commonParams(); 6332 6333 if (NS_WARN_IF(!VerifyPrincipalInfo(commonParams.principalInfo(), 6334 commonParams.storagePrincipalInfo(), 6335 false))) { 6336 return false; 6337 } 6338 6339 if (params.clientPrincipalInfo() && 6340 NS_WARN_IF(!VerifyPrincipalInfo(commonParams.principalInfo(), 6341 params.clientPrincipalInfo().ref(), 6342 true))) { 6343 return false; 6344 } 6345 6346 if (NS_WARN_IF(!VerifyClientId(mContentParentId, 6347 params.clientPrincipalInfo(), 6348 params.clientId()))) { 6349 return false; 6350 } 6351 6352 if (NS_WARN_IF(!VerifyOriginKey(commonParams.originKey(), 6353 commonParams.principalInfo()))) { 6354 return false; 6355 } 6356 6357 break; 6358 } 6359 6360 case LSRequestParams::TLSRequestPrepareObserverParams: { 6361 const LSRequestPrepareObserverParams& params = 6362 mParams.get_LSRequestPrepareObserverParams(); 6363 6364 if (NS_WARN_IF(!VerifyPrincipalInfo( 6365 params.principalInfo(), params.storagePrincipalInfo(), false))) { 6366 return false; 6367 } 6368 6369 if (params.clientPrincipalInfo() && 6370 NS_WARN_IF(!VerifyPrincipalInfo(params.principalInfo(), 6371 params.clientPrincipalInfo().ref(), 6372 true))) { 6373 return false; 6374 } 6375 6376 if (NS_WARN_IF(!VerifyClientId(mContentParentId, 6377 params.clientPrincipalInfo(), 6378 params.clientId()))) { 6379 return false; 6380 } 6381 6382 break; 6383 } 6384 6385 default: 6386 MOZ_CRASH("Should never get here!"); 6387 } 6388 6389 return true; 6390 } 6391 6392 nsresult LSRequestBase::StartRequest() { 6393 AssertIsOnOwningThread(); 6394 MOZ_ASSERT(mState == State::StartingRequest); 6395 6396 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 6397 !MayProceed()) { 6398 return NS_ERROR_ABORT; 6399 } 6400 6401 #ifdef DEBUG 6402 // Always verify parameters in DEBUG builds! 6403 bool trustParams = false; 6404 #else 6405 bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager()); 6406 #endif 6407 6408 if (!trustParams && NS_WARN_IF(!VerifyRequestParams())) { 6409 return NS_ERROR_FAILURE; 6410 } 6411 6412 QM_TRY(MOZ_TO_RESULT(Start())); 6413 6414 return NS_OK; 6415 } 6416 6417 void LSRequestBase::SendReadyMessage() { 6418 AssertIsOnOwningThread(); 6419 MOZ_ASSERT(mState == State::SendingReadyMessage); 6420 6421 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 6422 !MayProceed()) { 6423 MaybeSetFailureCode(NS_ERROR_ABORT); 6424 } 6425 6426 nsresult rv = SendReadyMessageInternal(); 6427 if (NS_WARN_IF(NS_FAILED(rv))) { 6428 MaybeSetFailureCode(rv); 6429 6430 FinishInternal(); 6431 } 6432 } 6433 6434 nsresult LSRequestBase::SendReadyMessageInternal() { 6435 AssertIsOnOwningThread(); 6436 MOZ_ASSERT(mState == State::SendingReadyMessage); 6437 6438 if (!MayProceed()) { 6439 return NS_ERROR_ABORT; 6440 } 6441 6442 if (NS_WARN_IF(!SendReady())) { 6443 return NS_ERROR_FAILURE; 6444 } 6445 6446 localstorage::NotifyRequestFinalizationStarted(); 6447 6448 mState = State::WaitingForFinish; 6449 6450 mWaitingForFinish = true; 6451 6452 return NS_OK; 6453 } 6454 6455 void LSRequestBase::Finish() { 6456 AssertIsOnOwningThread(); 6457 MOZ_ASSERT(mState == State::WaitingForFinish); 6458 6459 mWaitingForFinish = false; 6460 6461 FinishInternal(); 6462 } 6463 6464 void LSRequestBase::FinishInternal() { 6465 AssertIsOnOwningThread(); 6466 MOZ_ASSERT(mState == State::SendingReadyMessage || 6467 mState == State::WaitingForFinish); 6468 6469 mState = State::SendingResults; 6470 6471 // This LSRequestBase can only be held alive by the IPDL. Run() can end up 6472 // with clearing that last reference. So we need to add a self reference here. 6473 RefPtr<LSRequestBase> kungFuDeathGrip = this; 6474 6475 MOZ_ALWAYS_SUCCEEDS(this->Run()); 6476 } 6477 6478 void LSRequestBase::SendResults() { 6479 AssertIsOnOwningThread(); 6480 MOZ_ASSERT(mState == State::SendingResults); 6481 6482 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 6483 !MayProceed()) { 6484 MaybeSetFailureCode(NS_ERROR_ABORT); 6485 } 6486 6487 if (MayProceed()) { 6488 LSRequestResponse response; 6489 6490 if (NS_SUCCEEDED(ResultCode())) { 6491 GetResponse(response); 6492 6493 MOZ_ASSERT(response.type() != LSRequestResponse::T__None); 6494 6495 if (response.type() == LSRequestResponse::Tnsresult) { 6496 MOZ_ASSERT(NS_FAILED(response.get_nsresult())); 6497 6498 SetFailureCode(response.get_nsresult()); 6499 } 6500 } else { 6501 response = ResultCode(); 6502 } 6503 6504 (void)PBackgroundLSRequestParent::Send__delete__(this, std::move(response)); 6505 } 6506 6507 Cleanup(); 6508 6509 mState = State::Completed; 6510 } 6511 6512 NS_IMETHODIMP 6513 LSRequestBase::Run() { 6514 nsresult rv; 6515 6516 switch (mState) { 6517 case State::StartingRequest: 6518 rv = StartRequest(); 6519 break; 6520 6521 case State::Nesting: 6522 rv = NestedRun(); 6523 break; 6524 6525 case State::SendingReadyMessage: 6526 SendReadyMessage(); 6527 return NS_OK; 6528 6529 case State::SendingResults: 6530 SendResults(); 6531 return NS_OK; 6532 6533 default: 6534 MOZ_CRASH("Bad state!"); 6535 } 6536 6537 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingReadyMessage) { 6538 MaybeSetFailureCode(rv); 6539 6540 // Must set mState before dispatching otherwise we will race with the owning 6541 // thread. 6542 mState = State::SendingReadyMessage; 6543 6544 if (IsOnOwningThread()) { 6545 SendReadyMessage(); 6546 } else { 6547 MOZ_ALWAYS_SUCCEEDS( 6548 OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); 6549 } 6550 } 6551 6552 return NS_OK; 6553 } 6554 6555 void LSRequestBase::ActorDestroy(ActorDestroyReason aWhy) { 6556 AssertIsOnOwningThread(); 6557 6558 NoteComplete(); 6559 6560 // Assume ActorDestroy can happen at any time, so we can't probe the current 6561 // state since mState can be modified on any thread (only one thread at a time 6562 // based on the state machine). However we can use mWaitingForFinish which is 6563 // only touched on the owning thread. If mWaitingForFinisg is true, we can 6564 // also modify mState since we are guaranteed that there are no pending 6565 // runnables which would probe mState to decide what code needs to run (there 6566 // shouldn't be any running runnables on other threads either). 6567 6568 if (mWaitingForFinish) { 6569 Finish(); 6570 } 6571 6572 // We don't have to handle the case when mWaitingForFinish is not true since 6573 // it means that either nothing has been initialized yet, so nothing to 6574 // cleanup or there are pending runnables that will detect that the actor has 6575 // been destroyed and cleanup accordingly. 6576 } 6577 6578 mozilla::ipc::IPCResult LSRequestBase::RecvCancel() { 6579 AssertIsOnOwningThread(); 6580 6581 Log(); 6582 6583 glean::localstorage_request::recv_cancel_counter.Add(); 6584 6585 const char* crashOnCancel = PR_GetEnv("LSNG_CRASH_ON_CANCEL"); 6586 if (crashOnCancel) { 6587 MOZ_CRASH("LSNG: Crash on cancel."); 6588 } 6589 6590 IProtocol* mgr = Manager(); 6591 if (!PBackgroundLSRequestParent::Send__delete__(this, NS_ERROR_ABORT)) { 6592 return IPC_FAIL(mgr, "Send__delete__ failed!"); 6593 } 6594 6595 return IPC_OK(); 6596 } 6597 6598 mozilla::ipc::IPCResult LSRequestBase::RecvFinish() { 6599 AssertIsOnOwningThread(); 6600 6601 Finish(); 6602 6603 return IPC_OK(); 6604 } 6605 6606 /******************************************************************************* 6607 * PrepareDatastoreOp 6608 ******************************************************************************/ 6609 6610 PrepareDatastoreOp::PrepareDatastoreOp( 6611 const LSRequestParams& aParams, 6612 const Maybe<ContentParentId>& aContentParentId) 6613 : LSRequestBase(aParams, aContentParentId), 6614 mProcessingTimerId( 6615 glean::localstorage_request::prepare_datastore_processing_time 6616 .Start()), 6617 mLoadDataOp(nullptr), 6618 mPrivateBrowsingId(0), 6619 mUsage(0), 6620 mSizeOfKeys(0), 6621 mSizeOfItems(0), 6622 mDatastoreId(0), 6623 mNestedState(NestedState::BeforeNesting), 6624 mForPreload(aParams.type() == 6625 LSRequestParams::TLSRequestPreloadDatastoreParams), 6626 mEnableMigration( 6627 StaticPrefs:: 6628 dom_storage_enable_migration_from_unsupported_legacy_implementation()), 6629 mDatabaseNotAvailable(false), 6630 mInvalidated(false) 6631 #ifdef DEBUG 6632 , 6633 mDEBUGUsage(0) 6634 #endif 6635 { 6636 MOZ_ASSERT( 6637 aParams.type() == LSRequestParams::TLSRequestPreloadDatastoreParams || 6638 aParams.type() == LSRequestParams::TLSRequestPrepareDatastoreParams); 6639 } 6640 6641 PrepareDatastoreOp::~PrepareDatastoreOp() { 6642 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockHandle.IsInert()); 6643 MOZ_DIAGNOSTIC_ASSERT(mExtraDirectoryLockHandle.IsInert()); 6644 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(), 6645 mState == State::Initial || mState == State::Completed); 6646 MOZ_ASSERT(!mLoadDataOp); 6647 } 6648 6649 void PrepareDatastoreOp::StringifyNestedState(nsACString& aResult) const { 6650 AssertIsOnOwningThread(); 6651 6652 switch (mNestedState) { 6653 case NestedState::BeforeNesting: 6654 aResult.AppendLiteral("BeforeNesting"); 6655 return; 6656 6657 case NestedState::CheckExistingOperations: 6658 aResult.AppendLiteral("CheckExistingOperations"); 6659 return; 6660 6661 case NestedState::CheckClosingDatastore: 6662 aResult.AppendLiteral("CheckClosingDatastore"); 6663 return; 6664 6665 case NestedState::PreparationPending: 6666 aResult.AppendLiteral("PreparationPending"); 6667 return; 6668 6669 case NestedState::DirectoryOpenPending: 6670 aResult.AppendLiteral("DirectoryOpenPending"); 6671 return; 6672 6673 case NestedState::DatabaseWorkOpen: 6674 aResult.AppendLiteral("DatabaseWorkOpen"); 6675 return; 6676 6677 case NestedState::BeginLoadData: 6678 aResult.AppendLiteral("BeginLoadData"); 6679 return; 6680 6681 case NestedState::DatabaseWorkLoadData: 6682 aResult.AppendLiteral("DatabaseWorkLoadData"); 6683 return; 6684 6685 case NestedState::AfterNesting: 6686 aResult.AppendLiteral("AfterNesting"); 6687 return; 6688 6689 default: 6690 MOZ_CRASH("Bad state!"); 6691 } 6692 } 6693 6694 void PrepareDatastoreOp::Stringify(nsACString& aResult) const { 6695 AssertIsOnOwningThread(); 6696 6697 LSRequestBase::Stringify(aResult); 6698 aResult.Append(kQuotaGenericDelimiter); 6699 6700 aResult.AppendLiteral("Origin:"); 6701 aResult.Append(AnonymizedOriginString(Origin())); 6702 aResult.Append(kQuotaGenericDelimiter); 6703 6704 aResult.AppendLiteral("NestedState:"); 6705 StringifyNestedState(aResult); 6706 } 6707 6708 void PrepareDatastoreOp::Log() { 6709 AssertIsOnOwningThread(); 6710 6711 LSRequestBase::Log(); 6712 6713 if (!LS_LOG_TEST()) { 6714 return; 6715 } 6716 6717 nsCString nestedState; 6718 StringifyNestedState(nestedState); 6719 6720 LS_LOG((" mNestedState: %s", nestedState.get())); 6721 6722 switch (mNestedState) { 6723 case NestedState::CheckClosingDatastore: { 6724 for (const auto& blockedOn : mBlockedOn) { 6725 LS_LOG((" blockedOn: [%p]", blockedOn.get().get())); 6726 6727 blockedOn->Log(); 6728 } 6729 6730 break; 6731 } 6732 6733 case NestedState::DirectoryOpenPending: { 6734 MOZ_ASSERT(mPendingDirectoryLock); 6735 6736 LS_LOG((" mPendingDirectoryLock: [%p]", mPendingDirectoryLock.get())); 6737 6738 mPendingDirectoryLock->Log(); 6739 6740 break; 6741 } 6742 6743 default:; 6744 } 6745 } 6746 6747 nsresult PrepareDatastoreOp::Start() { 6748 AssertIsOnOwningThread(); 6749 MOZ_ASSERT(mState == State::StartingRequest); 6750 MOZ_ASSERT(mNestedState == NestedState::BeforeNesting); 6751 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 6752 MOZ_ASSERT(MayProceed()); 6753 6754 QM_TRY(QuotaManager::EnsureCreated()); 6755 6756 const LSRequestCommonParams& commonParams = 6757 mForPreload 6758 ? mParams.get_LSRequestPreloadDatastoreParams().commonParams() 6759 : mParams.get_LSRequestPrepareDatastoreParams().commonParams(); 6760 6761 const PrincipalInfo& storagePrincipalInfo = 6762 commonParams.storagePrincipalInfo(); 6763 6764 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { 6765 mOriginMetadata = {quota::GetInfoForChrome(), PERSISTENCE_TYPE_DEFAULT}; 6766 } else { 6767 MOZ_ASSERT(storagePrincipalInfo.type() == 6768 PrincipalInfo::TContentPrincipalInfo); 6769 6770 QM_TRY_UNWRAP(auto principalMetadata, 6771 quota::GetInfoFromValidatedPrincipalInfo( 6772 *QuotaManager::Get(), storagePrincipalInfo)); 6773 6774 mOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix); 6775 mOriginMetadata.mGroup = std::move(principalMetadata.mGroup); 6776 // XXX We can probably get rid of mMainThreadOrigin if we change 6777 // LSRequestBase::Dispatch to synchronously run LSRequestBase::StartRequest 6778 // through LSRequestBase::Run. 6779 mMainThreadOrigin = std::move(principalMetadata.mOrigin); 6780 mOriginMetadata.mStorageOrigin = 6781 std::move(principalMetadata.mStorageOrigin); 6782 mOriginMetadata.mIsPrivate = principalMetadata.mIsPrivate; 6783 mOriginMetadata.mPersistenceType = principalMetadata.mIsPrivate 6784 ? PERSISTENCE_TYPE_PRIVATE 6785 : PERSISTENCE_TYPE_DEFAULT; 6786 } 6787 6788 mState = State::Nesting; 6789 mNestedState = NestedState::OpenDirectory; 6790 6791 // XXX We could call OpenDirectory directly here or even fold it here instead 6792 // of dispatching to the same event target. 6793 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); 6794 6795 return NS_OK; 6796 } 6797 6798 nsresult PrepareDatastoreOp::OpenDirectory() { 6799 AssertIsOnOwningThread(); 6800 MOZ_ASSERT(mState == State::Nesting); 6801 MOZ_ASSERT(mNestedState == NestedState::OpenDirectory); 6802 MOZ_ASSERT(!mDirectoryLockHandle); 6803 6804 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 6805 !MayProceed()) { 6806 return NS_ERROR_ABORT; 6807 } 6808 6809 const LSRequestCommonParams& commonParams = 6810 mForPreload 6811 ? mParams.get_LSRequestPreloadDatastoreParams().commonParams() 6812 : mParams.get_LSRequestPrepareDatastoreParams().commonParams(); 6813 6814 const PrincipalInfo& storagePrincipalInfo = 6815 commonParams.storagePrincipalInfo(); 6816 6817 nsCString originAttrSuffix; 6818 uint32_t privateBrowsingId; 6819 6820 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { 6821 privateBrowsingId = 0; 6822 } else { 6823 MOZ_ASSERT(storagePrincipalInfo.type() == 6824 PrincipalInfo::TContentPrincipalInfo); 6825 6826 const ContentPrincipalInfo& info = 6827 storagePrincipalInfo.get_ContentPrincipalInfo(); 6828 const OriginAttributes& attrs = info.attrs(); 6829 attrs.CreateSuffix(originAttrSuffix); 6830 6831 privateBrowsingId = attrs.mPrivateBrowsingId; 6832 } 6833 6834 mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin( 6835 originAttrSuffix, commonParams.originKey()); 6836 MOZ_ASSERT(mArchivedOriginScope); 6837 6838 // Normally it's safe to access member variables without a mutex because even 6839 // though we hop between threads, the variables are never accessed by multiple 6840 // threads at the same time. 6841 // However, the methods OriginIsKnown and Origin can be called at any time. 6842 // So we have to make sure the member variable is set on the same thread as 6843 // those methods are called. 6844 mOriginMetadata.mOrigin = mMainThreadOrigin; 6845 6846 MOZ_ASSERT(OriginIsKnown()); 6847 6848 mPrivateBrowsingId = privateBrowsingId; 6849 6850 QuotaManager* quotaManager = QuotaManager::Get(); 6851 MOZ_ASSERT(quotaManager); 6852 6853 mNestedState = NestedState::DirectoryOpenPending; 6854 6855 quotaManager 6856 ->OpenClientDirectory( 6857 {mOriginMetadata, mozilla::dom::quota::Client::LS}, 6858 /* aInitializeOrigin */ !mForPreload || mEnableMigration, 6859 /* aCreateIfNonExistent */ false, SomeRef(mPendingDirectoryLock)) 6860 ->Then( 6861 GetCurrentSerialEventTarget(), __func__, 6862 [self = RefPtr(this)](QuotaManager::ClientDirectoryLockHandlePromise:: 6863 ResolveOrRejectValue&& aValue) { 6864 self->mPendingDirectoryLock = nullptr; 6865 6866 if (aValue.IsResolve()) { 6867 self->DirectoryLockAcquired(std::move(aValue.ResolveValue())); 6868 } else { 6869 self->DirectoryLockFailed(); 6870 } 6871 }); 6872 6873 return NS_OK; 6874 } 6875 6876 nsresult PrepareDatastoreOp::CheckExistingOperations() { 6877 AssertIsOnOwningThread(); 6878 MOZ_ASSERT(mState == State::Nesting); 6879 MOZ_ASSERT(mNestedState == NestedState::CheckExistingOperations); 6880 MOZ_ASSERT(gPrepareDatastoreOps); 6881 6882 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 6883 !MayProceed()) { 6884 return NS_ERROR_ABORT; 6885 } 6886 6887 mNestedState = NestedState::CheckClosingDatastore; 6888 6889 // See if this PrepareDatastoreOp needs to wait. 6890 bool foundThis = false; 6891 bool blocked = false; 6892 6893 for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0; index--) { 6894 const auto& existingOp = (*gPrepareDatastoreOps)[index - 1]; 6895 6896 if (existingOp == this) { 6897 foundThis = true; 6898 continue; 6899 } 6900 6901 if (foundThis && existingOp->Origin() == Origin()) { 6902 existingOp->AddBlockingOp(*this); 6903 AddBlockedOnOp(*existingOp); 6904 blocked = true; 6905 } 6906 } 6907 6908 if (!blocked) { 6909 QM_TRY(MOZ_TO_RESULT(CheckClosingDatastoreInternal())); 6910 } 6911 6912 return NS_OK; 6913 } 6914 6915 nsresult PrepareDatastoreOp::CheckClosingDatastore() { 6916 AssertIsOnOwningThread(); 6917 MOZ_ASSERT(mState == State::Nesting); 6918 MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore); 6919 6920 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 6921 !MayProceed()) { 6922 return NS_ERROR_ABORT; 6923 } 6924 6925 QM_TRY(MOZ_TO_RESULT(CheckClosingDatastoreInternal())); 6926 6927 return NS_OK; 6928 } 6929 6930 nsresult PrepareDatastoreOp::CheckClosingDatastoreInternal() { 6931 AssertIsOnOwningThread(); 6932 MOZ_ASSERT(mState == State::Nesting); 6933 MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore); 6934 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 6935 MOZ_ASSERT(MayProceed()); 6936 6937 mNestedState = NestedState::PreparationPending; 6938 6939 RefPtr<Datastore> datastore; 6940 if ((datastore = GetDatastore(Origin())) && datastore->IsClosed()) { 6941 datastore->WaitForConnectionToComplete(this); 6942 6943 return NS_OK; 6944 } 6945 6946 QM_TRY(MOZ_TO_RESULT(BeginDatastorePreparationInternal())); 6947 6948 return NS_OK; 6949 } 6950 6951 nsresult PrepareDatastoreOp::BeginDatastorePreparation() { 6952 AssertIsOnOwningThread(); 6953 MOZ_ASSERT(mState == State::Nesting); 6954 MOZ_ASSERT(mNestedState == NestedState::PreparationPending); 6955 6956 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 6957 !MayProceed()) { 6958 return NS_ERROR_ABORT; 6959 } 6960 6961 QM_TRY(MOZ_TO_RESULT(BeginDatastorePreparationInternal())); 6962 6963 return NS_OK; 6964 } 6965 6966 nsresult PrepareDatastoreOp::BeginDatastorePreparationInternal() { 6967 AssertIsOnOwningThread(); 6968 MOZ_ASSERT(mState == State::Nesting); 6969 MOZ_ASSERT(mNestedState == NestedState::PreparationPending); 6970 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 6971 MOZ_ASSERT(MayProceed()); 6972 MOZ_ASSERT(OriginIsKnown()); 6973 6974 if ((mDatastore = GetDatastore(Origin()))) { 6975 MOZ_ASSERT(!mDatastore->IsClosed()); 6976 6977 mExtraDirectoryLockHandle = std::move(mDirectoryLockHandle); 6978 6979 mDatastore->NoteLivePrepareDatastoreOp(this); 6980 6981 FinishNesting(); 6982 6983 return NS_OK; 6984 } 6985 6986 SendToIOThread(); 6987 6988 return NS_OK; 6989 } 6990 6991 void PrepareDatastoreOp::SendToIOThread() { 6992 AssertIsOnOwningThread(); 6993 MOZ_ASSERT(mState == State::Nesting); 6994 MOZ_ASSERT(mNestedState == NestedState::PreparationPending); 6995 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 6996 MOZ_ASSERT(MayProceed()); 6997 6998 // Skip all disk related stuff and transition to SendingReadyMessage if we 6999 // are preparing a datastore for private browsing. 7000 // Note that we do use a directory lock for private browsing even though we 7001 // don't do any stuff on disk. The thing is that without a directory lock, 7002 // quota manager wouldn't call AbortOperationsForLocks for our private 7003 // browsing origin when a clear origin operation is requested. 7004 // AbortOperationsForLocks requests all databases to close and the datastore 7005 // is destroyed in the end. Any following LocalStorage API call will trigger 7006 // preparation of a new (empty) datastore. 7007 if (mPrivateBrowsingId) { 7008 FinishNesting(); 7009 7010 return; 7011 } 7012 7013 QuotaManager* quotaManager = QuotaManager::Get(); 7014 MOZ_ASSERT(quotaManager); 7015 7016 // Must set this before dispatching otherwise we will race with the IO thread. 7017 mNestedState = NestedState::DatabaseWorkOpen; 7018 7019 MOZ_ALWAYS_SUCCEEDS( 7020 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)); 7021 7022 localstorage::NotifyDatabaseWorkStarted(); 7023 } 7024 7025 nsresult PrepareDatastoreOp::DatabaseWork() { 7026 GECKO_TRACE_SCOPE("dom::localstorage", "PrepareDatastoreOp::DatabaseWork"); 7027 7028 AssertIsOnIOThread(); 7029 MOZ_ASSERT(mArchivedOriginScope); 7030 MOZ_ASSERT(mUsage == 0); 7031 MOZ_ASSERT(mState == State::Nesting); 7032 MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen); 7033 7034 const auto innerFunc = [&](const auto&) -> nsresult { 7035 // XXX This function is too long, refactor it into helper functions for 7036 // readability. 7037 7038 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 7039 !MayProceedOnNonOwningThread()) { 7040 return NS_ERROR_ABORT; 7041 } 7042 7043 QuotaManager* quotaManager = QuotaManager::Get(); 7044 MOZ_ASSERT(quotaManager); 7045 7046 const UsageInfo usageInfo = quotaManager->GetUsageForClient( 7047 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata, 7048 mozilla::dom::quota::Client::LS); 7049 7050 const bool hasUsage = usageInfo.DatabaseUsage().isSome(); 7051 MOZ_ASSERT(usageInfo.FileUsage().isNothing()); 7052 7053 if (!gArchivedOrigins) { 7054 QM_TRY(MOZ_TO_RESULT(LoadArchivedOrigins())); 7055 MOZ_ASSERT(gArchivedOrigins); 7056 } 7057 7058 bool hasDataForMigration = 7059 mEnableMigration && mArchivedOriginScope->HasMatches(gArchivedOrigins); 7060 7061 // If there's nothing to preload (except the case when we want to migrate 7062 // data during preloading), then we can finish the operation without 7063 // creating a datastore in GetResponse (GetResponse won't create a datastore 7064 // if mDatatabaseNotAvailable and mForPreload are both true). 7065 if (mForPreload && !hasUsage && !hasDataForMigration) { 7066 return DatabaseNotAvailable(); 7067 } 7068 7069 // The origin directory doesn't need to be created when we don't have data 7070 // for migration. It will be created on the connection thread in 7071 // Connection::EnsureStorageConnection. 7072 // However, origin quota must be initialized, GetQuotaObject in GetResponse 7073 // would fail otherwise. 7074 QM_TRY_INSPECT( 7075 const auto& directoryEntry, 7076 ([hasDataForMigration, "aManager, 7077 this]() -> mozilla::Result<nsCOMPtr<nsIFile>, nsresult> { 7078 if (hasDataForMigration) { 7079 QM_TRY_RETURN(quotaManager->GetOrCreateTemporaryOriginDirectory( 7080 mOriginMetadata)); 7081 } 7082 7083 MOZ_ASSERT(mOriginMetadata.mPersistenceType == 7084 PERSISTENCE_TYPE_DEFAULT); 7085 7086 QM_TRY_RETURN(quotaManager->GetOriginDirectory(mOriginMetadata)); 7087 }())); 7088 7089 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append( 7090 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME)))); 7091 7092 QM_TRY_INSPECT( 7093 const auto& directoryPath, 7094 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directoryEntry, GetPath)); 7095 7096 // The ls directory doesn't need to be created when we don't have data for 7097 // migration. It will be created on the connection thread in 7098 // Connection::EnsureStorageConnection. 7099 QM_TRY(MOZ_TO_RESULT( 7100 EnsureDirectoryEntry(directoryEntry, 7101 /* aCreateIfNotExists */ hasDataForMigration, 7102 /* aIsDirectory */ true))); 7103 7104 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName))); 7105 7106 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDatabaseFilePath))); 7107 7108 // The database doesn't need to be created when we don't have data for 7109 // migration. It will be created on the connection thread in 7110 // Connection::EnsureStorageConnection. 7111 bool alreadyExisted; 7112 QM_TRY(MOZ_TO_RESULT( 7113 EnsureDirectoryEntry(directoryEntry, 7114 /* aCreateIfNotExists */ hasDataForMigration, 7115 /* aIsDirectory */ false, &alreadyExisted))); 7116 7117 if (alreadyExisted) { 7118 // The database does exist. 7119 MOZ_ASSERT(hasUsage); 7120 7121 // XXX Change type of mUsage to UsageInfo or DatabaseUsageType. 7122 mUsage = usageInfo.DatabaseUsage().valueOr(0); 7123 } else { 7124 // The database doesn't exist. 7125 MOZ_ASSERT(!hasUsage); 7126 7127 if (!hasDataForMigration) { 7128 // The database doesn't exist and we don't have data for migration. 7129 // Finish the operation, but create an empty datastore in GetResponse 7130 // (GetResponse will create an empty datastore if mDatabaseNotAvailable 7131 // is true and mForPreload is false). 7132 return DatabaseNotAvailable(); 7133 } 7134 } 7135 7136 // We initialized mDatabaseFilePath and mUsage, GetQuotaObject can now be 7137 // called. 7138 const RefPtr<QuotaObject> quotaObject = GetQuotaObject(); 7139 7140 QM_TRY(OkIf(quotaObject), Err(NS_ERROR_FAILURE)); 7141 7142 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(directoryPath)); 7143 7144 QM_TRY_INSPECT(const auto& usageJournalFile, 7145 GetUsageJournalFile(directoryPath)); 7146 7147 QM_TRY_INSPECT( 7148 const auto& connection, 7149 (CreateStorageConnectionWithRecovery( 7150 *directoryEntry, *usageFile, Origin(), ["aObject, this] { 7151 // This is called when the usage file was removed or we notice 7152 // that the usage file doesn't exist anymore. Adjust the usage 7153 // accordingly. 7154 7155 MOZ_ALWAYS_TRUE( 7156 quotaObject->MaybeUpdateSize(0, /* aTruncate */ true)); 7157 7158 mUsage = 0; 7159 }))); 7160 7161 QM_TRY(MOZ_TO_RESULT(VerifyDatabaseInformation(connection))); 7162 7163 if (hasDataForMigration) { 7164 MOZ_ASSERT(mUsage == 0); 7165 7166 { 7167 QM_TRY_INSPECT(const auto& archiveFile, 7168 GetArchiveFile(quotaManager->GetStoragePath())); 7169 7170 auto autoArchiveDatabaseAttacher = 7171 AutoDatabaseAttacher(connection, archiveFile, "archive"_ns); 7172 7173 QM_TRY(MOZ_TO_RESULT(autoArchiveDatabaseAttacher.Attach())); 7174 7175 QM_TRY_INSPECT(const int64_t& newUsage, 7176 GetUsage(*connection, mArchivedOriginScope.get())); 7177 7178 QM_TRY( 7179 OkIf(quotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)), 7180 NS_ERROR_FILE_NO_DEVICE_SPACE); 7181 7182 auto autoUpdateSize = MakeScopeExit(["aObject] { 7183 MOZ_ALWAYS_TRUE( 7184 quotaObject->MaybeUpdateSize(0, /* aTruncate */ true)); 7185 }); 7186 7187 mozStorageTransaction transaction( 7188 connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); 7189 7190 QM_TRY(MOZ_TO_RESULT(transaction.Start())); 7191 7192 { 7193 nsCOMPtr<mozIStorageFunction> function = new CompressFunction(); 7194 7195 QM_TRY(MOZ_TO_RESULT( 7196 connection->CreateFunction("compress"_ns, 1, function))); 7197 7198 function = new CompressionTypeFunction(); 7199 7200 QM_TRY(MOZ_TO_RESULT( 7201 connection->CreateFunction("compressionType"_ns, 1, function))); 7202 7203 QM_TRY_INSPECT( 7204 const auto& stmt, 7205 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 7206 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement, 7207 "INSERT INTO data (key, utf16_length, conversion_type, " 7208 "compression_type, value) " 7209 "SELECT key, utf16Length(value), :conversionType, " 7210 "compressionType(value), compress(value)" 7211 "FROM webappsstore2 " 7212 "WHERE originKey = :originKey " 7213 "AND originAttributes = :originAttributes;"_ns)); 7214 7215 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName( 7216 "conversionType"_ns, 7217 static_cast<int32_t>(LSValue::ConversionType::UTF16_UTF8)))); 7218 7219 QM_TRY(MOZ_TO_RESULT(mArchivedOriginScope->BindToStatement(stmt))); 7220 7221 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 7222 7223 QM_TRY(MOZ_TO_RESULT(connection->RemoveFunction("compress"_ns))); 7224 7225 QM_TRY( 7226 MOZ_TO_RESULT(connection->RemoveFunction("compressionType"_ns))); 7227 } 7228 7229 { 7230 QM_TRY_INSPECT( 7231 const auto& stmt, 7232 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 7233 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement, 7234 "UPDATE database SET usage = :usage;"_ns)); 7235 7236 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName("usage"_ns, newUsage))); 7237 7238 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 7239 } 7240 7241 { 7242 QM_TRY_INSPECT( 7243 const auto& stmt, 7244 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 7245 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement, 7246 "DELETE FROM webappsstore2 " 7247 "WHERE originKey = :originKey " 7248 "AND originAttributes = :originAttributes;"_ns)); 7249 7250 QM_TRY(MOZ_TO_RESULT(mArchivedOriginScope->BindToStatement(stmt))); 7251 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 7252 } 7253 7254 QM_TRY(MOZ_TO_RESULT( 7255 UpdateUsageFile(usageFile, usageJournalFile, newUsage))); 7256 QM_TRY(MOZ_TO_RESULT(transaction.Commit())); 7257 7258 autoUpdateSize.release(); 7259 7260 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false))); 7261 7262 mUsage = newUsage; 7263 7264 QM_TRY(MOZ_TO_RESULT(autoArchiveDatabaseAttacher.Detach())); 7265 } 7266 7267 MOZ_ASSERT(gArchivedOrigins); 7268 MOZ_ASSERT(mArchivedOriginScope->HasMatches(gArchivedOrigins)); 7269 mArchivedOriginScope->RemoveMatches(gArchivedOrigins); 7270 } 7271 7272 nsCOMPtr<mozIStorageConnection> shadowConnection; 7273 if (!gInitializedShadowStorage) { 7274 QM_TRY_UNWRAP(shadowConnection, 7275 CreateShadowStorageConnection(quotaManager->GetBasePath())); 7276 7277 gInitializedShadowStorage = true; 7278 } 7279 7280 // Must close connections before dispatching otherwise we might race with 7281 // the connection thread which needs to open the same databases. 7282 MOZ_ALWAYS_SUCCEEDS(connection->Close()); 7283 7284 if (shadowConnection) { 7285 MOZ_ALWAYS_SUCCEEDS(shadowConnection->Close()); 7286 } 7287 7288 SleepIfEnabled( 7289 StaticPrefs::dom_storage_databaseInitialization_pauseOnIOThreadMs()); 7290 7291 // Must set this before dispatching otherwise we will race with the owning 7292 // thread. 7293 mNestedState = NestedState::BeginLoadData; 7294 7295 QM_TRY( 7296 MOZ_TO_RESULT(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL))); 7297 7298 return NS_OK; 7299 }; 7300 7301 return ExecuteOriginInitialization( 7302 mOriginMetadata.mOrigin, LSOriginInitialization::Datastore, 7303 "dom::localstorage::FirstOriginInitializationAttempt::Datastore"_ns, 7304 innerFunc); 7305 } 7306 7307 nsresult PrepareDatastoreOp::DatabaseNotAvailable() { 7308 AssertIsOnIOThread(); 7309 MOZ_ASSERT(mState == State::Nesting); 7310 MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen); 7311 7312 mDatabaseNotAvailable = true; 7313 7314 nsresult rv = FinishNestingOnNonOwningThread(); 7315 if (NS_WARN_IF(NS_FAILED(rv))) { 7316 return rv; 7317 } 7318 7319 return NS_OK; 7320 } 7321 7322 nsresult PrepareDatastoreOp::EnsureDirectoryEntry(nsIFile* aEntry, 7323 bool aCreateIfNotExists, 7324 bool aIsDirectory, 7325 bool* aAlreadyExisted) { 7326 AssertIsOnIOThread(); 7327 MOZ_ASSERT(aEntry); 7328 7329 QM_TRY_INSPECT(const bool& exists, 7330 MOZ_TO_RESULT_INVOKE_MEMBER(aEntry, Exists)); 7331 7332 if (!exists) { 7333 if (!aCreateIfNotExists) { 7334 if (aAlreadyExisted) { 7335 *aAlreadyExisted = false; 7336 } 7337 return NS_OK; 7338 } 7339 7340 if (aIsDirectory) { 7341 QM_TRY(MOZ_TO_RESULT(aEntry->Create(nsIFile::DIRECTORY_TYPE, 0755))); 7342 } 7343 } 7344 #ifdef DEBUG 7345 else { 7346 bool isDirectory; 7347 MOZ_ASSERT(NS_SUCCEEDED(aEntry->IsDirectory(&isDirectory))); 7348 MOZ_ASSERT(isDirectory == aIsDirectory); 7349 } 7350 #endif 7351 7352 if (aAlreadyExisted) { 7353 *aAlreadyExisted = exists; 7354 } 7355 return NS_OK; 7356 } 7357 7358 nsresult PrepareDatastoreOp::VerifyDatabaseInformation( 7359 mozIStorageConnection* aConnection) { 7360 AssertIsOnIOThread(); 7361 MOZ_ASSERT(aConnection); 7362 7363 QM_TRY_INSPECT(const auto& stmt, 7364 CreateAndExecuteSingleStepStatement< 7365 SingleStepResult::ReturnNullIfNoResult>( 7366 *aConnection, "SELECT origin FROM database"_ns)); 7367 7368 QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED); 7369 7370 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 7371 nsCString, stmt, GetUTF8String, 0)); 7372 7373 QM_TRY(OkIf(QuotaManager::AreOriginsEqualOnDisk(Origin(), origin)), 7374 NS_ERROR_FILE_CORRUPTED); 7375 7376 return NS_OK; 7377 } 7378 7379 already_AddRefed<QuotaObject> PrepareDatastoreOp::GetQuotaObject() { 7380 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread()); 7381 MOZ_ASSERT(!mOriginMetadata.mGroup.IsEmpty()); 7382 MOZ_ASSERT(OriginIsKnown()); 7383 MOZ_ASSERT(!mDatabaseFilePath.IsEmpty()); 7384 7385 QuotaManager* quotaManager = QuotaManager::Get(); 7386 MOZ_ASSERT(quotaManager); 7387 7388 RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject( 7389 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata, 7390 mozilla::dom::quota::Client::LS, mDatabaseFilePath, mUsage); 7391 7392 if (!quotaObject) { 7393 LS_WARNING("Failed to get quota object for group (%s) and origin (%s)!", 7394 mOriginMetadata.mGroup.get(), Origin().get()); 7395 } 7396 7397 return quotaObject.forget(); 7398 } 7399 7400 nsresult PrepareDatastoreOp::BeginLoadData() { 7401 AssertIsOnOwningThread(); 7402 MOZ_ASSERT(mState == State::Nesting); 7403 MOZ_ASSERT(mNestedState == NestedState::BeginLoadData); 7404 MOZ_ASSERT(!mConnection); 7405 7406 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 7407 !MayProceed()) { 7408 return NS_ERROR_ABORT; 7409 } 7410 7411 if (!gConnectionThread) { 7412 gConnectionThread = new ConnectionThread(); 7413 } 7414 7415 mConnection = gConnectionThread->CreateConnection( 7416 mOriginMetadata, std::move(mArchivedOriginScope), 7417 /* aDatabaseWasNotAvailable */ false); 7418 MOZ_ASSERT(mConnection); 7419 7420 // Must set this before dispatching otherwise we will race with the 7421 // connection thread. 7422 mNestedState = NestedState::DatabaseWorkLoadData; 7423 7424 // Can't assign to mLoadDataOp directly since that's a weak reference and 7425 // LoadDataOp is reference counted. 7426 RefPtr<LoadDataOp> loadDataOp = new LoadDataOp(this); 7427 7428 // This add refs loadDataOp. 7429 mConnection->Dispatch(loadDataOp); 7430 7431 // This is cleared in LoadDataOp::Cleanup() before the load data op is 7432 // destroyed. 7433 mLoadDataOp = loadDataOp; 7434 7435 return NS_OK; 7436 } 7437 7438 void PrepareDatastoreOp::FinishNesting() { 7439 AssertIsOnOwningThread(); 7440 MOZ_ASSERT(mState == State::Nesting); 7441 7442 // The caller holds a strong reference to us, no need for a self reference 7443 // before calling Run(). 7444 7445 mState = State::SendingReadyMessage; 7446 mNestedState = NestedState::AfterNesting; 7447 7448 MOZ_ALWAYS_SUCCEEDS(Run()); 7449 } 7450 7451 nsresult PrepareDatastoreOp::FinishNestingOnNonOwningThread() { 7452 MOZ_ASSERT(!IsOnOwningThread()); 7453 MOZ_ASSERT(mState == State::Nesting); 7454 7455 // Must set mState before dispatching otherwise we will race with the owning 7456 // thread. 7457 mState = State::SendingReadyMessage; 7458 mNestedState = NestedState::AfterNesting; 7459 7460 QM_TRY( 7461 MOZ_TO_RESULT(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL))); 7462 7463 return NS_OK; 7464 } 7465 7466 nsresult PrepareDatastoreOp::NestedRun() { 7467 nsresult rv; 7468 7469 switch (mNestedState) { 7470 case NestedState::OpenDirectory: 7471 rv = OpenDirectory(); 7472 break; 7473 7474 case NestedState::CheckExistingOperations: 7475 rv = CheckExistingOperations(); 7476 break; 7477 7478 case NestedState::CheckClosingDatastore: 7479 rv = CheckClosingDatastore(); 7480 break; 7481 7482 case NestedState::PreparationPending: 7483 rv = BeginDatastorePreparation(); 7484 break; 7485 7486 case NestedState::DatabaseWorkOpen: 7487 rv = DatabaseWork(); 7488 break; 7489 7490 case NestedState::BeginLoadData: 7491 rv = BeginLoadData(); 7492 break; 7493 7494 default: 7495 MOZ_CRASH("Bad state!"); 7496 } 7497 7498 if (NS_WARN_IF(NS_FAILED(rv))) { 7499 mNestedState = NestedState::AfterNesting; 7500 7501 return rv; 7502 } 7503 7504 return NS_OK; 7505 } 7506 7507 void PrepareDatastoreOp::GetResponse(LSRequestResponse& aResponse) { 7508 AssertIsOnOwningThread(); 7509 MOZ_ASSERT(mState == State::SendingResults); 7510 MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); 7511 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 7512 MOZ_ASSERT(MayProceed()); 7513 7514 // A datastore is not created when we are just trying to preload data and 7515 // there's no database file. 7516 if (mDatabaseNotAvailable && mForPreload) { 7517 LSRequestPreloadDatastoreResponse preloadDatastoreResponse; 7518 7519 aResponse = preloadDatastoreResponse; 7520 7521 return; 7522 } 7523 7524 if (!mDatastore) { 7525 MOZ_ASSERT(mUsage == mDEBUGUsage); 7526 7527 RefPtr<QuotaObject> quotaObject; 7528 7529 if (mPrivateBrowsingId == 0) { 7530 if (!mConnection) { 7531 // This can happen when there's no database file. 7532 MOZ_ASSERT(mDatabaseNotAvailable); 7533 7534 // Even though there's no database file, we need to create a connection 7535 // and pass it to datastore. 7536 if (!gConnectionThread) { 7537 gConnectionThread = new ConnectionThread(); 7538 } 7539 7540 mConnection = gConnectionThread->CreateConnection( 7541 mOriginMetadata, std::move(mArchivedOriginScope), 7542 /* aDatabaseWasNotAvailable */ true); 7543 MOZ_ASSERT(mConnection); 7544 } 7545 7546 quotaObject = GetQuotaObject(); 7547 if (!quotaObject) { 7548 aResponse = NS_ERROR_FAILURE; 7549 return; 7550 } 7551 } 7552 7553 MOZ_ASSERT(mDirectoryLockHandle); 7554 MOZ_ASSERT_IF(mDirectoryLockHandle->Invalidated(), mInvalidated); 7555 7556 mDatastore = new Datastore( 7557 mOriginMetadata, mPrivateBrowsingId, mUsage, mSizeOfKeys, mSizeOfItems, 7558 std::move(mDirectoryLockHandle), std::move(mConnection), 7559 std::move(quotaObject), mValues, std::move(mOrderedItems)); 7560 7561 mDatastore->NoteLivePrepareDatastoreOp(this); 7562 7563 if (!gDatastores) { 7564 gDatastores = new DatastoreHashtable(); 7565 } 7566 7567 MOZ_DIAGNOSTIC_ASSERT(!gDatastores->Contains(Origin())); 7568 gDatastores->InsertOrUpdate(Origin(), 7569 WrapMovingNotNullUnchecked(mDatastore)); 7570 } 7571 7572 if (mPrivateBrowsingId && !mInvalidated) { 7573 if (!gPrivateDatastores) { 7574 gPrivateDatastores = MakeUnique<PrivateDatastoreHashtable>(); 7575 } 7576 7577 gPrivateDatastores->LookupOrInsertWith(Origin(), [&] { 7578 auto privateDatastore = 7579 MakeUnique<PrivateDatastore>(WrapMovingNotNull(mDatastore)); 7580 7581 mPrivateDatastoreRegistered.Flip(); 7582 7583 return privateDatastore; 7584 }); 7585 } 7586 7587 mDatastoreId = ++gLastDatastoreId; 7588 7589 if (!gPreparedDatastores) { 7590 gPreparedDatastores = new PreparedDatastoreHashtable(); 7591 } 7592 const auto& preparedDatastore = gPreparedDatastores->InsertOrUpdate( 7593 mDatastoreId, MakeUnique<PreparedDatastore>( 7594 mDatastore, mContentParentId, Origin(), mDatastoreId, 7595 /* aForPreload */ mForPreload)); 7596 7597 if (mInvalidated) { 7598 preparedDatastore->Invalidate(); 7599 } 7600 7601 mPreparedDatastoreRegistered.Flip(); 7602 7603 if (mForPreload) { 7604 LSRequestPreloadDatastoreResponse preloadDatastoreResponse; 7605 preloadDatastoreResponse.invalidated() = mInvalidated; 7606 7607 aResponse = preloadDatastoreResponse; 7608 } else { 7609 const LSRequestCommonParams& commonParams = 7610 mParams.get_LSRequestPrepareDatastoreParams().commonParams(); 7611 7612 const PrincipalInfo& storagePrincipalInfo = 7613 commonParams.storagePrincipalInfo(); 7614 7615 Endpoint<PBackgroundLSDatabaseParent> parentEndpoint; 7616 Endpoint<PBackgroundLSDatabaseChild> childEndpoint; 7617 MOZ_ALWAYS_SUCCEEDS(PBackgroundLSDatabase::CreateEndpoints(&parentEndpoint, 7618 &childEndpoint)); 7619 7620 if (!RecvCreateBackgroundLSDatabaseParent(storagePrincipalInfo, 7621 mPrivateBrowsingId, mDatastoreId, 7622 std::move(parentEndpoint))) { 7623 aResponse = NS_ERROR_FAILURE; 7624 } else { 7625 LSRequestPrepareDatastoreResponse prepareDatastoreResponse; 7626 prepareDatastoreResponse.databaseChildEndpoint() = 7627 std::move(childEndpoint); 7628 prepareDatastoreResponse.invalidated() = mInvalidated; 7629 7630 aResponse = std::move(prepareDatastoreResponse); 7631 } 7632 } 7633 } 7634 7635 void PrepareDatastoreOp::Cleanup() { 7636 AssertIsOnOwningThread(); 7637 7638 if (mDatastore) { 7639 MOZ_ASSERT(!mDirectoryLockHandle); 7640 MOZ_ASSERT(!mConnection); 7641 7642 if (NS_FAILED(ResultCode())) { 7643 if (mPrivateDatastoreRegistered) { 7644 MOZ_ASSERT(gPrivateDatastores); 7645 DebugOnly<bool> removed = gPrivateDatastores->Remove(Origin()); 7646 MOZ_ASSERT(removed); 7647 7648 if (!gPrivateDatastores->Count()) { 7649 gPrivateDatastores = nullptr; 7650 } 7651 } 7652 7653 if (mPreparedDatastoreRegistered) { 7654 // Just in case we failed to send datastoreId to the child, we need to 7655 // destroy prepared datastore, otherwise it won't be destroyed until 7656 // the timer fires (after 20 seconds). 7657 MOZ_ASSERT(gPreparedDatastores); 7658 MOZ_ASSERT(mDatastoreId > 0); 7659 DebugOnly<bool> removed = gPreparedDatastores->Remove(mDatastoreId); 7660 MOZ_ASSERT(removed); 7661 7662 if (!gPreparedDatastores->Count()) { 7663 gPreparedDatastores = nullptr; 7664 } 7665 } 7666 } 7667 7668 // Make sure to release the datastore on this thread. 7669 7670 mDatastore->NoteFinishedPrepareDatastoreOp(this); 7671 7672 mDatastore = nullptr; 7673 7674 { 7675 auto extraDirectoryLockHandle = std::move(mExtraDirectoryLockHandle); 7676 } 7677 7678 CleanupMetadata(); 7679 } else if (mConnection) { 7680 // If we have a connection then the operation must have failed and there 7681 // must be a directory lock too. 7682 MOZ_ASSERT(NS_FAILED(ResultCode())); 7683 MOZ_ASSERT(mDirectoryLockHandle); 7684 MOZ_ASSERT(!mExtraDirectoryLockHandle); 7685 7686 // We must close the connection on the connection thread before releasing 7687 // it on this thread. The directory lock can't be released either. 7688 nsCOMPtr<nsIRunnable> callback = 7689 NewRunnableMethod("dom::OpenDatabaseOp::ConnectionClosedCallback", this, 7690 &PrepareDatastoreOp::ConnectionClosedCallback); 7691 7692 mConnection->Close(callback); 7693 } else { 7694 // If we don't have a connection, but we do have a directory lock then the 7695 // operation must have failed or we were preloading a datastore and there 7696 // was no physical database on disk. 7697 MOZ_ASSERT_IF(mDirectoryLockHandle, 7698 NS_FAILED(ResultCode()) || mDatabaseNotAvailable); 7699 MOZ_ASSERT(!mExtraDirectoryLockHandle); 7700 7701 // There's no connection, so it's safe to release the directory lock and 7702 // unregister itself from the array. 7703 7704 { 7705 auto destroyingDdirectoryLockHandle = std::move(mDirectoryLockHandle); 7706 } 7707 7708 CleanupMetadata(); 7709 } 7710 } 7711 7712 void PrepareDatastoreOp::ConnectionClosedCallback() { 7713 AssertIsOnOwningThread(); 7714 MOZ_ASSERT(NS_FAILED(ResultCode())); 7715 MOZ_ASSERT(mDirectoryLockHandle); 7716 MOZ_ASSERT(mConnection); 7717 7718 mConnection = nullptr; 7719 7720 { 7721 auto destroyingDirectoryLockHandle = std::move(mDirectoryLockHandle); 7722 } 7723 7724 CleanupMetadata(); 7725 } 7726 7727 void PrepareDatastoreOp::CleanupMetadata() { 7728 AssertIsOnOwningThread(); 7729 7730 for (const NotNull<RefPtr<PrepareDatastoreOp>>& blockingOp : mBlocking) { 7731 blockingOp->MaybeUnblock(*this); 7732 } 7733 mBlocking.Clear(); 7734 7735 MOZ_ASSERT(gPrepareDatastoreOps); 7736 gPrepareDatastoreOps->RemoveElement(this); 7737 7738 QuotaManager::MaybeRecordQuotaClientShutdownStep( 7739 quota::Client::LS, "PrepareDatastoreOp completed"_ns); 7740 7741 if (gPrepareDatastoreOps->IsEmpty()) { 7742 gPrepareDatastoreOps = nullptr; 7743 } 7744 7745 if (NS_SUCCEEDED(ResultCode())) { 7746 glean::localstorage_request::prepare_datastore_processing_time 7747 .StopAndAccumulate(std::move(mProcessingTimerId)); 7748 } else { 7749 glean::localstorage_request::prepare_datastore_processing_time.Cancel( 7750 std::move(mProcessingTimerId)); 7751 } 7752 } 7753 7754 void PrepareDatastoreOp::ActorDestroy(ActorDestroyReason aWhy) { 7755 AssertIsOnOwningThread(); 7756 7757 LSRequestBase::ActorDestroy(aWhy); 7758 7759 if (mLoadDataOp) { 7760 mLoadDataOp->NoteComplete(); 7761 } 7762 } 7763 7764 void PrepareDatastoreOp::DirectoryLockAcquired( 7765 ClientDirectoryLockHandle aLockHandle) { 7766 AssertIsOnOwningThread(); 7767 MOZ_ASSERT(mState == State::Nesting); 7768 MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending); 7769 MOZ_ASSERT(!mDirectoryLockHandle); 7770 7771 mPendingDirectoryLock = nullptr; 7772 7773 mDirectoryLockHandle = std::move(aLockHandle); 7774 7775 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 7776 !MayProceed() || mDirectoryLockHandle->Invalidated()) { 7777 MaybeSetFailureCode(NS_ERROR_ABORT); 7778 7779 FinishNesting(); 7780 7781 return; 7782 } 7783 7784 mNestedState = NestedState::CheckExistingOperations; 7785 7786 // XXX We could call CheckExistingOperations directly here or even fold it 7787 // here instead of dispatching to the same event target 7788 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); 7789 } 7790 7791 void PrepareDatastoreOp::DirectoryLockFailed() { 7792 AssertIsOnOwningThread(); 7793 MOZ_ASSERT(mState == State::Nesting); 7794 MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending); 7795 MOZ_ASSERT(!mDirectoryLockHandle); 7796 7797 mPendingDirectoryLock = nullptr; 7798 7799 MaybeSetFailureCode(NS_ERROR_FAILURE); 7800 7801 FinishNesting(); 7802 } 7803 7804 nsresult PrepareDatastoreOp::LoadDataOp::DoDatastoreWork() { 7805 AssertIsOnGlobalConnectionThread(); 7806 MOZ_ASSERT(mConnection); 7807 MOZ_ASSERT(mPrepareDatastoreOp); 7808 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting); 7809 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState == 7810 NestedState::DatabaseWorkLoadData); 7811 7812 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || 7813 !MayProceedOnNonOwningThread()) { 7814 return NS_ERROR_ABORT; 7815 } 7816 7817 QM_TRY_INSPECT( 7818 const auto& stmt, 7819 mConnection->BorrowCachedStatement( 7820 "SELECT key, utf16_length, conversion_type, compression_type, value " 7821 "FROM data;"_ns)); 7822 7823 QM_TRY(quota::CollectWhileHasResult( 7824 *stmt, [this](auto& stmt) -> mozilla::Result<Ok, nsresult> { 7825 QM_TRY_UNWRAP(auto key, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 7826 nsString, stmt, GetString, 0)); 7827 7828 LSValue value; 7829 QM_TRY(MOZ_TO_RESULT(value.InitFromStatement(&stmt, 1))); 7830 7831 mPrepareDatastoreOp->mValues.InsertOrUpdate(key, value); 7832 mPrepareDatastoreOp->mSizeOfKeys += key.Length(); 7833 mPrepareDatastoreOp->mSizeOfItems += key.Length() + value.Length(); 7834 #ifdef DEBUG 7835 mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.UTF16Length(); 7836 #endif 7837 7838 auto item = mPrepareDatastoreOp->mOrderedItems.AppendElement(); 7839 item->key() = std::move(key); 7840 item->value() = std::move(value); 7841 7842 return Ok{}; 7843 })); 7844 7845 return NS_OK; 7846 } 7847 7848 void PrepareDatastoreOp::LoadDataOp::OnSuccess() { 7849 AssertIsOnOwningThread(); 7850 MOZ_ASSERT(mPrepareDatastoreOp); 7851 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting); 7852 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState == 7853 NestedState::DatabaseWorkLoadData); 7854 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this); 7855 7856 mPrepareDatastoreOp->FinishNesting(); 7857 } 7858 7859 void PrepareDatastoreOp::LoadDataOp::OnFailure(nsresult aResultCode) { 7860 AssertIsOnOwningThread(); 7861 MOZ_ASSERT(mPrepareDatastoreOp); 7862 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting); 7863 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState == 7864 NestedState::DatabaseWorkLoadData); 7865 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this); 7866 7867 mPrepareDatastoreOp->SetFailureCode(aResultCode); 7868 7869 mPrepareDatastoreOp->FinishNesting(); 7870 } 7871 7872 void PrepareDatastoreOp::LoadDataOp::Cleanup() { 7873 AssertIsOnOwningThread(); 7874 MOZ_ASSERT(mPrepareDatastoreOp); 7875 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this); 7876 7877 mPrepareDatastoreOp->mLoadDataOp = nullptr; 7878 mPrepareDatastoreOp = nullptr; 7879 7880 ConnectionDatastoreOperationBase::Cleanup(); 7881 } 7882 7883 NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressFunction, mozIStorageFunction) 7884 7885 NS_IMETHODIMP 7886 PrepareDatastoreOp::CompressFunction::OnFunctionCall( 7887 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { 7888 AssertIsOnIOThread(); 7889 MOZ_ASSERT(aFunctionArguments); 7890 MOZ_ASSERT(aResult); 7891 7892 #ifdef DEBUG 7893 { 7894 uint32_t argCount; 7895 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount)); 7896 MOZ_ASSERT(argCount == 1); 7897 7898 int32_t type; 7899 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetTypeOfIndex(0, &type)); 7900 MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT); 7901 } 7902 #endif 7903 7904 QM_TRY_INSPECT(const auto& value, 7905 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 7906 nsCString, aFunctionArguments, GetUTF8String, 0)); 7907 7908 nsCString compressed; 7909 QM_TRY(OkIf(SnappyCompress(value, compressed)), NS_ERROR_OUT_OF_MEMORY); 7910 7911 const nsCString& buffer = compressed.IsVoid() ? value : compressed; 7912 7913 // mozStorage transforms empty blobs into null values, but our database 7914 // schema doesn't allow null values. We can workaround this by storing 7915 // empty buffers as UTF8 text (SQLite supports the type affinity, so the type 7916 // of the column is not fixed). 7917 nsCOMPtr<nsIVariant> result; 7918 if (0u == buffer.Length()) { // Otherwise empty string becomes null 7919 result = new storage::UTF8TextVariant(buffer); 7920 } else { 7921 result = new storage::BlobVariant(std::make_pair( 7922 static_cast<const void*>(buffer.get()), int(buffer.Length()))); 7923 } 7924 7925 result.forget(aResult); 7926 return NS_OK; 7927 } 7928 7929 NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressionTypeFunction, 7930 mozIStorageFunction) 7931 7932 NS_IMETHODIMP 7933 PrepareDatastoreOp::CompressionTypeFunction::OnFunctionCall( 7934 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { 7935 AssertIsOnIOThread(); 7936 MOZ_ASSERT(aFunctionArguments); 7937 MOZ_ASSERT(aResult); 7938 7939 #ifdef DEBUG 7940 { 7941 uint32_t argCount; 7942 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount)); 7943 MOZ_ASSERT(argCount == 1); 7944 7945 int32_t type; 7946 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetTypeOfIndex(0, &type)); 7947 MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT); 7948 } 7949 #endif 7950 7951 QM_TRY_INSPECT(const auto& value, 7952 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 7953 nsCString, aFunctionArguments, GetUTF8String, 0)); 7954 7955 nsCString compressed; 7956 QM_TRY(OkIf(SnappyCompress(value, compressed)), NS_ERROR_OUT_OF_MEMORY); 7957 7958 const int32_t compression = static_cast<int32_t>( 7959 compressed.IsVoid() ? LSValue::CompressionType::UNCOMPRESSED 7960 : LSValue::CompressionType::SNAPPY); 7961 7962 nsCOMPtr<nsIVariant> result = new storage::IntegerVariant(compression); 7963 7964 result.forget(aResult); 7965 return NS_OK; 7966 } 7967 7968 /******************************************************************************* 7969 * PrepareObserverOp 7970 ******************************************************************************/ 7971 7972 PrepareObserverOp::PrepareObserverOp( 7973 const LSRequestParams& aParams, 7974 const Maybe<ContentParentId>& aContentParentId) 7975 : LSRequestBase(aParams, aContentParentId) { 7976 MOZ_ASSERT(aParams.type() == 7977 LSRequestParams::TLSRequestPrepareObserverParams); 7978 } 7979 7980 nsresult PrepareObserverOp::Start() { 7981 AssertIsOnOwningThread(); 7982 MOZ_ASSERT(mState == State::StartingRequest); 7983 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 7984 MOZ_ASSERT(MayProceed()); 7985 7986 const LSRequestPrepareObserverParams params = 7987 mParams.get_LSRequestPrepareObserverParams(); 7988 7989 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo(); 7990 7991 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { 7992 mOrigin = quota::GetOriginForChrome(); 7993 } else { 7994 MOZ_ASSERT(storagePrincipalInfo.type() == 7995 PrincipalInfo::TContentPrincipalInfo); 7996 7997 mOrigin = quota::GetOriginFromValidatedPrincipalInfo(storagePrincipalInfo); 7998 } 7999 8000 mState = State::SendingReadyMessage; 8001 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); 8002 8003 return NS_OK; 8004 } 8005 8006 void PrepareObserverOp::GetResponse(LSRequestResponse& aResponse) { 8007 AssertIsOnOwningThread(); 8008 MOZ_ASSERT(mState == State::SendingResults); 8009 MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); 8010 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 8011 MOZ_ASSERT(MayProceed()); 8012 8013 uint64_t observerId = ++gLastObserverId; 8014 8015 RefPtr<Observer> observer = new Observer(mContentParentId, mOrigin); 8016 8017 if (!gPreparedObsevers) { 8018 gPreparedObsevers = new PreparedObserverHashtable(); 8019 } 8020 gPreparedObsevers->InsertOrUpdate(observerId, std::move(observer)); 8021 8022 LSRequestPrepareObserverResponse prepareObserverResponse; 8023 prepareObserverResponse.observerId() = observerId; 8024 8025 aResponse = prepareObserverResponse; 8026 } 8027 8028 /******************************************************************************* 8029 + * LSSimpleRequestBase 8030 + 8031 ******************************************************************************/ 8032 8033 LSSimpleRequestBase::LSSimpleRequestBase( 8034 const LSSimpleRequestParams& aParams, 8035 const Maybe<ContentParentId>& aContentParentId) 8036 : mParams(aParams), 8037 mContentParentId(aContentParentId), 8038 mState(State::Initial) {} 8039 8040 LSSimpleRequestBase::~LSSimpleRequestBase() { 8041 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(), 8042 mState == State::Initial || mState == State::Completed); 8043 } 8044 8045 void LSSimpleRequestBase::Dispatch() { 8046 AssertIsOnOwningThread(); 8047 8048 mState = State::StartingRequest; 8049 8050 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this)); 8051 } 8052 8053 bool LSSimpleRequestBase::VerifyRequestParams() { 8054 AssertIsOnBackgroundThread(); 8055 8056 MOZ_ASSERT(mParams.type() != LSSimpleRequestParams::T__None); 8057 8058 switch (mParams.type()) { 8059 case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: { 8060 const LSSimpleRequestPreloadedParams& params = 8061 mParams.get_LSSimpleRequestPreloadedParams(); 8062 8063 if (NS_WARN_IF(!VerifyPrincipalInfo( 8064 params.principalInfo(), params.storagePrincipalInfo(), false))) { 8065 return false; 8066 } 8067 8068 break; 8069 } 8070 8071 case LSSimpleRequestParams::TLSSimpleRequestGetStateParams: { 8072 const LSSimpleRequestGetStateParams& params = 8073 mParams.get_LSSimpleRequestGetStateParams(); 8074 8075 if (NS_WARN_IF(!VerifyPrincipalInfo( 8076 params.principalInfo(), params.storagePrincipalInfo(), false))) { 8077 return false; 8078 } 8079 8080 break; 8081 } 8082 8083 default: 8084 MOZ_CRASH("Should never get here!"); 8085 } 8086 8087 return true; 8088 } 8089 8090 nsresult LSSimpleRequestBase::StartRequest() { 8091 AssertIsOnOwningThread(); 8092 MOZ_ASSERT(mState == State::StartingRequest); 8093 8094 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 8095 !MayProceed()) { 8096 return NS_ERROR_ABORT; 8097 } 8098 8099 #ifdef DEBUG 8100 // Always verify parameters in DEBUG builds! 8101 bool trustParams = false; 8102 #else 8103 bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager()); 8104 #endif 8105 8106 if (!trustParams && NS_WARN_IF(!VerifyRequestParams())) { 8107 return NS_ERROR_FAILURE; 8108 } 8109 8110 QM_TRY(MOZ_TO_RESULT(Start())); 8111 8112 return NS_OK; 8113 } 8114 8115 void LSSimpleRequestBase::SendResults() { 8116 AssertIsOnOwningThread(); 8117 MOZ_ASSERT(mState == State::SendingResults); 8118 8119 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || 8120 !MayProceed()) { 8121 MaybeSetFailureCode(NS_ERROR_ABORT); 8122 } 8123 8124 if (MayProceed()) { 8125 LSSimpleRequestResponse response; 8126 8127 if (NS_SUCCEEDED(ResultCode())) { 8128 GetResponse(response); 8129 } else { 8130 response = ResultCode(); 8131 } 8132 8133 (void)PBackgroundLSSimpleRequestParent::Send__delete__(this, response); 8134 } 8135 8136 mState = State::Completed; 8137 } 8138 8139 NS_IMETHODIMP 8140 LSSimpleRequestBase::Run() { 8141 nsresult rv; 8142 8143 switch (mState) { 8144 case State::StartingRequest: 8145 rv = StartRequest(); 8146 break; 8147 8148 case State::SendingResults: 8149 SendResults(); 8150 return NS_OK; 8151 8152 default: 8153 MOZ_CRASH("Bad state!"); 8154 } 8155 8156 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) { 8157 MaybeSetFailureCode(rv); 8158 8159 // Must set mState before dispatching otherwise we will race with the owning 8160 // thread. 8161 mState = State::SendingResults; 8162 8163 if (IsOnOwningThread()) { 8164 SendResults(); 8165 } else { 8166 MOZ_ALWAYS_SUCCEEDS( 8167 OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); 8168 } 8169 } 8170 8171 return NS_OK; 8172 } 8173 8174 void LSSimpleRequestBase::ActorDestroy(ActorDestroyReason aWhy) { 8175 AssertIsOnOwningThread(); 8176 8177 NoteComplete(); 8178 } 8179 8180 /******************************************************************************* 8181 * PreloadedOp 8182 ******************************************************************************/ 8183 8184 PreloadedOp::PreloadedOp(const LSSimpleRequestParams& aParams, 8185 const Maybe<ContentParentId>& aContentParentId) 8186 : LSSimpleRequestBase(aParams, aContentParentId) { 8187 MOZ_ASSERT(aParams.type() == 8188 LSSimpleRequestParams::TLSSimpleRequestPreloadedParams); 8189 } 8190 8191 nsresult PreloadedOp::Start() { 8192 AssertIsOnOwningThread(); 8193 MOZ_ASSERT(mState == State::StartingRequest); 8194 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 8195 MOZ_ASSERT(MayProceed()); 8196 8197 const LSSimpleRequestPreloadedParams& params = 8198 mParams.get_LSSimpleRequestPreloadedParams(); 8199 8200 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo(); 8201 8202 MOZ_ASSERT( 8203 storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo || 8204 storagePrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo); 8205 mOrigin = 8206 storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo 8207 ? nsCString{quota::GetOriginForChrome()} 8208 : quota::GetOriginFromValidatedPrincipalInfo(storagePrincipalInfo); 8209 8210 mState = State::SendingResults; 8211 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); 8212 8213 return NS_OK; 8214 } 8215 8216 void PreloadedOp::GetResponse(LSSimpleRequestResponse& aResponse) { 8217 AssertIsOnOwningThread(); 8218 MOZ_ASSERT(mState == State::SendingResults); 8219 MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); 8220 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 8221 MOZ_ASSERT(MayProceed()); 8222 8223 bool preloaded; 8224 RefPtr<Datastore> datastore; 8225 if ((datastore = GetDatastore(mOrigin)) && !datastore->IsClosed()) { 8226 preloaded = true; 8227 } else { 8228 preloaded = false; 8229 } 8230 8231 LSSimpleRequestPreloadedResponse preloadedResponse; 8232 preloadedResponse.preloaded() = preloaded; 8233 8234 aResponse = preloadedResponse; 8235 } 8236 8237 /******************************************************************************* 8238 * GetStateOp 8239 ******************************************************************************/ 8240 8241 GetStateOp::GetStateOp(const LSSimpleRequestParams& aParams, 8242 const Maybe<ContentParentId>& aContentParentId) 8243 : LSSimpleRequestBase(aParams, aContentParentId) { 8244 MOZ_ASSERT(aParams.type() == 8245 LSSimpleRequestParams::TLSSimpleRequestGetStateParams); 8246 } 8247 8248 nsresult GetStateOp::Start() { 8249 AssertIsOnOwningThread(); 8250 MOZ_ASSERT(mState == State::StartingRequest); 8251 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 8252 MOZ_ASSERT(MayProceed()); 8253 8254 const LSSimpleRequestGetStateParams& params = 8255 mParams.get_LSSimpleRequestGetStateParams(); 8256 8257 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo(); 8258 8259 MOZ_ASSERT( 8260 storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo || 8261 storagePrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo); 8262 mOrigin = 8263 storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo 8264 ? nsCString{quota::GetOriginForChrome()} 8265 : quota::GetOriginFromValidatedPrincipalInfo(storagePrincipalInfo); 8266 8267 mState = State::SendingResults; 8268 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); 8269 8270 return NS_OK; 8271 } 8272 8273 void GetStateOp::GetResponse(LSSimpleRequestResponse& aResponse) { 8274 AssertIsOnOwningThread(); 8275 MOZ_ASSERT(mState == State::SendingResults); 8276 MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); 8277 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); 8278 MOZ_ASSERT(MayProceed()); 8279 8280 LSSimpleRequestGetStateResponse getStateResponse; 8281 8282 if (RefPtr<Datastore> datastore = GetDatastore(mOrigin)) { 8283 if (!datastore->IsClosed()) { 8284 getStateResponse.itemInfos() = datastore->GetOrderedItems().Clone(); 8285 } 8286 } 8287 8288 aResponse = getStateResponse; 8289 } 8290 8291 /******************************************************************************* 8292 * ArchivedOriginScope 8293 ******************************************************************************/ 8294 8295 // static 8296 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromOrigin( 8297 const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey) { 8298 return WrapUnique( 8299 new ArchivedOriginScope(Origin(aOriginAttrSuffix, aOriginKey))); 8300 } 8301 8302 // static 8303 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromPrefix( 8304 const nsACString& aOriginKey) { 8305 return WrapUnique(new ArchivedOriginScope(Prefix(aOriginKey))); 8306 } 8307 8308 // static 8309 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromPattern( 8310 const OriginAttributesPattern& aPattern) { 8311 return WrapUnique(new ArchivedOriginScope(Pattern(aPattern))); 8312 } 8313 8314 // static 8315 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromNull() { 8316 return WrapUnique(new ArchivedOriginScope(Null())); 8317 } 8318 8319 nsLiteralCString ArchivedOriginScope::GetBindingClause() const { 8320 return mData.match( 8321 [](const Origin&) { 8322 return " WHERE originKey = :originKey " 8323 "AND originAttributes = :originAttributes"_ns; 8324 }, 8325 [](const Pattern&) { 8326 return " WHERE originAttributes MATCH :originAttributesPattern"_ns; 8327 }, 8328 [](const Prefix&) { return " WHERE originKey = :originKey"_ns; }, 8329 [](const Null&) { return ""_ns; }); 8330 } 8331 8332 nsresult ArchivedOriginScope::BindToStatement( 8333 mozIStorageStatement* aStmt) const { 8334 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread()); 8335 MOZ_ASSERT(aStmt); 8336 8337 struct Matcher { 8338 mozIStorageStatement* mStmt; 8339 8340 explicit Matcher(mozIStorageStatement* aStmt) : mStmt(aStmt) {} 8341 8342 nsresult operator()(const Origin& aOrigin) { 8343 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName( 8344 "originKey"_ns, aOrigin.OriginNoSuffix()))); 8345 8346 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName( 8347 "originAttributes"_ns, aOrigin.OriginSuffix()))); 8348 8349 return NS_OK; 8350 } 8351 8352 nsresult operator()(const Prefix& aPrefix) { 8353 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName( 8354 "originKey"_ns, aPrefix.OriginNoSuffix()))); 8355 8356 return NS_OK; 8357 } 8358 8359 nsresult operator()(const Pattern& aPattern) { 8360 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName( 8361 "originAttributesPattern"_ns, "pattern1"_ns))); 8362 8363 return NS_OK; 8364 } 8365 8366 nsresult operator()(const Null& aNull) { return NS_OK; } 8367 }; 8368 8369 QM_TRY(MOZ_TO_RESULT(mData.match(Matcher(aStmt)))); 8370 8371 return NS_OK; 8372 } 8373 8374 bool ArchivedOriginScope::HasMatches( 8375 ArchivedOriginHashtable* aHashtable) const { 8376 AssertIsOnIOThread(); 8377 MOZ_ASSERT(aHashtable); 8378 8379 return mData.match( 8380 [aHashtable](const Origin& aOrigin) { 8381 const nsCString hashKey = GetArchivedOriginHashKey( 8382 aOrigin.OriginSuffix(), aOrigin.OriginNoSuffix()); 8383 8384 return aHashtable->Contains(hashKey); 8385 }, 8386 [aHashtable](const Pattern& aPattern) { 8387 return std::any_of( 8388 aHashtable->Values().cbegin(), aHashtable->Values().cend(), 8389 [&aPattern](const auto& entry) { 8390 return aPattern.GetPattern().Matches(entry->mOriginAttributes); 8391 }); 8392 }, 8393 [aHashtable](const Prefix& aPrefix) { 8394 return std::any_of( 8395 aHashtable->Values().cbegin(), aHashtable->Values().cend(), 8396 [&aPrefix](const auto& entry) { 8397 return entry->mOriginNoSuffix == aPrefix.OriginNoSuffix(); 8398 }); 8399 }, 8400 [aHashtable](const Null& aNull) { return !aHashtable->IsEmpty(); }); 8401 } 8402 8403 void ArchivedOriginScope::RemoveMatches( 8404 ArchivedOriginHashtable* aHashtable) const { 8405 AssertIsOnIOThread(); 8406 MOZ_ASSERT(aHashtable); 8407 8408 struct Matcher { 8409 ArchivedOriginHashtable* mHashtable; 8410 8411 explicit Matcher(ArchivedOriginHashtable* aHashtable) 8412 : mHashtable(aHashtable) {} 8413 8414 void operator()(const Origin& aOrigin) { 8415 nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(), 8416 aOrigin.OriginNoSuffix()); 8417 8418 mHashtable->Remove(hashKey); 8419 } 8420 8421 void operator()(const Prefix& aPrefix) { 8422 for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) { 8423 const auto& archivedOriginInfo = iter.Data(); 8424 8425 if (archivedOriginInfo->mOriginNoSuffix == aPrefix.OriginNoSuffix()) { 8426 iter.Remove(); 8427 } 8428 } 8429 } 8430 8431 void operator()(const Pattern& aPattern) { 8432 for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) { 8433 const auto& archivedOriginInfo = iter.Data(); 8434 8435 if (aPattern.GetPattern().Matches( 8436 archivedOriginInfo->mOriginAttributes)) { 8437 iter.Remove(); 8438 } 8439 } 8440 } 8441 8442 void operator()(const Null& aNull) { mHashtable->Clear(); } 8443 }; 8444 8445 mData.match(Matcher(aHashtable)); 8446 } 8447 8448 /******************************************************************************* 8449 * QuotaClient 8450 ******************************************************************************/ 8451 8452 QuotaClient* QuotaClient::sInstance = nullptr; 8453 8454 QuotaClient::QuotaClient() 8455 : mShadowDatabaseMutex("LocalStorage mShadowDatabaseMutex") { 8456 AssertIsOnBackgroundThread(); 8457 MOZ_ASSERT(!sInstance, "We expect this to be a singleton!"); 8458 8459 sInstance = this; 8460 } 8461 8462 QuotaClient::~QuotaClient() { 8463 AssertIsOnBackgroundThread(); 8464 MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!"); 8465 8466 sInstance = nullptr; 8467 } 8468 8469 mozilla::dom::quota::Client::Type QuotaClient::GetType() { 8470 return QuotaClient::LS; 8471 } 8472 8473 Result<UsageInfo, nsresult> QuotaClient::InitOrigin( 8474 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 8475 const AtomicBool& aCanceled) { 8476 AssertIsOnIOThread(); 8477 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT); 8478 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType); 8479 8480 QuotaManager* quotaManager = QuotaManager::Get(); 8481 MOZ_ASSERT(quotaManager); 8482 8483 QM_TRY_INSPECT(const auto& directory, 8484 quotaManager->GetOriginDirectory(aOriginMetadata)); 8485 8486 MOZ_ASSERT(directory); 8487 8488 QM_TRY(MOZ_TO_RESULT( 8489 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME)))); 8490 8491 #ifdef DEBUG 8492 { 8493 QM_TRY_INSPECT(const bool& exists, 8494 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists)); 8495 MOZ_ASSERT(exists); 8496 } 8497 #endif 8498 8499 QM_TRY_INSPECT(const auto& directoryPath, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 8500 nsString, directory, GetPath)); 8501 8502 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(directoryPath)); 8503 8504 // XXX Try to make usageFileExists const 8505 QM_TRY_UNWRAP(bool usageFileExists, ExistsAsFile(*usageFile)); 8506 8507 QM_TRY_INSPECT(const auto& usageJournalFile, 8508 GetUsageJournalFile(directoryPath)); 8509 8510 QM_TRY_INSPECT(const bool& usageJournalFileExists, 8511 ExistsAsFile(*usageJournalFile)); 8512 8513 if (usageJournalFileExists) { 8514 if (usageFileExists) { 8515 QM_TRY(MOZ_TO_RESULT(usageFile->Remove(false))); 8516 8517 usageFileExists = false; 8518 } 8519 8520 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false))); 8521 } 8522 8523 QM_TRY_INSPECT(const auto& file, 8524 CloneFileAndAppend(*directory, kDataFileName)); 8525 8526 QM_TRY_INSPECT(const bool& fileExists, ExistsAsFile(*file)); 8527 8528 QM_TRY_INSPECT( 8529 const UsageInfo& res, 8530 ([fileExists, usageFileExists, &file, &usageFile, &usageJournalFile, 8531 &aOriginMetadata]() -> Result<UsageInfo, nsresult> { 8532 if (fileExists) { 8533 QM_TRY_RETURN(QM_OR_ELSE_WARN( 8534 // Expression. To simplify control flow, we call LoadUsageFile 8535 // unconditionally here, even though it will necessarily fail if 8536 // usageFileExists is false. 8537 LoadUsageFile(*usageFile), 8538 // Fallback. 8539 ([&file, &usageFile, &usageJournalFile, &aOriginMetadata]( 8540 const nsresult) -> Result<UsageInfo, nsresult> { 8541 QM_TRY_INSPECT( 8542 const auto& connection, 8543 CreateStorageConnectionWithRecovery( 8544 *file, *usageFile, aOriginMetadata.mOrigin, [] {})); 8545 8546 QM_TRY_INSPECT(const int64_t& usage, 8547 GetUsage(*connection, 8548 /* aArchivedOriginScope */ nullptr)); 8549 8550 QM_TRY(MOZ_TO_RESULT( 8551 UpdateUsageFile(usageFile, usageJournalFile, usage))); 8552 8553 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false))); 8554 8555 MOZ_ASSERT(usage >= 0); 8556 return UsageInfo{DatabaseUsageType(Some(uint64_t(usage)))}; 8557 }))); 8558 } 8559 8560 if (usageFileExists) { 8561 QM_TRY(MOZ_TO_RESULT(usageFile->Remove(false))); 8562 } 8563 8564 return UsageInfo{}; 8565 }())); 8566 8567 // Report unknown files in debug builds, but don't fail, just warn (we don't 8568 // report unknown files in release builds because that requires extra 8569 // scanning of the directory which would slow down entire initialization for 8570 // little benefit). 8571 8572 #ifdef DEBUG 8573 QM_TRY(CollectEachFileAtomicCancelable( 8574 *directory, aCanceled, 8575 [](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> { 8576 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file)); 8577 8578 switch (dirEntryKind) { 8579 case nsIFileKind::ExistsAsDirectory: 8580 (void)WARN_IF_FILE_IS_UNKNOWN(*file); 8581 break; 8582 8583 case nsIFileKind::ExistsAsFile: { 8584 QM_TRY_INSPECT( 8585 const auto& leafName, 8586 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName)); 8587 8588 if (leafName.Equals(kDataFileName) || 8589 leafName.Equals(kJournalFileName) || 8590 leafName.Equals(kUsageFileName) || 8591 leafName.Equals(kUsageJournalFileName)) { 8592 return Ok{}; 8593 } 8594 8595 (void)WARN_IF_FILE_IS_UNKNOWN(*file); 8596 8597 break; 8598 } 8599 8600 case nsIFileKind::DoesNotExist: 8601 // Ignore files that got removed externally while iterating. 8602 break; 8603 } 8604 return Ok{}; 8605 })); 8606 #endif 8607 8608 return res; 8609 } 8610 8611 nsresult QuotaClient::InitOriginWithoutTracking( 8612 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 8613 const AtomicBool& aCanceled) { 8614 AssertIsOnIOThread(); 8615 8616 // This is called when a storage/permanent/${origin}/ls directory exists. Even 8617 // though this shouldn't happen with a "good" profile, we shouldn't return an 8618 // error here, since that would cause origin initialization to fail. We just 8619 // warn and otherwise ignore that. 8620 UNKNOWN_FILE_WARNING(NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME)); 8621 return NS_OK; 8622 } 8623 8624 Result<UsageInfo, nsresult> QuotaClient::GetUsageForOrigin( 8625 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 8626 const AtomicBool& aCanceled) { 8627 AssertIsOnIOThread(); 8628 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT); 8629 8630 // We can't open the database at this point, since it can be already used 8631 // by the connection thread. Use the cached value instead. 8632 8633 QuotaManager* quotaManager = QuotaManager::Get(); 8634 MOZ_ASSERT(quotaManager); 8635 8636 return quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT, 8637 aOriginMetadata, Client::LS); 8638 } 8639 8640 nsresult QuotaClient::AboutToClearOrigins( 8641 const PersistenceScope& aPersistenceScope, 8642 const OriginScope& aOriginScope) { 8643 AssertIsOnIOThread(); 8644 8645 // This method is not called when the clearing is triggered by the eviction 8646 // process. It's on purpose to avoid a problem with the origin access time 8647 // which can be described as follows: 8648 // When there's a storage pressure condition and quota manager starts 8649 // collecting origins for eviction, there can be an origin that hasn't been 8650 // touched for long time. However, the old implementation of local storage 8651 // could have touched the origin only recently and the new implementation 8652 // hasn't had a chance to create a new per origin database for it yet (the 8653 // data is still in the archive database), so the origin access time hasn't 8654 // been updated either. In the end, the origin would be evicted despite the 8655 // fact that there was recent local storage activity. 8656 // So this method clears the archived data and shadow database entries for 8657 // given origin scope, but only if it's a privacy-related origin clearing. 8658 8659 if (!aPersistenceScope.Matches( 8660 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_DEFAULT))) { 8661 return NS_OK; 8662 } 8663 8664 // There can be no data for the system principal in the archive or the shadow 8665 // database. This early return silences potential warnings caused by failed 8666 // `CreateAerchivedOriginScope` because it calls `GenerateOriginKey2` which 8667 // doesn't support the system principal. 8668 if (aOriginScope.IsOrigin() && 8669 aOriginScope.GetOrigin() == quota::GetOriginForChrome()) { 8670 return NS_OK; 8671 } 8672 8673 const bool shadowWrites = gShadowWrites; 8674 8675 QM_TRY_INSPECT(const auto& archivedOriginScope, 8676 CreateArchivedOriginScope(aOriginScope)); 8677 8678 if (!gArchivedOrigins) { 8679 QM_TRY(MOZ_TO_RESULT(LoadArchivedOrigins())); 8680 MOZ_ASSERT(gArchivedOrigins); 8681 } 8682 8683 const bool hasDataForRemoval = 8684 archivedOriginScope->HasMatches(gArchivedOrigins); 8685 8686 QuotaManager* quotaManager = QuotaManager::Get(); 8687 MOZ_ASSERT(quotaManager); 8688 8689 const nsString& basePath = quotaManager->GetBasePath(); 8690 8691 { 8692 MutexAutoLock shadowDatabaseLock(mShadowDatabaseMutex); 8693 8694 QM_TRY_INSPECT( 8695 const auto& connection, 8696 ([&basePath]() -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> { 8697 if (gInitializedShadowStorage) { 8698 QM_TRY_RETURN(GetShadowStorageConnection(basePath)); 8699 } 8700 8701 QM_TRY_UNWRAP(auto connection, 8702 CreateShadowStorageConnection(basePath)); 8703 8704 gInitializedShadowStorage = true; 8705 8706 return connection; 8707 }())); 8708 8709 { 8710 Maybe<AutoDatabaseAttacher> maybeAutoArchiveDatabaseAttacher; 8711 8712 if (hasDataForRemoval) { 8713 QM_TRY_INSPECT(const auto& archiveFile, 8714 GetArchiveFile(quotaManager->GetStoragePath())); 8715 8716 maybeAutoArchiveDatabaseAttacher.emplace( 8717 AutoDatabaseAttacher(connection, archiveFile, "archive"_ns)); 8718 8719 QM_TRY(MOZ_TO_RESULT(maybeAutoArchiveDatabaseAttacher->Attach())); 8720 } 8721 8722 if (archivedOriginScope->IsPattern()) { 8723 nsCOMPtr<mozIStorageFunction> function( 8724 new MatchFunction(archivedOriginScope->GetPattern())); 8725 8726 QM_TRY( 8727 MOZ_TO_RESULT(connection->CreateFunction("match"_ns, 2, function))); 8728 } 8729 8730 { 8731 QM_TRY_INSPECT(const auto& stmt, 8732 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 8733 nsCOMPtr<mozIStorageStatement>, connection, 8734 CreateStatement, "BEGIN IMMEDIATE;"_ns)); 8735 8736 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 8737 } 8738 8739 if (shadowWrites) { 8740 QM_TRY(MOZ_TO_RESULT( 8741 PerformDelete(connection, "main"_ns, archivedOriginScope.get()))); 8742 } 8743 8744 if (hasDataForRemoval) { 8745 QM_TRY(MOZ_TO_RESULT(PerformDelete(connection, "archive"_ns, 8746 archivedOriginScope.get()))); 8747 } 8748 8749 { 8750 QM_TRY_INSPECT(const auto& stmt, 8751 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 8752 nsCOMPtr<mozIStorageStatement>, connection, 8753 CreateStatement, "COMMIT;"_ns)); 8754 8755 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 8756 } 8757 8758 if (archivedOriginScope->IsPattern()) { 8759 QM_TRY(MOZ_TO_RESULT(connection->RemoveFunction("match"_ns))); 8760 } 8761 8762 if (hasDataForRemoval) { 8763 MOZ_ASSERT(maybeAutoArchiveDatabaseAttacher.isSome()); 8764 QM_TRY(MOZ_TO_RESULT(maybeAutoArchiveDatabaseAttacher->Detach())); 8765 8766 maybeAutoArchiveDatabaseAttacher.reset(); 8767 8768 MOZ_ASSERT(gArchivedOrigins); 8769 MOZ_ASSERT(archivedOriginScope->HasMatches(gArchivedOrigins)); 8770 archivedOriginScope->RemoveMatches(gArchivedOrigins); 8771 } 8772 } 8773 QM_TRY(MOZ_TO_RESULT(connection->Close())); 8774 } 8775 8776 if (aOriginScope.IsNull()) { 8777 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(basePath)); 8778 8779 QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false))); 8780 8781 gInitializedShadowStorage = false; 8782 } 8783 8784 return NS_OK; 8785 } 8786 8787 void QuotaClient::OnOriginClearCompleted( 8788 const OriginMetadata& aOriginMetadata) { 8789 AssertIsOnIOThread(); 8790 } 8791 8792 void QuotaClient::OnRepositoryClearCompleted(PersistenceType aPersistenceType) { 8793 AssertIsOnIOThread(); 8794 } 8795 8796 void QuotaClient::ReleaseIOThreadObjects() { 8797 AssertIsOnIOThread(); 8798 8799 gInitializationInfo = nullptr; 8800 8801 // Delete archived origins hashtable since QuotaManager clears the whole 8802 // storage directory including ls-archive.sqlite. 8803 8804 gArchivedOrigins = nullptr; 8805 } 8806 8807 void QuotaClient::AbortOperationsForLocks( 8808 const DirectoryLockIdTable& aDirectoryLockIds) { 8809 AssertIsOnBackgroundThread(); 8810 8811 // A PrepareDatastoreOp object could already acquire a directory lock for 8812 // the given origin. Its last step is creation of a Datastore object (which 8813 // will take ownership of the directory lock) and a PreparedDatastore object 8814 // which keeps the Datastore alive until a database actor is created. 8815 // We need to invalidate the PreparedDatastore object when it's created, 8816 // otherwise the Datastore object can block the origin clear operation for 8817 // long time. It's not a problem that we don't fail the PrepareDatastoreOp 8818 // immediatelly (avoiding the creation of the Datastore and PreparedDatastore 8819 // object). We will call RequestAllowToClose on the database actor once it's 8820 // created and the child actor will respond by sending AllowToClose which 8821 // will close the Datastore on the parent side (the closing releases the 8822 // directory lock). 8823 8824 InvalidatePrepareDatastoreOpsMatching( 8825 [&aDirectoryLockIds](const auto& prepareDatastoreOp) { 8826 // Check if the PrepareDatastoreOp holds an acquired DirectoryLock. 8827 // Origin clearing can't be blocked by this PrepareDatastoreOp if there 8828 // is no acquired DirectoryLock. If there is an acquired DirectoryLock, 8829 // check if the table contains the lock for the PrepareDatastoreOp. 8830 return IsLockForObjectAcquiredAndContainedInLockTable( 8831 prepareDatastoreOp, aDirectoryLockIds); 8832 }); 8833 8834 if (gPrivateDatastores) { 8835 gPrivateDatastores->RemoveIf([&aDirectoryLockIds](const auto& iter) { 8836 const auto& privateDatastore = iter.Data(); 8837 8838 // The PrivateDatastore::mDatastore member is not cleared until the 8839 // PrivateDatastore is destroyed. 8840 auto& datastore = privateDatastore->MutableDatastoreRef(); 8841 8842 // If the PrivateDatastore exists then it must be registered in 8843 // Datastore::mHasLivePrivateDatastore as well. The Datastore must have 8844 // a DirectoryLock if there is a registered PrivateDatastore. 8845 bool result = 8846 IsLockForObjectContainedInLockTable(datastore, aDirectoryLockIds); 8847 8848 // The datastore should be closed after removing from gPrivateDatastores 8849 // (and eventually unregistered from gDatastores, so a new private 8850 // browsing session won't see any data from a previous private browsing 8851 // session) but just in case something still keeps alive the datastore, 8852 // let's explicitly clear it here. 8853 if (result) { 8854 datastore.Clear(nullptr); 8855 } 8856 8857 return result; 8858 }); 8859 8860 if (!gPrivateDatastores->Count()) { 8861 gPrivateDatastores = nullptr; 8862 } 8863 } 8864 8865 InvalidatePreparedDatastoresMatching([&aDirectoryLockIds]( 8866 const auto& preparedDatastore) { 8867 // The PreparedDatastore::mDatastore member is not cleared until the 8868 // PreparedDatastore is destroyed. 8869 const auto& datastore = preparedDatastore.DatastoreRef(); 8870 8871 // If the PreparedDatastore exists then it must be registered in 8872 // Datastore::mPreparedDatastores as well. The Datastore must have a 8873 // DirectoryLock if there are registered PreparedDatastore objects. 8874 return IsLockForObjectContainedInLockTable(datastore, aDirectoryLockIds); 8875 }); 8876 8877 RequestAllowToCloseDatabasesMatching( 8878 [&aDirectoryLockIds](const auto& database) { 8879 const auto& maybeDatastore = database.MaybeDatastoreRef(); 8880 8881 // If the Database is registered in gLiveDatabases then it must have a 8882 // Datastore. 8883 MOZ_ASSERT(maybeDatastore.isSome()); 8884 8885 // If the Database is registered in gLiveDatabases then it must be 8886 // registered in Datastore::mDatabases as well. The Datastore must have 8887 // a DirectoryLock if there are registered Database objects. 8888 return IsLockForObjectContainedInLockTable(*maybeDatastore, 8889 aDirectoryLockIds); 8890 }); 8891 } 8892 8893 void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) { 8894 AssertIsOnBackgroundThread(); 8895 8896 // XXX Quota Manager should do the wrapping. 8897 Maybe<ContentParentId> contentParentId; 8898 8899 if (aContentParentId) { 8900 contentParentId = Some(aContentParentId); 8901 } 8902 8903 // XXX We could try to invalidate other objects here. 8904 8905 RequestAllowToCloseDatabasesMatching( 8906 [&contentParentId](const auto& database) { 8907 return database.ContentParentIdRef() == contentParentId; 8908 }); 8909 } 8910 8911 void QuotaClient::AbortAllOperations() { 8912 AssertIsOnBackgroundThread(); 8913 8914 InvalidatePrepareDatastoreOpsMatching([](const auto& prepareDatastoreOp) { 8915 return prepareDatastoreOp.MaybeDirectoryLockRef(); 8916 }); 8917 8918 if (gPrivateDatastores) { 8919 gPrivateDatastores = nullptr; 8920 } 8921 8922 InvalidatePreparedDatastoresMatching([](const auto&) { return true; }); 8923 8924 RequestAllowToCloseDatabasesMatching([](const auto&) { return true; }); 8925 } 8926 8927 void QuotaClient::StartIdleMaintenance() { AssertIsOnBackgroundThread(); } 8928 8929 void QuotaClient::StopIdleMaintenance() { AssertIsOnBackgroundThread(); } 8930 8931 void QuotaClient::InitiateShutdown() { 8932 // gPrepareDatastoreOps are short lived objects running a state machine. 8933 // The shutdown flag is checked between states, so we don't have to notify 8934 // all the objects here. 8935 // Allocation of a new PrepareDatastoreOp object is prevented once the 8936 // shutdown flag is set. 8937 // When the last PrepareDatastoreOp finishes, the gPrepareDatastoreOps array 8938 // is destroyed. 8939 8940 if (gPreparedDatastores) { 8941 gPreparedDatastores = nullptr; 8942 } 8943 8944 if (gPrivateDatastores) { 8945 gPrivateDatastores = nullptr; 8946 } 8947 8948 RequestAllowToCloseDatabasesMatching([](const auto&) { return true; }); 8949 8950 if (gPreparedObsevers) { 8951 gPreparedObsevers = nullptr; 8952 } 8953 } 8954 8955 bool QuotaClient::IsShutdownCompleted() const { 8956 // Don't have to check gPrivateDatastores and gPreparedDatastores since we 8957 // nulled it out in InitiateShutdown. 8958 return !gPrepareDatastoreOps && !gDatastores && !gLiveDatabases; 8959 } 8960 8961 void QuotaClient::ForceKillActors() { ForceKillAllDatabases(); } 8962 8963 nsCString QuotaClient::GetShutdownStatus() const { 8964 AssertIsOnBackgroundThread(); 8965 8966 nsCString data; 8967 8968 if (gPrepareDatastoreOps) { 8969 data.Append("PrepareDatastoreOperations: "); 8970 data.AppendInt(static_cast<uint32_t>(gPrepareDatastoreOps->Length())); 8971 data.Append(" ("); 8972 8973 // XXX What's the purpose of adding these to a hashtable before joining them 8974 // to the string? (Maybe this used to be an ordered container before???) 8975 nsTHashSet<nsCString> ids; 8976 std::transform(gPrepareDatastoreOps->cbegin(), gPrepareDatastoreOps->cend(), 8977 MakeInserter(ids), [](const auto& prepareDatastoreOp) { 8978 nsCString id; 8979 prepareDatastoreOp->Stringify(id); 8980 return id; 8981 }); 8982 8983 StringJoinAppend(data, ", "_ns, ids); 8984 8985 data.Append(")\n"); 8986 } 8987 8988 if (gDatastores) { 8989 data.Append("Datastores: "); 8990 data.AppendInt(gDatastores->Count()); 8991 data.Append(" ("); 8992 8993 // XXX It might be confusing to remove duplicates here, as the actual list 8994 // won't match the count then. 8995 nsTHashSet<nsCString> ids; 8996 std::transform(gDatastores->Values().cbegin(), gDatastores->Values().cend(), 8997 MakeInserter(ids), [](const auto& entry) { 8998 nsCString id; 8999 entry->Stringify(id); 9000 return id; 9001 }); 9002 9003 StringJoinAppend(data, ", "_ns, ids); 9004 9005 data.Append(")\n"); 9006 } 9007 9008 if (gLiveDatabases) { 9009 data.Append("LiveDatabases: "); 9010 data.AppendInt(static_cast<uint32_t>(gLiveDatabases->Length())); 9011 data.Append(" ("); 9012 9013 // XXX It might be confusing to remove duplicates here, as the actual list 9014 // won't match the count then. 9015 nsTHashSet<nsCString> ids; 9016 std::transform(gLiveDatabases->cbegin(), gLiveDatabases->cend(), 9017 MakeInserter(ids), [](const auto& database) { 9018 nsCString id; 9019 database->Stringify(id); 9020 return id; 9021 }); 9022 9023 StringJoinAppend(data, ", "_ns, ids); 9024 9025 data.Append(")\n"); 9026 } 9027 9028 return data; 9029 } 9030 9031 void QuotaClient::FinalizeShutdown() { 9032 // And finally, shutdown the connection thread. 9033 if (gConnectionThread) { 9034 gConnectionThread->Shutdown(); 9035 9036 gConnectionThread = nullptr; 9037 } 9038 9039 if (gDatabases) { 9040 nsTArray<RefPtr<Database>> databases; 9041 9042 for (const auto& database : *gDatabases) { 9043 databases.AppendElement(database); 9044 } 9045 9046 for (const auto& database : databases) { 9047 database->Close(); 9048 } 9049 } 9050 } 9051 9052 Result<UniquePtr<ArchivedOriginScope>, nsresult> 9053 QuotaClient::CreateArchivedOriginScope(const OriginScope& aOriginScope) { 9054 AssertIsOnIOThread(); 9055 9056 if (aOriginScope.IsOrigin()) { 9057 QM_TRY_INSPECT(const auto& principalInfo, 9058 QuotaManager::ParseOrigin(aOriginScope.GetOrigin())); 9059 9060 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]), 9061 GenerateOriginKey2(principalInfo)); 9062 9063 return ArchivedOriginScope::CreateFromOrigin(originAttrSuffix, originKey); 9064 } 9065 9066 if (aOriginScope.IsPrefix()) { 9067 QM_TRY_INSPECT(const auto& principalInfo, 9068 QuotaManager::ParseOrigin(aOriginScope.GetOriginNoSuffix())); 9069 9070 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]), 9071 GenerateOriginKey2(principalInfo)); 9072 9073 (void)originAttrSuffix; 9074 9075 return ArchivedOriginScope::CreateFromPrefix(originKey); 9076 } 9077 9078 if (aOriginScope.IsPattern()) { 9079 return ArchivedOriginScope::CreateFromPattern(aOriginScope.GetPattern()); 9080 } 9081 9082 MOZ_ASSERT(aOriginScope.IsNull()); 9083 9084 return ArchivedOriginScope::CreateFromNull(); 9085 } 9086 9087 nsresult QuotaClient::PerformDelete( 9088 mozIStorageConnection* aConnection, const nsACString& aSchemaName, 9089 ArchivedOriginScope* aArchivedOriginScope) const { 9090 AssertIsOnIOThread(); 9091 MOZ_ASSERT(aConnection); 9092 MOZ_ASSERT(aArchivedOriginScope); 9093 9094 QM_TRY_INSPECT( 9095 const auto& stmt, 9096 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9097 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement, 9098 "DELETE FROM "_ns + aSchemaName + ".webappsstore2"_ns + 9099 aArchivedOriginScope->GetBindingClause() + ";"_ns)); 9100 9101 QM_TRY(MOZ_TO_RESULT(aArchivedOriginScope->BindToStatement(stmt))); 9102 9103 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 9104 9105 return NS_OK; 9106 } 9107 9108 NS_IMPL_ISUPPORTS(QuotaClient::MatchFunction, mozIStorageFunction) 9109 9110 NS_IMETHODIMP 9111 QuotaClient::MatchFunction::OnFunctionCall( 9112 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { 9113 AssertIsOnIOThread(); 9114 MOZ_ASSERT(aFunctionArguments); 9115 MOZ_ASSERT(aResult); 9116 9117 QM_TRY_INSPECT(const auto& suffix, 9118 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9119 nsAutoCString, aFunctionArguments, GetUTF8String, 1)); 9120 9121 OriginAttributes oa; 9122 QM_TRY(OkIf(oa.PopulateFromSuffix(suffix)), NS_ERROR_FAILURE); 9123 9124 const bool result = mPattern.Matches(oa); 9125 9126 RefPtr<nsVariant> outVar(new nsVariant()); 9127 QM_TRY(MOZ_TO_RESULT(outVar->SetAsBool(result))); 9128 9129 outVar.forget(aResult); 9130 return NS_OK; 9131 } 9132 9133 /******************************************************************************* 9134 * AutoWriteTransaction 9135 ******************************************************************************/ 9136 9137 AutoWriteTransaction::AutoWriteTransaction(bool aShadowWrites) 9138 : mConnection(nullptr), mShadowWrites(aShadowWrites) { 9139 AssertIsOnGlobalConnectionThread(); 9140 9141 MOZ_COUNT_CTOR(mozilla::dom::AutoWriteTransaction); 9142 } 9143 9144 AutoWriteTransaction::~AutoWriteTransaction() { 9145 AssertIsOnGlobalConnectionThread(); 9146 9147 MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction); 9148 9149 if (mConnection) { 9150 QM_WARNONLY_TRY(QM_TO_RESULT(mConnection->RollbackWriteTransaction())); 9151 9152 if (mShadowWrites) { 9153 QM_WARNONLY_TRY(QM_TO_RESULT(DetachShadowDatabaseAndUnlock())); 9154 } 9155 } 9156 } 9157 9158 nsresult AutoWriteTransaction::Start(Connection* aConnection) { 9159 AssertIsOnGlobalConnectionThread(); 9160 MOZ_ASSERT(aConnection); 9161 MOZ_ASSERT(!mConnection); 9162 9163 if (mShadowWrites) { 9164 QM_TRY(MOZ_TO_RESULT(LockAndAttachShadowDatabase(aConnection))); 9165 } 9166 9167 QM_TRY(MOZ_TO_RESULT(aConnection->BeginWriteTransaction())); 9168 9169 mConnection = aConnection; 9170 9171 return NS_OK; 9172 } 9173 9174 nsresult AutoWriteTransaction::Commit() { 9175 AssertIsOnGlobalConnectionThread(); 9176 MOZ_ASSERT(mConnection); 9177 9178 QM_TRY(MOZ_TO_RESULT(mConnection->CommitWriteTransaction())); 9179 9180 if (mShadowWrites) { 9181 QM_TRY(MOZ_TO_RESULT(DetachShadowDatabaseAndUnlock())); 9182 } 9183 9184 mConnection = nullptr; 9185 9186 return NS_OK; 9187 } 9188 9189 nsresult AutoWriteTransaction::LockAndAttachShadowDatabase( 9190 Connection* aConnection) { 9191 AssertIsOnGlobalConnectionThread(); 9192 MOZ_ASSERT(aConnection); 9193 MOZ_ASSERT(!mConnection); 9194 MOZ_ASSERT(mShadowDatabaseLock.isNothing()); 9195 MOZ_ASSERT(mShadowWrites); 9196 9197 QuotaManager* quotaManager = QuotaManager::Get(); 9198 MOZ_ASSERT(quotaManager); 9199 9200 mShadowDatabaseLock.emplace( 9201 aConnection->GetQuotaClient()->ShadowDatabaseMutex()); 9202 9203 QM_TRY(MOZ_TO_RESULT(AttachShadowDatabase( 9204 quotaManager->GetBasePath(), &aConnection->MutableStorageConnection()))); 9205 9206 return NS_OK; 9207 } 9208 9209 nsresult AutoWriteTransaction::DetachShadowDatabaseAndUnlock() { 9210 AssertIsOnGlobalConnectionThread(); 9211 MOZ_ASSERT(mConnection); 9212 MOZ_ASSERT(mShadowDatabaseLock.isSome()); 9213 MOZ_ASSERT(mShadowWrites); 9214 9215 nsCOMPtr<mozIStorageConnection> storageConnection = 9216 mConnection->StorageConnection(); 9217 MOZ_ASSERT(storageConnection); 9218 9219 QM_TRY(MOZ_TO_RESULT(DetachShadowDatabase(storageConnection))); 9220 9221 mShadowDatabaseLock.reset(); 9222 9223 return NS_OK; 9224 } 9225 9226 } // namespace mozilla::dom