Bug 1839170 - Refactor Snap pulling, Add Firefox Snap Core22 and GNOME 42 SDK symbols...
[gecko.git] / dom / localstorage / ActorsParent.cpp
blob865f3327bbef033d562fc405035f5954d2ff5270
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)
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 OpenDirectoryListener,
2188 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
2189 class LoadDataOp;
2191 class CompressFunction;
2192 class CompressionTypeFunction;
2194 enum class NestedState {
2195 // The nesting has not yet taken place. Next step is
2196 // CheckExistingOperations.
2197 BeforeNesting,
2199 // Checking if a prepare datastore operation is already running for given
2200 // origin on the PBackground thread. Next step is CheckClosingDatastore.
2201 CheckExistingOperations,
2203 // Checking if a datastore is closing the connection for given origin on
2204 // the PBackground thread. Next step is PreparationPending.
2205 CheckClosingDatastore,
2207 // Ensuring quota manager is created and opening directory on the
2208 // PBackground thread. Next step is either SendingResults if quota manager
2209 // is not available or DirectoryOpenPending if quota manager is available.
2210 // If a datastore already exists for given origin then the next state is
2211 // SendingReadyMessage.
2212 PreparationPending,
2214 // Waiting for directory open allowed on the PBackground thread. The next
2215 // step is either SendingReadyMessage if directory lock failed to acquire,
2216 // or DatabaseWorkOpen if directory lock is acquired.
2217 DirectoryOpenPending,
2219 // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
2220 // BeginLoadData.
2221 DatabaseWorkOpen,
2223 // Starting a load data operation on the PBackground thread. Next step is
2224 // DatabaseWorkLoadData.
2225 BeginLoadData,
2227 // Waiting to do/doing work on the connection thread. This involves waiting
2228 // for the LoadDataOp to do its work. Eventually the state will transition
2229 // to SendingReadyMessage.
2230 DatabaseWorkLoadData,
2232 // The nesting has completed.
2233 AfterNesting
2236 RefPtr<PrepareDatastoreOp> mDelayedOp;
2237 RefPtr<DirectoryLock> mPendingDirectoryLock;
2238 RefPtr<DirectoryLock> mDirectoryLock;
2239 RefPtr<Connection> mConnection;
2240 RefPtr<Datastore> mDatastore;
2241 UniquePtr<ArchivedOriginScope> mArchivedOriginScope;
2242 LoadDataOp* mLoadDataOp;
2243 nsTHashMap<nsStringHashKey, LSValue> mValues;
2244 nsTArray<LSItemInfo> mOrderedItems;
2245 OriginMetadata mOriginMetadata;
2246 nsCString mMainThreadOrigin;
2247 nsString mDatabaseFilePath;
2248 uint32_t mPrivateBrowsingId;
2249 int64_t mUsage;
2250 int64_t mSizeOfKeys;
2251 int64_t mSizeOfItems;
2252 uint64_t mDatastoreId;
2253 NestedState mNestedState;
2254 const bool mForPreload;
2255 bool mDatabaseNotAvailable;
2256 // Set when the Datastore has been registered with gPrivateDatastores so that
2257 // it can be unregistered if an error is encountered in PrepareDatastoreOp.
2258 FlippedOnce<false> mPrivateDatastoreRegistered;
2259 // Set when the Datastore has been registered with gPreparedDatastores so
2260 // that it can be unregistered if an error is encountered in
2261 // PrepareDatastoreOp.
2262 FlippedOnce<false> mPreparedDatastoreRegistered;
2263 bool mInvalidated;
2265 #ifdef DEBUG
2266 int64_t mDEBUGUsage;
2267 #endif
2269 public:
2270 PrepareDatastoreOp(const LSRequestParams& aParams,
2271 const Maybe<ContentParentId>& aContentParentId);
2273 Maybe<DirectoryLock&> MaybeDirectoryLockRef() const {
2274 AssertIsOnBackgroundThread();
2276 return ToMaybeRef(mDirectoryLock.get());
2279 bool OriginIsKnown() const {
2280 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
2282 return !mOriginMetadata.mOrigin.IsEmpty();
2285 const nsCString& Origin() const {
2286 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
2287 MOZ_ASSERT(OriginIsKnown());
2289 return mOriginMetadata.mOrigin;
2292 void Invalidate() {
2293 AssertIsOnOwningThread();
2295 mInvalidated = true;
2298 void StringifyNestedState(nsACString& aResult) const;
2300 void Stringify(nsACString& aResult) const override;
2302 void Log() override;
2304 private:
2305 ~PrepareDatastoreOp() override;
2307 nsresult Start() override;
2309 nsresult CheckExistingOperations();
2311 nsresult CheckClosingDatastoreInternal();
2313 nsresult CheckClosingDatastore();
2315 nsresult BeginDatastorePreparationInternal();
2317 nsresult BeginDatastorePreparation();
2319 void SendToIOThread();
2321 nsresult DatabaseWork();
2323 nsresult DatabaseNotAvailable();
2325 nsresult EnsureDirectoryEntry(nsIFile* aEntry, bool aCreateIfNotExists,
2326 bool aDirectory,
2327 bool* aAlreadyExisted = nullptr);
2329 nsresult VerifyDatabaseInformation(mozIStorageConnection* aConnection);
2331 already_AddRefed<QuotaObject> GetQuotaObject();
2333 nsresult BeginLoadData();
2335 void FinishNesting();
2337 nsresult FinishNestingOnNonOwningThread();
2339 nsresult NestedRun() override;
2341 void GetResponse(LSRequestResponse& aResponse) override;
2343 void Cleanup() override;
2345 void ConnectionClosedCallback();
2347 void CleanupMetadata();
2349 NS_DECL_ISUPPORTS_INHERITED
2351 // IPDL overrides.
2352 void ActorDestroy(ActorDestroyReason aWhy) override;
2354 // OpenDirectoryListener overrides.
2355 void DirectoryLockAcquired(DirectoryLock* aLock) override;
2357 void DirectoryLockFailed() override;
2360 class PrepareDatastoreOp::LoadDataOp final
2361 : public ConnectionDatastoreOperationBase {
2362 RefPtr<PrepareDatastoreOp> mPrepareDatastoreOp;
2364 public:
2365 explicit LoadDataOp(PrepareDatastoreOp* aPrepareDatastoreOp)
2366 : ConnectionDatastoreOperationBase(aPrepareDatastoreOp->mConnection),
2367 mPrepareDatastoreOp(aPrepareDatastoreOp) {}
2369 private:
2370 ~LoadDataOp() = default;
2372 nsresult DoDatastoreWork() override;
2374 void OnSuccess() override;
2376 void OnFailure(nsresult aResultCode) override;
2378 void Cleanup() override;
2381 class PrepareDatastoreOp::CompressFunction final : public mozIStorageFunction {
2382 private:
2383 ~CompressFunction() = default;
2385 NS_DECL_ISUPPORTS
2386 NS_DECL_MOZISTORAGEFUNCTION
2389 class PrepareDatastoreOp::CompressionTypeFunction final
2390 : public mozIStorageFunction {
2391 private:
2392 ~CompressionTypeFunction() = default;
2394 NS_DECL_ISUPPORTS
2395 NS_DECL_MOZISTORAGEFUNCTION
2398 class PrepareObserverOp : public LSRequestBase {
2399 nsCString mOrigin;
2401 public:
2402 PrepareObserverOp(const LSRequestParams& aParams,
2403 const Maybe<ContentParentId>& aContentParentId);
2405 private:
2406 nsresult Start() override;
2408 void GetResponse(LSRequestResponse& aResponse) override;
2411 class LSSimpleRequestBase : public DatastoreOperationBase,
2412 public PBackgroundLSSimpleRequestParent {
2413 protected:
2414 enum class State {
2415 // Just created on the PBackground thread. Next step is StartingRequest.
2416 Initial,
2418 // Waiting to start/starting request on the PBackground thread. Next step is
2419 // SendingResults.
2420 StartingRequest,
2422 // Waiting to send/sending results on the PBackground thread. Next step is
2423 // Completed.
2424 SendingResults,
2426 // All done.
2427 Completed
2430 const LSSimpleRequestParams mParams;
2431 Maybe<ContentParentId> mContentParentId;
2432 State mState;
2434 public:
2435 LSSimpleRequestBase(const LSSimpleRequestParams& aParams,
2436 const Maybe<ContentParentId>& aContentParentId);
2438 void Dispatch();
2440 protected:
2441 ~LSSimpleRequestBase() override;
2443 virtual nsresult Start() = 0;
2445 virtual void GetResponse(LSSimpleRequestResponse& aResponse) = 0;
2447 private:
2448 bool VerifyRequestParams();
2450 nsresult StartRequest();
2452 void SendResults();
2454 // Common nsIRunnable implementation that subclasses may not override.
2455 NS_IMETHOD
2456 Run() final;
2458 // IPDL methods.
2459 void ActorDestroy(ActorDestroyReason aWhy) override;
2462 class PreloadedOp : public LSSimpleRequestBase {
2463 nsCString mOrigin;
2465 public:
2466 PreloadedOp(const LSSimpleRequestParams& aParams,
2467 const Maybe<ContentParentId>& aContentParentId);
2469 private:
2470 nsresult Start() override;
2472 void GetResponse(LSSimpleRequestResponse& aResponse) override;
2475 class GetStateOp : public LSSimpleRequestBase {
2476 nsCString mOrigin;
2478 public:
2479 GetStateOp(const LSSimpleRequestParams& aParams,
2480 const Maybe<ContentParentId>& aContentParentId);
2482 private:
2483 nsresult Start() override;
2485 void GetResponse(LSSimpleRequestResponse& aResponse) override;
2488 /*******************************************************************************
2489 * Other class declarations
2490 ******************************************************************************/
2492 struct ArchivedOriginInfo {
2493 OriginAttributes mOriginAttributes;
2494 nsCString mOriginNoSuffix;
2496 ArchivedOriginInfo(const OriginAttributes& aOriginAttributes,
2497 const nsACString& aOriginNoSuffix)
2498 : mOriginAttributes(aOriginAttributes),
2499 mOriginNoSuffix(aOriginNoSuffix) {}
2502 class ArchivedOriginScope {
2503 struct Origin {
2504 nsCString mOriginSuffix;
2505 nsCString mOriginNoSuffix;
2507 Origin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix)
2508 : mOriginSuffix(aOriginSuffix), mOriginNoSuffix(aOriginNoSuffix) {}
2510 const nsACString& OriginSuffix() const { return mOriginSuffix; }
2512 const nsACString& OriginNoSuffix() const { return mOriginNoSuffix; }
2515 struct Prefix {
2516 nsCString mOriginNoSuffix;
2518 explicit Prefix(const nsACString& aOriginNoSuffix)
2519 : mOriginNoSuffix(aOriginNoSuffix) {}
2521 const nsACString& OriginNoSuffix() const { return mOriginNoSuffix; }
2524 struct Pattern {
2525 UniquePtr<OriginAttributesPattern> mPattern;
2527 explicit Pattern(const OriginAttributesPattern& aPattern)
2528 : mPattern(MakeUnique<OriginAttributesPattern>(aPattern)) {}
2530 Pattern(const Pattern& aOther)
2531 : mPattern(MakeUnique<OriginAttributesPattern>(*aOther.mPattern)) {}
2533 Pattern(Pattern&& aOther) = default;
2535 const OriginAttributesPattern& GetPattern() const {
2536 MOZ_ASSERT(mPattern);
2537 return *mPattern;
2541 struct Null {};
2543 using DataType = Variant<Origin, Pattern, Prefix, Null>;
2545 DataType mData;
2547 public:
2548 static UniquePtr<ArchivedOriginScope> CreateFromOrigin(
2549 const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey);
2551 static UniquePtr<ArchivedOriginScope> CreateFromPrefix(
2552 const nsACString& aOriginKey);
2554 static UniquePtr<ArchivedOriginScope> CreateFromPattern(
2555 const OriginAttributesPattern& aPattern);
2557 static UniquePtr<ArchivedOriginScope> CreateFromNull();
2559 bool IsOrigin() const { return mData.is<Origin>(); }
2561 bool IsPrefix() const { return mData.is<Prefix>(); }
2563 bool IsPattern() const { return mData.is<Pattern>(); }
2565 bool IsNull() const { return mData.is<Null>(); }
2567 const nsACString& OriginSuffix() const {
2568 MOZ_ASSERT(IsOrigin());
2570 return mData.as<Origin>().OriginSuffix();
2573 const nsACString& OriginNoSuffix() const {
2574 MOZ_ASSERT(IsOrigin() || IsPrefix());
2576 if (IsOrigin()) {
2577 return mData.as<Origin>().OriginNoSuffix();
2579 return mData.as<Prefix>().OriginNoSuffix();
2582 const OriginAttributesPattern& GetPattern() const {
2583 MOZ_ASSERT(IsPattern());
2585 return mData.as<Pattern>().GetPattern();
2588 nsLiteralCString GetBindingClause() const;
2590 nsresult BindToStatement(mozIStorageStatement* aStatement) const;
2592 bool HasMatches(ArchivedOriginHashtable* aHashtable) const;
2594 void RemoveMatches(ArchivedOriginHashtable* aHashtable) const;
2596 private:
2597 // Move constructors
2598 explicit ArchivedOriginScope(const Origin&& aOrigin) : mData(aOrigin) {}
2600 explicit ArchivedOriginScope(const Pattern&& aPattern) : mData(aPattern) {}
2602 explicit ArchivedOriginScope(const Prefix&& aPrefix) : mData(aPrefix) {}
2604 explicit ArchivedOriginScope(const Null&& aNull) : mData(aNull) {}
2607 class QuotaClient final : public mozilla::dom::quota::Client {
2608 class MatchFunction;
2610 static QuotaClient* sInstance;
2612 Mutex mShadowDatabaseMutex MOZ_UNANNOTATED;
2614 public:
2615 QuotaClient();
2617 static QuotaClient* GetInstance() {
2618 AssertIsOnBackgroundThread();
2620 return sInstance;
2623 mozilla::Mutex& ShadowDatabaseMutex() {
2624 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
2626 return mShadowDatabaseMutex;
2629 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::QuotaClient, override)
2631 Type GetType() override;
2633 Result<UsageInfo, nsresult> InitOrigin(PersistenceType aPersistenceType,
2634 const OriginMetadata& aOriginMetadata,
2635 const AtomicBool& aCanceled) override;
2637 nsresult InitOriginWithoutTracking(PersistenceType aPersistenceType,
2638 const OriginMetadata& aOriginMetadata,
2639 const AtomicBool& aCanceled) override;
2641 Result<UsageInfo, nsresult> GetUsageForOrigin(
2642 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
2643 const AtomicBool& aCanceled) override;
2645 nsresult AboutToClearOrigins(
2646 const Nullable<PersistenceType>& aPersistenceType,
2647 const OriginScope& aOriginScope) override;
2649 void OnOriginClearCompleted(PersistenceType aPersistenceType,
2650 const nsACString& aOrigin) override;
2652 void OnRepositoryClearCompleted(PersistenceType aPersistenceType) override;
2654 void ReleaseIOThreadObjects() override;
2656 void AbortOperationsForLocks(
2657 const DirectoryLockIdTable& aDirectoryLockIds) override;
2659 void AbortOperationsForProcess(ContentParentId aContentParentId) override;
2661 void AbortAllOperations() override;
2663 void StartIdleMaintenance() override;
2665 void StopIdleMaintenance() override;
2667 private:
2668 ~QuotaClient() override;
2670 void InitiateShutdown() override;
2671 bool IsShutdownCompleted() const override;
2672 nsCString GetShutdownStatus() const override;
2673 void ForceKillActors() override;
2674 void FinalizeShutdown() override;
2676 Result<UniquePtr<ArchivedOriginScope>, nsresult> CreateArchivedOriginScope(
2677 const OriginScope& aOriginScope);
2679 nsresult PerformDelete(mozIStorageConnection* aConnection,
2680 const nsACString& aSchemaName,
2681 ArchivedOriginScope* aArchivedOriginScope) const;
2684 class QuotaClient::MatchFunction final : public mozIStorageFunction {
2685 OriginAttributesPattern mPattern;
2687 public:
2688 explicit MatchFunction(const OriginAttributesPattern& aPattern)
2689 : mPattern(aPattern) {}
2691 private:
2692 ~MatchFunction() = default;
2694 NS_DECL_ISUPPORTS
2695 NS_DECL_MOZISTORAGEFUNCTION
2698 /*******************************************************************************
2699 * Helper classes
2700 ******************************************************************************/
2702 class MOZ_STACK_CLASS AutoWriteTransaction final {
2703 Connection* mConnection;
2704 Maybe<MutexAutoLock> mShadowDatabaseLock;
2705 bool mShadowWrites;
2707 public:
2708 explicit AutoWriteTransaction(bool aShadowWrites);
2710 ~AutoWriteTransaction();
2712 nsresult Start(Connection* aConnection);
2714 nsresult Commit();
2716 private:
2717 nsresult LockAndAttachShadowDatabase(Connection* aConnection);
2719 nsresult DetachShadowDatabaseAndUnlock();
2722 /*******************************************************************************
2723 * Globals
2724 ******************************************************************************/
2726 #ifdef DEBUG
2727 bool gLocalStorageInitialized = false;
2728 #endif
2730 using PrepareDatastoreOpArray =
2731 nsTArray<NotNull<CheckedUnsafePtr<PrepareDatastoreOp>>>;
2733 StaticAutoPtr<PrepareDatastoreOpArray> gPrepareDatastoreOps;
2735 // nsCStringHashKey with disabled memmove
2736 class nsCStringHashKeyDM : public nsCStringHashKey {
2737 public:
2738 explicit nsCStringHashKeyDM(const nsCStringHashKey::KeyTypePointer aKey)
2739 : nsCStringHashKey(aKey) {}
2740 enum { ALLOW_MEMMOVE = false };
2743 // When CheckedUnsafePtr's checking is enabled, it's necessary to ensure that
2744 // the hashtable uses the copy constructor instead of memmove for moving entries
2745 // since memmove will break CheckedUnsafePtr in a memory-corrupting way.
2746 using DatastoreHashKey =
2747 std::conditional<DiagnosticAssertEnabled::value, nsCStringHashKeyDM,
2748 nsCStringHashKey>::type;
2750 using DatastoreHashtable =
2751 nsBaseHashtable<DatastoreHashKey, NotNull<CheckedUnsafePtr<Datastore>>,
2752 MovingNotNull<CheckedUnsafePtr<Datastore>>>;
2754 StaticAutoPtr<DatastoreHashtable> gDatastores;
2756 uint64_t gLastDatastoreId = 0;
2758 using PreparedDatastoreHashtable =
2759 nsClassHashtable<nsUint64HashKey, PreparedDatastore>;
2761 StaticAutoPtr<PreparedDatastoreHashtable> gPreparedDatastores;
2763 using PrivateDatastoreHashtable =
2764 nsClassHashtable<nsCStringHashKey, PrivateDatastore>;
2766 // Keeps Private Browsing Datastores alive until the private browsing session
2767 // is closed. This is necessary because LocalStorage Private Browsing data is
2768 // (currently) not written to disk and therefore needs to explicitly be kept
2769 // alive in memory so that if a user browses away from a site during a session
2770 // and then back to it that they will still have their data.
2772 // The entries are wrapped by PrivateDatastore instances which call
2773 // NoteLivePrivateDatastore and NoteFinishedPrivateDatastore which set and
2774 // clear mHasLivePrivateDatastore which inhibits MaybeClose() from closing the
2775 // datastore (which would discard the data) when there are no active windows
2776 // using LocalStorage for the origin.
2778 // The table is cleared when the Private Browsing session is closed, which will
2779 // cause NoteFinishedPrivateDatastore to be called on each Datastore which will
2780 // in turn call MaybeClose which should then discard the Datastore. Or in the
2781 // event of an (unlikely) race where the private browsing windows are still
2782 // being torn down, will cause the Datastore to be discarded when the last
2783 // window actually goes away.
2784 UniquePtr<PrivateDatastoreHashtable> gPrivateDatastores;
2786 using LiveDatabaseArray = nsTArray<NotNull<CheckedUnsafePtr<Database>>>;
2788 StaticAutoPtr<LiveDatabaseArray> gLiveDatabases;
2790 StaticRefPtr<ConnectionThread> gConnectionThread;
2792 uint64_t gLastObserverId = 0;
2794 using PreparedObserverHashtable = nsRefPtrHashtable<nsUint64HashKey, Observer>;
2796 StaticAutoPtr<PreparedObserverHashtable> gPreparedObsevers;
2798 using ObserverHashtable =
2799 nsClassHashtable<nsCStringHashKey, nsTArray<NotNull<Observer*>>>;
2801 StaticAutoPtr<ObserverHashtable> gObservers;
2803 Atomic<bool> gShadowWrites(kDefaultShadowWrites);
2804 Atomic<int32_t, Relaxed> gSnapshotPrefill(kDefaultSnapshotPrefill);
2805 Atomic<int32_t, Relaxed> gSnapshotGradualPrefill(
2806 kDefaultSnapshotGradualPrefill);
2807 Atomic<bool> gClientValidation(kDefaultClientValidation);
2809 using UsageHashtable = nsTHashMap<nsCStringHashKey, int64_t>;
2811 StaticAutoPtr<ArchivedOriginHashtable> gArchivedOrigins;
2813 // Can only be touched on the Quota Manager I/O thread.
2814 bool gInitializedShadowStorage = false;
2816 StaticAutoPtr<LSInitializationInfo> gInitializationInfo;
2818 bool IsOnGlobalConnectionThread() {
2819 MOZ_ASSERT(gConnectionThread);
2820 return gConnectionThread->IsOnConnectionThread();
2823 void AssertIsOnGlobalConnectionThread() {
2824 MOZ_ASSERT(gConnectionThread);
2825 gConnectionThread->AssertIsOnConnectionThread();
2828 already_AddRefed<Datastore> GetDatastore(const nsACString& aOrigin) {
2829 AssertIsOnBackgroundThread();
2831 if (gDatastores) {
2832 auto maybeDatastore = gDatastores->MaybeGet(aOrigin);
2833 if (maybeDatastore) {
2834 RefPtr<Datastore> result(std::move(*maybeDatastore).unwrapBasePtr());
2835 return result.forget();
2839 return nullptr;
2842 nsresult LoadArchivedOrigins() {
2843 AssertIsOnIOThread();
2844 MOZ_ASSERT(!gArchivedOrigins);
2846 QuotaManager* quotaManager = QuotaManager::Get();
2847 MOZ_ASSERT(quotaManager);
2849 // Ensure that the webappsstore.sqlite is moved to new place.
2850 QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized()));
2852 QM_TRY_INSPECT(const auto& connection, CreateArchiveStorageConnection(
2853 quotaManager->GetStoragePath()));
2855 if (!connection) {
2856 gArchivedOrigins = new ArchivedOriginHashtable();
2857 return NS_OK;
2860 QM_TRY_INSPECT(
2861 const auto& stmt,
2862 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2863 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
2864 "SELECT DISTINCT originAttributes, originKey "
2865 "FROM webappsstore2;"_ns));
2867 auto archivedOrigins = MakeUnique<ArchivedOriginHashtable>();
2869 // XXX Actually, this could use a hashtable variant of
2870 // CollectElementsWhileHasResult
2871 QM_TRY(quota::CollectWhileHasResult(
2872 *stmt, [&archivedOrigins](auto& stmt) -> Result<Ok, nsresult> {
2873 QM_TRY_INSPECT(const auto& originSuffix,
2874 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2875 GetUTF8String, 0));
2876 QM_TRY_INSPECT(const auto& originNoSuffix,
2877 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2878 GetUTF8String, 1));
2880 const nsCString hashKey =
2881 GetArchivedOriginHashKey(originSuffix, originNoSuffix);
2883 OriginAttributes originAttributes;
2884 QM_TRY(OkIf(originAttributes.PopulateFromSuffix(originSuffix)),
2885 Err(NS_ERROR_FAILURE));
2887 archivedOrigins->InsertOrUpdate(
2888 hashKey,
2889 MakeUnique<ArchivedOriginInfo>(originAttributes, originNoSuffix));
2891 return Ok{};
2892 }));
2894 gArchivedOrigins = archivedOrigins.release();
2895 return NS_OK;
2898 Result<int64_t, nsresult> GetUsage(mozIStorageConnection& aConnection,
2899 ArchivedOriginScope* aArchivedOriginScope) {
2900 AssertIsOnIOThread();
2902 QM_TRY_INSPECT(
2903 const auto& stmt,
2904 ([aArchivedOriginScope,
2905 &aConnection]() -> Result<nsCOMPtr<mozIStorageStatement>, nsresult> {
2906 if (aArchivedOriginScope) {
2907 QM_TRY_RETURN(CreateAndExecuteSingleStepStatement<
2908 SingleStepResult::ReturnNullIfNoResult>(
2909 aConnection,
2910 "SELECT "
2911 "total(utf16Length(key) + utf16Length(value)) "
2912 "FROM webappsstore2 "
2913 "WHERE originKey = :originKey "
2914 "AND originAttributes = :originAttributes;"_ns,
2915 [aArchivedOriginScope](auto& stmt) -> Result<Ok, nsresult> {
2916 QM_TRY(MOZ_TO_RESULT(
2917 aArchivedOriginScope->BindToStatement(&stmt)));
2918 return Ok{};
2919 }));
2922 QM_TRY_RETURN(CreateAndExecuteSingleStepStatement<
2923 SingleStepResult::ReturnNullIfNoResult>(
2924 aConnection, "SELECT usage FROM database"_ns));
2925 }()));
2927 QM_TRY(OkIf(stmt), Err(NS_ERROR_FAILURE));
2929 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
2932 void ShadowWritesPrefChangedCallback(const char* aPrefName, void* aClosure) {
2933 MOZ_ASSERT(NS_IsMainThread());
2934 MOZ_ASSERT(!strcmp(aPrefName, kShadowWritesPref));
2935 MOZ_ASSERT(!aClosure);
2937 gShadowWrites = Preferences::GetBool(aPrefName, kDefaultShadowWrites);
2940 void SnapshotPrefillPrefChangedCallback(const char* aPrefName, void* aClosure) {
2941 MOZ_ASSERT(NS_IsMainThread());
2942 MOZ_ASSERT(!strcmp(aPrefName, kSnapshotPrefillPref));
2943 MOZ_ASSERT(!aClosure);
2945 int32_t snapshotPrefill =
2946 Preferences::GetInt(aPrefName, kDefaultSnapshotPrefill);
2948 // The magic -1 is for use only by tests.
2949 if (snapshotPrefill == -1) {
2950 snapshotPrefill = INT32_MAX;
2953 gSnapshotPrefill = snapshotPrefill;
2956 void SnapshotGradualPrefillPrefChangedCallback(const char* aPrefName,
2957 void* aClosure) {
2958 MOZ_ASSERT(NS_IsMainThread());
2959 MOZ_ASSERT(!strcmp(aPrefName, kSnapshotGradualPrefillPref));
2960 MOZ_ASSERT(!aClosure);
2962 int32_t snapshotGradualPrefill =
2963 Preferences::GetInt(aPrefName, kDefaultSnapshotGradualPrefill);
2965 // The magic -1 is for use only by tests.
2966 if (snapshotGradualPrefill == -1) {
2967 snapshotGradualPrefill = INT32_MAX;
2970 gSnapshotGradualPrefill = snapshotGradualPrefill;
2973 int64_t GetSnapshotPeakUsagePreincrement(bool aInitial) {
2974 return aInitial ? StaticPrefs::
2975 dom_storage_snapshot_peak_usage_initial_preincrement()
2976 : StaticPrefs::
2977 dom_storage_snapshot_peak_usage_gradual_preincrement();
2980 int64_t GetSnapshotPeakUsageReducedPreincrement(bool aInitial) {
2981 return aInitial
2982 ? StaticPrefs::
2983 dom_storage_snapshot_peak_usage_reduced_initial_preincrement()
2984 : StaticPrefs::
2985 dom_storage_snapshot_peak_usage_reduced_gradual_preincrement();
2988 void ClientValidationPrefChangedCallback(const char* aPrefName,
2989 void* aClosure) {
2990 MOZ_ASSERT(NS_IsMainThread());
2991 MOZ_ASSERT(!strcmp(aPrefName, kClientValidationPref));
2992 MOZ_ASSERT(!aClosure);
2994 gClientValidation = Preferences::GetBool(aPrefName, kDefaultClientValidation);
2997 template <typename Condition>
2998 void InvalidatePrepareDatastoreOpsMatching(const Condition& aCondition) {
2999 if (!gPrepareDatastoreOps) {
3000 return;
3003 for (const auto& prepareDatastoreOp : *gPrepareDatastoreOps) {
3004 if (aCondition(*prepareDatastoreOp)) {
3005 prepareDatastoreOp->Invalidate();
3010 template <typename Condition>
3011 void InvalidatePreparedDatastoresMatching(const Condition& aCondition) {
3012 if (!gPreparedDatastores) {
3013 return;
3016 for (const auto& preparedDatastore : gPreparedDatastores->Values()) {
3017 MOZ_ASSERT(preparedDatastore);
3019 if (aCondition(*preparedDatastore)) {
3020 preparedDatastore->Invalidate();
3025 template <typename Condition>
3026 nsTArray<RefPtr<Database>> CollectDatabasesMatching(Condition aCondition) {
3027 AssertIsOnBackgroundThread();
3029 if (!gLiveDatabases) {
3030 return nsTArray<RefPtr<Database>>{};
3033 nsTArray<RefPtr<Database>> databases;
3035 for (const auto& database : *gLiveDatabases) {
3036 if (aCondition(*database)) {
3037 databases.AppendElement(database.get());
3041 return databases;
3044 template <typename Condition>
3045 void RequestAllowToCloseDatabasesMatching(Condition aCondition) {
3046 AssertIsOnBackgroundThread();
3048 nsTArray<RefPtr<Database>> databases = CollectDatabasesMatching(aCondition);
3050 for (const auto& database : databases) {
3051 MOZ_ASSERT(database);
3053 database->RequestAllowToClose();
3057 void ForceKillAllDatabases() {
3058 AssertIsOnBackgroundThread();
3060 nsTArray<RefPtr<Database>> databases =
3061 CollectDatabasesMatching([](const auto&) { return true; });
3063 for (const auto& database : databases) {
3064 MOZ_ASSERT(database);
3066 database->ForceKill();
3070 bool VerifyPrincipalInfo(const PrincipalInfo& aPrincipalInfo,
3071 const PrincipalInfo& aStoragePrincipalInfo,
3072 bool aCheckClientPrincipal) {
3073 AssertIsOnBackgroundThread();
3075 if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
3076 return false;
3079 // Note that the client prinicpal could have a different spec than the node
3080 // principal but they should have the same origin. It's because the client
3081 // could be initialized when opening the initial about:blank document and pass
3082 // to the newly opened window and reuse over there if the new window has the
3083 // same origin as the initial about:blank document. But, the FilePath could be
3084 // different. Therefore, we have to ignore comparing the Spec of the
3085 // principals if we are verifying clinet principal here. Also, when
3086 // document.domain is set, client principal won't get it. So, we don't compare
3087 // domain for client princpal too.
3088 bool result = aCheckClientPrincipal
3089 ? StoragePrincipalHelper::
3090 VerifyValidClientPrincipalInfoForPrincipalInfo(
3091 aStoragePrincipalInfo, aPrincipalInfo)
3092 : StoragePrincipalHelper::
3093 VerifyValidStoragePrincipalInfoForPrincipalInfo(
3094 aStoragePrincipalInfo, aPrincipalInfo);
3095 if (NS_WARN_IF(!result)) {
3096 return false;
3099 return true;
3102 bool VerifyClientId(const Maybe<ContentParentId>& aContentParentId,
3103 const Maybe<PrincipalInfo>& aPrincipalInfo,
3104 const Maybe<nsID>& aClientId) {
3105 AssertIsOnBackgroundThread();
3107 if (gClientValidation) {
3108 if (NS_WARN_IF(aClientId.isNothing())) {
3109 return false;
3112 if (NS_WARN_IF(aPrincipalInfo.isNothing())) {
3113 return false;
3116 RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance();
3117 if (svc && NS_WARN_IF(!svc->HasWindow(
3118 aContentParentId, aPrincipalInfo.ref(), aClientId.ref()))) {
3119 return false;
3123 return true;
3126 bool VerifyOriginKey(const nsACString& aOriginKey,
3127 const PrincipalInfo& aPrincipalInfo) {
3128 AssertIsOnBackgroundThread();
3130 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]),
3131 GenerateOriginKey2(aPrincipalInfo), false);
3133 Unused << originAttrSuffix;
3135 QM_TRY(OkIf(originKey == aOriginKey), false,
3136 ([&originKey = originKey, &aOriginKey](const auto) {
3137 LS_WARNING("originKey (%s) doesn't match passed one (%s)!",
3138 originKey.get(), nsCString(aOriginKey).get());
3139 }));
3141 return true;
3144 LSInitializationInfo& MutableInitializationInfoRef(const CreateIfNonExistent&) {
3145 if (!gInitializationInfo) {
3146 gInitializationInfo = new LSInitializationInfo();
3148 return *gInitializationInfo;
3151 template <typename Func>
3152 auto ExecuteOriginInitialization(const nsACString& aOrigin,
3153 const LSOriginInitialization aInitialization,
3154 const nsACString& aContext, Func&& aFunc)
3155 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
3156 LSOriginInitialization, Nothing>&> {
3157 return ExecuteInitialization(
3158 MutableInitializationInfoRef(CreateIfNonExistent{})
3159 .MutableOriginInitializationInfoRef(aOrigin, CreateIfNonExistent{}),
3160 aInitialization, aContext, std::forward<Func>(aFunc));
3163 } // namespace
3165 /*******************************************************************************
3166 * Exported functions
3167 ******************************************************************************/
3169 void InitializeLocalStorage() {
3170 MOZ_ASSERT(XRE_IsParentProcess());
3171 MOZ_ASSERT(NS_IsMainThread());
3172 MOZ_ASSERT(!gLocalStorageInitialized);
3174 // XXX Isn't this redundant? It's already done in InitializeQuotaManager.
3175 if (!QuotaManager::IsRunningGTests()) {
3176 // This service has to be started on the main thread currently.
3177 const nsCOMPtr<mozIStorageService> ss =
3178 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
3180 QM_WARNONLY_TRY(OkIf(ss));
3183 Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback,
3184 kShadowWritesPref);
3186 Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback,
3187 kSnapshotPrefillPref);
3189 Preferences::RegisterCallbackAndCall(
3190 SnapshotGradualPrefillPrefChangedCallback, kSnapshotGradualPrefillPref);
3192 Preferences::RegisterCallbackAndCall(ClientValidationPrefChangedCallback,
3193 kClientValidationPref);
3195 #ifdef DEBUG
3196 gLocalStorageInitialized = true;
3197 #endif
3200 PBackgroundLSDatabaseParent* AllocPBackgroundLSDatabaseParent(
3201 const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
3202 const uint64_t& aDatastoreId) {
3203 AssertIsOnBackgroundThread();
3205 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3206 return nullptr;
3209 if (NS_WARN_IF(!gPreparedDatastores)) {
3210 MOZ_ASSERT_UNLESS_FUZZING(false);
3211 return nullptr;
3214 PreparedDatastore* preparedDatastore = gPreparedDatastores->Get(aDatastoreId);
3215 if (NS_WARN_IF(!preparedDatastore)) {
3216 MOZ_ASSERT_UNLESS_FUZZING(false);
3217 return nullptr;
3220 // If we ever decide to return null from this point on, we need to make sure
3221 // that the datastore is closed and the prepared datastore is removed from the
3222 // gPreparedDatastores hashtable.
3223 // We also assume that IPDL must call RecvPBackgroundLSDatabaseConstructor
3224 // once we return a valid actor in this method.
3226 RefPtr<Database> database =
3227 new Database(aPrincipalInfo, preparedDatastore->GetContentParentId(),
3228 preparedDatastore->Origin(), aPrivateBrowsingId);
3230 // Transfer ownership to IPDL.
3231 return database.forget().take();
3234 bool RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor,
3235 const PrincipalInfo& aPrincipalInfo,
3236 const uint32_t& aPrivateBrowsingId,
3237 const uint64_t& aDatastoreId) {
3238 AssertIsOnBackgroundThread();
3239 MOZ_ASSERT(aActor);
3240 MOZ_ASSERT(gPreparedDatastores);
3241 MOZ_ASSERT(gPreparedDatastores->Get(aDatastoreId));
3242 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3244 // The actor is now completely built (it has a manager, channel and it's
3245 // registered as a subprotocol).
3246 // ActorDestroy will be called if we fail here.
3248 mozilla::UniquePtr<PreparedDatastore> preparedDatastore;
3249 gPreparedDatastores->Remove(aDatastoreId, &preparedDatastore);
3250 MOZ_ASSERT(preparedDatastore);
3252 auto* database = static_cast<Database*>(aActor);
3254 database->SetActorAlive(&preparedDatastore->MutableDatastoreRef());
3256 // It's possible that AbortOperationsForLocks was called before the database
3257 // actor was created and became live. Let the child know that the database is
3258 // no longer valid.
3259 if (preparedDatastore->IsInvalidated()) {
3260 database->RequestAllowToClose();
3263 return true;
3266 bool DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor) {
3267 AssertIsOnBackgroundThread();
3268 MOZ_ASSERT(aActor);
3270 // Transfer ownership back from IPDL.
3271 RefPtr<Database> actor = dont_AddRef(static_cast<Database*>(aActor));
3273 return true;
3276 PBackgroundLSObserverParent* AllocPBackgroundLSObserverParent(
3277 const uint64_t& aObserverId) {
3278 AssertIsOnBackgroundThread();
3280 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3281 return nullptr;
3284 if (NS_WARN_IF(!gPreparedObsevers)) {
3285 MOZ_ASSERT_UNLESS_FUZZING(false);
3286 return nullptr;
3289 RefPtr<Observer> observer = gPreparedObsevers->Get(aObserverId);
3290 if (NS_WARN_IF(!observer)) {
3291 MOZ_ASSERT_UNLESS_FUZZING(false);
3292 return nullptr;
3295 // observer->SetObject(this);
3297 // Transfer ownership to IPDL.
3298 return observer.forget().take();
3301 bool RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent* aActor,
3302 const uint64_t& aObserverId) {
3303 AssertIsOnBackgroundThread();
3304 MOZ_ASSERT(aActor);
3305 MOZ_ASSERT(gPreparedObsevers);
3306 MOZ_ASSERT(gPreparedObsevers->GetWeak(aObserverId));
3308 RefPtr<Observer> observer;
3309 gPreparedObsevers->Remove(aObserverId, observer.StartAssignment());
3311 if (!gPreparedObsevers->Count()) {
3312 gPreparedObsevers = nullptr;
3315 if (!gObservers) {
3316 gObservers = new ObserverHashtable();
3319 const auto notNullObserver = WrapNotNull(observer.get());
3321 nsTArray<NotNull<Observer*>>* const array =
3322 gObservers->GetOrInsertNew(notNullObserver->Origin());
3323 array->AppendElement(notNullObserver);
3325 if (RefPtr<Datastore> datastore = GetDatastore(observer->Origin())) {
3326 datastore->NoteChangedObserverArray(*array);
3329 return true;
3332 bool DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor) {
3333 AssertIsOnBackgroundThread();
3334 MOZ_ASSERT(aActor);
3336 // Transfer ownership back from IPDL.
3337 RefPtr<Observer> actor = dont_AddRef(static_cast<Observer*>(aActor));
3339 return true;
3342 PBackgroundLSRequestParent* AllocPBackgroundLSRequestParent(
3343 PBackgroundParent* aBackgroundActor, const LSRequestParams& aParams) {
3344 AssertIsOnBackgroundThread();
3345 MOZ_ASSERT(aParams.type() != LSRequestParams::T__None);
3347 if (NS_WARN_IF(!NextGenLocalStorageEnabled())) {
3348 return nullptr;
3351 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3352 return nullptr;
3355 Maybe<ContentParentId> contentParentId;
3357 uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
3358 if (childID) {
3359 contentParentId = Some(ContentParentId(childID));
3362 RefPtr<LSRequestBase> actor;
3364 switch (aParams.type()) {
3365 case LSRequestParams::TLSRequestPreloadDatastoreParams:
3366 case LSRequestParams::TLSRequestPrepareDatastoreParams: {
3367 RefPtr<PrepareDatastoreOp> prepareDatastoreOp =
3368 new PrepareDatastoreOp(aParams, contentParentId);
3370 if (!gPrepareDatastoreOps) {
3371 gPrepareDatastoreOps = new PrepareDatastoreOpArray();
3373 gPrepareDatastoreOps->AppendElement(
3374 WrapNotNullUnchecked(prepareDatastoreOp.get()));
3376 actor = std::move(prepareDatastoreOp);
3378 break;
3381 case LSRequestParams::TLSRequestPrepareObserverParams: {
3382 RefPtr<PrepareObserverOp> prepareObserverOp =
3383 new PrepareObserverOp(aParams, contentParentId);
3385 actor = std::move(prepareObserverOp);
3387 break;
3390 default:
3391 MOZ_CRASH("Should never get here!");
3394 // Transfer ownership to IPDL.
3395 return actor.forget().take();
3398 bool RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor,
3399 const LSRequestParams& aParams) {
3400 AssertIsOnBackgroundThread();
3401 MOZ_ASSERT(aActor);
3402 MOZ_ASSERT(aParams.type() != LSRequestParams::T__None);
3403 MOZ_ASSERT(NextGenLocalStorageEnabled());
3404 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3406 // The actor is now completely built.
3408 auto* op = static_cast<LSRequestBase*>(aActor);
3410 op->Dispatch();
3412 return true;
3415 bool DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent* aActor) {
3416 AssertIsOnBackgroundThread();
3418 // Transfer ownership back from IPDL.
3419 RefPtr<LSRequestBase> actor =
3420 dont_AddRef(static_cast<LSRequestBase*>(aActor));
3422 return true;
3425 PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent(
3426 PBackgroundParent* aBackgroundActor, const LSSimpleRequestParams& aParams) {
3427 AssertIsOnBackgroundThread();
3428 MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None);
3430 if (NS_WARN_IF(!NextGenLocalStorageEnabled())) {
3431 return nullptr;
3434 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3435 return nullptr;
3438 Maybe<ContentParentId> contentParentId;
3440 uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
3441 if (childID) {
3442 contentParentId = Some(ContentParentId(childID));
3445 RefPtr<LSSimpleRequestBase> actor;
3447 switch (aParams.type()) {
3448 case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: {
3449 RefPtr<PreloadedOp> preloadedOp =
3450 new PreloadedOp(aParams, contentParentId);
3452 actor = std::move(preloadedOp);
3454 break;
3457 case LSSimpleRequestParams::TLSSimpleRequestGetStateParams: {
3458 RefPtr<GetStateOp> getStateOp = new GetStateOp(aParams, contentParentId);
3460 actor = std::move(getStateOp);
3462 break;
3465 default:
3466 MOZ_CRASH("Should never get here!");
3469 // Transfer ownership to IPDL.
3470 return actor.forget().take();
3473 bool RecvPBackgroundLSSimpleRequestConstructor(
3474 PBackgroundLSSimpleRequestParent* aActor,
3475 const LSSimpleRequestParams& aParams) {
3476 AssertIsOnBackgroundThread();
3477 MOZ_ASSERT(aActor);
3478 MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None);
3479 MOZ_ASSERT(NextGenLocalStorageEnabled());
3480 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3482 // The actor is now completely built.
3484 auto* op = static_cast<LSSimpleRequestBase*>(aActor);
3486 op->Dispatch();
3488 return true;
3491 bool DeallocPBackgroundLSSimpleRequestParent(
3492 PBackgroundLSSimpleRequestParent* aActor) {
3493 AssertIsOnBackgroundThread();
3495 // Transfer ownership back from IPDL.
3496 RefPtr<LSSimpleRequestBase> actor =
3497 dont_AddRef(static_cast<LSSimpleRequestBase*>(aActor));
3499 return true;
3502 namespace localstorage {
3504 already_AddRefed<mozilla::dom::quota::Client> CreateQuotaClient() {
3505 AssertIsOnBackgroundThread();
3506 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
3508 RefPtr<QuotaClient> client = new QuotaClient();
3509 return client.forget();
3512 } // namespace localstorage
3514 /*******************************************************************************
3515 * DatastoreWriteOptimizer
3516 ******************************************************************************/
3518 void DatastoreWriteOptimizer::ApplyAndReset(
3519 nsTArray<LSItemInfo>& aOrderedItems) {
3520 AssertIsOnOwningThread();
3522 // The mWriteInfos hash table contains all write infos, but it keeps them in
3523 // an arbitrary order, which means write infos need to be sorted before being
3524 // processed. However, the order is not important for deletions and normal
3525 // updates. Usually, filtering out deletions and updates would require extra
3526 // work, but we have to check the hash table for each ordered item anyway, so
3527 // we can remove the write info if it is a deletion or update without adding
3528 // extra overhead. In the end, only insertions need to be sorted before being
3529 // processed.
3531 if (mTruncateInfo) {
3532 aOrderedItems.Clear();
3533 mTruncateInfo = nullptr;
3536 for (int32_t index = aOrderedItems.Length() - 1; index >= 0; index--) {
3537 LSItemInfo& item = aOrderedItems[index];
3539 if (auto entry = mWriteInfos.Lookup(item.key())) {
3540 WriteInfo* writeInfo = entry->get();
3542 switch (writeInfo->GetType()) {
3543 case WriteInfo::DeleteItem:
3544 aOrderedItems.RemoveElementAt(index);
3545 entry.Remove();
3546 break;
3548 case WriteInfo::UpdateItem: {
3549 auto updateItemInfo = static_cast<UpdateItemInfo*>(writeInfo);
3550 if (updateItemInfo->UpdateWithMove()) {
3551 // See the comment in LSWriteOptimizer::InsertItem for more details
3552 // about the UpdateWithMove flag.
3554 aOrderedItems.RemoveElementAt(index);
3555 entry.Data() = MakeUnique<InsertItemInfo>(
3556 updateItemInfo->SerialNumber(), updateItemInfo->GetKey(),
3557 updateItemInfo->GetValue());
3558 } else {
3559 item.value() = updateItemInfo->GetValue();
3560 entry.Remove();
3562 break;
3565 case WriteInfo::InsertItem:
3566 break;
3568 default:
3569 MOZ_CRASH("Bad type!");
3574 nsTArray<NotNull<WriteInfo*>> writeInfos;
3575 GetSortedWriteInfos(writeInfos);
3577 for (WriteInfo* writeInfo : writeInfos) {
3578 MOZ_ASSERT(writeInfo->GetType() == WriteInfo::InsertItem);
3580 auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo);
3582 LSItemInfo* itemInfo = aOrderedItems.AppendElement();
3583 itemInfo->key() = insertItemInfo->GetKey();
3584 itemInfo->value() = insertItemInfo->GetValue();
3587 mWriteInfos.Clear();
3590 /*******************************************************************************
3591 * ConnectionWriteOptimizer
3592 ******************************************************************************/
3594 Result<int64_t, nsresult> ConnectionWriteOptimizer::Perform(
3595 Connection* aConnection, bool aShadowWrites) {
3596 AssertIsOnGlobalConnectionThread();
3597 MOZ_ASSERT(aConnection);
3599 // The order of elements is not stored in the database, so write infos don't
3600 // need to be sorted before being processed.
3602 if (mTruncateInfo) {
3603 QM_TRY(MOZ_TO_RESULT(PerformTruncate(aConnection, aShadowWrites)));
3606 for (const auto& entry : mWriteInfos) {
3607 const WriteInfo* const writeInfo = entry.GetWeak();
3609 switch (writeInfo->GetType()) {
3610 case WriteInfo::InsertItem:
3611 case WriteInfo::UpdateItem: {
3612 const auto* const insertItemInfo =
3613 static_cast<const InsertItemInfo*>(writeInfo);
3615 QM_TRY(MOZ_TO_RESULT(PerformInsertOrUpdate(
3616 aConnection, aShadowWrites, insertItemInfo->GetKey(),
3617 insertItemInfo->GetValue())));
3619 break;
3622 case WriteInfo::DeleteItem: {
3623 const auto* const deleteItemInfo =
3624 static_cast<const DeleteItemInfo*>(writeInfo);
3626 QM_TRY(MOZ_TO_RESULT(PerformDelete(aConnection, aShadowWrites,
3627 deleteItemInfo->GetKey())));
3629 break;
3632 default:
3633 MOZ_CRASH("Bad type!");
3637 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3638 "UPDATE database "
3639 "SET usage = usage + :delta"_ns,
3640 [this](auto& stmt) -> Result<Ok, nsresult> {
3641 QM_TRY(MOZ_TO_RESULT(stmt.BindInt64ByName("delta"_ns, mTotalDelta)));
3643 return Ok{};
3644 })));
3646 QM_TRY_INSPECT(const auto& stmt, CreateAndExecuteSingleStepStatement<
3647 SingleStepResult::ReturnNullIfNoResult>(
3648 aConnection->MutableStorageConnection(),
3649 "SELECT usage FROM database"_ns));
3651 QM_TRY(OkIf(stmt), Err(NS_ERROR_FAILURE));
3653 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*stmt, GetInt64, 0));
3656 nsresult ConnectionWriteOptimizer::PerformInsertOrUpdate(
3657 Connection* aConnection, bool aShadowWrites, const nsAString& aKey,
3658 const LSValue& aValue) {
3659 AssertIsOnGlobalConnectionThread();
3660 MOZ_ASSERT(aConnection);
3662 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3663 "INSERT OR REPLACE INTO data (key, utf16_length, conversion_type, "
3664 "compression_type, value) "
3665 "VALUES(:key, :utf16_length, :conversion_type, :compression_type, :value)"_ns,
3666 [&aKey, &aValue](auto& stmt) -> Result<Ok, nsresult> {
3667 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey)));
3668 QM_TRY(MOZ_TO_RESULT(
3669 stmt.BindInt32ByName("utf16_length"_ns, aValue.UTF16Length())));
3670 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByName(
3671 "conversion_type"_ns,
3672 static_cast<int32_t>(aValue.GetConversionType()))));
3673 QM_TRY(MOZ_TO_RESULT(stmt.BindInt32ByName(
3674 "compression_type"_ns,
3675 static_cast<int32_t>(aValue.GetCompressionType()))));
3677 if (0u == aValue.Length()) { // Otherwise empty string becomes null
3678 QM_TRY(MOZ_TO_RESULT(
3679 stmt.BindUTF8StringByName("value"_ns, aValue.AsCString())));
3680 } else {
3681 QM_TRY(MOZ_TO_RESULT(
3682 stmt.BindUTF8StringAsBlobByName("value"_ns, aValue.AsCString())));
3685 return Ok{};
3686 })));
3688 if (!aShadowWrites) {
3689 return NS_OK;
3692 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3693 "INSERT OR REPLACE INTO shadow.webappsstore2 "
3694 "(originAttributes, originKey, scope, key, value) "
3695 "VALUES (:originAttributes, :originKey, :scope, :key, :value) "_ns,
3696 [&aConnection, &aKey, &aValue](auto& stmt) -> Result<Ok, nsresult> {
3697 using ConversionType = LSValue::ConversionType;
3698 using CompressionType = LSValue::CompressionType;
3700 const ArchivedOriginScope* const archivedOriginScope =
3701 aConnection->GetArchivedOriginScope();
3703 QM_TRY(MOZ_TO_RESULT(archivedOriginScope->BindToStatement(&stmt)));
3705 QM_TRY(MOZ_TO_RESULT(stmt.BindUTF8StringByName(
3706 "scope"_ns, Scheme0Scope(archivedOriginScope->OriginSuffix(),
3707 archivedOriginScope->OriginNoSuffix()))));
3709 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey)));
3711 bool isCompressed =
3712 CompressionType::UNCOMPRESSED != aValue.GetCompressionType();
3713 bool isAlreadyConverted =
3714 ConversionType::NONE != aValue.GetConversionType();
3716 nsCString buffer;
3717 const nsCString& valueBlob = aValue.AsCString();
3718 if (isCompressed) {
3719 QM_TRY(OkIf(SnappyUncompress(valueBlob, buffer)),
3720 Err(NS_ERROR_FAILURE));
3722 const nsCString& value = isCompressed ? buffer : valueBlob;
3724 // For shadow writes, we undo buffer swap and convert destructively
3725 nsCString unconverted;
3726 if (!isAlreadyConverted) {
3727 nsString converted;
3728 QM_TRY(OkIf(PutCStringBytesToString(value, converted)),
3729 Err(NS_ERROR_OUT_OF_MEMORY));
3730 QM_TRY(OkIf(CopyUTF16toUTF8(converted, unconverted, fallible)),
3731 Err(NS_ERROR_OUT_OF_MEMORY)); // Corrupt invalid data
3733 const nsCString& untransformed =
3734 (!isAlreadyConverted) ? unconverted : value;
3736 QM_TRY(MOZ_TO_RESULT(
3737 stmt.BindUTF8StringByName("value"_ns, untransformed)));
3739 return Ok{};
3740 })));
3742 return NS_OK;
3745 nsresult ConnectionWriteOptimizer::PerformDelete(Connection* aConnection,
3746 bool aShadowWrites,
3747 const nsAString& aKey) {
3748 AssertIsOnGlobalConnectionThread();
3749 MOZ_ASSERT(aConnection);
3751 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3752 "DELETE FROM data "
3753 "WHERE key = :key;"_ns,
3754 [&aKey](auto& stmt) -> Result<Ok, nsresult> {
3755 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey)));
3757 return Ok{};
3758 })));
3760 if (!aShadowWrites) {
3761 return NS_OK;
3764 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3765 "DELETE FROM shadow.webappsstore2 "
3766 "WHERE originAttributes = :originAttributes "
3767 "AND originKey = :originKey "
3768 "AND key = :key;"_ns,
3769 [&aConnection, &aKey](auto& stmt) -> Result<Ok, nsresult> {
3770 QM_TRY(MOZ_TO_RESULT(
3771 aConnection->GetArchivedOriginScope()->BindToStatement(&stmt)));
3773 QM_TRY(MOZ_TO_RESULT(stmt.BindStringByName("key"_ns, aKey)));
3775 return Ok{};
3776 })));
3778 return NS_OK;
3781 nsresult ConnectionWriteOptimizer::PerformTruncate(Connection* aConnection,
3782 bool aShadowWrites) {
3783 AssertIsOnGlobalConnectionThread();
3784 MOZ_ASSERT(aConnection);
3786 QM_TRY(MOZ_TO_RESULT(
3787 aConnection->ExecuteCachedStatement("DELETE FROM data;"_ns)));
3789 if (!aShadowWrites) {
3790 return NS_OK;
3793 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteCachedStatement(
3794 "DELETE FROM shadow.webappsstore2 "
3795 "WHERE originAttributes = :originAttributes "
3796 "AND originKey = :originKey;"_ns,
3797 [&aConnection](auto& stmt) -> Result<Ok, nsresult> {
3798 QM_TRY(MOZ_TO_RESULT(
3799 aConnection->GetArchivedOriginScope()->BindToStatement(&stmt)));
3801 return Ok{};
3802 })));
3804 return NS_OK;
3807 /*******************************************************************************
3808 * DatastoreOperationBase
3809 ******************************************************************************/
3811 /*******************************************************************************
3812 * ConnectionDatastoreOperationBase
3813 ******************************************************************************/
3815 ConnectionDatastoreOperationBase::ConnectionDatastoreOperationBase(
3816 Connection* aConnection, bool aEnsureStorageConnection)
3817 : mConnection(aConnection),
3818 mEnsureStorageConnection(aEnsureStorageConnection) {
3819 MOZ_ASSERT(aConnection);
3822 ConnectionDatastoreOperationBase::~ConnectionDatastoreOperationBase() {
3823 MOZ_ASSERT(!mConnection,
3824 "ConnectionDatabaseOperationBase::Cleanup() was not called by a "
3825 "subclass!");
3828 void ConnectionDatastoreOperationBase::Cleanup() {
3829 AssertIsOnOwningThread();
3830 MOZ_ASSERT(mConnection);
3832 mConnection = nullptr;
3834 NoteComplete();
3837 void ConnectionDatastoreOperationBase::OnSuccess() { AssertIsOnOwningThread(); }
3839 void ConnectionDatastoreOperationBase::OnFailure(nsresult aResultCode) {
3840 AssertIsOnOwningThread();
3841 MOZ_ASSERT(NS_FAILED(aResultCode));
3844 void ConnectionDatastoreOperationBase::RunOnConnectionThread() {
3845 AssertIsOnGlobalConnectionThread();
3846 MOZ_ASSERT(mConnection);
3847 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
3849 if (!MayProceedOnNonOwningThread()) {
3850 SetFailureCode(NS_ERROR_ABORT);
3851 } else {
3852 nsresult rv = NS_OK;
3854 // The boolean flag is only used by the CloseOp to avoid creating empty
3855 // databases.
3856 if (mEnsureStorageConnection) {
3857 rv = mConnection->EnsureStorageConnection();
3858 if (NS_WARN_IF(NS_FAILED(rv))) {
3859 SetFailureCode(rv);
3860 } else {
3861 MOZ_ASSERT(mConnection->HasStorageConnection());
3865 if (NS_SUCCEEDED(rv)) {
3866 rv = DoDatastoreWork();
3867 if (NS_FAILED(rv)) {
3868 SetFailureCode(rv);
3873 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
3876 void ConnectionDatastoreOperationBase::RunOnOwningThread() {
3877 AssertIsOnOwningThread();
3878 MOZ_ASSERT(mConnection);
3880 if (!MayProceed()) {
3881 MaybeSetFailureCode(NS_ERROR_ABORT);
3884 if (NS_SUCCEEDED(ResultCode())) {
3885 OnSuccess();
3886 } else {
3887 OnFailure(ResultCode());
3890 Cleanup();
3893 NS_IMETHODIMP
3894 ConnectionDatastoreOperationBase::Run() {
3895 if (IsOnGlobalConnectionThread()) {
3896 RunOnConnectionThread();
3897 } else {
3898 RunOnOwningThread();
3901 return NS_OK;
3904 /*******************************************************************************
3905 * Connection implementation
3906 ******************************************************************************/
3908 Connection::Connection(ConnectionThread* aConnectionThread,
3909 const OriginMetadata& aOriginMetadata,
3910 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope,
3911 bool aDatabaseWasNotAvailable)
3912 : mConnectionThread(aConnectionThread),
3913 mQuotaClient(QuotaClient::GetInstance()),
3914 mArchivedOriginScope(std::move(aArchivedOriginScope)),
3915 mOriginMetadata(aOriginMetadata),
3916 mDatabaseWasNotAvailable(aDatabaseWasNotAvailable),
3917 mHasCreatedDatabase(false),
3918 mFlushScheduled(false)
3919 #ifdef DEBUG
3921 mInUpdateBatch(false),
3922 mFinished(false)
3923 #endif
3925 AssertIsOnOwningThread();
3926 MOZ_ASSERT(!aOriginMetadata.mGroup.IsEmpty());
3927 MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
3930 Connection::~Connection() {
3931 AssertIsOnOwningThread();
3932 MOZ_ASSERT(!mFlushScheduled);
3933 MOZ_ASSERT(!mInUpdateBatch);
3934 MOZ_ASSERT(mFinished);
3937 void Connection::Dispatch(ConnectionDatastoreOperationBase* aOp) {
3938 AssertIsOnOwningThread();
3939 MOZ_ASSERT(mConnectionThread);
3941 MOZ_ALWAYS_SUCCEEDS(
3942 mConnectionThread->mThread->Dispatch(aOp, NS_DISPATCH_NORMAL));
3945 void Connection::Close(nsIRunnable* aCallback) {
3946 AssertIsOnOwningThread();
3947 MOZ_ASSERT(aCallback);
3949 if (mFlushScheduled) {
3950 MOZ_ASSERT(mFlushTimer);
3951 MOZ_ALWAYS_SUCCEEDS(mFlushTimer->Cancel());
3953 Flush();
3955 mFlushTimer = nullptr;
3958 RefPtr<CloseOp> op = new CloseOp(this, aCallback);
3960 Dispatch(op);
3963 void Connection::SetItem(const nsString& aKey, const LSValue& aValue,
3964 int64_t aDelta, bool aIsNewItem) {
3965 AssertIsOnOwningThread();
3966 MOZ_ASSERT(mInUpdateBatch);
3968 if (aIsNewItem) {
3969 mWriteOptimizer.InsertItem(aKey, aValue, aDelta);
3970 } else {
3971 mWriteOptimizer.UpdateItem(aKey, aValue, aDelta);
3975 void Connection::RemoveItem(const nsString& aKey, int64_t aDelta) {
3976 AssertIsOnOwningThread();
3977 MOZ_ASSERT(mInUpdateBatch);
3979 mWriteOptimizer.DeleteItem(aKey, aDelta);
3982 void Connection::Clear(int64_t aDelta) {
3983 AssertIsOnOwningThread();
3984 MOZ_ASSERT(mInUpdateBatch);
3986 mWriteOptimizer.Truncate(aDelta);
3989 void Connection::BeginUpdateBatch() {
3990 AssertIsOnOwningThread();
3991 MOZ_ASSERT(!mInUpdateBatch);
3993 #ifdef DEBUG
3994 mInUpdateBatch = true;
3995 #endif
3998 void Connection::EndUpdateBatch() {
3999 AssertIsOnOwningThread();
4000 MOZ_ASSERT(mInUpdateBatch);
4002 if (mWriteOptimizer.HasWrites() && !mFlushScheduled) {
4003 ScheduleFlush();
4006 #ifdef DEBUG
4007 mInUpdateBatch = false;
4008 #endif
4011 nsresult Connection::EnsureStorageConnection() {
4012 AssertIsOnGlobalConnectionThread();
4014 if (HasStorageConnection()) {
4015 return NS_OK;
4018 QuotaManager* quotaManager = QuotaManager::Get();
4019 MOZ_ASSERT(quotaManager);
4021 if (!mDatabaseWasNotAvailable || mHasCreatedDatabase) {
4022 MOZ_ASSERT(mOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
4024 QM_TRY_INSPECT(const auto& directoryEntry,
4025 quotaManager->GetOriginDirectory(mOriginMetadata));
4027 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(
4028 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME))));
4030 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDirectoryPath)));
4031 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName)));
4033 QM_TRY_INSPECT(
4034 const auto& databaseFilePath,
4035 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directoryEntry, GetPath));
4037 QM_TRY_UNWRAP(auto storageConnection,
4038 GetStorageConnection(databaseFilePath));
4039 LazyInit(WrapMovingNotNull(std::move(storageConnection)));
4041 return NS_OK;
4044 RefPtr<InitTemporaryOriginHelper> helper =
4045 new InitTemporaryOriginHelper(mOriginMetadata);
4047 QM_TRY_INSPECT(const auto& originDirectoryPath,
4048 helper->BlockAndReturnOriginDirectoryPath());
4050 QM_TRY_INSPECT(const auto& directoryEntry,
4051 QM_NewLocalFile(originDirectoryPath));
4053 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(
4054 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME))));
4056 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDirectoryPath)));
4058 QM_TRY_INSPECT(const bool& exists,
4059 MOZ_TO_RESULT_INVOKE_MEMBER(directoryEntry, Exists));
4061 if (!exists) {
4062 QM_TRY(
4063 MOZ_TO_RESULT(directoryEntry->Create(nsIFile::DIRECTORY_TYPE, 0755)));
4066 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName)));
4068 #ifdef DEBUG
4070 QM_TRY_INSPECT(const bool& exists,
4071 MOZ_TO_RESULT_INVOKE_MEMBER(directoryEntry, Exists));
4073 MOZ_ASSERT(!exists);
4075 #endif
4077 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(mDirectoryPath));
4079 nsCOMPtr<mozIStorageConnection> storageConnection;
4081 auto autoRemove = MakeScopeExit([&storageConnection, &directoryEntry] {
4082 if (storageConnection) {
4083 MOZ_ALWAYS_SUCCEEDS(storageConnection->Close());
4086 nsresult rv = directoryEntry->Remove(false);
4087 if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
4088 NS_WARNING("Failed to remove database file!");
4092 QM_TRY_UNWRAP(storageConnection, CreateStorageConnectionWithRecovery(
4093 *directoryEntry, *usageFile, Origin(),
4094 [] { MOZ_ASSERT_UNREACHABLE(); }));
4096 MOZ_ASSERT(mQuotaClient);
4098 MutexAutoLock shadowDatabaseLock(mQuotaClient->ShadowDatabaseMutex());
4100 nsCOMPtr<mozIStorageConnection> shadowConnection;
4101 if (!gInitializedShadowStorage) {
4102 QM_TRY_UNWRAP(shadowConnection,
4103 CreateShadowStorageConnection(quotaManager->GetBasePath()));
4105 gInitializedShadowStorage = true;
4108 autoRemove.release();
4110 if (!mHasCreatedDatabase) {
4111 mHasCreatedDatabase = true;
4114 LazyInit(WrapMovingNotNull(std::move(storageConnection)));
4116 return NS_OK;
4119 void Connection::CloseStorageConnection() {
4120 AssertIsOnGlobalConnectionThread();
4122 CachingDatabaseConnection::Close();
4125 nsresult Connection::BeginWriteTransaction() {
4126 AssertIsOnGlobalConnectionThread();
4127 MOZ_ASSERT(HasStorageConnection());
4129 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN IMMEDIATE;"_ns)));
4131 return NS_OK;
4134 nsresult Connection::CommitWriteTransaction() {
4135 AssertIsOnGlobalConnectionThread();
4136 MOZ_ASSERT(HasStorageConnection());
4138 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("COMMIT;"_ns)));
4140 return NS_OK;
4143 nsresult Connection::RollbackWriteTransaction() {
4144 AssertIsOnGlobalConnectionThread();
4145 MOZ_ASSERT(HasStorageConnection());
4147 QM_TRY_INSPECT(const auto& stmt, BorrowCachedStatement("ROLLBACK;"_ns));
4149 // This may fail if SQLite already rolled back the transaction so ignore any
4150 // errors.
4151 Unused << stmt->Execute();
4153 return NS_OK;
4156 void Connection::ScheduleFlush() {
4157 AssertIsOnOwningThread();
4158 MOZ_ASSERT(mWriteOptimizer.HasWrites());
4159 MOZ_ASSERT(!mFlushScheduled);
4161 if (!mFlushTimer) {
4162 mFlushTimer = NS_NewTimer();
4163 MOZ_ASSERT(mFlushTimer);
4166 MOZ_ALWAYS_SUCCEEDS(mFlushTimer->InitWithNamedFuncCallback(
4167 FlushTimerCallback, this, kFlushTimeoutMs, nsITimer::TYPE_ONE_SHOT,
4168 "Connection::FlushTimerCallback"));
4170 mFlushScheduled = true;
4173 void Connection::Flush() {
4174 AssertIsOnOwningThread();
4175 MOZ_ASSERT(mFlushScheduled);
4177 if (mWriteOptimizer.HasWrites()) {
4178 RefPtr<FlushOp> op = new FlushOp(this, std::move(mWriteOptimizer));
4180 Dispatch(op);
4183 mFlushScheduled = false;
4186 // static
4187 void Connection::FlushTimerCallback(nsITimer* aTimer, void* aClosure) {
4188 MOZ_ASSERT(aClosure);
4190 auto* self = static_cast<Connection*>(aClosure);
4191 MOZ_ASSERT(self);
4192 MOZ_ASSERT(self->mFlushScheduled);
4194 self->Flush();
4197 Result<nsString, nsresult>
4198 Connection::InitTemporaryOriginHelper::BlockAndReturnOriginDirectoryPath() {
4199 AssertIsOnGlobalConnectionThread();
4201 QuotaManager* quotaManager = QuotaManager::Get();
4202 MOZ_ASSERT(quotaManager);
4204 MOZ_ALWAYS_SUCCEEDS(
4205 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
4207 mozilla::MonitorAutoLock lock(mMonitor);
4208 while (mWaiting) {
4209 lock.Wait();
4212 QM_TRY(MOZ_TO_RESULT(mIOThreadResultCode));
4214 return mOriginDirectoryPath;
4217 nsresult Connection::InitTemporaryOriginHelper::RunOnIOThread() {
4218 AssertIsOnIOThread();
4220 QuotaManager* quotaManager = QuotaManager::Get();
4221 MOZ_ASSERT(quotaManager);
4223 QM_TRY_INSPECT(const auto& directoryEntry,
4224 quotaManager
4225 ->EnsureTemporaryOriginIsInitialized(
4226 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata)
4227 .map([](const auto& res) { return res.first; }));
4229 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mOriginDirectoryPath)));
4231 return NS_OK;
4234 NS_IMETHODIMP
4235 Connection::InitTemporaryOriginHelper::Run() {
4236 AssertIsOnIOThread();
4238 nsresult rv = RunOnIOThread();
4239 if (NS_WARN_IF(NS_FAILED(rv))) {
4240 mIOThreadResultCode = rv;
4243 mozilla::MonitorAutoLock lock(mMonitor);
4244 MOZ_ASSERT(mWaiting);
4246 mWaiting = false;
4247 lock.Notify();
4249 return NS_OK;
4252 Connection::FlushOp::FlushOp(Connection* aConnection,
4253 ConnectionWriteOptimizer&& aWriteOptimizer)
4254 : ConnectionDatastoreOperationBase(aConnection),
4255 mWriteOptimizer(std::move(aWriteOptimizer)),
4256 mShadowWrites(gShadowWrites) {}
4258 nsresult Connection::FlushOp::DoDatastoreWork() {
4259 AssertIsOnGlobalConnectionThread();
4260 MOZ_ASSERT(mConnection);
4262 AutoWriteTransaction autoWriteTransaction(mShadowWrites);
4264 QM_TRY(MOZ_TO_RESULT(autoWriteTransaction.Start(mConnection)));
4266 QM_TRY_INSPECT(const int64_t& usage,
4267 mWriteOptimizer.Perform(mConnection, mShadowWrites));
4269 QM_TRY_INSPECT(const auto& usageFile,
4270 GetUsageFile(mConnection->DirectoryPath()));
4272 QM_TRY_INSPECT(const auto& usageJournalFile,
4273 GetUsageJournalFile(mConnection->DirectoryPath()));
4275 QM_TRY(MOZ_TO_RESULT(UpdateUsageFile(usageFile, usageJournalFile, usage)));
4277 QM_TRY(MOZ_TO_RESULT(autoWriteTransaction.Commit()));
4279 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false)));
4281 return NS_OK;
4284 void Connection::FlushOp::Cleanup() {
4285 AssertIsOnOwningThread();
4287 mWriteOptimizer.Reset();
4289 MOZ_ASSERT(!mWriteOptimizer.HasWrites());
4291 ConnectionDatastoreOperationBase::Cleanup();
4294 nsresult Connection::CloseOp::DoDatastoreWork() {
4295 AssertIsOnGlobalConnectionThread();
4296 MOZ_ASSERT(mConnection);
4298 if (mConnection->HasStorageConnection()) {
4299 mConnection->CloseStorageConnection();
4302 return NS_OK;
4305 void Connection::CloseOp::Cleanup() {
4306 AssertIsOnOwningThread();
4307 MOZ_ASSERT(mConnection);
4309 mConnection->mConnectionThread->mConnections.Remove(mConnection->Origin());
4311 #ifdef DEBUG
4312 MOZ_ASSERT(!mConnection->mFinished);
4313 mConnection->mFinished = true;
4314 #endif
4316 nsCOMPtr<nsIRunnable> callback;
4317 mCallback.swap(callback);
4319 callback->Run();
4321 ConnectionDatastoreOperationBase::Cleanup();
4324 /*******************************************************************************
4325 * ConnectionThread implementation
4326 ******************************************************************************/
4328 ConnectionThread::ConnectionThread() {
4329 AssertIsOnOwningThread();
4330 AssertIsOnBackgroundThread();
4332 MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread("LS Thread", getter_AddRefs(mThread)));
4335 ConnectionThread::~ConnectionThread() {
4336 AssertIsOnOwningThread();
4337 MOZ_ASSERT(!mConnections.Count());
4340 bool ConnectionThread::IsOnConnectionThread() {
4341 MOZ_ASSERT(mThread);
4343 bool current;
4344 return NS_SUCCEEDED(mThread->IsOnCurrentThread(&current)) && current;
4347 void ConnectionThread::AssertIsOnConnectionThread() {
4348 MOZ_ASSERT(IsOnConnectionThread());
4351 already_AddRefed<Connection> ConnectionThread::CreateConnection(
4352 const OriginMetadata& aOriginMetadata,
4353 UniquePtr<ArchivedOriginScope>&& aArchivedOriginScope,
4354 bool aDatabaseWasNotAvailable) {
4355 AssertIsOnOwningThread();
4356 MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
4357 MOZ_ASSERT(!mConnections.Contains(aOriginMetadata.mOrigin));
4359 RefPtr<Connection> connection =
4360 new Connection(this, aOriginMetadata, std::move(aArchivedOriginScope),
4361 aDatabaseWasNotAvailable);
4362 mConnections.InsertOrUpdate(aOriginMetadata.mOrigin, RefPtr{connection});
4364 return connection.forget();
4367 void ConnectionThread::Shutdown() {
4368 AssertIsOnOwningThread();
4369 MOZ_ASSERT(mThread);
4371 mThread->Shutdown();
4374 /*******************************************************************************
4375 * Datastore
4376 ******************************************************************************/
4378 Datastore::Datastore(const OriginMetadata& aOriginMetadata,
4379 uint32_t aPrivateBrowsingId, int64_t aUsage,
4380 int64_t aSizeOfKeys, int64_t aSizeOfItems,
4381 RefPtr<DirectoryLock>&& aDirectoryLock,
4382 RefPtr<Connection>&& aConnection,
4383 RefPtr<QuotaObject>&& aQuotaObject,
4384 nsTHashMap<nsStringHashKey, LSValue>& aValues,
4385 nsTArray<LSItemInfo>&& aOrderedItems)
4386 : mDirectoryLock(std::move(aDirectoryLock)),
4387 mConnection(std::move(aConnection)),
4388 mQuotaObject(std::move(aQuotaObject)),
4389 mOrderedItems(std::move(aOrderedItems)),
4390 mOriginMetadata(aOriginMetadata),
4391 mPrivateBrowsingId(aPrivateBrowsingId),
4392 mUsage(aUsage),
4393 mUpdateBatchUsage(-1),
4394 mSizeOfKeys(aSizeOfKeys),
4395 mSizeOfItems(aSizeOfItems),
4396 mClosed(false),
4397 mInUpdateBatch(false),
4398 mHasLivePrivateDatastore(false) {
4399 AssertIsOnBackgroundThread();
4401 mValues.SwapElements(aValues);
4404 Datastore::~Datastore() {
4405 AssertIsOnBackgroundThread();
4406 MOZ_ASSERT(mClosed);
4409 void Datastore::Close() {
4410 AssertIsOnBackgroundThread();
4411 MOZ_ASSERT(!mClosed);
4412 MOZ_ASSERT(!mPrepareDatastoreOps.Count());
4413 MOZ_ASSERT(!mPreparedDatastores.Count());
4414 MOZ_ASSERT(!mDatabases.Count());
4415 MOZ_ASSERT(mDirectoryLock);
4417 mClosed = true;
4419 if (IsPersistent()) {
4420 MOZ_ASSERT(mConnection);
4421 MOZ_ASSERT(mQuotaObject);
4423 // We can't release the directory lock and unregister itself from the
4424 // hashtable until the connection is fully closed.
4425 nsCOMPtr<nsIRunnable> callback =
4426 NewRunnableMethod("dom::Datastore::ConnectionClosedCallback", this,
4427 &Datastore::ConnectionClosedCallback);
4428 mConnection->Close(callback);
4429 } else {
4430 MOZ_ASSERT(!mConnection);
4431 MOZ_ASSERT(!mQuotaObject);
4433 // There's no connection, so it's safe to release the directory lock and
4434 // unregister itself from the hashtable.
4436 mDirectoryLock = nullptr;
4438 CleanupMetadata();
4442 void Datastore::WaitForConnectionToComplete(nsIRunnable* aCallback) {
4443 AssertIsOnBackgroundThread();
4444 MOZ_ASSERT(aCallback);
4445 MOZ_ASSERT(!mCompleteCallback);
4446 MOZ_ASSERT(mClosed);
4448 mCompleteCallback = aCallback;
4451 void Datastore::NoteLivePrepareDatastoreOp(
4452 PrepareDatastoreOp* aPrepareDatastoreOp) {
4453 AssertIsOnBackgroundThread();
4454 MOZ_ASSERT(aPrepareDatastoreOp);
4455 MOZ_ASSERT(!mPrepareDatastoreOps.Contains(aPrepareDatastoreOp));
4456 MOZ_ASSERT(mDirectoryLock);
4457 MOZ_ASSERT(!mClosed);
4459 mPrepareDatastoreOps.Insert(aPrepareDatastoreOp);
4462 void Datastore::NoteFinishedPrepareDatastoreOp(
4463 PrepareDatastoreOp* aPrepareDatastoreOp) {
4464 AssertIsOnBackgroundThread();
4465 MOZ_ASSERT(aPrepareDatastoreOp);
4466 MOZ_ASSERT(mPrepareDatastoreOps.Contains(aPrepareDatastoreOp));
4467 MOZ_ASSERT(mDirectoryLock);
4468 MOZ_ASSERT(!mClosed);
4470 mPrepareDatastoreOps.Remove(aPrepareDatastoreOp);
4472 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4473 quota::Client::LS, "PrepareDatastoreOp finished"_ns);
4475 MaybeClose();
4478 void Datastore::NoteLivePrivateDatastore() {
4479 AssertIsOnBackgroundThread();
4480 MOZ_ASSERT(!mHasLivePrivateDatastore);
4481 MOZ_ASSERT(mDirectoryLock);
4482 MOZ_ASSERT(!mClosed);
4484 mHasLivePrivateDatastore = true;
4487 void Datastore::NoteFinishedPrivateDatastore() {
4488 AssertIsOnBackgroundThread();
4489 MOZ_ASSERT(mHasLivePrivateDatastore);
4490 MOZ_ASSERT(mDirectoryLock);
4491 MOZ_ASSERT(!mClosed);
4493 mHasLivePrivateDatastore = false;
4495 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4496 quota::Client::LS, "PrivateDatastore finished"_ns);
4498 MaybeClose();
4501 void Datastore::NoteLivePreparedDatastore(
4502 PreparedDatastore* aPreparedDatastore) {
4503 AssertIsOnBackgroundThread();
4504 MOZ_ASSERT(aPreparedDatastore);
4505 MOZ_ASSERT(!mPreparedDatastores.Contains(aPreparedDatastore));
4506 MOZ_ASSERT(mDirectoryLock);
4507 MOZ_ASSERT(!mClosed);
4509 mPreparedDatastores.Insert(aPreparedDatastore);
4512 void Datastore::NoteFinishedPreparedDatastore(
4513 PreparedDatastore* aPreparedDatastore) {
4514 AssertIsOnBackgroundThread();
4515 MOZ_ASSERT(aPreparedDatastore);
4516 MOZ_ASSERT(mPreparedDatastores.Contains(aPreparedDatastore));
4517 MOZ_ASSERT(mDirectoryLock);
4518 MOZ_ASSERT(!mClosed);
4520 mPreparedDatastores.Remove(aPreparedDatastore);
4522 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4523 quota::Client::LS, "PreparedDatastore finished"_ns);
4525 MaybeClose();
4528 bool Datastore::HasOtherProcessDatabases(Database* aDatabase) {
4529 AssertIsOnBackgroundThread();
4531 PBackgroundParent* databaseBackgroundActor = aDatabase->Manager();
4533 for (Database* database : mDatabases) {
4534 if (database->Manager() != databaseBackgroundActor) {
4535 return true;
4539 return false;
4542 void Datastore::NoteLiveDatabase(Database* aDatabase) {
4543 AssertIsOnBackgroundThread();
4544 MOZ_ASSERT(aDatabase);
4545 MOZ_ASSERT(!mDatabases.Contains(aDatabase));
4546 MOZ_ASSERT(mDirectoryLock);
4547 MOZ_ASSERT(!mClosed);
4549 mDatabases.Insert(aDatabase);
4551 NoteChangedDatabaseMap();
4554 void Datastore::NoteFinishedDatabase(Database* aDatabase) {
4555 AssertIsOnBackgroundThread();
4556 MOZ_ASSERT(aDatabase);
4557 MOZ_ASSERT(mDatabases.Contains(aDatabase));
4558 MOZ_ASSERT(!mActiveDatabases.Contains(aDatabase));
4559 MOZ_ASSERT(mDirectoryLock);
4560 MOZ_ASSERT(!mClosed);
4562 mDatabases.Remove(aDatabase);
4564 NoteChangedDatabaseMap();
4566 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS,
4567 "Database finished"_ns);
4569 MaybeClose();
4572 void Datastore::NoteActiveDatabase(Database* aDatabase) {
4573 AssertIsOnBackgroundThread();
4574 MOZ_ASSERT(aDatabase);
4575 MOZ_ASSERT(mDatabases.Contains(aDatabase));
4576 MOZ_ASSERT(!mActiveDatabases.Contains(aDatabase));
4577 MOZ_ASSERT(!mClosed);
4579 mActiveDatabases.Insert(aDatabase);
4582 void Datastore::NoteInactiveDatabase(Database* aDatabase) {
4583 AssertIsOnBackgroundThread();
4584 MOZ_ASSERT(aDatabase);
4585 MOZ_ASSERT(mDatabases.Contains(aDatabase));
4586 MOZ_ASSERT(mActiveDatabases.Contains(aDatabase));
4587 MOZ_ASSERT(!mClosed);
4589 mActiveDatabases.Remove(aDatabase);
4591 if (!mActiveDatabases.Count() && mPendingUsageDeltas.Length()) {
4592 int64_t finalDelta = 0;
4594 for (auto delta : mPendingUsageDeltas) {
4595 finalDelta += delta;
4598 MOZ_ASSERT(finalDelta <= 0);
4600 if (finalDelta != 0) {
4601 DebugOnly<bool> ok = UpdateUsage(finalDelta);
4602 MOZ_ASSERT(ok);
4605 mPendingUsageDeltas.Clear();
4609 void Datastore::GetSnapshotLoadInfo(const nsAString& aKey,
4610 bool& aAddKeyToUnknownItems,
4611 nsTHashtable<nsStringHashKey>& aLoadedItems,
4612 nsTArray<LSItemInfo>& aItemInfos,
4613 uint32_t& aNextLoadIndex,
4614 LSSnapshot::LoadState& aLoadState) {
4615 AssertIsOnBackgroundThread();
4616 MOZ_ASSERT(!mClosed);
4617 MOZ_ASSERT(!mInUpdateBatch);
4619 #ifdef DEBUG
4620 int64_t sizeOfKeys = 0;
4621 int64_t sizeOfItems = 0;
4622 for (auto item : mOrderedItems) {
4623 int64_t sizeOfKey = static_cast<int64_t>(item.key().Length());
4624 sizeOfKeys += sizeOfKey;
4625 sizeOfItems += sizeOfKey + static_cast<int64_t>(item.value().Length());
4627 MOZ_ASSERT(mSizeOfKeys == sizeOfKeys);
4628 MOZ_ASSERT(mSizeOfItems == sizeOfItems);
4629 #endif
4631 // Computes load state optimized for current size of keys and items.
4632 // Zero key length and value can be passed to do a quick initial estimation.
4633 // If computed load state is already AllOrderedItems then excluded key length
4634 // and value length can't make it any better.
4635 auto GetLoadState = [&](int64_t aKeyLength, int64_t aValueLength) {
4636 if (mSizeOfKeys - aKeyLength <= gSnapshotPrefill) {
4637 if (mSizeOfItems - aKeyLength - aValueLength <= gSnapshotPrefill) {
4638 return LSSnapshot::LoadState::AllOrderedItems;
4641 return LSSnapshot::LoadState::AllOrderedKeys;
4644 return LSSnapshot::LoadState::Partial;
4647 // Value for given aKey if aKey is not void (can be void too if value doesn't
4648 // exist for given aKey).
4649 LSValue value;
4650 // If aKey and value are not void, checkKey will be set to true. Once we find
4651 // an item for given aKey in one of the loops below, checkKey is set to false
4652 // to prevent additional comparison of strings (string implementation compares
4653 // string lengths first to avoid char by char comparison if possible).
4654 bool checkKey = false;
4656 // Avoid additional hash lookup if all ordered items fit into initial prefill
4657 // already.
4658 LSSnapshot::LoadState loadState = GetLoadState(/* aKeyLength */ 0,
4659 /* aValueLength */ 0);
4660 if (loadState != LSSnapshot::LoadState::AllOrderedItems && !aKey.IsVoid()) {
4661 GetItem(aKey, value);
4662 if (!value.IsVoid()) {
4663 // Ok, we have a non void aKey and value.
4665 // We have to watch for aKey during one of the loops below to exclude it
4666 // from the size computation. The super fast mode (AllOrderedItems)
4667 // doesn't have to do that though.
4668 checkKey = true;
4670 // We have to compute load state again because aKey length and value
4671 // length is excluded from the size in this case.
4672 loadState = GetLoadState(aKey.Length(), value.Length());
4676 switch (loadState) {
4677 case LSSnapshot::LoadState::AllOrderedItems: {
4678 // We're sending all ordered items, we don't need to check keys because
4679 // mOrderedItems must contain a value for aKey if checkKey is true.
4681 aItemInfos.AppendElements(mOrderedItems);
4683 MOZ_ASSERT(aItemInfos.Length() == mValues.Count());
4684 aNextLoadIndex = mValues.Count();
4686 aAddKeyToUnknownItems = false;
4688 break;
4691 case LSSnapshot::LoadState::AllOrderedKeys: {
4692 // We don't have enough snapshot budget to send all items, but we do have
4693 // enough to send all of the keys and to make a best effort to populate as
4694 // many values as possible. We send void string values once we run out of
4695 // budget. A complicating factor is that we want to make sure that we send
4696 // the value for aKey which is a localStorage read that's triggering this
4697 // request. Since that key can happen anywhere in the list of items, we
4698 // need to handle it specially.
4700 // The loop is effectively doing 2 things in parallel:
4702 // 1. Looking for the `aKey` to send. This is tracked by `checkKey`
4703 // which is true if there was an `aKey` specified and until we
4704 // populate its value, and false thereafter.
4705 // 2. Sending values until we run out of `size` budget and switch to
4706 // sending void values. `doneSendingValues` tracks when we've run out
4707 // of size budget, with `setVoidValue` tracking whether a value
4708 // should be sent for each turn of the event loop but can be
4709 // overridden when `aKey` is found.
4711 int64_t size = mSizeOfKeys;
4712 bool setVoidValue = false;
4713 bool doneSendingValues = false;
4714 for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
4715 const LSItemInfo& item = mOrderedItems[index];
4717 const nsString& key = item.key();
4718 const LSValue& value = item.value();
4720 if (checkKey && key == aKey) {
4721 checkKey = false;
4722 setVoidValue = false;
4723 } else if (!setVoidValue) {
4724 if (doneSendingValues) {
4725 setVoidValue = true;
4726 } else {
4727 size += static_cast<int64_t>(value.Length());
4729 if (size > gSnapshotPrefill) {
4730 setVoidValue = true;
4731 doneSendingValues = true;
4733 // We set doneSendingValues to true and that will guard against
4734 // entering this branch during next iterations. So aNextLoadIndex
4735 // is set only once.
4736 aNextLoadIndex = index;
4741 LSItemInfo* itemInfo = aItemInfos.AppendElement();
4742 itemInfo->key() = key;
4743 if (setVoidValue) {
4744 itemInfo->value().SetIsVoid(true);
4745 } else {
4746 aLoadedItems.PutEntry(key);
4747 itemInfo->value() = value;
4751 aAddKeyToUnknownItems = false;
4753 break;
4756 case LSSnapshot::LoadState::Partial: {
4757 int64_t size = 0;
4758 for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
4759 const LSItemInfo& item = mOrderedItems[index];
4761 const nsString& key = item.key();
4762 const LSValue& value = item.value();
4764 if (checkKey && key == aKey) {
4765 checkKey = false;
4766 } else {
4767 size += static_cast<int64_t>(key.Length()) +
4768 static_cast<int64_t>(value.Length());
4770 if (size > gSnapshotPrefill) {
4771 aNextLoadIndex = index;
4772 break;
4776 aLoadedItems.PutEntry(key);
4778 LSItemInfo* itemInfo = aItemInfos.AppendElement();
4779 itemInfo->key() = key;
4780 itemInfo->value() = value;
4783 aAddKeyToUnknownItems = false;
4785 if (!aKey.IsVoid()) {
4786 if (value.IsVoid()) {
4787 aAddKeyToUnknownItems = true;
4788 } else if (checkKey) {
4789 // The item wasn't added in the loop above, add it here.
4791 LSItemInfo* itemInfo = aItemInfos.AppendElement();
4792 itemInfo->key() = aKey;
4793 itemInfo->value() = value;
4797 MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length());
4799 break;
4802 default:
4803 MOZ_CRASH("Bad load state value!");
4806 aLoadState = loadState;
4809 void Datastore::GetItem(const nsAString& aKey, LSValue& aValue) const {
4810 AssertIsOnBackgroundThread();
4811 MOZ_ASSERT(!mClosed);
4813 if (!mValues.Get(aKey, &aValue)) {
4814 aValue.SetIsVoid(true);
4818 void Datastore::GetKeys(nsTArray<nsString>& aKeys) const {
4819 AssertIsOnBackgroundThread();
4820 MOZ_ASSERT(!mClosed);
4822 for (auto item : mOrderedItems) {
4823 aKeys.AppendElement(item.key());
4827 void Datastore::SetItem(Database* aDatabase, const nsString& aKey,
4828 const LSValue& aValue) {
4829 AssertIsOnBackgroundThread();
4830 MOZ_ASSERT(aDatabase);
4831 MOZ_ASSERT(!mClosed);
4832 MOZ_ASSERT(mInUpdateBatch);
4834 LSValue oldValue;
4835 GetItem(aKey, oldValue);
4837 if (oldValue != aValue) {
4838 bool isNewItem = oldValue.IsVoid();
4840 NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem);
4842 mValues.InsertOrUpdate(aKey, aValue);
4844 int64_t delta;
4846 if (isNewItem) {
4847 mWriteOptimizer.InsertItem(aKey, aValue);
4849 int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
4851 delta = sizeOfKey + static_cast<int64_t>(aValue.UTF16Length());
4853 mUpdateBatchUsage += delta;
4855 mSizeOfKeys += sizeOfKey;
4856 mSizeOfItems += sizeOfKey + static_cast<int64_t>(aValue.Length());
4857 } else {
4858 mWriteOptimizer.UpdateItem(aKey, aValue);
4860 delta = static_cast<int64_t>(aValue.UTF16Length()) -
4861 static_cast<int64_t>(oldValue.UTF16Length());
4863 mUpdateBatchUsage += delta;
4865 mSizeOfItems += static_cast<int64_t>(aValue.Length()) -
4866 static_cast<int64_t>(oldValue.Length());
4869 if (IsPersistent()) {
4870 mConnection->SetItem(aKey, aValue, delta, isNewItem);
4875 void Datastore::RemoveItem(Database* aDatabase, const nsString& aKey) {
4876 AssertIsOnBackgroundThread();
4877 MOZ_ASSERT(aDatabase);
4878 MOZ_ASSERT(!mClosed);
4879 MOZ_ASSERT(mInUpdateBatch);
4881 LSValue oldValue;
4882 GetItem(aKey, oldValue);
4884 if (!oldValue.IsVoid()) {
4885 NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true);
4887 mValues.Remove(aKey);
4889 mWriteOptimizer.DeleteItem(aKey);
4891 int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
4893 int64_t delta = -sizeOfKey - static_cast<int64_t>(oldValue.UTF16Length());
4895 mUpdateBatchUsage += delta;
4897 mSizeOfKeys -= sizeOfKey;
4898 mSizeOfItems -= sizeOfKey + static_cast<int64_t>(oldValue.Length());
4900 if (IsPersistent()) {
4901 mConnection->RemoveItem(aKey, delta);
4906 void Datastore::Clear(Database* aDatabase) {
4907 AssertIsOnBackgroundThread();
4908 MOZ_ASSERT(!mClosed);
4910 if (mValues.Count()) {
4911 int64_t delta = 0;
4912 for (const auto& entry : mValues) {
4913 const nsAString& key = entry.GetKey();
4914 const LSValue& value = entry.GetData();
4916 delta += -static_cast<int64_t>(key.Length()) -
4917 static_cast<int64_t>(value.UTF16Length());
4919 NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
4922 mValues.Clear();
4924 if (mInUpdateBatch) {
4925 mWriteOptimizer.Truncate();
4927 mUpdateBatchUsage += delta;
4928 } else {
4929 mOrderedItems.Clear();
4931 DebugOnly<bool> ok = UpdateUsage(delta);
4932 MOZ_ASSERT(ok);
4935 mSizeOfKeys = 0;
4936 mSizeOfItems = 0;
4938 if (IsPersistent()) {
4939 mConnection->Clear(delta);
4944 void Datastore::BeginUpdateBatch(int64_t aSnapshotUsage) {
4945 AssertIsOnBackgroundThread();
4946 // Don't assert `aSnapshotUsage >= 0`, it can be negative when multiple
4947 // snapshots are operating in parallel.
4948 MOZ_ASSERT(!mClosed);
4949 MOZ_ASSERT(mUpdateBatchUsage == -1);
4950 MOZ_ASSERT(!mInUpdateBatch);
4952 mUpdateBatchUsage = aSnapshotUsage;
4954 if (IsPersistent()) {
4955 mConnection->BeginUpdateBatch();
4958 mInUpdateBatch = true;
4961 int64_t Datastore::EndUpdateBatch(int64_t aSnapshotPeakUsage) {
4962 AssertIsOnBackgroundThread();
4963 MOZ_ASSERT(!mClosed);
4964 MOZ_ASSERT(mInUpdateBatch);
4966 mWriteOptimizer.ApplyAndReset(mOrderedItems);
4968 MOZ_ASSERT(!mWriteOptimizer.HasWrites());
4970 if (aSnapshotPeakUsage >= 0) {
4971 int64_t delta = mUpdateBatchUsage - aSnapshotPeakUsage;
4973 if (mActiveDatabases.Count()) {
4974 // We can't apply deltas while other databases are still active.
4975 // The final delta must be zero or negative, but individual deltas can
4976 // be positive. A positive delta can't be applied asynchronously since
4977 // there's no way to fire the quota exceeded error event.
4979 mPendingUsageDeltas.AppendElement(delta);
4980 } else {
4981 MOZ_ASSERT(delta <= 0);
4982 if (delta != 0) {
4983 DebugOnly<bool> ok = UpdateUsage(delta);
4984 MOZ_ASSERT(ok);
4989 int64_t result = mUpdateBatchUsage;
4990 mUpdateBatchUsage = -1;
4992 if (IsPersistent()) {
4993 mConnection->EndUpdateBatch();
4996 mInUpdateBatch = false;
4998 return result;
5001 int64_t Datastore::AttemptToUpdateUsage(int64_t aMinSize, bool aInitial) {
5002 AssertIsOnBackgroundThread();
5003 MOZ_ASSERT_IF(aInitial, aMinSize >= 0);
5004 MOZ_ASSERT_IF(!aInitial, aMinSize > 0);
5006 const int64_t size = aMinSize + GetSnapshotPeakUsagePreincrement(aInitial);
5008 if (size && UpdateUsage(size)) {
5009 return size;
5012 const int64_t reducedSize =
5013 aMinSize + GetSnapshotPeakUsageReducedPreincrement(aInitial);
5015 if (reducedSize && UpdateUsage(reducedSize)) {
5016 return reducedSize;
5019 if (aMinSize > 0 && UpdateUsage(aMinSize)) {
5020 return aMinSize;
5023 return 0;
5026 bool Datastore::HasOtherProcessObservers(Database* aDatabase) {
5027 AssertIsOnBackgroundThread();
5028 MOZ_ASSERT(aDatabase);
5030 if (!gObservers) {
5031 return false;
5034 nsTArray<NotNull<Observer*>>* array;
5035 if (!gObservers->Get(mOriginMetadata.mOrigin, &array)) {
5036 return false;
5039 MOZ_ASSERT(array);
5041 PBackgroundParent* databaseBackgroundActor = aDatabase->Manager();
5043 for (Observer* observer : *array) {
5044 if (observer->Manager() != databaseBackgroundActor) {
5045 return true;
5049 return false;
5052 void Datastore::NotifyOtherProcessObservers(Database* aDatabase,
5053 const nsString& aDocumentURI,
5054 const nsString& aKey,
5055 const LSValue& aOldValue,
5056 const LSValue& aNewValue) {
5057 AssertIsOnBackgroundThread();
5058 MOZ_ASSERT(aDatabase);
5060 if (!gObservers) {
5061 return;
5064 nsTArray<NotNull<Observer*>>* array;
5065 if (!gObservers->Get(mOriginMetadata.mOrigin, &array)) {
5066 return;
5069 MOZ_ASSERT(array);
5071 // We do not want to send information about events back to the content process
5072 // that caused the change.
5073 PBackgroundParent* databaseBackgroundActor = aDatabase->Manager();
5075 for (Observer* observer : *array) {
5076 if (observer->Manager() != databaseBackgroundActor) {
5077 observer->Observe(aDatabase, aDocumentURI, aKey, aOldValue, aNewValue);
5082 void Datastore::NoteChangedObserverArray(
5083 const nsTArray<NotNull<Observer*>>& aObservers) {
5084 AssertIsOnBackgroundThread();
5086 for (Database* database : mActiveDatabases) {
5087 Snapshot* snapshot = database->GetSnapshot();
5088 MOZ_ASSERT(snapshot);
5090 if (snapshot->IsDirty()) {
5091 continue;
5094 bool hasOtherProcessObservers = false;
5096 PBackgroundParent* databaseBackgroundActor = database->Manager();
5098 for (Observer* observer : aObservers) {
5099 if (observer->Manager() != databaseBackgroundActor) {
5100 hasOtherProcessObservers = true;
5101 break;
5105 if (snapshot->HasOtherProcessObservers() != hasOtherProcessObservers) {
5106 snapshot->MarkDirty();
5111 void Datastore::Stringify(nsACString& aResult) const {
5112 AssertIsOnBackgroundThread();
5114 aResult.AppendLiteral("DirectoryLock:");
5115 aResult.AppendInt(!!mDirectoryLock);
5116 aResult.Append(kQuotaGenericDelimiter);
5118 aResult.AppendLiteral("Connection:");
5119 aResult.AppendInt(!!mConnection);
5120 aResult.Append(kQuotaGenericDelimiter);
5122 aResult.AppendLiteral("QuotaObject:");
5123 aResult.AppendInt(!!mQuotaObject);
5124 aResult.Append(kQuotaGenericDelimiter);
5126 aResult.AppendLiteral("PrepareDatastoreOps:");
5127 aResult.AppendInt(mPrepareDatastoreOps.Count());
5128 aResult.Append(kQuotaGenericDelimiter);
5130 aResult.AppendLiteral("PreparedDatastores:");
5131 aResult.AppendInt(mPreparedDatastores.Count());
5132 aResult.Append(kQuotaGenericDelimiter);
5134 aResult.AppendLiteral("Databases:");
5135 aResult.AppendInt(mDatabases.Count());
5136 aResult.Append(kQuotaGenericDelimiter);
5138 aResult.AppendLiteral("ActiveDatabases:");
5139 aResult.AppendInt(mActiveDatabases.Count());
5140 aResult.Append(kQuotaGenericDelimiter);
5142 aResult.AppendLiteral("Origin:");
5143 aResult.Append(AnonymizedOriginString(mOriginMetadata.mOrigin));
5144 aResult.Append(kQuotaGenericDelimiter);
5146 aResult.AppendLiteral("PrivateBrowsingId:");
5147 aResult.AppendInt(mPrivateBrowsingId);
5148 aResult.Append(kQuotaGenericDelimiter);
5150 aResult.AppendLiteral("Closed:");
5151 aResult.AppendInt(mClosed);
5154 bool Datastore::UpdateUsage(int64_t aDelta) {
5155 AssertIsOnBackgroundThread();
5157 // Check internal LocalStorage origin limit.
5158 int64_t newUsage = mUsage + aDelta;
5160 MOZ_ASSERT(newUsage >= 0);
5162 if (newUsage > StaticPrefs::dom_storage_default_quota() * 1024) {
5163 return false;
5166 // Check QuotaManager limits (group and global limit).
5167 if (IsPersistent()) {
5168 MOZ_ASSERT(mQuotaObject);
5170 if (!mQuotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) {
5171 return false;
5175 // Quota checks passed, set new usage.
5176 mUsage = newUsage;
5178 return true;
5181 void Datastore::MaybeClose() {
5182 AssertIsOnBackgroundThread();
5184 if (!mPrepareDatastoreOps.Count() && !mHasLivePrivateDatastore &&
5185 !mPreparedDatastores.Count() && !mDatabases.Count()) {
5186 Close();
5190 void Datastore::ConnectionClosedCallback() {
5191 AssertIsOnBackgroundThread();
5192 MOZ_ASSERT(mDirectoryLock);
5193 MOZ_ASSERT(mConnection);
5194 MOZ_ASSERT(mQuotaObject);
5195 MOZ_ASSERT(mClosed);
5197 // Release the quota object first.
5198 mQuotaObject = nullptr;
5200 bool databaseWasNotAvailable;
5201 bool hasCreatedDatabase;
5202 mConnection->GetFinishInfo(databaseWasNotAvailable, hasCreatedDatabase);
5204 if (databaseWasNotAvailable && !hasCreatedDatabase) {
5205 MOZ_ASSERT(mUsage == 0);
5207 QuotaManager* quotaManager = QuotaManager::Get();
5208 MOZ_ASSERT(quotaManager);
5210 quotaManager->ResetUsageForClient(
5211 ClientMetadata{mOriginMetadata, mozilla::dom::quota::Client::LS});
5214 mConnection = nullptr;
5216 // Now it's safe to release the directory lock and unregister itself from
5217 // the hashtable.
5219 mDirectoryLock = nullptr;
5221 CleanupMetadata();
5223 if (mCompleteCallback) {
5224 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget()));
5228 void Datastore::CleanupMetadata() {
5229 AssertIsOnBackgroundThread();
5231 MOZ_ASSERT(gDatastores);
5232 const DebugOnly<bool> removed = gDatastores->Remove(mOriginMetadata.mOrigin);
5233 MOZ_ASSERT(removed);
5235 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS,
5236 "Datastore removed"_ns);
5238 if (!gDatastores->Count()) {
5239 gDatastores = nullptr;
5243 void Datastore::NotifySnapshots(Database* aDatabase, const nsAString& aKey,
5244 const LSValue& aOldValue, bool aAffectsOrder) {
5245 AssertIsOnBackgroundThread();
5247 for (Database* database : mDatabases) {
5248 MOZ_ASSERT(database);
5250 if (database == aDatabase) {
5251 continue;
5254 Snapshot* snapshot = database->GetSnapshot();
5255 if (snapshot) {
5256 snapshot->SaveItem(aKey, aOldValue, aAffectsOrder);
5261 void Datastore::NoteChangedDatabaseMap() {
5262 AssertIsOnBackgroundThread();
5264 for (Database* database : mActiveDatabases) {
5265 Snapshot* snapshot = database->GetSnapshot();
5266 MOZ_ASSERT(snapshot);
5268 if (snapshot->IsDirty()) {
5269 continue;
5272 if (snapshot->HasOtherProcessDatabases() !=
5273 HasOtherProcessDatabases(database)) {
5274 snapshot->MarkDirty();
5279 /*******************************************************************************
5280 * PreparedDatastore
5281 ******************************************************************************/
5283 void PreparedDatastore::Destroy() {
5284 AssertIsOnBackgroundThread();
5285 MOZ_ASSERT(gPreparedDatastores);
5286 DebugOnly<bool> removed = gPreparedDatastores->Remove(mDatastoreId);
5287 MOZ_ASSERT(removed);
5290 // static
5291 void PreparedDatastore::TimerCallback(nsITimer* aTimer, void* aClosure) {
5292 AssertIsOnBackgroundThread();
5294 auto* self = static_cast<PreparedDatastore*>(aClosure);
5295 MOZ_ASSERT(self);
5297 self->Destroy();
5300 /*******************************************************************************
5301 * Database
5302 ******************************************************************************/
5304 Database::Database(const PrincipalInfo& aPrincipalInfo,
5305 const Maybe<ContentParentId>& aContentParentId,
5306 const nsACString& aOrigin, uint32_t aPrivateBrowsingId)
5307 : mSnapshot(nullptr),
5308 mPrincipalInfo(aPrincipalInfo),
5309 mContentParentId(aContentParentId),
5310 mOrigin(aOrigin),
5311 mPrivateBrowsingId(aPrivateBrowsingId),
5312 mAllowedToClose(false),
5313 mActorDestroyed(false),
5314 mRequestedAllowToClose(false)
5315 #ifdef DEBUG
5317 mActorWasAlive(false)
5318 #endif
5320 AssertIsOnBackgroundThread();
5323 Database::~Database() {
5324 MOZ_ASSERT_IF(mActorWasAlive, mAllowedToClose);
5325 MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
5328 void Database::SetActorAlive(Datastore* aDatastore) {
5329 AssertIsOnBackgroundThread();
5330 MOZ_ASSERT(!mActorWasAlive);
5331 MOZ_ASSERT(!mActorDestroyed);
5333 #ifdef DEBUG
5334 mActorWasAlive = true;
5335 #endif
5337 mDatastore = aDatastore;
5339 mDatastore->NoteLiveDatabase(this);
5341 if (!gLiveDatabases) {
5342 gLiveDatabases = new LiveDatabaseArray();
5345 gLiveDatabases->AppendElement(WrapNotNullUnchecked(this));
5348 void Database::RegisterSnapshot(Snapshot* aSnapshot) {
5349 AssertIsOnBackgroundThread();
5350 MOZ_ASSERT(aSnapshot);
5351 MOZ_ASSERT(!mSnapshot);
5352 MOZ_ASSERT(!mAllowedToClose);
5354 // Only one snapshot at a time is currently supported.
5355 mSnapshot = aSnapshot;
5357 mDatastore->NoteActiveDatabase(this);
5360 void Database::UnregisterSnapshot(Snapshot* aSnapshot) {
5361 MOZ_ASSERT(aSnapshot);
5362 MOZ_ASSERT(mSnapshot == aSnapshot);
5364 mSnapshot = nullptr;
5366 mDatastore->NoteInactiveDatabase(this);
5369 void Database::RequestAllowToClose() {
5370 AssertIsOnBackgroundThread();
5372 if (mRequestedAllowToClose) {
5373 return;
5376 mRequestedAllowToClose = true;
5378 // Send the RequestAllowToClose message to the child to avoid racing with the
5379 // child actor. Except the case when the actor was already destroyed.
5380 if (mActorDestroyed) {
5381 MOZ_ASSERT(mAllowedToClose);
5382 return;
5385 if (NS_WARN_IF(!SendRequestAllowToClose()) && !mSnapshot) {
5386 // This is not necessary, because there should be a runnable scheduled that
5387 // will call ActorDestroy which calls AllowToClose. However we can speedup
5388 // the shutdown a bit if we do it here directly, but only if there's no
5389 // registered snapshot.
5390 AllowToClose();
5394 void Database::ForceKill() {
5395 AssertIsOnBackgroundThread();
5397 if (mActorDestroyed) {
5398 MOZ_ASSERT(mAllowedToClose);
5399 return;
5402 Unused << PBackgroundLSDatabaseParent::Send__delete__(this);
5405 void Database::Stringify(nsACString& aResult) const {
5406 AssertIsOnBackgroundThread();
5408 aResult.AppendLiteral("SnapshotRegistered:");
5409 aResult.AppendInt(!!mSnapshot);
5410 aResult.Append(kQuotaGenericDelimiter);
5412 aResult.AppendLiteral("OtherProcessActor:");
5413 aResult.AppendInt(BackgroundParent::IsOtherProcessActor(Manager()));
5414 aResult.Append(kQuotaGenericDelimiter);
5416 aResult.AppendLiteral("Origin:");
5417 aResult.Append(AnonymizedOriginString(mOrigin));
5418 aResult.Append(kQuotaGenericDelimiter);
5420 aResult.AppendLiteral("PrivateBrowsingId:");
5421 aResult.AppendInt(mPrivateBrowsingId);
5422 aResult.Append(kQuotaGenericDelimiter);
5424 aResult.AppendLiteral("AllowedToClose:");
5425 aResult.AppendInt(mAllowedToClose);
5426 aResult.Append(kQuotaGenericDelimiter);
5428 aResult.AppendLiteral("ActorDestroyed:");
5429 aResult.AppendInt(mActorDestroyed);
5430 aResult.Append(kQuotaGenericDelimiter);
5432 aResult.AppendLiteral("RequestedAllowToClose:");
5433 aResult.AppendInt(mRequestedAllowToClose);
5436 void Database::AllowToClose() {
5437 AssertIsOnBackgroundThread();
5438 MOZ_ASSERT(!mAllowedToClose);
5439 MOZ_ASSERT(mDatastore);
5440 MOZ_ASSERT(!mSnapshot);
5442 mAllowedToClose = true;
5444 mDatastore->NoteFinishedDatabase(this);
5446 mDatastore = nullptr;
5448 MOZ_ASSERT(gLiveDatabases);
5449 gLiveDatabases->RemoveElement(this);
5451 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS,
5452 "Live database removed"_ns);
5454 if (gLiveDatabases->IsEmpty()) {
5455 gLiveDatabases = nullptr;
5459 void Database::ActorDestroy(ActorDestroyReason aWhy) {
5460 AssertIsOnBackgroundThread();
5461 MOZ_ASSERT(!mActorDestroyed);
5463 mActorDestroyed = true;
5465 if (!mAllowedToClose) {
5466 AllowToClose();
5470 mozilla::ipc::IPCResult Database::RecvDeleteMe() {
5471 AssertIsOnBackgroundThread();
5472 MOZ_ASSERT(!mActorDestroyed);
5474 IProtocol* mgr = Manager();
5475 if (!PBackgroundLSDatabaseParent::Send__delete__(this)) {
5476 return IPC_FAIL(mgr, "Send__delete__ failed!");
5478 return IPC_OK();
5481 mozilla::ipc::IPCResult Database::RecvAllowToClose() {
5482 AssertIsOnBackgroundThread();
5484 if (NS_WARN_IF(mAllowedToClose)) {
5485 return IPC_FAIL(this, "mAllowedToClose already set!");
5488 AllowToClose();
5490 return IPC_OK();
5493 PBackgroundLSSnapshotParent* Database::AllocPBackgroundLSSnapshotParent(
5494 const nsAString& aDocumentURI, const nsAString& aKey,
5495 const bool& aIncreasePeakUsage, const int64_t& aMinSize,
5496 LSSnapshotInitInfo* aInitInfo) {
5497 AssertIsOnBackgroundThread();
5499 if (NS_WARN_IF(aIncreasePeakUsage && aMinSize < 0)) {
5500 MOZ_ASSERT_UNLESS_FUZZING(false);
5501 return nullptr;
5504 if (NS_WARN_IF(mAllowedToClose)) {
5505 MOZ_ASSERT_UNLESS_FUZZING(false);
5506 return nullptr;
5509 RefPtr<Snapshot> snapshot = new Snapshot(this, aDocumentURI);
5511 // Transfer ownership to IPDL.
5512 return snapshot.forget().take();
5515 mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor(
5516 PBackgroundLSSnapshotParent* aActor, const nsAString& aDocumentURI,
5517 const nsAString& aKey, const bool& aIncreasePeakUsage,
5518 const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) {
5519 AssertIsOnBackgroundThread();
5520 MOZ_ASSERT_IF(aIncreasePeakUsage, aMinSize >= 0);
5521 MOZ_ASSERT(aInitInfo);
5522 MOZ_ASSERT(!mAllowedToClose);
5524 auto* snapshot = static_cast<Snapshot*>(aActor);
5526 bool addKeyToUnknownItems;
5527 nsTHashtable<nsStringHashKey> loadedItems;
5528 nsTArray<LSItemInfo> itemInfos;
5529 uint32_t nextLoadIndex;
5530 LSSnapshot::LoadState loadState;
5531 mDatastore->GetSnapshotLoadInfo(aKey, addKeyToUnknownItems, loadedItems,
5532 itemInfos, nextLoadIndex, loadState);
5534 nsTHashSet<nsString> unknownItems;
5535 if (addKeyToUnknownItems) {
5536 unknownItems.Insert(aKey);
5539 uint32_t totalLength = mDatastore->GetLength();
5541 int64_t usage = mDatastore->GetUsage();
5543 int64_t peakUsage = usage;
5545 if (aIncreasePeakUsage) {
5546 int64_t size =
5547 mDatastore->AttemptToUpdateUsage(aMinSize, /* aInitial */ true);
5549 peakUsage += size;
5552 bool hasOtherProcessDatabases = mDatastore->HasOtherProcessDatabases(this);
5553 bool hasOtherProcessObservers = mDatastore->HasOtherProcessObservers(this);
5555 snapshot->Init(loadedItems, std::move(unknownItems), nextLoadIndex,
5556 totalLength, usage, peakUsage, loadState,
5557 hasOtherProcessDatabases, hasOtherProcessObservers);
5559 RegisterSnapshot(snapshot);
5561 aInitInfo->addKeyToUnknownItems() = addKeyToUnknownItems;
5562 aInitInfo->itemInfos() = std::move(itemInfos);
5563 aInitInfo->totalLength() = totalLength;
5564 aInitInfo->usage() = usage;
5565 aInitInfo->peakUsage() = peakUsage;
5566 aInitInfo->loadState() = loadState;
5567 aInitInfo->hasOtherProcessDatabases() = hasOtherProcessDatabases;
5568 aInitInfo->hasOtherProcessObservers() = hasOtherProcessObservers;
5570 return IPC_OK();
5573 bool Database::DeallocPBackgroundLSSnapshotParent(
5574 PBackgroundLSSnapshotParent* aActor) {
5575 AssertIsOnBackgroundThread();
5576 MOZ_ASSERT(aActor);
5578 // Transfer ownership back from IPDL.
5579 RefPtr<Snapshot> actor = dont_AddRef(static_cast<Snapshot*>(aActor));
5581 return true;
5584 /*******************************************************************************
5585 * Snapshot
5586 ******************************************************************************/
5588 Snapshot::Snapshot(Database* aDatabase, const nsAString& aDocumentURI)
5589 : mDatabase(aDatabase),
5590 mDatastore(aDatabase->GetDatastore()),
5591 mDocumentURI(aDocumentURI),
5592 mTotalLength(0),
5593 mUsage(-1),
5594 mPeakUsage(-1),
5595 mSavedKeys(false),
5596 mActorDestroyed(false),
5597 mFinishReceived(false),
5598 mLoadedReceived(false),
5599 mLoadedAllItems(false),
5600 mLoadKeysReceived(false),
5601 mSentMarkDirty(false) {
5602 AssertIsOnBackgroundThread();
5603 MOZ_ASSERT(aDatabase);
5606 Snapshot::~Snapshot() {
5607 MOZ_ASSERT(mActorDestroyed);
5608 MOZ_ASSERT(mFinishReceived);
5611 void Snapshot::SaveItem(const nsAString& aKey, const LSValue& aOldValue,
5612 bool aAffectsOrder) {
5613 AssertIsOnBackgroundThread();
5615 MarkDirty();
5617 if (mLoadedAllItems) {
5618 return;
5621 if (!mLoadedItems.Contains(aKey) && !mUnknownItems.Contains(aKey)) {
5622 mValues.LookupOrInsert(aKey, aOldValue);
5625 if (aAffectsOrder && !mSavedKeys) {
5626 mDatastore->GetKeys(mKeys);
5627 mSavedKeys = true;
5631 void Snapshot::MarkDirty() {
5632 AssertIsOnBackgroundThread();
5634 if (!mSentMarkDirty) {
5635 Unused << SendMarkDirty();
5636 mSentMarkDirty = true;
5640 void Snapshot::Finish() {
5641 AssertIsOnBackgroundThread();
5642 MOZ_ASSERT(mDatabase);
5643 MOZ_ASSERT(mDatastore);
5644 MOZ_ASSERT(!mFinishReceived);
5646 mDatastore->BeginUpdateBatch(mUsage);
5648 mDatastore->EndUpdateBatch(mPeakUsage);
5650 mDatabase->UnregisterSnapshot(this);
5652 mFinishReceived = true;
5655 void Snapshot::ActorDestroy(ActorDestroyReason aWhy) {
5656 AssertIsOnBackgroundThread();
5657 MOZ_ASSERT(!mActorDestroyed);
5659 mActorDestroyed = true;
5661 if (!mFinishReceived) {
5662 Finish();
5666 mozilla::ipc::IPCResult Snapshot::RecvDeleteMe() {
5667 AssertIsOnBackgroundThread();
5668 MOZ_ASSERT(!mActorDestroyed);
5670 IProtocol* mgr = Manager();
5671 if (!PBackgroundLSSnapshotParent::Send__delete__(this)) {
5672 return IPC_FAIL(mgr, "Send__delete__ failed!");
5674 return IPC_OK();
5677 mozilla::ipc::IPCResult Snapshot::Checkpoint(
5678 nsTArray<LSWriteInfo>&& aWriteInfos) {
5679 AssertIsOnBackgroundThread();
5680 // Don't assert `mUsage >= 0`, it can be negative when multiple snapshots are
5681 // operating in parallel.
5682 MOZ_ASSERT(mPeakUsage >= mUsage);
5684 if (NS_WARN_IF(aWriteInfos.IsEmpty())) {
5685 return IPC_FAIL(this, "aWriteInfos is empty!");
5688 if (NS_WARN_IF(mHasOtherProcessObservers)) {
5689 return IPC_FAIL(this, "mHasOtherProcessObservers already set!");
5692 mDatastore->BeginUpdateBatch(mUsage);
5694 for (uint32_t index = 0; index < aWriteInfos.Length(); index++) {
5695 const LSWriteInfo& writeInfo = aWriteInfos[index];
5697 switch (writeInfo.type()) {
5698 case LSWriteInfo::TLSSetItemInfo: {
5699 const LSSetItemInfo& info = writeInfo.get_LSSetItemInfo();
5701 mDatastore->SetItem(mDatabase, info.key(), info.value());
5703 break;
5706 case LSWriteInfo::TLSRemoveItemInfo: {
5707 const LSRemoveItemInfo& info = writeInfo.get_LSRemoveItemInfo();
5709 mDatastore->RemoveItem(mDatabase, info.key());
5711 break;
5714 case LSWriteInfo::TLSClearInfo: {
5715 mDatastore->Clear(mDatabase);
5717 break;
5720 default:
5721 MOZ_CRASH("Should never get here!");
5725 mUsage = mDatastore->EndUpdateBatch(-1);
5727 return IPC_OK();
5730 mozilla::ipc::IPCResult Snapshot::CheckpointAndNotify(
5731 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) {
5732 AssertIsOnBackgroundThread();
5733 // Don't assert `mUsage >= 0`, it can be negative when multiple snapshots are
5734 // operating in parallel.
5735 MOZ_ASSERT(mPeakUsage >= mUsage);
5737 if (NS_WARN_IF(aWriteAndNotifyInfos.IsEmpty())) {
5738 return IPC_FAIL(this, "aWriteAndNotifyInfos is empty!");
5741 if (NS_WARN_IF(!mHasOtherProcessObservers)) {
5742 return IPC_FAIL(this, "mHasOtherProcessObservers is not set!");
5745 mDatastore->BeginUpdateBatch(mUsage);
5747 for (uint32_t index = 0; index < aWriteAndNotifyInfos.Length(); index++) {
5748 const LSWriteAndNotifyInfo& writeAndNotifyInfo =
5749 aWriteAndNotifyInfos[index];
5751 switch (writeAndNotifyInfo.type()) {
5752 case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: {
5753 const LSSetItemAndNotifyInfo& info =
5754 writeAndNotifyInfo.get_LSSetItemAndNotifyInfo();
5756 mDatastore->SetItem(mDatabase, info.key(), info.value());
5758 mDatastore->NotifyOtherProcessObservers(
5759 mDatabase, mDocumentURI, info.key(), info.oldValue(), info.value());
5761 break;
5764 case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: {
5765 const LSRemoveItemAndNotifyInfo& info =
5766 writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo();
5768 mDatastore->RemoveItem(mDatabase, info.key());
5770 mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI,
5771 info.key(), info.oldValue(),
5772 VoidLSValue());
5774 break;
5777 case LSWriteAndNotifyInfo::TLSClearInfo: {
5778 mDatastore->Clear(mDatabase);
5780 mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI,
5781 VoidString(), VoidLSValue(),
5782 VoidLSValue());
5784 break;
5787 default:
5788 MOZ_CRASH("Should never get here!");
5792 mUsage = mDatastore->EndUpdateBatch(-1);
5794 return IPC_OK();
5797 mozilla::ipc::IPCResult Snapshot::RecvAsyncCheckpoint(
5798 nsTArray<LSWriteInfo>&& aWriteInfos) {
5799 return Checkpoint(std::move(aWriteInfos));
5802 mozilla::ipc::IPCResult Snapshot::RecvAsyncCheckpointAndNotify(
5803 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) {
5804 return CheckpointAndNotify(std::move(aWriteAndNotifyInfos));
5807 mozilla::ipc::IPCResult Snapshot::RecvSyncCheckpoint(
5808 nsTArray<LSWriteInfo>&& aWriteInfos) {
5809 return Checkpoint(std::move(aWriteInfos));
5812 mozilla::ipc::IPCResult Snapshot::RecvSyncCheckpointAndNotify(
5813 nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) {
5814 return CheckpointAndNotify(std::move(aWriteAndNotifyInfos));
5817 mozilla::ipc::IPCResult Snapshot::RecvAsyncFinish() {
5818 AssertIsOnBackgroundThread();
5820 if (NS_WARN_IF(mFinishReceived)) {
5821 MOZ_ASSERT_UNLESS_FUZZING(false);
5822 return IPC_FAIL(this, "Already finished");
5825 Finish();
5827 return IPC_OK();
5830 mozilla::ipc::IPCResult Snapshot::RecvSyncFinish() {
5831 AssertIsOnBackgroundThread();
5833 if (NS_WARN_IF(mFinishReceived)) {
5834 MOZ_ASSERT_UNLESS_FUZZING(false);
5835 return IPC_FAIL(this, "Already finished");
5838 Finish();
5840 return IPC_OK();
5843 mozilla::ipc::IPCResult Snapshot::RecvLoaded() {
5844 AssertIsOnBackgroundThread();
5846 if (NS_WARN_IF(mFinishReceived)) {
5847 return IPC_FAIL(this, "mFinishReceived already set!");
5850 if (NS_WARN_IF(mLoadedReceived)) {
5851 return IPC_FAIL(this, "mLoadedReceived already set!");
5854 if (NS_WARN_IF(mLoadedAllItems)) {
5855 return IPC_FAIL(this, "mLoadedAllItems already set!");
5858 if (NS_WARN_IF(mLoadKeysReceived)) {
5859 return IPC_FAIL(this, "mLoadKeysReceived already set!");
5862 mLoadedReceived = true;
5864 mLoadedItems.Clear();
5865 mUnknownItems.Clear();
5866 mValues.Clear();
5867 mKeys.Clear();
5868 mLoadedAllItems = true;
5869 mLoadKeysReceived = true;
5871 return IPC_OK();
5874 mozilla::ipc::IPCResult Snapshot::RecvLoadValueAndMoreItems(
5875 const nsAString& aKey, LSValue* aValue, nsTArray<LSItemInfo>* aItemInfos) {
5876 AssertIsOnBackgroundThread();
5877 MOZ_ASSERT(aValue);
5878 MOZ_ASSERT(aItemInfos);
5879 MOZ_ASSERT(mDatastore);
5881 if (NS_WARN_IF(mFinishReceived)) {
5882 return IPC_FAIL(this, "mFinishReceived already set!");
5885 if (NS_WARN_IF(mLoadedReceived)) {
5886 return IPC_FAIL(this, "mLoadedReceived already set!");
5889 if (NS_WARN_IF(mLoadedAllItems)) {
5890 return IPC_FAIL(this, "mLoadedAllItems already set!");
5893 if (mLoadedItems.Contains(aKey)) {
5894 return IPC_FAIL(this, "mLoadedItems already contains aKey!");
5897 if (mUnknownItems.Contains(aKey)) {
5898 return IPC_FAIL(this, "mUnknownItems already contains aKey!");
5901 if (auto entry = mValues.Lookup(aKey)) {
5902 *aValue = entry.Data();
5903 entry.Remove();
5904 } else {
5905 mDatastore->GetItem(aKey, *aValue);
5908 if (aValue->IsVoid()) {
5909 mUnknownItems.Insert(aKey);
5910 } else {
5911 mLoadedItems.PutEntry(aKey);
5913 // mLoadedItems.Count()==mTotalLength is checked below.
5916 // Load some more key/value pairs (as many as the snapshot gradual prefill
5917 // byte budget allows).
5919 if (gSnapshotGradualPrefill > 0) {
5920 const nsTArray<LSItemInfo>& orderedItems = mDatastore->GetOrderedItems();
5922 uint32_t length;
5923 if (mSavedKeys) {
5924 length = mKeys.Length();
5925 } else {
5926 length = orderedItems.Length();
5929 int64_t size = 0;
5930 while (mNextLoadIndex < length) {
5931 // If the datastore's ordering has changed, mSavedKeys will be true and
5932 // mKeys contains an ordered list of the keys. Otherwise we can use the
5933 // datastore's key ordering which is still the same as when the snapshot
5934 // was created.
5936 nsString key;
5937 if (mSavedKeys) {
5938 key = mKeys[mNextLoadIndex];
5939 } else {
5940 key = orderedItems[mNextLoadIndex].key();
5943 // Normally we would do this:
5944 // if (!mLoadedItems.GetEntry(key)) {
5945 // ...
5946 // mLoadedItems.PutEntry(key);
5947 // }
5948 // but that requires two hash lookups. We can reduce that to just one
5949 // hash lookup if we always call PutEntry and check the number of entries
5950 // before and after the put (which is very cheap). However, if we reach
5951 // the prefill limit, we need to call RemoveEntry, but that is also cheap
5952 // because we pass the entry (not the key).
5954 uint32_t countBeforePut = mLoadedItems.Count();
5955 auto loadedItemEntry = mLoadedItems.PutEntry(key);
5956 if (countBeforePut != mLoadedItems.Count()) {
5957 // Check mValues first since that contains values as they existed when
5958 // our snapshot was created, but have since been changed/removed in the
5959 // datastore. If it's not there, then the datastore has the
5960 // still-current value. However, if the datastore's key ordering has
5961 // changed, we need to do a hash lookup rather than being able to do an
5962 // optimized direct access to the index.
5964 LSValue value;
5965 auto valueEntry = mValues.Lookup(key);
5966 if (valueEntry) {
5967 value = valueEntry.Data();
5968 } else if (mSavedKeys) {
5969 mDatastore->GetItem(nsString(key), value);
5970 } else {
5971 value = orderedItems[mNextLoadIndex].value();
5974 // All not loaded keys must have a value.
5975 MOZ_ASSERT(!value.IsVoid());
5977 size += static_cast<int64_t>(key.Length()) +
5978 static_cast<int64_t>(value.Length());
5980 if (size > gSnapshotGradualPrefill) {
5981 mLoadedItems.RemoveEntry(loadedItemEntry);
5983 // mNextLoadIndex is not incremented, so we will resume at the same
5984 // position next time.
5985 break;
5988 if (valueEntry) {
5989 valueEntry.Remove();
5992 LSItemInfo* itemInfo = aItemInfos->AppendElement();
5993 itemInfo->key() = key;
5994 itemInfo->value() = value;
5997 mNextLoadIndex++;
6001 if (mLoadedItems.Count() == mTotalLength) {
6002 mLoadedItems.Clear();
6003 mUnknownItems.Clear();
6004 #ifdef DEBUG
6005 const bool allValuesVoid =
6006 std::all_of(mValues.Values().cbegin(), mValues.Values().cend(),
6007 [](const auto& entry) { return entry.IsVoid(); });
6008 MOZ_ASSERT(allValuesVoid);
6009 #endif
6010 mValues.Clear();
6011 mLoadedAllItems = true;
6014 return IPC_OK();
6017 mozilla::ipc::IPCResult Snapshot::RecvLoadKeys(nsTArray<nsString>* aKeys) {
6018 AssertIsOnBackgroundThread();
6019 MOZ_ASSERT(aKeys);
6020 MOZ_ASSERT(mDatastore);
6022 if (NS_WARN_IF(mFinishReceived)) {
6023 return IPC_FAIL(this, "mFinishReceived already set!");
6026 if (NS_WARN_IF(mLoadedReceived)) {
6027 return IPC_FAIL(this, "mLoadedReceived already set!");
6030 if (NS_WARN_IF(mLoadKeysReceived)) {
6031 return IPC_FAIL(this, "mLoadKeysReceived already set!");
6034 mLoadKeysReceived = true;
6036 if (mSavedKeys) {
6037 aKeys->AppendElements(std::move(mKeys));
6038 } else {
6039 mDatastore->GetKeys(*aKeys);
6042 return IPC_OK();
6045 mozilla::ipc::IPCResult Snapshot::RecvIncreasePeakUsage(const int64_t& aMinSize,
6046 int64_t* aSize) {
6047 AssertIsOnBackgroundThread();
6048 MOZ_ASSERT(aSize);
6050 if (NS_WARN_IF(aMinSize <= 0)) {
6051 return IPC_FAIL(this, "aMinSize not valid!");
6054 if (NS_WARN_IF(mFinishReceived)) {
6055 return IPC_FAIL(this, "mFinishReceived already set!");
6058 int64_t size =
6059 mDatastore->AttemptToUpdateUsage(aMinSize, /* aInitial */ false);
6061 mPeakUsage += size;
6063 *aSize = size;
6065 return IPC_OK();
6068 /*******************************************************************************
6069 * Observer
6070 ******************************************************************************/
6072 Observer::Observer(const nsACString& aOrigin)
6073 : mOrigin(aOrigin), mActorDestroyed(false) {
6074 AssertIsOnBackgroundThread();
6077 Observer::~Observer() { MOZ_ASSERT(mActorDestroyed); }
6079 void Observer::Observe(Database* aDatabase, const nsString& aDocumentURI,
6080 const nsString& aKey, const LSValue& aOldValue,
6081 const LSValue& aNewValue) {
6082 AssertIsOnBackgroundThread();
6083 MOZ_ASSERT(aDatabase);
6085 Unused << SendObserve(aDatabase->GetPrincipalInfo(),
6086 aDatabase->PrivateBrowsingId(), aDocumentURI, aKey,
6087 aOldValue, aNewValue);
6090 void Observer::ActorDestroy(ActorDestroyReason aWhy) {
6091 AssertIsOnBackgroundThread();
6092 MOZ_ASSERT(!mActorDestroyed);
6094 mActorDestroyed = true;
6096 MOZ_ASSERT(gObservers);
6098 nsTArray<NotNull<Observer*>>* array;
6099 gObservers->Get(mOrigin, &array);
6100 MOZ_ASSERT(array);
6102 array->RemoveElement(this);
6104 if (RefPtr<Datastore> datastore = GetDatastore(mOrigin)) {
6105 datastore->NoteChangedObserverArray(*array);
6108 if (array->IsEmpty()) {
6109 gObservers->Remove(mOrigin);
6112 if (!gObservers->Count()) {
6113 gObservers = nullptr;
6117 mozilla::ipc::IPCResult Observer::RecvDeleteMe() {
6118 AssertIsOnBackgroundThread();
6119 MOZ_ASSERT(!mActorDestroyed);
6121 IProtocol* mgr = Manager();
6122 if (!PBackgroundLSObserverParent::Send__delete__(this)) {
6123 return IPC_FAIL(mgr, "Send__delete__ failed!");
6125 return IPC_OK();
6128 /*******************************************************************************
6129 * LSRequestBase
6130 ******************************************************************************/
6132 LSRequestBase::LSRequestBase(const LSRequestParams& aParams,
6133 const Maybe<ContentParentId>& aContentParentId)
6134 : mParams(aParams),
6135 mContentParentId(aContentParentId),
6136 mState(State::Initial),
6137 mWaitingForFinish(false) {}
6139 LSRequestBase::~LSRequestBase() {
6140 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
6141 mState == State::Initial || mState == State::Completed);
6144 void LSRequestBase::Dispatch() {
6145 AssertIsOnOwningThread();
6147 mState = State::StartingRequest;
6149 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
6152 void LSRequestBase::StringifyState(nsACString& aResult) const {
6153 AssertIsOnOwningThread();
6155 switch (mState) {
6156 case State::Initial:
6157 aResult.AppendLiteral("Initial");
6158 return;
6160 case State::StartingRequest:
6161 aResult.AppendLiteral("StartingRequest");
6162 return;
6164 case State::Nesting:
6165 aResult.AppendLiteral("Nesting");
6166 return;
6168 case State::SendingReadyMessage:
6169 aResult.AppendLiteral("SendingReadyMessage");
6170 return;
6172 case State::WaitingForFinish:
6173 aResult.AppendLiteral("WaitingForFinish");
6174 return;
6176 case State::SendingResults:
6177 aResult.AppendLiteral("SendingResults");
6178 return;
6180 case State::Completed:
6181 aResult.AppendLiteral("Completed");
6182 return;
6184 default:
6185 MOZ_CRASH("Bad state!");
6189 void LSRequestBase::Stringify(nsACString& aResult) const {
6190 AssertIsOnOwningThread();
6192 aResult.AppendLiteral("State:");
6193 StringifyState(aResult);
6196 void LSRequestBase::Log() {
6197 AssertIsOnOwningThread();
6199 if (!LS_LOG_TEST()) {
6200 return;
6203 LS_LOG(("LSRequestBase [%p]", this));
6205 nsCString state;
6206 StringifyState(state);
6208 LS_LOG((" mState: %s", state.get()));
6211 nsresult LSRequestBase::NestedRun() { return NS_OK; }
6213 bool LSRequestBase::VerifyRequestParams() {
6214 AssertIsOnBackgroundThread();
6216 MOZ_ASSERT(mParams.type() != LSRequestParams::T__None);
6218 switch (mParams.type()) {
6219 case LSRequestParams::TLSRequestPreloadDatastoreParams: {
6220 const LSRequestCommonParams& params =
6221 mParams.get_LSRequestPreloadDatastoreParams().commonParams();
6223 if (NS_WARN_IF(!VerifyPrincipalInfo(
6224 params.principalInfo(), params.storagePrincipalInfo(), false))) {
6225 return false;
6228 if (NS_WARN_IF(
6229 !VerifyOriginKey(params.originKey(), params.principalInfo()))) {
6230 return false;
6233 break;
6236 case LSRequestParams::TLSRequestPrepareDatastoreParams: {
6237 const LSRequestPrepareDatastoreParams& params =
6238 mParams.get_LSRequestPrepareDatastoreParams();
6240 const LSRequestCommonParams& commonParams = params.commonParams();
6242 if (NS_WARN_IF(!VerifyPrincipalInfo(commonParams.principalInfo(),
6243 commonParams.storagePrincipalInfo(),
6244 false))) {
6245 return false;
6248 if (params.clientPrincipalInfo() &&
6249 NS_WARN_IF(!VerifyPrincipalInfo(commonParams.principalInfo(),
6250 params.clientPrincipalInfo().ref(),
6251 true))) {
6252 return false;
6255 if (NS_WARN_IF(!VerifyClientId(mContentParentId,
6256 params.clientPrincipalInfo(),
6257 params.clientId()))) {
6258 return false;
6261 if (NS_WARN_IF(!VerifyOriginKey(commonParams.originKey(),
6262 commonParams.principalInfo()))) {
6263 return false;
6266 break;
6269 case LSRequestParams::TLSRequestPrepareObserverParams: {
6270 const LSRequestPrepareObserverParams& params =
6271 mParams.get_LSRequestPrepareObserverParams();
6273 if (NS_WARN_IF(!VerifyPrincipalInfo(
6274 params.principalInfo(), params.storagePrincipalInfo(), false))) {
6275 return false;
6278 if (params.clientPrincipalInfo() &&
6279 NS_WARN_IF(!VerifyPrincipalInfo(params.principalInfo(),
6280 params.clientPrincipalInfo().ref(),
6281 true))) {
6282 return false;
6285 if (NS_WARN_IF(!VerifyClientId(mContentParentId,
6286 params.clientPrincipalInfo(),
6287 params.clientId()))) {
6288 return false;
6291 break;
6294 default:
6295 MOZ_CRASH("Should never get here!");
6298 return true;
6301 nsresult LSRequestBase::StartRequest() {
6302 AssertIsOnOwningThread();
6303 MOZ_ASSERT(mState == State::StartingRequest);
6305 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6306 !MayProceed()) {
6307 return NS_ERROR_ABORT;
6310 #ifdef DEBUG
6311 // Always verify parameters in DEBUG builds!
6312 bool trustParams = false;
6313 #else
6314 bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
6315 #endif
6317 if (!trustParams && NS_WARN_IF(!VerifyRequestParams())) {
6318 return NS_ERROR_FAILURE;
6321 QM_TRY(MOZ_TO_RESULT(Start()));
6323 return NS_OK;
6326 void LSRequestBase::SendReadyMessage() {
6327 AssertIsOnOwningThread();
6328 MOZ_ASSERT(mState == State::SendingReadyMessage);
6330 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6331 !MayProceed()) {
6332 MaybeSetFailureCode(NS_ERROR_ABORT);
6335 nsresult rv = SendReadyMessageInternal();
6336 if (NS_WARN_IF(NS_FAILED(rv))) {
6337 MaybeSetFailureCode(rv);
6339 FinishInternal();
6343 nsresult LSRequestBase::SendReadyMessageInternal() {
6344 AssertIsOnOwningThread();
6345 MOZ_ASSERT(mState == State::SendingReadyMessage);
6347 if (!MayProceed()) {
6348 return NS_ERROR_ABORT;
6351 if (NS_WARN_IF(!SendReady())) {
6352 return NS_ERROR_FAILURE;
6355 mState = State::WaitingForFinish;
6357 mWaitingForFinish = true;
6359 return NS_OK;
6362 void LSRequestBase::Finish() {
6363 AssertIsOnOwningThread();
6364 MOZ_ASSERT(mState == State::WaitingForFinish);
6366 mWaitingForFinish = false;
6368 FinishInternal();
6371 void LSRequestBase::FinishInternal() {
6372 AssertIsOnOwningThread();
6373 MOZ_ASSERT(mState == State::SendingReadyMessage ||
6374 mState == State::WaitingForFinish);
6376 mState = State::SendingResults;
6378 // This LSRequestBase can only be held alive by the IPDL. Run() can end up
6379 // with clearing that last reference. So we need to add a self reference here.
6380 RefPtr<LSRequestBase> kungFuDeathGrip = this;
6382 MOZ_ALWAYS_SUCCEEDS(this->Run());
6385 void LSRequestBase::SendResults() {
6386 AssertIsOnOwningThread();
6387 MOZ_ASSERT(mState == State::SendingResults);
6389 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6390 !MayProceed()) {
6391 MaybeSetFailureCode(NS_ERROR_ABORT);
6394 if (MayProceed()) {
6395 LSRequestResponse response;
6397 if (NS_SUCCEEDED(ResultCode())) {
6398 GetResponse(response);
6400 MOZ_ASSERT(response.type() != LSRequestResponse::T__None);
6402 if (response.type() == LSRequestResponse::Tnsresult) {
6403 MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
6405 SetFailureCode(response.get_nsresult());
6407 } else {
6408 response = ResultCode();
6411 Unused << PBackgroundLSRequestParent::Send__delete__(this, response);
6414 Cleanup();
6416 mState = State::Completed;
6419 NS_IMETHODIMP
6420 LSRequestBase::Run() {
6421 nsresult rv;
6423 switch (mState) {
6424 case State::StartingRequest:
6425 rv = StartRequest();
6426 break;
6428 case State::Nesting:
6429 rv = NestedRun();
6430 break;
6432 case State::SendingReadyMessage:
6433 SendReadyMessage();
6434 return NS_OK;
6436 case State::SendingResults:
6437 SendResults();
6438 return NS_OK;
6440 default:
6441 MOZ_CRASH("Bad state!");
6444 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingReadyMessage) {
6445 MaybeSetFailureCode(rv);
6447 // Must set mState before dispatching otherwise we will race with the owning
6448 // thread.
6449 mState = State::SendingReadyMessage;
6451 if (IsOnOwningThread()) {
6452 SendReadyMessage();
6453 } else {
6454 MOZ_ALWAYS_SUCCEEDS(
6455 OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
6459 return NS_OK;
6462 void LSRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
6463 AssertIsOnOwningThread();
6465 NoteComplete();
6467 // Assume ActorDestroy can happen at any time, so we can't probe the current
6468 // state since mState can be modified on any thread (only one thread at a time
6469 // based on the state machine). However we can use mWaitingForFinish which is
6470 // only touched on the owning thread. If mWaitingForFinisg is true, we can
6471 // also modify mState since we are guaranteed that there are no pending
6472 // runnables which would probe mState to decide what code needs to run (there
6473 // shouldn't be any running runnables on other threads either).
6475 if (mWaitingForFinish) {
6476 Finish();
6479 // We don't have to handle the case when mWaitingForFinish is not true since
6480 // it means that either nothing has been initialized yet, so nothing to
6481 // cleanup or there are pending runnables that will detect that the actor has
6482 // been destroyed and cleanup accordingly.
6485 mozilla::ipc::IPCResult LSRequestBase::RecvCancel() {
6486 AssertIsOnOwningThread();
6488 Log();
6490 const char* crashOnCancel = PR_GetEnv("LSNG_CRASH_ON_CANCEL");
6491 if (crashOnCancel) {
6492 MOZ_CRASH("LSNG: Crash on cancel.");
6495 IProtocol* mgr = Manager();
6496 if (!PBackgroundLSRequestParent::Send__delete__(this, NS_ERROR_ABORT)) {
6497 return IPC_FAIL(mgr, "Send__delete__ failed!");
6500 return IPC_OK();
6503 mozilla::ipc::IPCResult LSRequestBase::RecvFinish() {
6504 AssertIsOnOwningThread();
6506 Finish();
6508 return IPC_OK();
6511 /*******************************************************************************
6512 * PrepareDatastoreOp
6513 ******************************************************************************/
6515 PrepareDatastoreOp::PrepareDatastoreOp(
6516 const LSRequestParams& aParams,
6517 const Maybe<ContentParentId>& aContentParentId)
6518 : LSRequestBase(aParams, aContentParentId),
6519 mLoadDataOp(nullptr),
6520 mPrivateBrowsingId(0),
6521 mUsage(0),
6522 mSizeOfKeys(0),
6523 mSizeOfItems(0),
6524 mDatastoreId(0),
6525 mNestedState(NestedState::BeforeNesting),
6526 mForPreload(aParams.type() ==
6527 LSRequestParams::TLSRequestPreloadDatastoreParams),
6528 mDatabaseNotAvailable(false),
6529 mInvalidated(false)
6530 #ifdef DEBUG
6532 mDEBUGUsage(0)
6533 #endif
6535 MOZ_ASSERT(
6536 aParams.type() == LSRequestParams::TLSRequestPreloadDatastoreParams ||
6537 aParams.type() == LSRequestParams::TLSRequestPrepareDatastoreParams);
6540 PrepareDatastoreOp::~PrepareDatastoreOp() {
6541 MOZ_ASSERT(!mDirectoryLock);
6542 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
6543 mState == State::Initial || mState == State::Completed);
6544 MOZ_ASSERT(!mLoadDataOp);
6547 void PrepareDatastoreOp::StringifyNestedState(nsACString& aResult) const {
6548 AssertIsOnOwningThread();
6550 switch (mNestedState) {
6551 case NestedState::BeforeNesting:
6552 aResult.AppendLiteral("BeforeNesting");
6553 return;
6555 case NestedState::CheckExistingOperations:
6556 aResult.AppendLiteral("CheckExistingOperations");
6557 return;
6559 case NestedState::CheckClosingDatastore:
6560 aResult.AppendLiteral("CheckClosingDatastore");
6561 return;
6563 case NestedState::PreparationPending:
6564 aResult.AppendLiteral("PreparationPending");
6565 return;
6567 case NestedState::DirectoryOpenPending:
6568 aResult.AppendLiteral("DirectoryOpenPending");
6569 return;
6571 case NestedState::DatabaseWorkOpen:
6572 aResult.AppendLiteral("DatabaseWorkOpen");
6573 return;
6575 case NestedState::BeginLoadData:
6576 aResult.AppendLiteral("BeginLoadData");
6577 return;
6579 case NestedState::DatabaseWorkLoadData:
6580 aResult.AppendLiteral("DatabaseWorkLoadData");
6581 return;
6583 case NestedState::AfterNesting:
6584 aResult.AppendLiteral("AfterNesting");
6585 return;
6587 default:
6588 MOZ_CRASH("Bad state!");
6592 void PrepareDatastoreOp::Stringify(nsACString& aResult) const {
6593 AssertIsOnOwningThread();
6595 LSRequestBase::Stringify(aResult);
6596 aResult.Append(kQuotaGenericDelimiter);
6598 aResult.AppendLiteral("Origin:");
6599 aResult.Append(AnonymizedOriginString(Origin()));
6600 aResult.Append(kQuotaGenericDelimiter);
6602 aResult.AppendLiteral("NestedState:");
6603 StringifyNestedState(aResult);
6606 void PrepareDatastoreOp::Log() {
6607 AssertIsOnOwningThread();
6609 LSRequestBase::Log();
6611 if (!LS_LOG_TEST()) {
6612 return;
6615 nsCString nestedState;
6616 StringifyNestedState(nestedState);
6618 LS_LOG((" mNestedState: %s", nestedState.get()));
6620 switch (mNestedState) {
6621 case NestedState::CheckClosingDatastore: {
6622 for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0;
6623 index--) {
6624 const auto& existingOp = (*gPrepareDatastoreOps)[index - 1];
6626 if (existingOp->mDelayedOp == this) {
6627 LS_LOG((" mDelayedBy: [%p]",
6628 static_cast<PrepareDatastoreOp*>(existingOp.get())));
6630 existingOp->Log();
6632 break;
6636 break;
6639 case NestedState::DirectoryOpenPending: {
6640 MOZ_ASSERT(mPendingDirectoryLock);
6642 LS_LOG((" mPendingDirectoryLock: [%p]", mPendingDirectoryLock.get()));
6644 mPendingDirectoryLock->Log();
6646 break;
6649 default:;
6653 nsresult PrepareDatastoreOp::Start() {
6654 AssertIsOnOwningThread();
6655 MOZ_ASSERT(mState == State::StartingRequest);
6656 MOZ_ASSERT(mNestedState == NestedState::BeforeNesting);
6657 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6658 MOZ_ASSERT(MayProceed());
6660 QM_TRY(QuotaManager::EnsureCreated());
6662 const LSRequestCommonParams& commonParams =
6663 mForPreload
6664 ? mParams.get_LSRequestPreloadDatastoreParams().commonParams()
6665 : mParams.get_LSRequestPrepareDatastoreParams().commonParams();
6667 const PrincipalInfo& storagePrincipalInfo =
6668 commonParams.storagePrincipalInfo();
6670 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
6671 mOriginMetadata = {QuotaManager::GetInfoForChrome(),
6672 PERSISTENCE_TYPE_DEFAULT};
6673 } else {
6674 MOZ_ASSERT(storagePrincipalInfo.type() ==
6675 PrincipalInfo::TContentPrincipalInfo);
6677 QM_TRY_UNWRAP(auto principalMetadata,
6678 QuotaManager::Get()->GetInfoFromValidatedPrincipalInfo(
6679 storagePrincipalInfo));
6681 mOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix);
6682 mOriginMetadata.mGroup = std::move(principalMetadata.mGroup);
6683 // XXX We can probably get rid of mMainThreadOrigin if we change
6684 // LSRequestBase::Dispatch to synchronously run LSRequestBase::StartRequest
6685 // through LSRequestBase::Run.
6686 mMainThreadOrigin = std::move(principalMetadata.mOrigin);
6687 mOriginMetadata.mStorageOrigin =
6688 std::move(principalMetadata.mStorageOrigin);
6689 mOriginMetadata.mIsPrivate = principalMetadata.mIsPrivate;
6690 mOriginMetadata.mPersistenceType = principalMetadata.mIsPrivate
6691 ? PERSISTENCE_TYPE_PRIVATE
6692 : PERSISTENCE_TYPE_DEFAULT;
6695 mState = State::Nesting;
6696 mNestedState = NestedState::CheckExistingOperations;
6698 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
6700 return NS_OK;
6703 nsresult PrepareDatastoreOp::CheckExistingOperations() {
6704 AssertIsOnOwningThread();
6705 MOZ_ASSERT(mState == State::Nesting);
6706 MOZ_ASSERT(mNestedState == NestedState::CheckExistingOperations);
6707 MOZ_ASSERT(gPrepareDatastoreOps);
6709 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6710 !MayProceed()) {
6711 return NS_ERROR_ABORT;
6714 const LSRequestCommonParams& commonParams =
6715 mForPreload
6716 ? mParams.get_LSRequestPreloadDatastoreParams().commonParams()
6717 : mParams.get_LSRequestPrepareDatastoreParams().commonParams();
6719 const PrincipalInfo& storagePrincipalInfo =
6720 commonParams.storagePrincipalInfo();
6722 nsCString originAttrSuffix;
6723 uint32_t privateBrowsingId;
6725 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
6726 privateBrowsingId = 0;
6727 } else {
6728 MOZ_ASSERT(storagePrincipalInfo.type() ==
6729 PrincipalInfo::TContentPrincipalInfo);
6731 const ContentPrincipalInfo& info =
6732 storagePrincipalInfo.get_ContentPrincipalInfo();
6733 const OriginAttributes& attrs = info.attrs();
6734 attrs.CreateSuffix(originAttrSuffix);
6736 privateBrowsingId = attrs.mPrivateBrowsingId;
6739 mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(
6740 originAttrSuffix, commonParams.originKey());
6741 MOZ_ASSERT(mArchivedOriginScope);
6743 // Normally it's safe to access member variables without a mutex because even
6744 // though we hop between threads, the variables are never accessed by multiple
6745 // threads at the same time.
6746 // However, the methods OriginIsKnown and Origin can be called at any time.
6747 // So we have to make sure the member variable is set on the same thread as
6748 // those methods are called.
6749 mOriginMetadata.mOrigin = mMainThreadOrigin;
6751 MOZ_ASSERT(OriginIsKnown());
6753 mPrivateBrowsingId = privateBrowsingId;
6755 mNestedState = NestedState::CheckClosingDatastore;
6757 // See if this PrepareDatastoreOp needs to wait.
6758 bool foundThis = false;
6759 for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0; index--) {
6760 const auto& existingOp = (*gPrepareDatastoreOps)[index - 1];
6762 if (existingOp == this) {
6763 foundThis = true;
6764 continue;
6767 if (foundThis && existingOp->Origin() == Origin()) {
6768 // Only one op can be delayed.
6769 MOZ_ASSERT(!existingOp->mDelayedOp);
6770 existingOp->mDelayedOp = this;
6772 return NS_OK;
6776 QM_TRY(MOZ_TO_RESULT(CheckClosingDatastoreInternal()));
6778 return NS_OK;
6781 nsresult PrepareDatastoreOp::CheckClosingDatastore() {
6782 AssertIsOnOwningThread();
6783 MOZ_ASSERT(mState == State::Nesting);
6784 MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore);
6786 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6787 !MayProceed()) {
6788 return NS_ERROR_ABORT;
6791 QM_TRY(MOZ_TO_RESULT(CheckClosingDatastoreInternal()));
6793 return NS_OK;
6796 nsresult PrepareDatastoreOp::CheckClosingDatastoreInternal() {
6797 AssertIsOnOwningThread();
6798 MOZ_ASSERT(mState == State::Nesting);
6799 MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore);
6800 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6801 MOZ_ASSERT(MayProceed());
6803 mNestedState = NestedState::PreparationPending;
6805 RefPtr<Datastore> datastore;
6806 if ((datastore = GetDatastore(Origin())) && datastore->IsClosed()) {
6807 datastore->WaitForConnectionToComplete(this);
6809 return NS_OK;
6812 QM_TRY(MOZ_TO_RESULT(BeginDatastorePreparationInternal()));
6814 return NS_OK;
6817 nsresult PrepareDatastoreOp::BeginDatastorePreparation() {
6818 AssertIsOnOwningThread();
6819 MOZ_ASSERT(mState == State::Nesting);
6820 MOZ_ASSERT(mNestedState == NestedState::PreparationPending);
6822 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6823 !MayProceed()) {
6824 return NS_ERROR_ABORT;
6827 QM_TRY(MOZ_TO_RESULT(BeginDatastorePreparationInternal()));
6829 return NS_OK;
6832 nsresult PrepareDatastoreOp::BeginDatastorePreparationInternal() {
6833 AssertIsOnOwningThread();
6834 MOZ_ASSERT(mState == State::Nesting);
6835 MOZ_ASSERT(mNestedState == NestedState::PreparationPending);
6836 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6837 MOZ_ASSERT(MayProceed());
6838 MOZ_ASSERT(OriginIsKnown());
6839 MOZ_ASSERT(!mDirectoryLock);
6841 if ((mDatastore = GetDatastore(Origin()))) {
6842 MOZ_ASSERT(!mDatastore->IsClosed());
6844 mDatastore->NoteLivePrepareDatastoreOp(this);
6846 FinishNesting();
6848 return NS_OK;
6851 QuotaManager* quotaManager = QuotaManager::Get();
6852 MOZ_ASSERT(quotaManager);
6854 // Open directory
6855 mPendingDirectoryLock = quotaManager->CreateDirectoryLock(
6856 mOriginMetadata.mPersistenceType, mOriginMetadata,
6857 mozilla::dom::quota::Client::LS,
6858 /* aExclusive */ false);
6860 mNestedState = NestedState::DirectoryOpenPending;
6863 // Pin the directory lock, because Acquire might clear mPendingDirectoryLock
6864 // during the Acquire call.
6865 RefPtr pinnedDirectoryLock = mPendingDirectoryLock;
6866 pinnedDirectoryLock->Acquire(this);
6869 return NS_OK;
6872 void PrepareDatastoreOp::SendToIOThread() {
6873 AssertIsOnOwningThread();
6874 MOZ_ASSERT(mState == State::Nesting);
6875 MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
6876 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6877 MOZ_ASSERT(MayProceed());
6879 // Skip all disk related stuff and transition to SendingReadyMessage if we
6880 // are preparing a datastore for private browsing.
6881 // Note that we do use a directory lock for private browsing even though we
6882 // don't do any stuff on disk. The thing is that without a directory lock,
6883 // quota manager wouldn't call AbortOperationsForLocks for our private
6884 // browsing origin when a clear origin operation is requested.
6885 // AbortOperationsForLocks requests all databases to close and the datastore
6886 // is destroyed in the end. Any following LocalStorage API call will trigger
6887 // preparation of a new (empty) datastore.
6888 if (mPrivateBrowsingId) {
6889 FinishNesting();
6891 return;
6894 QuotaManager* quotaManager = QuotaManager::Get();
6895 MOZ_ASSERT(quotaManager);
6897 // Must set this before dispatching otherwise we will race with the IO thread.
6898 mNestedState = NestedState::DatabaseWorkOpen;
6900 MOZ_ALWAYS_SUCCEEDS(
6901 quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
6904 nsresult PrepareDatastoreOp::DatabaseWork() {
6905 AssertIsOnIOThread();
6906 MOZ_ASSERT(mArchivedOriginScope);
6907 MOZ_ASSERT(mUsage == 0);
6908 MOZ_ASSERT(mState == State::Nesting);
6909 MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen);
6911 const auto innerFunc = [&](const auto&) -> nsresult {
6912 // XXX This function is too long, refactor it into helper functions for
6913 // readability.
6915 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
6916 !MayProceedOnNonOwningThread()) {
6917 return NS_ERROR_ABORT;
6920 QuotaManager* quotaManager = QuotaManager::Get();
6921 MOZ_ASSERT(quotaManager);
6923 // This must be called before EnsureTemporaryStorageIsInitialized.
6924 QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized()));
6926 // This ensures that usages for existings origin directories are cached in
6927 // memory.
6928 QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized()));
6930 const UsageInfo usageInfo = quotaManager->GetUsageForClient(
6931 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata,
6932 mozilla::dom::quota::Client::LS);
6934 const bool hasUsage = usageInfo.DatabaseUsage().isSome();
6935 MOZ_ASSERT(usageInfo.FileUsage().isNothing());
6937 if (!gArchivedOrigins) {
6938 QM_TRY(MOZ_TO_RESULT(LoadArchivedOrigins()));
6939 MOZ_ASSERT(gArchivedOrigins);
6942 bool hasDataForMigration =
6943 mArchivedOriginScope->HasMatches(gArchivedOrigins);
6945 // If there's nothing to preload (except the case when we want to migrate
6946 // data during preloading), then we can finish the operation without
6947 // creating a datastore in GetResponse (GetResponse won't create a datastore
6948 // if mDatatabaseNotAvailable and mForPreload are both true).
6949 if (mForPreload && !hasUsage && !hasDataForMigration) {
6950 return DatabaseNotAvailable();
6953 // The origin directory doesn't need to be created when we don't have data
6954 // for migration. It will be created on the connection thread in
6955 // Connection::EnsureStorageConnection.
6956 // However, origin quota must be initialized, GetQuotaObject in GetResponse
6957 // would fail otherwise.
6958 QM_TRY_INSPECT(
6959 const auto& directoryEntry,
6960 ([hasDataForMigration, &quotaManager,
6961 this]() -> mozilla::Result<nsCOMPtr<nsIFile>, nsresult> {
6962 if (hasDataForMigration) {
6963 QM_TRY_RETURN(quotaManager
6964 ->EnsureTemporaryOriginIsInitialized(
6965 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata)
6966 .map([](const auto& res) { return res.first; }));
6969 MOZ_ASSERT(mOriginMetadata.mPersistenceType ==
6970 PERSISTENCE_TYPE_DEFAULT);
6972 QM_TRY_UNWRAP(auto directoryEntry,
6973 quotaManager->GetOriginDirectory(mOriginMetadata));
6975 quotaManager->EnsureQuotaForOrigin(mOriginMetadata);
6977 return directoryEntry;
6978 }()));
6980 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(
6981 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME))));
6983 QM_TRY_INSPECT(
6984 const auto& directoryPath,
6985 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directoryEntry, GetPath));
6987 // The ls directory doesn't need to be created when we don't have data for
6988 // migration. It will be created on the connection thread in
6989 // Connection::EnsureStorageConnection.
6990 QM_TRY(MOZ_TO_RESULT(
6991 EnsureDirectoryEntry(directoryEntry,
6992 /* aCreateIfNotExists */ hasDataForMigration,
6993 /* aIsDirectory */ true)));
6995 QM_TRY(MOZ_TO_RESULT(directoryEntry->Append(kDataFileName)));
6997 QM_TRY(MOZ_TO_RESULT(directoryEntry->GetPath(mDatabaseFilePath)));
6999 // The database doesn't need to be created when we don't have data for
7000 // migration. It will be created on the connection thread in
7001 // Connection::EnsureStorageConnection.
7002 bool alreadyExisted;
7003 QM_TRY(MOZ_TO_RESULT(
7004 EnsureDirectoryEntry(directoryEntry,
7005 /* aCreateIfNotExists */ hasDataForMigration,
7006 /* aIsDirectory */ false, &alreadyExisted)));
7008 if (alreadyExisted) {
7009 // The database does exist.
7010 MOZ_ASSERT(hasUsage);
7012 // XXX Change type of mUsage to UsageInfo or DatabaseUsageType.
7013 mUsage = usageInfo.DatabaseUsage().valueOr(0);
7014 } else {
7015 // The database doesn't exist.
7016 MOZ_ASSERT(!hasUsage);
7018 if (!hasDataForMigration) {
7019 // The database doesn't exist and we don't have data for migration.
7020 // Finish the operation, but create an empty datastore in GetResponse
7021 // (GetResponse will create an empty datastore if mDatabaseNotAvailable
7022 // is true and mForPreload is false).
7023 return DatabaseNotAvailable();
7027 // We initialized mDatabaseFilePath and mUsage, GetQuotaObject can now be
7028 // called.
7029 const RefPtr<QuotaObject> quotaObject = GetQuotaObject();
7031 QM_TRY(OkIf(quotaObject), Err(NS_ERROR_FAILURE));
7033 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(directoryPath));
7035 QM_TRY_INSPECT(const auto& usageJournalFile,
7036 GetUsageJournalFile(directoryPath));
7038 QM_TRY_INSPECT(
7039 const auto& connection,
7040 (CreateStorageConnectionWithRecovery(
7041 *directoryEntry, *usageFile, Origin(), [&quotaObject, this] {
7042 // This is called when the usage file was removed or we notice
7043 // that the usage file doesn't exist anymore. Adjust the usage
7044 // accordingly.
7046 MOZ_ALWAYS_TRUE(
7047 quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
7049 mUsage = 0;
7050 })));
7052 QM_TRY(MOZ_TO_RESULT(VerifyDatabaseInformation(connection)));
7054 if (hasDataForMigration) {
7055 MOZ_ASSERT(mUsage == 0);
7058 QM_TRY_INSPECT(const auto& archiveFile,
7059 GetArchiveFile(quotaManager->GetStoragePath()));
7061 auto autoArchiveDatabaseAttacher =
7062 AutoDatabaseAttacher(connection, archiveFile, "archive"_ns);
7064 QM_TRY(MOZ_TO_RESULT(autoArchiveDatabaseAttacher.Attach()));
7066 QM_TRY_INSPECT(const int64_t& newUsage,
7067 GetUsage(*connection, mArchivedOriginScope.get()));
7069 QM_TRY(
7070 OkIf(quotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)),
7071 NS_ERROR_FILE_NO_DEVICE_SPACE);
7073 auto autoUpdateSize = MakeScopeExit([&quotaObject] {
7074 MOZ_ALWAYS_TRUE(
7075 quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
7078 mozStorageTransaction transaction(
7079 connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
7081 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
7084 nsCOMPtr<mozIStorageFunction> function = new CompressFunction();
7086 QM_TRY(MOZ_TO_RESULT(
7087 connection->CreateFunction("compress"_ns, 1, function)));
7089 function = new CompressionTypeFunction();
7091 QM_TRY(MOZ_TO_RESULT(
7092 connection->CreateFunction("compressionType"_ns, 1, function)));
7094 QM_TRY_INSPECT(
7095 const auto& stmt,
7096 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7097 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
7098 "INSERT INTO data (key, utf16_length, conversion_type, "
7099 "compression_type, value) "
7100 "SELECT key, utf16Length(value), :conversionType, "
7101 "compressionType(value), compress(value)"
7102 "FROM webappsstore2 "
7103 "WHERE originKey = :originKey "
7104 "AND originAttributes = :originAttributes;"_ns));
7106 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName(
7107 "conversionType"_ns,
7108 static_cast<int32_t>(LSValue::ConversionType::UTF16_UTF8))));
7110 QM_TRY(MOZ_TO_RESULT(mArchivedOriginScope->BindToStatement(stmt)));
7112 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
7114 QM_TRY(MOZ_TO_RESULT(connection->RemoveFunction("compress"_ns)));
7116 QM_TRY(
7117 MOZ_TO_RESULT(connection->RemoveFunction("compressionType"_ns)));
7121 QM_TRY_INSPECT(
7122 const auto& stmt,
7123 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7124 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
7125 "UPDATE database SET usage = :usage;"_ns));
7127 QM_TRY(MOZ_TO_RESULT(stmt->BindInt64ByName("usage"_ns, newUsage)));
7129 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
7133 QM_TRY_INSPECT(
7134 const auto& stmt,
7135 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7136 nsCOMPtr<mozIStorageStatement>, connection, CreateStatement,
7137 "DELETE FROM webappsstore2 "
7138 "WHERE originKey = :originKey "
7139 "AND originAttributes = :originAttributes;"_ns));
7141 QM_TRY(MOZ_TO_RESULT(mArchivedOriginScope->BindToStatement(stmt)));
7142 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
7145 QM_TRY(MOZ_TO_RESULT(
7146 UpdateUsageFile(usageFile, usageJournalFile, newUsage)));
7147 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
7149 autoUpdateSize.release();
7151 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false)));
7153 mUsage = newUsage;
7155 QM_TRY(MOZ_TO_RESULT(autoArchiveDatabaseAttacher.Detach()));
7158 MOZ_ASSERT(gArchivedOrigins);
7159 MOZ_ASSERT(mArchivedOriginScope->HasMatches(gArchivedOrigins));
7160 mArchivedOriginScope->RemoveMatches(gArchivedOrigins);
7163 nsCOMPtr<mozIStorageConnection> shadowConnection;
7164 if (!gInitializedShadowStorage) {
7165 QM_TRY_UNWRAP(shadowConnection,
7166 CreateShadowStorageConnection(quotaManager->GetBasePath()));
7168 gInitializedShadowStorage = true;
7171 // Must close connections before dispatching otherwise we might race with
7172 // the connection thread which needs to open the same databases.
7173 MOZ_ALWAYS_SUCCEEDS(connection->Close());
7175 if (shadowConnection) {
7176 MOZ_ALWAYS_SUCCEEDS(shadowConnection->Close());
7179 // Must set this before dispatching otherwise we will race with the owning
7180 // thread.
7181 mNestedState = NestedState::BeginLoadData;
7183 QM_TRY(
7184 MOZ_TO_RESULT(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)));
7186 return NS_OK;
7189 return ExecuteOriginInitialization(
7190 mOriginMetadata.mOrigin, LSOriginInitialization::Datastore,
7191 "dom::localstorage::FirstOriginInitializationAttempt::Datastore"_ns,
7192 innerFunc);
7195 nsresult PrepareDatastoreOp::DatabaseNotAvailable() {
7196 AssertIsOnIOThread();
7197 MOZ_ASSERT(mState == State::Nesting);
7198 MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen);
7200 mDatabaseNotAvailable = true;
7202 nsresult rv = FinishNestingOnNonOwningThread();
7203 if (NS_WARN_IF(NS_FAILED(rv))) {
7204 return rv;
7207 return NS_OK;
7210 nsresult PrepareDatastoreOp::EnsureDirectoryEntry(nsIFile* aEntry,
7211 bool aCreateIfNotExists,
7212 bool aIsDirectory,
7213 bool* aAlreadyExisted) {
7214 AssertIsOnIOThread();
7215 MOZ_ASSERT(aEntry);
7217 QM_TRY_INSPECT(const bool& exists,
7218 MOZ_TO_RESULT_INVOKE_MEMBER(aEntry, Exists));
7220 if (!exists) {
7221 if (!aCreateIfNotExists) {
7222 if (aAlreadyExisted) {
7223 *aAlreadyExisted = false;
7225 return NS_OK;
7228 if (aIsDirectory) {
7229 QM_TRY(MOZ_TO_RESULT(aEntry->Create(nsIFile::DIRECTORY_TYPE, 0755)));
7232 #ifdef DEBUG
7233 else {
7234 bool isDirectory;
7235 MOZ_ASSERT(NS_SUCCEEDED(aEntry->IsDirectory(&isDirectory)));
7236 MOZ_ASSERT(isDirectory == aIsDirectory);
7238 #endif
7240 if (aAlreadyExisted) {
7241 *aAlreadyExisted = exists;
7243 return NS_OK;
7246 nsresult PrepareDatastoreOp::VerifyDatabaseInformation(
7247 mozIStorageConnection* aConnection) {
7248 AssertIsOnIOThread();
7249 MOZ_ASSERT(aConnection);
7251 QM_TRY_INSPECT(const auto& stmt,
7252 CreateAndExecuteSingleStepStatement<
7253 SingleStepResult::ReturnNullIfNoResult>(
7254 *aConnection, "SELECT origin FROM database"_ns));
7256 QM_TRY(OkIf(stmt), NS_ERROR_FILE_CORRUPTED);
7258 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7259 nsCString, stmt, GetUTF8String, 0));
7261 QM_TRY(OkIf(QuotaManager::AreOriginsEqualOnDisk(Origin(), origin)),
7262 NS_ERROR_FILE_CORRUPTED);
7264 return NS_OK;
7267 already_AddRefed<QuotaObject> PrepareDatastoreOp::GetQuotaObject() {
7268 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
7269 MOZ_ASSERT(!mOriginMetadata.mGroup.IsEmpty());
7270 MOZ_ASSERT(OriginIsKnown());
7271 MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
7273 QuotaManager* quotaManager = QuotaManager::Get();
7274 MOZ_ASSERT(quotaManager);
7276 RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject(
7277 PERSISTENCE_TYPE_DEFAULT, mOriginMetadata,
7278 mozilla::dom::quota::Client::LS, mDatabaseFilePath, mUsage);
7280 if (!quotaObject) {
7281 LS_WARNING("Failed to get quota object for group (%s) and origin (%s)!",
7282 mOriginMetadata.mGroup.get(), Origin().get());
7285 return quotaObject.forget();
7288 nsresult PrepareDatastoreOp::BeginLoadData() {
7289 AssertIsOnOwningThread();
7290 MOZ_ASSERT(mState == State::Nesting);
7291 MOZ_ASSERT(mNestedState == NestedState::BeginLoadData);
7292 MOZ_ASSERT(!mConnection);
7294 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7295 !MayProceed()) {
7296 return NS_ERROR_ABORT;
7299 if (!gConnectionThread) {
7300 gConnectionThread = new ConnectionThread();
7303 mConnection = gConnectionThread->CreateConnection(
7304 mOriginMetadata, std::move(mArchivedOriginScope),
7305 /* aDatabaseWasNotAvailable */ false);
7306 MOZ_ASSERT(mConnection);
7308 // Must set this before dispatching otherwise we will race with the
7309 // connection thread.
7310 mNestedState = NestedState::DatabaseWorkLoadData;
7312 // Can't assign to mLoadDataOp directly since that's a weak reference and
7313 // LoadDataOp is reference counted.
7314 RefPtr<LoadDataOp> loadDataOp = new LoadDataOp(this);
7316 // This add refs loadDataOp.
7317 mConnection->Dispatch(loadDataOp);
7319 // This is cleared in LoadDataOp::Cleanup() before the load data op is
7320 // destroyed.
7321 mLoadDataOp = loadDataOp;
7323 return NS_OK;
7326 void PrepareDatastoreOp::FinishNesting() {
7327 AssertIsOnOwningThread();
7328 MOZ_ASSERT(mState == State::Nesting);
7330 // The caller holds a strong reference to us, no need for a self reference
7331 // before calling Run().
7333 mState = State::SendingReadyMessage;
7334 mNestedState = NestedState::AfterNesting;
7336 MOZ_ALWAYS_SUCCEEDS(Run());
7339 nsresult PrepareDatastoreOp::FinishNestingOnNonOwningThread() {
7340 MOZ_ASSERT(!IsOnOwningThread());
7341 MOZ_ASSERT(mState == State::Nesting);
7343 // Must set mState before dispatching otherwise we will race with the owning
7344 // thread.
7345 mState = State::SendingReadyMessage;
7346 mNestedState = NestedState::AfterNesting;
7348 QM_TRY(
7349 MOZ_TO_RESULT(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)));
7351 return NS_OK;
7354 nsresult PrepareDatastoreOp::NestedRun() {
7355 nsresult rv;
7357 switch (mNestedState) {
7358 case NestedState::CheckExistingOperations:
7359 rv = CheckExistingOperations();
7360 break;
7362 case NestedState::CheckClosingDatastore:
7363 rv = CheckClosingDatastore();
7364 break;
7366 case NestedState::PreparationPending:
7367 rv = BeginDatastorePreparation();
7368 break;
7370 case NestedState::DatabaseWorkOpen:
7371 rv = DatabaseWork();
7372 break;
7374 case NestedState::BeginLoadData:
7375 rv = BeginLoadData();
7376 break;
7378 default:
7379 MOZ_CRASH("Bad state!");
7382 if (NS_WARN_IF(NS_FAILED(rv))) {
7383 mNestedState = NestedState::AfterNesting;
7385 return rv;
7388 return NS_OK;
7391 void PrepareDatastoreOp::GetResponse(LSRequestResponse& aResponse) {
7392 AssertIsOnOwningThread();
7393 MOZ_ASSERT(mState == State::SendingResults);
7394 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
7395 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7396 MOZ_ASSERT(MayProceed());
7398 // A datastore is not created when we are just trying to preload data and
7399 // there's no database file.
7400 if (mDatabaseNotAvailable && mForPreload) {
7401 LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
7403 aResponse = preloadDatastoreResponse;
7405 return;
7408 if (!mDatastore) {
7409 MOZ_ASSERT(mUsage == mDEBUGUsage);
7411 RefPtr<QuotaObject> quotaObject;
7413 if (mPrivateBrowsingId == 0) {
7414 if (!mConnection) {
7415 // This can happen when there's no database file.
7416 MOZ_ASSERT(mDatabaseNotAvailable);
7418 // Even though there's no database file, we need to create a connection
7419 // and pass it to datastore.
7420 if (!gConnectionThread) {
7421 gConnectionThread = new ConnectionThread();
7424 mConnection = gConnectionThread->CreateConnection(
7425 mOriginMetadata, std::move(mArchivedOriginScope),
7426 /* aDatabaseWasNotAvailable */ true);
7427 MOZ_ASSERT(mConnection);
7430 quotaObject = GetQuotaObject();
7431 if (!quotaObject) {
7432 aResponse = NS_ERROR_FAILURE;
7433 return;
7437 mDatastore = new Datastore(
7438 mOriginMetadata, mPrivateBrowsingId, mUsage, mSizeOfKeys, mSizeOfItems,
7439 std::move(mDirectoryLock), std::move(mConnection),
7440 std::move(quotaObject), mValues, std::move(mOrderedItems));
7442 mDatastore->NoteLivePrepareDatastoreOp(this);
7444 if (!gDatastores) {
7445 gDatastores = new DatastoreHashtable();
7448 MOZ_ASSERT(!gDatastores->Contains(Origin()));
7449 gDatastores->InsertOrUpdate(Origin(),
7450 WrapMovingNotNullUnchecked(mDatastore));
7453 if (mPrivateBrowsingId && !mInvalidated) {
7454 if (!gPrivateDatastores) {
7455 gPrivateDatastores = MakeUnique<PrivateDatastoreHashtable>();
7458 gPrivateDatastores->LookupOrInsertWith(Origin(), [&] {
7459 auto privateDatastore =
7460 MakeUnique<PrivateDatastore>(WrapMovingNotNull(mDatastore));
7462 mPrivateDatastoreRegistered.Flip();
7464 return privateDatastore;
7468 mDatastoreId = ++gLastDatastoreId;
7470 if (!gPreparedDatastores) {
7471 gPreparedDatastores = new PreparedDatastoreHashtable();
7473 const auto& preparedDatastore = gPreparedDatastores->InsertOrUpdate(
7474 mDatastoreId, MakeUnique<PreparedDatastore>(
7475 mDatastore, mContentParentId, Origin(), mDatastoreId,
7476 /* aForPreload */ mForPreload));
7478 if (mInvalidated) {
7479 preparedDatastore->Invalidate();
7482 mPreparedDatastoreRegistered.Flip();
7484 if (mForPreload) {
7485 LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
7487 aResponse = preloadDatastoreResponse;
7488 } else {
7489 LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
7490 prepareDatastoreResponse.datastoreId() = mDatastoreId;
7492 aResponse = prepareDatastoreResponse;
7496 void PrepareDatastoreOp::Cleanup() {
7497 AssertIsOnOwningThread();
7499 if (mDatastore) {
7500 MOZ_ASSERT(!mDirectoryLock);
7501 MOZ_ASSERT(!mConnection);
7503 if (NS_FAILED(ResultCode())) {
7504 if (mPrivateDatastoreRegistered) {
7505 MOZ_ASSERT(gPrivateDatastores);
7506 DebugOnly<bool> removed = gPrivateDatastores->Remove(Origin());
7507 MOZ_ASSERT(removed);
7509 if (!gPrivateDatastores->Count()) {
7510 gPrivateDatastores = nullptr;
7514 if (mPreparedDatastoreRegistered) {
7515 // Just in case we failed to send datastoreId to the child, we need to
7516 // destroy prepared datastore, otherwise it won't be destroyed until
7517 // the timer fires (after 20 seconds).
7518 MOZ_ASSERT(gPreparedDatastores);
7519 MOZ_ASSERT(mDatastoreId > 0);
7520 DebugOnly<bool> removed = gPreparedDatastores->Remove(mDatastoreId);
7521 MOZ_ASSERT(removed);
7523 if (!gPreparedDatastores->Count()) {
7524 gPreparedDatastores = nullptr;
7529 // Make sure to release the datastore on this thread.
7531 mDatastore->NoteFinishedPrepareDatastoreOp(this);
7533 mDatastore = nullptr;
7535 CleanupMetadata();
7536 } else if (mConnection) {
7537 // If we have a connection then the operation must have failed and there
7538 // must be a directory lock too.
7539 MOZ_ASSERT(NS_FAILED(ResultCode()));
7540 MOZ_ASSERT(mDirectoryLock);
7542 // We must close the connection on the connection thread before releasing
7543 // it on this thread. The directory lock can't be released either.
7544 nsCOMPtr<nsIRunnable> callback =
7545 NewRunnableMethod("dom::OpenDatabaseOp::ConnectionClosedCallback", this,
7546 &PrepareDatastoreOp::ConnectionClosedCallback);
7548 mConnection->Close(callback);
7549 } else {
7550 // If we don't have a connection, but we do have a directory lock then the
7551 // operation must have failed or we were preloading a datastore and there
7552 // was no physical database on disk.
7553 MOZ_ASSERT_IF(mDirectoryLock,
7554 NS_FAILED(ResultCode()) || mDatabaseNotAvailable);
7556 // There's no connection, so it's safe to release the directory lock and
7557 // unregister itself from the array.
7559 mDirectoryLock = nullptr;
7561 CleanupMetadata();
7565 void PrepareDatastoreOp::ConnectionClosedCallback() {
7566 AssertIsOnOwningThread();
7567 MOZ_ASSERT(NS_FAILED(ResultCode()));
7568 MOZ_ASSERT(mDirectoryLock);
7569 MOZ_ASSERT(mConnection);
7571 mConnection = nullptr;
7572 mDirectoryLock = nullptr;
7574 CleanupMetadata();
7577 void PrepareDatastoreOp::CleanupMetadata() {
7578 AssertIsOnOwningThread();
7580 if (mDelayedOp) {
7581 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget()));
7584 MOZ_ASSERT(gPrepareDatastoreOps);
7585 gPrepareDatastoreOps->RemoveElement(this);
7587 QuotaManager::MaybeRecordQuotaClientShutdownStep(
7588 quota::Client::LS, "PrepareDatastoreOp completed"_ns);
7590 if (gPrepareDatastoreOps->IsEmpty()) {
7591 gPrepareDatastoreOps = nullptr;
7595 NS_IMPL_ISUPPORTS_INHERITED0(PrepareDatastoreOp, LSRequestBase)
7597 void PrepareDatastoreOp::ActorDestroy(ActorDestroyReason aWhy) {
7598 AssertIsOnOwningThread();
7600 LSRequestBase::ActorDestroy(aWhy);
7602 if (mLoadDataOp) {
7603 mLoadDataOp->NoteComplete();
7607 void PrepareDatastoreOp::DirectoryLockAcquired(DirectoryLock* aLock) {
7608 AssertIsOnOwningThread();
7609 MOZ_ASSERT(mState == State::Nesting);
7610 MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
7611 MOZ_ASSERT(!mDirectoryLock);
7613 mPendingDirectoryLock = nullptr;
7615 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7616 !MayProceed()) {
7617 MaybeSetFailureCode(NS_ERROR_ABORT);
7619 FinishNesting();
7621 return;
7624 mDirectoryLock = aLock;
7626 SendToIOThread();
7629 void PrepareDatastoreOp::DirectoryLockFailed() {
7630 AssertIsOnOwningThread();
7631 MOZ_ASSERT(mState == State::Nesting);
7632 MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
7633 MOZ_ASSERT(!mDirectoryLock);
7635 mPendingDirectoryLock = nullptr;
7637 MaybeSetFailureCode(NS_ERROR_FAILURE);
7639 FinishNesting();
7642 nsresult PrepareDatastoreOp::LoadDataOp::DoDatastoreWork() {
7643 AssertIsOnGlobalConnectionThread();
7644 MOZ_ASSERT(mConnection);
7645 MOZ_ASSERT(mPrepareDatastoreOp);
7646 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
7647 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
7648 NestedState::DatabaseWorkLoadData);
7650 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
7651 !MayProceedOnNonOwningThread()) {
7652 return NS_ERROR_ABORT;
7655 QM_TRY_INSPECT(
7656 const auto& stmt,
7657 mConnection->BorrowCachedStatement(
7658 "SELECT key, utf16_length, conversion_type, compression_type, value "
7659 "FROM data;"_ns));
7661 QM_TRY(quota::CollectWhileHasResult(
7662 *stmt, [this](auto& stmt) -> mozilla::Result<Ok, nsresult> {
7663 QM_TRY_UNWRAP(auto key, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7664 nsString, stmt, GetString, 0));
7666 LSValue value;
7667 QM_TRY(MOZ_TO_RESULT(value.InitFromStatement(&stmt, 1)));
7669 mPrepareDatastoreOp->mValues.InsertOrUpdate(key, value);
7670 mPrepareDatastoreOp->mSizeOfKeys += key.Length();
7671 mPrepareDatastoreOp->mSizeOfItems += key.Length() + value.Length();
7672 #ifdef DEBUG
7673 mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.UTF16Length();
7674 #endif
7676 auto item = mPrepareDatastoreOp->mOrderedItems.AppendElement();
7677 item->key() = std::move(key);
7678 item->value() = std::move(value);
7680 return Ok{};
7681 }));
7683 return NS_OK;
7686 void PrepareDatastoreOp::LoadDataOp::OnSuccess() {
7687 AssertIsOnOwningThread();
7688 MOZ_ASSERT(mPrepareDatastoreOp);
7689 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
7690 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
7691 NestedState::DatabaseWorkLoadData);
7692 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this);
7694 mPrepareDatastoreOp->FinishNesting();
7697 void PrepareDatastoreOp::LoadDataOp::OnFailure(nsresult aResultCode) {
7698 AssertIsOnOwningThread();
7699 MOZ_ASSERT(mPrepareDatastoreOp);
7700 MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
7701 MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
7702 NestedState::DatabaseWorkLoadData);
7703 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this);
7705 mPrepareDatastoreOp->SetFailureCode(aResultCode);
7707 mPrepareDatastoreOp->FinishNesting();
7710 void PrepareDatastoreOp::LoadDataOp::Cleanup() {
7711 AssertIsOnOwningThread();
7712 MOZ_ASSERT(mPrepareDatastoreOp);
7713 MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this);
7715 mPrepareDatastoreOp->mLoadDataOp = nullptr;
7716 mPrepareDatastoreOp = nullptr;
7718 ConnectionDatastoreOperationBase::Cleanup();
7721 NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressFunction, mozIStorageFunction)
7723 NS_IMETHODIMP
7724 PrepareDatastoreOp::CompressFunction::OnFunctionCall(
7725 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
7726 AssertIsOnIOThread();
7727 MOZ_ASSERT(aFunctionArguments);
7728 MOZ_ASSERT(aResult);
7730 #ifdef DEBUG
7732 uint32_t argCount;
7733 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount));
7734 MOZ_ASSERT(argCount == 1);
7736 int32_t type;
7737 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetTypeOfIndex(0, &type));
7738 MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT);
7740 #endif
7742 QM_TRY_INSPECT(const auto& value,
7743 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7744 nsCString, aFunctionArguments, GetUTF8String, 0));
7746 nsCString compressed;
7747 QM_TRY(OkIf(SnappyCompress(value, compressed)), NS_ERROR_OUT_OF_MEMORY);
7749 const nsCString& buffer = compressed.IsVoid() ? value : compressed;
7751 // mozStorage transforms empty blobs into null values, but our database
7752 // schema doesn't allow null values. We can workaround this by storing
7753 // empty buffers as UTF8 text (SQLite supports the type affinity, so the type
7754 // of the column is not fixed).
7755 nsCOMPtr<nsIVariant> result;
7756 if (0u == buffer.Length()) { // Otherwise empty string becomes null
7757 result = new storage::UTF8TextVariant(buffer);
7758 } else {
7759 result = new storage::BlobVariant(std::make_pair(
7760 static_cast<const void*>(buffer.get()), int(buffer.Length())));
7763 result.forget(aResult);
7764 return NS_OK;
7767 NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressionTypeFunction,
7768 mozIStorageFunction)
7770 NS_IMETHODIMP
7771 PrepareDatastoreOp::CompressionTypeFunction::OnFunctionCall(
7772 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
7773 AssertIsOnIOThread();
7774 MOZ_ASSERT(aFunctionArguments);
7775 MOZ_ASSERT(aResult);
7777 #ifdef DEBUG
7779 uint32_t argCount;
7780 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount));
7781 MOZ_ASSERT(argCount == 1);
7783 int32_t type;
7784 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetTypeOfIndex(0, &type));
7785 MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT);
7787 #endif
7789 QM_TRY_INSPECT(const auto& value,
7790 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7791 nsCString, aFunctionArguments, GetUTF8String, 0));
7793 nsCString compressed;
7794 QM_TRY(OkIf(SnappyCompress(value, compressed)), NS_ERROR_OUT_OF_MEMORY);
7796 const int32_t compression = static_cast<int32_t>(
7797 compressed.IsVoid() ? LSValue::CompressionType::UNCOMPRESSED
7798 : LSValue::CompressionType::SNAPPY);
7800 nsCOMPtr<nsIVariant> result = new storage::IntegerVariant(compression);
7802 result.forget(aResult);
7803 return NS_OK;
7806 /*******************************************************************************
7807 * PrepareObserverOp
7808 ******************************************************************************/
7810 PrepareObserverOp::PrepareObserverOp(
7811 const LSRequestParams& aParams,
7812 const Maybe<ContentParentId>& aContentParentId)
7813 : LSRequestBase(aParams, aContentParentId) {
7814 MOZ_ASSERT(aParams.type() ==
7815 LSRequestParams::TLSRequestPrepareObserverParams);
7818 nsresult PrepareObserverOp::Start() {
7819 AssertIsOnOwningThread();
7820 MOZ_ASSERT(mState == State::StartingRequest);
7821 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7822 MOZ_ASSERT(MayProceed());
7824 const LSRequestPrepareObserverParams params =
7825 mParams.get_LSRequestPrepareObserverParams();
7827 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo();
7829 if (storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
7830 mOrigin = QuotaManager::GetOriginForChrome();
7831 } else {
7832 MOZ_ASSERT(storagePrincipalInfo.type() ==
7833 PrincipalInfo::TContentPrincipalInfo);
7835 mOrigin =
7836 QuotaManager::GetOriginFromValidatedPrincipalInfo(storagePrincipalInfo);
7839 mState = State::SendingReadyMessage;
7840 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
7842 return NS_OK;
7845 void PrepareObserverOp::GetResponse(LSRequestResponse& aResponse) {
7846 AssertIsOnOwningThread();
7847 MOZ_ASSERT(mState == State::SendingResults);
7848 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
7849 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7850 MOZ_ASSERT(MayProceed());
7852 uint64_t observerId = ++gLastObserverId;
7854 RefPtr<Observer> observer = new Observer(mOrigin);
7856 if (!gPreparedObsevers) {
7857 gPreparedObsevers = new PreparedObserverHashtable();
7859 gPreparedObsevers->InsertOrUpdate(observerId, std::move(observer));
7861 LSRequestPrepareObserverResponse prepareObserverResponse;
7862 prepareObserverResponse.observerId() = observerId;
7864 aResponse = prepareObserverResponse;
7867 /*******************************************************************************
7868 + * LSSimpleRequestBase
7870 ******************************************************************************/
7872 LSSimpleRequestBase::LSSimpleRequestBase(
7873 const LSSimpleRequestParams& aParams,
7874 const Maybe<ContentParentId>& aContentParentId)
7875 : mParams(aParams),
7876 mContentParentId(aContentParentId),
7877 mState(State::Initial) {}
7879 LSSimpleRequestBase::~LSSimpleRequestBase() {
7880 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
7881 mState == State::Initial || mState == State::Completed);
7884 void LSSimpleRequestBase::Dispatch() {
7885 AssertIsOnOwningThread();
7887 mState = State::StartingRequest;
7889 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
7892 bool LSSimpleRequestBase::VerifyRequestParams() {
7893 AssertIsOnBackgroundThread();
7895 MOZ_ASSERT(mParams.type() != LSSimpleRequestParams::T__None);
7897 switch (mParams.type()) {
7898 case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: {
7899 const LSSimpleRequestPreloadedParams& params =
7900 mParams.get_LSSimpleRequestPreloadedParams();
7902 if (NS_WARN_IF(!VerifyPrincipalInfo(
7903 params.principalInfo(), params.storagePrincipalInfo(), false))) {
7904 return false;
7907 break;
7910 case LSSimpleRequestParams::TLSSimpleRequestGetStateParams: {
7911 const LSSimpleRequestGetStateParams& params =
7912 mParams.get_LSSimpleRequestGetStateParams();
7914 if (NS_WARN_IF(!VerifyPrincipalInfo(
7915 params.principalInfo(), params.storagePrincipalInfo(), false))) {
7916 return false;
7919 break;
7922 default:
7923 MOZ_CRASH("Should never get here!");
7926 return true;
7929 nsresult LSSimpleRequestBase::StartRequest() {
7930 AssertIsOnOwningThread();
7931 MOZ_ASSERT(mState == State::StartingRequest);
7933 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7934 !MayProceed()) {
7935 return NS_ERROR_ABORT;
7938 #ifdef DEBUG
7939 // Always verify parameters in DEBUG builds!
7940 bool trustParams = false;
7941 #else
7942 bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
7943 #endif
7945 if (!trustParams && NS_WARN_IF(!VerifyRequestParams())) {
7946 return NS_ERROR_FAILURE;
7949 QM_TRY(MOZ_TO_RESULT(Start()));
7951 return NS_OK;
7954 void LSSimpleRequestBase::SendResults() {
7955 AssertIsOnOwningThread();
7956 MOZ_ASSERT(mState == State::SendingResults);
7958 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7959 !MayProceed()) {
7960 MaybeSetFailureCode(NS_ERROR_ABORT);
7963 if (MayProceed()) {
7964 LSSimpleRequestResponse response;
7966 if (NS_SUCCEEDED(ResultCode())) {
7967 GetResponse(response);
7968 } else {
7969 response = ResultCode();
7972 Unused << PBackgroundLSSimpleRequestParent::Send__delete__(this, response);
7975 mState = State::Completed;
7978 NS_IMETHODIMP
7979 LSSimpleRequestBase::Run() {
7980 nsresult rv;
7982 switch (mState) {
7983 case State::StartingRequest:
7984 rv = StartRequest();
7985 break;
7987 case State::SendingResults:
7988 SendResults();
7989 return NS_OK;
7991 default:
7992 MOZ_CRASH("Bad state!");
7995 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) {
7996 MaybeSetFailureCode(rv);
7998 // Must set mState before dispatching otherwise we will race with the owning
7999 // thread.
8000 mState = State::SendingResults;
8002 if (IsOnOwningThread()) {
8003 SendResults();
8004 } else {
8005 MOZ_ALWAYS_SUCCEEDS(
8006 OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
8010 return NS_OK;
8013 void LSSimpleRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
8014 AssertIsOnOwningThread();
8016 NoteComplete();
8019 /*******************************************************************************
8020 * PreloadedOp
8021 ******************************************************************************/
8023 PreloadedOp::PreloadedOp(const LSSimpleRequestParams& aParams,
8024 const Maybe<ContentParentId>& aContentParentId)
8025 : LSSimpleRequestBase(aParams, aContentParentId) {
8026 MOZ_ASSERT(aParams.type() ==
8027 LSSimpleRequestParams::TLSSimpleRequestPreloadedParams);
8030 nsresult PreloadedOp::Start() {
8031 AssertIsOnOwningThread();
8032 MOZ_ASSERT(mState == State::StartingRequest);
8033 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8034 MOZ_ASSERT(MayProceed());
8036 const LSSimpleRequestPreloadedParams& params =
8037 mParams.get_LSSimpleRequestPreloadedParams();
8039 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo();
8041 MOZ_ASSERT(
8042 storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
8043 storagePrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
8044 mOrigin = storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo
8045 ? nsCString{QuotaManager::GetOriginForChrome()}
8046 : QuotaManager::GetOriginFromValidatedPrincipalInfo(
8047 storagePrincipalInfo);
8049 mState = State::SendingResults;
8050 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
8052 return NS_OK;
8055 void PreloadedOp::GetResponse(LSSimpleRequestResponse& aResponse) {
8056 AssertIsOnOwningThread();
8057 MOZ_ASSERT(mState == State::SendingResults);
8058 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
8059 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8060 MOZ_ASSERT(MayProceed());
8062 bool preloaded;
8063 RefPtr<Datastore> datastore;
8064 if ((datastore = GetDatastore(mOrigin)) && !datastore->IsClosed()) {
8065 preloaded = true;
8066 } else {
8067 preloaded = false;
8070 LSSimpleRequestPreloadedResponse preloadedResponse;
8071 preloadedResponse.preloaded() = preloaded;
8073 aResponse = preloadedResponse;
8076 /*******************************************************************************
8077 * GetStateOp
8078 ******************************************************************************/
8080 GetStateOp::GetStateOp(const LSSimpleRequestParams& aParams,
8081 const Maybe<ContentParentId>& aContentParentId)
8082 : LSSimpleRequestBase(aParams, aContentParentId) {
8083 MOZ_ASSERT(aParams.type() ==
8084 LSSimpleRequestParams::TLSSimpleRequestGetStateParams);
8087 nsresult GetStateOp::Start() {
8088 AssertIsOnOwningThread();
8089 MOZ_ASSERT(mState == State::StartingRequest);
8090 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8091 MOZ_ASSERT(MayProceed());
8093 const LSSimpleRequestGetStateParams& params =
8094 mParams.get_LSSimpleRequestGetStateParams();
8096 const PrincipalInfo& storagePrincipalInfo = params.storagePrincipalInfo();
8098 MOZ_ASSERT(
8099 storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
8100 storagePrincipalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
8101 mOrigin = storagePrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo
8102 ? nsCString{QuotaManager::GetOriginForChrome()}
8103 : QuotaManager::GetOriginFromValidatedPrincipalInfo(
8104 storagePrincipalInfo);
8106 mState = State::SendingResults;
8107 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
8109 return NS_OK;
8112 void GetStateOp::GetResponse(LSSimpleRequestResponse& aResponse) {
8113 AssertIsOnOwningThread();
8114 MOZ_ASSERT(mState == State::SendingResults);
8115 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
8116 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8117 MOZ_ASSERT(MayProceed());
8119 LSSimpleRequestGetStateResponse getStateResponse;
8121 if (RefPtr<Datastore> datastore = GetDatastore(mOrigin)) {
8122 if (!datastore->IsClosed()) {
8123 getStateResponse.itemInfos() = datastore->GetOrderedItems().Clone();
8127 aResponse = getStateResponse;
8130 /*******************************************************************************
8131 * ArchivedOriginScope
8132 ******************************************************************************/
8134 // static
8135 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromOrigin(
8136 const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey) {
8137 return WrapUnique(
8138 new ArchivedOriginScope(Origin(aOriginAttrSuffix, aOriginKey)));
8141 // static
8142 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromPrefix(
8143 const nsACString& aOriginKey) {
8144 return WrapUnique(new ArchivedOriginScope(Prefix(aOriginKey)));
8147 // static
8148 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromPattern(
8149 const OriginAttributesPattern& aPattern) {
8150 return WrapUnique(new ArchivedOriginScope(Pattern(aPattern)));
8153 // static
8154 UniquePtr<ArchivedOriginScope> ArchivedOriginScope::CreateFromNull() {
8155 return WrapUnique(new ArchivedOriginScope(Null()));
8158 nsLiteralCString ArchivedOriginScope::GetBindingClause() const {
8159 return mData.match(
8160 [](const Origin&) {
8161 return " WHERE originKey = :originKey "
8162 "AND originAttributes = :originAttributes"_ns;
8164 [](const Pattern&) {
8165 return " WHERE originAttributes MATCH :originAttributesPattern"_ns;
8167 [](const Prefix&) { return " WHERE originKey = :originKey"_ns; },
8168 [](const Null&) { return ""_ns; });
8171 nsresult ArchivedOriginScope::BindToStatement(
8172 mozIStorageStatement* aStmt) const {
8173 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
8174 MOZ_ASSERT(aStmt);
8176 struct Matcher {
8177 mozIStorageStatement* mStmt;
8179 explicit Matcher(mozIStorageStatement* aStmt) : mStmt(aStmt) {}
8181 nsresult operator()(const Origin& aOrigin) {
8182 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName(
8183 "originKey"_ns, aOrigin.OriginNoSuffix())));
8185 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName(
8186 "originAttributes"_ns, aOrigin.OriginSuffix())));
8188 return NS_OK;
8191 nsresult operator()(const Prefix& aPrefix) {
8192 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName(
8193 "originKey"_ns, aPrefix.OriginNoSuffix())));
8195 return NS_OK;
8198 nsresult operator()(const Pattern& aPattern) {
8199 QM_TRY(MOZ_TO_RESULT(mStmt->BindUTF8StringByName(
8200 "originAttributesPattern"_ns, "pattern1"_ns)));
8202 return NS_OK;
8205 nsresult operator()(const Null& aNull) { return NS_OK; }
8208 QM_TRY(MOZ_TO_RESULT(mData.match(Matcher(aStmt))));
8210 return NS_OK;
8213 bool ArchivedOriginScope::HasMatches(
8214 ArchivedOriginHashtable* aHashtable) const {
8215 AssertIsOnIOThread();
8216 MOZ_ASSERT(aHashtable);
8218 return mData.match(
8219 [aHashtable](const Origin& aOrigin) {
8220 const nsCString hashKey = GetArchivedOriginHashKey(
8221 aOrigin.OriginSuffix(), aOrigin.OriginNoSuffix());
8223 return aHashtable->Contains(hashKey);
8225 [aHashtable](const Pattern& aPattern) {
8226 return std::any_of(
8227 aHashtable->Values().cbegin(), aHashtable->Values().cend(),
8228 [&aPattern](const auto& entry) {
8229 return aPattern.GetPattern().Matches(entry->mOriginAttributes);
8232 [aHashtable](const Prefix& aPrefix) {
8233 return std::any_of(
8234 aHashtable->Values().cbegin(), aHashtable->Values().cend(),
8235 [&aPrefix](const auto& entry) {
8236 return entry->mOriginNoSuffix == aPrefix.OriginNoSuffix();
8239 [aHashtable](const Null& aNull) { return !aHashtable->IsEmpty(); });
8242 void ArchivedOriginScope::RemoveMatches(
8243 ArchivedOriginHashtable* aHashtable) const {
8244 AssertIsOnIOThread();
8245 MOZ_ASSERT(aHashtable);
8247 struct Matcher {
8248 ArchivedOriginHashtable* mHashtable;
8250 explicit Matcher(ArchivedOriginHashtable* aHashtable)
8251 : mHashtable(aHashtable) {}
8253 void operator()(const Origin& aOrigin) {
8254 nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(),
8255 aOrigin.OriginNoSuffix());
8257 mHashtable->Remove(hashKey);
8260 void operator()(const Prefix& aPrefix) {
8261 for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) {
8262 const auto& archivedOriginInfo = iter.Data();
8264 if (archivedOriginInfo->mOriginNoSuffix == aPrefix.OriginNoSuffix()) {
8265 iter.Remove();
8270 void operator()(const Pattern& aPattern) {
8271 for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) {
8272 const auto& archivedOriginInfo = iter.Data();
8274 if (aPattern.GetPattern().Matches(
8275 archivedOriginInfo->mOriginAttributes)) {
8276 iter.Remove();
8281 void operator()(const Null& aNull) { mHashtable->Clear(); }
8284 mData.match(Matcher(aHashtable));
8287 /*******************************************************************************
8288 * QuotaClient
8289 ******************************************************************************/
8291 QuotaClient* QuotaClient::sInstance = nullptr;
8293 QuotaClient::QuotaClient()
8294 : mShadowDatabaseMutex("LocalStorage mShadowDatabaseMutex") {
8295 AssertIsOnBackgroundThread();
8296 MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
8298 sInstance = this;
8301 QuotaClient::~QuotaClient() {
8302 AssertIsOnBackgroundThread();
8303 MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
8305 sInstance = nullptr;
8308 mozilla::dom::quota::Client::Type QuotaClient::GetType() {
8309 return QuotaClient::LS;
8312 Result<UsageInfo, nsresult> QuotaClient::InitOrigin(
8313 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
8314 const AtomicBool& aCanceled) {
8315 AssertIsOnIOThread();
8316 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
8317 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
8319 QuotaManager* quotaManager = QuotaManager::Get();
8320 MOZ_ASSERT(quotaManager);
8322 QM_TRY_INSPECT(const auto& directory,
8323 quotaManager->GetOriginDirectory(aOriginMetadata));
8325 MOZ_ASSERT(directory);
8327 QM_TRY(MOZ_TO_RESULT(
8328 directory->Append(NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME))));
8330 #ifdef DEBUG
8332 QM_TRY_INSPECT(const bool& exists,
8333 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
8334 MOZ_ASSERT(exists);
8336 #endif
8338 QM_TRY_INSPECT(const auto& directoryPath, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8339 nsString, directory, GetPath));
8341 QM_TRY_INSPECT(const auto& usageFile, GetUsageFile(directoryPath));
8343 // XXX Try to make usageFileExists const
8344 QM_TRY_UNWRAP(bool usageFileExists, ExistsAsFile(*usageFile));
8346 QM_TRY_INSPECT(const auto& usageJournalFile,
8347 GetUsageJournalFile(directoryPath));
8349 QM_TRY_INSPECT(const bool& usageJournalFileExists,
8350 ExistsAsFile(*usageJournalFile));
8352 if (usageJournalFileExists) {
8353 if (usageFileExists) {
8354 QM_TRY(MOZ_TO_RESULT(usageFile->Remove(false)));
8356 usageFileExists = false;
8359 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false)));
8362 QM_TRY_INSPECT(const auto& file,
8363 CloneFileAndAppend(*directory, kDataFileName));
8365 QM_TRY_INSPECT(const bool& fileExists, ExistsAsFile(*file));
8367 QM_TRY_INSPECT(
8368 const UsageInfo& res,
8369 ([fileExists, usageFileExists, &file, &usageFile, &usageJournalFile,
8370 &aOriginMetadata]() -> Result<UsageInfo, nsresult> {
8371 if (fileExists) {
8372 QM_TRY_RETURN(QM_OR_ELSE_WARN(
8373 // Expression. To simplify control flow, we call LoadUsageFile
8374 // unconditionally here, even though it will necessarily fail if
8375 // usageFileExists is false.
8376 LoadUsageFile(*usageFile),
8377 // Fallback.
8378 ([&file, &usageFile, &usageJournalFile, &aOriginMetadata](
8379 const nsresult) -> Result<UsageInfo, nsresult> {
8380 QM_TRY_INSPECT(
8381 const auto& connection,
8382 CreateStorageConnectionWithRecovery(
8383 *file, *usageFile, aOriginMetadata.mOrigin, [] {}));
8385 QM_TRY_INSPECT(const int64_t& usage,
8386 GetUsage(*connection,
8387 /* aArchivedOriginScope */ nullptr));
8389 QM_TRY(MOZ_TO_RESULT(
8390 UpdateUsageFile(usageFile, usageJournalFile, usage)));
8392 QM_TRY(MOZ_TO_RESULT(usageJournalFile->Remove(false)));
8394 MOZ_ASSERT(usage >= 0);
8395 return UsageInfo{DatabaseUsageType(Some(uint64_t(usage)))};
8396 })));
8399 if (usageFileExists) {
8400 QM_TRY(MOZ_TO_RESULT(usageFile->Remove(false)));
8403 return UsageInfo{};
8404 }()));
8406 // Report unknown files in debug builds, but don't fail, just warn (we don't
8407 // report unknown files in release builds because that requires extra
8408 // scanning of the directory which would slow down entire initialization for
8409 // little benefit).
8411 #ifdef DEBUG
8412 QM_TRY(CollectEachFileAtomicCancelable(
8413 *directory, aCanceled,
8414 [](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
8415 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
8417 switch (dirEntryKind) {
8418 case nsIFileKind::ExistsAsDirectory:
8419 Unused << WARN_IF_FILE_IS_UNKNOWN(*file);
8420 break;
8422 case nsIFileKind::ExistsAsFile: {
8423 QM_TRY_INSPECT(
8424 const auto& leafName,
8425 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, file, GetLeafName));
8427 if (leafName.Equals(kDataFileName) ||
8428 leafName.Equals(kJournalFileName) ||
8429 leafName.Equals(kUsageFileName) ||
8430 leafName.Equals(kUsageJournalFileName)) {
8431 return Ok{};
8434 Unused << WARN_IF_FILE_IS_UNKNOWN(*file);
8436 break;
8439 case nsIFileKind::DoesNotExist:
8440 // Ignore files that got removed externally while iterating.
8441 break;
8443 return Ok{};
8444 }));
8445 #endif
8447 return res;
8450 nsresult QuotaClient::InitOriginWithoutTracking(
8451 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
8452 const AtomicBool& aCanceled) {
8453 AssertIsOnIOThread();
8455 // This is called when a storage/permanent/${origin}/ls directory exists. Even
8456 // though this shouldn't happen with a "good" profile, we shouldn't return an
8457 // error here, since that would cause origin initialization to fail. We just
8458 // warn and otherwise ignore that.
8459 UNKNOWN_FILE_WARNING(NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME));
8460 return NS_OK;
8463 Result<UsageInfo, nsresult> QuotaClient::GetUsageForOrigin(
8464 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
8465 const AtomicBool& aCanceled) {
8466 AssertIsOnIOThread();
8467 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
8469 // We can't open the database at this point, since it can be already used
8470 // by the connection thread. Use the cached value instead.
8472 QuotaManager* quotaManager = QuotaManager::Get();
8473 MOZ_ASSERT(quotaManager);
8475 return quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT,
8476 aOriginMetadata, Client::LS);
8479 nsresult QuotaClient::AboutToClearOrigins(
8480 const Nullable<PersistenceType>& aPersistenceType,
8481 const OriginScope& aOriginScope) {
8482 AssertIsOnIOThread();
8484 // This method is not called when the clearing is triggered by the eviction
8485 // process. It's on purpose to avoid a problem with the origin access time
8486 // which can be described as follows:
8487 // When there's a storage pressure condition and quota manager starts
8488 // collecting origins for eviction, there can be an origin that hasn't been
8489 // touched for long time. However, the old implementation of local storage
8490 // could have touched the origin only recently and the new implementation
8491 // hasn't had a chance to create a new per origin database for it yet (the
8492 // data is still in the archive database), so the origin access time hasn't
8493 // been updated either. In the end, the origin would be evicted despite the
8494 // fact that there was recent local storage activity.
8495 // So this method clears the archived data and shadow database entries for
8496 // given origin scope, but only if it's a privacy-related origin clearing.
8498 if (!aPersistenceType.IsNull() &&
8499 aPersistenceType.Value() != PERSISTENCE_TYPE_DEFAULT) {
8500 return NS_OK;
8503 // There can be no data for the system principal in the archive or the shadow
8504 // database. This early return silences potential warnings caused by failed
8505 // `CreateAerchivedOriginScope` because it calls `GenerateOriginKey2` which
8506 // doesn't support the system principal.
8507 if (aOriginScope.IsOrigin() &&
8508 aOriginScope.GetOrigin() == QuotaManager::GetOriginForChrome()) {
8509 return NS_OK;
8512 const bool shadowWrites = gShadowWrites;
8514 QM_TRY_INSPECT(const auto& archivedOriginScope,
8515 CreateArchivedOriginScope(aOriginScope));
8517 if (!gArchivedOrigins) {
8518 QM_TRY(MOZ_TO_RESULT(LoadArchivedOrigins()));
8519 MOZ_ASSERT(gArchivedOrigins);
8522 const bool hasDataForRemoval =
8523 archivedOriginScope->HasMatches(gArchivedOrigins);
8525 QuotaManager* quotaManager = QuotaManager::Get();
8526 MOZ_ASSERT(quotaManager);
8528 const nsString& basePath = quotaManager->GetBasePath();
8531 MutexAutoLock shadowDatabaseLock(mShadowDatabaseMutex);
8533 QM_TRY_INSPECT(
8534 const auto& connection,
8535 ([&basePath]() -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
8536 if (gInitializedShadowStorage) {
8537 QM_TRY_RETURN(GetShadowStorageConnection(basePath));
8540 QM_TRY_UNWRAP(auto connection,
8541 CreateShadowStorageConnection(basePath));
8543 gInitializedShadowStorage = true;
8545 return connection;
8546 }()));
8549 Maybe<AutoDatabaseAttacher> maybeAutoArchiveDatabaseAttacher;
8551 if (hasDataForRemoval) {
8552 QM_TRY_INSPECT(const auto& archiveFile,
8553 GetArchiveFile(quotaManager->GetStoragePath()));
8555 maybeAutoArchiveDatabaseAttacher.emplace(
8556 AutoDatabaseAttacher(connection, archiveFile, "archive"_ns));
8558 QM_TRY(MOZ_TO_RESULT(maybeAutoArchiveDatabaseAttacher->Attach()));
8561 if (archivedOriginScope->IsPattern()) {
8562 nsCOMPtr<mozIStorageFunction> function(
8563 new MatchFunction(archivedOriginScope->GetPattern()));
8565 QM_TRY(
8566 MOZ_TO_RESULT(connection->CreateFunction("match"_ns, 2, function)));
8570 QM_TRY_INSPECT(const auto& stmt,
8571 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8572 nsCOMPtr<mozIStorageStatement>, connection,
8573 CreateStatement, "BEGIN IMMEDIATE;"_ns));
8575 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
8578 if (shadowWrites) {
8579 QM_TRY(MOZ_TO_RESULT(
8580 PerformDelete(connection, "main"_ns, archivedOriginScope.get())));
8583 if (hasDataForRemoval) {
8584 QM_TRY(MOZ_TO_RESULT(PerformDelete(connection, "archive"_ns,
8585 archivedOriginScope.get())));
8589 QM_TRY_INSPECT(const auto& stmt,
8590 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8591 nsCOMPtr<mozIStorageStatement>, connection,
8592 CreateStatement, "COMMIT;"_ns));
8594 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
8597 if (archivedOriginScope->IsPattern()) {
8598 QM_TRY(MOZ_TO_RESULT(connection->RemoveFunction("match"_ns)));
8601 if (hasDataForRemoval) {
8602 MOZ_ASSERT(maybeAutoArchiveDatabaseAttacher.isSome());
8603 QM_TRY(MOZ_TO_RESULT(maybeAutoArchiveDatabaseAttacher->Detach()));
8605 maybeAutoArchiveDatabaseAttacher.reset();
8607 MOZ_ASSERT(gArchivedOrigins);
8608 MOZ_ASSERT(archivedOriginScope->HasMatches(gArchivedOrigins));
8609 archivedOriginScope->RemoveMatches(gArchivedOrigins);
8612 QM_TRY(MOZ_TO_RESULT(connection->Close()));
8615 if (aOriginScope.IsNull()) {
8616 QM_TRY_INSPECT(const auto& shadowFile, GetShadowFile(basePath));
8618 QM_TRY(MOZ_TO_RESULT(shadowFile->Remove(false)));
8620 gInitializedShadowStorage = false;
8623 return NS_OK;
8626 void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
8627 const nsACString& aOrigin) {
8628 AssertIsOnIOThread();
8631 void QuotaClient::OnRepositoryClearCompleted(PersistenceType aPersistenceType) {
8632 AssertIsOnIOThread();
8635 void QuotaClient::ReleaseIOThreadObjects() {
8636 AssertIsOnIOThread();
8638 gInitializationInfo = nullptr;
8640 // Delete archived origins hashtable since QuotaManager clears the whole
8641 // storage directory including ls-archive.sqlite.
8643 gArchivedOrigins = nullptr;
8646 void QuotaClient::AbortOperationsForLocks(
8647 const DirectoryLockIdTable& aDirectoryLockIds) {
8648 AssertIsOnBackgroundThread();
8650 // A PrepareDatastoreOp object could already acquire a directory lock for
8651 // the given origin. Its last step is creation of a Datastore object (which
8652 // will take ownership of the directory lock) and a PreparedDatastore object
8653 // which keeps the Datastore alive until a database actor is created.
8654 // We need to invalidate the PreparedDatastore object when it's created,
8655 // otherwise the Datastore object can block the origin clear operation for
8656 // long time. It's not a problem that we don't fail the PrepareDatastoreOp
8657 // immediatelly (avoiding the creation of the Datastore and PreparedDatastore
8658 // object). We will call RequestAllowToClose on the database actor once it's
8659 // created and the child actor will respond by sending AllowToClose which
8660 // will close the Datastore on the parent side (the closing releases the
8661 // directory lock).
8663 InvalidatePrepareDatastoreOpsMatching(
8664 [&aDirectoryLockIds](const auto& prepareDatastoreOp) {
8665 // Check if the PrepareDatastoreOp holds an acquired DirectoryLock.
8666 // Origin clearing can't be blocked by this PrepareDatastoreOp if there
8667 // is no acquired DirectoryLock. If there is an acquired DirectoryLock,
8668 // check if the table contains the lock for the PrepareDatastoreOp.
8669 return IsLockForObjectAcquiredAndContainedInLockTable(
8670 prepareDatastoreOp, aDirectoryLockIds);
8673 if (gPrivateDatastores) {
8674 gPrivateDatastores->RemoveIf([&aDirectoryLockIds](const auto& iter) {
8675 const auto& privateDatastore = iter.Data();
8677 // The PrivateDatastore::mDatastore member is not cleared until the
8678 // PrivateDatastore is destroyed.
8679 const auto& datastore = privateDatastore->DatastoreRef();
8681 // If the PrivateDatastore exists then it must be registered in
8682 // Datastore::mHasLivePrivateDatastore as well. The Datastore must have
8683 // a DirectoryLock if there is a registered PrivateDatastore.
8684 return IsLockForObjectContainedInLockTable(datastore, aDirectoryLockIds);
8687 if (!gPrivateDatastores->Count()) {
8688 gPrivateDatastores = nullptr;
8692 InvalidatePreparedDatastoresMatching([&aDirectoryLockIds](
8693 const auto& preparedDatastore) {
8694 // The PreparedDatastore::mDatastore member is not cleared until the
8695 // PreparedDatastore is destroyed.
8696 const auto& datastore = preparedDatastore.DatastoreRef();
8698 // If the PreparedDatastore exists then it must be registered in
8699 // Datastore::mPreparedDatastores as well. The Datastore must have a
8700 // DirectoryLock if there are registered PreparedDatastore objects.
8701 return IsLockForObjectContainedInLockTable(datastore, aDirectoryLockIds);
8704 RequestAllowToCloseDatabasesMatching(
8705 [&aDirectoryLockIds](const auto& database) {
8706 const auto& maybeDatastore = database.MaybeDatastoreRef();
8708 // If the Database is registered in gLiveDatabases then it must have a
8709 // Datastore.
8710 MOZ_ASSERT(maybeDatastore.isSome());
8712 // If the Database is registered in gLiveDatabases then it must be
8713 // registered in Datastore::mDatabases as well. The Datastore must have
8714 // a DirectoryLock if there are registered Database objects.
8715 return IsLockForObjectContainedInLockTable(*maybeDatastore,
8716 aDirectoryLockIds);
8720 void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) {
8721 AssertIsOnBackgroundThread();
8723 RequestAllowToCloseDatabasesMatching(
8724 [&aContentParentId](const auto& database) {
8725 return database.IsOwnedByProcess(aContentParentId);
8729 void QuotaClient::AbortAllOperations() {
8730 AssertIsOnBackgroundThread();
8732 InvalidatePrepareDatastoreOpsMatching([](const auto& prepareDatastoreOp) {
8733 return prepareDatastoreOp.MaybeDirectoryLockRef();
8736 if (gPrivateDatastores) {
8737 gPrivateDatastores = nullptr;
8740 InvalidatePreparedDatastoresMatching([](const auto&) { return true; });
8742 RequestAllowToCloseDatabasesMatching([](const auto&) { return true; });
8745 void QuotaClient::StartIdleMaintenance() { AssertIsOnBackgroundThread(); }
8747 void QuotaClient::StopIdleMaintenance() { AssertIsOnBackgroundThread(); }
8749 void QuotaClient::InitiateShutdown() {
8750 // gPrepareDatastoreOps are short lived objects running a state machine.
8751 // The shutdown flag is checked between states, so we don't have to notify
8752 // all the objects here.
8753 // Allocation of a new PrepareDatastoreOp object is prevented once the
8754 // shutdown flag is set.
8755 // When the last PrepareDatastoreOp finishes, the gPrepareDatastoreOps array
8756 // is destroyed.
8758 if (gPreparedDatastores) {
8759 gPreparedDatastores = nullptr;
8762 if (gPrivateDatastores) {
8763 gPrivateDatastores = nullptr;
8766 RequestAllowToCloseDatabasesMatching([](const auto&) { return true; });
8768 if (gPreparedObsevers) {
8769 gPreparedObsevers = nullptr;
8773 bool QuotaClient::IsShutdownCompleted() const {
8774 // Don't have to check gPrivateDatastores and gPreparedDatastores since we
8775 // nulled it out in InitiateShutdown.
8776 return !gPrepareDatastoreOps && !gDatastores && !gLiveDatabases;
8779 void QuotaClient::ForceKillActors() { ForceKillAllDatabases(); }
8781 nsCString QuotaClient::GetShutdownStatus() const {
8782 AssertIsOnBackgroundThread();
8784 nsCString data;
8786 if (gPrepareDatastoreOps) {
8787 data.Append("PrepareDatastoreOperations: ");
8788 data.AppendInt(static_cast<uint32_t>(gPrepareDatastoreOps->Length()));
8789 data.Append(" (");
8791 // XXX What's the purpose of adding these to a hashtable before joining them
8792 // to the string? (Maybe this used to be an ordered container before???)
8793 nsTHashSet<nsCString> ids;
8794 std::transform(gPrepareDatastoreOps->cbegin(), gPrepareDatastoreOps->cend(),
8795 MakeInserter(ids), [](const auto& prepareDatastoreOp) {
8796 nsCString id;
8797 prepareDatastoreOp->Stringify(id);
8798 return id;
8801 StringJoinAppend(data, ", "_ns, ids);
8803 data.Append(")\n");
8806 if (gDatastores) {
8807 data.Append("Datastores: ");
8808 data.AppendInt(gDatastores->Count());
8809 data.Append(" (");
8811 // XXX It might be confusing to remove duplicates here, as the actual list
8812 // won't match the count then.
8813 nsTHashSet<nsCString> ids;
8814 std::transform(gDatastores->Values().cbegin(), gDatastores->Values().cend(),
8815 MakeInserter(ids), [](const auto& entry) {
8816 nsCString id;
8817 entry->Stringify(id);
8818 return id;
8821 StringJoinAppend(data, ", "_ns, ids);
8823 data.Append(")\n");
8826 if (gLiveDatabases) {
8827 data.Append("LiveDatabases: ");
8828 data.AppendInt(static_cast<uint32_t>(gLiveDatabases->Length()));
8829 data.Append(" (");
8831 // XXX It might be confusing to remove duplicates here, as the actual list
8832 // won't match the count then.
8833 nsTHashSet<nsCString> ids;
8834 std::transform(gLiveDatabases->cbegin(), gLiveDatabases->cend(),
8835 MakeInserter(ids), [](const auto& database) {
8836 nsCString id;
8837 database->Stringify(id);
8838 return id;
8841 StringJoinAppend(data, ", "_ns, ids);
8843 data.Append(")\n");
8846 return data;
8849 void QuotaClient::FinalizeShutdown() {
8850 // And finally, shutdown the connection thread.
8851 if (gConnectionThread) {
8852 gConnectionThread->Shutdown();
8854 gConnectionThread = nullptr;
8858 Result<UniquePtr<ArchivedOriginScope>, nsresult>
8859 QuotaClient::CreateArchivedOriginScope(const OriginScope& aOriginScope) {
8860 AssertIsOnIOThread();
8862 if (aOriginScope.IsOrigin()) {
8863 QM_TRY_INSPECT(const auto& principalInfo,
8864 QuotaManager::ParseOrigin(aOriginScope.GetOrigin()));
8866 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]),
8867 GenerateOriginKey2(principalInfo));
8869 return ArchivedOriginScope::CreateFromOrigin(originAttrSuffix, originKey);
8872 if (aOriginScope.IsPrefix()) {
8873 QM_TRY_INSPECT(const auto& principalInfo,
8874 QuotaManager::ParseOrigin(aOriginScope.GetOriginNoSuffix()));
8876 QM_TRY_INSPECT((const auto& [originAttrSuffix, originKey]),
8877 GenerateOriginKey2(principalInfo));
8879 Unused << originAttrSuffix;
8881 return ArchivedOriginScope::CreateFromPrefix(originKey);
8884 if (aOriginScope.IsPattern()) {
8885 return ArchivedOriginScope::CreateFromPattern(aOriginScope.GetPattern());
8888 MOZ_ASSERT(aOriginScope.IsNull());
8890 return ArchivedOriginScope::CreateFromNull();
8893 nsresult QuotaClient::PerformDelete(
8894 mozIStorageConnection* aConnection, const nsACString& aSchemaName,
8895 ArchivedOriginScope* aArchivedOriginScope) const {
8896 AssertIsOnIOThread();
8897 MOZ_ASSERT(aConnection);
8898 MOZ_ASSERT(aArchivedOriginScope);
8900 QM_TRY_INSPECT(
8901 const auto& stmt,
8902 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8903 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
8904 "DELETE FROM "_ns + aSchemaName + ".webappsstore2"_ns +
8905 aArchivedOriginScope->GetBindingClause() + ";"_ns));
8907 QM_TRY(MOZ_TO_RESULT(aArchivedOriginScope->BindToStatement(stmt)));
8909 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
8911 return NS_OK;
8914 NS_IMPL_ISUPPORTS(QuotaClient::MatchFunction, mozIStorageFunction)
8916 NS_IMETHODIMP
8917 QuotaClient::MatchFunction::OnFunctionCall(
8918 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
8919 AssertIsOnIOThread();
8920 MOZ_ASSERT(aFunctionArguments);
8921 MOZ_ASSERT(aResult);
8923 QM_TRY_INSPECT(const auto& suffix,
8924 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8925 nsAutoCString, aFunctionArguments, GetUTF8String, 1));
8927 OriginAttributes oa;
8928 QM_TRY(OkIf(oa.PopulateFromSuffix(suffix)), NS_ERROR_FAILURE);
8930 const bool result = mPattern.Matches(oa);
8932 RefPtr<nsVariant> outVar(new nsVariant());
8933 QM_TRY(MOZ_TO_RESULT(outVar->SetAsBool(result)));
8935 outVar.forget(aResult);
8936 return NS_OK;
8939 /*******************************************************************************
8940 * AutoWriteTransaction
8941 ******************************************************************************/
8943 AutoWriteTransaction::AutoWriteTransaction(bool aShadowWrites)
8944 : mConnection(nullptr), mShadowWrites(aShadowWrites) {
8945 AssertIsOnGlobalConnectionThread();
8947 MOZ_COUNT_CTOR(mozilla::dom::AutoWriteTransaction);
8950 AutoWriteTransaction::~AutoWriteTransaction() {
8951 AssertIsOnGlobalConnectionThread();
8953 MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction);
8955 if (mConnection) {
8956 QM_WARNONLY_TRY(QM_TO_RESULT(mConnection->RollbackWriteTransaction()));
8958 if (mShadowWrites) {
8959 QM_WARNONLY_TRY(QM_TO_RESULT(DetachShadowDatabaseAndUnlock()));
8964 nsresult AutoWriteTransaction::Start(Connection* aConnection) {
8965 AssertIsOnGlobalConnectionThread();
8966 MOZ_ASSERT(aConnection);
8967 MOZ_ASSERT(!mConnection);
8969 if (mShadowWrites) {
8970 QM_TRY(MOZ_TO_RESULT(LockAndAttachShadowDatabase(aConnection)));
8973 QM_TRY(MOZ_TO_RESULT(aConnection->BeginWriteTransaction()));
8975 mConnection = aConnection;
8977 return NS_OK;
8980 nsresult AutoWriteTransaction::Commit() {
8981 AssertIsOnGlobalConnectionThread();
8982 MOZ_ASSERT(mConnection);
8984 QM_TRY(MOZ_TO_RESULT(mConnection->CommitWriteTransaction()));
8986 if (mShadowWrites) {
8987 QM_TRY(MOZ_TO_RESULT(DetachShadowDatabaseAndUnlock()));
8990 mConnection = nullptr;
8992 return NS_OK;
8995 nsresult AutoWriteTransaction::LockAndAttachShadowDatabase(
8996 Connection* aConnection) {
8997 AssertIsOnGlobalConnectionThread();
8998 MOZ_ASSERT(aConnection);
8999 MOZ_ASSERT(!mConnection);
9000 MOZ_ASSERT(mShadowDatabaseLock.isNothing());
9001 MOZ_ASSERT(mShadowWrites);
9003 QuotaManager* quotaManager = QuotaManager::Get();
9004 MOZ_ASSERT(quotaManager);
9006 mShadowDatabaseLock.emplace(
9007 aConnection->GetQuotaClient()->ShadowDatabaseMutex());
9009 QM_TRY(MOZ_TO_RESULT(AttachShadowDatabase(
9010 quotaManager->GetBasePath(), &aConnection->MutableStorageConnection())));
9012 return NS_OK;
9015 nsresult AutoWriteTransaction::DetachShadowDatabaseAndUnlock() {
9016 AssertIsOnGlobalConnectionThread();
9017 MOZ_ASSERT(mConnection);
9018 MOZ_ASSERT(mShadowDatabaseLock.isSome());
9019 MOZ_ASSERT(mShadowWrites);
9021 nsCOMPtr<mozIStorageConnection> storageConnection =
9022 mConnection->StorageConnection();
9023 MOZ_ASSERT(storageConnection);
9025 QM_TRY(MOZ_TO_RESULT(DetachShadowDatabase(storageConnection)));
9027 mShadowDatabaseLock.reset();
9029 return NS_OK;
9032 } // namespace mozilla::dom