no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / localstorage / ActorsParent.cpp
blob2554628d7fdc1d8c9a8296529affa2b218e6b90f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ActorsParent.h"
9 // Local includes
10 #include "LSInitializationTypes.h"
11 #include "LSObject.h"
12 #include "ReportInternalError.h"
14 // Global includes
15 #include <cinttypes>
16 #include <cstdlib>
17 #include <cstring>
18 #include <new>
19 #include <tuple>
20 #include <type_traits>
21 #include <utility>
22 #include "ErrorList.h"
23 #include "MainThreadUtils.h"
24 #include "mozIStorageAsyncConnection.h"
25 #include "mozIStorageConnection.h"
26 #include "mozIStorageFunction.h"
27 #include "mozIStorageService.h"
28 #include "mozIStorageStatement.h"
29 #include "mozIStorageValueArray.h"
30 #include "mozStorageCID.h"
31 #include "mozStorageHelper.h"
32 #include "mozilla/Assertions.h"
33 #include "mozilla/Atomics.h"
34 #include "mozilla/Attributes.h"
35 #include "mozilla/DebugOnly.h"
36 #include "mozilla/Logging.h"
37 #include "mozilla/MacroForEach.h"
38 #include "mozilla/Maybe.h"
39 #include "mozilla/Monitor.h"
40 #include "mozilla/Mutex.h"
41 #include "mozilla/NotNull.h"
42 #include "mozilla/OriginAttributes.h"
43 #include "mozilla/Preferences.h"
44 #include "mozilla/RefPtr.h"
45 #include "mozilla/Result.h"
46 #include "mozilla/ResultExtensions.h"
47 #include "mozilla/ScopeExit.h"
48 #include "mozilla/Services.h"
49 #include "mozilla/StaticPrefs_dom.h"
50 #include "mozilla/StaticPtr.h"
51 #include "mozilla/StoragePrincipalHelper.h"
52 #include "mozilla/UniquePtr.h"
53 #include "mozilla/Unused.h"
54 #include "mozilla/Utf8.h"
55 #include "mozilla/Variant.h"
56 #include "mozilla/dom/ClientManagerService.h"
57 #include "mozilla/dom/FlippedOnce.h"
58 #include "mozilla/dom/LSSnapshot.h"
59 #include "mozilla/dom/LSValue.h"
60 #include "mozilla/dom/LSWriteOptimizer.h"
61 #include "mozilla/dom/LSWriteOptimizerImpl.h"
62 #include "mozilla/dom/LocalStorageCommon.h"
63 #include "mozilla/dom/Nullable.h"
64 #include "mozilla/dom/PBackgroundLSDatabase.h"
65 #include "mozilla/dom/PBackgroundLSDatabaseParent.h"
66 #include "mozilla/dom/PBackgroundLSObserverParent.h"
67 #include "mozilla/dom/PBackgroundLSRequestParent.h"
68 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
69 #include "mozilla/dom/PBackgroundLSSimpleRequestParent.h"
70 #include "mozilla/dom/PBackgroundLSSnapshotParent.h"
71 #include "mozilla/dom/SnappyUtils.h"
72 #include "mozilla/dom/StorageDBUpdater.h"
73 #include "mozilla/dom/StorageUtils.h"
74 #include "mozilla/dom/ipc/IdType.h"
75 #include "mozilla/dom/quota/CachingDatabaseConnection.h"
76 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
77 #include "mozilla/dom/quota/Client.h"
78 #include "mozilla/dom/quota/ClientImpl.h"
79 #include "mozilla/dom/quota/DirectoryLock.h"
80 #include "mozilla/dom/quota/FirstInitializationAttemptsImpl.h"
81 #include "mozilla/dom/quota/OriginScope.h"
82 #include "mozilla/dom/quota/PersistenceType.h"
83 #include "mozilla/dom/quota/QuotaCommon.h"
84 #include "mozilla/dom/quota/StorageHelpers.h"
85 #include "mozilla/dom/quota/QuotaManager.h"
86 #include "mozilla/dom/quota/QuotaObject.h"
87 #include "mozilla/dom/quota/ResultExtensions.h"
88 #include "mozilla/dom/quota/UsageInfo.h"
89 #include "mozilla/ipc/BackgroundChild.h"
90 #include "mozilla/ipc/BackgroundParent.h"
91 #include "mozilla/ipc/PBackgroundChild.h"
92 #include "mozilla/ipc/PBackgroundParent.h"
93 #include "mozilla/ipc/PBackgroundSharedTypes.h"
94 #include "mozilla/ipc/ProtocolUtils.h"
95 #include "mozilla/storage/Variant.h"
96 #include "nsBaseHashtable.h"
97 #include "nsCOMPtr.h"
98 #include "nsClassHashtable.h"
99 #include "nsTHashMap.h"
100 #include "nsDebug.h"
101 #include "nsError.h"
102 #include "nsHashKeys.h"
103 #include "nsIBinaryInputStream.h"
104 #include "nsIBinaryOutputStream.h"
105 #include "nsIDirectoryEnumerator.h"
106 #include "nsIEventTarget.h"
107 #include "nsIFile.h"
108 #include "nsIInputStream.h"
109 #include "nsIObjectInputStream.h"
110 #include "nsIObjectOutputStream.h"
111 #include "nsIObserver.h"
112 #include "nsIObserverService.h"
113 #include "nsIOutputStream.h"
114 #include "nsIRunnable.h"
115 #include "nsISerialEventTarget.h"
116 #include "nsISupports.h"
117 #include "nsIThread.h"
118 #include "nsITimer.h"
119 #include "nsIVariant.h"
120 #include "nsInterfaceHashtable.h"
121 #include "nsLiteralString.h"
122 #include "nsNetUtil.h"
123 #include "nsPointerHashKeys.h"
124 #include "nsPrintfCString.h"
125 #include "nsRefPtrHashtable.h"
126 #include "nsServiceManagerUtils.h"
127 #include "nsString.h"
128 #include "nsStringFlags.h"
129 #include "nsStringFwd.h"
130 #include "nsTArray.h"
131 #include "nsTHashSet.h"
132 #include "nsTLiteralString.h"
133 #include "nsTStringRepr.h"
134 #include "nsThreadUtils.h"
135 #include "nsVariant.h"
136 #include "nsXPCOM.h"
137 #include "nsXULAppAPI.h"
138 #include "nscore.h"
139 #include "prenv.h"
140 #include "prtime.h"
142 #define LS_LOG_TEST() MOZ_LOG_TEST(GetLocalStorageLogger(), LogLevel::Info)
143 #define LS_LOG(_args) MOZ_LOG(GetLocalStorageLogger(), LogLevel::Info, _args)
145 #if defined(MOZ_WIDGET_ANDROID)
146 # define LS_MOBILE
147 #endif
149 namespace mozilla::dom {
151 using namespace mozilla::dom::quota;
152 using namespace mozilla::dom::StorageUtils;
153 using namespace mozilla::ipc;
155 namespace {
157 struct ArchivedOriginInfo;
158 class ArchivedOriginScope;
159 class Connection;
160 class ConnectionThread;
161 class Database;
162 class Observer;
163 class PrepareDatastoreOp;
164 class PreparedDatastore;
165 class QuotaClient;
166 class Snapshot;
168 using ArchivedOriginHashtable =
169 nsClassHashtable<nsCStringHashKey, ArchivedOriginInfo>;
171 /*******************************************************************************
172 * Constants
173 ******************************************************************************/
175 // Major schema version. Bump for almost everything.
176 const uint32_t kMajorSchemaVersion = 5;
178 // Minor schema version. Should almost always be 0 (maybe bump on release
179 // branches if we have to).
180 const uint32_t kMinorSchemaVersion = 0;
182 // The schema version we store in the SQLite database is a (signed) 32-bit
183 // integer. The major version is left-shifted 4 bits so the max value is
184 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
185 static_assert(kMajorSchemaVersion <= 0xFFFFFFF,
186 "Major version needs to fit in 28 bits.");
187 static_assert(kMinorSchemaVersion <= 0xF,
188 "Minor version needs to fit in 4 bits.");
190 const int32_t kSQLiteSchemaVersion =
191 int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);
193 // Changing the value here will override the page size of new databases only.
194 // A journal mode change and VACUUM are needed to change existing databases, so
195 // the best way to do that is to use the schema version upgrade mechanism.
196 const uint32_t kSQLitePageSizeOverride =
197 #ifdef LS_MOBILE
198 512;
199 #else
200 1024;
201 #endif
203 static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
204 (kSQLitePageSizeOverride % 2 == 0 &&
205 kSQLitePageSizeOverride >= 512 &&
206 kSQLitePageSizeOverride <= 65536),
207 "Must be 0 (disabled) or a power of 2 between 512 and 65536!");
209 // Set to some multiple of the page size to grow the database in larger chunks.
210 const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
212 static_assert(kSQLiteGrowthIncrement >= 0 &&
213 kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
214 kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
215 "Must be 0 (disabled) or a positive multiple of the page size!");
218 * The database name for LocalStorage data in a per-origin directory.
220 constexpr auto kDataFileName = u"data.sqlite"_ns;
223 * The journal corresponding to kDataFileName. (We don't use WAL mode.)
224 * Currently only needed in QuotaClient::InitOrigin and only in DEBUG builds.
225 * See the corresponding comment in QuotaClient::InitOrigin.
227 #ifdef DEBUG
228 constexpr auto kJournalFileName = u"data.sqlite-journal"_ns;
229 #endif
232 * This file contains the current usage of the LocalStorage database as defined
233 * by the mozLength totals of all keys and values for the database, which
234 * differs from the actual size on disk. We store this value in a separate
235 * file as a cache so that we can initialize the QuotaClient faster.
236 * In the future, this file will be eliminated and the information will be
237 * stored in PROFILE/storage.sqlite or similar QuotaManager-wide storage.
239 * The file contains a binary verification cookie (32-bits) followed by the
240 * actual usage (64-bits).
242 constexpr auto kUsageFileName = u"usage"_ns;
245 * Following a QuotaManager idiom, this journal file's existence is a marker
246 * that the usage file was in the process of being updated and is currently
247 * invalid. This file is created prior to updating the usage file and only
248 * deleted after the usage file has been written and closed and any pending
249 * database transactions have been committed. Note that this idiom is expected
250 * to work if Gecko crashes in the middle of a write, but is not expected to be
251 * foolproof in the face of a system crash, as we do not explicitly attempt to
252 * fsync the directory containing the journal file.
254 * If the journal file is found to exist at origin initialization time, the
255 * usage will be re-computed from the current state of DATA_FILE_NAME.
257 constexpr auto kUsageJournalFileName = u"usage-journal"_ns;
259 static const uint32_t kUsageFileSize = 12;
260 static const uint32_t kUsageFileCookie = 0x420a420a;
263 * How long between the first moment we know we have data to be written on a
264 * `Connection` and when we should actually perform the write. This helps
265 * limit disk churn under silly usage patterns and is historically consistent
266 * with the previous, legacy implementation.
268 * Note that flushing happens downstream of Snapshot checkpointing and its
269 * batch mechanism which helps avoid wasteful IPC in the case of silly content
270 * code.
272 const uint32_t kFlushTimeoutMs = 5000;
274 const bool kDefaultShadowWrites = false;
275 const uint32_t kDefaultSnapshotPrefill = 16384;
276 const uint32_t kDefaultSnapshotGradualPrefill = 4096;
277 const bool kDefaultClientValidation = true;
279 * Should all mutations also be reflected in the "shadow" database, which is
280 * the legacy webappsstore.sqlite database. When this is enabled, users can
281 * downgrade their version of Firefox and/or otherwise fall back to the legacy
282 * implementation without loss of data. (Older versions of Firefox will
283 * recognize the presence of ls-archive.sqlite and purge it and the other
284 * LocalStorage directories so privacy is maintained.)
286 const char kShadowWritesPref[] = "dom.storage.shadow_writes";
288 * Byte budget for sending data down to the LSSnapshot instance when it is first
289 * created. If there is less data than this (measured by tallying the string
290 * length of the keys and values), all data is sent, otherwise partial data is
291 * sent. See `Snapshot`.
293 const char kSnapshotPrefillPref[] = "dom.storage.snapshot_prefill";
295 * When a specific value is requested by an LSSnapshot that is not already fully
296 * populated, gradual prefill is used. This preference specifies the number of
297 * bytes to be used to send values beyond the specific value that is requested.
298 * (The size of the explicitly requested value does not impact this preference.)
299 * Setting the value to 0 disables gradual prefill. Tests may set this value to
300 * -1 which is converted to INT_MAX in order to cause gradual prefill to send
301 * all values not previously sent.
303 const char kSnapshotGradualPrefillPref[] =
304 "dom.storage.snapshot_gradual_prefill";
306 const char kClientValidationPref[] = "dom.storage.client_validation";
309 * The amount of time a PreparedDatastore instance should stick around after a
310 * preload is triggered in order to give time for the page to use LocalStorage
311 * without triggering worst-case synchronous jank.
313 const uint32_t kPreparedDatastoreTimeoutMs = 20000;
316 * Cold storage for LocalStorage data extracted from webappsstore.sqlite at
317 * LSNG first-run that has not yet been migrated to its own per-origin directory
318 * by use.
320 * In other words, at first run, LSNG copies the contents of webappsstore.sqlite
321 * into this database. As requests are made for that LocalStorage data, the
322 * contents are removed from this database and placed into per-origin QM
323 * storage. So the contents of this database are always old, unused
324 * LocalStorage data that we can potentially get rid of at some point in the
325 * future.
327 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
329 * The legacy LocalStorage database. Its contents are maintained as our
330 * "shadow" database so that LSNG can be disabled without loss of user data.
332 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
334 // Shadow database Write Ahead Log's maximum size is 512KB
335 const uint32_t kShadowMaxWALSize = 512 * 1024;
337 bool IsOnGlobalConnectionThread();
339 void AssertIsOnGlobalConnectionThread();
341 /*******************************************************************************
342 * SQLite functions
343 ******************************************************************************/
345 int32_t MakeSchemaVersion(uint32_t aMajorSchemaVersion,
346 uint32_t aMinorSchemaVersion) {
347 return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
350 nsCString GetArchivedOriginHashKey(const nsACString& aOriginSuffix,
351 const nsACString& aOriginNoSuffix) {
352 return aOriginSuffix + ":"_ns + aOriginNoSuffix;
355 nsresult CreateDataTable(mozIStorageConnection* aConnection) {
356 return aConnection->ExecuteSimpleSQL(
357 "CREATE TABLE data"
358 "( key TEXT PRIMARY KEY"
359 ", utf16_length INTEGER NOT NULL"
360 ", conversion_type INTEGER NOT NULL"
361 ", compression_type INTEGER NOT NULL"
362 ", last_access_time INTEGER NOT NULL DEFAULT 0"
363 ", value BLOB NOT NULL"
364 ");"_ns);
367 nsresult CreateTables(mozIStorageConnection* aConnection) {
368 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
369 MOZ_ASSERT(aConnection);
371 // Table `database`
372 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
373 "CREATE TABLE database"
374 "( origin TEXT NOT NULL"
375 ", usage INTEGER NOT NULL DEFAULT 0"
376 ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
377 ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
378 ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
379 ");"_ns)));
381 // Table `data`
382 QM_TRY(MOZ_TO_RESULT(CreateDataTable(aConnection)));
384 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kSQLiteSchemaVersion)));
386 return NS_OK;
389 nsresult UpgradeSchemaFrom1_0To2_0(mozIStorageConnection* aConnection) {
390 AssertIsOnIOThread();
391 MOZ_ASSERT(aConnection);
393 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
394 "ALTER TABLE database ADD COLUMN usage INTEGER NOT NULL DEFAULT 0;"_ns)));
396 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
397 "UPDATE database "
398 "SET usage = (SELECT total(utf16Length(key) + utf16Length(value)) "
399 "FROM data);"_ns)));
401 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0))));
403 return NS_OK;
406 nsresult UpgradeSchemaFrom2_0To3_0(mozIStorageConnection* aConnection) {
407 AssertIsOnIOThread();
408 MOZ_ASSERT(aConnection);
410 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
411 "ALTER TABLE data ADD COLUMN utf16Length INTEGER NOT NULL DEFAULT 0;"_ns)));
413 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
414 "UPDATE data SET utf16Length = utf16Length(value);"_ns)));
416 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(3, 0))));
418 return NS_OK;
421 nsresult UpgradeSchemaFrom3_0To4_0(mozIStorageConnection* aConnection) {
422 AssertIsOnIOThread();
423 MOZ_ASSERT(aConnection);
425 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(4, 0))));
427 return NS_OK;
430 nsresult UpgradeSchemaFrom4_0To5_0(mozIStorageConnection* aConnection) {
431 AssertIsOnIOThread();
432 MOZ_ASSERT(aConnection);
434 // Recreate data table in new format following steps at
435 // https://www.sqlite.org/lang_altertable.html
436 // section "Making Other Kinds Of Table Schema Changes"
437 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
438 "CREATE TABLE migrated_data"
439 "( key TEXT PRIMARY KEY"
440 ", utf16_length INTEGER NOT NULL"
441 ", conversion_type INTEGER NOT NULL"
442 ", compression_type INTEGER NOT NULL"
443 ", last_access_time INTEGER NOT NULL DEFAULT 0"
444 ", value BLOB NOT NULL"
445 ");"_ns)));
447 // Reinsert old data, all legacy data is UTF8
448 static_assert(1u ==
449 static_cast<uint8_t>(LSValue::ConversionType::UTF16_UTF8));
450 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
451 "INSERT INTO migrated_data (key, utf16_length, conversion_type, "
452 "compression_type, last_access_time, value) "
453 "SELECT key, utf16Length, 1, compressed, lastAccessTime, value "
454 "FROM data;"_ns)));
456 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL("DROP TABLE data;"_ns)));
458 // Rename to data
459 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
460 "ALTER TABLE migrated_data RENAME TO data;"_ns)));
462 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeSchemaVersion(5, 0))));
464 return NS_OK;
467 nsresult SetDefaultPragmas(mozIStorageConnection* aConnection) {
468 MOZ_ASSERT(!NS_IsMainThread());
469 MOZ_ASSERT(aConnection);
471 QM_TRY(MOZ_TO_RESULT(
472 aConnection->ExecuteSimpleSQL("PRAGMA synchronous = FULL;"_ns)));
474 #ifndef LS_MOBILE
475 if (kSQLiteGrowthIncrement) {
476 // This is just an optimization so ignore the failure if the disk is
477 // currently too full.
478 QM_TRY(QM_OR_ELSE_WARN_IF(
479 // Expression.
480 MOZ_TO_RESULT(
481 aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement, ""_ns)),
482 // Predicate.
483 IsSpecificError<NS_ERROR_FILE_TOO_BIG>,
484 // Fallback.
485 ErrToDefaultOk<>));
487 #endif // LS_MOBILE
489 return NS_OK;
492 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateStorageConnection(
493 nsIFile& aDBFile, nsIFile& aUsageFile, const nsACString& aOrigin) {
494 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
496 // XXX Common logic should be refactored out of this method and
497 // cache::DBAction::OpenDBConnection, and maybe other similar functions.
499 QM_TRY_INSPECT(const auto& storageService,
500 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
501 MOZ_SELECT_OVERLOAD(do_GetService),
502 MOZ_STORAGE_SERVICE_CONTRACTID));
504 QM_TRY_UNWRAP(auto connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
505 nsCOMPtr<mozIStorageConnection>,
506 storageService, OpenDatabase, &aDBFile,
507 mozIStorageService::CONNECTION_DEFAULT));
509 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(connection)));
511 // Check to make sure that the database schema is correct.
512 // XXX Try to make schemaVersion const.
513 QM_TRY_UNWRAP(int32_t schemaVersion,
514 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion));
516 QM_TRY(OkIf(schemaVersion <= kSQLiteSchemaVersion), Err(NS_ERROR_FAILURE));
518 if (schemaVersion != kSQLiteSchemaVersion) {
519 const bool newDatabase = !schemaVersion;
521 if (newDatabase) {
522 // Set the page size first.
523 if (kSQLitePageSizeOverride) {
524 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(nsPrintfCString(
525 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
528 // We have to set the auto_vacuum mode before opening a transaction.
529 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL(
530 #ifdef LS_MOBILE
531 // Turn on full auto_vacuum mode to reclaim disk space on mobile
532 // devices (at the cost of some COMMIT speed).
533 "PRAGMA auto_vacuum = FULL;"_ns
534 #else
535 // Turn on incremental auto_vacuum mode on desktop builds.
536 "PRAGMA auto_vacuum = INCREMENTAL;"_ns
537 #endif
538 )));
541 bool vacuumNeeded = false;
543 if (newDatabase) {
544 mozStorageTransaction transaction(
545 connection,
546 /* aCommitOnComplete */ false,
547 mozIStorageConnection::TRANSACTION_IMMEDIATE);
549 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
551 QM_TRY(MOZ_TO_RESULT(CreateTables(connection)));
553 #ifdef DEBUG
555 QM_TRY_INSPECT(
556 const int32_t& schemaVersion,
557 MOZ_TO_RESULT_INVOKE_MEMBER(connection, GetSchemaVersion),
558 QM_ASSERT_UNREACHABLE);
560 MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
562 #endif
564 QM_TRY_INSPECT(
565 const auto& stmt,
566 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
567 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
568 "INSERT INTO database (origin) VALUES (:origin)"_ns));
570 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("origin"_ns, aOrigin)));
572 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
574 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
575 } else {
576 // This logic needs to change next time we change the schema!
577 static_assert(kSQLiteSchemaVersion == int32_t((5 << 4) + 0),
578 "Upgrade function needed due to schema version increase.");
580 while (schemaVersion != kSQLiteSchemaVersion) {
581 mozStorageTransaction transaction(
582 connection,
583 /* aCommitOnComplete */ false,
584 mozIStorageConnection::TRANSACTION_IMMEDIATE);
586 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
588 if (schemaVersion == MakeSchemaVersion(1, 0)) {
589 QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom1_0To2_0(connection)));
590 } else if (schemaVersion == MakeSchemaVersion(2, 0)) {
591 QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom2_0To3_0(connection)));
592 } else if (schemaVersion == MakeSchemaVersion(3, 0)) {
593 QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom3_0To4_0(connection)));
594 } else if (schemaVersion == MakeSchemaVersion(4, 0)) {
595 QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom4_0To5_0(connection)));
596 vacuumNeeded = true;
597 } else {
598 LS_WARNING(
599 "Unable to open LocalStorage database, no upgrade path is "
600 "available!");
601 return Err(NS_ERROR_FAILURE);
604 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
606 QM_TRY_UNWRAP(schemaVersion, MOZ_TO_RESULT_INVOKE_MEMBER(
607 connection, GetSchemaVersion));
610 MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
613 if (vacuumNeeded) {
614 QM_TRY(MOZ_TO_RESULT(connection->ExecuteSimpleSQL("VACUUM;"_ns)));
617 if (newDatabase) {
618 // Windows caches the file size, let's force it to stat the file again.
619 QM_TRY_INSPECT(const bool& exists,
620 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, Exists));
621 Unused << exists;
623 QM_TRY_INSPECT(const int64_t& fileSize,
624 MOZ_TO_RESULT_INVOKE_MEMBER(aDBFile, GetFileSize));
626 MOZ_ASSERT(fileSize > 0);
628 const PRTime vacuumTime = PR_Now();
629 MOZ_ASSERT(vacuumTime);
631 QM_TRY_INSPECT(
632 const auto& vacuumTimeStmt,
633 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<mozIStorageStatement>,
634 connection, CreateStatement,
635 "UPDATE database "
636 "SET last_vacuum_time = :time"
637 ", last_vacuum_size = :size;"_ns));
639 QM_TRY(MOZ_TO_RESULT(
640 vacuumTimeStmt->BindInt64ByName("time"_ns, vacuumTime)));
642 QM_TRY(
643 MOZ_TO_RESULT(vacuumTimeStmt->BindInt64ByName("size"_ns, fileSize)));
645 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt->Execute()));
649 return connection;
652 template <typename CorruptedFileHandler>
653 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
654 CreateStorageConnectionWithRecovery(
655 nsIFile& aDBFile, nsIFile& aUsageFile, const nsACString& aOrigin,
656 CorruptedFileHandler&& aCorruptedFileHandler) {
657 QM_TRY_RETURN(QM_OR_ELSE_WARN_IF(
658 // Expression.
659 CreateStorageConnection(aDBFile, aUsageFile, aOrigin),
660 // Predicate.
661 IsDatabaseCorruptionError,
662 // Fallback.
663 ([&aDBFile, &aUsageFile, &aOrigin,
664 &aCorruptedFileHandler](const nsresult rv)
665 -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
666 // Remove the usage file first (it might not exist at all due
667 // to corrupted state, which is ignored here).
669 // Usually we only use QM_OR_ELSE_LOG_VERBOSE(_IF) with Remove and
670 // NS_ERROR_FILE_NOT_FOUND check, but we're already in the rare case
671 // of corruption here, so the use of QM_OR_ELSE_WARN_IF is ok here.
672 QM_TRY(QM_OR_ELSE_WARN_IF(
673 // Expression.
674 MOZ_TO_RESULT(aUsageFile.Remove(false)),
675 // Predicate.
676 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
677 // Fallback.
678 ErrToDefaultOk<>));
680 // Call the corrupted file handler before trying to remove the
681 // database file, which might fail.
682 aCorruptedFileHandler();
684 // Nuke the database file.
685 QM_TRY(MOZ_TO_RESULT(aDBFile.Remove(false)));
687 QM_TRY_RETURN(CreateStorageConnection(aDBFile, aUsageFile, aOrigin));
688 })));
691 Result<nsCOMPtr<mozIStorageConnection>, nsresult> GetStorageConnection(
692 const nsAString& aDatabaseFilePath) {
693 AssertIsOnGlobalConnectionThread();
694 MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
695 MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, u".sqlite"_ns));
697 QM_TRY_INSPECT(const auto& databaseFile, QM_NewLocalFile(aDatabaseFilePath));
699 QM_TRY_INSPECT(const bool& exists,
700 MOZ_TO_RESULT_INVOKE_MEMBER(databaseFile, Exists));
702 QM_TRY(OkIf(exists), Err(NS_ERROR_FAILURE));
704 QM_TRY_INSPECT(const auto& ss,
705 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
706 MOZ_SELECT_OVERLOAD(do_GetService),
707 MOZ_STORAGE_SERVICE_CONTRACTID));
709 QM_TRY_UNWRAP(auto connection,
710 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
711 nsCOMPtr<mozIStorageConnection>, ss, OpenDatabase,
712 databaseFile, mozIStorageService::CONNECTION_DEFAULT));
714 QM_TRY(MOZ_TO_RESULT(SetDefaultPragmas(connection)));
716 return connection;
719 Result<nsCOMPtr<nsIFile>, nsresult> GetArchiveFile(
720 const nsAString& aStoragePath) {
721 AssertIsOnIOThread();
722 MOZ_ASSERT(!aStoragePath.IsEmpty());
724 QM_TRY_UNWRAP(auto archiveFile, QM_NewLocalFile(aStoragePath));
726 QM_TRY(MOZ_TO_RESULT(
727 archiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));
729 return archiveFile;
732 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
733 CreateArchiveStorageConnection(const nsAString& aStoragePath) {
734 AssertIsOnIOThread();
735 MOZ_ASSERT(!aStoragePath.IsEmpty());
737 QM_TRY_INSPECT(const auto& archiveFile, GetArchiveFile(aStoragePath));
739 // QuotaManager ensures this file always exists.
740 DebugOnly<bool> exists;
741 MOZ_ASSERT(NS_SUCCEEDED(archiveFile->Exists(&exists)));
742 MOZ_ASSERT(exists);
744 QM_TRY_INSPECT(const bool& isDirectory,
745 MOZ_TO_RESULT_INVOKE_MEMBER(archiveFile, IsDirectory));
747 if (isDirectory) {
748 LS_WARNING("ls-archive is not a file!");
749 return nsCOMPtr<mozIStorageConnection>{};
752 QM_TRY_INSPECT(const auto& ss,
753 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
754 MOZ_SELECT_OVERLOAD(do_GetService),
755 MOZ_STORAGE_SERVICE_CONTRACTID));
757 QM_TRY_UNWRAP(
758 auto connection,
759 QM_OR_ELSE_WARN_IF(
760 // Expression.
761 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
762 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
763 archiveFile, mozIStorageService::CONNECTION_DEFAULT),
764 // Predicate.
765 IsDatabaseCorruptionError,
766 // Fallback. Don't throw an error, leave a corrupted ls-archive
767 // database as it is.
768 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
770 if (connection) {
771 const nsresult rv = StorageDBUpdater::Update(connection);
772 if (NS_FAILED(rv)) {
773 // Don't throw an error, leave a non-updateable ls-archive database as
774 // it is.
775 return nsCOMPtr<mozIStorageConnection>{};
779 return connection;
782 Result<nsCOMPtr<nsIFile>, nsresult> GetShadowFile(const nsAString& aBasePath) {
783 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
784 MOZ_ASSERT(!aBasePath.IsEmpty());
786 QM_TRY_UNWRAP(auto archiveFile, QM_NewLocalFile(aBasePath));
788 QM_TRY(MOZ_TO_RESULT(
789 archiveFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME))));
791 return archiveFile;
794 nsresult SetShadowJournalMode(mozIStorageConnection* aConnection) {
795 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
796 MOZ_ASSERT(aConnection);
798 // Try enabling WAL mode. This can fail in various circumstances so we have to
799 // check the results here.
800 constexpr auto journalModeQueryStart = "PRAGMA journal_mode = "_ns;
801 constexpr auto journalModeWAL = "wal"_ns;
803 QM_TRY_INSPECT(const auto& stmt,
804 CreateAndExecuteSingleStepStatement(
805 *aConnection, journalModeQueryStart + journalModeWAL));
807 QM_TRY_INSPECT(const auto& journalMode,
808 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt,
809 GetUTF8String, 0));
811 if (journalMode.Equals(journalModeWAL)) {
812 // WAL mode successfully enabled. Set limits on its size here.
814 // Set the threshold for auto-checkpointing the WAL. We don't want giant
815 // logs slowing down us.
816 QM_TRY_INSPECT(const auto& stmt, CreateAndExecuteSingleStepStatement(
817 *aConnection, "PRAGMA page_size;"_ns));
819 QM_TRY_INSPECT(const int32_t& pageSize,
820 MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt32, 0));
822 MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
824 // Note there is a default journal_size_limit set by mozStorage.
825 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
826 "PRAGMA wal_autocheckpoint = "_ns +
827 IntToCString(static_cast<int32_t>(kShadowMaxWALSize / pageSize)))));
828 } else {
829 QM_TRY(MOZ_TO_RESULT(
830 aConnection->ExecuteSimpleSQL(journalModeQueryStart + "truncate"_ns)));
833 return NS_OK;
836 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateShadowStorageConnection(
837 const nsAString& aBasePath) {
838 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
839 MOZ_ASSERT(!aBasePath.IsEmpty());
841 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath));
843 QM_TRY_INSPECT(const auto& ss,
844 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
845 MOZ_SELECT_OVERLOAD(do_GetService),
846 MOZ_STORAGE_SERVICE_CONTRACTID));
848 QM_TRY_UNWRAP(
849 auto connection,
850 QM_OR_ELSE_WARN_IF(
851 // Expression.
852 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
853 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
854 shadowFile, mozIStorageService::CONNECTION_DEFAULT),
855 // Predicate.
856 IsDatabaseCorruptionError,
857 // Fallback.
858 ([&shadowFile, &ss](const nsresult rv)
859 -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
860 QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false)));
862 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
863 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
864 shadowFile, mozIStorageService::CONNECTION_DEFAULT));
865 })));
867 QM_TRY(MOZ_TO_RESULT(SetShadowJournalMode(connection)));
869 // XXX Depending on whether the *first* call to OpenUnsharedDatabase above
870 // failed, we (a) might or (b) might not be dealing with a fresh database
871 // here. This is confusing, since in a failure of case (a) we would do the
872 // same thing again. Probably, the control flow should be changed here so that
873 // it's clear we only delete & create a fresh database once. If we still have
874 // a failure then, we better give up. Or, if we really want to handle that,
875 // the number of 2 retries seems arbitrary, and we should better do this in
876 // some loop until a maximum number of retries is reached.
878 // Compare this with QuotaManager::CreateLocalStorageArchiveConnection, which
879 // actually tracks if the file was removed before, but it's also more
880 // complicated than it should be. Maybe these two methods can be merged (which
881 // would mean that a parameter must be added that indicates whether it's
882 // handling the shadow file or not).
883 QM_TRY(QM_OR_ELSE_WARN(
884 // Expression.
885 MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
886 // Fallback.
887 ([&connection, &shadowFile, &ss](const nsresult) -> Result<Ok, nsresult> {
888 QM_TRY(MOZ_TO_RESULT(connection->Close()));
889 QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false)));
891 QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
892 nsCOMPtr<mozIStorageConnection>, ss,
893 OpenUnsharedDatabase, shadowFile,
894 mozIStorageService::CONNECTION_DEFAULT));
896 QM_TRY(MOZ_TO_RESULT(SetShadowJournalMode(connection)));
898 QM_TRY(
899 MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection)));
901 return Ok{};
902 })));
904 return connection;
907 Result<nsCOMPtr<mozIStorageConnection>, nsresult> GetShadowStorageConnection(
908 const nsAString& aBasePath) {
909 AssertIsOnIOThread();
910 MOZ_ASSERT(!aBasePath.IsEmpty());
912 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath));
914 QM_TRY_INSPECT(const bool& exists,
915 MOZ_TO_RESULT_INVOKE_MEMBER(shadowFile, Exists));
917 QM_TRY(OkIf(exists), Err(NS_ERROR_FAILURE));
919 QM_TRY_INSPECT(const auto& ss,
920 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
921 MOZ_SELECT_OVERLOAD(do_GetService),
922 MOZ_STORAGE_SERVICE_CONTRACTID));
924 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
925 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, shadowFile,
926 mozIStorageService::CONNECTION_DEFAULT));
929 nsresult AttachShadowDatabase(const nsAString& aBasePath,
930 mozIStorageConnection* aConnection) {
931 AssertIsOnGlobalConnectionThread();
932 MOZ_ASSERT(!aBasePath.IsEmpty());
933 MOZ_ASSERT(aConnection);
935 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(aBasePath));
937 #ifdef DEBUG
939 QM_TRY_INSPECT(const bool& exists,
940 MOZ_TO_RESULT_INVOKE_MEMBER(shadowFile, Exists));
942 MOZ_ASSERT(exists);
944 #endif
946 QM_TRY_INSPECT(const auto& path, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
947 nsString, shadowFile, GetPath));
949 QM_TRY_INSPECT(const auto& stmt,
950 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
951 nsCOMPtr<mozIStorageStatement>, aConnection,
952 CreateStatement, "ATTACH DATABASE :path AS shadow;"_ns));
954 QM_TRY(MOZ_TO_RESULT(stmt->BindStringByName("path"_ns, path)));
956 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
958 return NS_OK;
961 nsresult DetachShadowDatabase(mozIStorageConnection* aConnection) {
962 AssertIsOnGlobalConnectionThread();
963 MOZ_ASSERT(aConnection);
965 QM_TRY(MOZ_TO_RESULT(
966 aConnection->ExecuteSimpleSQL("DETACH DATABASE shadow"_ns)));
968 return NS_OK;
971 Result<nsCOMPtr<nsIFile>, nsresult> GetUsageFile(
972 const nsAString& aDirectoryPath) {
973 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
974 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
976 QM_TRY_UNWRAP(auto usageFile, QM_NewLocalFile(aDirectoryPath));
978 QM_TRY(MOZ_TO_RESULT(usageFile->Append(kUsageFileName)));
980 return usageFile;
983 Result<nsCOMPtr<nsIFile>, nsresult> GetUsageJournalFile(
984 const nsAString& aDirectoryPath) {
985 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
986 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
988 QM_TRY_UNWRAP(auto usageJournalFile, QM_NewLocalFile(aDirectoryPath));
990 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Append(kUsageJournalFileName)));
992 return usageJournalFile;
995 // Checks if aFile exists and is a file. Returns true if it exists and is a
996 // file, false if it doesn't exist, and an error if it exists but isn't a file.
997 Result<bool, nsresult> ExistsAsFile(nsIFile& aFile) {
998 enum class ExistsAsFileResult { DoesNotExist, IsDirectory, IsFile };
1000 // This is an optimization to check both properties in one OS case, rather
1001 // than calling Exists first, and then IsDirectory. IsDirectory also checks
1002 // if the path exists. QM_OR_ELSE_WARN_IF is not used here since we just want
1003 // to log NS_ERROR_FILE_NOT_FOUND result and not spam the reports.
1004 QM_TRY_INSPECT(
1005 const auto& res,
1006 QM_OR_ELSE_LOG_VERBOSE_IF(
1007 // Expression.
1008 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, IsDirectory)
1009 .map([](const bool isDirectory) {
1010 return isDirectory ? ExistsAsFileResult::IsDirectory
1011 : ExistsAsFileResult::IsFile;
1013 // Predicate.
1014 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
1015 // Fallback.
1016 ErrToOk<ExistsAsFileResult::DoesNotExist>));
1018 QM_TRY(OkIf(res != ExistsAsFileResult::IsDirectory), Err(NS_ERROR_FAILURE));
1020 return res == ExistsAsFileResult::IsFile;
1023 nsresult UpdateUsageFile(nsIFile* aUsageFile, nsIFile* aUsageJournalFile,
1024 int64_t aUsage) {
1025 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
1026 MOZ_ASSERT(aUsageFile);
1027 MOZ_ASSERT(aUsageJournalFile);
1028 MOZ_ASSERT(aUsage >= 0);
1030 QM_TRY_INSPECT(const bool& usageJournalFileExists,
1031 ExistsAsFile(*aUsageJournalFile));
1032 if (!usageJournalFileExists) {
1033 QM_TRY(MOZ_TO_RESULT(
1034 aUsageJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
1037 QM_TRY_INSPECT(const auto& stream, NS_NewLocalFileOutputStream(aUsageFile));
1039 nsCOMPtr<nsIBinaryOutputStream> binaryStream =
1040 NS_NewObjectOutputStream(stream);
1042 QM_TRY(MOZ_TO_RESULT(binaryStream->Write32(kUsageFileCookie)));
1044 QM_TRY(MOZ_TO_RESULT(binaryStream->Write64(aUsage)));
1046 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
1047 QM_TRY(MOZ_TO_RESULT(stream->Flush()));
1048 #endif
1050 QM_TRY(MOZ_TO_RESULT(stream->Close()));
1052 return NS_OK;
1055 Result<UsageInfo, nsresult> LoadUsageFile(nsIFile& aUsageFile) {
1056 AssertIsOnIOThread();
1058 QM_TRY_INSPECT(const int64_t& fileSize,
1059 MOZ_TO_RESULT_INVOKE_MEMBER(aUsageFile, GetFileSize));
1061 QM_TRY(OkIf(fileSize == kUsageFileSize), Err(NS_ERROR_FILE_CORRUPTED));
1063 QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(&aUsageFile));
1065 QM_TRY_INSPECT(const auto& bufferedStream,
1066 NS_NewBufferedInputStream(stream.forget(), 16));
1068 const nsCOMPtr<nsIBinaryInputStream> binaryStream =
1069 NS_NewObjectInputStream(bufferedStream);
1071 QM_TRY_INSPECT(const uint32_t& cookie,
1072 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
1074 QM_TRY(OkIf(cookie == kUsageFileCookie), Err(NS_ERROR_FILE_CORRUPTED));
1076 QM_TRY_INSPECT(const uint64_t& usage,
1077 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
1079 return UsageInfo{DatabaseUsageType(Some(usage))};
1082 /*******************************************************************************
1083 * Non-actor class declarations
1084 ******************************************************************************/
1087 * Coalescing manipulation queue used by `Datastore`. Used by `Datastore` to
1088 * update `Datastore::mOrderedItems` efficiently/for code simplification.
1089 * (Datastore does not actually depend on the coalescing, as mutations are
1090 * applied atomically when a Snapshot Checkpoints, and with `Datastore::mValues`
1091 * being updated at the same time the mutations are applied to Datastore's
1092 * mWriteOptimizer.)
1094 class DatastoreWriteOptimizer final : public LSWriteOptimizer<LSValue> {
1095 public:
1096 void ApplyAndReset(nsTArray<LSItemInfo>& aOrderedItems);
1100 * Coalescing manipulation queue used by `Connection`. Used by `Connection` to
1101 * buffer and coalesce manipulations applied to the Datastore in batches by
1102 * Snapshot Checkpointing until flushed to disk.
1104 class ConnectionWriteOptimizer final : public LSWriteOptimizer<LSValue> {
1105 public:
1106 // Returns the usage as the success value.
1107 Result<int64_t, nsresult> Perform(Connection* aConnection,
1108 bool aShadowWrites);
1110 private:
1112 * Handlers for specific mutations. Each method knows how to `Perform` the
1113 * manipulation against a `Connection` and the "shadow" database (legacy
1114 * webappsstore.sqlite database that exists so LSNG can be disabled/safely
1115 * downgraded from.)
1117 nsresult PerformInsertOrUpdate(Connection* aConnection, bool aShadowWrites,
1118 const nsAString& aKey, const LSValue& aValue);
1120 nsresult PerformDelete(Connection* aConnection, bool aShadowWrites,
1121 const nsAString& aKey);
1123 nsresult PerformTruncate(Connection* aConnection, bool aShadowWrites);
1126 class DatastoreOperationBase : public Runnable {
1127 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
1128 nsresult mResultCode;
1129 Atomic<bool> mMayProceedOnNonOwningThread;
1130 bool mMayProceed;
1132 public:
1133 nsIEventTarget* OwningEventTarget() const {
1134 MOZ_ASSERT(mOwningEventTarget);
1136 return mOwningEventTarget;
1139 bool IsOnOwningThread() const {
1140 MOZ_ASSERT(mOwningEventTarget);
1142 bool current;
1143 return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
1144 current;
1147 void AssertIsOnOwningThread() const {
1148 MOZ_ASSERT(IsOnBackgroundThread());
1149 MOZ_ASSERT(IsOnOwningThread());
1152 nsresult ResultCode() const { return mResultCode; }
1154 void SetFailureCode(nsresult aErrorCode) {
1155 MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
1156 MOZ_ASSERT(NS_FAILED(aErrorCode));
1158 mResultCode = aErrorCode;
1161 void MaybeSetFailureCode(nsresult aErrorCode) {
1162 MOZ_ASSERT(NS_FAILED(aErrorCode));
1164 if (NS_SUCCEEDED(mResultCode)) {
1165 mResultCode = aErrorCode;
1169 void NoteComplete() {
1170 AssertIsOnOwningThread();
1172 mMayProceed = false;
1173 mMayProceedOnNonOwningThread = false;
1176 bool MayProceed() const {
1177 AssertIsOnOwningThread();
1179 return mMayProceed;
1182 // May be called on any thread, but you should call MayProceed() if you know
1183 // you're on the background thread because it is slightly faster.
1184 bool MayProceedOnNonOwningThread() const {
1185 return mMayProceedOnNonOwningThread;
1188 protected:
1189 DatastoreOperationBase()
1190 : Runnable("dom::DatastoreOperationBase"),
1191 mOwningEventTarget(GetCurrentSerialEventTarget()),
1192 mResultCode(NS_OK),
1193 mMayProceedOnNonOwningThread(true),
1194 mMayProceed(true) {}
1196 ~DatastoreOperationBase() override { MOZ_ASSERT(!mMayProceed); }
1199 class ConnectionDatastoreOperationBase : public DatastoreOperationBase {
1200 protected:
1201 RefPtr<Connection> mConnection;
1203 * This boolean flag is used by the CloseOp to avoid creating empty databases.
1205 const bool mEnsureStorageConnection;
1207 public:
1208 // This callback will be called on the background thread before releasing the
1209 // final reference to this request object. Subclasses may perform any
1210 // additional cleanup here but must always call the base class implementation.
1211 virtual void Cleanup();
1213 protected:
1214 ConnectionDatastoreOperationBase(Connection* aConnection,
1215 bool aEnsureStorageConnection = true);
1217 ~ConnectionDatastoreOperationBase();
1219 // Must be overridden in subclasses. Called on the target thread to allow the
1220 // subclass to perform necessary datastore operations. A successful return
1221 // value will trigger an OnSuccess callback on the background thread while
1222 // while a failure value will trigger an OnFailure callback.
1223 virtual nsresult DoDatastoreWork() = 0;
1225 // Methods that subclasses may implement.
1226 virtual void OnSuccess();
1228 virtual void OnFailure(nsresult aResultCode);
1230 private:
1231 void RunOnConnectionThread();
1233 void RunOnOwningThread();
1235 // Not to be overridden by subclasses.
1236 NS_DECL_NSIRUNNABLE
1239 class Connection final : public CachingDatabaseConnection {
1240 friend class ConnectionThread;
1242 class InitTemporaryOriginHelper;
1244 class FlushOp;
1245 class CloseOp;
1247 RefPtr<ConnectionThread> mConnectionThread;
1248 RefPtr<QuotaClient> mQuotaClient;
1249 nsCOMPtr<nsITimer> mFlushTimer;
1250 UniquePtr<ArchivedOriginScope> mArchivedOriginScope;
1251 ConnectionWriteOptimizer mWriteOptimizer;
1252 // XXX Consider changing this to ClientMetadata.
1253 const OriginMetadata mOriginMetadata;
1254 nsString mDirectoryPath;
1256 * Propagated from PrepareDatastoreOp. PrepareDatastoreOp may defer the
1257 * creation of the localstorage client directory and database on the
1258 * QuotaManager IO thread in its DatabaseWork method to
1259 * Connection::EnsureStorageConnection, in which case the method needs to know
1260 * it is responsible for taking those actions (without redundantly performing
1261 * the existence checks).
1263 const bool mDatabaseWasNotAvailable;
1264 bool mHasCreatedDatabase;
1265 bool mFlushScheduled;
1266 #ifdef DEBUG
1267 bool mInUpdateBatch;
1268 bool mFinished;
1269 #endif
1271 public:
1272 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)
1274 void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); }
1276 QuotaClient* GetQuotaClient() const {
1277 MOZ_ASSERT(mQuotaClient);
1279 return mQuotaClient;
1282 ArchivedOriginScope* GetArchivedOriginScope() const {
1283 return mArchivedOriginScope.get();
1286 const nsCString& Origin() const { return mOriginMetadata.mOrigin; }
1288 const nsString& DirectoryPath() const { return mDirectoryPath; }
1290 void GetFinishInfo(bool& aDatabaseWasNotAvailable,
1291 bool& aHasCreatedDatabase) const {
1292 AssertIsOnOwningThread();
1293 MOZ_ASSERT(mFinished);
1295 aDatabaseWasNotAvailable = mDatabaseWasNotAvailable;
1296 aHasCreatedDatabase = mHasCreatedDatabase;
1299 //////////////////////////////////////////////////////////////////////////////
1300 // Methods which can only be called on the owning thread.
1302 // This method is used to asynchronously execute a connection datastore
1303 // operation on the connection thread.
1304 void Dispatch(ConnectionDatastoreOperationBase* aOp);
1306 // This method is used to asynchronously close the storage connection on the
1307 // connection thread.
1308 void Close(nsIRunnable* aCallback);
1310 void SetItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta,
1311 bool aIsNewItem);
1313 void RemoveItem(const nsString& aKey, int64_t aDelta);
1315 void Clear(int64_t aDelta);
1317 void BeginUpdateBatch();
1319 void EndUpdateBatch();
1321 //////////////////////////////////////////////////////////////////////////////
1322 // Methods which can only be called on the connection thread.
1324 nsresult EnsureStorageConnection();
1326 mozIStorageConnection* StorageConnection() const {
1327 AssertIsOnGlobalConnectionThread();
1329 return &MutableStorageConnection();
1332 void CloseStorageConnection();
1334 nsresult BeginWriteTransaction();
1336 nsresult CommitWriteTransaction();
1338 nsresult RollbackWriteTransaction();
1340 private:
1341 // Only created by ConnectionThread.
1342 Connection(ConnectionThread* aConnectionThread,
1343 const OriginMetadata& aOriginMetadata,
1344 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope,
1345 bool aDatabaseWasNotAvailable);
1347 ~Connection();
1349 void ScheduleFlush();
1351 void Flush();
1353 static void FlushTimerCallback(nsITimer* aTimer, void* aClosure);
1357 * Helper to invoke EnsureTemporaryOriginIsInitialized on the QuotaManager IO
1358 * thread from the LocalStorage connection thread when creating a database
1359 * connection on demand. This is necessary because we attempt to defer the
1360 * creation of the origin directory and the database until absolutely needed,
1361 * but the directory creation and origin initialization must happen on the QM
1362 * IO thread for invariant reasons. (We can't just use a mutex because there
1363 * could be logic on the IO thread that also wants to deal with the same
1364 * origin, so we need to queue a runnable and wait our turn.)
1366 class Connection::InitTemporaryOriginHelper final : public Runnable {
1367 mozilla::Monitor mMonitor MOZ_UNANNOTATED;
1368 const OriginMetadata mOriginMetadata;
1369 nsString mOriginDirectoryPath;
1370 nsresult mIOThreadResultCode;
1371 bool mWaiting;
1373 public:
1374 explicit InitTemporaryOriginHelper(const OriginMetadata& aOriginMetadata)
1375 : Runnable("dom::localstorage::Connection::InitTemporaryOriginHelper"),
1376 mMonitor("InitTemporaryOriginHelper::mMonitor"),
1377 mOriginMetadata(aOriginMetadata),
1378 mIOThreadResultCode(NS_OK),
1379 mWaiting(true) {
1380 AssertIsOnGlobalConnectionThread();
1383 Result<nsString, nsresult> BlockAndReturnOriginDirectoryPath();
1385 private:
1386 ~InitTemporaryOriginHelper() = default;
1388 nsresult RunOnIOThread();
1390 NS_DECL_NSIRUNNABLE
1393 class Connection::FlushOp final : public ConnectionDatastoreOperationBase {
1394 ConnectionWriteOptimizer mWriteOptimizer;
1395 bool mShadowWrites;
1397 public:
1398 FlushOp(Connection* aConnection, ConnectionWriteOptimizer&& aWriteOptimizer);
1400 private:
1401 nsresult DoDatastoreWork() override;
1403 void Cleanup() override;
1406 class Connection::CloseOp final : public ConnectionDatastoreOperationBase {
1407 nsCOMPtr<nsIRunnable> mCallback;
1409 public:
1410 CloseOp(Connection* aConnection, nsIRunnable* aCallback)
1411 : ConnectionDatastoreOperationBase(aConnection,
1412 /* aEnsureStorageConnection */ false),
1413 mCallback(aCallback) {}
1415 private:
1416 nsresult DoDatastoreWork() override;
1418 void Cleanup() override;
1421 class ConnectionThread final {
1422 friend class Connection;
1424 nsCOMPtr<nsIThread> mThread;
1425 nsRefPtrHashtable<nsCStringHashKey, Connection> mConnections;
1427 public:
1428 ConnectionThread();
1430 void AssertIsOnOwningThread() const {
1431 NS_ASSERT_OWNINGTHREAD(ConnectionThread);
1434 bool IsOnConnectionThread();
1436 void AssertIsOnConnectionThread();
1438 already_AddRefed<Connection> CreateConnection(
1439 const OriginMetadata& aOriginMetadata,
1440 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope,
1441 bool aDatabaseWasNotAvailable);
1443 void Shutdown();
1445 NS_INLINE_DECL_REFCOUNTING(ConnectionThread)
1447 private:
1448 ~ConnectionThread();
1452 * Canonical state of Storage for an origin, containing all keys and their
1453 * values in the parent process. Specifically, this is the state that will
1454 * be handed out to freshly created Snapshots and that will be persisted to disk
1455 * when the Connection's flush completes. State is mutated in batches as
1456 * Snapshot instances Checkpoint their mutations locally accumulated in the
1457 * child LSSnapshots.
1459 class Datastore final
1460 : public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
1461 RefPtr<DirectoryLock> mDirectoryLock;
1462 RefPtr<Connection> mConnection;
1463 RefPtr<QuotaObject> mQuotaObject;
1464 nsCOMPtr<nsIRunnable> mCompleteCallback;
1466 * PrepareDatastoreOps register themselves with the Datastore at
1467 * and unregister in PrepareDatastoreOp::Cleanup.
1469 nsTHashSet<PrepareDatastoreOp*> mPrepareDatastoreOps;
1471 * PreparedDatastore instances register themselves with their associated
1472 * Datastore at construction time and unregister at destruction time. They
1473 * hang around for kPreparedDatastoreTimeoutMs in order to keep the Datastore
1474 * from closing itself via MaybeClose(), thereby giving the document enough
1475 * time to load and access LocalStorage.
1477 nsTHashSet<PreparedDatastore*> mPreparedDatastores;
1479 * A database is live (and in this hashtable) if it has a live LSDatabase
1480 * actor. There is at most one Database per origin per content process. Each
1481 * Database corresponds to an LSDatabase in its associated content process.
1483 nsTHashSet<Database*> mDatabases;
1485 * A database is active if it has a non-null `mSnapshot`. As long as there
1486 * are any active databases final deltas can't be calculated and
1487 * `UpdateUsage()` can't be invoked.
1489 nsTHashSet<Database*> mActiveDatabases;
1491 * Non-authoritative hashtable representation of mOrderedItems for efficient
1492 * lookup.
1494 nsTHashMap<nsStringHashKey, LSValue> mValues;
1496 * The authoritative ordered state of the Datastore; mValue also exists as an
1497 * unordered hashtable for efficient lookup.
1499 nsTArray<LSItemInfo> mOrderedItems;
1500 nsTArray<int64_t> mPendingUsageDeltas;
1501 DatastoreWriteOptimizer mWriteOptimizer;
1502 const OriginMetadata mOriginMetadata;
1503 const uint32_t mPrivateBrowsingId;
1504 int64_t mUsage;
1505 int64_t mUpdateBatchUsage;
1506 int64_t mSizeOfKeys;
1507 int64_t mSizeOfItems;
1508 bool mClosed;
1509 bool mInUpdateBatch;
1510 bool mHasLivePrivateDatastore;
1512 public:
1513 // Created by PrepareDatastoreOp.
1514 Datastore(const OriginMetadata& aOriginMetadata, uint32_t aPrivateBrowsingId,
1515 int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems,
1516 RefPtr<DirectoryLock>&& aDirectoryLock,
1517 RefPtr<Connection>&& aConnection,
1518 RefPtr<QuotaObject>&& aQuotaObject,
1519 nsTHashMap<nsStringHashKey, LSValue>& aValues,
1520 nsTArray<LSItemInfo>&& aOrderedItems);
1522 Maybe<DirectoryLock&> MaybeDirectoryLockRef() const {
1523 AssertIsOnBackgroundThread();
1525 return ToMaybeRef(mDirectoryLock.get());
1528 const nsCString& Origin() const { return mOriginMetadata.mOrigin; }
1530 uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; }
1532 bool IsPersistent() const {
1533 // Private-browsing is forbidden from touching disk, but
1534 // StorageAccess::eSessionScoped is allowed to touch disk because
1535 // QuotaManager's storage for such origins is wiped at shutdown.
1536 return mPrivateBrowsingId == 0;
1539 void Close();
1541 bool IsClosed() const {
1542 AssertIsOnBackgroundThread();
1544 return mClosed;
1547 void WaitForConnectionToComplete(nsIRunnable* aCallback);
1549 void NoteLivePrepareDatastoreOp(PrepareDatastoreOp* aPrepareDatastoreOp);
1551 void NoteFinishedPrepareDatastoreOp(PrepareDatastoreOp* aPrepareDatastoreOp);
1553 void NoteLivePrivateDatastore();
1555 void NoteFinishedPrivateDatastore();
1557 void NoteLivePreparedDatastore(PreparedDatastore* aPreparedDatastore);
1559 void NoteFinishedPreparedDatastore(PreparedDatastore* aPreparedDatastore);
1561 bool HasOtherProcessDatabases(Database* aDatabase);
1563 void NoteLiveDatabase(Database* aDatabase);
1565 void NoteFinishedDatabase(Database* aDatabase);
1567 void NoteActiveDatabase(Database* aDatabase);
1569 void NoteInactiveDatabase(Database* aDatabase);
1571 void GetSnapshotLoadInfo(const nsAString& aKey, bool& aAddKeyToUnknownItems,
1572 nsTHashtable<nsStringHashKey>& aLoadedItems,
1573 nsTArray<LSItemInfo>& aItemInfos,
1574 uint32_t& aNextLoadIndex,
1575 LSSnapshot::LoadState& aLoadState);
1577 uint32_t GetLength() const { return mValues.Count(); }
1579 const nsTArray<LSItemInfo>& GetOrderedItems() const { return mOrderedItems; }
1581 void GetItem(const nsAString& aKey, LSValue& aValue) const;
1583 void GetKeys(nsTArray<nsString>& aKeys) const;
1585 //////////////////////////////////////////////////////////////////////////////
1586 // Mutation Methods
1588 // These are only called during Snapshot::Checkpoint
1591 * Used by Snapshot::Checkpoint to set a key/value pair as part of an
1592 * explicit batch.
1594 void SetItem(Database* aDatabase, const nsString& aKey,
1595 const LSValue& aValue);
1597 void RemoveItem(Database* aDatabase, const nsString& aKey);
1599 void Clear(Database* aDatabase);
1601 void BeginUpdateBatch(int64_t aSnapshotUsage);
1603 int64_t EndUpdateBatch(int64_t aSnapshotPeakUsage);
1605 int64_t GetUsage() const { return mUsage; }
1607 int64_t AttemptToUpdateUsage(int64_t aMinSize, bool aInitial);
1609 bool HasOtherProcessObservers(Database* aDatabase);
1611 void NotifyOtherProcessObservers(Database* aDatabase,
1612 const nsString& aDocumentURI,
1613 const nsString& aKey,
1614 const LSValue& aOldValue,
1615 const LSValue& aNewValue);
1617 void NoteChangedObserverArray(const nsTArray<NotNull<Observer*>>& aObservers);
1619 void Stringify(nsACString& aResult) const;
1621 NS_INLINE_DECL_REFCOUNTING(Datastore)
1623 private:
1624 // Reference counted.
1625 ~Datastore();
1627 bool UpdateUsage(int64_t aDelta);
1629 void MaybeClose();
1631 void ConnectionClosedCallback();
1633 void CleanupMetadata();
1635 void NotifySnapshots(Database* aDatabase, const nsAString& aKey,
1636 const LSValue& aOldValue, bool aAffectsOrder);
1638 void NoteChangedDatabaseMap();
1641 class PrivateDatastore {
1642 const NotNull<RefPtr<Datastore>> mDatastore;
1644 public:
1645 explicit PrivateDatastore(MovingNotNull<RefPtr<Datastore>> aDatastore)
1646 : mDatastore(std::move(aDatastore)) {
1647 AssertIsOnBackgroundThread();
1649 mDatastore->NoteLivePrivateDatastore();
1652 ~PrivateDatastore() { mDatastore->NoteFinishedPrivateDatastore(); }
1654 const Datastore& DatastoreRef() const {
1655 AssertIsOnBackgroundThread();
1657 return *mDatastore;
1661 class PreparedDatastore {
1662 RefPtr<Datastore> mDatastore;
1663 nsCOMPtr<nsITimer> mTimer;
1664 const Maybe<ContentParentId> mContentParentId;
1665 // Strings share buffers if possible, so it's not a problem to duplicate the
1666 // origin here.
1667 const nsCString mOrigin;
1668 uint64_t mDatastoreId;
1669 bool mForPreload;
1670 bool mInvalidated;
1672 public:
1673 PreparedDatastore(Datastore* aDatastore,
1674 const Maybe<ContentParentId>& aContentParentId,
1675 const nsACString& aOrigin, uint64_t aDatastoreId,
1676 bool aForPreload)
1677 : mDatastore(aDatastore),
1678 mTimer(NS_NewTimer()),
1679 mContentParentId(aContentParentId),
1680 mOrigin(aOrigin),
1681 mDatastoreId(aDatastoreId),
1682 mForPreload(aForPreload),
1683 mInvalidated(false) {
1684 AssertIsOnBackgroundThread();
1685 MOZ_ASSERT(aDatastore);
1686 MOZ_ASSERT(mTimer);
1688 aDatastore->NoteLivePreparedDatastore(this);
1690 MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback(
1691 TimerCallback, this, kPreparedDatastoreTimeoutMs,
1692 nsITimer::TYPE_ONE_SHOT, "PreparedDatastore::TimerCallback"));
1695 ~PreparedDatastore() {
1696 MOZ_ASSERT(mDatastore);
1697 MOZ_ASSERT(mTimer);
1699 mTimer->Cancel();
1701 mDatastore->NoteFinishedPreparedDatastore(this);
1704 const Datastore& DatastoreRef() const {
1705 AssertIsOnBackgroundThread();
1706 MOZ_ASSERT(mDatastore);
1708 return *mDatastore;
1711 Datastore& MutableDatastoreRef() const {
1712 AssertIsOnBackgroundThread();
1713 MOZ_ASSERT(mDatastore);
1715 return *mDatastore;
1718 const Maybe<ContentParentId>& GetContentParentId() const {
1719 return mContentParentId;
1722 const nsCString& Origin() const { return mOrigin; }
1724 void Invalidate() {
1725 AssertIsOnBackgroundThread();
1727 mInvalidated = true;
1729 if (mForPreload) {
1730 mTimer->Cancel();
1732 MOZ_ALWAYS_SUCCEEDS(mTimer->InitWithNamedFuncCallback(
1733 TimerCallback, this, 0, nsITimer::TYPE_ONE_SHOT,
1734 "PreparedDatastore::TimerCallback"));
1738 bool IsInvalidated() const {
1739 AssertIsOnBackgroundThread();
1741 return mInvalidated;
1744 private:
1745 void Destroy();
1747 static void TimerCallback(nsITimer* aTimer, void* aClosure);
1750 /*******************************************************************************
1751 * Actor class declarations
1752 ******************************************************************************/
1754 class Database final
1755 : public PBackgroundLSDatabaseParent,
1756 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
1757 RefPtr<Datastore> mDatastore;
1758 Snapshot* mSnapshot;
1759 const PrincipalInfo mPrincipalInfo;
1760 const Maybe<ContentParentId> mContentParentId;
1761 // Strings share buffers if possible, so it's not a problem to duplicate the
1762 // origin here.
1763 nsCString mOrigin;
1764 uint32_t mPrivateBrowsingId;
1765 bool mAllowedToClose;
1766 bool mActorDestroyed;
1767 bool mRequestedAllowToClose;
1768 #ifdef DEBUG
1769 bool mActorWasAlive;
1770 #endif
1772 public:
1773 // Created in AllocPBackgroundLSDatabaseParent.
1774 Database(const PrincipalInfo& aPrincipalInfo,
1775 const Maybe<ContentParentId>& aContentParentId,
1776 const nsACString& aOrigin, uint32_t aPrivateBrowsingId);
1778 Datastore* GetDatastore() const {
1779 AssertIsOnBackgroundThread();
1780 return mDatastore;
1783 Maybe<Datastore&> MaybeDatastoreRef() const {
1784 AssertIsOnBackgroundThread();
1786 return ToMaybeRef(mDatastore.get());
1789 const PrincipalInfo& GetPrincipalInfo() const { return mPrincipalInfo; }
1791 bool IsOwnedByProcess(ContentParentId aContentParentId) const {
1792 return mContentParentId && mContentParentId.value() == aContentParentId;
1795 uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; }
1797 const nsCString& Origin() const { return mOrigin; }
1799 void SetActorAlive(Datastore* aDatastore);
1801 void RegisterSnapshot(Snapshot* aSnapshot);
1803 void UnregisterSnapshot(Snapshot* aSnapshot);
1805 Snapshot* GetSnapshot() const {
1806 AssertIsOnBackgroundThread();
1807 return mSnapshot;
1810 void RequestAllowToClose();
1812 void ForceKill();
1814 void Stringify(nsACString& aResult) const;
1816 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Database, override)
1818 private:
1819 // Reference counted.
1820 ~Database();
1822 void AllowToClose();
1824 // IPDL methods are only called by IPDL.
1825 void ActorDestroy(ActorDestroyReason aWhy) override;
1827 mozilla::ipc::IPCResult RecvDeleteMe() override;
1829 mozilla::ipc::IPCResult RecvAllowToClose() override;
1831 PBackgroundLSSnapshotParent* AllocPBackgroundLSSnapshotParent(
1832 const nsAString& aDocumentURI, const nsAString& aKey,
1833 const bool& aIncreasePeakUsage, const int64_t& aMinSize,
1834 LSSnapshotInitInfo* aInitInfo) override;
1836 mozilla::ipc::IPCResult RecvPBackgroundLSSnapshotConstructor(
1837 PBackgroundLSSnapshotParent* aActor, const nsAString& aDocumentURI,
1838 const nsAString& aKey, const bool& aIncreasePeakUsage,
1839 const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;
1841 bool DeallocPBackgroundLSSnapshotParent(
1842 PBackgroundLSSnapshotParent* aActor) override;
1846 * Attempts to capture the state of the underlying Datastore at the time of its
1847 * creation so run-to-completion semantics can be honored.
1849 * Rather than simply duplicate the contents of `DataStore::mValues` and
1850 * `Datastore::mOrderedItems` at the time of their creation, the Snapshot tracks
1851 * mutations to the Datastore as they happen, saving off the state of values as
1852 * they existed when the Snapshot was created. In other words, given an initial
1853 * Datastore state of { foo: 'bar', bar: 'baz' }, the Snapshot won't store those
1854 * values until it hears via `SaveItem` that "foo" is being over-written. At
1855 * that time, it will save off foo='bar' in mValues.
1857 * ## Quota Allocation ##
1859 * ## States ##
1862 class Snapshot final : public PBackgroundLSSnapshotParent {
1864 * The Database that owns this snapshot. There is a 1:1 relationship between
1865 * snapshots and databases.
1867 RefPtr<Database> mDatabase;
1868 RefPtr<Datastore> mDatastore;
1870 * The set of keys for which values have been sent to the child LSSnapshot.
1871 * Cleared once all values have been sent as indicated by
1872 * mLoadedItems.Count()==mTotalLength and therefore mLoadedAllItems should be
1873 * true. No requests should be received for keys already in this set, and
1874 * this is enforced by fatal IPC error (unless fuzzing).
1876 nsTHashtable<nsStringHashKey> mLoadedItems;
1878 * The set of keys for which a RecvLoadValueAndMoreItems request was received
1879 * but there was no such key, and so null was returned. The child LSSnapshot
1880 * will also cache these values, so redundant requests are also handled with
1881 * fatal process termination just like for mLoadedItems. Also cleared when
1882 * mLoadedAllItems becomes true because then the child can infer that all
1883 * other values must be null. (Note: this could also be done when
1884 * mLoadKeysReceived is true as a further optimization, but is not.)
1886 nsTHashSet<nsString> mUnknownItems;
1888 * Values that have changed in mDatastore as reported by SaveItem
1889 * notifications that are not yet known to the child LSSnapshot.
1891 * The naive way to snapshot the state of mDatastore would be to duplicate its
1892 * internal mValues at the time of our creation, but that is wasteful if few
1893 * changes are made to the Datastore's state. So we only track values that
1894 * are changed/evicted from the Datastore as they happen, as reported to us by
1895 * SaveItem notifications.
1897 nsTHashMap<nsStringHashKey, LSValue> mValues;
1899 * Latched state of mDatastore's keys during a SaveItem notification with
1900 * aAffectsOrder=true. The ordered keys needed to be saved off so that a
1901 * consistent ordering could be presented to the child LSSnapshot when it asks
1902 * for them via RecvLoadKeys.
1904 nsTArray<nsString> mKeys;
1905 nsString mDocumentURI;
1907 * The index used for restoring iteration over not yet sent key/value pairs to
1908 * the child LSSnapshot.
1910 uint32_t mNextLoadIndex;
1912 * The number of key/value pairs that were present in the Datastore at the
1913 * time the snapshot was created. Once we have sent this many values to the
1914 * child LSSnapshot, we can infer that it has received all of the keys/values
1915 * and set mLoadedAllItems to true and clear mLoadedItems and mUnknownItems.
1916 * Note that knowing the keys/values is not the same as knowing their ordering
1917 * and so mKeys may be retained.
1919 uint32_t mTotalLength;
1920 int64_t mUsage;
1921 int64_t mPeakUsage;
1923 * True if SaveItem has saved mDatastore's keys into mKeys because a SaveItem
1924 * notification with aAffectsOrder=true was received.
1926 bool mSavedKeys;
1927 bool mActorDestroyed;
1928 bool mFinishReceived;
1929 bool mLoadedReceived;
1931 * True if LSSnapshot's mLoadState should be LoadState::AllOrderedItems or
1932 * LoadState::AllUnorderedItems. It will be AllOrderedItems if the initial
1933 * snapshot contained all the data or if the state was AllOrderedKeys and
1934 * successive RecvLoadValueAndMoreItems requests have resulted in the
1935 * LSSnapshot being told all of the key/value pairs. It will be
1936 * AllUnorderedItems if the state was LoadState::Partial and successive
1937 * RecvLoadValueAndMoreItem requests got all the keys/values but the key
1938 * ordering was not retrieved.
1940 bool mLoadedAllItems;
1942 * True if LSSnapshot's mLoadState should be LoadState::AllOrderedItems or
1943 * AllOrderedKeys. This can occur because of the initial snapshot, or because
1944 * a RecvLoadKeys request was received.
1946 bool mLoadKeysReceived;
1947 bool mSentMarkDirty;
1950 * True if there are Database objects in other content processes. The value
1951 * never gets updated, we instead mark snapshots as dirty when Database
1952 * objects are added or removed. Marking snapshots as dirty forces creation
1953 * of new snapshots for new tasks.
1955 bool mHasOtherProcessDatabases;
1956 bool mHasOtherProcessObservers;
1958 public:
1959 // Created in AllocPBackgroundLSSnapshotParent.
1960 Snapshot(Database* aDatabase, const nsAString& aDocumentURI);
1962 void Init(nsTHashtable<nsStringHashKey>& aLoadedItems,
1963 nsTHashSet<nsString>&& aUnknownItems, uint32_t aNextLoadIndex,
1964 uint32_t aTotalLength, int64_t aUsage, int64_t aPeakUsage,
1965 LSSnapshot::LoadState aLoadState, bool aHasOtherProcessDatabases,
1966 bool aHasOtherProcessObservers) {
1967 AssertIsOnBackgroundThread();
1968 MOZ_ASSERT(aUsage >= 0);
1969 MOZ_ASSERT(aPeakUsage >= aUsage);
1970 MOZ_ASSERT_IF(aLoadState != LSSnapshot::LoadState::AllOrderedItems,
1971 aNextLoadIndex < aTotalLength);
1972 MOZ_ASSERT(mTotalLength == 0);
1973 MOZ_ASSERT(mUsage == -1);
1974 MOZ_ASSERT(mPeakUsage == -1);
1976 mLoadedItems.SwapElements(aLoadedItems);
1977 mUnknownItems = std::move(aUnknownItems);
1978 mNextLoadIndex = aNextLoadIndex;
1979 mTotalLength = aTotalLength;
1980 mUsage = aUsage;
1981 mPeakUsage = aPeakUsage;
1982 if (aLoadState == LSSnapshot::LoadState::AllOrderedKeys) {
1983 MOZ_ASSERT(mUnknownItems.Count() == 0);
1984 mLoadKeysReceived = true;
1985 } else if (aLoadState == LSSnapshot::LoadState::AllOrderedItems) {
1986 MOZ_ASSERT(mLoadedItems.Count() == 0);
1987 MOZ_ASSERT(mUnknownItems.Count() == 0);
1988 MOZ_ASSERT(mNextLoadIndex == mTotalLength);
1989 mLoadedReceived = true;
1990 mLoadedAllItems = true;
1991 mLoadKeysReceived = true;
1993 mHasOtherProcessDatabases = aHasOtherProcessDatabases;
1994 mHasOtherProcessObservers = aHasOtherProcessObservers;
1998 * Called via NotifySnapshots by Datastore whenever it is updating its
1999 * internal state so that snapshots can save off the state of a value at the
2000 * time of their creation.
2002 void SaveItem(const nsAString& aKey, const LSValue& aOldValue,
2003 bool aAffectsOrder);
2005 void MarkDirty();
2007 bool IsDirty() const {
2008 AssertIsOnBackgroundThread();
2010 return mSentMarkDirty;
2013 bool HasOtherProcessDatabases() const {
2014 AssertIsOnBackgroundThread();
2016 return mHasOtherProcessDatabases;
2019 bool HasOtherProcessObservers() const {
2020 AssertIsOnBackgroundThread();
2022 return mHasOtherProcessObservers;
2025 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Snapshot)
2027 private:
2028 // Reference counted.
2029 ~Snapshot();
2031 mozilla::ipc::IPCResult Checkpoint(nsTArray<LSWriteInfo>&& aWriteInfos);
2033 mozilla::ipc::IPCResult CheckpointAndNotify(
2034 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos);
2036 void Finish();
2038 // IPDL methods are only called by IPDL.
2039 void ActorDestroy(ActorDestroyReason aWhy) override;
2041 mozilla::ipc::IPCResult RecvDeleteMe() override;
2043 mozilla::ipc::IPCResult RecvAsyncCheckpoint(
2044 nsTArray<LSWriteInfo>&& aWriteInfos) override;
2046 mozilla::ipc::IPCResult RecvAsyncCheckpointAndNotify(
2047 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) override;
2049 mozilla::ipc::IPCResult RecvSyncCheckpoint(
2050 nsTArray<LSWriteInfo>&& aWriteInfos) override;
2052 mozilla::ipc::IPCResult RecvSyncCheckpointAndNotify(
2053 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) override;
2055 mozilla::ipc::IPCResult RecvAsyncFinish() override;
2057 mozilla::ipc::IPCResult RecvSyncFinish() override;
2059 mozilla::ipc::IPCResult RecvLoaded() override;
2061 mozilla::ipc::IPCResult RecvLoadValueAndMoreItems(
2062 const nsAString& aKey, LSValue* aValue,
2063 nsTArray<LSItemInfo>* aItemInfos) override;
2065 mozilla::ipc::IPCResult RecvLoadKeys(nsTArray<nsString>* aKeys) override;
2067 mozilla::ipc::IPCResult RecvIncreasePeakUsage(const int64_t& aMinSize,
2068 int64_t* aSize) override;
2071 class Observer final : public PBackgroundLSObserverParent {
2072 nsCString mOrigin;
2073 bool mActorDestroyed;
2075 public:
2076 // Created in AllocPBackgroundLSObserverParent.
2077 explicit Observer(const nsACString& aOrigin);
2079 const nsCString& Origin() const { return mOrigin; }
2081 void Observe(Database* aDatabase, const nsString& aDocumentURI,
2082 const nsString& aKey, const LSValue& aOldValue,
2083 const LSValue& aNewValue);
2085 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Observer)
2087 private:
2088 // Reference counted.
2089 ~Observer();
2091 // IPDL methods are only called by IPDL.
2092 void ActorDestroy(ActorDestroyReason aWhy) override;
2094 mozilla::ipc::IPCResult RecvDeleteMe() override;
2097 class LSRequestBase : public DatastoreOperationBase,
2098 public PBackgroundLSRequestParent {
2099 protected:
2100 enum class State {
2101 // Just created on the PBackground thread. Next step is StartingRequest.
2102 Initial,
2104 // Waiting to start/starting request on the PBackground thread. Next step is
2105 // either Nesting if a subclass needs to process more nested states or
2106 // SendingReadyMessage if a subclass doesn't need any nested processing.
2107 StartingRequest,
2109 // Doing nested processing.
2110 Nesting,
2112 // Waiting to send/sending the ready message on the PBackground thread. Next
2113 // step is WaitingForFinish.
2114 SendingReadyMessage,
2116 // Waiting for the finish message on the PBackground thread. Next step is
2117 // SendingResults.
2118 WaitingForFinish,
2120 // Waiting to send/sending results on the PBackground thread. Next step is
2121 // Completed.
2122 SendingResults,
2124 // All done.
2125 Completed
2128 const LSRequestParams mParams;
2129 Maybe<ContentParentId> mContentParentId;
2130 State mState;
2131 bool mWaitingForFinish;
2133 public:
2134 LSRequestBase(const LSRequestParams& aParams,
2135 const Maybe<ContentParentId>& aContentParentId);
2137 void Dispatch();
2139 void StringifyState(nsACString& aResult) const;
2141 virtual void Stringify(nsACString& aResult) const;
2143 virtual void Log();
2145 protected:
2146 ~LSRequestBase() override;
2148 virtual nsresult Start() = 0;
2150 virtual nsresult NestedRun();
2152 virtual void GetResponse(LSRequestResponse& aResponse) = 0;
2154 virtual void Cleanup() {}
2156 private:
2157 bool VerifyRequestParams();
2159 nsresult StartRequest();
2161 void SendReadyMessage();
2163 nsresult SendReadyMessageInternal();
2165 void Finish();
2167 void FinishInternal();
2169 void SendResults();
2171 protected:
2172 // Common nsIRunnable implementation that subclasses may not override.
2173 NS_IMETHOD
2174 Run() final;
2176 // IPDL methods.
2177 void ActorDestroy(ActorDestroyReason aWhy) override;
2179 private:
2180 mozilla::ipc::IPCResult RecvCancel() final;
2182 mozilla::ipc::IPCResult RecvFinish() final;
2185 class PrepareDatastoreOp
2186 : public LSRequestBase,
2187 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
2188 class LoadDataOp;
2190 class CompressFunction;
2191 class CompressionTypeFunction;
2193 enum class NestedState {
2194 // The nesting has not yet taken place. Next step is
2195 // CheckExistingOperations.
2196 BeforeNesting,
2198 // Checking if a prepare datastore operation is already running for given
2199 // origin on the PBackground thread. Next step is CheckClosingDatastore.
2200 CheckExistingOperations,
2202 // Checking if a datastore is closing the connection for given origin on
2203 // the PBackground thread. Next step is PreparationPending.
2204 CheckClosingDatastore,
2206 // Ensuring quota manager is created and opening directory on the
2207 // PBackground thread. Next step is either SendingResults if quota manager
2208 // is not available or DirectoryOpenPending if quota manager is available.
2209 // If a datastore already exists for given origin then the next state is
2210 // SendingReadyMessage.
2211 PreparationPending,
2213 // Waiting for directory open allowed on the PBackground thread. The next
2214 // step is either SendingReadyMessage if directory lock failed to acquire,
2215 // or DatabaseWorkOpen if directory lock is acquired.
2216 DirectoryOpenPending,
2218 // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
2219 // BeginLoadData.
2220 DatabaseWorkOpen,
2222 // Starting a load data operation on the PBackground thread. Next step is
2223 // DatabaseWorkLoadData.
2224 BeginLoadData,
2226 // Waiting to do/doing work on the connection thread. This involves waiting
2227 // for the LoadDataOp to do its work. Eventually the state will transition
2228 // to SendingReadyMessage.
2229 DatabaseWorkLoadData,
2231 // The nesting has completed.
2232 AfterNesting
2235 RefPtr<PrepareDatastoreOp> mDelayedOp;
2236 RefPtr<ClientDirectoryLock> mPendingDirectoryLock;
2237 RefPtr<DirectoryLock> mDirectoryLock;
2238 RefPtr<Connection> mConnection;
2239 RefPtr<Datastore> mDatastore;
2240 UniquePtr<ArchivedOriginScope> mArchivedOriginScope;
2241 LoadDataOp* mLoadDataOp;
2242 nsTHashMap<nsStringHashKey, LSValue> mValues;
2243 nsTArray<LSItemInfo> mOrderedItems;
2244 OriginMetadata mOriginMetadata;
2245 nsCString mMainThreadOrigin;
2246 nsString mDatabaseFilePath;
2247 uint32_t mPrivateBrowsingId;
2248 int64_t mUsage;
2249 int64_t mSizeOfKeys;
2250 int64_t mSizeOfItems;
2251 uint64_t mDatastoreId;
2252 NestedState mNestedState;
2253 const bool mForPreload;
2254 bool mDatabaseNotAvailable;
2255 // Set when the Datastore has been registered with gPrivateDatastores so that
2256 // it can be unregistered if an error is encountered in PrepareDatastoreOp.
2257 FlippedOnce<false> mPrivateDatastoreRegistered;
2258 // Set when the Datastore has been registered with gPreparedDatastores so
2259 // that it can be unregistered if an error is encountered in
2260 // PrepareDatastoreOp.
2261 FlippedOnce<false> mPreparedDatastoreRegistered;
2262 bool mInvalidated;
2264 #ifdef DEBUG
2265 int64_t mDEBUGUsage;
2266 #endif
2268 public:
2269 PrepareDatastoreOp(const LSRequestParams& aParams,
2270 const Maybe<ContentParentId>& aContentParentId);
2272 Maybe<DirectoryLock&> MaybeDirectoryLockRef() const {
2273 AssertIsOnBackgroundThread();
2275 return ToMaybeRef(mDirectoryLock.get());
2278 bool OriginIsKnown() const {
2279 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
2281 return !mOriginMetadata.mOrigin.IsEmpty();
2284 const nsCString& Origin() const {
2285 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
2286 MOZ_ASSERT(OriginIsKnown());
2288 return mOriginMetadata.mOrigin;
2291 void Invalidate() {
2292 AssertIsOnOwningThread();
2294 mInvalidated = true;
2297 void StringifyNestedState(nsACString& aResult) const;
2299 void Stringify(nsACString& aResult) const override;
2301 void Log() override;
2303 private:
2304 ~PrepareDatastoreOp() override;
2306 nsresult Start() override;
2308 nsresult CheckExistingOperations();
2310 nsresult CheckClosingDatastoreInternal();
2312 nsresult CheckClosingDatastore();
2314 nsresult BeginDatastorePreparationInternal();
2316 nsresult BeginDatastorePreparation();
2318 void SendToIOThread();
2320 nsresult DatabaseWork();
2322 nsresult DatabaseNotAvailable();
2324 nsresult EnsureDirectoryEntry(nsIFile* aEntry, bool aCreateIfNotExists,
2325 bool aDirectory,
2326 bool* aAlreadyExisted = nullptr);
2328 nsresult VerifyDatabaseInformation(mozIStorageConnection* aConnection);
2330 already_AddRefed<QuotaObject> GetQuotaObject();
2332 nsresult BeginLoadData();
2334 void FinishNesting();
2336 nsresult FinishNestingOnNonOwningThread();
2338 nsresult NestedRun() override;
2340 void GetResponse(LSRequestResponse& aResponse) override;
2342 void Cleanup() override;
2344 void ConnectionClosedCallback();
2346 void CleanupMetadata();
2348 // IPDL overrides.
2349 void ActorDestroy(ActorDestroyReason aWhy) override;
2351 void DirectoryLockAcquired(DirectoryLock* aLock);
2353 void DirectoryLockFailed();
2356 class PrepareDatastoreOp::LoadDataOp final
2357 : public ConnectionDatastoreOperationBase {
2358 RefPtr<PrepareDatastoreOp> mPrepareDatastoreOp;
2360 public:
2361 explicit LoadDataOp(PrepareDatastoreOp* aPrepareDatastoreOp)
2362 : ConnectionDatastoreOperationBase(aPrepareDatastoreOp->mConnection),
2363 mPrepareDatastoreOp(aPrepareDatastoreOp) {}
2365 private:
2366 ~LoadDataOp() = default;
2368 nsresult DoDatastoreWork() override;
2370 void OnSuccess() override;
2372 void OnFailure(nsresult aResultCode) override;
2374 void Cleanup() override;
2377 class PrepareDatastoreOp::CompressFunction final : public mozIStorageFunction {
2378 private:
2379 ~CompressFunction() = default;
2381 NS_DECL_ISUPPORTS
2382 NS_DECL_MOZISTORAGEFUNCTION
2385 class PrepareDatastoreOp::CompressionTypeFunction final
2386 : public mozIStorageFunction {
2387 private:
2388 ~CompressionTypeFunction() = default;
2390 NS_DECL_ISUPPORTS
2391 NS_DECL_MOZISTORAGEFUNCTION
2394 class PrepareObserverOp : public LSRequestBase {
2395 nsCString mOrigin;
2397 public:
2398 PrepareObserverOp(const LSRequestParams& aParams,
2399 const Maybe<ContentParentId>& aContentParentId);
2401 private:
2402 nsresult Start() override;
2404 void GetResponse(LSRequestResponse& aResponse) override;
2407 class LSSimpleRequestBase : public DatastoreOperationBase,
2408 public PBackgroundLSSimpleRequestParent {
2409 protected:
2410 enum class State {
2411 // Just created on the PBackground thread. Next step is StartingRequest.
2412 Initial,
2414 // Waiting to start/starting request on the PBackground thread. Next step is
2415 // SendingResults.
2416 StartingRequest,
2418 // Waiting to send/sending results on the PBackground thread. Next step is
2419 // Completed.
2420 SendingResults,
2422 // All done.
2423 Completed
2426 const LSSimpleRequestParams mParams;
2427 Maybe<ContentParentId> mContentParentId;
2428 State mState;
2430 public:
2431 LSSimpleRequestBase(const LSSimpleRequestParams& aParams,
2432 const Maybe<ContentParentId>& aContentParentId);
2434 void Dispatch();
2436 protected:
2437 ~LSSimpleRequestBase() override;
2439 virtual nsresult Start() = 0;
2441 virtual void GetResponse(LSSimpleRequestResponse& aResponse) = 0;
2443 private:
2444 bool VerifyRequestParams();
2446 nsresult StartRequest();
2448 void SendResults();
2450 // Common nsIRunnable implementation that subclasses may not override.
2451 NS_IMETHOD
2452 Run() final;
2454 // IPDL methods.
2455 void ActorDestroy(ActorDestroyReason aWhy) override;
2458 class PreloadedOp : public LSSimpleRequestBase {
2459 nsCString mOrigin;
2461 public:
2462 PreloadedOp(const LSSimpleRequestParams& aParams,
2463 const Maybe<ContentParentId>& aContentParentId);
2465 private:
2466 nsresult Start() override;
2468 void GetResponse(LSSimpleRequestResponse& aResponse) override;
2471 class GetStateOp : public LSSimpleRequestBase {
2472 nsCString mOrigin;
2474 public:
2475 GetStateOp(const LSSimpleRequestParams& aParams,
2476 const Maybe<ContentParentId>& aContentParentId);
2478 private:
2479 nsresult Start() override;
2481 void GetResponse(LSSimpleRequestResponse& aResponse) override;
2484 /*******************************************************************************
2485 * Other class declarations
2486 ******************************************************************************/
2488 struct ArchivedOriginInfo {
2489 OriginAttributes mOriginAttributes;
2490 nsCString mOriginNoSuffix;
2492 ArchivedOriginInfo(const OriginAttributes& aOriginAttributes,
2493 const nsACString& aOriginNoSuffix)
2494 : mOriginAttributes(aOriginAttributes),
2495 mOriginNoSuffix(aOriginNoSuffix) {}
2498 class ArchivedOriginScope {
2499 struct Origin {
2500 nsCString mOriginSuffix;
2501 nsCString mOriginNoSuffix;
2503 Origin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix)
2504 : mOriginSuffix(aOriginSuffix), mOriginNoSuffix(aOriginNoSuffix) {}
2506 const nsACString& OriginSuffix() const { return mOriginSuffix; }
2508 const nsACString& OriginNoSuffix() const { return mOriginNoSuffix; }
2511 struct Prefix {
2512 nsCString mOriginNoSuffix;
2514 explicit Prefix(const nsACString& aOriginNoSuffix)
2515 : mOriginNoSuffix(aOriginNoSuffix) {}
2517 const nsACString& OriginNoSuffix() const { return mOriginNoSuffix; }
2520 struct Pattern {
2521 UniquePtr<OriginAttributesPattern> mPattern;
2523 explicit Pattern(const OriginAttributesPattern& aPattern)
2524 : mPattern(MakeUnique<OriginAttributesPattern>(aPattern)) {}
2526 Pattern(const Pattern& aOther)
2527 : mPattern(MakeUnique<OriginAttributesPattern>(*aOther.mPattern)) {}
2529 Pattern(Pattern&& aOther) = default;
2531 const OriginAttributesPattern& GetPattern() const {
2532 MOZ_ASSERT(mPattern);
2533 return *mPattern;
2537 struct Null {};
2539 using DataType = Variant<Origin, Pattern, Prefix, Null>;
2541 DataType mData;
2543 public:
2544 static UniquePtr<ArchivedOriginScope> CreateFromOrigin(
2545 const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey);
2547 static UniquePtr<ArchivedOriginScope> CreateFromPrefix(
2548 const nsACString& aOriginKey);
2550 static UniquePtr<ArchivedOriginScope> CreateFromPattern(
2551 const OriginAttributesPattern& aPattern);
2553 static UniquePtr<ArchivedOriginScope> CreateFromNull();
2555 bool IsOrigin() const { return mData.is<Origin>(); }
2557 bool IsPrefix() const { return mData.is<Prefix>(); }
2559 bool IsPattern() const { return mData.is<Pattern>(); }
2561 bool IsNull() const { return mData.is<Null>(); }
2563 const nsACString& OriginSuffix() const {
2564 MOZ_ASSERT(IsOrigin());
2566 return mData.as<Origin>().OriginSuffix();
2569 const nsACString& OriginNoSuffix() const {
2570 MOZ_ASSERT(IsOrigin() || IsPrefix());
2572 if (IsOrigin()) {
2573 return mData.as<Origin>().OriginNoSuffix();
2575 return mData.as<Prefix>().OriginNoSuffix();
2578 const OriginAttributesPattern& GetPattern() const {
2579 MOZ_ASSERT(IsPattern());
2581 return mData.as<Pattern>().GetPattern();
2584 nsLiteralCString GetBindingClause() const;
2586 nsresult BindToStatement(mozIStorageStatement* aStatement) const;
2588 bool HasMatches(ArchivedOriginHashtable* aHashtable) const;
2590 void RemoveMatches(ArchivedOriginHashtable* aHashtable) const;
2592 private:
2593 // Move constructors
2594 explicit ArchivedOriginScope(const Origin&& aOrigin) : mData(aOrigin) {}
2596 explicit ArchivedOriginScope(const Pattern&& aPattern) : mData(aPattern) {}
2598 explicit ArchivedOriginScope(const Prefix&& aPrefix) : mData(aPrefix) {}
2600 explicit ArchivedOriginScope(const Null&& aNull) : mData(aNull) {}
2603 class QuotaClient final : public mozilla::dom::quota::Client {
2604 class MatchFunction;
2606 static QuotaClient* sInstance;
2608 Mutex mShadowDatabaseMutex MOZ_UNANNOTATED;
2610 public:
2611 QuotaClient();
2613 static QuotaClient* GetInstance() {
2614 AssertIsOnBackgroundThread();
2616 return sInstance;
2619 mozilla::Mutex& ShadowDatabaseMutex() {
2620 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
2622 return mShadowDatabaseMutex;
2625 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::QuotaClient, override)
2627 Type GetType() override;
2629 Result<UsageInfo, nsresult> InitOrigin(PersistenceType aPersistenceType,
2630 const OriginMetadata& aOriginMetadata,
2631 const AtomicBool& aCanceled) override;
2633 nsresult InitOriginWithoutTracking(PersistenceType aPersistenceType,
2634 const OriginMetadata& aOriginMetadata,
2635 const AtomicBool& aCanceled) override;
2637 Result<UsageInfo, nsresult> GetUsageForOrigin(
2638 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
2639 const AtomicBool& aCanceled) override;
2641 nsresult AboutToClearOrigins(
2642 const Nullable<PersistenceType>& aPersistenceType,
2643 const OriginScope& aOriginScope) override;
2645 void OnOriginClearCompleted(PersistenceType aPersistenceType,
2646 const nsACString& aOrigin) override;
2648 void OnRepositoryClearCompleted(PersistenceType aPersistenceType) override;
2650 void ReleaseIOThreadObjects() override;
2652 void AbortOperationsForLocks(
2653 const DirectoryLockIdTable& aDirectoryLockIds) override;
2655 void AbortOperationsForProcess(ContentParentId aContentParentId) override;
2657 void AbortAllOperations() override;
2659 void StartIdleMaintenance() override;
2661 void StopIdleMaintenance() override;
2663 private:
2664 ~QuotaClient() override;
2666 void InitiateShutdown() override;
2667 bool IsShutdownCompleted() const override;
2668 nsCString GetShutdownStatus() const override;
2669 void ForceKillActors() override;
2670 void FinalizeShutdown() override;
2672 Result<UniquePtr<ArchivedOriginScope>, nsresult> CreateArchivedOriginScope(
2673 const OriginScope& aOriginScope);
2675 nsresult PerformDelete(mozIStorageConnection* aConnection,
2676 const nsACString& aSchemaName,
2677 ArchivedOriginScope* aArchivedOriginScope) const;
2680 class QuotaClient::MatchFunction final : public mozIStorageFunction {
2681 OriginAttributesPattern mPattern;
2683 public:
2684 explicit MatchFunction(const OriginAttributesPattern& aPattern)
2685 : mPattern(aPattern) {}
2687 private:
2688 ~MatchFunction() = default;
2690 NS_DECL_ISUPPORTS
2691 NS_DECL_MOZISTORAGEFUNCTION
2694 /*******************************************************************************
2695 * Helper classes
2696 ******************************************************************************/
2698 class MOZ_STACK_CLASS AutoWriteTransaction final {
2699 Connection* mConnection;
2700 Maybe<MutexAutoLock> mShadowDatabaseLock;
2701 bool mShadowWrites;
2703 public:
2704 explicit AutoWriteTransaction(bool aShadowWrites);
2706 ~AutoWriteTransaction();
2708 nsresult Start(Connection* aConnection);
2710 nsresult Commit();
2712 private:
2713 nsresult LockAndAttachShadowDatabase(Connection* aConnection);
2715 nsresult DetachShadowDatabaseAndUnlock();
2718 /*******************************************************************************
2719 * Globals
2720 ******************************************************************************/
2722 #ifdef DEBUG
2723 bool gLocalStorageInitialized = false;
2724 #endif
2726 using PrepareDatastoreOpArray =
2727 nsTArray<NotNull<CheckedUnsafePtr<PrepareDatastoreOp>>>;
2729 StaticAutoPtr<PrepareDatastoreOpArray> gPrepareDatastoreOps;
2731 // nsCStringHashKey with disabled memmove
2732 class nsCStringHashKeyDM : public nsCStringHashKey {
2733 public:
2734 explicit nsCStringHashKeyDM(const nsCStringHashKey::KeyTypePointer aKey)
2735 : nsCStringHashKey(aKey) {}
2736 enum { ALLOW_MEMMOVE = false };
2739 // When CheckedUnsafePtr's checking is enabled, it's necessary to ensure that
2740 // the hashtable uses the copy constructor instead of memmove for moving entries
2741 // since memmove will break CheckedUnsafePtr in a memory-corrupting way.
2742 using DatastoreHashKey =
2743 std::conditional<DiagnosticAssertEnabled::value, nsCStringHashKeyDM,
2744 nsCStringHashKey>::type;
2746 using DatastoreHashtable =
2747 nsBaseHashtable<DatastoreHashKey, NotNull<CheckedUnsafePtr<Datastore>>,
2748 MovingNotNull<CheckedUnsafePtr<Datastore>>>;
2750 StaticAutoPtr<DatastoreHashtable> gDatastores;
2752 uint64_t gLastDatastoreId = 0;
2754 using PreparedDatastoreHashtable =
2755 nsClassHashtable<nsUint64HashKey, PreparedDatastore>;
2757 StaticAutoPtr<PreparedDatastoreHashtable> gPreparedDatastores;
2759 using PrivateDatastoreHashtable =
2760 nsClassHashtable<nsCStringHashKey, PrivateDatastore>;
2762 // Keeps Private Browsing Datastores alive until the private browsing session
2763 // is closed. This is necessary because LocalStorage Private Browsing data is
2764 // (currently) not written to disk and therefore needs to explicitly be kept
2765 // alive in memory so that if a user browses away from a site during a session
2766 // and then back to it that they will still have their data.
2768 // The entries are wrapped by PrivateDatastore instances which call
2769 // NoteLivePrivateDatastore and NoteFinishedPrivateDatastore which set and
2770 // clear mHasLivePrivateDatastore which inhibits MaybeClose() from closing the
2771 // datastore (which would discard the data) when there are no active windows
2772 // using LocalStorage for the origin.
2774 // The table is cleared when the Private Browsing session is closed, which will
2775 // cause NoteFinishedPrivateDatastore to be called on each Datastore which will
2776 // in turn call MaybeClose which should then discard the Datastore. Or in the
2777 // event of an (unlikely) race where the private browsing windows are still
2778 // being torn down, will cause the Datastore to be discarded when the last
2779 // window actually goes away.
2780 UniquePtr<PrivateDatastoreHashtable> gPrivateDatastores;
2782 using LiveDatabaseArray = nsTArray<NotNull<CheckedUnsafePtr<Database>>>;
2784 StaticAutoPtr<LiveDatabaseArray> gLiveDatabases;
2786 StaticRefPtr<ConnectionThread> gConnectionThread;
2788 uint64_t gLastObserverId = 0;
2790 using PreparedObserverHashtable = nsRefPtrHashtable<nsUint64HashKey, Observer>;
2792 StaticAutoPtr<PreparedObserverHashtable> gPreparedObsevers;
2794 using ObserverHashtable =
2795 nsClassHashtable<nsCStringHashKey, nsTArray<NotNull<Observer*>>>;
2797 StaticAutoPtr<ObserverHashtable> gObservers;
2799 Atomic<bool> gShadowWrites(kDefaultShadowWrites);
2800 Atomic<int32_t, Relaxed> gSnapshotPrefill(kDefaultSnapshotPrefill);
2801 Atomic<int32_t, Relaxed> gSnapshotGradualPrefill(
2802 kDefaultSnapshotGradualPrefill);
2803 Atomic<bool> gClientValidation(kDefaultClientValidation);
2805 using UsageHashtable = nsTHashMap<nsCStringHashKey, int64_t>;
2807 StaticAutoPtr<ArchivedOriginHashtable> gArchivedOrigins;
2809 // Can only be touched on the Quota Manager I/O thread.
2810 bool gInitializedShadowStorage = false;
2812 StaticAutoPtr<LSInitializationInfo> gInitializationInfo;
2814 bool IsOnGlobalConnectionThread() {
2815 MOZ_ASSERT(gConnectionThread);
2816 return gConnectionThread->IsOnConnectionThread();
2819 void AssertIsOnGlobalConnectionThread() {
2820 MOZ_ASSERT(gConnectionThread);
2821 gConnectionThread->AssertIsOnConnectionThread();
2824 already_AddRefed<Datastore> GetDatastore(const nsACString& aOrigin) {
2825 AssertIsOnBackgroundThread();
2827 if (gDatastores) {
2828 auto maybeDatastore = gDatastores->MaybeGet(aOrigin);
2829 if (maybeDatastore) {
2830 RefPtr<Datastore> result(std::move(*maybeDatastore).unwrapBasePtr());
2831 return result.forget();
2835 return nullptr;
2838 nsresult LoadArchivedOrigins() {
2839 AssertIsOnIOThread();
2840 MOZ_ASSERT(!gArchivedOrigins);
2842 QuotaManager* quotaManager = QuotaManager::Get();
2843 MOZ_ASSERT(quotaManager);
2845 QM_TRY_INSPECT(const auto& connection, CreateArchiveStorageConnection(
2846 quotaManager->GetStoragePath()));
2848 if (!connection) {
2849 gArchivedOrigins = new ArchivedOriginHashtable();
2850 return NS_OK;
2853 QM_TRY_INSPECT(
2854 const auto& stmt,
2855 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2856 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
2857 "SELECT DISTINCT originAttributes, originKey "
2858 "FROM webappsstore2;"_ns));
2860 auto archivedOrigins = MakeUnique<ArchivedOriginHashtable>();
2862 // XXX Actually, this could use a hashtable variant of
2863 // CollectElementsWhileHasResult
2864 QM_TRY(quota::CollectWhileHasResult(
2865 *stmt, [&archivedOrigins](auto& stmt) -> Result<Ok, nsresult> {
2866 QM_TRY_INSPECT(const auto& originSuffix,
2867 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2868 GetUTF8String, 0));
2869 QM_TRY_INSPECT(const auto& originNoSuffix,
2870 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2871 GetUTF8String, 1));
2873 const nsCString hashKey =
2874 GetArchivedOriginHashKey(originSuffix, originNoSuffix);
2876 OriginAttributes originAttributes;
2877 QM_TRY(OkIf(originAttributes.PopulateFromSuffix(originSuffix)),
2878 Err(NS_ERROR_FAILURE));
2880 archivedOrigins->InsertOrUpdate(
2881 hashKey,
2882 MakeUnique<ArchivedOriginInfo>(originAttributes, originNoSuffix));
2884 return Ok{};
2885 }));
2887 gArchivedOrigins = archivedOrigins.release();
2888 return NS_OK;
2891 Result<int64_t, nsresult> GetUsage(mozIStorageConnection& aConnection,
2892 ArchivedOriginScope* aArchivedOriginScope) {
2893 AssertIsOnIOThread();
2895 QM_TRY_INSPECT(
2896 const auto& stmt,
2897 ([aArchivedOriginScope,
2898 &aConnection]() -> Result<nsCOMPtr<mozIStorageStatement>, nsresult> {
2899 if (aArchivedOriginScope) {
2900 QM_TRY_RETURN(CreateAndExecuteSingleStepStatement<
2901 SingleStepResult::ReturnNullIfNoResult>(
2902 aConnection,
2903 "SELECT "
2904 "total(utf16Length(key) + utf16Length(value)) "
2905 "FROM webappsstore2 "
2906 "WHERE originKey = :originKey "
2907 "AND originAttributes = :originAttributes;"_ns,
2908 [aArchivedOriginScope](auto& stmt) -> Result<Ok, nsresult> {
2909 QM_TRY(MOZ_TO_RESULT(
2910 aArchivedOriginScope->BindToStatement(&stmt)));
2911 return Ok{};
2912 }));
2915 QM_TRY_RETURN(CreateAndExecuteSingleStepStatement<
2916 SingleStepResult::ReturnNullIfNoResult>(
2917 aConnection, "SELECT usage FROM database"_ns));
2918 }()));
2920 QM_TRY(OkIf(stmt), Err(NS_ERROR_FAILURE));
2922 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
2925 void ShadowWritesPrefChangedCallback(const char* aPrefName, void* aClosure) {
2926 MOZ_ASSERT(NS_IsMainThread());
2927 MOZ_ASSERT(!strcmp(aPrefName, kShadowWritesPref));
2928 MOZ_ASSERT(!aClosure);
2930 gShadowWrites = Preferences::GetBool(aPrefName, kDefaultShadowWrites);
2933 void SnapshotPrefillPrefChangedCallback(const char* aPrefName, void* aClosure) {
2934 MOZ_ASSERT(NS_IsMainThread());
2935 MOZ_ASSERT(!strcmp(aPrefName, kSnapshotPrefillPref));
2936 MOZ_ASSERT(!aClosure);
2938 int32_t snapshotPrefill =
2939 Preferences::GetInt(aPrefName, kDefaultSnapshotPrefill);
2941 // The magic -1 is for use only by tests.
2942 if (snapshotPrefill == -1) {
2943 snapshotPrefill = INT32_MAX;
2946 gSnapshotPrefill = snapshotPrefill;
2949 void SnapshotGradualPrefillPrefChangedCallback(const char* aPrefName,
2950 void* aClosure) {
2951 MOZ_ASSERT(NS_IsMainThread());
2952 MOZ_ASSERT(!strcmp(aPrefName, kSnapshotGradualPrefillPref));
2953 MOZ_ASSERT(!aClosure);
2955 int32_t snapshotGradualPrefill =
2956 Preferences::GetInt(aPrefName, kDefaultSnapshotGradualPrefill);
2958 // The magic -1 is for use only by tests.
2959 if (snapshotGradualPrefill == -1) {
2960 snapshotGradualPrefill = INT32_MAX;
2963 gSnapshotGradualPrefill = snapshotGradualPrefill;
2966 int64_t GetSnapshotPeakUsagePreincrement(bool aInitial) {
2967 return aInitial ? StaticPrefs::
2968 dom_storage_snapshot_peak_usage_initial_preincrement()
2969 : StaticPrefs::
2970 dom_storage_snapshot_peak_usage_gradual_preincrement();
2973 int64_t GetSnapshotPeakUsageReducedPreincrement(bool aInitial) {
2974 return aInitial
2975 ? StaticPrefs::
2976 dom_storage_snapshot_peak_usage_reduced_initial_preincrement()
2977 : StaticPrefs::
2978 dom_storage_snapshot_peak_usage_reduced_gradual_preincrement();
2981 void ClientValidationPrefChangedCallback(const char* aPrefName,
2982 void* aClosure) {
2983 MOZ_ASSERT(NS_IsMainThread());
2984 MOZ_ASSERT(!strcmp(aPrefName, kClientValidationPref));
2985 MOZ_ASSERT(!aClosure);
2987 gClientValidation = Preferences::GetBool(aPrefName, kDefaultClientValidation);
2990 template <typename Condition>
2991 void InvalidatePrepareDatastoreOpsMatching(const Condition& aCondition) {
2992 if (!gPrepareDatastoreOps) {
2993 return;
2996 for (const auto& prepareDatastoreOp : *gPrepareDatastoreOps) {
2997 if (aCondition(*prepareDatastoreOp)) {
2998 prepareDatastoreOp->Invalidate();
3003 template <typename Condition>
3004 void InvalidatePreparedDatastoresMatching(const Condition& aCondition) {
3005 if (!gPreparedDatastores) {
3006 return;
3009 for (const auto& preparedDatastore : gPreparedDatastores->Values()) {
3010 MOZ_ASSERT(preparedDatastore);
3012 if (aCondition(*preparedDatastore)) {
3013 preparedDatastore->Invalidate();
3018 template <typename Condition>
3019 nsTArray<RefPtr<Database>> CollectDatabasesMatching(Condition aCondition) {
3020 AssertIsOnBackgroundThread();
3022 if (!gLiveDatabases) {
3023 return nsTArray<RefPtr<Database>>{};
3026 nsTArray<RefPtr<Database>> databases;
3028 for (const auto& database : *gLiveDatabases) {
3029 if (aCondition(*database)) {
3030 databases.AppendElement(database.get());
3034 return databases;
3037 template <typename Condition>
3038 void RequestAllowToCloseDatabasesMatching(Condition aCondition) {
3039 AssertIsOnBackgroundThread();
3041 nsTArray<RefPtr<Database>> databases = CollectDatabasesMatching(aCondition);
3043 for (const auto& database : databases) {
3044 MOZ_ASSERT(database);
3046 database->RequestAllowToClose();
3050 void ForceKillAllDatabases() {
3051 AssertIsOnBackgroundThread();
3053 nsTArray<RefPtr<Database>> databases =
3054 CollectDatabasesMatching([](const auto&) { return true; });
3056 for (const auto& database : databases) {
3057 MOZ_ASSERT(database);
3059 database->ForceKill();
3063 bool VerifyPrincipalInfo(const PrincipalInfo& aPrincipalInfo,
3064 const PrincipalInfo& aStoragePrincipalInfo,
3065 bool aCheckClientPrincipal) {
3066 AssertIsOnBackgroundThread();
3068 if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
3069 return false;
3072 // Note that the client prinicpal could have a different spec than the node
3073 // principal but they should have the same origin. It's because the client
3074 // could be initialized when opening the initial about:blank document and pass
3075 // to the newly opened window and reuse over there if the new window has the
3076 // same origin as the initial about:blank document. But, the FilePath could be
3077 // different. Therefore, we have to ignore comparing the Spec of the
3078 // principals if we are verifying clinet principal here. Also, when
3079 // document.domain is set, client principal won't get it. So, we don't compare
3080 // domain for client princpal too.
3081 bool result = aCheckClientPrincipal
3082 ? StoragePrincipalHelper::
3083 VerifyValidClientPrincipalInfoForPrincipalInfo(
3084 aStoragePrincipalInfo, aPrincipalInfo)
3085 : StoragePrincipalHelper::
3086 VerifyValidStoragePrincipalInfoForPrincipalInfo(
3087 aStoragePrincipalInfo, aPrincipalInfo);
3088 if (NS_WARN_IF(!result)) {
3089 return false;
3092 return true;
3095 bool VerifyClientId(const Maybe<ContentParentId>& aContentParentId,
3096 const Maybe<PrincipalInfo>& aPrincipalInfo,
3097 const Maybe<nsID>& aClientId) {
3098 AssertIsOnBackgroundThread();
3100 if (gClientValidation) {
3101 if (NS_WARN_IF(aClientId.isNothing())) {
3102 return false;
3105 if (NS_WARN_IF(aPrincipalInfo.isNothing())) {
3106 return false;
3109 RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance();
3110 if (svc && NS_WARN_IF(!svc->HasWindow(
3111 aContentParentId, aPrincipalInfo.ref(), aClientId.ref()))) {
3112 return false;
3116 return true;
3119 bool VerifyOriginKey(const nsACString& aOriginKey,
3120 const PrincipalInfo& aPrincipalInfo) {
3121 AssertIsOnBackgroundThread();
3123 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]),
3124 GenerateOriginKey2(aPrincipalInfo), false);
3126 Unused << originAttrSuffix;
3128 QM_TRY(OkIf(originKey == aOriginKey), false,
3129 ([&originKey = originKey, &aOriginKey](const auto) {
3130 LS_WARNING("originKey (%s) doesn't match passed one (%s)!",
3131 originKey.get(), nsCString(aOriginKey).get());
3132 }));
3134 return true;
3137 LSInitializationInfo& MutableInitializationInfoRef(const CreateIfNonExistent&) {
3138 if (!gInitializationInfo) {
3139 gInitializationInfo = new LSInitializationInfo();
3141 return *gInitializationInfo;
3144 template <typename Func>
3145 auto ExecuteOriginInitialization(const nsACString& aOrigin,
3146 const LSOriginInitialization aInitialization,
3147 const nsACString& aContext, Func&& aFunc)
3148 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
3149 LSOriginInitialization, Nothing>&> {
3150 return ExecuteInitialization(
3151 MutableInitializationInfoRef(CreateIfNonExistent{})
3152 .MutableOriginInitializationInfoRef(aOrigin, CreateIfNonExistent{}),
3153 aInitialization, aContext, std::forward<Func>(aFunc));
3156 } // namespace
3158 /*******************************************************************************
3159 * Exported functions
3160 ******************************************************************************/
3162 void InitializeLocalStorage() {
3163 MOZ_ASSERT(XRE_IsParentProcess());
3164 MOZ_ASSERT(NS_IsMainThread());
3165 MOZ_ASSERT(!gLocalStorageInitialized);
3167 // XXX Isn't this redundant? It's already done in InitializeQuotaManager.
3168 if (!QuotaManager::IsRunningGTests()) {
3169 // This service has to be started on the main thread currently.
3170 const nsCOMPtr<mozIStorageService> ss =
3171 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
3173 QM_WARNONLY_TRY(OkIf(ss));
3176 Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback,
3177 kShadowWritesPref);
3179 Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback,
3180 kSnapshotPrefillPref);
3182 Preferences::RegisterCallbackAndCall(
3183 SnapshotGradualPrefillPrefChangedCallback, kSnapshotGradualPrefillPref);
3185 Preferences::RegisterCallbackAndCall(ClientValidationPrefChangedCallback,
3186 kClientValidationPref);
3188 #ifdef DEBUG
3189 gLocalStorageInitialized = true;
3190 #endif
3193 already_AddRefed<PBackgroundLSDatabaseParent> AllocPBackgroundLSDatabaseParent(
3194 const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
3195 const uint64_t& aDatastoreId) {
3196 AssertIsOnBackgroundThread();
3198 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3199 return nullptr;
3202 if (NS_WARN_IF(!gPreparedDatastores)) {
3203 MOZ_ASSERT_UNLESS_FUZZING(false);
3204 return nullptr;
3207 PreparedDatastore* preparedDatastore = gPreparedDatastores->Get(aDatastoreId);
3208 if (NS_WARN_IF(!preparedDatastore)) {
3209 MOZ_ASSERT_UNLESS_FUZZING(false);
3210 return nullptr;
3213 // If we ever decide to return null from this point on, we need to make sure
3214 // that the datastore is closed and the prepared datastore is removed from the
3215 // gPreparedDatastores hashtable.
3216 // We also assume that IPDL must call RecvPBackgroundLSDatabaseConstructor
3217 // once we return a valid actor in this method.
3219 RefPtr<Database> database =
3220 new Database(aPrincipalInfo, preparedDatastore->GetContentParentId(),
3221 preparedDatastore->Origin(), aPrivateBrowsingId);
3223 // Transfer ownership to IPDL.
3224 return database.forget();
3227 bool RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor,
3228 const PrincipalInfo& aPrincipalInfo,
3229 const uint32_t& aPrivateBrowsingId,
3230 const uint64_t& aDatastoreId) {
3231 AssertIsOnBackgroundThread();
3232 MOZ_ASSERT(aActor);
3233 MOZ_ASSERT(gPreparedDatastores);
3234 MOZ_ASSERT(gPreparedDatastores->Get(aDatastoreId));
3235 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3237 // The actor is now completely built (it has a manager, channel and it's
3238 // registered as a subprotocol).
3239 // ActorDestroy will be called if we fail here.
3241 mozilla::UniquePtr<PreparedDatastore> preparedDatastore;
3242 gPreparedDatastores->Remove(aDatastoreId, &preparedDatastore);
3243 MOZ_ASSERT(preparedDatastore);
3245 auto* database = static_cast<Database*>(aActor);
3247 database->SetActorAlive(&preparedDatastore->MutableDatastoreRef());
3249 // It's possible that AbortOperationsForLocks was called before the database
3250 // actor was created and became live. Let the child know that the database is
3251 // no longer valid.
3252 if (preparedDatastore->IsInvalidated()) {
3253 database->RequestAllowToClose();
3256 return true;
3259 PBackgroundLSObserverParent* AllocPBackgroundLSObserverParent(
3260 const uint64_t& aObserverId) {
3261 AssertIsOnBackgroundThread();
3263 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3264 return nullptr;
3267 if (NS_WARN_IF(!gPreparedObsevers)) {
3268 MOZ_ASSERT_UNLESS_FUZZING(false);
3269 return nullptr;
3272 RefPtr<Observer> observer = gPreparedObsevers->Get(aObserverId);
3273 if (NS_WARN_IF(!observer)) {
3274 MOZ_ASSERT_UNLESS_FUZZING(false);
3275 return nullptr;
3278 // observer->SetObject(this);
3280 // Transfer ownership to IPDL.
3281 return observer.forget().take();
3284 bool RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent* aActor,
3285 const uint64_t& aObserverId) {
3286 AssertIsOnBackgroundThread();
3287 MOZ_ASSERT(aActor);
3288 MOZ_ASSERT(gPreparedObsevers);
3289 MOZ_ASSERT(gPreparedObsevers->GetWeak(aObserverId));
3291 RefPtr<Observer> observer;
3292 gPreparedObsevers->Remove(aObserverId, observer.StartAssignment());
3294 if (!gPreparedObsevers->Count()) {
3295 gPreparedObsevers = nullptr;
3298 if (!gObservers) {
3299 gObservers = new ObserverHashtable();
3302 const auto notNullObserver = WrapNotNull(observer.get());
3304 nsTArray<NotNull<Observer*>>* const array =
3305 gObservers->GetOrInsertNew(notNullObserver->Origin());
3306 array->AppendElement(notNullObserver);
3308 if (RefPtr<Datastore> datastore = GetDatastore(observer->Origin())) {
3309 datastore->NoteChangedObserverArray(*array);
3312 return true;
3315 bool DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor) {
3316 AssertIsOnBackgroundThread();
3317 MOZ_ASSERT(aActor);
3319 // Transfer ownership back from IPDL.
3320 RefPtr<Observer> actor = dont_AddRef(static_cast<Observer*>(aActor));
3322 return true;
3325 PBackgroundLSRequestParent* AllocPBackgroundLSRequestParent(
3326 PBackgroundParent* aBackgroundActor, const LSRequestParams& aParams) {
3327 AssertIsOnBackgroundThread();
3328 MOZ_ASSERT(aParams.type() != LSRequestParams::T__None);
3330 if (NS_WARN_IF(!NextGenLocalStorageEnabled())) {
3331 return nullptr;
3334 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3335 return nullptr;
3338 Maybe<ContentParentId> contentParentId;
3340 uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
3341 if (childID) {
3342 contentParentId = Some(ContentParentId(childID));
3345 RefPtr<LSRequestBase> actor;
3347 switch (aParams.type()) {
3348 case LSRequestParams::TLSRequestPreloadDatastoreParams:
3349 case LSRequestParams::TLSRequestPrepareDatastoreParams: {
3350 RefPtr<PrepareDatastoreOp> prepareDatastoreOp =
3351 new PrepareDatastoreOp(aParams, contentParentId);
3353 if (!gPrepareDatastoreOps) {
3354 gPrepareDatastoreOps = new PrepareDatastoreOpArray();
3356 gPrepareDatastoreOps->AppendElement(
3357 WrapNotNullUnchecked(prepareDatastoreOp.get()));
3359 actor = std::move(prepareDatastoreOp);
3361 break;
3364 case LSRequestParams::TLSRequestPrepareObserverParams: {
3365 RefPtr<PrepareObserverOp> prepareObserverOp =
3366 new PrepareObserverOp(aParams, contentParentId);
3368 actor = std::move(prepareObserverOp);
3370 break;
3373 default:
3374 MOZ_CRASH("Should never get here!");
3377 // Transfer ownership to IPDL.
3378 return actor.forget().take();
3381 bool RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor,
3382 const LSRequestParams& aParams) {
3383 AssertIsOnBackgroundThread();
3384 MOZ_ASSERT(aActor);
3385 MOZ_ASSERT(aParams.type() != LSRequestParams::T__None);
3386 MOZ_ASSERT(NextGenLocalStorageEnabled());
3387 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3389 // The actor is now completely built.
3391 auto* op = static_cast<LSRequestBase*>(aActor);
3393 op->Dispatch();
3395 return true;
3398 bool DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent* aActor) {
3399 AssertIsOnBackgroundThread();
3401 // Transfer ownership back from IPDL.
3402 RefPtr<LSRequestBase> actor =
3403 dont_AddRef(static_cast<LSRequestBase*>(aActor));
3405 return true;
3408 PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent(
3409 PBackgroundParent* aBackgroundActor, const LSSimpleRequestParams& aParams) {
3410 AssertIsOnBackgroundThread();
3411 MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None);
3413 if (NS_WARN_IF(!NextGenLocalStorageEnabled())) {
3414 return nullptr;
3417 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3418 return nullptr;
3421 Maybe<ContentParentId> contentParentId;
3423 uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
3424 if (childID) {
3425 contentParentId = Some(ContentParentId(childID));
3428 RefPtr<LSSimpleRequestBase> actor;
3430 switch (aParams.type()) {
3431 case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: {
3432 RefPtr<PreloadedOp> preloadedOp =
3433 new PreloadedOp(aParams, contentParentId);
3435 actor = std::move(preloadedOp);
3437 break;
3440 case LSSimpleRequestParams::TLSSimpleRequestGetStateParams: {
3441 RefPtr<GetStateOp> getStateOp = new GetStateOp(aParams, contentParentId);
3443 actor = std::move(getStateOp);
3445 break;
3448 default:
3449 MOZ_CRASH("Should never get here!");
3452 // Transfer ownership to IPDL.
3453 return actor.forget().take();
3456 bool RecvPBackgroundLSSimpleRequestConstructor(
3457 PBackgroundLSSimpleRequestParent* aActor,
3458 const LSSimpleRequestParams& aParams) {
3459 AssertIsOnBackgroundThread();
3460 MOZ_ASSERT(aActor);
3461 MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None);
3462 MOZ_ASSERT(NextGenLocalStorageEnabled());
3463 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3465 // The actor is now completely built.
3467 auto* op = static_cast<LSSimpleRequestBase*>(aActor);
3469 op->Dispatch();
3471 return true;
3474 bool DeallocPBackgroundLSSimpleRequestParent(
3475 PBackgroundLSSimpleRequestParent* aActor) {
3476 AssertIsOnBackgroundThread();
3478 // Transfer ownership back from IPDL.
3479 RefPtr<LSSimpleRequestBase> actor =
3480 dont_AddRef(static_cast<LSSimpleRequestBase*>(aActor));
3482 return true;
3485 namespace localstorage {
3487 already_AddRefed<mozilla::dom::quota::Client> CreateQuotaClient() {
3488 AssertIsOnBackgroundThread();
3489 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
3491 RefPtr<QuotaClient> client = new QuotaClient();
3492 return client.forget();
3495 } // namespace localstorage
3497 /*******************************************************************************
3498 * DatastoreWriteOptimizer
3499 ******************************************************************************/
3501 void DatastoreWriteOptimizer::ApplyAndReset(
3502 nsTArray<LSItemInfo>& aOrderedItems) {
3503 AssertIsOnOwningThread();
3505 // The mWriteInfos hash table contains all write infos, but it keeps them in
3506 // an arbitrary order, which means write infos need to be sorted before being
3507 // processed. However, the order is not important for deletions and normal
3508 // updates. Usually, filtering out deletions and updates would require extra
3509 // work, but we have to check the hash table for each ordered item anyway, so
3510 // we can remove the write info if it is a deletion or update without adding
3511 // extra overhead. In the end, only insertions need to be sorted before being
3512 // processed.
3514 if (mTruncateInfo) {
3515 aOrderedItems.Clear();
3516 mTruncateInfo = nullptr;
3519 for (int32_t index = aOrderedItems.Length() - 1; index >= 0; index--) {
3520 LSItemInfo& item = aOrderedItems[index];
3522 if (auto entry = mWriteInfos.Lookup(item.key())) {
3523 WriteInfo* writeInfo = entry->get();
3525 switch (writeInfo->GetType()) {
3526 case WriteInfo::DeleteItem:
3527 aOrderedItems.RemoveElementAt(index);
3528 entry.Remove();
3529 break;
3531 case WriteInfo::UpdateItem: {
3532 auto updateItemInfo = static_cast<UpdateItemInfo*>(writeInfo);
3533 if (updateItemInfo->UpdateWithMove()) {
3534 // See the comment in LSWriteOptimizer::InsertItem for more details
3535 // about the UpdateWithMove flag.
3537 aOrderedItems.RemoveElementAt(index);
3538 entry.Data() = MakeUnique<InsertItemInfo>(
3539 updateItemInfo->SerialNumber(), updateItemInfo->GetKey(),
3540 updateItemInfo->GetValue());
3541 } else {
3542 item.value() = updateItemInfo->GetValue();
3543 entry.Remove();
3545 break;
3548 case WriteInfo::InsertItem:
3549 break;
3551 default:
3552 MOZ_CRASH("Bad type!");
3557 nsTArray<NotNull<WriteInfo*>> writeInfos;
3558 GetSortedWriteInfos(writeInfos);
3560 for (WriteInfo* writeInfo : writeInfos) {
3561 MOZ_ASSERT(writeInfo->GetType() == WriteInfo::InsertItem);
3563 auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo);
3565 LSItemInfo* itemInfo = aOrderedItems.AppendElement();
3566 itemInfo->key() = insertItemInfo->GetKey();
3567 itemInfo->value() = insertItemInfo->GetValue();
3570 mWriteInfos.Clear();
3573 /*******************************************************************************
3574 * ConnectionWriteOptimizer
3575 ******************************************************************************/
3577 Result<int64_t, nsresult> ConnectionWriteOptimizer::Perform(
3578 Connection* aConnection, bool aShadowWrites) {
3579 AssertIsOnGlobalConnectionThread();
3580 MOZ_ASSERT(aConnection);
3582 // The order of elements is not stored in the database, so write infos don't
3583 // need to be sorted before being processed.
3585 if (mTruncateInfo) {
3586 QM_TRY(MOZ_TO_RESULT(PerformTruncate(aConnection, aShadowWrites)));
3589 for (const auto& entry : mWriteInfos) {
3590 const WriteInfo* const writeInfo = entry.GetWeak();
3592 switch (writeInfo->GetType()) {
3593 case WriteInfo::InsertItem:
3594 case WriteInfo::UpdateItem: {
3595 const auto* const insertItemInfo =
3596 static_cast<const InsertItemInfo*>(writeInfo);
3598 QM_TRY(MOZ_TO_RESULT(PerformInsertOrUpdate(
3599 aConnection, aShadowWrites, insertItemInfo->GetKey(),
3600 insertItemInfo->GetValue())));
3602 break;
3605 case WriteInfo::DeleteItem: {
3606 const auto* const deleteItemInfo =
3607 static_cast<const DeleteItemInfo*>(writeInfo);
3609 QM_TRY(MOZ_TO_RESULT(PerformDelete(aConnection, aShadowWrites,
3610 deleteItemInfo->GetKey())));
3612 break;
3615 default:
3616 MOZ_CRASH("Bad type!");
3620 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3621 "UPDATE database "
3622 "SET usage = usage + :delta"_ns,
3623 [this](auto& stmt) -> Result<Ok, nsresult> {
3624 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName("delta"_ns, mTotalDelta)));
3626 return Ok{};
3627 })));
3629 QM_TRY_INSPECT(const auto& stmt, CreateAndExecuteSingleStepStatement<
3630 SingleStepResult::ReturnNullIfNoResult>(
3631 aConnection->MutableStorageConnection(),
3632 "SELECT usage FROM database"_ns));
3634 QM_TRY(OkIf(stmt), Err(NS_ERROR_FAILURE));
3636 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 0));
3639 nsresult ConnectionWriteOptimizer::PerformInsertOrUpdate(
3640 Connection* aConnection, bool aShadowWrites, const nsAString& aKey,
3641 const LSValue& aValue) {
3642 AssertIsOnGlobalConnectionThread();
3643 MOZ_ASSERT(aConnection);
3645 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3646 "INSERT OR REPLACE INTO data (key, utf16_length, conversion_type, "
3647 "compression_type, value) "
3648 "VALUES(:key, :utf16_length, :conversion_type, :compression_type, :value)"_ns,
3649 [&aKey, &aValue](auto& stmt) -> Result<Ok, nsresult> {
3650 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey)));
3651 QM_TRY(MOZ_TO_RESULT(
3652 stmt.BindInt32ByName("utf16_length"_ns, aValue.UTF16Length())));
3653 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByName(
3654 "conversion_type"_ns,
3655 static_cast<int32_t>(aValue.GetConversionType()))));
3656 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByName(
3657 "compression_type"_ns,
3658 static_cast<int32_t>(aValue.GetCompressionType()))));
3660 if (0u == aValue.Length()) { // Otherwise empty string becomes null
3661 QM_TRY(MOZ_TO_RESULT(
3662 stmt.BindUTF8StringByName("value"_ns, aValue.AsCString())));
3663 } else {
3664 QM_TRY(MOZ_TO_RESULT(
3665 stmt.BindUTF8StringAsBlobByName("value"_ns, aValue.AsCString())));
3668 return Ok{};
3669 })));
3671 if (!aShadowWrites) {
3672 return NS_OK;
3675 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3676 "INSERT OR REPLACE INTO shadow.webappsstore2 "
3677 "(originAttributes, originKey, scope, key, value) "
3678 "VALUES (:originAttributes, :originKey, :scope, :key, :value) "_ns,
3679 [&aConnection, &aKey, &aValue](auto& stmt) -> Result<Ok, nsresult> {
3680 using ConversionType = LSValue::ConversionType;
3681 using CompressionType = LSValue::CompressionType;
3683 const ArchivedOriginScope* const archivedOriginScope =
3684 aConnection->GetArchivedOriginScope();
3686 QM_TRY(MOZ_TO_RESULT(archivedOriginScope->BindToStatement(&stmt)));
3688 QM_TRY(MOZ_TO_RESULT(stmt.BindUTF8StringByName(
3689 "scope"_ns, Scheme0Scope(archivedOriginScope->OriginSuffix(),
3690 archivedOriginScope->OriginNoSuffix()))));
3692 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey)));
3694 bool isCompressed =
3695 CompressionType::UNCOMPRESSED != aValue.GetCompressionType();
3696 bool isAlreadyConverted =
3697 ConversionType::NONE != aValue.GetConversionType();
3699 nsCString buffer;
3700 const nsCString& valueBlob = aValue.AsCString();
3701 if (isCompressed) {
3702 QM_TRY(OkIf(SnappyUncompress(valueBlob, buffer)),
3703 Err(NS_ERROR_FAILURE));
3705 const nsCString& value = isCompressed ? buffer : valueBlob;
3707 // For shadow writes, we undo buffer swap and convert destructively
3708 nsCString unconverted;
3709 if (!isAlreadyConverted) {
3710 nsString converted;
3711 QM_TRY(OkIf(PutCStringBytesToString(value, converted)),
3712 Err(NS_ERROR_OUT_OF_MEMORY));
3713 QM_TRY(OkIf(CopyUTF16toUTF8(converted, unconverted, fallible)),
3714 Err(NS_ERROR_OUT_OF_MEMORY)); // Corrupt invalid data
3716 const nsCString& untransformed =
3717 (!isAlreadyConverted) ? unconverted : value;
3719 QM_TRY(MOZ_TO_RESULT(
3720 stmt.BindUTF8StringByName("value"_ns, untransformed)));
3722 return Ok{};
3723 })));
3725 return NS_OK;
3728 nsresult ConnectionWriteOptimizer::PerformDelete(Connection* aConnection,
3729 bool aShadowWrites,
3730 const nsAString& aKey) {
3731 AssertIsOnGlobalConnectionThread();
3732 MOZ_ASSERT(aConnection);
3734 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3735 "DELETE FROM data "
3736 "WHERE key = :key;"_ns,
3737 [&aKey](auto& stmt) -> Result<Ok, nsresult> {
3738 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey)));
3740 return Ok{};
3741 })));
3743 if (!aShadowWrites) {
3744 return NS_OK;
3747 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3748 "DELETE FROM shadow.webappsstore2 "
3749 "WHERE originAttributes = :originAttributes "
3750 "AND originKey = :originKey "
3751 "AND key = :key;"_ns,
3752 [&aConnection, &aKey](auto& stmt) -> Result<Ok, nsresult> {
3753 QM_TRY(MOZ_TO_RESULT(
3754 aConnection->GetArchivedOriginScope()->BindToStatement(&stmt)));
3756 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey)));
3758 return Ok{};
3759 })));
3761 return NS_OK;
3764 nsresult ConnectionWriteOptimizer::PerformTruncate(Connection* aConnection,
3765 bool aShadowWrites) {
3766 AssertIsOnGlobalConnectionThread();
3767 MOZ_ASSERT(aConnection);
3769 QM_TRY(MOZ_TO_RESULT(
3770 aConnection->ExecuteCachedStatement("DELETE FROM data;"_ns)));
3772 if (!aShadowWrites) {
3773 return NS_OK;
3776 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3777 "DELETE FROM shadow.webappsstore2 "
3778 "WHERE originAttributes = :originAttributes "
3779 "AND originKey = :originKey;"_ns,
3780 [&aConnection](auto& stmt) -> Result<Ok, nsresult> {
3781 QM_TRY(MOZ_TO_RESULT(
3782 aConnection->GetArchivedOriginScope()->BindToStatement(&stmt)));
3784 return Ok{};
3785 })));
3787 return NS_OK;
3790 /*******************************************************************************
3791 * DatastoreOperationBase
3792 ******************************************************************************/
3794 /*******************************************************************************
3795 * ConnectionDatastoreOperationBase
3796 ******************************************************************************/
3798 ConnectionDatastoreOperationBase::ConnectionDatastoreOperationBase(
3799 Connection* aConnection, bool aEnsureStorageConnection)
3800 : mConnection(aConnection),
3801 mEnsureStorageConnection(aEnsureStorageConnection) {
3802 MOZ_ASSERT(aConnection);
3805 ConnectionDatastoreOperationBase::~ConnectionDatastoreOperationBase() {
3806 MOZ_ASSERT(!mConnection,
3807 "ConnectionDatabaseOperationBase::Cleanup() was not called by a "
3808 "subclass!");
3811 void ConnectionDatastoreOperationBase::Cleanup() {
3812 AssertIsOnOwningThread();
3813 MOZ_ASSERT(mConnection);
3815 mConnection = nullptr;
3817 NoteComplete();
3820 void ConnectionDatastoreOperationBase::OnSuccess() { AssertIsOnOwningThread(); }
3822 void ConnectionDatastoreOperationBase::OnFailure(nsresult aResultCode) {
3823 AssertIsOnOwningThread();
3824 MOZ_ASSERT(NS_FAILED(aResultCode));
3827 void ConnectionDatastoreOperationBase::RunOnConnectionThread() {
3828 AssertIsOnGlobalConnectionThread();
3829 MOZ_ASSERT(mConnection);
3830 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
3832 if (!MayProceedOnNonOwningThread()) {
3833 SetFailureCode(NS_ERROR_ABORT);
3834 } else {
3835 nsresult rv = NS_OK;
3837 // The boolean flag is only used by the CloseOp to avoid creating empty
3838 // databases.
3839 if (mEnsureStorageConnection) {
3840 rv = mConnection->EnsureStorageConnection();
3841 if (NS_WARN_IF(NS_FAILED(rv))) {
3842 SetFailureCode(rv);
3843 } else {
3844 MOZ_ASSERT(mConnection->HasStorageConnection());
3848 if (NS_SUCCEEDED(rv)) {
3849 rv = DoDatastoreWork();
3850 if (NS_FAILED(rv)) {
3851 SetFailureCode(rv);
3856 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
3859 void ConnectionDatastoreOperationBase::RunOnOwningThread() {
3860 AssertIsOnOwningThread();
3861 MOZ_ASSERT(mConnection);
3863 if (!MayProceed()) {
3864 MaybeSetFailureCode(NS_ERROR_ABORT);
3867 if (NS_SUCCEEDED(ResultCode())) {
3868 OnSuccess();
3869 } else {
3870 OnFailure(ResultCode());
3873 Cleanup();
3876 NS_IMETHODIMP
3877 ConnectionDatastoreOperationBase::Run() {
3878 if (IsOnGlobalConnectionThread()) {
3879 RunOnConnectionThread();
3880 } else {
3881 RunOnOwningThread();
3884 return NS_OK;
3887 /*******************************************************************************
3888 * Connection implementation
3889 ******************************************************************************/
3891 Connection::Connection(ConnectionThread* aConnectionThread,
3892 const OriginMetadata& aOriginMetadata,
3893 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope,
3894 bool aDatabaseWasNotAvailable)
3895 : mConnectionThread(aConnectionThread),
3896 mQuotaClient(QuotaClient::GetInstance()),
3897 mArchivedOriginScope(std::move(aArchivedOriginScope)),
3898 mOriginMetadata(aOriginMetadata),
3899 mDatabaseWasNotAvailable(aDatabaseWasNotAvailable),
3900 mHasCreatedDatabase(false),
3901 mFlushScheduled(false)
3902 #ifdef DEBUG
3904 mInUpdateBatch(false),
3905 mFinished(false)
3906 #endif
3908 AssertIsOnOwningThread();
3909 MOZ_ASSERT(!aOriginMetadata.mGroup.IsEmpty());
3910 MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
3913 Connection::~Connection() {
3914 AssertIsOnOwningThread();
3915 MOZ_ASSERT(!mFlushScheduled);
3916 MOZ_ASSERT(!mInUpdateBatch);
3917 MOZ_ASSERT(mFinished);
3920 void Connection::Dispatch(ConnectionDatastoreOperationBase* aOp) {
3921 AssertIsOnOwningThread();
3922 MOZ_ASSERT(mConnectionThread);
3924 MOZ_ALWAYS_SUCCEEDS(
3925 mConnectionThread->mThread->Dispatch(aOp, NS_DISPATCH_NORMAL));
3928 void Connection::Close(nsIRunnable* aCallback) {
3929 AssertIsOnOwningThread();
3930 MOZ_ASSERT(aCallback);
3932 if (mFlushScheduled) {
3933 MOZ_ASSERT(mFlushTimer);
3934 MOZ_ALWAYS_SUCCEEDS(mFlushTimer->Cancel());
3936 Flush();
3938 mFlushTimer = nullptr;
3941 RefPtr<CloseOp> op = new CloseOp(this, aCallback);
3943 Dispatch(op);
3946 void Connection::SetItem(const nsString& aKey, const LSValue& aValue,
3947 int64_t aDelta, bool aIsNewItem) {
3948 AssertIsOnOwningThread();
3949 MOZ_ASSERT(mInUpdateBatch);
3951 if (aIsNewItem) {
3952 mWriteOptimizer.InsertItem(aKey, aValue, aDelta);
3953 } else {
3954 mWriteOptimizer.UpdateItem(aKey, aValue, aDelta);
3958 void Connection::RemoveItem(const nsString& aKey, int64_t aDelta) {
3959 AssertIsOnOwningThread();
3960 MOZ_ASSERT(mInUpdateBatch);
3962 mWriteOptimizer.DeleteItem(aKey, aDelta);
3965 void Connection::Clear(int64_t aDelta) {
3966 AssertIsOnOwningThread();
3967 MOZ_ASSERT(mInUpdateBatch);
3969 mWriteOptimizer.Truncate(aDelta);
3972 void Connection::BeginUpdateBatch() {
3973 AssertIsOnOwningThread();
3974 MOZ_ASSERT(!mInUpdateBatch);
3976 #ifdef DEBUG
3977 mInUpdateBatch = true;
3978 #endif
3981 void Connection::EndUpdateBatch() {
3982 AssertIsOnOwningThread();
3983 MOZ_ASSERT(mInUpdateBatch);
3985 if (mWriteOptimizer.HasWrites() && !mFlushScheduled) {
3986 ScheduleFlush();
3989 #ifdef DEBUG
3990 mInUpdateBatch = false;
3991 #endif
3994 nsresult Connection::EnsureStorageConnection() {
3995 AssertIsOnGlobalConnectionThread();
3997 if (HasStorageConnection()) {
3998 return NS_OK;
4001 QuotaManager* quotaManager = QuotaManager::Get();
4002 MOZ_ASSERT(quotaManager);
4004 if (!mDatabaseWasNotAvailable || mHasCreatedDatabase) {
4005 MOZ_ASSERT(mOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
4007 QM_TRY_INSPECT(const auto& directoryEntry,
4008 quotaManager->GetOriginDirectory(mOriginMetadata));
4010 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(
4011 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME))));
4013 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDirectoryPath)));
4014 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName)));
4016 QM_TRY_INSPECT(
4017 const auto& databaseFilePath,
4018 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directoryEntry, GetPath));
4020 QM_TRY_UNWRAP(auto storageConnection,
4021 GetStorageConnection(databaseFilePath));
4022 LazyInit(WrapMovingNotNull(std::move(storageConnection)));
4024 return NS_OK;
4027 RefPtr<InitTemporaryOriginHelper> helper =
4028 new InitTemporaryOriginHelper(mOriginMetadata);
4030 QM_TRY_INSPECT(const auto& originDirectoryPath,
4031 helper->BlockAndReturnOriginDirectoryPath());
4033 QM_TRY_INSPECT(const auto& directoryEntry,
4034 QM_NewLocalFile(originDirectoryPath));
4036 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(
4037 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME))));
4039 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDirectoryPath)));
4041 QM_TRY_INSPECT(const bool& exists,
4042 MOZ_TO_RESULT_INVOKE_MEMBER(directoryEntry, Exists));
4044 if (!exists) {
4045 QM_TRY(
4046 MOZ_TO_RESULT(directoryEntry->Create(nsIFile::DIRECTORY_TYPE, 0755)));
4049 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName)));
4051 #ifdef DEBUG
4053 QM_TRY_INSPECT(const bool& exists,
4054 MOZ_TO_RESULT_INVOKE_MEMBER(directoryEntry, Exists));
4056 MOZ_ASSERT(!exists);
4058 #endif
4060 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(mDirectoryPath));
4062 nsCOMPtr<mozIStorageConnection> storageConnection;
4064 auto autoRemove = MakeScopeExit([&storageConnection, &directoryEntry] {
4065 if (storageConnection) {
4066 MOZ_ALWAYS_SUCCEEDS(storageConnection->Close());
4069 nsresult rv = directoryEntry->Remove(false);
4070 if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
4071 NS_WARNING("Failed to remove database file!");
4075 QM_TRY_UNWRAP(storageConnection, CreateStorageConnectionWithRecovery(
4076 *directoryEntry, *usageFile, Origin(),
4077 [] { MOZ_ASSERT_UNREACHABLE(); }));
4079 MOZ_ASSERT(mQuotaClient);
4081 MutexAutoLock shadowDatabaseLock(mQuotaClient->ShadowDatabaseMutex());
4083 nsCOMPtr<mozIStorageConnection> shadowConnection;
4084 if (!gInitializedShadowStorage) {
4085 QM_TRY_UNWRAP(shadowConnection,
4086 CreateShadowStorageConnection(quotaManager->GetBasePath()));
4088 gInitializedShadowStorage = true;
4091 autoRemove.release();
4093 if (!mHasCreatedDatabase) {
4094 mHasCreatedDatabase = true;
4097 LazyInit(WrapMovingNotNull(std::move(storageConnection)));
4099 return NS_OK;
4102 void Connection::CloseStorageConnection() {
4103 AssertIsOnGlobalConnectionThread();
4105 CachingDatabaseConnection::Close();
4108 nsresult Connection::BeginWriteTransaction() {
4109 AssertIsOnGlobalConnectionThread();
4110 MOZ_ASSERT(HasStorageConnection());
4112 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN IMMEDIATE;"_ns)));
4114 return NS_OK;
4117 nsresult Connection::CommitWriteTransaction() {
4118 AssertIsOnGlobalConnectionThread();
4119 MOZ_ASSERT(HasStorageConnection());
4121 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("COMMIT;"_ns)));
4123 return NS_OK;
4126 nsresult Connection::RollbackWriteTransaction() {
4127 AssertIsOnGlobalConnectionThread();
4128 MOZ_ASSERT(HasStorageConnection());
4130 QM_TRY_INSPECT(const auto& stmt, BorrowCachedStatement("ROLLBACK;"_ns));
4132 // This may fail if SQLite already rolled back the transaction so ignore any
4133 // errors.
4134 Unused << stmt->Execute();
4136 return NS_OK;
4139 void Connection::ScheduleFlush() {
4140 AssertIsOnOwningThread();
4141 MOZ_ASSERT(mWriteOptimizer.HasWrites());
4142 MOZ_ASSERT(!mFlushScheduled);
4144 if (!mFlushTimer) {
4145 mFlushTimer = NS_NewTimer();
4146 MOZ_ASSERT(mFlushTimer);
4149 MOZ_ALWAYS_SUCCEEDS(mFlushTimer->InitWithNamedFuncCallback(
4150 FlushTimerCallback, this, kFlushTimeoutMs, nsITimer::TYPE_ONE_SHOT,
4151 "Connection::FlushTimerCallback"));
4153 mFlushScheduled = true;
4156 void Connection::Flush() {
4157 AssertIsOnOwningThread();
4158 MOZ_ASSERT(mFlushScheduled);
4160 if (mWriteOptimizer.HasWrites()) {
4161 RefPtr<FlushOp> op = new FlushOp(this, std::move(mWriteOptimizer));
4163 Dispatch(op);
4166 mFlushScheduled = false;
4169 // static
4170 void Connection::FlushTimerCallback(nsITimer* aTimer, void* aClosure) {
4171 MOZ_ASSERT(aClosure);
4173 auto* self = static_cast<Connection*>(aClosure);
4174 MOZ_ASSERT(self);
4175 MOZ_ASSERT(self->mFlushScheduled);
4177 self->Flush();
4180 Result<nsString, nsresult>
4181 Connection::InitTemporaryOriginHelper::BlockAndReturnOriginDirectoryPath() {
4182 AssertIsOnGlobalConnectionThread();
4184 QuotaManager* quotaManager = QuotaManager::Get();
4185 MOZ_ASSERT(quotaManager);
4187 MOZ_ALWAYS_SUCCEEDS(
4188 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
4190 mozilla::MonitorAutoLock lock(mMonitor);
4191 while (mWaiting) {
4192 lock.Wait();
4195 QM_TRY(MOZ_TO_RESULT(mIOThreadResultCode));
4197 return mOriginDirectoryPath;
4200 nsresult Connection::InitTemporaryOriginHelper::RunOnIOThread() {
4201 AssertIsOnIOThread();
4203 QuotaManager* quotaManager = QuotaManager::Get();
4204 MOZ_ASSERT(quotaManager);
4206 QM_TRY_INSPECT(const auto& directoryEntry,
4207 quotaManager
4208 ->EnsureTemporaryOriginIsInitialized(
4209 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata)
4210 .map([](const auto& res) { return res.first; }));
4212 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mOriginDirectoryPath)));
4214 return NS_OK;
4217 NS_IMETHODIMP
4218 Connection::InitTemporaryOriginHelper::Run() {
4219 AssertIsOnIOThread();
4221 nsresult rv = RunOnIOThread();
4222 if (NS_WARN_IF(NS_FAILED(rv))) {
4223 mIOThreadResultCode = rv;
4226 mozilla::MonitorAutoLock lock(mMonitor);
4227 MOZ_ASSERT(mWaiting);
4229 mWaiting = false;
4230 lock.Notify();
4232 return NS_OK;
4235 Connection::FlushOp::FlushOp(Connection* aConnection,
4236 ConnectionWriteOptimizer&& aWriteOptimizer)
4237 : ConnectionDatastoreOperationBase(aConnection),
4238 mWriteOptimizer(std::move(aWriteOptimizer)),
4239 mShadowWrites(gShadowWrites) {}
4241 nsresult Connection::FlushOp::DoDatastoreWork() {
4242 AssertIsOnGlobalConnectionThread();
4243 MOZ_ASSERT(mConnection);
4245 AutoWriteTransaction autoWriteTransaction(mShadowWrites);
4247 QM_TRY(MOZ_TO_RESULT(autoWriteTransaction.Start(mConnection)));
4249 QM_TRY_INSPECT(const int64_t& usage,
4250 mWriteOptimizer.Perform(mConnection, mShadowWrites));
4252 QM_TRY_INSPECT(const auto& usageFile,
4253 GetUsageFile(mConnection->DirectoryPath()));
4255 QM_TRY_INSPECT(const auto& usageJournalFile,
4256 GetUsageJournalFile(mConnection->DirectoryPath()));
4258 QM_TRY(MOZ_TO_RESULT(UpdateUsageFile(usageFile, usageJournalFile, usage)));
4260 QM_TRY(MOZ_TO_RESULT(autoWriteTransaction.Commit()));
4262 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false)));
4264 return NS_OK;
4267 void Connection::FlushOp::Cleanup() {
4268 AssertIsOnOwningThread();
4270 mWriteOptimizer.Reset();
4272 MOZ_ASSERT(!mWriteOptimizer.HasWrites());
4274 ConnectionDatastoreOperationBase::Cleanup();
4277 nsresult Connection::CloseOp::DoDatastoreWork() {
4278 AssertIsOnGlobalConnectionThread();
4279 MOZ_ASSERT(mConnection);
4281 if (mConnection->HasStorageConnection()) {
4282 mConnection->CloseStorageConnection();
4285 return NS_OK;
4288 void Connection::CloseOp::Cleanup() {
4289 AssertIsOnOwningThread();
4290 MOZ_ASSERT(mConnection);
4292 mConnection->mConnectionThread->mConnections.Remove(mConnection->Origin());
4294 #ifdef DEBUG
4295 MOZ_ASSERT(!mConnection->mFinished);
4296 mConnection->mFinished = true;
4297 #endif
4299 nsCOMPtr<nsIRunnable> callback;
4300 mCallback.swap(callback);
4302 callback->Run();
4304 ConnectionDatastoreOperationBase::Cleanup();
4307 /*******************************************************************************
4308 * ConnectionThread implementation
4309 ******************************************************************************/
4311 ConnectionThread::ConnectionThread() {
4312 AssertIsOnOwningThread();
4313 AssertIsOnBackgroundThread();
4315 MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread("LS Thread", getter_AddRefs(mThread)));
4318 ConnectionThread::~ConnectionThread() {
4319 AssertIsOnOwningThread();
4320 MOZ_ASSERT(!mConnections.Count());
4323 bool ConnectionThread::IsOnConnectionThread() {
4324 MOZ_ASSERT(mThread);
4326 bool current;
4327 return NS_SUCCEEDED(mThread->IsOnCurrentThread(&current)) && current;
4330 void ConnectionThread::AssertIsOnConnectionThread() {
4331 MOZ_ASSERT(IsOnConnectionThread());
4334 already_AddRefed<Connection> ConnectionThread::CreateConnection(
4335 const OriginMetadata& aOriginMetadata,
4336 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope,
4337 bool aDatabaseWasNotAvailable) {
4338 AssertIsOnOwningThread();
4339 MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
4340 MOZ_ASSERT(!mConnections.Contains(aOriginMetadata.mOrigin));
4342 RefPtr<Connection> connection =
4343 new Connection(this, aOriginMetadata, std::move(aArchivedOriginScope),
4344 aDatabaseWasNotAvailable);
4345 mConnections.InsertOrUpdate(aOriginMetadata.mOrigin, RefPtr{connection});
4347 return connection.forget();
4350 void ConnectionThread::Shutdown() {
4351 AssertIsOnOwningThread();
4352 MOZ_ASSERT(mThread);
4354 mThread->Shutdown();
4357 /*******************************************************************************
4358 * Datastore
4359 ******************************************************************************/
4361 Datastore::Datastore(const OriginMetadata& aOriginMetadata,
4362 uint32_t aPrivateBrowsingId, int64_t aUsage,
4363 int64_t aSizeOfKeys, int64_t aSizeOfItems,
4364 RefPtr<DirectoryLock>&& aDirectoryLock,
4365 RefPtr<Connection>&& aConnection,
4366 RefPtr<QuotaObject>&& aQuotaObject,
4367 nsTHashMap<nsStringHashKey, LSValue>& aValues,
4368 nsTArray<LSItemInfo>&& aOrderedItems)
4369 : mDirectoryLock(std::move(aDirectoryLock)),
4370 mConnection(std::move(aConnection)),
4371 mQuotaObject(std::move(aQuotaObject)),
4372 mOrderedItems(std::move(aOrderedItems)),
4373 mOriginMetadata(aOriginMetadata),
4374 mPrivateBrowsingId(aPrivateBrowsingId),
4375 mUsage(aUsage),
4376 mUpdateBatchUsage(-1),
4377 mSizeOfKeys(aSizeOfKeys),
4378 mSizeOfItems(aSizeOfItems),
4379 mClosed(false),
4380 mInUpdateBatch(false),
4381 mHasLivePrivateDatastore(false) {
4382 AssertIsOnBackgroundThread();
4384 mValues.SwapElements(aValues);
4387 Datastore::~Datastore() {
4388 AssertIsOnBackgroundThread();
4389 MOZ_ASSERT(mClosed);
4392 void Datastore::Close() {
4393 AssertIsOnBackgroundThread();
4394 MOZ_ASSERT(!mClosed);
4395 MOZ_ASSERT(!mPrepareDatastoreOps.Count());
4396 MOZ_ASSERT(!mPreparedDatastores.Count());
4397 MOZ_ASSERT(!mDatabases.Count());
4398 MOZ_ASSERT(mDirectoryLock);
4400 mClosed = true;
4402 if (IsPersistent()) {
4403 MOZ_ASSERT(mConnection);
4404 MOZ_ASSERT(mQuotaObject);
4406 // We can't release the directory lock and unregister itself from the
4407 // hashtable until the connection is fully closed.
4408 nsCOMPtr<nsIRunnable> callback =
4409 NewRunnableMethod("dom::Datastore::ConnectionClosedCallback", this,
4410 &Datastore::ConnectionClosedCallback);
4411 mConnection->Close(callback);
4412 } else {
4413 MOZ_ASSERT(!mConnection);
4414 MOZ_ASSERT(!mQuotaObject);
4416 // There's no connection, so it's safe to release the directory lock and
4417 // unregister itself from the hashtable.
4419 mDirectoryLock = nullptr;
4421 CleanupMetadata();
4425 void Datastore::WaitForConnectionToComplete(nsIRunnable* aCallback) {
4426 AssertIsOnBackgroundThread();
4427 MOZ_ASSERT(aCallback);
4428 MOZ_ASSERT(!mCompleteCallback);
4429 MOZ_ASSERT(mClosed);
4431 mCompleteCallback = aCallback;
4434 void Datastore::NoteLivePrepareDatastoreOp(
4435 PrepareDatastoreOp* aPrepareDatastoreOp) {
4436 AssertIsOnBackgroundThread();
4437 MOZ_ASSERT(aPrepareDatastoreOp);
4438 MOZ_ASSERT(!mPrepareDatastoreOps.Contains(aPrepareDatastoreOp));
4439 MOZ_ASSERT(mDirectoryLock);
4440 MOZ_ASSERT(!mClosed);
4442 mPrepareDatastoreOps.Insert(aPrepareDatastoreOp);
4445 void Datastore::NoteFinishedPrepareDatastoreOp(
4446 PrepareDatastoreOp* aPrepareDatastoreOp) {
4447 AssertIsOnBackgroundThread();
4448 MOZ_ASSERT(aPrepareDatastoreOp);
4449 MOZ_ASSERT(mPrepareDatastoreOps.Contains(aPrepareDatastoreOp));
4450 MOZ_ASSERT(mDirectoryLock);
4451 MOZ_ASSERT(!mClosed);
4453 mPrepareDatastoreOps.Remove(aPrepareDatastoreOp);
4455 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4456 quota::Client::LS, "PrepareDatastoreOp finished"_ns);
4458 MaybeClose();
4461 void Datastore::NoteLivePrivateDatastore() {
4462 AssertIsOnBackgroundThread();
4463 MOZ_ASSERT(!mHasLivePrivateDatastore);
4464 MOZ_ASSERT(mDirectoryLock);
4465 MOZ_ASSERT(!mClosed);
4467 mHasLivePrivateDatastore = true;
4470 void Datastore::NoteFinishedPrivateDatastore() {
4471 AssertIsOnBackgroundThread();
4472 MOZ_ASSERT(mHasLivePrivateDatastore);
4473 MOZ_ASSERT(mDirectoryLock);
4474 MOZ_ASSERT(!mClosed);
4476 mHasLivePrivateDatastore = false;
4478 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4479 quota::Client::LS, "PrivateDatastore finished"_ns);
4481 MaybeClose();
4484 void Datastore::NoteLivePreparedDatastore(
4485 PreparedDatastore* aPreparedDatastore) {
4486 AssertIsOnBackgroundThread();
4487 MOZ_ASSERT(aPreparedDatastore);
4488 MOZ_ASSERT(!mPreparedDatastores.Contains(aPreparedDatastore));
4489 MOZ_ASSERT(mDirectoryLock);
4490 MOZ_ASSERT(!mClosed);
4492 mPreparedDatastores.Insert(aPreparedDatastore);
4495 void Datastore::NoteFinishedPreparedDatastore(
4496 PreparedDatastore* aPreparedDatastore) {
4497 AssertIsOnBackgroundThread();
4498 MOZ_ASSERT(aPreparedDatastore);
4499 MOZ_ASSERT(mPreparedDatastores.Contains(aPreparedDatastore));
4500 MOZ_ASSERT(mDirectoryLock);
4501 MOZ_ASSERT(!mClosed);
4503 mPreparedDatastores.Remove(aPreparedDatastore);
4505 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4506 quota::Client::LS, "PreparedDatastore finished"_ns);
4508 MaybeClose();
4511 bool Datastore::HasOtherProcessDatabases(Database* aDatabase) {
4512 AssertIsOnBackgroundThread();
4514 PBackgroundParent* databaseBackgroundActor = aDatabase->Manager();
4516 for (Database* database : mDatabases) {
4517 if (database->Manager() != databaseBackgroundActor) {
4518 return true;
4522 return false;
4525 void Datastore::NoteLiveDatabase(Database* aDatabase) {
4526 AssertIsOnBackgroundThread();
4527 MOZ_ASSERT(aDatabase);
4528 MOZ_ASSERT(!mDatabases.Contains(aDatabase));
4529 MOZ_ASSERT(mDirectoryLock);
4530 MOZ_ASSERT(!mClosed);
4532 mDatabases.Insert(aDatabase);
4534 NoteChangedDatabaseMap();
4537 void Datastore::NoteFinishedDatabase(Database* aDatabase) {
4538 AssertIsOnBackgroundThread();
4539 MOZ_ASSERT(aDatabase);
4540 MOZ_ASSERT(mDatabases.Contains(aDatabase));
4541 MOZ_ASSERT(!mActiveDatabases.Contains(aDatabase));
4542 MOZ_ASSERT(mDirectoryLock);
4543 MOZ_ASSERT(!mClosed);
4545 mDatabases.Remove(aDatabase);
4547 NoteChangedDatabaseMap();
4549 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS,
4550 "Database finished"_ns);
4552 MaybeClose();
4555 void Datastore::NoteActiveDatabase(Database* aDatabase) {
4556 AssertIsOnBackgroundThread();
4557 MOZ_ASSERT(aDatabase);
4558 MOZ_ASSERT(mDatabases.Contains(aDatabase));
4559 MOZ_ASSERT(!mActiveDatabases.Contains(aDatabase));
4560 MOZ_ASSERT(!mClosed);
4562 mActiveDatabases.Insert(aDatabase);
4565 void Datastore::NoteInactiveDatabase(Database* aDatabase) {
4566 AssertIsOnBackgroundThread();
4567 MOZ_ASSERT(aDatabase);
4568 MOZ_ASSERT(mDatabases.Contains(aDatabase));
4569 MOZ_ASSERT(mActiveDatabases.Contains(aDatabase));
4570 MOZ_ASSERT(!mClosed);
4572 mActiveDatabases.Remove(aDatabase);
4574 if (!mActiveDatabases.Count() && mPendingUsageDeltas.Length()) {
4575 int64_t finalDelta = 0;
4577 for (auto delta : mPendingUsageDeltas) {
4578 finalDelta += delta;
4581 MOZ_ASSERT(finalDelta <= 0);
4583 if (finalDelta != 0) {
4584 DebugOnly<bool> ok = UpdateUsage(finalDelta);
4585 MOZ_ASSERT(ok);
4588 mPendingUsageDeltas.Clear();
4592 void Datastore::GetSnapshotLoadInfo(const nsAString& aKey,
4593 bool& aAddKeyToUnknownItems,
4594 nsTHashtable<nsStringHashKey>& aLoadedItems,
4595 nsTArray<LSItemInfo>& aItemInfos,
4596 uint32_t& aNextLoadIndex,
4597 LSSnapshot::LoadState& aLoadState) {
4598 AssertIsOnBackgroundThread();
4599 MOZ_ASSERT(!mClosed);
4600 MOZ_ASSERT(!mInUpdateBatch);
4602 #ifdef DEBUG
4603 int64_t sizeOfKeys = 0;
4604 int64_t sizeOfItems = 0;
4605 for (auto item : mOrderedItems) {
4606 int64_t sizeOfKey = static_cast<int64_t>(item.key().Length());
4607 sizeOfKeys += sizeOfKey;
4608 sizeOfItems += sizeOfKey + static_cast<int64_t>(item.value().Length());
4610 MOZ_ASSERT(mSizeOfKeys == sizeOfKeys);
4611 MOZ_ASSERT(mSizeOfItems == sizeOfItems);
4612 #endif
4614 // Computes load state optimized for current size of keys and items.
4615 // Zero key length and value can be passed to do a quick initial estimation.
4616 // If computed load state is already AllOrderedItems then excluded key length
4617 // and value length can't make it any better.
4618 auto GetLoadState = [&](int64_t aKeyLength, int64_t aValueLength) {
4619 if (mSizeOfKeys - aKeyLength <= gSnapshotPrefill) {
4620 if (mSizeOfItems - aKeyLength - aValueLength <= gSnapshotPrefill) {
4621 return LSSnapshot::LoadState::AllOrderedItems;
4624 return LSSnapshot::LoadState::AllOrderedKeys;
4627 return LSSnapshot::LoadState::Partial;
4630 // Value for given aKey if aKey is not void (can be void too if value doesn't
4631 // exist for given aKey).
4632 LSValue value;
4633 // If aKey and value are not void, checkKey will be set to true. Once we find
4634 // an item for given aKey in one of the loops below, checkKey is set to false
4635 // to prevent additional comparison of strings (string implementation compares
4636 // string lengths first to avoid char by char comparison if possible).
4637 bool checkKey = false;
4639 // Avoid additional hash lookup if all ordered items fit into initial prefill
4640 // already.
4641 LSSnapshot::LoadState loadState = GetLoadState(/* aKeyLength */ 0,
4642 /* aValueLength */ 0);
4643 if (loadState != LSSnapshot::LoadState::AllOrderedItems && !aKey.IsVoid()) {
4644 GetItem(aKey, value);
4645 if (!value.IsVoid()) {
4646 // Ok, we have a non void aKey and value.
4648 // We have to watch for aKey during one of the loops below to exclude it
4649 // from the size computation. The super fast mode (AllOrderedItems)
4650 // doesn't have to do that though.
4651 checkKey = true;
4653 // We have to compute load state again because aKey length and value
4654 // length is excluded from the size in this case.
4655 loadState = GetLoadState(aKey.Length(), value.Length());
4659 switch (loadState) {
4660 case LSSnapshot::LoadState::AllOrderedItems: {
4661 // We're sending all ordered items, we don't need to check keys because
4662 // mOrderedItems must contain a value for aKey if checkKey is true.
4664 aItemInfos.AppendElements(mOrderedItems);
4666 MOZ_ASSERT(aItemInfos.Length() == mValues.Count());
4667 aNextLoadIndex = mValues.Count();
4669 aAddKeyToUnknownItems = false;
4671 break;
4674 case LSSnapshot::LoadState::AllOrderedKeys: {
4675 // We don't have enough snapshot budget to send all items, but we do have
4676 // enough to send all of the keys and to make a best effort to populate as
4677 // many values as possible. We send void string values once we run out of
4678 // budget. A complicating factor is that we want to make sure that we send
4679 // the value for aKey which is a localStorage read that's triggering this
4680 // request. Since that key can happen anywhere in the list of items, we
4681 // need to handle it specially.
4683 // The loop is effectively doing 2 things in parallel:
4685 // 1. Looking for the `aKey` to send. This is tracked by `checkKey`
4686 // which is true if there was an `aKey` specified and until we
4687 // populate its value, and false thereafter.
4688 // 2. Sending values until we run out of `size` budget and switch to
4689 // sending void values. `doneSendingValues` tracks when we've run out
4690 // of size budget, with `setVoidValue` tracking whether a value
4691 // should be sent for each turn of the event loop but can be
4692 // overridden when `aKey` is found.
4694 int64_t size = mSizeOfKeys;
4695 bool setVoidValue = false;
4696 bool doneSendingValues = false;
4697 for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
4698 const LSItemInfo& item = mOrderedItems[index];
4700 const nsString& key = item.key();
4701 const LSValue& value = item.value();
4703 if (checkKey && key == aKey) {
4704 checkKey = false;
4705 setVoidValue = false;
4706 } else if (!setVoidValue) {
4707 if (doneSendingValues) {
4708 setVoidValue = true;
4709 } else {
4710 size += static_cast<int64_t>(value.Length());
4712 if (size > gSnapshotPrefill) {
4713 setVoidValue = true;
4714 doneSendingValues = true;
4716 // We set doneSendingValues to true and that will guard against
4717 // entering this branch during next iterations. So aNextLoadIndex
4718 // is set only once.
4719 aNextLoadIndex = index;
4724 LSItemInfo* itemInfo = aItemInfos.AppendElement();
4725 itemInfo->key() = key;
4726 if (setVoidValue) {
4727 itemInfo->value().SetIsVoid(true);
4728 } else {
4729 aLoadedItems.PutEntry(key);
4730 itemInfo->value() = value;
4734 aAddKeyToUnknownItems = false;
4736 break;
4739 case LSSnapshot::LoadState::Partial: {
4740 int64_t size = 0;
4741 for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
4742 const LSItemInfo& item = mOrderedItems[index];
4744 const nsString& key = item.key();
4745 const LSValue& value = item.value();
4747 if (checkKey && key == aKey) {
4748 checkKey = false;
4749 } else {
4750 size += static_cast<int64_t>(key.Length()) +
4751 static_cast<int64_t>(value.Length());
4753 if (size > gSnapshotPrefill) {
4754 aNextLoadIndex = index;
4755 break;
4759 aLoadedItems.PutEntry(key);
4761 LSItemInfo* itemInfo = aItemInfos.AppendElement();
4762 itemInfo->key() = key;
4763 itemInfo->value() = value;
4766 aAddKeyToUnknownItems = false;
4768 if (!aKey.IsVoid()) {
4769 if (value.IsVoid()) {
4770 aAddKeyToUnknownItems = true;
4771 } else if (checkKey) {
4772 // The item wasn't added in the loop above, add it here.
4774 LSItemInfo* itemInfo = aItemInfos.AppendElement();
4775 itemInfo->key() = aKey;
4776 itemInfo->value() = value;
4780 MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length());
4782 break;
4785 default:
4786 MOZ_CRASH("Bad load state value!");
4789 aLoadState = loadState;
4792 void Datastore::GetItem(const nsAString& aKey, LSValue& aValue) const {
4793 AssertIsOnBackgroundThread();
4794 MOZ_ASSERT(!mClosed);
4796 if (!mValues.Get(aKey, &aValue)) {
4797 aValue.SetIsVoid(true);
4801 void Datastore::GetKeys(nsTArray<nsString>& aKeys) const {
4802 AssertIsOnBackgroundThread();
4803 MOZ_ASSERT(!mClosed);
4805 for (auto item : mOrderedItems) {
4806 aKeys.AppendElement(item.key());
4810 void Datastore::SetItem(Database* aDatabase, const nsString& aKey,
4811 const LSValue& aValue) {
4812 AssertIsOnBackgroundThread();
4813 MOZ_ASSERT(aDatabase);
4814 MOZ_ASSERT(!mClosed);
4815 MOZ_ASSERT(mInUpdateBatch);
4817 LSValue oldValue;
4818 GetItem(aKey, oldValue);
4820 if (oldValue != aValue) {
4821 bool isNewItem = oldValue.IsVoid();
4823 NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem);
4825 mValues.InsertOrUpdate(aKey, aValue);
4827 int64_t delta;
4829 if (isNewItem) {
4830 mWriteOptimizer.InsertItem(aKey, aValue);
4832 int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
4834 delta = sizeOfKey + static_cast<int64_t>(aValue.UTF16Length());
4836 mUpdateBatchUsage += delta;
4838 mSizeOfKeys += sizeOfKey;
4839 mSizeOfItems += sizeOfKey + static_cast<int64_t>(aValue.Length());
4840 } else {
4841 mWriteOptimizer.UpdateItem(aKey, aValue);
4843 delta = static_cast<int64_t>(aValue.UTF16Length()) -
4844 static_cast<int64_t>(oldValue.UTF16Length());
4846 mUpdateBatchUsage += delta;
4848 mSizeOfItems += static_cast<int64_t>(aValue.Length()) -
4849 static_cast<int64_t>(oldValue.Length());
4852 if (IsPersistent()) {
4853 mConnection->SetItem(aKey, aValue, delta, isNewItem);
4858 void Datastore::RemoveItem(Database* aDatabase, const nsString& aKey) {
4859 AssertIsOnBackgroundThread();
4860 MOZ_ASSERT(aDatabase);
4861 MOZ_ASSERT(!mClosed);
4862 MOZ_ASSERT(mInUpdateBatch);
4864 LSValue oldValue;
4865 GetItem(aKey, oldValue);
4867 if (!oldValue.IsVoid()) {
4868 NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true);
4870 mValues.Remove(aKey);
4872 mWriteOptimizer.DeleteItem(aKey);
4874 int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
4876 int64_t delta = -sizeOfKey - static_cast<int64_t>(oldValue.UTF16Length());
4878 mUpdateBatchUsage += delta;
4880 mSizeOfKeys -= sizeOfKey;
4881 mSizeOfItems -= sizeOfKey + static_cast<int64_t>(oldValue.Length());
4883 if (IsPersistent()) {
4884 mConnection->RemoveItem(aKey, delta);
4889 void Datastore::Clear(Database* aDatabase) {
4890 AssertIsOnBackgroundThread();
4891 MOZ_ASSERT(!mClosed);
4893 if (mValues.Count()) {
4894 int64_t delta = 0;
4895 for (const auto& entry : mValues) {
4896 const nsAString& key = entry.GetKey();
4897 const LSValue& value = entry.GetData();
4899 delta += -static_cast<int64_t>(key.Length()) -
4900 static_cast<int64_t>(value.UTF16Length());
4902 NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
4905 mValues.Clear();
4907 if (mInUpdateBatch) {
4908 mWriteOptimizer.Truncate();
4910 mUpdateBatchUsage += delta;
4911 } else {
4912 mOrderedItems.Clear();
4914 DebugOnly<bool> ok = UpdateUsage(delta);
4915 MOZ_ASSERT(ok);
4918 mSizeOfKeys = 0;
4919 mSizeOfItems = 0;
4921 if (IsPersistent()) {
4922 mConnection->Clear(delta);
4927 void Datastore::BeginUpdateBatch(int64_t aSnapshotUsage) {
4928 AssertIsOnBackgroundThread();
4929 // Don't assert `aSnapshotUsage >= 0`, it can be negative when multiple
4930 // snapshots are operating in parallel.
4931 MOZ_ASSERT(!mClosed);
4932 MOZ_ASSERT(mUpdateBatchUsage == -1);
4933 MOZ_ASSERT(!mInUpdateBatch);
4935 mUpdateBatchUsage = aSnapshotUsage;
4937 if (IsPersistent()) {
4938 mConnection->BeginUpdateBatch();
4941 mInUpdateBatch = true;
4944 int64_t Datastore::EndUpdateBatch(int64_t aSnapshotPeakUsage) {
4945 AssertIsOnBackgroundThread();
4946 MOZ_ASSERT(!mClosed);
4947 MOZ_ASSERT(mInUpdateBatch);
4949 mWriteOptimizer.ApplyAndReset(mOrderedItems);
4951 MOZ_ASSERT(!mWriteOptimizer.HasWrites());
4953 if (aSnapshotPeakUsage >= 0) {
4954 int64_t delta = mUpdateBatchUsage - aSnapshotPeakUsage;
4956 if (mActiveDatabases.Count()) {
4957 // We can't apply deltas while other databases are still active.
4958 // The final delta must be zero or negative, but individual deltas can
4959 // be positive. A positive delta can't be applied asynchronously since
4960 // there's no way to fire the quota exceeded error event.
4962 mPendingUsageDeltas.AppendElement(delta);
4963 } else {
4964 MOZ_ASSERT(delta <= 0);
4965 if (delta != 0) {
4966 DebugOnly<bool> ok = UpdateUsage(delta);
4967 MOZ_ASSERT(ok);
4972 int64_t result = mUpdateBatchUsage;
4973 mUpdateBatchUsage = -1;
4975 if (IsPersistent()) {
4976 mConnection->EndUpdateBatch();
4979 mInUpdateBatch = false;
4981 return result;
4984 int64_t Datastore::AttemptToUpdateUsage(int64_t aMinSize, bool aInitial) {
4985 AssertIsOnBackgroundThread();
4986 MOZ_ASSERT_IF(aInitial, aMinSize >= 0);
4987 MOZ_ASSERT_IF(!aInitial, aMinSize > 0);
4989 const int64_t size = aMinSize + GetSnapshotPeakUsagePreincrement(aInitial);
4991 if (size && UpdateUsage(size)) {
4992 return size;
4995 const int64_t reducedSize =
4996 aMinSize + GetSnapshotPeakUsageReducedPreincrement(aInitial);
4998 if (reducedSize && UpdateUsage(reducedSize)) {
4999 return reducedSize;
5002 if (aMinSize > 0 && UpdateUsage(aMinSize)) {
5003 return aMinSize;
5006 return 0;
5009 bool Datastore::HasOtherProcessObservers(Database* aDatabase) {
5010 AssertIsOnBackgroundThread();
5011 MOZ_ASSERT(aDatabase);
5013 if (!gObservers) {
5014 return false;
5017 nsTArray<NotNull<Observer*>>* array;
5018 if (!gObservers->Get(mOriginMetadata.mOrigin, &array)) {
5019 return false;
5022 MOZ_ASSERT(array);
5024 PBackgroundParent* databaseBackgroundActor = aDatabase->Manager();
5026 for (Observer* observer : *array) {
5027 if (observer->Manager() != databaseBackgroundActor) {
5028 return true;
5032 return false;
5035 void Datastore::NotifyOtherProcessObservers(Database* aDatabase,
5036 const nsString& aDocumentURI,
5037 const nsString& aKey,
5038 const LSValue& aOldValue,
5039 const LSValue& aNewValue) {
5040 AssertIsOnBackgroundThread();
5041 MOZ_ASSERT(aDatabase);
5043 if (!gObservers) {
5044 return;
5047 nsTArray<NotNull<Observer*>>* array;
5048 if (!gObservers->Get(mOriginMetadata.mOrigin, &array)) {
5049 return;
5052 MOZ_ASSERT(array);
5054 // We do not want to send information about events back to the content process
5055 // that caused the change.
5056 PBackgroundParent* databaseBackgroundActor = aDatabase->Manager();
5058 for (Observer* observer : *array) {
5059 if (observer->Manager() != databaseBackgroundActor) {
5060 observer->Observe(aDatabase, aDocumentURI, aKey, aOldValue, aNewValue);
5065 void Datastore::NoteChangedObserverArray(
5066 const nsTArray<NotNull<Observer*>>& aObservers) {
5067 AssertIsOnBackgroundThread();
5069 for (Database* database : mActiveDatabases) {
5070 Snapshot* snapshot = database->GetSnapshot();
5071 MOZ_ASSERT(snapshot);
5073 if (snapshot->IsDirty()) {
5074 continue;
5077 bool hasOtherProcessObservers = false;
5079 PBackgroundParent* databaseBackgroundActor = database->Manager();
5081 for (Observer* observer : aObservers) {
5082 if (observer->Manager() != databaseBackgroundActor) {
5083 hasOtherProcessObservers = true;
5084 break;
5088 if (snapshot->HasOtherProcessObservers() != hasOtherProcessObservers) {
5089 snapshot->MarkDirty();
5094 void Datastore::Stringify(nsACString& aResult) const {
5095 AssertIsOnBackgroundThread();
5097 aResult.AppendLiteral("DirectoryLock:");
5098 aResult.AppendInt(!!mDirectoryLock);
5099 aResult.Append(kQuotaGenericDelimiter);
5101 aResult.AppendLiteral("Connection:");
5102 aResult.AppendInt(!!mConnection);
5103 aResult.Append(kQuotaGenericDelimiter);
5105 aResult.AppendLiteral("QuotaObject:");
5106 aResult.AppendInt(!!mQuotaObject);
5107 aResult.Append(kQuotaGenericDelimiter);
5109 aResult.AppendLiteral("PrepareDatastoreOps:");
5110 aResult.AppendInt(mPrepareDatastoreOps.Count());
5111 aResult.Append(kQuotaGenericDelimiter);
5113 aResult.AppendLiteral("PreparedDatastores:");
5114 aResult.AppendInt(mPreparedDatastores.Count());
5115 aResult.Append(kQuotaGenericDelimiter);
5117 aResult.AppendLiteral("Databases:");
5118 aResult.AppendInt(mDatabases.Count());
5119 aResult.Append(kQuotaGenericDelimiter);
5121 aResult.AppendLiteral("ActiveDatabases:");
5122 aResult.AppendInt(mActiveDatabases.Count());
5123 aResult.Append(kQuotaGenericDelimiter);
5125 aResult.AppendLiteral("Origin:");
5126 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin));
5127 aResult.Append(kQuotaGenericDelimiter);
5129 aResult.AppendLiteral("PrivateBrowsingId:");
5130 aResult.AppendInt(mPrivateBrowsingId);
5131 aResult.Append(kQuotaGenericDelimiter);
5133 aResult.AppendLiteral("Closed:");
5134 aResult.AppendInt(mClosed);
5137 bool Datastore::UpdateUsage(int64_t aDelta) {
5138 AssertIsOnBackgroundThread();
5140 // Check internal LocalStorage origin limit.
5141 int64_t newUsage = mUsage + aDelta;
5143 MOZ_ASSERT(newUsage >= 0);
5145 if (newUsage > StaticPrefs::dom_storage_default_quota() * 1024) {
5146 return false;
5149 // Check QuotaManager limits (group and global limit).
5150 if (IsPersistent()) {
5151 MOZ_ASSERT(mQuotaObject);
5153 if (!mQuotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) {
5154 return false;
5158 // Quota checks passed, set new usage.
5159 mUsage = newUsage;
5161 return true;
5164 void Datastore::MaybeClose() {
5165 AssertIsOnBackgroundThread();
5167 if (!mPrepareDatastoreOps.Count() && !mHasLivePrivateDatastore &&
5168 !mPreparedDatastores.Count() && !mDatabases.Count()) {
5169 Close();
5173 void Datastore::ConnectionClosedCallback() {
5174 AssertIsOnBackgroundThread();
5175 MOZ_ASSERT(mDirectoryLock);
5176 MOZ_ASSERT(mConnection);
5177 MOZ_ASSERT(mQuotaObject);
5178 MOZ_ASSERT(mClosed);
5180 // Release the quota object first.
5181 mQuotaObject = nullptr;
5183 bool databaseWasNotAvailable;
5184 bool hasCreatedDatabase;
5185 mConnection->GetFinishInfo(databaseWasNotAvailable, hasCreatedDatabase);
5187 if (databaseWasNotAvailable && !hasCreatedDatabase) {
5188 MOZ_ASSERT(mUsage == 0);
5190 QuotaManager* quotaManager = QuotaManager::Get();
5191 MOZ_ASSERT(quotaManager);
5193 quotaManager->ResetUsageForClient(
5194 ClientMetadata{mOriginMetadata, mozilla::dom::quota::Client::LS});
5197 mConnection = nullptr;
5199 // Now it's safe to release the directory lock and unregister itself from
5200 // the hashtable.
5202 mDirectoryLock = nullptr;
5204 CleanupMetadata();
5206 if (mCompleteCallback) {
5207 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget()));
5211 void Datastore::CleanupMetadata() {
5212 AssertIsOnBackgroundThread();
5214 MOZ_ASSERT(gDatastores);
5215 const DebugOnly<bool> removed = gDatastores->Remove(mOriginMetadata.mOrigin);
5216 MOZ_ASSERT(removed);
5218 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS,
5219 "Datastore removed"_ns);
5221 if (!gDatastores->Count()) {
5222 gDatastores = nullptr;
5226 void Datastore::NotifySnapshots(Database* aDatabase, const nsAString& aKey,
5227 const LSValue& aOldValue, bool aAffectsOrder) {
5228 AssertIsOnBackgroundThread();
5230 for (Database* database : mDatabases) {
5231 MOZ_ASSERT(database);
5233 if (database == aDatabase) {
5234 continue;
5237 Snapshot* snapshot = database->GetSnapshot();
5238 if (snapshot) {
5239 snapshot->SaveItem(aKey, aOldValue, aAffectsOrder);
5244 void Datastore::NoteChangedDatabaseMap() {
5245 AssertIsOnBackgroundThread();
5247 for (Database* database : mActiveDatabases) {
5248 Snapshot* snapshot = database->GetSnapshot();
5249 MOZ_ASSERT(snapshot);
5251 if (snapshot->IsDirty()) {
5252 continue;
5255 if (snapshot->HasOtherProcessDatabases() !=
5256 HasOtherProcessDatabases(database)) {
5257 snapshot->MarkDirty();
5262 /*******************************************************************************
5263 * PreparedDatastore
5264 ******************************************************************************/
5266 void PreparedDatastore::Destroy() {
5267 AssertIsOnBackgroundThread();
5268 MOZ_ASSERT(gPreparedDatastores);
5269 DebugOnly<bool> removed = gPreparedDatastores->Remove(mDatastoreId);
5270 MOZ_ASSERT(removed);
5273 // static
5274 void PreparedDatastore::TimerCallback(nsITimer* aTimer, void* aClosure) {
5275 AssertIsOnBackgroundThread();
5277 auto* self = static_cast<PreparedDatastore*>(aClosure);
5278 MOZ_ASSERT(self);
5280 self->Destroy();
5283 /*******************************************************************************
5284 * Database
5285 ******************************************************************************/
5287 Database::Database(const PrincipalInfo& aPrincipalInfo,
5288 const Maybe<ContentParentId>& aContentParentId,
5289 const nsACString& aOrigin, uint32_t aPrivateBrowsingId)
5290 : mSnapshot(nullptr),
5291 mPrincipalInfo(aPrincipalInfo),
5292 mContentParentId(aContentParentId),
5293 mOrigin(aOrigin),
5294 mPrivateBrowsingId(aPrivateBrowsingId),
5295 mAllowedToClose(false),
5296 mActorDestroyed(false),
5297 mRequestedAllowToClose(false)
5298 #ifdef DEBUG
5300 mActorWasAlive(false)
5301 #endif
5303 AssertIsOnBackgroundThread();
5306 Database::~Database() {
5307 MOZ_ASSERT_IF(mActorWasAlive, mAllowedToClose);
5308 MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
5311 void Database::SetActorAlive(Datastore* aDatastore) {
5312 AssertIsOnBackgroundThread();
5313 MOZ_ASSERT(!mActorWasAlive);
5314 MOZ_ASSERT(!mActorDestroyed);
5316 #ifdef DEBUG
5317 mActorWasAlive = true;
5318 #endif
5320 mDatastore = aDatastore;
5322 mDatastore->NoteLiveDatabase(this);
5324 if (!gLiveDatabases) {
5325 gLiveDatabases = new LiveDatabaseArray();
5328 gLiveDatabases->AppendElement(WrapNotNullUnchecked(this));
5331 void Database::RegisterSnapshot(Snapshot* aSnapshot) {
5332 AssertIsOnBackgroundThread();
5333 MOZ_ASSERT(aSnapshot);
5334 MOZ_ASSERT(!mSnapshot);
5335 MOZ_ASSERT(!mAllowedToClose);
5337 // Only one snapshot at a time is currently supported.
5338 mSnapshot = aSnapshot;
5340 mDatastore->NoteActiveDatabase(this);
5343 void Database::UnregisterSnapshot(Snapshot* aSnapshot) {
5344 MOZ_ASSERT(aSnapshot);
5345 MOZ_ASSERT(mSnapshot == aSnapshot);
5347 mSnapshot = nullptr;
5349 mDatastore->NoteInactiveDatabase(this);
5352 void Database::RequestAllowToClose() {
5353 AssertIsOnBackgroundThread();
5355 if (mRequestedAllowToClose) {
5356 return;
5359 mRequestedAllowToClose = true;
5361 // Send the RequestAllowToClose message to the child to avoid racing with the
5362 // child actor. Except the case when the actor was already destroyed.
5363 if (mActorDestroyed) {
5364 MOZ_ASSERT(mAllowedToClose);
5365 return;
5368 if (NS_WARN_IF(!SendRequestAllowToClose()) && !mSnapshot) {
5369 // This is not necessary, because there should be a runnable scheduled that
5370 // will call ActorDestroy which calls AllowToClose. However we can speedup
5371 // the shutdown a bit if we do it here directly, but only if there's no
5372 // registered snapshot.
5373 AllowToClose();
5377 void Database::ForceKill() {
5378 AssertIsOnBackgroundThread();
5380 if (mActorDestroyed) {
5381 MOZ_ASSERT(mAllowedToClose);
5382 return;
5385 Unused << PBackgroundLSDatabaseParent::Send__delete__(this);
5388 void Database::Stringify(nsACString& aResult) const {
5389 AssertIsOnBackgroundThread();
5391 aResult.AppendLiteral("SnapshotRegistered:");
5392 aResult.AppendInt(!!mSnapshot);
5393 aResult.Append(kQuotaGenericDelimiter);
5395 aResult.AppendLiteral("OtherProcessActor:");
5396 aResult.AppendInt(BackgroundParent::IsOtherProcessActor(Manager()));
5397 aResult.Append(kQuotaGenericDelimiter);
5399 aResult.AppendLiteral("Origin:");
5400 aResult.Append(AnonymizedOriginString(mOrigin));
5401 aResult.Append(kQuotaGenericDelimiter);
5403 aResult.AppendLiteral("PrivateBrowsingId:");
5404 aResult.AppendInt(mPrivateBrowsingId);
5405 aResult.Append(kQuotaGenericDelimiter);
5407 aResult.AppendLiteral("AllowedToClose:");
5408 aResult.AppendInt(mAllowedToClose);
5409 aResult.Append(kQuotaGenericDelimiter);
5411 aResult.AppendLiteral("ActorDestroyed:");
5412 aResult.AppendInt(mActorDestroyed);
5413 aResult.Append(kQuotaGenericDelimiter);
5415 aResult.AppendLiteral("RequestedAllowToClose:");
5416 aResult.AppendInt(mRequestedAllowToClose);
5419 void Database::AllowToClose() {
5420 AssertIsOnBackgroundThread();
5421 MOZ_ASSERT(!mAllowedToClose);
5422 MOZ_ASSERT(mDatastore);
5423 MOZ_ASSERT(!mSnapshot);
5425 mAllowedToClose = true;
5427 mDatastore->NoteFinishedDatabase(this);
5429 mDatastore = nullptr;
5431 MOZ_ASSERT(gLiveDatabases);
5432 gLiveDatabases->RemoveElement(this);
5434 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS,
5435 "Live database removed"_ns);
5437 if (gLiveDatabases->IsEmpty()) {
5438 gLiveDatabases = nullptr;
5442 void Database::ActorDestroy(ActorDestroyReason aWhy) {
5443 AssertIsOnBackgroundThread();
5444 MOZ_ASSERT(!mActorDestroyed);
5446 mActorDestroyed = true;
5448 if (!mAllowedToClose) {
5449 AllowToClose();
5453 mozilla::ipc::IPCResult Database::RecvDeleteMe() {
5454 AssertIsOnBackgroundThread();
5455 MOZ_ASSERT(!mActorDestroyed);
5457 IProtocol* mgr = Manager();
5458 if (!PBackgroundLSDatabaseParent::Send__delete__(this)) {
5459 return IPC_FAIL(mgr, "Send__delete__ failed!");
5461 return IPC_OK();
5464 mozilla::ipc::IPCResult Database::RecvAllowToClose() {
5465 AssertIsOnBackgroundThread();
5467 if (NS_WARN_IF(mAllowedToClose)) {
5468 return IPC_FAIL(this, "mAllowedToClose already set!");
5471 AllowToClose();
5473 return IPC_OK();
5476 PBackgroundLSSnapshotParent* Database::AllocPBackgroundLSSnapshotParent(
5477 const nsAString& aDocumentURI, const nsAString& aKey,
5478 const bool& aIncreasePeakUsage, const int64_t& aMinSize,
5479 LSSnapshotInitInfo* aInitInfo) {
5480 AssertIsOnBackgroundThread();
5482 if (NS_WARN_IF(aIncreasePeakUsage && aMinSize < 0)) {
5483 MOZ_ASSERT_UNLESS_FUZZING(false);
5484 return nullptr;
5487 if (NS_WARN_IF(mAllowedToClose)) {
5488 MOZ_ASSERT_UNLESS_FUZZING(false);
5489 return nullptr;
5492 RefPtr<Snapshot> snapshot = new Snapshot(this, aDocumentURI);
5494 // Transfer ownership to IPDL.
5495 return snapshot.forget().take();
5498 mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor(
5499 PBackgroundLSSnapshotParent* aActor, const nsAString& aDocumentURI,
5500 const nsAString& aKey, const bool& aIncreasePeakUsage,
5501 const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) {
5502 AssertIsOnBackgroundThread();
5503 MOZ_ASSERT_IF(aIncreasePeakUsage, aMinSize >= 0);
5504 MOZ_ASSERT(aInitInfo);
5505 MOZ_ASSERT(!mAllowedToClose);
5507 auto* snapshot = static_cast<Snapshot*>(aActor);
5509 bool addKeyToUnknownItems;
5510 nsTHashtable<nsStringHashKey> loadedItems;
5511 nsTArray<LSItemInfo> itemInfos;
5512 uint32_t nextLoadIndex;
5513 LSSnapshot::LoadState loadState;
5514 mDatastore->GetSnapshotLoadInfo(aKey, addKeyToUnknownItems, loadedItems,
5515 itemInfos, nextLoadIndex, loadState);
5517 nsTHashSet<nsString> unknownItems;
5518 if (addKeyToUnknownItems) {
5519 unknownItems.Insert(aKey);
5522 uint32_t totalLength = mDatastore->GetLength();
5524 int64_t usage = mDatastore->GetUsage();
5526 int64_t peakUsage = usage;
5528 if (aIncreasePeakUsage) {
5529 int64_t size =
5530 mDatastore->AttemptToUpdateUsage(aMinSize, /* aInitial */ true);
5532 peakUsage += size;
5535 bool hasOtherProcessDatabases = mDatastore->HasOtherProcessDatabases(this);
5536 bool hasOtherProcessObservers = mDatastore->HasOtherProcessObservers(this);
5538 snapshot->Init(loadedItems, std::move(unknownItems), nextLoadIndex,
5539 totalLength, usage, peakUsage, loadState,
5540 hasOtherProcessDatabases, hasOtherProcessObservers);
5542 RegisterSnapshot(snapshot);
5544 aInitInfo->addKeyToUnknownItems() = addKeyToUnknownItems;
5545 aInitInfo->itemInfos() = std::move(itemInfos);
5546 aInitInfo->totalLength() = totalLength;
5547 aInitInfo->usage() = usage;
5548 aInitInfo->peakUsage() = peakUsage;
5549 aInitInfo->loadState() = loadState;
5550 aInitInfo->hasOtherProcessDatabases() = hasOtherProcessDatabases;
5551 aInitInfo->hasOtherProcessObservers() = hasOtherProcessObservers;
5553 return IPC_OK();
5556 bool Database::DeallocPBackgroundLSSnapshotParent(
5557 PBackgroundLSSnapshotParent* aActor) {
5558 AssertIsOnBackgroundThread();
5559 MOZ_ASSERT(aActor);
5561 // Transfer ownership back from IPDL.
5562 RefPtr<Snapshot> actor = dont_AddRef(static_cast<Snapshot*>(aActor));
5564 return true;
5567 /*******************************************************************************
5568 * Snapshot
5569 ******************************************************************************/
5571 Snapshot::Snapshot(Database* aDatabase, const nsAString& aDocumentURI)
5572 : mDatabase(aDatabase),
5573 mDatastore(aDatabase->GetDatastore()),
5574 mDocumentURI(aDocumentURI),
5575 mTotalLength(0),
5576 mUsage(-1),
5577 mPeakUsage(-1),
5578 mSavedKeys(false),
5579 mActorDestroyed(false),
5580 mFinishReceived(false),
5581 mLoadedReceived(false),
5582 mLoadedAllItems(false),
5583 mLoadKeysReceived(false),
5584 mSentMarkDirty(false) {
5585 AssertIsOnBackgroundThread();
5586 MOZ_ASSERT(aDatabase);
5589 Snapshot::~Snapshot() {
5590 MOZ_ASSERT(mActorDestroyed);
5591 MOZ_ASSERT(mFinishReceived);
5594 void Snapshot::SaveItem(const nsAString& aKey, const LSValue& aOldValue,
5595 bool aAffectsOrder) {
5596 AssertIsOnBackgroundThread();
5598 MarkDirty();
5600 if (mLoadedAllItems) {
5601 return;
5604 if (!mLoadedItems.Contains(aKey) && !mUnknownItems.Contains(aKey)) {
5605 mValues.LookupOrInsert(aKey, aOldValue);
5608 if (aAffectsOrder && !mSavedKeys) {
5609 mDatastore->GetKeys(mKeys);
5610 mSavedKeys = true;
5614 void Snapshot::MarkDirty() {
5615 AssertIsOnBackgroundThread();
5617 if (!mSentMarkDirty) {
5618 Unused << SendMarkDirty();
5619 mSentMarkDirty = true;
5623 void Snapshot::Finish() {
5624 AssertIsOnBackgroundThread();
5625 MOZ_ASSERT(mDatabase);
5626 MOZ_ASSERT(mDatastore);
5627 MOZ_ASSERT(!mFinishReceived);
5629 mDatastore->BeginUpdateBatch(mUsage);
5631 mDatastore->EndUpdateBatch(mPeakUsage);
5633 mDatabase->UnregisterSnapshot(this);
5635 mFinishReceived = true;
5638 void Snapshot::ActorDestroy(ActorDestroyReason aWhy) {
5639 AssertIsOnBackgroundThread();
5640 MOZ_ASSERT(!mActorDestroyed);
5642 mActorDestroyed = true;
5644 if (!mFinishReceived) {
5645 Finish();
5649 mozilla::ipc::IPCResult Snapshot::RecvDeleteMe() {
5650 AssertIsOnBackgroundThread();
5651 MOZ_ASSERT(!mActorDestroyed);
5653 IProtocol* mgr = Manager();
5654 if (!PBackgroundLSSnapshotParent::Send__delete__(this)) {
5655 return IPC_FAIL(mgr, "Send__delete__ failed!");
5657 return IPC_OK();
5660 mozilla::ipc::IPCResult Snapshot::Checkpoint(
5661 nsTArray<LSWriteInfo>&& aWriteInfos) {
5662 AssertIsOnBackgroundThread();
5663 // Don't assert `mUsage >= 0`, it can be negative when multiple snapshots are
5664 // operating in parallel.
5665 MOZ_ASSERT(mPeakUsage >= mUsage);
5667 if (NS_WARN_IF(aWriteInfos.IsEmpty())) {
5668 return IPC_FAIL(this, "aWriteInfos is empty!");
5671 if (NS_WARN_IF(mHasOtherProcessObservers)) {
5672 return IPC_FAIL(this, "mHasOtherProcessObservers already set!");
5675 mDatastore->BeginUpdateBatch(mUsage);
5677 for (uint32_t index = 0; index < aWriteInfos.Length(); index++) {
5678 const LSWriteInfo& writeInfo = aWriteInfos[index];
5680 switch (writeInfo.type()) {
5681 case LSWriteInfo::TLSSetItemInfo: {
5682 const LSSetItemInfo& info = writeInfo.get_LSSetItemInfo();
5684 mDatastore->SetItem(mDatabase, info.key(), info.value());
5686 break;
5689 case LSWriteInfo::TLSRemoveItemInfo: {
5690 const LSRemoveItemInfo& info = writeInfo.get_LSRemoveItemInfo();
5692 mDatastore->RemoveItem(mDatabase, info.key());
5694 break;
5697 case LSWriteInfo::TLSClearInfo: {
5698 mDatastore->Clear(mDatabase);
5700 break;
5703 default:
5704 MOZ_CRASH("Should never get here!");
5708 mUsage = mDatastore->EndUpdateBatch(-1);
5710 return IPC_OK();
5713 mozilla::ipc::IPCResult Snapshot::CheckpointAndNotify(
5714 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) {
5715 AssertIsOnBackgroundThread();
5716 // Don't assert `mUsage >= 0`, it can be negative when multiple snapshots are
5717 // operating in parallel.
5718 MOZ_ASSERT(mPeakUsage >= mUsage);
5720 if (NS_WARN_IF(aWriteAndNotifyInfos.IsEmpty())) {
5721 return IPC_FAIL(this, "aWriteAndNotifyInfos is empty!");
5724 if (NS_WARN_IF(!mHasOtherProcessObservers)) {
5725 return IPC_FAIL(this, "mHasOtherProcessObservers is not set!");
5728 mDatastore->BeginUpdateBatch(mUsage);
5730 for (uint32_t index = 0; index < aWriteAndNotifyInfos.Length(); index++) {
5731 const LSWriteAndNotifyInfo& writeAndNotifyInfo =
5732 aWriteAndNotifyInfos[index];
5734 switch (writeAndNotifyInfo.type()) {
5735 case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: {
5736 const LSSetItemAndNotifyInfo& info =
5737 writeAndNotifyInfo.get_LSSetItemAndNotifyInfo();
5739 mDatastore->SetItem(mDatabase, info.key(), info.value());
5741 mDatastore->NotifyOtherProcessObservers(
5742 mDatabase, mDocumentURI, info.key(), info.oldValue(), info.value());
5744 break;
5747 case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: {
5748 const LSRemoveItemAndNotifyInfo& info =
5749 writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo();
5751 mDatastore->RemoveItem(mDatabase, info.key());
5753 mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI,
5754 info.key(), info.oldValue(),
5755 VoidLSValue());
5757 break;
5760 case LSWriteAndNotifyInfo::TLSClearInfo: {
5761 mDatastore->Clear(mDatabase);
5763 mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI,
5764 VoidString(), VoidLSValue(),
5765 VoidLSValue());
5767 break;
5770 default:
5771 MOZ_CRASH("Should never get here!");
5775 mUsage = mDatastore->EndUpdateBatch(-1);
5777 return IPC_OK();
5780 mozilla::ipc::IPCResult Snapshot::RecvAsyncCheckpoint(
5781 nsTArray<LSWriteInfo>&& aWriteInfos) {
5782 return Checkpoint(std::move(aWriteInfos));
5785 mozilla::ipc::IPCResult Snapshot::RecvAsyncCheckpointAndNotify(
5786 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) {
5787 return CheckpointAndNotify(std::move(aWriteAndNotifyInfos));
5790 mozilla::ipc::IPCResult Snapshot::RecvSyncCheckpoint(
5791 nsTArray<LSWriteInfo>&& aWriteInfos) {
5792 return Checkpoint(std::move(aWriteInfos));
5795 mozilla::ipc::IPCResult Snapshot::RecvSyncCheckpointAndNotify(
5796 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) {
5797 return CheckpointAndNotify(std::move(aWriteAndNotifyInfos));
5800 mozilla::ipc::IPCResult Snapshot::RecvAsyncFinish() {
5801 AssertIsOnBackgroundThread();
5803 if (NS_WARN_IF(mFinishReceived)) {
5804 MOZ_ASSERT_UNLESS_FUZZING(false);
5805 return IPC_FAIL(this, "Already finished");
5808 Finish();
5810 return IPC_OK();
5813 mozilla::ipc::IPCResult Snapshot::RecvSyncFinish() {
5814 AssertIsOnBackgroundThread();
5816 if (NS_WARN_IF(mFinishReceived)) {
5817 MOZ_ASSERT_UNLESS_FUZZING(false);
5818 return IPC_FAIL(this, "Already finished");
5821 Finish();
5823 return IPC_OK();
5826 mozilla::ipc::IPCResult Snapshot::RecvLoaded() {
5827 AssertIsOnBackgroundThread();
5829 if (NS_WARN_IF(mFinishReceived)) {
5830 return IPC_FAIL(this, "mFinishReceived already set!");
5833 if (NS_WARN_IF(mLoadedReceived)) {
5834 return IPC_FAIL(this, "mLoadedReceived already set!");
5837 if (NS_WARN_IF(mLoadedAllItems)) {
5838 return IPC_FAIL(this, "mLoadedAllItems already set!");
5841 if (NS_WARN_IF(mLoadKeysReceived)) {
5842 return IPC_FAIL(this, "mLoadKeysReceived already set!");
5845 mLoadedReceived = true;
5847 mLoadedItems.Clear();
5848 mUnknownItems.Clear();
5849 mValues.Clear();
5850 mKeys.Clear();
5851 mLoadedAllItems = true;
5852 mLoadKeysReceived = true;
5854 return IPC_OK();
5857 mozilla::ipc::IPCResult Snapshot::RecvLoadValueAndMoreItems(
5858 const nsAString& aKey, LSValue* aValue, nsTArray<LSItemInfo>* aItemInfos) {
5859 AssertIsOnBackgroundThread();
5860 MOZ_ASSERT(aValue);
5861 MOZ_ASSERT(aItemInfos);
5862 MOZ_ASSERT(mDatastore);
5864 if (NS_WARN_IF(mFinishReceived)) {
5865 return IPC_FAIL(this, "mFinishReceived already set!");
5868 if (NS_WARN_IF(mLoadedReceived)) {
5869 return IPC_FAIL(this, "mLoadedReceived already set!");
5872 if (NS_WARN_IF(mLoadedAllItems)) {
5873 return IPC_FAIL(this, "mLoadedAllItems already set!");
5876 if (mLoadedItems.Contains(aKey)) {
5877 return IPC_FAIL(this, "mLoadedItems already contains aKey!");
5880 if (mUnknownItems.Contains(aKey)) {
5881 return IPC_FAIL(this, "mUnknownItems already contains aKey!");
5884 if (auto entry = mValues.Lookup(aKey)) {
5885 *aValue = entry.Data();
5886 entry.Remove();
5887 } else {
5888 mDatastore->GetItem(aKey, *aValue);
5891 if (aValue->IsVoid()) {
5892 mUnknownItems.Insert(aKey);
5893 } else {
5894 mLoadedItems.PutEntry(aKey);
5896 // mLoadedItems.Count()==mTotalLength is checked below.
5899 // Load some more key/value pairs (as many as the snapshot gradual prefill
5900 // byte budget allows).
5902 if (gSnapshotGradualPrefill > 0) {
5903 const nsTArray<LSItemInfo>& orderedItems = mDatastore->GetOrderedItems();
5905 uint32_t length;
5906 if (mSavedKeys) {
5907 length = mKeys.Length();
5908 } else {
5909 length = orderedItems.Length();
5912 int64_t size = 0;
5913 while (mNextLoadIndex < length) {
5914 // If the datastore's ordering has changed, mSavedKeys will be true and
5915 // mKeys contains an ordered list of the keys. Otherwise we can use the
5916 // datastore's key ordering which is still the same as when the snapshot
5917 // was created.
5919 nsString key;
5920 if (mSavedKeys) {
5921 key = mKeys[mNextLoadIndex];
5922 } else {
5923 key = orderedItems[mNextLoadIndex].key();
5926 // Normally we would do this:
5927 // if (!mLoadedItems.GetEntry(key)) {
5928 // ...
5929 // mLoadedItems.PutEntry(key);
5930 // }
5931 // but that requires two hash lookups. We can reduce that to just one
5932 // hash lookup if we always call PutEntry and check the number of entries
5933 // before and after the put (which is very cheap). However, if we reach
5934 // the prefill limit, we need to call RemoveEntry, but that is also cheap
5935 // because we pass the entry (not the key).
5937 uint32_t countBeforePut = mLoadedItems.Count();
5938 auto loadedItemEntry = mLoadedItems.PutEntry(key);
5939 if (countBeforePut != mLoadedItems.Count()) {
5940 // Check mValues first since that contains values as they existed when
5941 // our snapshot was created, but have since been changed/removed in the
5942 // datastore. If it's not there, then the datastore has the
5943 // still-current value. However, if the datastore's key ordering has
5944 // changed, we need to do a hash lookup rather than being able to do an
5945 // optimized direct access to the index.
5947 LSValue value;
5948 auto valueEntry = mValues.Lookup(key);
5949 if (valueEntry) {
5950 value = valueEntry.Data();
5951 } else if (mSavedKeys) {
5952 mDatastore->GetItem(nsString(key), value);
5953 } else {
5954 value = orderedItems[mNextLoadIndex].value();
5957 // All not loaded keys must have a value.
5958 MOZ_ASSERT(!value.IsVoid());
5960 size += static_cast<int64_t>(key.Length()) +
5961 static_cast<int64_t>(value.Length());
5963 if (size > gSnapshotGradualPrefill) {
5964 mLoadedItems.RemoveEntry(loadedItemEntry);
5966 // mNextLoadIndex is not incremented, so we will resume at the same
5967 // position next time.
5968 break;
5971 if (valueEntry) {
5972 valueEntry.Remove();
5975 LSItemInfo* itemInfo = aItemInfos->AppendElement();
5976 itemInfo->key() = key;
5977 itemInfo->value() = value;
5980 mNextLoadIndex++;
5984 if (mLoadedItems.Count() == mTotalLength) {
5985 mLoadedItems.Clear();
5986 mUnknownItems.Clear();
5987 #ifdef DEBUG
5988 const bool allValuesVoid =
5989 std::all_of(mValues.Values().cbegin(), mValues.Values().cend(),
5990 [](const auto& entry) { return entry.IsVoid(); });
5991 MOZ_ASSERT(allValuesVoid);
5992 #endif
5993 mValues.Clear();
5994 mLoadedAllItems = true;
5997 return IPC_OK();
6000 mozilla::ipc::IPCResult Snapshot::RecvLoadKeys(nsTArray<nsString>* aKeys) {
6001 AssertIsOnBackgroundThread();
6002 MOZ_ASSERT(aKeys);
6003 MOZ_ASSERT(mDatastore);
6005 if (NS_WARN_IF(mFinishReceived)) {
6006 return IPC_FAIL(this, "mFinishReceived already set!");
6009 if (NS_WARN_IF(mLoadedReceived)) {
6010 return IPC_FAIL(this, "mLoadedReceived already set!");
6013 if (NS_WARN_IF(mLoadKeysReceived)) {
6014 return IPC_FAIL(this, "mLoadKeysReceived already set!");
6017 mLoadKeysReceived = true;
6019 if (mSavedKeys) {
6020 aKeys->AppendElements(std::move(mKeys));
6021 } else {
6022 mDatastore->GetKeys(*aKeys);
6025 return IPC_OK();
6028 mozilla::ipc::IPCResult Snapshot::RecvIncreasePeakUsage(const int64_t& aMinSize,
6029 int64_t* aSize) {
6030 AssertIsOnBackgroundThread();
6031 MOZ_ASSERT(aSize);
6033 if (NS_WARN_IF(aMinSize <= 0)) {
6034 return IPC_FAIL(this, "aMinSize not valid!");
6037 if (NS_WARN_IF(mFinishReceived)) {
6038 return IPC_FAIL(this, "mFinishReceived already set!");
6041 int64_t size =
6042 mDatastore->AttemptToUpdateUsage(aMinSize, /* aInitial */ false);
6044 mPeakUsage += size;
6046 *aSize = size;
6048 return IPC_OK();
6051 /*******************************************************************************
6052 * Observer
6053 ******************************************************************************/
6055 Observer::Observer(const nsACString& aOrigin)
6056 : mOrigin(aOrigin), mActorDestroyed(false) {
6057 AssertIsOnBackgroundThread();
6060 Observer::~Observer() { MOZ_ASSERT(mActorDestroyed); }
6062 void Observer::Observe(Database* aDatabase, const nsString& aDocumentURI,
6063 const nsString& aKey, const LSValue& aOldValue,
6064 const LSValue& aNewValue) {
6065 AssertIsOnBackgroundThread();
6066 MOZ_ASSERT(aDatabase);
6068 Unused << SendObserve(aDatabase->GetPrincipalInfo(),
6069 aDatabase->PrivateBrowsingId(), aDocumentURI, aKey,
6070 aOldValue, aNewValue);
6073 void Observer::ActorDestroy(ActorDestroyReason aWhy) {
6074 AssertIsOnBackgroundThread();
6075 MOZ_ASSERT(!mActorDestroyed);
6077 mActorDestroyed = true;
6079 MOZ_ASSERT(gObservers);
6081 nsTArray<NotNull<Observer*>>* array;
6082 gObservers->Get(mOrigin, &array);
6083 MOZ_ASSERT(array);
6085 array->RemoveElement(this);
6087 if (RefPtr<Datastore> datastore = GetDatastore(mOrigin)) {
6088 datastore->NoteChangedObserverArray(*array);
6091 if (array->IsEmpty()) {
6092 gObservers->Remove(mOrigin);
6095 if (!gObservers->Count()) {
6096 gObservers = nullptr;
6100 mozilla::ipc::IPCResult Observer::RecvDeleteMe() {
6101 AssertIsOnBackgroundThread();
6102 MOZ_ASSERT(!mActorDestroyed);
6104 IProtocol* mgr = Manager();
6105 if (!PBackgroundLSObserverParent::Send__delete__(this)) {
6106 return IPC_FAIL(mgr, "Send__delete__ failed!");
6108 return IPC_OK();
6111 /*******************************************************************************
6112 * LSRequestBase
6113 ******************************************************************************/
6115 LSRequestBase::LSRequestBase(const LSRequestParams& aParams,
6116 const Maybe<ContentParentId>& aContentParentId)
6117 : mParams(aParams),
6118 mContentParentId(aContentParentId),
6119 mState(State::Initial),
6120 mWaitingForFinish(false) {}
6122 LSRequestBase::~LSRequestBase() {
6123 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
6124 mState == State::Initial || mState == State::Completed);
6127 void LSRequestBase::Dispatch() {
6128 AssertIsOnOwningThread();
6130 mState = State::StartingRequest;
6132 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
6135 void LSRequestBase::StringifyState(nsACString& aResult) const {
6136 AssertIsOnOwningThread();
6138 switch (mState) {
6139 case State::Initial:
6140 aResult.AppendLiteral("Initial");
6141 return;
6143 case State::StartingRequest:
6144 aResult.AppendLiteral("StartingRequest");
6145 return;
6147 case State::Nesting:
6148 aResult.AppendLiteral("Nesting");
6149 return;
6151 case State::SendingReadyMessage:
6152 aResult.AppendLiteral("SendingReadyMessage");
6153 return;
6155 case State::WaitingForFinish:
6156 aResult.AppendLiteral("WaitingForFinish");
6157 return;
6159 case State::SendingResults:
6160 aResult.AppendLiteral("SendingResults");
6161 return;
6163 case State::Completed:
6164 aResult.AppendLiteral("Completed");
6165 return;
6167 default:
6168 MOZ_CRASH("Bad state!");
6172 void LSRequestBase::Stringify(nsACString& aResult) const {
6173 AssertIsOnOwningThread();
6175 aResult.AppendLiteral("State:");
6176 StringifyState(aResult);
6179 void LSRequestBase::Log() {
6180 AssertIsOnOwningThread();
6182 if (!LS_LOG_TEST()) {
6183 return;
6186 LS_LOG(("LSRequestBase [%p]", this));
6188 nsCString state;
6189 StringifyState(state);
6191 LS_LOG((" mState: %s", state.get()));
6194 nsresult LSRequestBase::NestedRun() { return NS_OK; }
6196 bool LSRequestBase::VerifyRequestParams() {
6197 AssertIsOnBackgroundThread();
6199 MOZ_ASSERT(mParams.type() != LSRequestParams::T__None);
6201 switch (mParams.type()) {
6202 case LSRequestParams::TLSRequestPreloadDatastoreParams: {
6203 const LSRequestCommonParams& params =
6204 mParams.get_LSRequestPreloadDatastoreParams().commonParams();
6206 if (NS_WARN_IF(!VerifyPrincipalInfo(
6207 params.principalInfo(), params.storagePrincipalInfo(), false))) {
6208 return false;
6211 if (NS_WARN_IF(
6212 !VerifyOriginKey(params.originKey(), params.principalInfo()))) {
6213 return false;
6216 break;
6219 case LSRequestParams::TLSRequestPrepareDatastoreParams: {
6220 const LSRequestPrepareDatastoreParams& params =
6221 mParams.get_LSRequestPrepareDatastoreParams();
6223 const LSRequestCommonParams& commonParams = params.commonParams();
6225 if (NS_WARN_IF(!VerifyPrincipalInfo(commonParams.principalInfo(),
6226 commonParams.storagePrincipalInfo(),
6227 false))) {
6228 return false;
6231 if (params.clientPrincipalInfo() &&
6232 NS_WARN_IF(!VerifyPrincipalInfo(commonParams.principalInfo(),
6233 params.clientPrincipalInfo().ref(),
6234 true))) {
6235 return false;
6238 if (NS_WARN_IF(!VerifyClientId(mContentParentId,
6239 params.clientPrincipalInfo(),
6240 params.clientId()))) {
6241 return false;
6244 if (NS_WARN_IF(!VerifyOriginKey(commonParams.originKey(),
6245 commonParams.principalInfo()))) {
6246 return false;
6249 break;
6252 case LSRequestParams::TLSRequestPrepareObserverParams: {
6253 const LSRequestPrepareObserverParams& params =
6254 mParams.get_LSRequestPrepareObserverParams();
6256 if (NS_WARN_IF(!VerifyPrincipalInfo(
6257 params.principalInfo(), params.storagePrincipalInfo(), false))) {
6258 return false;
6261 if (params.clientPrincipalInfo() &&
6262 NS_WARN_IF(!VerifyPrincipalInfo(params.principalInfo(),
6263 params.clientPrincipalInfo().ref(),
6264 true))) {
6265 return false;
6268 if (NS_WARN_IF(!VerifyClientId(mContentParentId,
6269 params.clientPrincipalInfo(),
6270 params.clientId()))) {
6271 return false;
6274 break;
6277 default:
6278 MOZ_CRASH("Should never get here!");
6281 return true;
6284 nsresult LSRequestBase::StartRequest() {
6285 AssertIsOnOwningThread();
6286 MOZ_ASSERT(mState == State::StartingRequest);
6288 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6289 !MayProceed()) {
6290 return NS_ERROR_ABORT;
6293 #ifdef DEBUG
6294 // Always verify parameters in DEBUG builds!
6295 bool trustParams = false;
6296 #else
6297 bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
6298 #endif
6300 if (!trustParams && NS_WARN_IF(!VerifyRequestParams())) {
6301 return NS_ERROR_FAILURE;
6304 QM_TRY(MOZ_TO_RESULT(Start()));
6306 return NS_OK;
6309 void LSRequestBase::SendReadyMessage() {
6310 AssertIsOnOwningThread();
6311 MOZ_ASSERT(mState == State::SendingReadyMessage);
6313 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6314 !MayProceed()) {
6315 MaybeSetFailureCode(NS_ERROR_ABORT);
6318 nsresult rv = SendReadyMessageInternal();
6319 if (NS_WARN_IF(NS_FAILED(rv))) {
6320 MaybeSetFailureCode(rv);
6322 FinishInternal();
6326 nsresult LSRequestBase::SendReadyMessageInternal() {
6327 AssertIsOnOwningThread();
6328 MOZ_ASSERT(mState == State::SendingReadyMessage);
6330 if (!MayProceed()) {
6331 return NS_ERROR_ABORT;
6334 if (NS_WARN_IF(!SendReady())) {
6335 return NS_ERROR_FAILURE;
6338 mState = State::WaitingForFinish;
6340 mWaitingForFinish = true;
6342 return NS_OK;
6345 void LSRequestBase::Finish() {
6346 AssertIsOnOwningThread();
6347 MOZ_ASSERT(mState == State::WaitingForFinish);
6349 mWaitingForFinish = false;
6351 FinishInternal();
6354 void LSRequestBase::FinishInternal() {
6355 AssertIsOnOwningThread();
6356 MOZ_ASSERT(mState == State::SendingReadyMessage ||
6357 mState == State::WaitingForFinish);
6359 mState = State::SendingResults;
6361 // This LSRequestBase can only be held alive by the IPDL. Run() can end up
6362 // with clearing that last reference. So we need to add a self reference here.
6363 RefPtr<LSRequestBase> kungFuDeathGrip = this;
6365 MOZ_ALWAYS_SUCCEEDS(this->Run());
6368 void LSRequestBase::SendResults() {
6369 AssertIsOnOwningThread();
6370 MOZ_ASSERT(mState == State::SendingResults);
6372 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6373 !MayProceed()) {
6374 MaybeSetFailureCode(NS_ERROR_ABORT);
6377 if (MayProceed()) {
6378 LSRequestResponse response;
6380 if (NS_SUCCEEDED(ResultCode())) {
6381 GetResponse(response);
6383 MOZ_ASSERT(response.type() != LSRequestResponse::T__None);
6385 if (response.type() == LSRequestResponse::Tnsresult) {
6386 MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
6388 SetFailureCode(response.get_nsresult());
6390 } else {
6391 response = ResultCode();
6394 Unused << PBackgroundLSRequestParent::Send__delete__(this, response);
6397 Cleanup();
6399 mState = State::Completed;
6402 NS_IMETHODIMP
6403 LSRequestBase::Run() {
6404 nsresult rv;
6406 switch (mState) {
6407 case State::StartingRequest:
6408 rv = StartRequest();
6409 break;
6411 case State::Nesting:
6412 rv = NestedRun();
6413 break;
6415 case State::SendingReadyMessage:
6416 SendReadyMessage();
6417 return NS_OK;
6419 case State::SendingResults:
6420 SendResults();
6421 return NS_OK;
6423 default:
6424 MOZ_CRASH("Bad state!");
6427 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingReadyMessage) {
6428 MaybeSetFailureCode(rv);
6430 // Must set mState before dispatching otherwise we will race with the owning
6431 // thread.
6432 mState = State::SendingReadyMessage;
6434 if (IsOnOwningThread()) {
6435 SendReadyMessage();
6436 } else {
6437 MOZ_ALWAYS_SUCCEEDS(
6438 OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
6442 return NS_OK;
6445 void LSRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
6446 AssertIsOnOwningThread();
6448 NoteComplete();
6450 // Assume ActorDestroy can happen at any time, so we can't probe the current
6451 // state since mState can be modified on any thread (only one thread at a time
6452 // based on the state machine). However we can use mWaitingForFinish which is
6453 // only touched on the owning thread. If mWaitingForFinisg is true, we can
6454 // also modify mState since we are guaranteed that there are no pending
6455 // runnables which would probe mState to decide what code needs to run (there
6456 // shouldn't be any running runnables on other threads either).
6458 if (mWaitingForFinish) {
6459 Finish();
6462 // We don't have to handle the case when mWaitingForFinish is not true since
6463 // it means that either nothing has been initialized yet, so nothing to
6464 // cleanup or there are pending runnables that will detect that the actor has
6465 // been destroyed and cleanup accordingly.
6468 mozilla::ipc::IPCResult LSRequestBase::RecvCancel() {
6469 AssertIsOnOwningThread();
6471 Log();
6473 const char* crashOnCancel = PR_GetEnv("LSNG_CRASH_ON_CANCEL");
6474 if (crashOnCancel) {
6475 MOZ_CRASH("LSNG: Crash on cancel.");
6478 IProtocol* mgr = Manager();
6479 if (!PBackgroundLSRequestParent::Send__delete__(this, NS_ERROR_ABORT)) {
6480 return IPC_FAIL(mgr, "Send__delete__ failed!");
6483 return IPC_OK();
6486 mozilla::ipc::IPCResult LSRequestBase::RecvFinish() {
6487 AssertIsOnOwningThread();
6489 Finish();
6491 return IPC_OK();
6494 /*******************************************************************************
6495 * PrepareDatastoreOp
6496 ******************************************************************************/
6498 PrepareDatastoreOp::PrepareDatastoreOp(
6499 const LSRequestParams& aParams,
6500 const Maybe<ContentParentId>& aContentParentId)
6501 : LSRequestBase(aParams, aContentParentId),
6502 mLoadDataOp(nullptr),
6503 mPrivateBrowsingId(0),
6504 mUsage(0),
6505 mSizeOfKeys(0),
6506 mSizeOfItems(0),
6507 mDatastoreId(0),
6508 mNestedState(NestedState::BeforeNesting),
6509 mForPreload(aParams.type() ==
6510 LSRequestParams::TLSRequestPreloadDatastoreParams),
6511 mDatabaseNotAvailable(false),
6512 mInvalidated(false)
6513 #ifdef DEBUG
6515 mDEBUGUsage(0)
6516 #endif
6518 MOZ_ASSERT(
6519 aParams.type() == LSRequestParams::TLSRequestPreloadDatastoreParams ||
6520 aParams.type() == LSRequestParams::TLSRequestPrepareDatastoreParams);
6523 PrepareDatastoreOp::~PrepareDatastoreOp() {
6524 MOZ_ASSERT(!mDirectoryLock);
6525 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
6526 mState == State::Initial || mState == State::Completed);
6527 MOZ_ASSERT(!mLoadDataOp);
6530 void PrepareDatastoreOp::StringifyNestedState(nsACString& aResult) const {
6531 AssertIsOnOwningThread();
6533 switch (mNestedState) {
6534 case NestedState::BeforeNesting:
6535 aResult.AppendLiteral("BeforeNesting");
6536 return;
6538 case NestedState::CheckExistingOperations:
6539 aResult.AppendLiteral("CheckExistingOperations");
6540 return;
6542 case NestedState::CheckClosingDatastore:
6543 aResult.AppendLiteral("CheckClosingDatastore");
6544 return;
6546 case NestedState::PreparationPending:
6547 aResult.AppendLiteral("PreparationPending");
6548 return;
6550 case NestedState::DirectoryOpenPending:
6551 aResult.AppendLiteral("DirectoryOpenPending");
6552 return;
6554 case NestedState::DatabaseWorkOpen:
6555 aResult.AppendLiteral("DatabaseWorkOpen");
6556 return;
6558 case NestedState::BeginLoadData:
6559 aResult.AppendLiteral("BeginLoadData");
6560 return;
6562 case NestedState::DatabaseWorkLoadData:
6563 aResult.AppendLiteral("DatabaseWorkLoadData");
6564 return;
6566 case NestedState::AfterNesting:
6567 aResult.AppendLiteral("AfterNesting");
6568 return;
6570 default:
6571 MOZ_CRASH("Bad state!");
6575 void PrepareDatastoreOp::Stringify(nsACString& aResult) const {
6576 AssertIsOnOwningThread();
6578 LSRequestBase::Stringify(aResult);
6579 aResult.Append(kQuotaGenericDelimiter);
6581 aResult.AppendLiteral("Origin:");
6582 aResult.Append(AnonymizedOriginString(Origin()));
6583 aResult.Append(kQuotaGenericDelimiter);
6585 aResult.AppendLiteral("NestedState:");
6586 StringifyNestedState(aResult);
6589 void PrepareDatastoreOp::Log() {
6590 AssertIsOnOwningThread();
6592 LSRequestBase::Log();
6594 if (!LS_LOG_TEST()) {
6595 return;
6598 nsCString nestedState;
6599 StringifyNestedState(nestedState);
6601 LS_LOG((" mNestedState: %s", nestedState.get()));
6603 switch (mNestedState) {
6604 case NestedState::CheckClosingDatastore: {
6605 for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0;
6606 index--) {
6607 const auto& existingOp = (*gPrepareDatastoreOps)[index - 1];
6609 if (existingOp->mDelayedOp == this) {
6610 LS_LOG((" mDelayedBy: [%p]",
6611 static_cast<PrepareDatastoreOp*>(existingOp.get())));
6613 existingOp->Log();
6615 break;
6619 break;
6622 case NestedState::DirectoryOpenPending: {
6623 MOZ_ASSERT(mPendingDirectoryLock);
6625 LS_LOG((" mPendingDirectoryLock: [%p]", mPendingDirectoryLock.get()));
6627 mPendingDirectoryLock->Log();
6629 break;
6632 default:;
6636 nsresult PrepareDatastoreOp::Start() {
6637 AssertIsOnOwningThread();
6638 MOZ_ASSERT(mState == State::StartingRequest);
6639 MOZ_ASSERT(mNestedState == NestedState::BeforeNesting);
6640 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6641 MOZ_ASSERT(MayProceed());
6643 QM_TRY(QuotaManager::EnsureCreated());
6645 const LSRequestCommonParams& commonParams =
6646 mForPreload
6647 ? mParams.get_LSRequestPreloadDatastoreParams().commonParams()
6648 : mParams.get_LSRequestPrepareDatastoreParams().commonParams();
6650 const PrincipalInfo& storagePrincipalInfo =
6651 commonParams.storagePrincipalInfo();
6653 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
6654 mOriginMetadata = {QuotaManager::GetInfoForChrome(),
6655 PERSISTENCE_TYPE_DEFAULT};
6656 } else {
6657 MOZ_ASSERT(storagePrincipalInfo.type() ==
6658 PrincipalInfo::TContentPrincipalInfo);
6660 QM_TRY_UNWRAP(auto principalMetadata,
6661 QuotaManager::Get()->GetInfoFromValidatedPrincipalInfo(
6662 storagePrincipalInfo));
6664 mOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix);
6665 mOriginMetadata.mGroup = std::move(principalMetadata.mGroup);
6666 // XXX We can probably get rid of mMainThreadOrigin if we change
6667 // LSRequestBase::Dispatch to synchronously run LSRequestBase::StartRequest
6668 // through LSRequestBase::Run.
6669 mMainThreadOrigin = std::move(principalMetadata.mOrigin);
6670 mOriginMetadata.mStorageOrigin =
6671 std::move(principalMetadata.mStorageOrigin);
6672 mOriginMetadata.mIsPrivate = principalMetadata.mIsPrivate;
6673 mOriginMetadata.mPersistenceType = principalMetadata.mIsPrivate
6674 ? PERSISTENCE_TYPE_PRIVATE
6675 : PERSISTENCE_TYPE_DEFAULT;
6678 mState = State::Nesting;
6679 mNestedState = NestedState::CheckExistingOperations;
6681 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
6683 return NS_OK;
6686 nsresult PrepareDatastoreOp::CheckExistingOperations() {
6687 AssertIsOnOwningThread();
6688 MOZ_ASSERT(mState == State::Nesting);
6689 MOZ_ASSERT(mNestedState == NestedState::CheckExistingOperations);
6690 MOZ_ASSERT(gPrepareDatastoreOps);
6692 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6693 !MayProceed()) {
6694 return NS_ERROR_ABORT;
6697 const LSRequestCommonParams& commonParams =
6698 mForPreload
6699 ? mParams.get_LSRequestPreloadDatastoreParams().commonParams()
6700 : mParams.get_LSRequestPrepareDatastoreParams().commonParams();
6702 const PrincipalInfo& storagePrincipalInfo =
6703 commonParams.storagePrincipalInfo();
6705 nsCString originAttrSuffix;
6706 uint32_t privateBrowsingId;
6708 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
6709 privateBrowsingId = 0;
6710 } else {
6711 MOZ_ASSERT(storagePrincipalInfo.type() ==
6712 PrincipalInfo::TContentPrincipalInfo);
6714 const ContentPrincipalInfo& info =
6715 storagePrincipalInfo.get_ContentPrincipalInfo();
6716 const OriginAttributes& attrs = info.attrs();
6717 attrs.CreateSuffix(originAttrSuffix);
6719 privateBrowsingId = attrs.mPrivateBrowsingId;
6722 mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(
6723 originAttrSuffix, commonParams.originKey());
6724 MOZ_ASSERT(mArchivedOriginScope);
6726 // Normally it's safe to access member variables without a mutex because even
6727 // though we hop between threads, the variables are never accessed by multiple
6728 // threads at the same time.
6729 // However, the methods OriginIsKnown and Origin can be called at any time.
6730 // So we have to make sure the member variable is set on the same thread as
6731 // those methods are called.
6732 mOriginMetadata.mOrigin = mMainThreadOrigin;
6734 MOZ_ASSERT(OriginIsKnown());
6736 mPrivateBrowsingId = privateBrowsingId;
6738 mNestedState = NestedState::CheckClosingDatastore;
6740 // See if this PrepareDatastoreOp needs to wait.
6741 bool foundThis = false;
6742 for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0; index--) {
6743 const auto& existingOp = (*gPrepareDatastoreOps)[index - 1];
6745 if (existingOp == this) {
6746 foundThis = true;
6747 continue;
6750 if (foundThis && existingOp->Origin() == Origin()) {
6751 // Only one op can be delayed.
6752 MOZ_ASSERT(!existingOp->mDelayedOp);
6753 existingOp->mDelayedOp = this;
6755 return NS_OK;
6759 QM_TRY(MOZ_TO_RESULT(CheckClosingDatastoreInternal()));
6761 return NS_OK;
6764 nsresult PrepareDatastoreOp::CheckClosingDatastore() {
6765 AssertIsOnOwningThread();
6766 MOZ_ASSERT(mState == State::Nesting);
6767 MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore);
6769 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6770 !MayProceed()) {
6771 return NS_ERROR_ABORT;
6774 QM_TRY(MOZ_TO_RESULT(CheckClosingDatastoreInternal()));
6776 return NS_OK;
6779 nsresult PrepareDatastoreOp::CheckClosingDatastoreInternal() {
6780 AssertIsOnOwningThread();
6781 MOZ_ASSERT(mState == State::Nesting);
6782 MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore);
6783 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6784 MOZ_ASSERT(MayProceed());
6786 mNestedState = NestedState::PreparationPending;
6788 RefPtr<Datastore> datastore;
6789 if ((datastore = GetDatastore(Origin())) && datastore->IsClosed()) {
6790 datastore->WaitForConnectionToComplete(this);
6792 return NS_OK;
6795 QM_TRY(MOZ_TO_RESULT(BeginDatastorePreparationInternal()));
6797 return NS_OK;
6800 nsresult PrepareDatastoreOp::BeginDatastorePreparation() {
6801 AssertIsOnOwningThread();
6802 MOZ_ASSERT(mState == State::Nesting);
6803 MOZ_ASSERT(mNestedState == NestedState::PreparationPending);
6805 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6806 !MayProceed()) {
6807 return NS_ERROR_ABORT;
6810 QM_TRY(MOZ_TO_RESULT(BeginDatastorePreparationInternal()));
6812 return NS_OK;
6815 nsresult PrepareDatastoreOp::BeginDatastorePreparationInternal() {
6816 AssertIsOnOwningThread();
6817 MOZ_ASSERT(mState == State::Nesting);
6818 MOZ_ASSERT(mNestedState == NestedState::PreparationPending);
6819 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6820 MOZ_ASSERT(MayProceed());
6821 MOZ_ASSERT(OriginIsKnown());
6822 MOZ_ASSERT(!mDirectoryLock);
6824 if ((mDatastore = GetDatastore(Origin()))) {
6825 MOZ_ASSERT(!mDatastore->IsClosed());
6827 mDatastore->NoteLivePrepareDatastoreOp(this);
6829 FinishNesting();
6831 return NS_OK;
6834 QuotaManager* quotaManager = QuotaManager::Get();
6835 MOZ_ASSERT(quotaManager);
6837 mNestedState = NestedState::DirectoryOpenPending;
6839 quotaManager
6840 ->OpenClientDirectory({mOriginMetadata, mozilla::dom::quota::Client::LS},
6841 SomeRef(mPendingDirectoryLock))
6842 ->Then(
6843 GetCurrentSerialEventTarget(), __func__,
6844 [self = RefPtr(this)](
6845 const ClientDirectoryLockPromise::ResolveOrRejectValue& aValue) {
6846 self->mPendingDirectoryLock = nullptr;
6848 if (aValue.IsResolve()) {
6849 self->DirectoryLockAcquired(aValue.ResolveValue());
6850 } else {
6851 self->DirectoryLockFailed();
6855 return NS_OK;
6858 void PrepareDatastoreOp::SendToIOThread() {
6859 AssertIsOnOwningThread();
6860 MOZ_ASSERT(mState == State::Nesting);
6861 MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
6862 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6863 MOZ_ASSERT(MayProceed());
6865 // Skip all disk related stuff and transition to SendingReadyMessage if we
6866 // are preparing a datastore for private browsing.
6867 // Note that we do use a directory lock for private browsing even though we
6868 // don't do any stuff on disk. The thing is that without a directory lock,
6869 // quota manager wouldn't call AbortOperationsForLocks for our private
6870 // browsing origin when a clear origin operation is requested.
6871 // AbortOperationsForLocks requests all databases to close and the datastore
6872 // is destroyed in the end. Any following LocalStorage API call will trigger
6873 // preparation of a new (empty) datastore.
6874 if (mPrivateBrowsingId) {
6875 FinishNesting();
6877 return;
6880 QuotaManager* quotaManager = QuotaManager::Get();
6881 MOZ_ASSERT(quotaManager);
6883 // Must set this before dispatching otherwise we will race with the IO thread.
6884 mNestedState = NestedState::DatabaseWorkOpen;
6886 MOZ_ALWAYS_SUCCEEDS(
6887 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
6890 nsresult PrepareDatastoreOp::DatabaseWork() {
6891 AssertIsOnIOThread();
6892 MOZ_ASSERT(mArchivedOriginScope);
6893 MOZ_ASSERT(mUsage == 0);
6894 MOZ_ASSERT(mState == State::Nesting);
6895 MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen);
6897 const auto innerFunc = [&](const auto&) -> nsresult {
6898 // XXX This function is too long, refactor it into helper functions for
6899 // readability.
6901 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
6902 !MayProceedOnNonOwningThread()) {
6903 return NS_ERROR_ABORT;
6906 QuotaManager* quotaManager = QuotaManager::Get();
6907 MOZ_ASSERT(quotaManager);
6909 // This ensures that usages for existings origin directories are cached in
6910 // memory.
6911 QM_TRY(MOZ_TO_RESULT(
6912 quotaManager->EnsureTemporaryStorageIsInitializedInternal()));
6914 const UsageInfo usageInfo = quotaManager->GetUsageForClient(
6915 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata,
6916 mozilla::dom::quota::Client::LS);
6918 const bool hasUsage = usageInfo.DatabaseUsage().isSome();
6919 MOZ_ASSERT(usageInfo.FileUsage().isNothing());
6921 if (!gArchivedOrigins) {
6922 QM_TRY(MOZ_TO_RESULT(LoadArchivedOrigins()));
6923 MOZ_ASSERT(gArchivedOrigins);
6926 bool hasDataForMigration =
6927 mArchivedOriginScope->HasMatches(gArchivedOrigins);
6929 // If there's nothing to preload (except the case when we want to migrate
6930 // data during preloading), then we can finish the operation without
6931 // creating a datastore in GetResponse (GetResponse won't create a datastore
6932 // if mDatatabaseNotAvailable and mForPreload are both true).
6933 if (mForPreload && !hasUsage && !hasDataForMigration) {
6934 return DatabaseNotAvailable();
6937 // The origin directory doesn't need to be created when we don't have data
6938 // for migration. It will be created on the connection thread in
6939 // Connection::EnsureStorageConnection.
6940 // However, origin quota must be initialized, GetQuotaObject in GetResponse
6941 // would fail otherwise.
6942 QM_TRY_INSPECT(
6943 const auto& directoryEntry,
6944 ([hasDataForMigration, &quotaManager,
6945 this]() -> mozilla::Result<nsCOMPtr<nsIFile>, nsresult> {
6946 if (hasDataForMigration) {
6947 QM_TRY_RETURN(quotaManager
6948 ->EnsureTemporaryOriginIsInitialized(
6949 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata)
6950 .map([](const auto& res) { return res.first; }));
6953 MOZ_ASSERT(mOriginMetadata.mPersistenceType ==
6954 PERSISTENCE_TYPE_DEFAULT);
6956 QM_TRY_UNWRAP(auto directoryEntry,
6957 quotaManager->GetOriginDirectory(mOriginMetadata));
6959 quotaManager->EnsureQuotaForOrigin(mOriginMetadata);
6961 return directoryEntry;
6962 }()));
6964 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(
6965 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME))));
6967 QM_TRY_INSPECT(
6968 const auto& directoryPath,
6969 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directoryEntry, GetPath));
6971 // The ls directory doesn't need to be created when we don't have data for
6972 // migration. It will be created on the connection thread in
6973 // Connection::EnsureStorageConnection.
6974 QM_TRY(MOZ_TO_RESULT(
6975 EnsureDirectoryEntry(directoryEntry,
6976 /* aCreateIfNotExists */ hasDataForMigration,
6977 /* aIsDirectory */ true)));
6979 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName)));
6981 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDatabaseFilePath)));
6983 // The database doesn't need to be created when we don't have data for
6984 // migration. It will be created on the connection thread in
6985 // Connection::EnsureStorageConnection.
6986 bool alreadyExisted;
6987 QM_TRY(MOZ_TO_RESULT(
6988 EnsureDirectoryEntry(directoryEntry,
6989 /* aCreateIfNotExists */ hasDataForMigration,
6990 /* aIsDirectory */ false, &alreadyExisted)));
6992 if (alreadyExisted) {
6993 // The database does exist.
6994 MOZ_ASSERT(hasUsage);
6996 // XXX Change type of mUsage to UsageInfo or DatabaseUsageType.
6997 mUsage = usageInfo.DatabaseUsage().valueOr(0);
6998 } else {
6999 // The database doesn't exist.
7000 MOZ_ASSERT(!hasUsage);
7002 if (!hasDataForMigration) {
7003 // The database doesn't exist and we don't have data for migration.
7004 // Finish the operation, but create an empty datastore in GetResponse
7005 // (GetResponse will create an empty datastore if mDatabaseNotAvailable
7006 // is true and mForPreload is false).
7007 return DatabaseNotAvailable();
7011 // We initialized mDatabaseFilePath and mUsage, GetQuotaObject can now be
7012 // called.
7013 const RefPtr<QuotaObject> quotaObject = GetQuotaObject();
7015 QM_TRY(OkIf(quotaObject), Err(NS_ERROR_FAILURE));
7017 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(directoryPath));
7019 QM_TRY_INSPECT(const auto& usageJournalFile,
7020 GetUsageJournalFile(directoryPath));
7022 QM_TRY_INSPECT(
7023 const auto& connection,
7024 (CreateStorageConnectionWithRecovery(
7025 *directoryEntry, *usageFile, Origin(), [&quotaObject, this] {
7026 // This is called when the usage file was removed or we notice
7027 // that the usage file doesn't exist anymore. Adjust the usage
7028 // accordingly.
7030 MOZ_ALWAYS_TRUE(
7031 quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
7033 mUsage = 0;
7034 })));
7036 QM_TRY(MOZ_TO_RESULT(VerifyDatabaseInformation(connection)));
7038 if (hasDataForMigration) {
7039 MOZ_ASSERT(mUsage == 0);
7042 QM_TRY_INSPECT(const auto& archiveFile,
7043 GetArchiveFile(quotaManager->GetStoragePath()));
7045 auto autoArchiveDatabaseAttacher =
7046 AutoDatabaseAttacher(connection, archiveFile, "archive"_ns);
7048 QM_TRY(MOZ_TO_RESULT(autoArchiveDatabaseAttacher.Attach()));
7050 QM_TRY_INSPECT(const int64_t& newUsage,
7051 GetUsage(*connection, mArchivedOriginScope.get()));
7053 QM_TRY(
7054 OkIf(quotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)),
7055 NS_ERROR_FILE_NO_DEVICE_SPACE);
7057 auto autoUpdateSize = MakeScopeExit([&quotaObject] {
7058 MOZ_ALWAYS_TRUE(
7059 quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
7062 mozStorageTransaction transaction(
7063 connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
7065 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
7068 nsCOMPtr<mozIStorageFunction> function = new CompressFunction();
7070 QM_TRY(MOZ_TO_RESULT(
7071 connection->CreateFunction("compress"_ns, 1, function)));
7073 function = new CompressionTypeFunction();
7075 QM_TRY(MOZ_TO_RESULT(
7076 connection->CreateFunction("compressionType"_ns, 1, function)));
7078 QM_TRY_INSPECT(
7079 const auto& stmt,
7080 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7081 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
7082 "INSERT INTO data (key, utf16_length, conversion_type, "
7083 "compression_type, value) "
7084 "SELECT key, utf16Length(value), :conversionType, "
7085 "compressionType(value), compress(value)"
7086 "FROM webappsstore2 "
7087 "WHERE originKey = :originKey "
7088 "AND originAttributes = :originAttributes;"_ns));
7090 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName(
7091 "conversionType"_ns,
7092 static_cast<int32_t>(LSValue::ConversionType::UTF16_UTF8))));
7094 QM_TRY(MOZ_TO_RESULT(mArchivedOriginScope->BindToStatement(stmt)));
7096 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
7098 QM_TRY(MOZ_TO_RESULT(connection->RemoveFunction("compress"_ns)));
7100 QM_TRY(
7101 MOZ_TO_RESULT(connection->RemoveFunction("compressionType"_ns)));
7105 QM_TRY_INSPECT(
7106 const auto& stmt,
7107 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7108 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
7109 "UPDATE database SET usage = :usage;"_ns));
7111 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName("usage"_ns, newUsage)));
7113 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
7117 QM_TRY_INSPECT(
7118 const auto& stmt,
7119 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7120 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
7121 "DELETE FROM webappsstore2 "
7122 "WHERE originKey = :originKey "
7123 "AND originAttributes = :originAttributes;"_ns));
7125 QM_TRY(MOZ_TO_RESULT(mArchivedOriginScope->BindToStatement(stmt)));
7126 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
7129 QM_TRY(MOZ_TO_RESULT(
7130 UpdateUsageFile(usageFile, usageJournalFile, newUsage)));
7131 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
7133 autoUpdateSize.release();
7135 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false)));
7137 mUsage = newUsage;
7139 QM_TRY(MOZ_TO_RESULT(autoArchiveDatabaseAttacher.Detach()));
7142 MOZ_ASSERT(gArchivedOrigins);
7143 MOZ_ASSERT(mArchivedOriginScope->HasMatches(gArchivedOrigins));
7144 mArchivedOriginScope->RemoveMatches(gArchivedOrigins);
7147 nsCOMPtr<mozIStorageConnection> shadowConnection;
7148 if (!gInitializedShadowStorage) {
7149 QM_TRY_UNWRAP(shadowConnection,
7150 CreateShadowStorageConnection(quotaManager->GetBasePath()));
7152 gInitializedShadowStorage = true;
7155 // Must close connections before dispatching otherwise we might race with
7156 // the connection thread which needs to open the same databases.
7157 MOZ_ALWAYS_SUCCEEDS(connection->Close());
7159 if (shadowConnection) {
7160 MOZ_ALWAYS_SUCCEEDS(shadowConnection->Close());
7163 // Must set this before dispatching otherwise we will race with the owning
7164 // thread.
7165 mNestedState = NestedState::BeginLoadData;
7167 QM_TRY(
7168 MOZ_TO_RESULT(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)));
7170 return NS_OK;
7173 return ExecuteOriginInitialization(
7174 mOriginMetadata.mOrigin, LSOriginInitialization::Datastore,
7175 "dom::localstorage::FirstOriginInitializationAttempt::Datastore"_ns,
7176 innerFunc);
7179 nsresult PrepareDatastoreOp::DatabaseNotAvailable() {
7180 AssertIsOnIOThread();
7181 MOZ_ASSERT(mState == State::Nesting);
7182 MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen);
7184 mDatabaseNotAvailable = true;
7186 nsresult rv = FinishNestingOnNonOwningThread();
7187 if (NS_WARN_IF(NS_FAILED(rv))) {
7188 return rv;
7191 return NS_OK;
7194 nsresult PrepareDatastoreOp::EnsureDirectoryEntry(nsIFile* aEntry,
7195 bool aCreateIfNotExists,
7196 bool aIsDirectory,
7197 bool* aAlreadyExisted) {
7198 AssertIsOnIOThread();
7199 MOZ_ASSERT(aEntry);
7201 QM_TRY_INSPECT(const bool& exists,
7202 MOZ_TO_RESULT_INVOKE_MEMBER(aEntry, Exists));
7204 if (!exists) {
7205 if (!aCreateIfNotExists) {
7206 if (aAlreadyExisted) {
7207 *aAlreadyExisted = false;
7209 return NS_OK;
7212 if (aIsDirectory) {
7213 QM_TRY(MOZ_TO_RESULT(aEntry->Create(nsIFile::DIRECTORY_TYPE, 0755)));
7216 #ifdef DEBUG
7217 else {
7218 bool isDirectory;
7219 MOZ_ASSERT(NS_SUCCEEDED(aEntry->IsDirectory(&isDirectory)));
7220 MOZ_ASSERT(isDirectory == aIsDirectory);
7222 #endif
7224 if (aAlreadyExisted) {
7225 *aAlreadyExisted = exists;
7227 return NS_OK;
7230 nsresult PrepareDatastoreOp::VerifyDatabaseInformation(
7231 mozIStorageConnection* aConnection) {
7232 AssertIsOnIOThread();
7233 MOZ_ASSERT(aConnection);
7235 QM_TRY_INSPECT(const auto& stmt,
7236 CreateAndExecuteSingleStepStatement<
7237 SingleStepResult::ReturnNullIfNoResult>(
7238 *aConnection, "SELECT origin FROM database"_ns));
7240 QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED);
7242 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7243 nsCString, stmt, GetUTF8String, 0));
7245 QM_TRY(OkIf(QuotaManager::AreOriginsEqualOnDisk(Origin(), origin)),
7246 NS_ERROR_FILE_CORRUPTED);
7248 return NS_OK;
7251 already_AddRefed<QuotaObject> PrepareDatastoreOp::GetQuotaObject() {
7252 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
7253 MOZ_ASSERT(!mOriginMetadata.mGroup.IsEmpty());
7254 MOZ_ASSERT(OriginIsKnown());
7255 MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
7257 QuotaManager* quotaManager = QuotaManager::Get();
7258 MOZ_ASSERT(quotaManager);
7260 RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject(
7261 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata,
7262 mozilla::dom::quota::Client::LS, mDatabaseFilePath, mUsage);
7264 if (!quotaObject) {
7265 LS_WARNING("Failed to get quota object for group (%s) and origin (%s)!",
7266 mOriginMetadata.mGroup.get(), Origin().get());
7269 return quotaObject.forget();
7272 nsresult PrepareDatastoreOp::BeginLoadData() {
7273 AssertIsOnOwningThread();
7274 MOZ_ASSERT(mState == State::Nesting);
7275 MOZ_ASSERT(mNestedState == NestedState::BeginLoadData);
7276 MOZ_ASSERT(!mConnection);
7278 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7279 !MayProceed()) {
7280 return NS_ERROR_ABORT;
7283 if (!gConnectionThread) {
7284 gConnectionThread = new ConnectionThread();
7287 mConnection = gConnectionThread->CreateConnection(
7288 mOriginMetadata, std::move(mArchivedOriginScope),
7289 /* aDatabaseWasNotAvailable */ false);
7290 MOZ_ASSERT(mConnection);
7292 // Must set this before dispatching otherwise we will race with the
7293 // connection thread.
7294 mNestedState = NestedState::DatabaseWorkLoadData;
7296 // Can't assign to mLoadDataOp directly since that's a weak reference and
7297 // LoadDataOp is reference counted.
7298 RefPtr<LoadDataOp> loadDataOp = new LoadDataOp(this);
7300 // This add refs loadDataOp.
7301 mConnection->Dispatch(loadDataOp);
7303 // This is cleared in LoadDataOp::Cleanup() before the load data op is
7304 // destroyed.
7305 mLoadDataOp = loadDataOp;
7307 return NS_OK;
7310 void PrepareDatastoreOp::FinishNesting() {
7311 AssertIsOnOwningThread();
7312 MOZ_ASSERT(mState == State::Nesting);
7314 // The caller holds a strong reference to us, no need for a self reference
7315 // before calling Run().
7317 mState = State::SendingReadyMessage;
7318 mNestedState = NestedState::AfterNesting;
7320 MOZ_ALWAYS_SUCCEEDS(Run());
7323 nsresult PrepareDatastoreOp::FinishNestingOnNonOwningThread() {
7324 MOZ_ASSERT(!IsOnOwningThread());
7325 MOZ_ASSERT(mState == State::Nesting);
7327 // Must set mState before dispatching otherwise we will race with the owning
7328 // thread.
7329 mState = State::SendingReadyMessage;
7330 mNestedState = NestedState::AfterNesting;
7332 QM_TRY(
7333 MOZ_TO_RESULT(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)));
7335 return NS_OK;
7338 nsresult PrepareDatastoreOp::NestedRun() {
7339 nsresult rv;
7341 switch (mNestedState) {
7342 case NestedState::CheckExistingOperations:
7343 rv = CheckExistingOperations();
7344 break;
7346 case NestedState::CheckClosingDatastore:
7347 rv = CheckClosingDatastore();
7348 break;
7350 case NestedState::PreparationPending:
7351 rv = BeginDatastorePreparation();
7352 break;
7354 case NestedState::DatabaseWorkOpen:
7355 rv = DatabaseWork();
7356 break;
7358 case NestedState::BeginLoadData:
7359 rv = BeginLoadData();
7360 break;
7362 default:
7363 MOZ_CRASH("Bad state!");
7366 if (NS_WARN_IF(NS_FAILED(rv))) {
7367 mNestedState = NestedState::AfterNesting;
7369 return rv;
7372 return NS_OK;
7375 void PrepareDatastoreOp::GetResponse(LSRequestResponse& aResponse) {
7376 AssertIsOnOwningThread();
7377 MOZ_ASSERT(mState == State::SendingResults);
7378 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
7379 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7380 MOZ_ASSERT(MayProceed());
7382 // A datastore is not created when we are just trying to preload data and
7383 // there's no database file.
7384 if (mDatabaseNotAvailable && mForPreload) {
7385 LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
7387 aResponse = preloadDatastoreResponse;
7389 return;
7392 if (!mDatastore) {
7393 MOZ_ASSERT(mUsage == mDEBUGUsage);
7395 RefPtr<QuotaObject> quotaObject;
7397 if (mPrivateBrowsingId == 0) {
7398 if (!mConnection) {
7399 // This can happen when there's no database file.
7400 MOZ_ASSERT(mDatabaseNotAvailable);
7402 // Even though there's no database file, we need to create a connection
7403 // and pass it to datastore.
7404 if (!gConnectionThread) {
7405 gConnectionThread = new ConnectionThread();
7408 mConnection = gConnectionThread->CreateConnection(
7409 mOriginMetadata, std::move(mArchivedOriginScope),
7410 /* aDatabaseWasNotAvailable */ true);
7411 MOZ_ASSERT(mConnection);
7414 quotaObject = GetQuotaObject();
7415 if (!quotaObject) {
7416 aResponse = NS_ERROR_FAILURE;
7417 return;
7421 mDatastore = new Datastore(
7422 mOriginMetadata, mPrivateBrowsingId, mUsage, mSizeOfKeys, mSizeOfItems,
7423 std::move(mDirectoryLock), std::move(mConnection),
7424 std::move(quotaObject), mValues, std::move(mOrderedItems));
7426 mDatastore->NoteLivePrepareDatastoreOp(this);
7428 if (!gDatastores) {
7429 gDatastores = new DatastoreHashtable();
7432 MOZ_ASSERT(!gDatastores->Contains(Origin()));
7433 gDatastores->InsertOrUpdate(Origin(),
7434 WrapMovingNotNullUnchecked(mDatastore));
7437 if (mPrivateBrowsingId && !mInvalidated) {
7438 if (!gPrivateDatastores) {
7439 gPrivateDatastores = MakeUnique<PrivateDatastoreHashtable>();
7442 gPrivateDatastores->LookupOrInsertWith(Origin(), [&] {
7443 auto privateDatastore =
7444 MakeUnique<PrivateDatastore>(WrapMovingNotNull(mDatastore));
7446 mPrivateDatastoreRegistered.Flip();
7448 return privateDatastore;
7452 mDatastoreId = ++gLastDatastoreId;
7454 if (!gPreparedDatastores) {
7455 gPreparedDatastores = new PreparedDatastoreHashtable();
7457 const auto& preparedDatastore = gPreparedDatastores->InsertOrUpdate(
7458 mDatastoreId, MakeUnique<PreparedDatastore>(
7459 mDatastore, mContentParentId, Origin(), mDatastoreId,
7460 /* aForPreload */ mForPreload));
7462 if (mInvalidated) {
7463 preparedDatastore->Invalidate();
7466 mPreparedDatastoreRegistered.Flip();
7468 if (mForPreload) {
7469 LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
7471 aResponse = preloadDatastoreResponse;
7472 } else {
7473 LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
7474 prepareDatastoreResponse.datastoreId() = mDatastoreId;
7476 aResponse = prepareDatastoreResponse;
7480 void PrepareDatastoreOp::Cleanup() {
7481 AssertIsOnOwningThread();
7483 if (mDatastore) {
7484 MOZ_ASSERT(!mDirectoryLock);
7485 MOZ_ASSERT(!mConnection);
7487 if (NS_FAILED(ResultCode())) {
7488 if (mPrivateDatastoreRegistered) {
7489 MOZ_ASSERT(gPrivateDatastores);
7490 DebugOnly<bool> removed = gPrivateDatastores->Remove(Origin());
7491 MOZ_ASSERT(removed);
7493 if (!gPrivateDatastores->Count()) {
7494 gPrivateDatastores = nullptr;
7498 if (mPreparedDatastoreRegistered) {
7499 // Just in case we failed to send datastoreId to the child, we need to
7500 // destroy prepared datastore, otherwise it won't be destroyed until
7501 // the timer fires (after 20 seconds).
7502 MOZ_ASSERT(gPreparedDatastores);
7503 MOZ_ASSERT(mDatastoreId > 0);
7504 DebugOnly<bool> removed = gPreparedDatastores->Remove(mDatastoreId);
7505 MOZ_ASSERT(removed);
7507 if (!gPreparedDatastores->Count()) {
7508 gPreparedDatastores = nullptr;
7513 // Make sure to release the datastore on this thread.
7515 mDatastore->NoteFinishedPrepareDatastoreOp(this);
7517 mDatastore = nullptr;
7519 CleanupMetadata();
7520 } else if (mConnection) {
7521 // If we have a connection then the operation must have failed and there
7522 // must be a directory lock too.
7523 MOZ_ASSERT(NS_FAILED(ResultCode()));
7524 MOZ_ASSERT(mDirectoryLock);
7526 // We must close the connection on the connection thread before releasing
7527 // it on this thread. The directory lock can't be released either.
7528 nsCOMPtr<nsIRunnable> callback =
7529 NewRunnableMethod("dom::OpenDatabaseOp::ConnectionClosedCallback", this,
7530 &PrepareDatastoreOp::ConnectionClosedCallback);
7532 mConnection->Close(callback);
7533 } else {
7534 // If we don't have a connection, but we do have a directory lock then the
7535 // operation must have failed or we were preloading a datastore and there
7536 // was no physical database on disk.
7537 MOZ_ASSERT_IF(mDirectoryLock,
7538 NS_FAILED(ResultCode()) || mDatabaseNotAvailable);
7540 // There's no connection, so it's safe to release the directory lock and
7541 // unregister itself from the array.
7543 mDirectoryLock = nullptr;
7545 CleanupMetadata();
7549 void PrepareDatastoreOp::ConnectionClosedCallback() {
7550 AssertIsOnOwningThread();
7551 MOZ_ASSERT(NS_FAILED(ResultCode()));
7552 MOZ_ASSERT(mDirectoryLock);
7553 MOZ_ASSERT(mConnection);
7555 mConnection = nullptr;
7556 mDirectoryLock = nullptr;
7558 CleanupMetadata();
7561 void PrepareDatastoreOp::CleanupMetadata() {
7562 AssertIsOnOwningThread();
7564 if (mDelayedOp) {
7565 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget()));
7568 MOZ_ASSERT(gPrepareDatastoreOps);
7569 gPrepareDatastoreOps->RemoveElement(this);
7571 QuotaManager::MaybeRecordQuotaClientShutdownStep(
7572 quota::Client::LS, "PrepareDatastoreOp completed"_ns);
7574 if (gPrepareDatastoreOps->IsEmpty()) {
7575 gPrepareDatastoreOps = nullptr;
7579 void PrepareDatastoreOp::ActorDestroy(ActorDestroyReason aWhy) {
7580 AssertIsOnOwningThread();
7582 LSRequestBase::ActorDestroy(aWhy);
7584 if (mLoadDataOp) {
7585 mLoadDataOp->NoteComplete();
7589 void PrepareDatastoreOp::DirectoryLockAcquired(DirectoryLock* aLock) {
7590 AssertIsOnOwningThread();
7591 MOZ_ASSERT(mState == State::Nesting);
7592 MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
7593 MOZ_ASSERT(!mDirectoryLock);
7595 mPendingDirectoryLock = nullptr;
7597 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7598 !MayProceed()) {
7599 MaybeSetFailureCode(NS_ERROR_ABORT);
7601 FinishNesting();
7603 return;
7606 mDirectoryLock = aLock;
7608 SendToIOThread();
7611 void PrepareDatastoreOp::DirectoryLockFailed() {
7612 AssertIsOnOwningThread();
7613 MOZ_ASSERT(mState == State::Nesting);
7614 MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
7615 MOZ_ASSERT(!mDirectoryLock);
7617 mPendingDirectoryLock = nullptr;
7619 MaybeSetFailureCode(NS_ERROR_FAILURE);
7621 FinishNesting();
7624 nsresult PrepareDatastoreOp::LoadDataOp::DoDatastoreWork() {
7625 AssertIsOnGlobalConnectionThread();
7626 MOZ_ASSERT(mConnection);
7627 MOZ_ASSERT(mPrepareDatastoreOp);
7628 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
7629 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
7630 NestedState::DatabaseWorkLoadData);
7632 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
7633 !MayProceedOnNonOwningThread()) {
7634 return NS_ERROR_ABORT;
7637 QM_TRY_INSPECT(
7638 const auto& stmt,
7639 mConnection->BorrowCachedStatement(
7640 "SELECT key, utf16_length, conversion_type, compression_type, value "
7641 "FROM data;"_ns));
7643 QM_TRY(quota::CollectWhileHasResult(
7644 *stmt, [this](auto& stmt) -> mozilla::Result<Ok, nsresult> {
7645 QM_TRY_UNWRAP(auto key, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7646 nsString, stmt, GetString, 0));
7648 LSValue value;
7649 QM_TRY(MOZ_TO_RESULT(value.InitFromStatement(&stmt, 1)));
7651 mPrepareDatastoreOp->mValues.InsertOrUpdate(key, value);
7652 mPrepareDatastoreOp->mSizeOfKeys += key.Length();
7653 mPrepareDatastoreOp->mSizeOfItems += key.Length() + value.Length();
7654 #ifdef DEBUG
7655 mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.UTF16Length();
7656 #endif
7658 auto item = mPrepareDatastoreOp->mOrderedItems.AppendElement();
7659 item->key() = std::move(key);
7660 item->value() = std::move(value);
7662 return Ok{};
7663 }));
7665 return NS_OK;
7668 void PrepareDatastoreOp::LoadDataOp::OnSuccess() {
7669 AssertIsOnOwningThread();
7670 MOZ_ASSERT(mPrepareDatastoreOp);
7671 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
7672 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
7673 NestedState::DatabaseWorkLoadData);
7674 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this);
7676 mPrepareDatastoreOp->FinishNesting();
7679 void PrepareDatastoreOp::LoadDataOp::OnFailure(nsresult aResultCode) {
7680 AssertIsOnOwningThread();
7681 MOZ_ASSERT(mPrepareDatastoreOp);
7682 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
7683 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
7684 NestedState::DatabaseWorkLoadData);
7685 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this);
7687 mPrepareDatastoreOp->SetFailureCode(aResultCode);
7689 mPrepareDatastoreOp->FinishNesting();
7692 void PrepareDatastoreOp::LoadDataOp::Cleanup() {
7693 AssertIsOnOwningThread();
7694 MOZ_ASSERT(mPrepareDatastoreOp);
7695 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this);
7697 mPrepareDatastoreOp->mLoadDataOp = nullptr;
7698 mPrepareDatastoreOp = nullptr;
7700 ConnectionDatastoreOperationBase::Cleanup();
7703 NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressFunction, mozIStorageFunction)
7705 NS_IMETHODIMP
7706 PrepareDatastoreOp::CompressFunction::OnFunctionCall(
7707 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
7708 AssertIsOnIOThread();
7709 MOZ_ASSERT(aFunctionArguments);
7710 MOZ_ASSERT(aResult);
7712 #ifdef DEBUG
7714 uint32_t argCount;
7715 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount));
7716 MOZ_ASSERT(argCount == 1);
7718 int32_t type;
7719 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetTypeOfIndex(0, &type));
7720 MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT);
7722 #endif
7724 QM_TRY_INSPECT(const auto& value,
7725 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7726 nsCString, aFunctionArguments, GetUTF8String, 0));
7728 nsCString compressed;
7729 QM_TRY(OkIf(SnappyCompress(value, compressed)), NS_ERROR_OUT_OF_MEMORY);
7731 const nsCString& buffer = compressed.IsVoid() ? value : compressed;
7733 // mozStorage transforms empty blobs into null values, but our database
7734 // schema doesn't allow null values. We can workaround this by storing
7735 // empty buffers as UTF8 text (SQLite supports the type affinity, so the type
7736 // of the column is not fixed).
7737 nsCOMPtr<nsIVariant> result;
7738 if (0u == buffer.Length()) { // Otherwise empty string becomes null
7739 result = new storage::UTF8TextVariant(buffer);
7740 } else {
7741 result = new storage::BlobVariant(std::make_pair(
7742 static_cast<const void*>(buffer.get()), int(buffer.Length())));
7745 result.forget(aResult);
7746 return NS_OK;
7749 NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressionTypeFunction,
7750 mozIStorageFunction)
7752 NS_IMETHODIMP
7753 PrepareDatastoreOp::CompressionTypeFunction::OnFunctionCall(
7754 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
7755 AssertIsOnIOThread();
7756 MOZ_ASSERT(aFunctionArguments);
7757 MOZ_ASSERT(aResult);
7759 #ifdef DEBUG
7761 uint32_t argCount;
7762 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount));
7763 MOZ_ASSERT(argCount == 1);
7765 int32_t type;
7766 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetTypeOfIndex(0, &type));
7767 MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT);
7769 #endif
7771 QM_TRY_INSPECT(const auto& value,
7772 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7773 nsCString, aFunctionArguments, GetUTF8String, 0));
7775 nsCString compressed;
7776 QM_TRY(OkIf(SnappyCompress(value, compressed)), NS_ERROR_OUT_OF_MEMORY);
7778 const int32_t compression = static_cast<int32_t>(
7779 compressed.IsVoid() ? LSValue::CompressionType::UNCOMPRESSED
7780 : LSValue::CompressionType::SNAPPY);
7782 nsCOMPtr<nsIVariant> result = new storage::IntegerVariant(compression);
7784 result.forget(aResult);
7785 return NS_OK;
7788 /*******************************************************************************
7789 * PrepareObserverOp
7790 ******************************************************************************/
7792 PrepareObserverOp::PrepareObserverOp(
7793 const LSRequestParams& aParams,
7794 const Maybe<ContentParentId>& aContentParentId)
7795 : LSRequestBase(aParams, aContentParentId) {
7796 MOZ_ASSERT(aParams.type() ==
7797 LSRequestParams::TLSRequestPrepareObserverParams);
7800 nsresult PrepareObserverOp::Start() {
7801 AssertIsOnOwningThread();
7802 MOZ_ASSERT(mState == State::StartingRequest);
7803 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7804 MOZ_ASSERT(MayProceed());
7806 const LSRequestPrepareObserverParams params =
7807 mParams.get_LSRequestPrepareObserverParams();
7809 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo();
7811 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
7812 mOrigin = QuotaManager::GetOriginForChrome();
7813 } else {
7814 MOZ_ASSERT(storagePrincipalInfo.type() ==
7815 PrincipalInfo::TContentPrincipalInfo);
7817 mOrigin =
7818 QuotaManager::GetOriginFromValidatedPrincipalInfo(storagePrincipalInfo);
7821 mState = State::SendingReadyMessage;
7822 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
7824 return NS_OK;
7827 void PrepareObserverOp::GetResponse(LSRequestResponse& aResponse) {
7828 AssertIsOnOwningThread();
7829 MOZ_ASSERT(mState == State::SendingResults);
7830 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
7831 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7832 MOZ_ASSERT(MayProceed());
7834 uint64_t observerId = ++gLastObserverId;
7836 RefPtr<Observer> observer = new Observer(mOrigin);
7838 if (!gPreparedObsevers) {
7839 gPreparedObsevers = new PreparedObserverHashtable();
7841 gPreparedObsevers->InsertOrUpdate(observerId, std::move(observer));
7843 LSRequestPrepareObserverResponse prepareObserverResponse;
7844 prepareObserverResponse.observerId() = observerId;
7846 aResponse = prepareObserverResponse;
7849 /*******************************************************************************
7850 + * LSSimpleRequestBase
7852 ******************************************************************************/
7854 LSSimpleRequestBase::LSSimpleRequestBase(
7855 const LSSimpleRequestParams& aParams,
7856 const Maybe<ContentParentId>& aContentParentId)
7857 : mParams(aParams),
7858 mContentParentId(aContentParentId),
7859 mState(State::Initial) {}
7861 LSSimpleRequestBase::~LSSimpleRequestBase() {
7862 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
7863 mState == State::Initial || mState == State::Completed);
7866 void LSSimpleRequestBase::Dispatch() {
7867 AssertIsOnOwningThread();
7869 mState = State::StartingRequest;
7871 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
7874 bool LSSimpleRequestBase::VerifyRequestParams() {
7875 AssertIsOnBackgroundThread();
7877 MOZ_ASSERT(mParams.type() != LSSimpleRequestParams::T__None);
7879 switch (mParams.type()) {
7880 case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: {
7881 const LSSimpleRequestPreloadedParams& params =
7882 mParams.get_LSSimpleRequestPreloadedParams();
7884 if (NS_WARN_IF(!VerifyPrincipalInfo(
7885 params.principalInfo(), params.storagePrincipalInfo(), false))) {
7886 return false;
7889 break;
7892 case LSSimpleRequestParams::TLSSimpleRequestGetStateParams: {
7893 const LSSimpleRequestGetStateParams& params =
7894 mParams.get_LSSimpleRequestGetStateParams();
7896 if (NS_WARN_IF(!VerifyPrincipalInfo(
7897 params.principalInfo(), params.storagePrincipalInfo(), false))) {
7898 return false;
7901 break;
7904 default:
7905 MOZ_CRASH("Should never get here!");
7908 return true;
7911 nsresult LSSimpleRequestBase::StartRequest() {
7912 AssertIsOnOwningThread();
7913 MOZ_ASSERT(mState == State::StartingRequest);
7915 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7916 !MayProceed()) {
7917 return NS_ERROR_ABORT;
7920 #ifdef DEBUG
7921 // Always verify parameters in DEBUG builds!
7922 bool trustParams = false;
7923 #else
7924 bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
7925 #endif
7927 if (!trustParams && NS_WARN_IF(!VerifyRequestParams())) {
7928 return NS_ERROR_FAILURE;
7931 QM_TRY(MOZ_TO_RESULT(Start()));
7933 return NS_OK;
7936 void LSSimpleRequestBase::SendResults() {
7937 AssertIsOnOwningThread();
7938 MOZ_ASSERT(mState == State::SendingResults);
7940 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7941 !MayProceed()) {
7942 MaybeSetFailureCode(NS_ERROR_ABORT);
7945 if (MayProceed()) {
7946 LSSimpleRequestResponse response;
7948 if (NS_SUCCEEDED(ResultCode())) {
7949 GetResponse(response);
7950 } else {
7951 response = ResultCode();
7954 Unused << PBackgroundLSSimpleRequestParent::Send__delete__(this, response);
7957 mState = State::Completed;
7960 NS_IMETHODIMP
7961 LSSimpleRequestBase::Run() {
7962 nsresult rv;
7964 switch (mState) {
7965 case State::StartingRequest:
7966 rv = StartRequest();
7967 break;
7969 case State::SendingResults:
7970 SendResults();
7971 return NS_OK;
7973 default:
7974 MOZ_CRASH("Bad state!");
7977 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) {
7978 MaybeSetFailureCode(rv);
7980 // Must set mState before dispatching otherwise we will race with the owning
7981 // thread.
7982 mState = State::SendingResults;
7984 if (IsOnOwningThread()) {
7985 SendResults();
7986 } else {
7987 MOZ_ALWAYS_SUCCEEDS(
7988 OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
7992 return NS_OK;
7995 void LSSimpleRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
7996 AssertIsOnOwningThread();
7998 NoteComplete();
8001 /*******************************************************************************
8002 * PreloadedOp
8003 ******************************************************************************/
8005 PreloadedOp::PreloadedOp(const LSSimpleRequestParams& aParams,
8006 const Maybe<ContentParentId>& aContentParentId)
8007 : LSSimpleRequestBase(aParams, aContentParentId) {
8008 MOZ_ASSERT(aParams.type() ==
8009 LSSimpleRequestParams::TLSSimpleRequestPreloadedParams);
8012 nsresult PreloadedOp::Start() {
8013 AssertIsOnOwningThread();
8014 MOZ_ASSERT(mState == State::StartingRequest);
8015 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8016 MOZ_ASSERT(MayProceed());
8018 const LSSimpleRequestPreloadedParams& params =
8019 mParams.get_LSSimpleRequestPreloadedParams();
8021 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo();
8023 MOZ_ASSERT(
8024 storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
8025 storagePrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
8026 mOrigin = storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo
8027 ? nsCString{QuotaManager::GetOriginForChrome()}
8028 : QuotaManager::GetOriginFromValidatedPrincipalInfo(
8029 storagePrincipalInfo);
8031 mState = State::SendingResults;
8032 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
8034 return NS_OK;
8037 void PreloadedOp::GetResponse(LSSimpleRequestResponse& aResponse) {
8038 AssertIsOnOwningThread();
8039 MOZ_ASSERT(mState == State::SendingResults);
8040 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
8041 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8042 MOZ_ASSERT(MayProceed());
8044 bool preloaded;
8045 RefPtr<Datastore> datastore;
8046 if ((datastore = GetDatastore(mOrigin)) && !datastore->IsClosed()) {
8047 preloaded = true;
8048 } else {
8049 preloaded = false;
8052 LSSimpleRequestPreloadedResponse preloadedResponse;
8053 preloadedResponse.preloaded() = preloaded;
8055 aResponse = preloadedResponse;
8058 /*******************************************************************************
8059 * GetStateOp
8060 ******************************************************************************/
8062 GetStateOp::GetStateOp(const LSSimpleRequestParams& aParams,
8063 const Maybe<ContentParentId>& aContentParentId)
8064 : LSSimpleRequestBase(aParams, aContentParentId) {
8065 MOZ_ASSERT(aParams.type() ==
8066 LSSimpleRequestParams::TLSSimpleRequestGetStateParams);
8069 nsresult GetStateOp::Start() {
8070 AssertIsOnOwningThread();
8071 MOZ_ASSERT(mState == State::StartingRequest);
8072 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8073 MOZ_ASSERT(MayProceed());
8075 const LSSimpleRequestGetStateParams& params =
8076 mParams.get_LSSimpleRequestGetStateParams();
8078 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo();
8080 MOZ_ASSERT(
8081 storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
8082 storagePrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
8083 mOrigin = storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo
8084 ? nsCString{QuotaManager::GetOriginForChrome()}
8085 : QuotaManager::GetOriginFromValidatedPrincipalInfo(
8086 storagePrincipalInfo);
8088 mState = State::SendingResults;
8089 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
8091 return NS_OK;
8094 void GetStateOp::GetResponse(LSSimpleRequestResponse& aResponse) {
8095 AssertIsOnOwningThread();
8096 MOZ_ASSERT(mState == State::SendingResults);
8097 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
8098 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8099 MOZ_ASSERT(MayProceed());
8101 LSSimpleRequestGetStateResponse getStateResponse;
8103 if (RefPtr<Datastore> datastore = GetDatastore(mOrigin)) {
8104 if (!datastore->IsClosed()) {
8105 getStateResponse.itemInfos() = datastore->GetOrderedItems().Clone();
8109 aResponse = getStateResponse;
8112 /*******************************************************************************
8113 * ArchivedOriginScope
8114 ******************************************************************************/
8116 // static
8117 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromOrigin(
8118 const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey) {
8119 return WrapUnique(
8120 new ArchivedOriginScope(Origin(aOriginAttrSuffix, aOriginKey)));
8123 // static
8124 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromPrefix(
8125 const nsACString& aOriginKey) {
8126 return WrapUnique(new ArchivedOriginScope(Prefix(aOriginKey)));
8129 // static
8130 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromPattern(
8131 const OriginAttributesPattern& aPattern) {
8132 return WrapUnique(new ArchivedOriginScope(Pattern(aPattern)));
8135 // static
8136 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromNull() {
8137 return WrapUnique(new ArchivedOriginScope(Null()));
8140 nsLiteralCString ArchivedOriginScope::GetBindingClause() const {
8141 return mData.match(
8142 [](const Origin&) {
8143 return " WHERE originKey = :originKey "
8144 "AND originAttributes = :originAttributes"_ns;
8146 [](const Pattern&) {
8147 return " WHERE originAttributes MATCH :originAttributesPattern"_ns;
8149 [](const Prefix&) { return " WHERE originKey = :originKey"_ns; },
8150 [](const Null&) { return ""_ns; });
8153 nsresult ArchivedOriginScope::BindToStatement(
8154 mozIStorageStatement* aStmt) const {
8155 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
8156 MOZ_ASSERT(aStmt);
8158 struct Matcher {
8159 mozIStorageStatement* mStmt;
8161 explicit Matcher(mozIStorageStatement* aStmt) : mStmt(aStmt) {}
8163 nsresult operator()(const Origin& aOrigin) {
8164 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName(
8165 "originKey"_ns, aOrigin.OriginNoSuffix())));
8167 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName(
8168 "originAttributes"_ns, aOrigin.OriginSuffix())));
8170 return NS_OK;
8173 nsresult operator()(const Prefix& aPrefix) {
8174 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName(
8175 "originKey"_ns, aPrefix.OriginNoSuffix())));
8177 return NS_OK;
8180 nsresult operator()(const Pattern& aPattern) {
8181 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName(
8182 "originAttributesPattern"_ns, "pattern1"_ns)));
8184 return NS_OK;
8187 nsresult operator()(const Null& aNull) { return NS_OK; }
8190 QM_TRY(MOZ_TO_RESULT(mData.match(Matcher(aStmt))));
8192 return NS_OK;
8195 bool ArchivedOriginScope::HasMatches(
8196 ArchivedOriginHashtable* aHashtable) const {
8197 AssertIsOnIOThread();
8198 MOZ_ASSERT(aHashtable);
8200 return mData.match(
8201 [aHashtable](const Origin& aOrigin) {
8202 const nsCString hashKey = GetArchivedOriginHashKey(
8203 aOrigin.OriginSuffix(), aOrigin.OriginNoSuffix());
8205 return aHashtable->Contains(hashKey);
8207 [aHashtable](const Pattern& aPattern) {
8208 return std::any_of(
8209 aHashtable->Values().cbegin(), aHashtable->Values().cend(),
8210 [&aPattern](const auto& entry) {
8211 return aPattern.GetPattern().Matches(entry->mOriginAttributes);
8214 [aHashtable](const Prefix& aPrefix) {
8215 return std::any_of(
8216 aHashtable->Values().cbegin(), aHashtable->Values().cend(),
8217 [&aPrefix](const auto& entry) {
8218 return entry->mOriginNoSuffix == aPrefix.OriginNoSuffix();
8221 [aHashtable](const Null& aNull) { return !aHashtable->IsEmpty(); });
8224 void ArchivedOriginScope::RemoveMatches(
8225 ArchivedOriginHashtable* aHashtable) const {
8226 AssertIsOnIOThread();
8227 MOZ_ASSERT(aHashtable);
8229 struct Matcher {
8230 ArchivedOriginHashtable* mHashtable;
8232 explicit Matcher(ArchivedOriginHashtable* aHashtable)
8233 : mHashtable(aHashtable) {}
8235 void operator()(const Origin& aOrigin) {
8236 nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(),
8237 aOrigin.OriginNoSuffix());
8239 mHashtable->Remove(hashKey);
8242 void operator()(const Prefix& aPrefix) {
8243 for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) {
8244 const auto& archivedOriginInfo = iter.Data();
8246 if (archivedOriginInfo->mOriginNoSuffix == aPrefix.OriginNoSuffix()) {
8247 iter.Remove();
8252 void operator()(const Pattern& aPattern) {
8253 for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) {
8254 const auto& archivedOriginInfo = iter.Data();
8256 if (aPattern.GetPattern().Matches(
8257 archivedOriginInfo->mOriginAttributes)) {
8258 iter.Remove();
8263 void operator()(const Null& aNull) { mHashtable->Clear(); }
8266 mData.match(Matcher(aHashtable));
8269 /*******************************************************************************
8270 * QuotaClient
8271 ******************************************************************************/
8273 QuotaClient* QuotaClient::sInstance = nullptr;
8275 QuotaClient::QuotaClient()
8276 : mShadowDatabaseMutex("LocalStorage mShadowDatabaseMutex") {
8277 AssertIsOnBackgroundThread();
8278 MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
8280 sInstance = this;
8283 QuotaClient::~QuotaClient() {
8284 AssertIsOnBackgroundThread();
8285 MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
8287 sInstance = nullptr;
8290 mozilla::dom::quota::Client::Type QuotaClient::GetType() {
8291 return QuotaClient::LS;
8294 Result<UsageInfo, nsresult> QuotaClient::InitOrigin(
8295 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
8296 const AtomicBool& aCanceled) {
8297 AssertIsOnIOThread();
8298 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
8299 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
8301 QuotaManager* quotaManager = QuotaManager::Get();
8302 MOZ_ASSERT(quotaManager);
8304 QM_TRY_INSPECT(const auto& directory,
8305 quotaManager->GetOriginDirectory(aOriginMetadata));
8307 MOZ_ASSERT(directory);
8309 QM_TRY(MOZ_TO_RESULT(
8310 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME))));
8312 #ifdef DEBUG
8314 QM_TRY_INSPECT(const bool& exists,
8315 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
8316 MOZ_ASSERT(exists);
8318 #endif
8320 QM_TRY_INSPECT(const auto& directoryPath, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8321 nsString, directory, GetPath));
8323 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(directoryPath));
8325 // XXX Try to make usageFileExists const
8326 QM_TRY_UNWRAP(bool usageFileExists, ExistsAsFile(*usageFile));
8328 QM_TRY_INSPECT(const auto& usageJournalFile,
8329 GetUsageJournalFile(directoryPath));
8331 QM_TRY_INSPECT(const bool& usageJournalFileExists,
8332 ExistsAsFile(*usageJournalFile));
8334 if (usageJournalFileExists) {
8335 if (usageFileExists) {
8336 QM_TRY(MOZ_TO_RESULT(usageFile->Remove(false)));
8338 usageFileExists = false;
8341 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false)));
8344 QM_TRY_INSPECT(const auto& file,
8345 CloneFileAndAppend(*directory, kDataFileName));
8347 QM_TRY_INSPECT(const bool& fileExists, ExistsAsFile(*file));
8349 QM_TRY_INSPECT(
8350 const UsageInfo& res,
8351 ([fileExists, usageFileExists, &file, &usageFile, &usageJournalFile,
8352 &aOriginMetadata]() -> Result<UsageInfo, nsresult> {
8353 if (fileExists) {
8354 QM_TRY_RETURN(QM_OR_ELSE_WARN(
8355 // Expression. To simplify control flow, we call LoadUsageFile
8356 // unconditionally here, even though it will necessarily fail if
8357 // usageFileExists is false.
8358 LoadUsageFile(*usageFile),
8359 // Fallback.
8360 ([&file, &usageFile, &usageJournalFile, &aOriginMetadata](
8361 const nsresult) -> Result<UsageInfo, nsresult> {
8362 QM_TRY_INSPECT(
8363 const auto& connection,
8364 CreateStorageConnectionWithRecovery(
8365 *file, *usageFile, aOriginMetadata.mOrigin, [] {}));
8367 QM_TRY_INSPECT(const int64_t& usage,
8368 GetUsage(*connection,
8369 /* aArchivedOriginScope */ nullptr));
8371 QM_TRY(MOZ_TO_RESULT(
8372 UpdateUsageFile(usageFile, usageJournalFile, usage)));
8374 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false)));
8376 MOZ_ASSERT(usage >= 0);
8377 return UsageInfo{DatabaseUsageType(Some(uint64_t(usage)))};
8378 })));
8381 if (usageFileExists) {
8382 QM_TRY(MOZ_TO_RESULT(usageFile->Remove(false)));
8385 return UsageInfo{};
8386 }()));
8388 // Report unknown files in debug builds, but don't fail, just warn (we don't
8389 // report unknown files in release builds because that requires extra
8390 // scanning of the directory which would slow down entire initialization for
8391 // little benefit).
8393 #ifdef DEBUG
8394 QM_TRY(CollectEachFileAtomicCancelable(
8395 *directory, aCanceled,
8396 [](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
8397 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
8399 switch (dirEntryKind) {
8400 case nsIFileKind::ExistsAsDirectory:
8401 Unused << WARN_IF_FILE_IS_UNKNOWN(*file);
8402 break;
8404 case nsIFileKind::ExistsAsFile: {
8405 QM_TRY_INSPECT(
8406 const auto& leafName,
8407 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName));
8409 if (leafName.Equals(kDataFileName) ||
8410 leafName.Equals(kJournalFileName) ||
8411 leafName.Equals(kUsageFileName) ||
8412 leafName.Equals(kUsageJournalFileName)) {
8413 return Ok{};
8416 Unused << WARN_IF_FILE_IS_UNKNOWN(*file);
8418 break;
8421 case nsIFileKind::DoesNotExist:
8422 // Ignore files that got removed externally while iterating.
8423 break;
8425 return Ok{};
8426 }));
8427 #endif
8429 return res;
8432 nsresult QuotaClient::InitOriginWithoutTracking(
8433 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
8434 const AtomicBool& aCanceled) {
8435 AssertIsOnIOThread();
8437 // This is called when a storage/permanent/${origin}/ls directory exists. Even
8438 // though this shouldn't happen with a "good" profile, we shouldn't return an
8439 // error here, since that would cause origin initialization to fail. We just
8440 // warn and otherwise ignore that.
8441 UNKNOWN_FILE_WARNING(NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME));
8442 return NS_OK;
8445 Result<UsageInfo, nsresult> QuotaClient::GetUsageForOrigin(
8446 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
8447 const AtomicBool& aCanceled) {
8448 AssertIsOnIOThread();
8449 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
8451 // We can't open the database at this point, since it can be already used
8452 // by the connection thread. Use the cached value instead.
8454 QuotaManager* quotaManager = QuotaManager::Get();
8455 MOZ_ASSERT(quotaManager);
8457 return quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT,
8458 aOriginMetadata, Client::LS);
8461 nsresult QuotaClient::AboutToClearOrigins(
8462 const Nullable<PersistenceType>& aPersistenceType,
8463 const OriginScope& aOriginScope) {
8464 AssertIsOnIOThread();
8466 // This method is not called when the clearing is triggered by the eviction
8467 // process. It's on purpose to avoid a problem with the origin access time
8468 // which can be described as follows:
8469 // When there's a storage pressure condition and quota manager starts
8470 // collecting origins for eviction, there can be an origin that hasn't been
8471 // touched for long time. However, the old implementation of local storage
8472 // could have touched the origin only recently and the new implementation
8473 // hasn't had a chance to create a new per origin database for it yet (the
8474 // data is still in the archive database), so the origin access time hasn't
8475 // been updated either. In the end, the origin would be evicted despite the
8476 // fact that there was recent local storage activity.
8477 // So this method clears the archived data and shadow database entries for
8478 // given origin scope, but only if it's a privacy-related origin clearing.
8480 if (!aPersistenceType.IsNull() &&
8481 aPersistenceType.Value() != PERSISTENCE_TYPE_DEFAULT) {
8482 return NS_OK;
8485 // There can be no data for the system principal in the archive or the shadow
8486 // database. This early return silences potential warnings caused by failed
8487 // `CreateAerchivedOriginScope` because it calls `GenerateOriginKey2` which
8488 // doesn't support the system principal.
8489 if (aOriginScope.IsOrigin() &&
8490 aOriginScope.GetOrigin() == QuotaManager::GetOriginForChrome()) {
8491 return NS_OK;
8494 const bool shadowWrites = gShadowWrites;
8496 QM_TRY_INSPECT(const auto& archivedOriginScope,
8497 CreateArchivedOriginScope(aOriginScope));
8499 if (!gArchivedOrigins) {
8500 QM_TRY(MOZ_TO_RESULT(LoadArchivedOrigins()));
8501 MOZ_ASSERT(gArchivedOrigins);
8504 const bool hasDataForRemoval =
8505 archivedOriginScope->HasMatches(gArchivedOrigins);
8507 QuotaManager* quotaManager = QuotaManager::Get();
8508 MOZ_ASSERT(quotaManager);
8510 const nsString& basePath = quotaManager->GetBasePath();
8513 MutexAutoLock shadowDatabaseLock(mShadowDatabaseMutex);
8515 QM_TRY_INSPECT(
8516 const auto& connection,
8517 ([&basePath]() -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
8518 if (gInitializedShadowStorage) {
8519 QM_TRY_RETURN(GetShadowStorageConnection(basePath));
8522 QM_TRY_UNWRAP(auto connection,
8523 CreateShadowStorageConnection(basePath));
8525 gInitializedShadowStorage = true;
8527 return connection;
8528 }()));
8531 Maybe<AutoDatabaseAttacher> maybeAutoArchiveDatabaseAttacher;
8533 if (hasDataForRemoval) {
8534 QM_TRY_INSPECT(const auto& archiveFile,
8535 GetArchiveFile(quotaManager->GetStoragePath()));
8537 maybeAutoArchiveDatabaseAttacher.emplace(
8538 AutoDatabaseAttacher(connection, archiveFile, "archive"_ns));
8540 QM_TRY(MOZ_TO_RESULT(maybeAutoArchiveDatabaseAttacher->Attach()));
8543 if (archivedOriginScope->IsPattern()) {
8544 nsCOMPtr<mozIStorageFunction> function(
8545 new MatchFunction(archivedOriginScope->GetPattern()));
8547 QM_TRY(
8548 MOZ_TO_RESULT(connection->CreateFunction("match"_ns, 2, function)));
8552 QM_TRY_INSPECT(const auto& stmt,
8553 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8554 nsCOMPtr<mozIStorageStatement>, connection,
8555 CreateStatement, "BEGIN IMMEDIATE;"_ns));
8557 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
8560 if (shadowWrites) {
8561 QM_TRY(MOZ_TO_RESULT(
8562 PerformDelete(connection, "main"_ns, archivedOriginScope.get())));
8565 if (hasDataForRemoval) {
8566 QM_TRY(MOZ_TO_RESULT(PerformDelete(connection, "archive"_ns,
8567 archivedOriginScope.get())));
8571 QM_TRY_INSPECT(const auto& stmt,
8572 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8573 nsCOMPtr<mozIStorageStatement>, connection,
8574 CreateStatement, "COMMIT;"_ns));
8576 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
8579 if (archivedOriginScope->IsPattern()) {
8580 QM_TRY(MOZ_TO_RESULT(connection->RemoveFunction("match"_ns)));
8583 if (hasDataForRemoval) {
8584 MOZ_ASSERT(maybeAutoArchiveDatabaseAttacher.isSome());
8585 QM_TRY(MOZ_TO_RESULT(maybeAutoArchiveDatabaseAttacher->Detach()));
8587 maybeAutoArchiveDatabaseAttacher.reset();
8589 MOZ_ASSERT(gArchivedOrigins);
8590 MOZ_ASSERT(archivedOriginScope->HasMatches(gArchivedOrigins));
8591 archivedOriginScope->RemoveMatches(gArchivedOrigins);
8594 QM_TRY(MOZ_TO_RESULT(connection->Close()));
8597 if (aOriginScope.IsNull()) {
8598 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(basePath));
8600 QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false)));
8602 gInitializedShadowStorage = false;
8605 return NS_OK;
8608 void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
8609 const nsACString& aOrigin) {
8610 AssertIsOnIOThread();
8613 void QuotaClient::OnRepositoryClearCompleted(PersistenceType aPersistenceType) {
8614 AssertIsOnIOThread();
8617 void QuotaClient::ReleaseIOThreadObjects() {
8618 AssertIsOnIOThread();
8620 gInitializationInfo = nullptr;
8622 // Delete archived origins hashtable since QuotaManager clears the whole
8623 // storage directory including ls-archive.sqlite.
8625 gArchivedOrigins = nullptr;
8628 void QuotaClient::AbortOperationsForLocks(
8629 const DirectoryLockIdTable& aDirectoryLockIds) {
8630 AssertIsOnBackgroundThread();
8632 // A PrepareDatastoreOp object could already acquire a directory lock for
8633 // the given origin. Its last step is creation of a Datastore object (which
8634 // will take ownership of the directory lock) and a PreparedDatastore object
8635 // which keeps the Datastore alive until a database actor is created.
8636 // We need to invalidate the PreparedDatastore object when it's created,
8637 // otherwise the Datastore object can block the origin clear operation for
8638 // long time. It's not a problem that we don't fail the PrepareDatastoreOp
8639 // immediatelly (avoiding the creation of the Datastore and PreparedDatastore
8640 // object). We will call RequestAllowToClose on the database actor once it's
8641 // created and the child actor will respond by sending AllowToClose which
8642 // will close the Datastore on the parent side (the closing releases the
8643 // directory lock).
8645 InvalidatePrepareDatastoreOpsMatching(
8646 [&aDirectoryLockIds](const auto& prepareDatastoreOp) {
8647 // Check if the PrepareDatastoreOp holds an acquired DirectoryLock.
8648 // Origin clearing can't be blocked by this PrepareDatastoreOp if there
8649 // is no acquired DirectoryLock. If there is an acquired DirectoryLock,
8650 // check if the table contains the lock for the PrepareDatastoreOp.
8651 return IsLockForObjectAcquiredAndContainedInLockTable(
8652 prepareDatastoreOp, aDirectoryLockIds);
8655 if (gPrivateDatastores) {
8656 gPrivateDatastores->RemoveIf([&aDirectoryLockIds](const auto& iter) {
8657 const auto& privateDatastore = iter.Data();
8659 // The PrivateDatastore::mDatastore member is not cleared until the
8660 // PrivateDatastore is destroyed.
8661 const auto& datastore = privateDatastore->DatastoreRef();
8663 // If the PrivateDatastore exists then it must be registered in
8664 // Datastore::mHasLivePrivateDatastore as well. The Datastore must have
8665 // a DirectoryLock if there is a registered PrivateDatastore.
8666 return IsLockForObjectContainedInLockTable(datastore, aDirectoryLockIds);
8669 if (!gPrivateDatastores->Count()) {
8670 gPrivateDatastores = nullptr;
8674 InvalidatePreparedDatastoresMatching([&aDirectoryLockIds](
8675 const auto& preparedDatastore) {
8676 // The PreparedDatastore::mDatastore member is not cleared until the
8677 // PreparedDatastore is destroyed.
8678 const auto& datastore = preparedDatastore.DatastoreRef();
8680 // If the PreparedDatastore exists then it must be registered in
8681 // Datastore::mPreparedDatastores as well. The Datastore must have a
8682 // DirectoryLock if there are registered PreparedDatastore objects.
8683 return IsLockForObjectContainedInLockTable(datastore, aDirectoryLockIds);
8686 RequestAllowToCloseDatabasesMatching(
8687 [&aDirectoryLockIds](const auto& database) {
8688 const auto& maybeDatastore = database.MaybeDatastoreRef();
8690 // If the Database is registered in gLiveDatabases then it must have a
8691 // Datastore.
8692 MOZ_ASSERT(maybeDatastore.isSome());
8694 // If the Database is registered in gLiveDatabases then it must be
8695 // registered in Datastore::mDatabases as well. The Datastore must have
8696 // a DirectoryLock if there are registered Database objects.
8697 return IsLockForObjectContainedInLockTable(*maybeDatastore,
8698 aDirectoryLockIds);
8702 void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) {
8703 AssertIsOnBackgroundThread();
8705 RequestAllowToCloseDatabasesMatching(
8706 [&aContentParentId](const auto& database) {
8707 return database.IsOwnedByProcess(aContentParentId);
8711 void QuotaClient::AbortAllOperations() {
8712 AssertIsOnBackgroundThread();
8714 InvalidatePrepareDatastoreOpsMatching([](const auto& prepareDatastoreOp) {
8715 return prepareDatastoreOp.MaybeDirectoryLockRef();
8718 if (gPrivateDatastores) {
8719 gPrivateDatastores = nullptr;
8722 InvalidatePreparedDatastoresMatching([](const auto&) { return true; });
8724 RequestAllowToCloseDatabasesMatching([](const auto&) { return true; });
8727 void QuotaClient::StartIdleMaintenance() { AssertIsOnBackgroundThread(); }
8729 void QuotaClient::StopIdleMaintenance() { AssertIsOnBackgroundThread(); }
8731 void QuotaClient::InitiateShutdown() {
8732 // gPrepareDatastoreOps are short lived objects running a state machine.
8733 // The shutdown flag is checked between states, so we don't have to notify
8734 // all the objects here.
8735 // Allocation of a new PrepareDatastoreOp object is prevented once the
8736 // shutdown flag is set.
8737 // When the last PrepareDatastoreOp finishes, the gPrepareDatastoreOps array
8738 // is destroyed.
8740 if (gPreparedDatastores) {
8741 gPreparedDatastores = nullptr;
8744 if (gPrivateDatastores) {
8745 gPrivateDatastores = nullptr;
8748 RequestAllowToCloseDatabasesMatching([](const auto&) { return true; });
8750 if (gPreparedObsevers) {
8751 gPreparedObsevers = nullptr;
8755 bool QuotaClient::IsShutdownCompleted() const {
8756 // Don't have to check gPrivateDatastores and gPreparedDatastores since we
8757 // nulled it out in InitiateShutdown.
8758 return !gPrepareDatastoreOps && !gDatastores && !gLiveDatabases;
8761 void QuotaClient::ForceKillActors() { ForceKillAllDatabases(); }
8763 nsCString QuotaClient::GetShutdownStatus() const {
8764 AssertIsOnBackgroundThread();
8766 nsCString data;
8768 if (gPrepareDatastoreOps) {
8769 data.Append("PrepareDatastoreOperations: ");
8770 data.AppendInt(static_cast<uint32_t>(gPrepareDatastoreOps->Length()));
8771 data.Append(" (");
8773 // XXX What's the purpose of adding these to a hashtable before joining them
8774 // to the string? (Maybe this used to be an ordered container before???)
8775 nsTHashSet<nsCString> ids;
8776 std::transform(gPrepareDatastoreOps->cbegin(), gPrepareDatastoreOps->cend(),
8777 MakeInserter(ids), [](const auto& prepareDatastoreOp) {
8778 nsCString id;
8779 prepareDatastoreOp->Stringify(id);
8780 return id;
8783 StringJoinAppend(data, ", "_ns, ids);
8785 data.Append(")\n");
8788 if (gDatastores) {
8789 data.Append("Datastores: ");
8790 data.AppendInt(gDatastores->Count());
8791 data.Append(" (");
8793 // XXX It might be confusing to remove duplicates here, as the actual list
8794 // won't match the count then.
8795 nsTHashSet<nsCString> ids;
8796 std::transform(gDatastores->Values().cbegin(), gDatastores->Values().cend(),
8797 MakeInserter(ids), [](const auto& entry) {
8798 nsCString id;
8799 entry->Stringify(id);
8800 return id;
8803 StringJoinAppend(data, ", "_ns, ids);
8805 data.Append(")\n");
8808 if (gLiveDatabases) {
8809 data.Append("LiveDatabases: ");
8810 data.AppendInt(static_cast<uint32_t>(gLiveDatabases->Length()));
8811 data.Append(" (");
8813 // XXX It might be confusing to remove duplicates here, as the actual list
8814 // won't match the count then.
8815 nsTHashSet<nsCString> ids;
8816 std::transform(gLiveDatabases->cbegin(), gLiveDatabases->cend(),
8817 MakeInserter(ids), [](const auto& database) {
8818 nsCString id;
8819 database->Stringify(id);
8820 return id;
8823 StringJoinAppend(data, ", "_ns, ids);
8825 data.Append(")\n");
8828 return data;
8831 void QuotaClient::FinalizeShutdown() {
8832 // And finally, shutdown the connection thread.
8833 if (gConnectionThread) {
8834 gConnectionThread->Shutdown();
8836 gConnectionThread = nullptr;
8840 Result<UniquePtr<ArchivedOriginScope>, nsresult>
8841 QuotaClient::CreateArchivedOriginScope(const OriginScope& aOriginScope) {
8842 AssertIsOnIOThread();
8844 if (aOriginScope.IsOrigin()) {
8845 QM_TRY_INSPECT(const auto& principalInfo,
8846 QuotaManager::ParseOrigin(aOriginScope.GetOrigin()));
8848 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]),
8849 GenerateOriginKey2(principalInfo));
8851 return ArchivedOriginScope::CreateFromOrigin(originAttrSuffix, originKey);
8854 if (aOriginScope.IsPrefix()) {
8855 QM_TRY_INSPECT(const auto& principalInfo,
8856 QuotaManager::ParseOrigin(aOriginScope.GetOriginNoSuffix()));
8858 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]),
8859 GenerateOriginKey2(principalInfo));
8861 Unused << originAttrSuffix;
8863 return ArchivedOriginScope::CreateFromPrefix(originKey);
8866 if (aOriginScope.IsPattern()) {
8867 return ArchivedOriginScope::CreateFromPattern(aOriginScope.GetPattern());
8870 MOZ_ASSERT(aOriginScope.IsNull());
8872 return ArchivedOriginScope::CreateFromNull();
8875 nsresult QuotaClient::PerformDelete(
8876 mozIStorageConnection* aConnection, const nsACString& aSchemaName,
8877 ArchivedOriginScope* aArchivedOriginScope) const {
8878 AssertIsOnIOThread();
8879 MOZ_ASSERT(aConnection);
8880 MOZ_ASSERT(aArchivedOriginScope);
8882 QM_TRY_INSPECT(
8883 const auto& stmt,
8884 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8885 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
8886 "DELETE FROM "_ns + aSchemaName + ".webappsstore2"_ns +
8887 aArchivedOriginScope->GetBindingClause() + ";"_ns));
8889 QM_TRY(MOZ_TO_RESULT(aArchivedOriginScope->BindToStatement(stmt)));
8891 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
8893 return NS_OK;
8896 NS_IMPL_ISUPPORTS(QuotaClient::MatchFunction, mozIStorageFunction)
8898 NS_IMETHODIMP
8899 QuotaClient::MatchFunction::OnFunctionCall(
8900 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
8901 AssertIsOnIOThread();
8902 MOZ_ASSERT(aFunctionArguments);
8903 MOZ_ASSERT(aResult);
8905 QM_TRY_INSPECT(const auto& suffix,
8906 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8907 nsAutoCString, aFunctionArguments, GetUTF8String, 1));
8909 OriginAttributes oa;
8910 QM_TRY(OkIf(oa.PopulateFromSuffix(suffix)), NS_ERROR_FAILURE);
8912 const bool result = mPattern.Matches(oa);
8914 RefPtr<nsVariant> outVar(new nsVariant());
8915 QM_TRY(MOZ_TO_RESULT(outVar->SetAsBool(result)));
8917 outVar.forget(aResult);
8918 return NS_OK;
8921 /*******************************************************************************
8922 * AutoWriteTransaction
8923 ******************************************************************************/
8925 AutoWriteTransaction::AutoWriteTransaction(bool aShadowWrites)
8926 : mConnection(nullptr), mShadowWrites(aShadowWrites) {
8927 AssertIsOnGlobalConnectionThread();
8929 MOZ_COUNT_CTOR(mozilla::dom::AutoWriteTransaction);
8932 AutoWriteTransaction::~AutoWriteTransaction() {
8933 AssertIsOnGlobalConnectionThread();
8935 MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction);
8937 if (mConnection) {
8938 QM_WARNONLY_TRY(QM_TO_RESULT(mConnection->RollbackWriteTransaction()));
8940 if (mShadowWrites) {
8941 QM_WARNONLY_TRY(QM_TO_RESULT(DetachShadowDatabaseAndUnlock()));
8946 nsresult AutoWriteTransaction::Start(Connection* aConnection) {
8947 AssertIsOnGlobalConnectionThread();
8948 MOZ_ASSERT(aConnection);
8949 MOZ_ASSERT(!mConnection);
8951 if (mShadowWrites) {
8952 QM_TRY(MOZ_TO_RESULT(LockAndAttachShadowDatabase(aConnection)));
8955 QM_TRY(MOZ_TO_RESULT(aConnection->BeginWriteTransaction()));
8957 mConnection = aConnection;
8959 return NS_OK;
8962 nsresult AutoWriteTransaction::Commit() {
8963 AssertIsOnGlobalConnectionThread();
8964 MOZ_ASSERT(mConnection);
8966 QM_TRY(MOZ_TO_RESULT(mConnection->CommitWriteTransaction()));
8968 if (mShadowWrites) {
8969 QM_TRY(MOZ_TO_RESULT(DetachShadowDatabaseAndUnlock()));
8972 mConnection = nullptr;
8974 return NS_OK;
8977 nsresult AutoWriteTransaction::LockAndAttachShadowDatabase(
8978 Connection* aConnection) {
8979 AssertIsOnGlobalConnectionThread();
8980 MOZ_ASSERT(aConnection);
8981 MOZ_ASSERT(!mConnection);
8982 MOZ_ASSERT(mShadowDatabaseLock.isNothing());
8983 MOZ_ASSERT(mShadowWrites);
8985 QuotaManager* quotaManager = QuotaManager::Get();
8986 MOZ_ASSERT(quotaManager);
8988 mShadowDatabaseLock.emplace(
8989 aConnection->GetQuotaClient()->ShadowDatabaseMutex());
8991 QM_TRY(MOZ_TO_RESULT(AttachShadowDatabase(
8992 quotaManager->GetBasePath(), &aConnection->MutableStorageConnection())));
8994 return NS_OK;
8997 nsresult AutoWriteTransaction::DetachShadowDatabaseAndUnlock() {
8998 AssertIsOnGlobalConnectionThread();
8999 MOZ_ASSERT(mConnection);
9000 MOZ_ASSERT(mShadowDatabaseLock.isSome());
9001 MOZ_ASSERT(mShadowWrites);
9003 nsCOMPtr<mozIStorageConnection> storageConnection =
9004 mConnection->StorageConnection();
9005 MOZ_ASSERT(storageConnection);
9007 QM_TRY(MOZ_TO_RESULT(DetachShadowDatabase(storageConnection)));
9009 mShadowDatabaseLock.reset();
9011 return NS_OK;
9014 } // namespace mozilla::dom