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"
10 #include "LSInitializationTypes.h"
12 #include "ReportInternalError.h"
20 #include <type_traits>
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"
98 #include "nsClassHashtable.h"
99 #include "nsTHashMap.h"
102 #include "nsHashKeys.h"
103 #include "nsIBinaryInputStream.h"
104 #include "nsIBinaryOutputStream.h"
105 #include "nsIDirectoryEnumerator.h"
106 #include "nsIEventTarget.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"
137 #include "nsXULAppAPI.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)
149 namespace mozilla::dom
{
151 using namespace mozilla::dom::quota
;
152 using namespace mozilla::dom::StorageUtils
;
153 using namespace mozilla::ipc
;
157 struct ArchivedOriginInfo
;
158 class ArchivedOriginScope
;
160 class ConnectionThread
;
163 class PrepareDatastoreOp
;
164 class PreparedDatastore
;
168 using ArchivedOriginHashtable
=
169 nsClassHashtable
<nsCStringHashKey
, ArchivedOriginInfo
>;
171 /*******************************************************************************
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
=
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.
228 constexpr auto kJournalFileName
= u
"data.sqlite-journal"_ns
;
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
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
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
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 /*******************************************************************************
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(
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"
367 nsresult
CreateTables(mozIStorageConnection
* aConnection
) {
368 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
369 MOZ_ASSERT(aConnection
);
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"
382 QM_TRY(MOZ_TO_RESULT(CreateDataTable(aConnection
)));
384 QM_TRY(MOZ_TO_RESULT(aConnection
->SetSchemaVersion(kSQLiteSchemaVersion
)));
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(
398 "SET usage = (SELECT total(utf16Length(key) + utf16Length(value)) "
401 QM_TRY(MOZ_TO_RESULT(aConnection
->SetSchemaVersion(MakeSchemaVersion(2, 0))));
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))));
421 nsresult
UpgradeSchemaFrom3_0To4_0(mozIStorageConnection
* aConnection
) {
422 AssertIsOnIOThread();
423 MOZ_ASSERT(aConnection
);
425 QM_TRY(MOZ_TO_RESULT(aConnection
->SetSchemaVersion(MakeSchemaVersion(4, 0))));
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"
447 // Reinsert old data, all legacy data is UTF8
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 "
456 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteSimpleSQL("DROP TABLE data;"_ns
)));
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))));
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
)));
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(
481 aConnection
->SetGrowthIncrement(kSQLiteGrowthIncrement
, ""_ns
)),
483 IsSpecificError
<NS_ERROR_FILE_TOO_BIG
>,
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
;
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(
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
535 // Turn on incremental auto_vacuum mode on desktop builds.
536 "PRAGMA auto_vacuum = INCREMENTAL;"_ns
541 bool vacuumNeeded
= false;
544 mozStorageTransaction
transaction(
546 /* aCommitOnComplete */ false,
547 mozIStorageConnection::TRANSACTION_IMMEDIATE
);
549 QM_TRY(MOZ_TO_RESULT(transaction
.Start()));
551 QM_TRY(MOZ_TO_RESULT(CreateTables(connection
)));
556 const int32_t& schemaVersion
,
557 MOZ_TO_RESULT_INVOKE_MEMBER(connection
, GetSchemaVersion
),
558 QM_ASSERT_UNREACHABLE
);
560 MOZ_ASSERT(schemaVersion
== kSQLiteSchemaVersion
);
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()));
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(
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
)));
599 "Unable to open LocalStorage database, no upgrade path is "
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
);
614 QM_TRY(MOZ_TO_RESULT(connection
->ExecuteSimpleSQL("VACUUM;"_ns
)));
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
));
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
);
632 const auto& vacuumTimeStmt
,
633 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr
<mozIStorageStatement
>,
634 connection
, CreateStatement
,
636 "SET last_vacuum_time = :time"
637 ", last_vacuum_size = :size;"_ns
));
639 QM_TRY(MOZ_TO_RESULT(
640 vacuumTimeStmt
->BindInt64ByName("time"_ns
, vacuumTime
)));
643 MOZ_TO_RESULT(vacuumTimeStmt
->BindInt64ByName("size"_ns
, fileSize
)));
645 QM_TRY(MOZ_TO_RESULT(vacuumTimeStmt
->Execute()));
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(
659 CreateStorageConnection(aDBFile
, aUsageFile
, aOrigin
),
661 IsDatabaseCorruptionError
,
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(
674 MOZ_TO_RESULT(aUsageFile
.Remove(false)),
676 ([](const nsresult rv
) { return rv
== NS_ERROR_FILE_NOT_FOUND
; }),
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
));
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
)));
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
))));
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
)));
744 QM_TRY_INSPECT(const bool& isDirectory
,
745 MOZ_TO_RESULT_INVOKE_MEMBER(archiveFile
, 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
));
761 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
762 nsCOMPtr
<mozIStorageConnection
>, ss
, OpenUnsharedDatabase
,
763 archiveFile
, mozIStorageService::CONNECTION_DEFAULT
),
765 IsDatabaseCorruptionError
,
766 // Fallback. Don't throw an error, leave a corrupted ls-archive
767 // database as it is.
768 ErrToDefaultOk
<nsCOMPtr
<mozIStorageConnection
>>));
771 const nsresult rv
= StorageDBUpdater::Update(connection
);
773 // Don't throw an error, leave a non-updateable ls-archive database as
775 return nsCOMPtr
<mozIStorageConnection
>{};
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
))));
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
,
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
)))));
829 QM_TRY(MOZ_TO_RESULT(
830 aConnection
->ExecuteSimpleSQL(journalModeQueryStart
+ "truncate"_ns
)));
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
));
852 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
853 nsCOMPtr
<mozIStorageConnection
>, ss
, OpenUnsharedDatabase
,
854 shadowFile
, mozIStorageService::CONNECTION_DEFAULT
),
856 IsDatabaseCorruptionError
,
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
));
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(
885 MOZ_TO_RESULT(StorageDBUpdater::Update(connection
)),
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
)));
899 MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(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
));
939 QM_TRY_INSPECT(const bool& exists
,
940 MOZ_TO_RESULT_INVOKE_MEMBER(shadowFile
, Exists
));
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()));
961 nsresult
DetachShadowDatabase(mozIStorageConnection
* aConnection
) {
962 AssertIsOnGlobalConnectionThread();
963 MOZ_ASSERT(aConnection
);
965 QM_TRY(MOZ_TO_RESULT(
966 aConnection
->ExecuteSimpleSQL("DETACH DATABASE shadow"_ns
)));
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
)));
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.
1006 QM_OR_ELSE_LOG_VERBOSE_IF(
1008 MOZ_TO_RESULT_INVOKE_MEMBER(aFile
, IsDirectory
)
1009 .map([](const bool isDirectory
) {
1010 return isDirectory
? ExistsAsFileResult::IsDirectory
1011 : ExistsAsFileResult::IsFile
;
1014 ([](const nsresult rv
) { return rv
== NS_ERROR_FILE_NOT_FOUND
; }),
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
,
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()));
1050 QM_TRY(MOZ_TO_RESULT(stream
->Close()));
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
1094 class DatastoreWriteOptimizer final
: public LSWriteOptimizer
<LSValue
> {
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
> {
1106 // Returns the usage as the success value.
1107 Result
<int64_t, nsresult
> Perform(Connection
* aConnection
,
1108 bool aShadowWrites
);
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
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
;
1133 nsIEventTarget
* OwningEventTarget() const {
1134 MOZ_ASSERT(mOwningEventTarget
);
1136 return mOwningEventTarget
;
1139 bool IsOnOwningThread() const {
1140 MOZ_ASSERT(mOwningEventTarget
);
1143 return NS_SUCCEEDED(mOwningEventTarget
->IsOnCurrentThread(¤t
)) &&
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();
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
;
1189 DatastoreOperationBase()
1190 : Runnable("dom::DatastoreOperationBase"),
1191 mOwningEventTarget(GetCurrentSerialEventTarget()),
1193 mMayProceedOnNonOwningThread(true),
1194 mMayProceed(true) {}
1196 ~DatastoreOperationBase() override
{ MOZ_ASSERT(!mMayProceed
); }
1199 class ConnectionDatastoreOperationBase
: public DatastoreOperationBase
{
1201 RefPtr
<Connection
> mConnection
;
1203 * This boolean flag is used by the CloseOp to avoid creating empty databases.
1205 const bool mEnsureStorageConnection
;
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();
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
);
1231 void RunOnConnectionThread();
1233 void RunOnOwningThread();
1235 // Not to be overridden by subclasses.
1239 class Connection final
: public CachingDatabaseConnection
{
1240 friend class ConnectionThread
;
1242 class InitTemporaryOriginHelper
;
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
;
1267 bool mInUpdateBatch
;
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
,
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();
1341 // Only created by ConnectionThread.
1342 Connection(ConnectionThread
* aConnectionThread
,
1343 const OriginMetadata
& aOriginMetadata
,
1344 UniquePtr
<ArchivedOriginScope
>&& aArchivedOriginScope
,
1345 bool aDatabaseWasNotAvailable
);
1349 void ScheduleFlush();
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
;
1374 explicit InitTemporaryOriginHelper(const OriginMetadata
& aOriginMetadata
)
1375 : Runnable("dom::localstorage::Connection::InitTemporaryOriginHelper"),
1376 mMonitor("InitTemporaryOriginHelper::mMonitor"),
1377 mOriginMetadata(aOriginMetadata
),
1378 mIOThreadResultCode(NS_OK
),
1380 AssertIsOnGlobalConnectionThread();
1383 Result
<nsString
, nsresult
> BlockAndReturnOriginDirectoryPath();
1386 ~InitTemporaryOriginHelper() = default;
1388 nsresult
RunOnIOThread();
1393 class Connection::FlushOp final
: public ConnectionDatastoreOperationBase
{
1394 ConnectionWriteOptimizer mWriteOptimizer
;
1398 FlushOp(Connection
* aConnection
, ConnectionWriteOptimizer
&& aWriteOptimizer
);
1401 nsresult
DoDatastoreWork() override
;
1403 void Cleanup() override
;
1406 class Connection::CloseOp final
: public ConnectionDatastoreOperationBase
{
1407 nsCOMPtr
<nsIRunnable
> mCallback
;
1410 CloseOp(Connection
* aConnection
, nsIRunnable
* aCallback
)
1411 : ConnectionDatastoreOperationBase(aConnection
,
1412 /* aEnsureStorageConnection */ false),
1413 mCallback(aCallback
) {}
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
;
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
);
1445 NS_INLINE_DECL_REFCOUNTING(ConnectionThread
)
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
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
;
1505 int64_t mUpdateBatchUsage
;
1506 int64_t mSizeOfKeys
;
1507 int64_t mSizeOfItems
;
1509 bool mInUpdateBatch
;
1510 bool mHasLivePrivateDatastore
;
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;
1541 bool IsClosed() const {
1542 AssertIsOnBackgroundThread();
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 //////////////////////////////////////////////////////////////////////////////
1588 // These are only called during Snapshot::Checkpoint
1591 * Used by Snapshot::Checkpoint to set a key/value pair as part of an
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
)
1624 // Reference counted.
1627 bool UpdateUsage(int64_t aDelta
);
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
;
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();
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
1667 const nsCString mOrigin
;
1668 uint64_t mDatastoreId
;
1673 PreparedDatastore(Datastore
* aDatastore
,
1674 const Maybe
<ContentParentId
>& aContentParentId
,
1675 const nsACString
& aOrigin
, uint64_t aDatastoreId
,
1677 : mDatastore(aDatastore
),
1678 mTimer(NS_NewTimer()),
1679 mContentParentId(aContentParentId
),
1681 mDatastoreId(aDatastoreId
),
1682 mForPreload(aForPreload
),
1683 mInvalidated(false) {
1684 AssertIsOnBackgroundThread();
1685 MOZ_ASSERT(aDatastore
);
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
);
1701 mDatastore
->NoteFinishedPreparedDatastore(this);
1704 const Datastore
& DatastoreRef() const {
1705 AssertIsOnBackgroundThread();
1706 MOZ_ASSERT(mDatastore
);
1711 Datastore
& MutableDatastoreRef() const {
1712 AssertIsOnBackgroundThread();
1713 MOZ_ASSERT(mDatastore
);
1718 const Maybe
<ContentParentId
>& GetContentParentId() const {
1719 return mContentParentId
;
1722 const nsCString
& Origin() const { return mOrigin
; }
1725 AssertIsOnBackgroundThread();
1727 mInvalidated
= true;
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
;
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
1764 uint32_t mPrivateBrowsingId
;
1765 bool mAllowedToClose
;
1766 bool mActorDestroyed
;
1767 bool mRequestedAllowToClose
;
1769 bool mActorWasAlive
;
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();
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();
1810 void RequestAllowToClose();
1814 void Stringify(nsACString
& aResult
) const;
1816 NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Database
, override
)
1819 // Reference counted.
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 ##
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
;
1923 * True if SaveItem has saved mDatastore's keys into mKeys because a SaveItem
1924 * notification with aAffectsOrder=true was received.
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
;
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
;
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
);
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
)
2028 // Reference counted.
2031 mozilla::ipc::IPCResult
Checkpoint(nsTArray
<LSWriteInfo
>&& aWriteInfos
);
2033 mozilla::ipc::IPCResult
CheckpointAndNotify(
2034 nsTArray
<LSWriteAndNotifyInfo
>&& aWriteAndNotifyInfos
);
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
{
2073 bool mActorDestroyed
;
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
)
2088 // Reference counted.
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
{
2101 // Just created on the PBackground thread. Next step is StartingRequest.
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.
2109 // Doing nested processing.
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
2120 // Waiting to send/sending results on the PBackground thread. Next step is
2128 const LSRequestParams mParams
;
2129 Maybe
<ContentParentId
> mContentParentId
;
2131 bool mWaitingForFinish
;
2134 LSRequestBase(const LSRequestParams
& aParams
,
2135 const Maybe
<ContentParentId
>& aContentParentId
);
2139 void StringifyState(nsACString
& aResult
) const;
2141 virtual void Stringify(nsACString
& aResult
) const;
2146 ~LSRequestBase() override
;
2148 virtual nsresult
Start() = 0;
2150 virtual nsresult
NestedRun();
2152 virtual void GetResponse(LSRequestResponse
& aResponse
) = 0;
2154 virtual void Cleanup() {}
2157 bool VerifyRequestParams();
2159 nsresult
StartRequest();
2161 void SendReadyMessage();
2163 nsresult
SendReadyMessageInternal();
2167 void FinishInternal();
2172 // Common nsIRunnable implementation that subclasses may not override.
2177 void ActorDestroy(ActorDestroyReason aWhy
) override
;
2180 mozilla::ipc::IPCResult
RecvCancel() final
;
2182 mozilla::ipc::IPCResult
RecvFinish() final
;
2185 class PrepareDatastoreOp
2186 : public LSRequestBase
,
2187 public SupportsCheckedUnsafePtr
<CheckIf
<DiagnosticAssertEnabled
>> {
2190 class CompressFunction
;
2191 class CompressionTypeFunction
;
2193 enum class NestedState
{
2194 // The nesting has not yet taken place. Next step is
2195 // CheckExistingOperations.
2198 // Checking if a prepare datastore operation is already running for given
2199 // origin on the PBackground thread. Next step is CheckClosingDatastore.
2200 CheckExistingOperations
,
2202 // Checking if a datastore is closing the connection for given origin on
2203 // the PBackground thread. Next step is PreparationPending.
2204 CheckClosingDatastore
,
2206 // Ensuring quota manager is created and opening directory on the
2207 // PBackground thread. Next step is either SendingResults if quota manager
2208 // is not available or DirectoryOpenPending if quota manager is available.
2209 // If a datastore already exists for given origin then the next state is
2210 // SendingReadyMessage.
2213 // Waiting for directory open allowed on the PBackground thread. The next
2214 // step is either SendingReadyMessage if directory lock failed to acquire,
2215 // or DatabaseWorkOpen if directory lock is acquired.
2216 DirectoryOpenPending
,
2218 // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
2222 // Starting a load data operation on the PBackground thread. Next step is
2223 // DatabaseWorkLoadData.
2226 // Waiting to do/doing work on the connection thread. This involves waiting
2227 // for the LoadDataOp to do its work. Eventually the state will transition
2228 // to SendingReadyMessage.
2229 DatabaseWorkLoadData
,
2231 // The nesting has completed.
2235 RefPtr
<PrepareDatastoreOp
> mDelayedOp
;
2236 RefPtr
<ClientDirectoryLock
> mPendingDirectoryLock
;
2237 RefPtr
<DirectoryLock
> mDirectoryLock
;
2238 RefPtr
<Connection
> mConnection
;
2239 RefPtr
<Datastore
> mDatastore
;
2240 UniquePtr
<ArchivedOriginScope
> mArchivedOriginScope
;
2241 LoadDataOp
* mLoadDataOp
;
2242 nsTHashMap
<nsStringHashKey
, LSValue
> mValues
;
2243 nsTArray
<LSItemInfo
> mOrderedItems
;
2244 OriginMetadata mOriginMetadata
;
2245 nsCString mMainThreadOrigin
;
2246 nsString mDatabaseFilePath
;
2247 uint32_t mPrivateBrowsingId
;
2249 int64_t mSizeOfKeys
;
2250 int64_t mSizeOfItems
;
2251 uint64_t mDatastoreId
;
2252 NestedState mNestedState
;
2253 const bool mForPreload
;
2254 bool mDatabaseNotAvailable
;
2255 // Set when the Datastore has been registered with gPrivateDatastores so that
2256 // it can be unregistered if an error is encountered in PrepareDatastoreOp.
2257 FlippedOnce
<false> mPrivateDatastoreRegistered
;
2258 // Set when the Datastore has been registered with gPreparedDatastores so
2259 // that it can be unregistered if an error is encountered in
2260 // PrepareDatastoreOp.
2261 FlippedOnce
<false> mPreparedDatastoreRegistered
;
2265 int64_t mDEBUGUsage
;
2269 PrepareDatastoreOp(const LSRequestParams
& aParams
,
2270 const Maybe
<ContentParentId
>& aContentParentId
);
2272 Maybe
<DirectoryLock
&> MaybeDirectoryLockRef() const {
2273 AssertIsOnBackgroundThread();
2275 return ToMaybeRef(mDirectoryLock
.get());
2278 bool OriginIsKnown() const {
2279 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
2281 return !mOriginMetadata
.mOrigin
.IsEmpty();
2284 const nsCString
& Origin() const {
2285 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
2286 MOZ_ASSERT(OriginIsKnown());
2288 return mOriginMetadata
.mOrigin
;
2292 AssertIsOnOwningThread();
2294 mInvalidated
= true;
2297 void StringifyNestedState(nsACString
& aResult
) const;
2299 void Stringify(nsACString
& aResult
) const override
;
2301 void Log() override
;
2304 ~PrepareDatastoreOp() override
;
2306 nsresult
Start() override
;
2308 nsresult
CheckExistingOperations();
2310 nsresult
CheckClosingDatastoreInternal();
2312 nsresult
CheckClosingDatastore();
2314 nsresult
BeginDatastorePreparationInternal();
2316 nsresult
BeginDatastorePreparation();
2318 void SendToIOThread();
2320 nsresult
DatabaseWork();
2322 nsresult
DatabaseNotAvailable();
2324 nsresult
EnsureDirectoryEntry(nsIFile
* aEntry
, bool aCreateIfNotExists
,
2326 bool* aAlreadyExisted
= nullptr);
2328 nsresult
VerifyDatabaseInformation(mozIStorageConnection
* aConnection
);
2330 already_AddRefed
<QuotaObject
> GetQuotaObject();
2332 nsresult
BeginLoadData();
2334 void FinishNesting();
2336 nsresult
FinishNestingOnNonOwningThread();
2338 nsresult
NestedRun() override
;
2340 void GetResponse(LSRequestResponse
& aResponse
) override
;
2342 void Cleanup() override
;
2344 void ConnectionClosedCallback();
2346 void CleanupMetadata();
2349 void ActorDestroy(ActorDestroyReason aWhy
) override
;
2351 void DirectoryLockAcquired(DirectoryLock
* aLock
);
2353 void DirectoryLockFailed();
2356 class PrepareDatastoreOp::LoadDataOp final
2357 : public ConnectionDatastoreOperationBase
{
2358 RefPtr
<PrepareDatastoreOp
> mPrepareDatastoreOp
;
2361 explicit LoadDataOp(PrepareDatastoreOp
* aPrepareDatastoreOp
)
2362 : ConnectionDatastoreOperationBase(aPrepareDatastoreOp
->mConnection
),
2363 mPrepareDatastoreOp(aPrepareDatastoreOp
) {}
2366 ~LoadDataOp() = default;
2368 nsresult
DoDatastoreWork() override
;
2370 void OnSuccess() override
;
2372 void OnFailure(nsresult aResultCode
) override
;
2374 void Cleanup() override
;
2377 class PrepareDatastoreOp::CompressFunction final
: public mozIStorageFunction
{
2379 ~CompressFunction() = default;
2382 NS_DECL_MOZISTORAGEFUNCTION
2385 class PrepareDatastoreOp::CompressionTypeFunction final
2386 : public mozIStorageFunction
{
2388 ~CompressionTypeFunction() = default;
2391 NS_DECL_MOZISTORAGEFUNCTION
2394 class PrepareObserverOp
: public LSRequestBase
{
2398 PrepareObserverOp(const LSRequestParams
& aParams
,
2399 const Maybe
<ContentParentId
>& aContentParentId
);
2402 nsresult
Start() override
;
2404 void GetResponse(LSRequestResponse
& aResponse
) override
;
2407 class LSSimpleRequestBase
: public DatastoreOperationBase
,
2408 public PBackgroundLSSimpleRequestParent
{
2411 // Just created on the PBackground thread. Next step is StartingRequest.
2414 // Waiting to start/starting request on the PBackground thread. Next step is
2418 // Waiting to send/sending results on the PBackground thread. Next step is
2426 const LSSimpleRequestParams mParams
;
2427 Maybe
<ContentParentId
> mContentParentId
;
2431 LSSimpleRequestBase(const LSSimpleRequestParams
& aParams
,
2432 const Maybe
<ContentParentId
>& aContentParentId
);
2437 ~LSSimpleRequestBase() override
;
2439 virtual nsresult
Start() = 0;
2441 virtual void GetResponse(LSSimpleRequestResponse
& aResponse
) = 0;
2444 bool VerifyRequestParams();
2446 nsresult
StartRequest();
2450 // Common nsIRunnable implementation that subclasses may not override.
2455 void ActorDestroy(ActorDestroyReason aWhy
) override
;
2458 class PreloadedOp
: public LSSimpleRequestBase
{
2462 PreloadedOp(const LSSimpleRequestParams
& aParams
,
2463 const Maybe
<ContentParentId
>& aContentParentId
);
2466 nsresult
Start() override
;
2468 void GetResponse(LSSimpleRequestResponse
& aResponse
) override
;
2471 class GetStateOp
: public LSSimpleRequestBase
{
2475 GetStateOp(const LSSimpleRequestParams
& aParams
,
2476 const Maybe
<ContentParentId
>& aContentParentId
);
2479 nsresult
Start() override
;
2481 void GetResponse(LSSimpleRequestResponse
& aResponse
) override
;
2484 /*******************************************************************************
2485 * Other class declarations
2486 ******************************************************************************/
2488 struct ArchivedOriginInfo
{
2489 OriginAttributes mOriginAttributes
;
2490 nsCString mOriginNoSuffix
;
2492 ArchivedOriginInfo(const OriginAttributes
& aOriginAttributes
,
2493 const nsACString
& aOriginNoSuffix
)
2494 : mOriginAttributes(aOriginAttributes
),
2495 mOriginNoSuffix(aOriginNoSuffix
) {}
2498 class ArchivedOriginScope
{
2500 nsCString mOriginSuffix
;
2501 nsCString mOriginNoSuffix
;
2503 Origin(const nsACString
& aOriginSuffix
, const nsACString
& aOriginNoSuffix
)
2504 : mOriginSuffix(aOriginSuffix
), mOriginNoSuffix(aOriginNoSuffix
) {}
2506 const nsACString
& OriginSuffix() const { return mOriginSuffix
; }
2508 const nsACString
& OriginNoSuffix() const { return mOriginNoSuffix
; }
2512 nsCString mOriginNoSuffix
;
2514 explicit Prefix(const nsACString
& aOriginNoSuffix
)
2515 : mOriginNoSuffix(aOriginNoSuffix
) {}
2517 const nsACString
& OriginNoSuffix() const { return mOriginNoSuffix
; }
2521 UniquePtr
<OriginAttributesPattern
> mPattern
;
2523 explicit Pattern(const OriginAttributesPattern
& aPattern
)
2524 : mPattern(MakeUnique
<OriginAttributesPattern
>(aPattern
)) {}
2526 Pattern(const Pattern
& aOther
)
2527 : mPattern(MakeUnique
<OriginAttributesPattern
>(*aOther
.mPattern
)) {}
2529 Pattern(Pattern
&& aOther
) = default;
2531 const OriginAttributesPattern
& GetPattern() const {
2532 MOZ_ASSERT(mPattern
);
2539 using DataType
= Variant
<Origin
, Pattern
, Prefix
, Null
>;
2544 static UniquePtr
<ArchivedOriginScope
> CreateFromOrigin(
2545 const nsACString
& aOriginAttrSuffix
, const nsACString
& aOriginKey
);
2547 static UniquePtr
<ArchivedOriginScope
> CreateFromPrefix(
2548 const nsACString
& aOriginKey
);
2550 static UniquePtr
<ArchivedOriginScope
> CreateFromPattern(
2551 const OriginAttributesPattern
& aPattern
);
2553 static UniquePtr
<ArchivedOriginScope
> CreateFromNull();
2555 bool IsOrigin() const { return mData
.is
<Origin
>(); }
2557 bool IsPrefix() const { return mData
.is
<Prefix
>(); }
2559 bool IsPattern() const { return mData
.is
<Pattern
>(); }
2561 bool IsNull() const { return mData
.is
<Null
>(); }
2563 const nsACString
& OriginSuffix() const {
2564 MOZ_ASSERT(IsOrigin());
2566 return mData
.as
<Origin
>().OriginSuffix();
2569 const nsACString
& OriginNoSuffix() const {
2570 MOZ_ASSERT(IsOrigin() || IsPrefix());
2573 return mData
.as
<Origin
>().OriginNoSuffix();
2575 return mData
.as
<Prefix
>().OriginNoSuffix();
2578 const OriginAttributesPattern
& GetPattern() const {
2579 MOZ_ASSERT(IsPattern());
2581 return mData
.as
<Pattern
>().GetPattern();
2584 nsLiteralCString
GetBindingClause() const;
2586 nsresult
BindToStatement(mozIStorageStatement
* aStatement
) const;
2588 bool HasMatches(ArchivedOriginHashtable
* aHashtable
) const;
2590 void RemoveMatches(ArchivedOriginHashtable
* aHashtable
) const;
2593 // Move constructors
2594 explicit ArchivedOriginScope(const Origin
&& aOrigin
) : mData(aOrigin
) {}
2596 explicit ArchivedOriginScope(const Pattern
&& aPattern
) : mData(aPattern
) {}
2598 explicit ArchivedOriginScope(const Prefix
&& aPrefix
) : mData(aPrefix
) {}
2600 explicit ArchivedOriginScope(const Null
&& aNull
) : mData(aNull
) {}
2603 class QuotaClient final
: public mozilla::dom::quota::Client
{
2604 class MatchFunction
;
2606 static QuotaClient
* sInstance
;
2608 Mutex mShadowDatabaseMutex MOZ_UNANNOTATED
;
2613 static QuotaClient
* GetInstance() {
2614 AssertIsOnBackgroundThread();
2619 mozilla::Mutex
& ShadowDatabaseMutex() {
2620 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
2622 return mShadowDatabaseMutex
;
2625 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::QuotaClient
, override
)
2627 Type
GetType() override
;
2629 Result
<UsageInfo
, nsresult
> InitOrigin(PersistenceType aPersistenceType
,
2630 const OriginMetadata
& aOriginMetadata
,
2631 const AtomicBool
& aCanceled
) override
;
2633 nsresult
InitOriginWithoutTracking(PersistenceType aPersistenceType
,
2634 const OriginMetadata
& aOriginMetadata
,
2635 const AtomicBool
& aCanceled
) override
;
2637 Result
<UsageInfo
, nsresult
> GetUsageForOrigin(
2638 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
2639 const AtomicBool
& aCanceled
) override
;
2641 nsresult
AboutToClearOrigins(
2642 const Nullable
<PersistenceType
>& aPersistenceType
,
2643 const OriginScope
& aOriginScope
) override
;
2645 void OnOriginClearCompleted(PersistenceType aPersistenceType
,
2646 const nsACString
& aOrigin
) override
;
2648 void OnRepositoryClearCompleted(PersistenceType aPersistenceType
) override
;
2650 void ReleaseIOThreadObjects() override
;
2652 void AbortOperationsForLocks(
2653 const DirectoryLockIdTable
& aDirectoryLockIds
) override
;
2655 void AbortOperationsForProcess(ContentParentId aContentParentId
) override
;
2657 void AbortAllOperations() override
;
2659 void StartIdleMaintenance() override
;
2661 void StopIdleMaintenance() override
;
2664 ~QuotaClient() override
;
2666 void InitiateShutdown() override
;
2667 bool IsShutdownCompleted() const override
;
2668 nsCString
GetShutdownStatus() const override
;
2669 void ForceKillActors() override
;
2670 void FinalizeShutdown() override
;
2672 Result
<UniquePtr
<ArchivedOriginScope
>, nsresult
> CreateArchivedOriginScope(
2673 const OriginScope
& aOriginScope
);
2675 nsresult
PerformDelete(mozIStorageConnection
* aConnection
,
2676 const nsACString
& aSchemaName
,
2677 ArchivedOriginScope
* aArchivedOriginScope
) const;
2680 class QuotaClient::MatchFunction final
: public mozIStorageFunction
{
2681 OriginAttributesPattern mPattern
;
2684 explicit MatchFunction(const OriginAttributesPattern
& aPattern
)
2685 : mPattern(aPattern
) {}
2688 ~MatchFunction() = default;
2691 NS_DECL_MOZISTORAGEFUNCTION
2694 /*******************************************************************************
2696 ******************************************************************************/
2698 class MOZ_STACK_CLASS AutoWriteTransaction final
{
2699 Connection
* mConnection
;
2700 Maybe
<MutexAutoLock
> mShadowDatabaseLock
;
2704 explicit AutoWriteTransaction(bool aShadowWrites
);
2706 ~AutoWriteTransaction();
2708 nsresult
Start(Connection
* aConnection
);
2713 nsresult
LockAndAttachShadowDatabase(Connection
* aConnection
);
2715 nsresult
DetachShadowDatabaseAndUnlock();
2718 /*******************************************************************************
2720 ******************************************************************************/
2723 bool gLocalStorageInitialized
= false;
2726 using PrepareDatastoreOpArray
=
2727 nsTArray
<NotNull
<CheckedUnsafePtr
<PrepareDatastoreOp
>>>;
2729 StaticAutoPtr
<PrepareDatastoreOpArray
> gPrepareDatastoreOps
;
2731 // nsCStringHashKey with disabled memmove
2732 class nsCStringHashKeyDM
: public nsCStringHashKey
{
2734 explicit nsCStringHashKeyDM(const nsCStringHashKey::KeyTypePointer aKey
)
2735 : nsCStringHashKey(aKey
) {}
2736 enum { ALLOW_MEMMOVE
= false };
2739 // When CheckedUnsafePtr's checking is enabled, it's necessary to ensure that
2740 // the hashtable uses the copy constructor instead of memmove for moving entries
2741 // since memmove will break CheckedUnsafePtr in a memory-corrupting way.
2742 using DatastoreHashKey
=
2743 std::conditional
<DiagnosticAssertEnabled::value
, nsCStringHashKeyDM
,
2744 nsCStringHashKey
>::type
;
2746 using DatastoreHashtable
=
2747 nsBaseHashtable
<DatastoreHashKey
, NotNull
<CheckedUnsafePtr
<Datastore
>>,
2748 MovingNotNull
<CheckedUnsafePtr
<Datastore
>>>;
2750 StaticAutoPtr
<DatastoreHashtable
> gDatastores
;
2752 uint64_t gLastDatastoreId
= 0;
2754 using PreparedDatastoreHashtable
=
2755 nsClassHashtable
<nsUint64HashKey
, PreparedDatastore
>;
2757 StaticAutoPtr
<PreparedDatastoreHashtable
> gPreparedDatastores
;
2759 using PrivateDatastoreHashtable
=
2760 nsClassHashtable
<nsCStringHashKey
, PrivateDatastore
>;
2762 // Keeps Private Browsing Datastores alive until the private browsing session
2763 // is closed. This is necessary because LocalStorage Private Browsing data is
2764 // (currently) not written to disk and therefore needs to explicitly be kept
2765 // alive in memory so that if a user browses away from a site during a session
2766 // and then back to it that they will still have their data.
2768 // The entries are wrapped by PrivateDatastore instances which call
2769 // NoteLivePrivateDatastore and NoteFinishedPrivateDatastore which set and
2770 // clear mHasLivePrivateDatastore which inhibits MaybeClose() from closing the
2771 // datastore (which would discard the data) when there are no active windows
2772 // using LocalStorage for the origin.
2774 // The table is cleared when the Private Browsing session is closed, which will
2775 // cause NoteFinishedPrivateDatastore to be called on each Datastore which will
2776 // in turn call MaybeClose which should then discard the Datastore. Or in the
2777 // event of an (unlikely) race where the private browsing windows are still
2778 // being torn down, will cause the Datastore to be discarded when the last
2779 // window actually goes away.
2780 UniquePtr
<PrivateDatastoreHashtable
> gPrivateDatastores
;
2782 using LiveDatabaseArray
= nsTArray
<NotNull
<CheckedUnsafePtr
<Database
>>>;
2784 StaticAutoPtr
<LiveDatabaseArray
> gLiveDatabases
;
2786 StaticRefPtr
<ConnectionThread
> gConnectionThread
;
2788 uint64_t gLastObserverId
= 0;
2790 using PreparedObserverHashtable
= nsRefPtrHashtable
<nsUint64HashKey
, Observer
>;
2792 StaticAutoPtr
<PreparedObserverHashtable
> gPreparedObsevers
;
2794 using ObserverHashtable
=
2795 nsClassHashtable
<nsCStringHashKey
, nsTArray
<NotNull
<Observer
*>>>;
2797 StaticAutoPtr
<ObserverHashtable
> gObservers
;
2799 Atomic
<bool> gShadowWrites(kDefaultShadowWrites
);
2800 Atomic
<int32_t, Relaxed
> gSnapshotPrefill(kDefaultSnapshotPrefill
);
2801 Atomic
<int32_t, Relaxed
> gSnapshotGradualPrefill(
2802 kDefaultSnapshotGradualPrefill
);
2803 Atomic
<bool> gClientValidation(kDefaultClientValidation
);
2805 using UsageHashtable
= nsTHashMap
<nsCStringHashKey
, int64_t>;
2807 StaticAutoPtr
<ArchivedOriginHashtable
> gArchivedOrigins
;
2809 // Can only be touched on the Quota Manager I/O thread.
2810 bool gInitializedShadowStorage
= false;
2812 StaticAutoPtr
<LSInitializationInfo
> gInitializationInfo
;
2814 bool IsOnGlobalConnectionThread() {
2815 MOZ_ASSERT(gConnectionThread
);
2816 return gConnectionThread
->IsOnConnectionThread();
2819 void AssertIsOnGlobalConnectionThread() {
2820 MOZ_ASSERT(gConnectionThread
);
2821 gConnectionThread
->AssertIsOnConnectionThread();
2824 already_AddRefed
<Datastore
> GetDatastore(const nsACString
& aOrigin
) {
2825 AssertIsOnBackgroundThread();
2828 auto maybeDatastore
= gDatastores
->MaybeGet(aOrigin
);
2829 if (maybeDatastore
) {
2830 RefPtr
<Datastore
> result(std::move(*maybeDatastore
).unwrapBasePtr());
2831 return result
.forget();
2838 nsresult
LoadArchivedOrigins() {
2839 AssertIsOnIOThread();
2840 MOZ_ASSERT(!gArchivedOrigins
);
2842 QuotaManager
* quotaManager
= QuotaManager::Get();
2843 MOZ_ASSERT(quotaManager
);
2845 QM_TRY_INSPECT(const auto& connection
, CreateArchiveStorageConnection(
2846 quotaManager
->GetStoragePath()));
2849 gArchivedOrigins
= new ArchivedOriginHashtable();
2855 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2856 nsCOMPtr
<mozIStorageStatement
>, connection
, CreateStatement
,
2857 "SELECT DISTINCT originAttributes, originKey "
2858 "FROM webappsstore2;"_ns
));
2860 auto archivedOrigins
= MakeUnique
<ArchivedOriginHashtable
>();
2862 // XXX Actually, this could use a hashtable variant of
2863 // CollectElementsWhileHasResult
2864 QM_TRY(quota::CollectWhileHasResult(
2865 *stmt
, [&archivedOrigins
](auto& stmt
) -> Result
<Ok
, nsresult
> {
2866 QM_TRY_INSPECT(const auto& originSuffix
,
2867 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString
, stmt
,
2869 QM_TRY_INSPECT(const auto& originNoSuffix
,
2870 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString
, stmt
,
2873 const nsCString hashKey
=
2874 GetArchivedOriginHashKey(originSuffix
, originNoSuffix
);
2876 OriginAttributes originAttributes
;
2877 QM_TRY(OkIf(originAttributes
.PopulateFromSuffix(originSuffix
)),
2878 Err(NS_ERROR_FAILURE
));
2880 archivedOrigins
->InsertOrUpdate(
2882 MakeUnique
<ArchivedOriginInfo
>(originAttributes
, originNoSuffix
));
2887 gArchivedOrigins
= archivedOrigins
.release();
2891 Result
<int64_t, nsresult
> GetUsage(mozIStorageConnection
& aConnection
,
2892 ArchivedOriginScope
* aArchivedOriginScope
) {
2893 AssertIsOnIOThread();
2897 ([aArchivedOriginScope
,
2898 &aConnection
]() -> Result
<nsCOMPtr
<mozIStorageStatement
>, nsresult
> {
2899 if (aArchivedOriginScope
) {
2900 QM_TRY_RETURN(CreateAndExecuteSingleStepStatement
<
2901 SingleStepResult::ReturnNullIfNoResult
>(
2904 "total(utf16Length(key) + utf16Length(value)) "
2905 "FROM webappsstore2 "
2906 "WHERE originKey = :originKey "
2907 "AND originAttributes = :originAttributes;"_ns
,
2908 [aArchivedOriginScope
](auto& stmt
) -> Result
<Ok
, nsresult
> {
2909 QM_TRY(MOZ_TO_RESULT(
2910 aArchivedOriginScope
->BindToStatement(&stmt
)));
2915 QM_TRY_RETURN(CreateAndExecuteSingleStepStatement
<
2916 SingleStepResult::ReturnNullIfNoResult
>(
2917 aConnection
, "SELECT usage FROM database"_ns
));
2920 QM_TRY(OkIf(stmt
), Err(NS_ERROR_FAILURE
));
2922 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt
, GetInt64
, 0));
2925 void ShadowWritesPrefChangedCallback(const char* aPrefName
, void* aClosure
) {
2926 MOZ_ASSERT(NS_IsMainThread());
2927 MOZ_ASSERT(!strcmp(aPrefName
, kShadowWritesPref
));
2928 MOZ_ASSERT(!aClosure
);
2930 gShadowWrites
= Preferences::GetBool(aPrefName
, kDefaultShadowWrites
);
2933 void SnapshotPrefillPrefChangedCallback(const char* aPrefName
, void* aClosure
) {
2934 MOZ_ASSERT(NS_IsMainThread());
2935 MOZ_ASSERT(!strcmp(aPrefName
, kSnapshotPrefillPref
));
2936 MOZ_ASSERT(!aClosure
);
2938 int32_t snapshotPrefill
=
2939 Preferences::GetInt(aPrefName
, kDefaultSnapshotPrefill
);
2941 // The magic -1 is for use only by tests.
2942 if (snapshotPrefill
== -1) {
2943 snapshotPrefill
= INT32_MAX
;
2946 gSnapshotPrefill
= snapshotPrefill
;
2949 void SnapshotGradualPrefillPrefChangedCallback(const char* aPrefName
,
2951 MOZ_ASSERT(NS_IsMainThread());
2952 MOZ_ASSERT(!strcmp(aPrefName
, kSnapshotGradualPrefillPref
));
2953 MOZ_ASSERT(!aClosure
);
2955 int32_t snapshotGradualPrefill
=
2956 Preferences::GetInt(aPrefName
, kDefaultSnapshotGradualPrefill
);
2958 // The magic -1 is for use only by tests.
2959 if (snapshotGradualPrefill
== -1) {
2960 snapshotGradualPrefill
= INT32_MAX
;
2963 gSnapshotGradualPrefill
= snapshotGradualPrefill
;
2966 int64_t GetSnapshotPeakUsagePreincrement(bool aInitial
) {
2967 return aInitial
? StaticPrefs::
2968 dom_storage_snapshot_peak_usage_initial_preincrement()
2970 dom_storage_snapshot_peak_usage_gradual_preincrement();
2973 int64_t GetSnapshotPeakUsageReducedPreincrement(bool aInitial
) {
2976 dom_storage_snapshot_peak_usage_reduced_initial_preincrement()
2978 dom_storage_snapshot_peak_usage_reduced_gradual_preincrement();
2981 void ClientValidationPrefChangedCallback(const char* aPrefName
,
2983 MOZ_ASSERT(NS_IsMainThread());
2984 MOZ_ASSERT(!strcmp(aPrefName
, kClientValidationPref
));
2985 MOZ_ASSERT(!aClosure
);
2987 gClientValidation
= Preferences::GetBool(aPrefName
, kDefaultClientValidation
);
2990 template <typename Condition
>
2991 void InvalidatePrepareDatastoreOpsMatching(const Condition
& aCondition
) {
2992 if (!gPrepareDatastoreOps
) {
2996 for (const auto& prepareDatastoreOp
: *gPrepareDatastoreOps
) {
2997 if (aCondition(*prepareDatastoreOp
)) {
2998 prepareDatastoreOp
->Invalidate();
3003 template <typename Condition
>
3004 void InvalidatePreparedDatastoresMatching(const Condition
& aCondition
) {
3005 if (!gPreparedDatastores
) {
3009 for (const auto& preparedDatastore
: gPreparedDatastores
->Values()) {
3010 MOZ_ASSERT(preparedDatastore
);
3012 if (aCondition(*preparedDatastore
)) {
3013 preparedDatastore
->Invalidate();
3018 template <typename Condition
>
3019 nsTArray
<RefPtr
<Database
>> CollectDatabasesMatching(Condition aCondition
) {
3020 AssertIsOnBackgroundThread();
3022 if (!gLiveDatabases
) {
3023 return nsTArray
<RefPtr
<Database
>>{};
3026 nsTArray
<RefPtr
<Database
>> databases
;
3028 for (const auto& database
: *gLiveDatabases
) {
3029 if (aCondition(*database
)) {
3030 databases
.AppendElement(database
.get());
3037 template <typename Condition
>
3038 void RequestAllowToCloseDatabasesMatching(Condition aCondition
) {
3039 AssertIsOnBackgroundThread();
3041 nsTArray
<RefPtr
<Database
>> databases
= CollectDatabasesMatching(aCondition
);
3043 for (const auto& database
: databases
) {
3044 MOZ_ASSERT(database
);
3046 database
->RequestAllowToClose();
3050 void ForceKillAllDatabases() {
3051 AssertIsOnBackgroundThread();
3053 nsTArray
<RefPtr
<Database
>> databases
=
3054 CollectDatabasesMatching([](const auto&) { return true; });
3056 for (const auto& database
: databases
) {
3057 MOZ_ASSERT(database
);
3059 database
->ForceKill();
3063 bool VerifyPrincipalInfo(const PrincipalInfo
& aPrincipalInfo
,
3064 const PrincipalInfo
& aStoragePrincipalInfo
,
3065 bool aCheckClientPrincipal
) {
3066 AssertIsOnBackgroundThread();
3068 if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo
))) {
3072 // Note that the client prinicpal could have a different spec than the node
3073 // principal but they should have the same origin. It's because the client
3074 // could be initialized when opening the initial about:blank document and pass
3075 // to the newly opened window and reuse over there if the new window has the
3076 // same origin as the initial about:blank document. But, the FilePath could be
3077 // different. Therefore, we have to ignore comparing the Spec of the
3078 // principals if we are verifying clinet principal here. Also, when
3079 // document.domain is set, client principal won't get it. So, we don't compare
3080 // domain for client princpal too.
3081 bool result
= aCheckClientPrincipal
3082 ? StoragePrincipalHelper::
3083 VerifyValidClientPrincipalInfoForPrincipalInfo(
3084 aStoragePrincipalInfo
, aPrincipalInfo
)
3085 : StoragePrincipalHelper::
3086 VerifyValidStoragePrincipalInfoForPrincipalInfo(
3087 aStoragePrincipalInfo
, aPrincipalInfo
);
3088 if (NS_WARN_IF(!result
)) {
3095 bool VerifyClientId(const Maybe
<ContentParentId
>& aContentParentId
,
3096 const Maybe
<PrincipalInfo
>& aPrincipalInfo
,
3097 const Maybe
<nsID
>& aClientId
) {
3098 AssertIsOnBackgroundThread();
3100 if (gClientValidation
) {
3101 if (NS_WARN_IF(aClientId
.isNothing())) {
3105 if (NS_WARN_IF(aPrincipalInfo
.isNothing())) {
3109 RefPtr
<ClientManagerService
> svc
= ClientManagerService::GetInstance();
3110 if (svc
&& NS_WARN_IF(!svc
->HasWindow(
3111 aContentParentId
, aPrincipalInfo
.ref(), aClientId
.ref()))) {
3119 bool VerifyOriginKey(const nsACString
& aOriginKey
,
3120 const PrincipalInfo
& aPrincipalInfo
) {
3121 AssertIsOnBackgroundThread();
3123 QM_TRY_INSPECT((const auto& [originAttrSuffix
, originKey
]),
3124 GenerateOriginKey2(aPrincipalInfo
), false);
3126 Unused
<< originAttrSuffix
;
3128 QM_TRY(OkIf(originKey
== aOriginKey
), false,
3129 ([&originKey
= originKey
, &aOriginKey
](const auto) {
3130 LS_WARNING("originKey (%s) doesn't match passed one (%s)!",
3131 originKey
.get(), nsCString(aOriginKey
).get());
3137 LSInitializationInfo
& MutableInitializationInfoRef(const CreateIfNonExistent
&) {
3138 if (!gInitializationInfo
) {
3139 gInitializationInfo
= new LSInitializationInfo();
3141 return *gInitializationInfo
;
3144 template <typename Func
>
3145 auto ExecuteOriginInitialization(const nsACString
& aOrigin
,
3146 const LSOriginInitialization aInitialization
,
3147 const nsACString
& aContext
, Func
&& aFunc
)
3148 -> std::invoke_result_t
<Func
, const FirstInitializationAttempt
<
3149 LSOriginInitialization
, Nothing
>&> {
3150 return ExecuteInitialization(
3151 MutableInitializationInfoRef(CreateIfNonExistent
{})
3152 .MutableOriginInitializationInfoRef(aOrigin
, CreateIfNonExistent
{}),
3153 aInitialization
, aContext
, std::forward
<Func
>(aFunc
));
3158 /*******************************************************************************
3159 * Exported functions
3160 ******************************************************************************/
3162 void InitializeLocalStorage() {
3163 MOZ_ASSERT(XRE_IsParentProcess());
3164 MOZ_ASSERT(NS_IsMainThread());
3165 MOZ_ASSERT(!gLocalStorageInitialized
);
3167 // XXX Isn't this redundant? It's already done in InitializeQuotaManager.
3168 if (!QuotaManager::IsRunningGTests()) {
3169 // This service has to be started on the main thread currently.
3170 const nsCOMPtr
<mozIStorageService
> ss
=
3171 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID
);
3173 QM_WARNONLY_TRY(OkIf(ss
));
3176 Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback
,
3179 Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback
,
3180 kSnapshotPrefillPref
);
3182 Preferences::RegisterCallbackAndCall(
3183 SnapshotGradualPrefillPrefChangedCallback
, kSnapshotGradualPrefillPref
);
3185 Preferences::RegisterCallbackAndCall(ClientValidationPrefChangedCallback
,
3186 kClientValidationPref
);
3189 gLocalStorageInitialized
= true;
3193 already_AddRefed
<PBackgroundLSDatabaseParent
> AllocPBackgroundLSDatabaseParent(
3194 const PrincipalInfo
& aPrincipalInfo
, const uint32_t& aPrivateBrowsingId
,
3195 const uint64_t& aDatastoreId
) {
3196 AssertIsOnBackgroundThread();
3198 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3202 if (NS_WARN_IF(!gPreparedDatastores
)) {
3203 MOZ_ASSERT_UNLESS_FUZZING(false);
3207 PreparedDatastore
* preparedDatastore
= gPreparedDatastores
->Get(aDatastoreId
);
3208 if (NS_WARN_IF(!preparedDatastore
)) {
3209 MOZ_ASSERT_UNLESS_FUZZING(false);
3213 // If we ever decide to return null from this point on, we need to make sure
3214 // that the datastore is closed and the prepared datastore is removed from the
3215 // gPreparedDatastores hashtable.
3216 // We also assume that IPDL must call RecvPBackgroundLSDatabaseConstructor
3217 // once we return a valid actor in this method.
3219 RefPtr
<Database
> database
=
3220 new Database(aPrincipalInfo
, preparedDatastore
->GetContentParentId(),
3221 preparedDatastore
->Origin(), aPrivateBrowsingId
);
3223 // Transfer ownership to IPDL.
3224 return database
.forget();
3227 bool RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent
* aActor
,
3228 const PrincipalInfo
& aPrincipalInfo
,
3229 const uint32_t& aPrivateBrowsingId
,
3230 const uint64_t& aDatastoreId
) {
3231 AssertIsOnBackgroundThread();
3233 MOZ_ASSERT(gPreparedDatastores
);
3234 MOZ_ASSERT(gPreparedDatastores
->Get(aDatastoreId
));
3235 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3237 // The actor is now completely built (it has a manager, channel and it's
3238 // registered as a subprotocol).
3239 // ActorDestroy will be called if we fail here.
3241 mozilla::UniquePtr
<PreparedDatastore
> preparedDatastore
;
3242 gPreparedDatastores
->Remove(aDatastoreId
, &preparedDatastore
);
3243 MOZ_ASSERT(preparedDatastore
);
3245 auto* database
= static_cast<Database
*>(aActor
);
3247 database
->SetActorAlive(&preparedDatastore
->MutableDatastoreRef());
3249 // It's possible that AbortOperationsForLocks was called before the database
3250 // actor was created and became live. Let the child know that the database is
3252 if (preparedDatastore
->IsInvalidated()) {
3253 database
->RequestAllowToClose();
3259 PBackgroundLSObserverParent
* AllocPBackgroundLSObserverParent(
3260 const uint64_t& aObserverId
) {
3261 AssertIsOnBackgroundThread();
3263 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3267 if (NS_WARN_IF(!gPreparedObsevers
)) {
3268 MOZ_ASSERT_UNLESS_FUZZING(false);
3272 RefPtr
<Observer
> observer
= gPreparedObsevers
->Get(aObserverId
);
3273 if (NS_WARN_IF(!observer
)) {
3274 MOZ_ASSERT_UNLESS_FUZZING(false);
3278 // observer->SetObject(this);
3280 // Transfer ownership to IPDL.
3281 return observer
.forget().take();
3284 bool RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent
* aActor
,
3285 const uint64_t& aObserverId
) {
3286 AssertIsOnBackgroundThread();
3288 MOZ_ASSERT(gPreparedObsevers
);
3289 MOZ_ASSERT(gPreparedObsevers
->GetWeak(aObserverId
));
3291 RefPtr
<Observer
> observer
;
3292 gPreparedObsevers
->Remove(aObserverId
, observer
.StartAssignment());
3294 if (!gPreparedObsevers
->Count()) {
3295 gPreparedObsevers
= nullptr;
3299 gObservers
= new ObserverHashtable();
3302 const auto notNullObserver
= WrapNotNull(observer
.get());
3304 nsTArray
<NotNull
<Observer
*>>* const array
=
3305 gObservers
->GetOrInsertNew(notNullObserver
->Origin());
3306 array
->AppendElement(notNullObserver
);
3308 if (RefPtr
<Datastore
> datastore
= GetDatastore(observer
->Origin())) {
3309 datastore
->NoteChangedObserverArray(*array
);
3315 bool DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent
* aActor
) {
3316 AssertIsOnBackgroundThread();
3319 // Transfer ownership back from IPDL.
3320 RefPtr
<Observer
> actor
= dont_AddRef(static_cast<Observer
*>(aActor
));
3325 PBackgroundLSRequestParent
* AllocPBackgroundLSRequestParent(
3326 PBackgroundParent
* aBackgroundActor
, const LSRequestParams
& aParams
) {
3327 AssertIsOnBackgroundThread();
3328 MOZ_ASSERT(aParams
.type() != LSRequestParams::T__None
);
3330 if (NS_WARN_IF(!NextGenLocalStorageEnabled())) {
3334 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3338 Maybe
<ContentParentId
> contentParentId
;
3340 uint64_t childID
= BackgroundParent::GetChildID(aBackgroundActor
);
3342 contentParentId
= Some(ContentParentId(childID
));
3345 RefPtr
<LSRequestBase
> actor
;
3347 switch (aParams
.type()) {
3348 case LSRequestParams::TLSRequestPreloadDatastoreParams
:
3349 case LSRequestParams::TLSRequestPrepareDatastoreParams
: {
3350 RefPtr
<PrepareDatastoreOp
> prepareDatastoreOp
=
3351 new PrepareDatastoreOp(aParams
, contentParentId
);
3353 if (!gPrepareDatastoreOps
) {
3354 gPrepareDatastoreOps
= new PrepareDatastoreOpArray();
3356 gPrepareDatastoreOps
->AppendElement(
3357 WrapNotNullUnchecked(prepareDatastoreOp
.get()));
3359 actor
= std::move(prepareDatastoreOp
);
3364 case LSRequestParams::TLSRequestPrepareObserverParams
: {
3365 RefPtr
<PrepareObserverOp
> prepareObserverOp
=
3366 new PrepareObserverOp(aParams
, contentParentId
);
3368 actor
= std::move(prepareObserverOp
);
3374 MOZ_CRASH("Should never get here!");
3377 // Transfer ownership to IPDL.
3378 return actor
.forget().take();
3381 bool RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent
* aActor
,
3382 const LSRequestParams
& aParams
) {
3383 AssertIsOnBackgroundThread();
3385 MOZ_ASSERT(aParams
.type() != LSRequestParams::T__None
);
3386 MOZ_ASSERT(NextGenLocalStorageEnabled());
3387 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3389 // The actor is now completely built.
3391 auto* op
= static_cast<LSRequestBase
*>(aActor
);
3398 bool DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent
* aActor
) {
3399 AssertIsOnBackgroundThread();
3401 // Transfer ownership back from IPDL.
3402 RefPtr
<LSRequestBase
> actor
=
3403 dont_AddRef(static_cast<LSRequestBase
*>(aActor
));
3408 PBackgroundLSSimpleRequestParent
* AllocPBackgroundLSSimpleRequestParent(
3409 PBackgroundParent
* aBackgroundActor
, const LSSimpleRequestParams
& aParams
) {
3410 AssertIsOnBackgroundThread();
3411 MOZ_ASSERT(aParams
.type() != LSSimpleRequestParams::T__None
);
3413 if (NS_WARN_IF(!NextGenLocalStorageEnabled())) {
3417 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
3421 Maybe
<ContentParentId
> contentParentId
;
3423 uint64_t childID
= BackgroundParent::GetChildID(aBackgroundActor
);
3425 contentParentId
= Some(ContentParentId(childID
));
3428 RefPtr
<LSSimpleRequestBase
> actor
;
3430 switch (aParams
.type()) {
3431 case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams
: {
3432 RefPtr
<PreloadedOp
> preloadedOp
=
3433 new PreloadedOp(aParams
, contentParentId
);
3435 actor
= std::move(preloadedOp
);
3440 case LSSimpleRequestParams::TLSSimpleRequestGetStateParams
: {
3441 RefPtr
<GetStateOp
> getStateOp
= new GetStateOp(aParams
, contentParentId
);
3443 actor
= std::move(getStateOp
);
3449 MOZ_CRASH("Should never get here!");
3452 // Transfer ownership to IPDL.
3453 return actor
.forget().take();
3456 bool RecvPBackgroundLSSimpleRequestConstructor(
3457 PBackgroundLSSimpleRequestParent
* aActor
,
3458 const LSSimpleRequestParams
& aParams
) {
3459 AssertIsOnBackgroundThread();
3461 MOZ_ASSERT(aParams
.type() != LSSimpleRequestParams::T__None
);
3462 MOZ_ASSERT(NextGenLocalStorageEnabled());
3463 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
3465 // The actor is now completely built.
3467 auto* op
= static_cast<LSSimpleRequestBase
*>(aActor
);
3474 bool DeallocPBackgroundLSSimpleRequestParent(
3475 PBackgroundLSSimpleRequestParent
* aActor
) {
3476 AssertIsOnBackgroundThread();
3478 // Transfer ownership back from IPDL.
3479 RefPtr
<LSSimpleRequestBase
> actor
=
3480 dont_AddRef(static_cast<LSSimpleRequestBase
*>(aActor
));
3485 namespace localstorage
{
3487 already_AddRefed
<mozilla::dom::quota::Client
> CreateQuotaClient() {
3488 AssertIsOnBackgroundThread();
3489 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
3491 RefPtr
<QuotaClient
> client
= new QuotaClient();
3492 return client
.forget();
3495 } // namespace localstorage
3497 /*******************************************************************************
3498 * DatastoreWriteOptimizer
3499 ******************************************************************************/
3501 void DatastoreWriteOptimizer::ApplyAndReset(
3502 nsTArray
<LSItemInfo
>& aOrderedItems
) {
3503 AssertIsOnOwningThread();
3505 // The mWriteInfos hash table contains all write infos, but it keeps them in
3506 // an arbitrary order, which means write infos need to be sorted before being
3507 // processed. However, the order is not important for deletions and normal
3508 // updates. Usually, filtering out deletions and updates would require extra
3509 // work, but we have to check the hash table for each ordered item anyway, so
3510 // we can remove the write info if it is a deletion or update without adding
3511 // extra overhead. In the end, only insertions need to be sorted before being
3514 if (mTruncateInfo
) {
3515 aOrderedItems
.Clear();
3516 mTruncateInfo
= nullptr;
3519 for (int32_t index
= aOrderedItems
.Length() - 1; index
>= 0; index
--) {
3520 LSItemInfo
& item
= aOrderedItems
[index
];
3522 if (auto entry
= mWriteInfos
.Lookup(item
.key())) {
3523 WriteInfo
* writeInfo
= entry
->get();
3525 switch (writeInfo
->GetType()) {
3526 case WriteInfo::DeleteItem
:
3527 aOrderedItems
.RemoveElementAt(index
);
3531 case WriteInfo::UpdateItem
: {
3532 auto updateItemInfo
= static_cast<UpdateItemInfo
*>(writeInfo
);
3533 if (updateItemInfo
->UpdateWithMove()) {
3534 // See the comment in LSWriteOptimizer::InsertItem for more details
3535 // about the UpdateWithMove flag.
3537 aOrderedItems
.RemoveElementAt(index
);
3538 entry
.Data() = MakeUnique
<InsertItemInfo
>(
3539 updateItemInfo
->SerialNumber(), updateItemInfo
->GetKey(),
3540 updateItemInfo
->GetValue());
3542 item
.value() = updateItemInfo
->GetValue();
3548 case WriteInfo::InsertItem
:
3552 MOZ_CRASH("Bad type!");
3557 nsTArray
<NotNull
<WriteInfo
*>> writeInfos
;
3558 GetSortedWriteInfos(writeInfos
);
3560 for (WriteInfo
* writeInfo
: writeInfos
) {
3561 MOZ_ASSERT(writeInfo
->GetType() == WriteInfo::InsertItem
);
3563 auto insertItemInfo
= static_cast<InsertItemInfo
*>(writeInfo
);
3565 LSItemInfo
* itemInfo
= aOrderedItems
.AppendElement();
3566 itemInfo
->key() = insertItemInfo
->GetKey();
3567 itemInfo
->value() = insertItemInfo
->GetValue();
3570 mWriteInfos
.Clear();
3573 /*******************************************************************************
3574 * ConnectionWriteOptimizer
3575 ******************************************************************************/
3577 Result
<int64_t, nsresult
> ConnectionWriteOptimizer::Perform(
3578 Connection
* aConnection
, bool aShadowWrites
) {
3579 AssertIsOnGlobalConnectionThread();
3580 MOZ_ASSERT(aConnection
);
3582 // The order of elements is not stored in the database, so write infos don't
3583 // need to be sorted before being processed.
3585 if (mTruncateInfo
) {
3586 QM_TRY(MOZ_TO_RESULT(PerformTruncate(aConnection
, aShadowWrites
)));
3589 for (const auto& entry
: mWriteInfos
) {
3590 const WriteInfo
* const writeInfo
= entry
.GetWeak();
3592 switch (writeInfo
->GetType()) {
3593 case WriteInfo::InsertItem
:
3594 case WriteInfo::UpdateItem
: {
3595 const auto* const insertItemInfo
=
3596 static_cast<const InsertItemInfo
*>(writeInfo
);
3598 QM_TRY(MOZ_TO_RESULT(PerformInsertOrUpdate(
3599 aConnection
, aShadowWrites
, insertItemInfo
->GetKey(),
3600 insertItemInfo
->GetValue())));
3605 case WriteInfo::DeleteItem
: {
3606 const auto* const deleteItemInfo
=
3607 static_cast<const DeleteItemInfo
*>(writeInfo
);
3609 QM_TRY(MOZ_TO_RESULT(PerformDelete(aConnection
, aShadowWrites
,
3610 deleteItemInfo
->GetKey())));
3616 MOZ_CRASH("Bad type!");
3620 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteCachedStatement(
3622 "SET usage = usage + :delta"_ns
,
3623 [this](auto& stmt
) -> Result
<Ok
, nsresult
> {
3624 QM_TRY(MOZ_TO_RESULT(stmt
.BindInt64ByName("delta"_ns
, mTotalDelta
)));
3629 QM_TRY_INSPECT(const auto& stmt
, CreateAndExecuteSingleStepStatement
<
3630 SingleStepResult::ReturnNullIfNoResult
>(
3631 aConnection
->MutableStorageConnection(),
3632 "SELECT usage FROM database"_ns
));
3634 QM_TRY(OkIf(stmt
), Err(NS_ERROR_FAILURE
));
3636 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*stmt
, GetInt64
, 0));
3639 nsresult
ConnectionWriteOptimizer::PerformInsertOrUpdate(
3640 Connection
* aConnection
, bool aShadowWrites
, const nsAString
& aKey
,
3641 const LSValue
& aValue
) {
3642 AssertIsOnGlobalConnectionThread();
3643 MOZ_ASSERT(aConnection
);
3645 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteCachedStatement(
3646 "INSERT OR REPLACE INTO data (key, utf16_length, conversion_type, "
3647 "compression_type, value) "
3648 "VALUES(:key, :utf16_length, :conversion_type, :compression_type, :value)"_ns
,
3649 [&aKey
, &aValue
](auto& stmt
) -> Result
<Ok
, nsresult
> {
3650 QM_TRY(MOZ_TO_RESULT(stmt
.BindStringByName("key"_ns
, aKey
)));
3651 QM_TRY(MOZ_TO_RESULT(
3652 stmt
.BindInt32ByName("utf16_length"_ns
, aValue
.UTF16Length())));
3653 QM_TRY(MOZ_TO_RESULT(stmt
.BindInt32ByName(
3654 "conversion_type"_ns
,
3655 static_cast<int32_t>(aValue
.GetConversionType()))));
3656 QM_TRY(MOZ_TO_RESULT(stmt
.BindInt32ByName(
3657 "compression_type"_ns
,
3658 static_cast<int32_t>(aValue
.GetCompressionType()))));
3660 if (0u == aValue
.Length()) { // Otherwise empty string becomes null
3661 QM_TRY(MOZ_TO_RESULT(
3662 stmt
.BindUTF8StringByName("value"_ns
, aValue
.AsCString())));
3664 QM_TRY(MOZ_TO_RESULT(
3665 stmt
.BindUTF8StringAsBlobByName("value"_ns
, aValue
.AsCString())));
3671 if (!aShadowWrites
) {
3675 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteCachedStatement(
3676 "INSERT OR REPLACE INTO shadow.webappsstore2 "
3677 "(originAttributes, originKey, scope, key, value) "
3678 "VALUES (:originAttributes, :originKey, :scope, :key, :value) "_ns
,
3679 [&aConnection
, &aKey
, &aValue
](auto& stmt
) -> Result
<Ok
, nsresult
> {
3680 using ConversionType
= LSValue::ConversionType
;
3681 using CompressionType
= LSValue::CompressionType
;
3683 const ArchivedOriginScope
* const archivedOriginScope
=
3684 aConnection
->GetArchivedOriginScope();
3686 QM_TRY(MOZ_TO_RESULT(archivedOriginScope
->BindToStatement(&stmt
)));
3688 QM_TRY(MOZ_TO_RESULT(stmt
.BindUTF8StringByName(
3689 "scope"_ns
, Scheme0Scope(archivedOriginScope
->OriginSuffix(),
3690 archivedOriginScope
->OriginNoSuffix()))));
3692 QM_TRY(MOZ_TO_RESULT(stmt
.BindStringByName("key"_ns
, aKey
)));
3695 CompressionType::UNCOMPRESSED
!= aValue
.GetCompressionType();
3696 bool isAlreadyConverted
=
3697 ConversionType::NONE
!= aValue
.GetConversionType();
3700 const nsCString
& valueBlob
= aValue
.AsCString();
3702 QM_TRY(OkIf(SnappyUncompress(valueBlob
, buffer
)),
3703 Err(NS_ERROR_FAILURE
));
3705 const nsCString
& value
= isCompressed
? buffer
: valueBlob
;
3707 // For shadow writes, we undo buffer swap and convert destructively
3708 nsCString unconverted
;
3709 if (!isAlreadyConverted
) {
3711 QM_TRY(OkIf(PutCStringBytesToString(value
, converted
)),
3712 Err(NS_ERROR_OUT_OF_MEMORY
));
3713 QM_TRY(OkIf(CopyUTF16toUTF8(converted
, unconverted
, fallible
)),
3714 Err(NS_ERROR_OUT_OF_MEMORY
)); // Corrupt invalid data
3716 const nsCString
& untransformed
=
3717 (!isAlreadyConverted
) ? unconverted
: value
;
3719 QM_TRY(MOZ_TO_RESULT(
3720 stmt
.BindUTF8StringByName("value"_ns
, untransformed
)));
3728 nsresult
ConnectionWriteOptimizer::PerformDelete(Connection
* aConnection
,
3730 const nsAString
& aKey
) {
3731 AssertIsOnGlobalConnectionThread();
3732 MOZ_ASSERT(aConnection
);
3734 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteCachedStatement(
3736 "WHERE key = :key;"_ns
,
3737 [&aKey
](auto& stmt
) -> Result
<Ok
, nsresult
> {
3738 QM_TRY(MOZ_TO_RESULT(stmt
.BindStringByName("key"_ns
, aKey
)));
3743 if (!aShadowWrites
) {
3747 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteCachedStatement(
3748 "DELETE FROM shadow.webappsstore2 "
3749 "WHERE originAttributes = :originAttributes "
3750 "AND originKey = :originKey "
3751 "AND key = :key;"_ns
,
3752 [&aConnection
, &aKey
](auto& stmt
) -> Result
<Ok
, nsresult
> {
3753 QM_TRY(MOZ_TO_RESULT(
3754 aConnection
->GetArchivedOriginScope()->BindToStatement(&stmt
)));
3756 QM_TRY(MOZ_TO_RESULT(stmt
.BindStringByName("key"_ns
, aKey
)));
3764 nsresult
ConnectionWriteOptimizer::PerformTruncate(Connection
* aConnection
,
3765 bool aShadowWrites
) {
3766 AssertIsOnGlobalConnectionThread();
3767 MOZ_ASSERT(aConnection
);
3769 QM_TRY(MOZ_TO_RESULT(
3770 aConnection
->ExecuteCachedStatement("DELETE FROM data;"_ns
)));
3772 if (!aShadowWrites
) {
3776 QM_TRY(MOZ_TO_RESULT(aConnection
->ExecuteCachedStatement(
3777 "DELETE FROM shadow.webappsstore2 "
3778 "WHERE originAttributes = :originAttributes "
3779 "AND originKey = :originKey;"_ns
,
3780 [&aConnection
](auto& stmt
) -> Result
<Ok
, nsresult
> {
3781 QM_TRY(MOZ_TO_RESULT(
3782 aConnection
->GetArchivedOriginScope()->BindToStatement(&stmt
)));
3790 /*******************************************************************************
3791 * DatastoreOperationBase
3792 ******************************************************************************/
3794 /*******************************************************************************
3795 * ConnectionDatastoreOperationBase
3796 ******************************************************************************/
3798 ConnectionDatastoreOperationBase::ConnectionDatastoreOperationBase(
3799 Connection
* aConnection
, bool aEnsureStorageConnection
)
3800 : mConnection(aConnection
),
3801 mEnsureStorageConnection(aEnsureStorageConnection
) {
3802 MOZ_ASSERT(aConnection
);
3805 ConnectionDatastoreOperationBase::~ConnectionDatastoreOperationBase() {
3806 MOZ_ASSERT(!mConnection
,
3807 "ConnectionDatabaseOperationBase::Cleanup() was not called by a "
3811 void ConnectionDatastoreOperationBase::Cleanup() {
3812 AssertIsOnOwningThread();
3813 MOZ_ASSERT(mConnection
);
3815 mConnection
= nullptr;
3820 void ConnectionDatastoreOperationBase::OnSuccess() { AssertIsOnOwningThread(); }
3822 void ConnectionDatastoreOperationBase::OnFailure(nsresult aResultCode
) {
3823 AssertIsOnOwningThread();
3824 MOZ_ASSERT(NS_FAILED(aResultCode
));
3827 void ConnectionDatastoreOperationBase::RunOnConnectionThread() {
3828 AssertIsOnGlobalConnectionThread();
3829 MOZ_ASSERT(mConnection
);
3830 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
3832 if (!MayProceedOnNonOwningThread()) {
3833 SetFailureCode(NS_ERROR_ABORT
);
3835 nsresult rv
= NS_OK
;
3837 // The boolean flag is only used by the CloseOp to avoid creating empty
3839 if (mEnsureStorageConnection
) {
3840 rv
= mConnection
->EnsureStorageConnection();
3841 if (NS_WARN_IF(NS_FAILED(rv
))) {
3844 MOZ_ASSERT(mConnection
->HasStorageConnection());
3848 if (NS_SUCCEEDED(rv
)) {
3849 rv
= DoDatastoreWork();
3850 if (NS_FAILED(rv
)) {
3856 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
));
3859 void ConnectionDatastoreOperationBase::RunOnOwningThread() {
3860 AssertIsOnOwningThread();
3861 MOZ_ASSERT(mConnection
);
3863 if (!MayProceed()) {
3864 MaybeSetFailureCode(NS_ERROR_ABORT
);
3867 if (NS_SUCCEEDED(ResultCode())) {
3870 OnFailure(ResultCode());
3877 ConnectionDatastoreOperationBase::Run() {
3878 if (IsOnGlobalConnectionThread()) {
3879 RunOnConnectionThread();
3881 RunOnOwningThread();
3887 /*******************************************************************************
3888 * Connection implementation
3889 ******************************************************************************/
3891 Connection::Connection(ConnectionThread
* aConnectionThread
,
3892 const OriginMetadata
& aOriginMetadata
,
3893 UniquePtr
<ArchivedOriginScope
>&& aArchivedOriginScope
,
3894 bool aDatabaseWasNotAvailable
)
3895 : mConnectionThread(aConnectionThread
),
3896 mQuotaClient(QuotaClient::GetInstance()),
3897 mArchivedOriginScope(std::move(aArchivedOriginScope
)),
3898 mOriginMetadata(aOriginMetadata
),
3899 mDatabaseWasNotAvailable(aDatabaseWasNotAvailable
),
3900 mHasCreatedDatabase(false),
3901 mFlushScheduled(false)
3904 mInUpdateBatch(false),
3908 AssertIsOnOwningThread();
3909 MOZ_ASSERT(!aOriginMetadata
.mGroup
.IsEmpty());
3910 MOZ_ASSERT(!aOriginMetadata
.mOrigin
.IsEmpty());
3913 Connection::~Connection() {
3914 AssertIsOnOwningThread();
3915 MOZ_ASSERT(!mFlushScheduled
);
3916 MOZ_ASSERT(!mInUpdateBatch
);
3917 MOZ_ASSERT(mFinished
);
3920 void Connection::Dispatch(ConnectionDatastoreOperationBase
* aOp
) {
3921 AssertIsOnOwningThread();
3922 MOZ_ASSERT(mConnectionThread
);
3924 MOZ_ALWAYS_SUCCEEDS(
3925 mConnectionThread
->mThread
->Dispatch(aOp
, NS_DISPATCH_NORMAL
));
3928 void Connection::Close(nsIRunnable
* aCallback
) {
3929 AssertIsOnOwningThread();
3930 MOZ_ASSERT(aCallback
);
3932 if (mFlushScheduled
) {
3933 MOZ_ASSERT(mFlushTimer
);
3934 MOZ_ALWAYS_SUCCEEDS(mFlushTimer
->Cancel());
3938 mFlushTimer
= nullptr;
3941 RefPtr
<CloseOp
> op
= new CloseOp(this, aCallback
);
3946 void Connection::SetItem(const nsString
& aKey
, const LSValue
& aValue
,
3947 int64_t aDelta
, bool aIsNewItem
) {
3948 AssertIsOnOwningThread();
3949 MOZ_ASSERT(mInUpdateBatch
);
3952 mWriteOptimizer
.InsertItem(aKey
, aValue
, aDelta
);
3954 mWriteOptimizer
.UpdateItem(aKey
, aValue
, aDelta
);
3958 void Connection::RemoveItem(const nsString
& aKey
, int64_t aDelta
) {
3959 AssertIsOnOwningThread();
3960 MOZ_ASSERT(mInUpdateBatch
);
3962 mWriteOptimizer
.DeleteItem(aKey
, aDelta
);
3965 void Connection::Clear(int64_t aDelta
) {
3966 AssertIsOnOwningThread();
3967 MOZ_ASSERT(mInUpdateBatch
);
3969 mWriteOptimizer
.Truncate(aDelta
);
3972 void Connection::BeginUpdateBatch() {
3973 AssertIsOnOwningThread();
3974 MOZ_ASSERT(!mInUpdateBatch
);
3977 mInUpdateBatch
= true;
3981 void Connection::EndUpdateBatch() {
3982 AssertIsOnOwningThread();
3983 MOZ_ASSERT(mInUpdateBatch
);
3985 if (mWriteOptimizer
.HasWrites() && !mFlushScheduled
) {
3990 mInUpdateBatch
= false;
3994 nsresult
Connection::EnsureStorageConnection() {
3995 AssertIsOnGlobalConnectionThread();
3997 if (HasStorageConnection()) {
4001 QuotaManager
* quotaManager
= QuotaManager::Get();
4002 MOZ_ASSERT(quotaManager
);
4004 if (!mDatabaseWasNotAvailable
|| mHasCreatedDatabase
) {
4005 MOZ_ASSERT(mOriginMetadata
.mPersistenceType
== PERSISTENCE_TYPE_DEFAULT
);
4007 QM_TRY_INSPECT(const auto& directoryEntry
,
4008 quotaManager
->GetOriginDirectory(mOriginMetadata
));
4010 QM_TRY(MOZ_TO_RESULT(directoryEntry
->Append(
4011 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME
))));
4013 QM_TRY(MOZ_TO_RESULT(directoryEntry
->GetPath(mDirectoryPath
)));
4014 QM_TRY(MOZ_TO_RESULT(directoryEntry
->Append(kDataFileName
)));
4017 const auto& databaseFilePath
,
4018 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString
, directoryEntry
, GetPath
));
4020 QM_TRY_UNWRAP(auto storageConnection
,
4021 GetStorageConnection(databaseFilePath
));
4022 LazyInit(WrapMovingNotNull(std::move(storageConnection
)));
4027 RefPtr
<InitTemporaryOriginHelper
> helper
=
4028 new InitTemporaryOriginHelper(mOriginMetadata
);
4030 QM_TRY_INSPECT(const auto& originDirectoryPath
,
4031 helper
->BlockAndReturnOriginDirectoryPath());
4033 QM_TRY_INSPECT(const auto& directoryEntry
,
4034 QM_NewLocalFile(originDirectoryPath
));
4036 QM_TRY(MOZ_TO_RESULT(directoryEntry
->Append(
4037 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME
))));
4039 QM_TRY(MOZ_TO_RESULT(directoryEntry
->GetPath(mDirectoryPath
)));
4041 QM_TRY_INSPECT(const bool& exists
,
4042 MOZ_TO_RESULT_INVOKE_MEMBER(directoryEntry
, Exists
));
4046 MOZ_TO_RESULT(directoryEntry
->Create(nsIFile::DIRECTORY_TYPE
, 0755)));
4049 QM_TRY(MOZ_TO_RESULT(directoryEntry
->Append(kDataFileName
)));
4053 QM_TRY_INSPECT(const bool& exists
,
4054 MOZ_TO_RESULT_INVOKE_MEMBER(directoryEntry
, Exists
));
4056 MOZ_ASSERT(!exists
);
4060 QM_TRY_INSPECT(const auto& usageFile
, GetUsageFile(mDirectoryPath
));
4062 nsCOMPtr
<mozIStorageConnection
> storageConnection
;
4064 auto autoRemove
= MakeScopeExit([&storageConnection
, &directoryEntry
] {
4065 if (storageConnection
) {
4066 MOZ_ALWAYS_SUCCEEDS(storageConnection
->Close());
4069 nsresult rv
= directoryEntry
->Remove(false);
4070 if (rv
!= NS_ERROR_FILE_NOT_FOUND
&& NS_FAILED(rv
)) {
4071 NS_WARNING("Failed to remove database file!");
4075 QM_TRY_UNWRAP(storageConnection
, CreateStorageConnectionWithRecovery(
4076 *directoryEntry
, *usageFile
, Origin(),
4077 [] { MOZ_ASSERT_UNREACHABLE(); }));
4079 MOZ_ASSERT(mQuotaClient
);
4081 MutexAutoLock
shadowDatabaseLock(mQuotaClient
->ShadowDatabaseMutex());
4083 nsCOMPtr
<mozIStorageConnection
> shadowConnection
;
4084 if (!gInitializedShadowStorage
) {
4085 QM_TRY_UNWRAP(shadowConnection
,
4086 CreateShadowStorageConnection(quotaManager
->GetBasePath()));
4088 gInitializedShadowStorage
= true;
4091 autoRemove
.release();
4093 if (!mHasCreatedDatabase
) {
4094 mHasCreatedDatabase
= true;
4097 LazyInit(WrapMovingNotNull(std::move(storageConnection
)));
4102 void Connection::CloseStorageConnection() {
4103 AssertIsOnGlobalConnectionThread();
4105 CachingDatabaseConnection::Close();
4108 nsresult
Connection::BeginWriteTransaction() {
4109 AssertIsOnGlobalConnectionThread();
4110 MOZ_ASSERT(HasStorageConnection());
4112 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("BEGIN IMMEDIATE;"_ns
)));
4117 nsresult
Connection::CommitWriteTransaction() {
4118 AssertIsOnGlobalConnectionThread();
4119 MOZ_ASSERT(HasStorageConnection());
4121 QM_TRY(MOZ_TO_RESULT(ExecuteCachedStatement("COMMIT;"_ns
)));
4126 nsresult
Connection::RollbackWriteTransaction() {
4127 AssertIsOnGlobalConnectionThread();
4128 MOZ_ASSERT(HasStorageConnection());
4130 QM_TRY_INSPECT(const auto& stmt
, BorrowCachedStatement("ROLLBACK;"_ns
));
4132 // This may fail if SQLite already rolled back the transaction so ignore any
4134 Unused
<< stmt
->Execute();
4139 void Connection::ScheduleFlush() {
4140 AssertIsOnOwningThread();
4141 MOZ_ASSERT(mWriteOptimizer
.HasWrites());
4142 MOZ_ASSERT(!mFlushScheduled
);
4145 mFlushTimer
= NS_NewTimer();
4146 MOZ_ASSERT(mFlushTimer
);
4149 MOZ_ALWAYS_SUCCEEDS(mFlushTimer
->InitWithNamedFuncCallback(
4150 FlushTimerCallback
, this, kFlushTimeoutMs
, nsITimer::TYPE_ONE_SHOT
,
4151 "Connection::FlushTimerCallback"));
4153 mFlushScheduled
= true;
4156 void Connection::Flush() {
4157 AssertIsOnOwningThread();
4158 MOZ_ASSERT(mFlushScheduled
);
4160 if (mWriteOptimizer
.HasWrites()) {
4161 RefPtr
<FlushOp
> op
= new FlushOp(this, std::move(mWriteOptimizer
));
4166 mFlushScheduled
= false;
4170 void Connection::FlushTimerCallback(nsITimer
* aTimer
, void* aClosure
) {
4171 MOZ_ASSERT(aClosure
);
4173 auto* self
= static_cast<Connection
*>(aClosure
);
4175 MOZ_ASSERT(self
->mFlushScheduled
);
4180 Result
<nsString
, nsresult
>
4181 Connection::InitTemporaryOriginHelper::BlockAndReturnOriginDirectoryPath() {
4182 AssertIsOnGlobalConnectionThread();
4184 QuotaManager
* quotaManager
= QuotaManager::Get();
4185 MOZ_ASSERT(quotaManager
);
4187 MOZ_ALWAYS_SUCCEEDS(
4188 quotaManager
->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL
));
4190 mozilla::MonitorAutoLock
lock(mMonitor
);
4195 QM_TRY(MOZ_TO_RESULT(mIOThreadResultCode
));
4197 return mOriginDirectoryPath
;
4200 nsresult
Connection::InitTemporaryOriginHelper::RunOnIOThread() {
4201 AssertIsOnIOThread();
4203 QuotaManager
* quotaManager
= QuotaManager::Get();
4204 MOZ_ASSERT(quotaManager
);
4206 QM_TRY_INSPECT(const auto& directoryEntry
,
4208 ->EnsureTemporaryOriginIsInitialized(
4209 PERSISTENCE_TYPE_DEFAULT
, mOriginMetadata
)
4210 .map([](const auto& res
) { return res
.first
; }));
4212 QM_TRY(MOZ_TO_RESULT(directoryEntry
->GetPath(mOriginDirectoryPath
)));
4218 Connection::InitTemporaryOriginHelper::Run() {
4219 AssertIsOnIOThread();
4221 nsresult rv
= RunOnIOThread();
4222 if (NS_WARN_IF(NS_FAILED(rv
))) {
4223 mIOThreadResultCode
= rv
;
4226 mozilla::MonitorAutoLock
lock(mMonitor
);
4227 MOZ_ASSERT(mWaiting
);
4235 Connection::FlushOp::FlushOp(Connection
* aConnection
,
4236 ConnectionWriteOptimizer
&& aWriteOptimizer
)
4237 : ConnectionDatastoreOperationBase(aConnection
),
4238 mWriteOptimizer(std::move(aWriteOptimizer
)),
4239 mShadowWrites(gShadowWrites
) {}
4241 nsresult
Connection::FlushOp::DoDatastoreWork() {
4242 AssertIsOnGlobalConnectionThread();
4243 MOZ_ASSERT(mConnection
);
4245 AutoWriteTransaction
autoWriteTransaction(mShadowWrites
);
4247 QM_TRY(MOZ_TO_RESULT(autoWriteTransaction
.Start(mConnection
)));
4249 QM_TRY_INSPECT(const int64_t& usage
,
4250 mWriteOptimizer
.Perform(mConnection
, mShadowWrites
));
4252 QM_TRY_INSPECT(const auto& usageFile
,
4253 GetUsageFile(mConnection
->DirectoryPath()));
4255 QM_TRY_INSPECT(const auto& usageJournalFile
,
4256 GetUsageJournalFile(mConnection
->DirectoryPath()));
4258 QM_TRY(MOZ_TO_RESULT(UpdateUsageFile(usageFile
, usageJournalFile
, usage
)));
4260 QM_TRY(MOZ_TO_RESULT(autoWriteTransaction
.Commit()));
4262 QM_TRY(MOZ_TO_RESULT(usageJournalFile
->Remove(false)));
4267 void Connection::FlushOp::Cleanup() {
4268 AssertIsOnOwningThread();
4270 mWriteOptimizer
.Reset();
4272 MOZ_ASSERT(!mWriteOptimizer
.HasWrites());
4274 ConnectionDatastoreOperationBase::Cleanup();
4277 nsresult
Connection::CloseOp::DoDatastoreWork() {
4278 AssertIsOnGlobalConnectionThread();
4279 MOZ_ASSERT(mConnection
);
4281 if (mConnection
->HasStorageConnection()) {
4282 mConnection
->CloseStorageConnection();
4288 void Connection::CloseOp::Cleanup() {
4289 AssertIsOnOwningThread();
4290 MOZ_ASSERT(mConnection
);
4292 mConnection
->mConnectionThread
->mConnections
.Remove(mConnection
->Origin());
4295 MOZ_ASSERT(!mConnection
->mFinished
);
4296 mConnection
->mFinished
= true;
4299 nsCOMPtr
<nsIRunnable
> callback
;
4300 mCallback
.swap(callback
);
4304 ConnectionDatastoreOperationBase::Cleanup();
4307 /*******************************************************************************
4308 * ConnectionThread implementation
4309 ******************************************************************************/
4311 ConnectionThread::ConnectionThread() {
4312 AssertIsOnOwningThread();
4313 AssertIsOnBackgroundThread();
4315 MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread("LS Thread", getter_AddRefs(mThread
)));
4318 ConnectionThread::~ConnectionThread() {
4319 AssertIsOnOwningThread();
4320 MOZ_ASSERT(!mConnections
.Count());
4323 bool ConnectionThread::IsOnConnectionThread() {
4324 MOZ_ASSERT(mThread
);
4327 return NS_SUCCEEDED(mThread
->IsOnCurrentThread(¤t
)) && current
;
4330 void ConnectionThread::AssertIsOnConnectionThread() {
4331 MOZ_ASSERT(IsOnConnectionThread());
4334 already_AddRefed
<Connection
> ConnectionThread::CreateConnection(
4335 const OriginMetadata
& aOriginMetadata
,
4336 UniquePtr
<ArchivedOriginScope
>&& aArchivedOriginScope
,
4337 bool aDatabaseWasNotAvailable
) {
4338 AssertIsOnOwningThread();
4339 MOZ_ASSERT(!aOriginMetadata
.mOrigin
.IsEmpty());
4340 MOZ_ASSERT(!mConnections
.Contains(aOriginMetadata
.mOrigin
));
4342 RefPtr
<Connection
> connection
=
4343 new Connection(this, aOriginMetadata
, std::move(aArchivedOriginScope
),
4344 aDatabaseWasNotAvailable
);
4345 mConnections
.InsertOrUpdate(aOriginMetadata
.mOrigin
, RefPtr
{connection
});
4347 return connection
.forget();
4350 void ConnectionThread::Shutdown() {
4351 AssertIsOnOwningThread();
4352 MOZ_ASSERT(mThread
);
4354 mThread
->Shutdown();
4357 /*******************************************************************************
4359 ******************************************************************************/
4361 Datastore::Datastore(const OriginMetadata
& aOriginMetadata
,
4362 uint32_t aPrivateBrowsingId
, int64_t aUsage
,
4363 int64_t aSizeOfKeys
, int64_t aSizeOfItems
,
4364 RefPtr
<DirectoryLock
>&& aDirectoryLock
,
4365 RefPtr
<Connection
>&& aConnection
,
4366 RefPtr
<QuotaObject
>&& aQuotaObject
,
4367 nsTHashMap
<nsStringHashKey
, LSValue
>& aValues
,
4368 nsTArray
<LSItemInfo
>&& aOrderedItems
)
4369 : mDirectoryLock(std::move(aDirectoryLock
)),
4370 mConnection(std::move(aConnection
)),
4371 mQuotaObject(std::move(aQuotaObject
)),
4372 mOrderedItems(std::move(aOrderedItems
)),
4373 mOriginMetadata(aOriginMetadata
),
4374 mPrivateBrowsingId(aPrivateBrowsingId
),
4376 mUpdateBatchUsage(-1),
4377 mSizeOfKeys(aSizeOfKeys
),
4378 mSizeOfItems(aSizeOfItems
),
4380 mInUpdateBatch(false),
4381 mHasLivePrivateDatastore(false) {
4382 AssertIsOnBackgroundThread();
4384 mValues
.SwapElements(aValues
);
4387 Datastore::~Datastore() {
4388 AssertIsOnBackgroundThread();
4389 MOZ_ASSERT(mClosed
);
4392 void Datastore::Close() {
4393 AssertIsOnBackgroundThread();
4394 MOZ_ASSERT(!mClosed
);
4395 MOZ_ASSERT(!mPrepareDatastoreOps
.Count());
4396 MOZ_ASSERT(!mPreparedDatastores
.Count());
4397 MOZ_ASSERT(!mDatabases
.Count());
4398 MOZ_ASSERT(mDirectoryLock
);
4402 if (IsPersistent()) {
4403 MOZ_ASSERT(mConnection
);
4404 MOZ_ASSERT(mQuotaObject
);
4406 // We can't release the directory lock and unregister itself from the
4407 // hashtable until the connection is fully closed.
4408 nsCOMPtr
<nsIRunnable
> callback
=
4409 NewRunnableMethod("dom::Datastore::ConnectionClosedCallback", this,
4410 &Datastore::ConnectionClosedCallback
);
4411 mConnection
->Close(callback
);
4413 MOZ_ASSERT(!mConnection
);
4414 MOZ_ASSERT(!mQuotaObject
);
4416 // There's no connection, so it's safe to release the directory lock and
4417 // unregister itself from the hashtable.
4419 mDirectoryLock
= nullptr;
4425 void Datastore::WaitForConnectionToComplete(nsIRunnable
* aCallback
) {
4426 AssertIsOnBackgroundThread();
4427 MOZ_ASSERT(aCallback
);
4428 MOZ_ASSERT(!mCompleteCallback
);
4429 MOZ_ASSERT(mClosed
);
4431 mCompleteCallback
= aCallback
;
4434 void Datastore::NoteLivePrepareDatastoreOp(
4435 PrepareDatastoreOp
* aPrepareDatastoreOp
) {
4436 AssertIsOnBackgroundThread();
4437 MOZ_ASSERT(aPrepareDatastoreOp
);
4438 MOZ_ASSERT(!mPrepareDatastoreOps
.Contains(aPrepareDatastoreOp
));
4439 MOZ_ASSERT(mDirectoryLock
);
4440 MOZ_ASSERT(!mClosed
);
4442 mPrepareDatastoreOps
.Insert(aPrepareDatastoreOp
);
4445 void Datastore::NoteFinishedPrepareDatastoreOp(
4446 PrepareDatastoreOp
* aPrepareDatastoreOp
) {
4447 AssertIsOnBackgroundThread();
4448 MOZ_ASSERT(aPrepareDatastoreOp
);
4449 MOZ_ASSERT(mPrepareDatastoreOps
.Contains(aPrepareDatastoreOp
));
4450 MOZ_ASSERT(mDirectoryLock
);
4451 MOZ_ASSERT(!mClosed
);
4453 mPrepareDatastoreOps
.Remove(aPrepareDatastoreOp
);
4455 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4456 quota::Client::LS
, "PrepareDatastoreOp finished"_ns
);
4461 void Datastore::NoteLivePrivateDatastore() {
4462 AssertIsOnBackgroundThread();
4463 MOZ_ASSERT(!mHasLivePrivateDatastore
);
4464 MOZ_ASSERT(mDirectoryLock
);
4465 MOZ_ASSERT(!mClosed
);
4467 mHasLivePrivateDatastore
= true;
4470 void Datastore::NoteFinishedPrivateDatastore() {
4471 AssertIsOnBackgroundThread();
4472 MOZ_ASSERT(mHasLivePrivateDatastore
);
4473 MOZ_ASSERT(mDirectoryLock
);
4474 MOZ_ASSERT(!mClosed
);
4476 mHasLivePrivateDatastore
= false;
4478 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4479 quota::Client::LS
, "PrivateDatastore finished"_ns
);
4484 void Datastore::NoteLivePreparedDatastore(
4485 PreparedDatastore
* aPreparedDatastore
) {
4486 AssertIsOnBackgroundThread();
4487 MOZ_ASSERT(aPreparedDatastore
);
4488 MOZ_ASSERT(!mPreparedDatastores
.Contains(aPreparedDatastore
));
4489 MOZ_ASSERT(mDirectoryLock
);
4490 MOZ_ASSERT(!mClosed
);
4492 mPreparedDatastores
.Insert(aPreparedDatastore
);
4495 void Datastore::NoteFinishedPreparedDatastore(
4496 PreparedDatastore
* aPreparedDatastore
) {
4497 AssertIsOnBackgroundThread();
4498 MOZ_ASSERT(aPreparedDatastore
);
4499 MOZ_ASSERT(mPreparedDatastores
.Contains(aPreparedDatastore
));
4500 MOZ_ASSERT(mDirectoryLock
);
4501 MOZ_ASSERT(!mClosed
);
4503 mPreparedDatastores
.Remove(aPreparedDatastore
);
4505 QuotaManager::MaybeRecordQuotaClientShutdownStep(
4506 quota::Client::LS
, "PreparedDatastore finished"_ns
);
4511 bool Datastore::HasOtherProcessDatabases(Database
* aDatabase
) {
4512 AssertIsOnBackgroundThread();
4514 PBackgroundParent
* databaseBackgroundActor
= aDatabase
->Manager();
4516 for (Database
* database
: mDatabases
) {
4517 if (database
->Manager() != databaseBackgroundActor
) {
4525 void Datastore::NoteLiveDatabase(Database
* aDatabase
) {
4526 AssertIsOnBackgroundThread();
4527 MOZ_ASSERT(aDatabase
);
4528 MOZ_ASSERT(!mDatabases
.Contains(aDatabase
));
4529 MOZ_ASSERT(mDirectoryLock
);
4530 MOZ_ASSERT(!mClosed
);
4532 mDatabases
.Insert(aDatabase
);
4534 NoteChangedDatabaseMap();
4537 void Datastore::NoteFinishedDatabase(Database
* aDatabase
) {
4538 AssertIsOnBackgroundThread();
4539 MOZ_ASSERT(aDatabase
);
4540 MOZ_ASSERT(mDatabases
.Contains(aDatabase
));
4541 MOZ_ASSERT(!mActiveDatabases
.Contains(aDatabase
));
4542 MOZ_ASSERT(mDirectoryLock
);
4543 MOZ_ASSERT(!mClosed
);
4545 mDatabases
.Remove(aDatabase
);
4547 NoteChangedDatabaseMap();
4549 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS
,
4550 "Database finished"_ns
);
4555 void Datastore::NoteActiveDatabase(Database
* aDatabase
) {
4556 AssertIsOnBackgroundThread();
4557 MOZ_ASSERT(aDatabase
);
4558 MOZ_ASSERT(mDatabases
.Contains(aDatabase
));
4559 MOZ_ASSERT(!mActiveDatabases
.Contains(aDatabase
));
4560 MOZ_ASSERT(!mClosed
);
4562 mActiveDatabases
.Insert(aDatabase
);
4565 void Datastore::NoteInactiveDatabase(Database
* aDatabase
) {
4566 AssertIsOnBackgroundThread();
4567 MOZ_ASSERT(aDatabase
);
4568 MOZ_ASSERT(mDatabases
.Contains(aDatabase
));
4569 MOZ_ASSERT(mActiveDatabases
.Contains(aDatabase
));
4570 MOZ_ASSERT(!mClosed
);
4572 mActiveDatabases
.Remove(aDatabase
);
4574 if (!mActiveDatabases
.Count() && mPendingUsageDeltas
.Length()) {
4575 int64_t finalDelta
= 0;
4577 for (auto delta
: mPendingUsageDeltas
) {
4578 finalDelta
+= delta
;
4581 MOZ_ASSERT(finalDelta
<= 0);
4583 if (finalDelta
!= 0) {
4584 DebugOnly
<bool> ok
= UpdateUsage(finalDelta
);
4588 mPendingUsageDeltas
.Clear();
4592 void Datastore::GetSnapshotLoadInfo(const nsAString
& aKey
,
4593 bool& aAddKeyToUnknownItems
,
4594 nsTHashtable
<nsStringHashKey
>& aLoadedItems
,
4595 nsTArray
<LSItemInfo
>& aItemInfos
,
4596 uint32_t& aNextLoadIndex
,
4597 LSSnapshot::LoadState
& aLoadState
) {
4598 AssertIsOnBackgroundThread();
4599 MOZ_ASSERT(!mClosed
);
4600 MOZ_ASSERT(!mInUpdateBatch
);
4603 int64_t sizeOfKeys
= 0;
4604 int64_t sizeOfItems
= 0;
4605 for (auto item
: mOrderedItems
) {
4606 int64_t sizeOfKey
= static_cast<int64_t>(item
.key().Length());
4607 sizeOfKeys
+= sizeOfKey
;
4608 sizeOfItems
+= sizeOfKey
+ static_cast<int64_t>(item
.value().Length());
4610 MOZ_ASSERT(mSizeOfKeys
== sizeOfKeys
);
4611 MOZ_ASSERT(mSizeOfItems
== sizeOfItems
);
4614 // Computes load state optimized for current size of keys and items.
4615 // Zero key length and value can be passed to do a quick initial estimation.
4616 // If computed load state is already AllOrderedItems then excluded key length
4617 // and value length can't make it any better.
4618 auto GetLoadState
= [&](int64_t aKeyLength
, int64_t aValueLength
) {
4619 if (mSizeOfKeys
- aKeyLength
<= gSnapshotPrefill
) {
4620 if (mSizeOfItems
- aKeyLength
- aValueLength
<= gSnapshotPrefill
) {
4621 return LSSnapshot::LoadState::AllOrderedItems
;
4624 return LSSnapshot::LoadState::AllOrderedKeys
;
4627 return LSSnapshot::LoadState::Partial
;
4630 // Value for given aKey if aKey is not void (can be void too if value doesn't
4631 // exist for given aKey).
4633 // If aKey and value are not void, checkKey will be set to true. Once we find
4634 // an item for given aKey in one of the loops below, checkKey is set to false
4635 // to prevent additional comparison of strings (string implementation compares
4636 // string lengths first to avoid char by char comparison if possible).
4637 bool checkKey
= false;
4639 // Avoid additional hash lookup if all ordered items fit into initial prefill
4641 LSSnapshot::LoadState loadState
= GetLoadState(/* aKeyLength */ 0,
4642 /* aValueLength */ 0);
4643 if (loadState
!= LSSnapshot::LoadState::AllOrderedItems
&& !aKey
.IsVoid()) {
4644 GetItem(aKey
, value
);
4645 if (!value
.IsVoid()) {
4646 // Ok, we have a non void aKey and value.
4648 // We have to watch for aKey during one of the loops below to exclude it
4649 // from the size computation. The super fast mode (AllOrderedItems)
4650 // doesn't have to do that though.
4653 // We have to compute load state again because aKey length and value
4654 // length is excluded from the size in this case.
4655 loadState
= GetLoadState(aKey
.Length(), value
.Length());
4659 switch (loadState
) {
4660 case LSSnapshot::LoadState::AllOrderedItems
: {
4661 // We're sending all ordered items, we don't need to check keys because
4662 // mOrderedItems must contain a value for aKey if checkKey is true.
4664 aItemInfos
.AppendElements(mOrderedItems
);
4666 MOZ_ASSERT(aItemInfos
.Length() == mValues
.Count());
4667 aNextLoadIndex
= mValues
.Count();
4669 aAddKeyToUnknownItems
= false;
4674 case LSSnapshot::LoadState::AllOrderedKeys
: {
4675 // We don't have enough snapshot budget to send all items, but we do have
4676 // enough to send all of the keys and to make a best effort to populate as
4677 // many values as possible. We send void string values once we run out of
4678 // budget. A complicating factor is that we want to make sure that we send
4679 // the value for aKey which is a localStorage read that's triggering this
4680 // request. Since that key can happen anywhere in the list of items, we
4681 // need to handle it specially.
4683 // The loop is effectively doing 2 things in parallel:
4685 // 1. Looking for the `aKey` to send. This is tracked by `checkKey`
4686 // which is true if there was an `aKey` specified and until we
4687 // populate its value, and false thereafter.
4688 // 2. Sending values until we run out of `size` budget and switch to
4689 // sending void values. `doneSendingValues` tracks when we've run out
4690 // of size budget, with `setVoidValue` tracking whether a value
4691 // should be sent for each turn of the event loop but can be
4692 // overridden when `aKey` is found.
4694 int64_t size
= mSizeOfKeys
;
4695 bool setVoidValue
= false;
4696 bool doneSendingValues
= false;
4697 for (uint32_t index
= 0; index
< mOrderedItems
.Length(); index
++) {
4698 const LSItemInfo
& item
= mOrderedItems
[index
];
4700 const nsString
& key
= item
.key();
4701 const LSValue
& value
= item
.value();
4703 if (checkKey
&& key
== aKey
) {
4705 setVoidValue
= false;
4706 } else if (!setVoidValue
) {
4707 if (doneSendingValues
) {
4708 setVoidValue
= true;
4710 size
+= static_cast<int64_t>(value
.Length());
4712 if (size
> gSnapshotPrefill
) {
4713 setVoidValue
= true;
4714 doneSendingValues
= true;
4716 // We set doneSendingValues to true and that will guard against
4717 // entering this branch during next iterations. So aNextLoadIndex
4718 // is set only once.
4719 aNextLoadIndex
= index
;
4724 LSItemInfo
* itemInfo
= aItemInfos
.AppendElement();
4725 itemInfo
->key() = key
;
4727 itemInfo
->value().SetIsVoid(true);
4729 aLoadedItems
.PutEntry(key
);
4730 itemInfo
->value() = value
;
4734 aAddKeyToUnknownItems
= false;
4739 case LSSnapshot::LoadState::Partial
: {
4741 for (uint32_t index
= 0; index
< mOrderedItems
.Length(); index
++) {
4742 const LSItemInfo
& item
= mOrderedItems
[index
];
4744 const nsString
& key
= item
.key();
4745 const LSValue
& value
= item
.value();
4747 if (checkKey
&& key
== aKey
) {
4750 size
+= static_cast<int64_t>(key
.Length()) +
4751 static_cast<int64_t>(value
.Length());
4753 if (size
> gSnapshotPrefill
) {
4754 aNextLoadIndex
= index
;
4759 aLoadedItems
.PutEntry(key
);
4761 LSItemInfo
* itemInfo
= aItemInfos
.AppendElement();
4762 itemInfo
->key() = key
;
4763 itemInfo
->value() = value
;
4766 aAddKeyToUnknownItems
= false;
4768 if (!aKey
.IsVoid()) {
4769 if (value
.IsVoid()) {
4770 aAddKeyToUnknownItems
= true;
4771 } else if (checkKey
) {
4772 // The item wasn't added in the loop above, add it here.
4774 LSItemInfo
* itemInfo
= aItemInfos
.AppendElement();
4775 itemInfo
->key() = aKey
;
4776 itemInfo
->value() = value
;
4780 MOZ_ASSERT(aItemInfos
.Length() < mOrderedItems
.Length());
4786 MOZ_CRASH("Bad load state value!");
4789 aLoadState
= loadState
;
4792 void Datastore::GetItem(const nsAString
& aKey
, LSValue
& aValue
) const {
4793 AssertIsOnBackgroundThread();
4794 MOZ_ASSERT(!mClosed
);
4796 if (!mValues
.Get(aKey
, &aValue
)) {
4797 aValue
.SetIsVoid(true);
4801 void Datastore::GetKeys(nsTArray
<nsString
>& aKeys
) const {
4802 AssertIsOnBackgroundThread();
4803 MOZ_ASSERT(!mClosed
);
4805 for (auto item
: mOrderedItems
) {
4806 aKeys
.AppendElement(item
.key());
4810 void Datastore::SetItem(Database
* aDatabase
, const nsString
& aKey
,
4811 const LSValue
& aValue
) {
4812 AssertIsOnBackgroundThread();
4813 MOZ_ASSERT(aDatabase
);
4814 MOZ_ASSERT(!mClosed
);
4815 MOZ_ASSERT(mInUpdateBatch
);
4818 GetItem(aKey
, oldValue
);
4820 if (oldValue
!= aValue
) {
4821 bool isNewItem
= oldValue
.IsVoid();
4823 NotifySnapshots(aDatabase
, aKey
, oldValue
, /* affectsOrder */ isNewItem
);
4825 mValues
.InsertOrUpdate(aKey
, aValue
);
4830 mWriteOptimizer
.InsertItem(aKey
, aValue
);
4832 int64_t sizeOfKey
= static_cast<int64_t>(aKey
.Length());
4834 delta
= sizeOfKey
+ static_cast<int64_t>(aValue
.UTF16Length());
4836 mUpdateBatchUsage
+= delta
;
4838 mSizeOfKeys
+= sizeOfKey
;
4839 mSizeOfItems
+= sizeOfKey
+ static_cast<int64_t>(aValue
.Length());
4841 mWriteOptimizer
.UpdateItem(aKey
, aValue
);
4843 delta
= static_cast<int64_t>(aValue
.UTF16Length()) -
4844 static_cast<int64_t>(oldValue
.UTF16Length());
4846 mUpdateBatchUsage
+= delta
;
4848 mSizeOfItems
+= static_cast<int64_t>(aValue
.Length()) -
4849 static_cast<int64_t>(oldValue
.Length());
4852 if (IsPersistent()) {
4853 mConnection
->SetItem(aKey
, aValue
, delta
, isNewItem
);
4858 void Datastore::RemoveItem(Database
* aDatabase
, const nsString
& aKey
) {
4859 AssertIsOnBackgroundThread();
4860 MOZ_ASSERT(aDatabase
);
4861 MOZ_ASSERT(!mClosed
);
4862 MOZ_ASSERT(mInUpdateBatch
);
4865 GetItem(aKey
, oldValue
);
4867 if (!oldValue
.IsVoid()) {
4868 NotifySnapshots(aDatabase
, aKey
, oldValue
, /* aAffectsOrder */ true);
4870 mValues
.Remove(aKey
);
4872 mWriteOptimizer
.DeleteItem(aKey
);
4874 int64_t sizeOfKey
= static_cast<int64_t>(aKey
.Length());
4876 int64_t delta
= -sizeOfKey
- static_cast<int64_t>(oldValue
.UTF16Length());
4878 mUpdateBatchUsage
+= delta
;
4880 mSizeOfKeys
-= sizeOfKey
;
4881 mSizeOfItems
-= sizeOfKey
+ static_cast<int64_t>(oldValue
.Length());
4883 if (IsPersistent()) {
4884 mConnection
->RemoveItem(aKey
, delta
);
4889 void Datastore::Clear(Database
* aDatabase
) {
4890 AssertIsOnBackgroundThread();
4891 MOZ_ASSERT(!mClosed
);
4893 if (mValues
.Count()) {
4895 for (const auto& entry
: mValues
) {
4896 const nsAString
& key
= entry
.GetKey();
4897 const LSValue
& value
= entry
.GetData();
4899 delta
+= -static_cast<int64_t>(key
.Length()) -
4900 static_cast<int64_t>(value
.UTF16Length());
4902 NotifySnapshots(aDatabase
, key
, value
, /* aAffectsOrder */ true);
4907 if (mInUpdateBatch
) {
4908 mWriteOptimizer
.Truncate();
4910 mUpdateBatchUsage
+= delta
;
4912 mOrderedItems
.Clear();
4914 DebugOnly
<bool> ok
= UpdateUsage(delta
);
4921 if (IsPersistent()) {
4922 mConnection
->Clear(delta
);
4927 void Datastore::BeginUpdateBatch(int64_t aSnapshotUsage
) {
4928 AssertIsOnBackgroundThread();
4929 // Don't assert `aSnapshotUsage >= 0`, it can be negative when multiple
4930 // snapshots are operating in parallel.
4931 MOZ_ASSERT(!mClosed
);
4932 MOZ_ASSERT(mUpdateBatchUsage
== -1);
4933 MOZ_ASSERT(!mInUpdateBatch
);
4935 mUpdateBatchUsage
= aSnapshotUsage
;
4937 if (IsPersistent()) {
4938 mConnection
->BeginUpdateBatch();
4941 mInUpdateBatch
= true;
4944 int64_t Datastore::EndUpdateBatch(int64_t aSnapshotPeakUsage
) {
4945 AssertIsOnBackgroundThread();
4946 MOZ_ASSERT(!mClosed
);
4947 MOZ_ASSERT(mInUpdateBatch
);
4949 mWriteOptimizer
.ApplyAndReset(mOrderedItems
);
4951 MOZ_ASSERT(!mWriteOptimizer
.HasWrites());
4953 if (aSnapshotPeakUsage
>= 0) {
4954 int64_t delta
= mUpdateBatchUsage
- aSnapshotPeakUsage
;
4956 if (mActiveDatabases
.Count()) {
4957 // We can't apply deltas while other databases are still active.
4958 // The final delta must be zero or negative, but individual deltas can
4959 // be positive. A positive delta can't be applied asynchronously since
4960 // there's no way to fire the quota exceeded error event.
4962 mPendingUsageDeltas
.AppendElement(delta
);
4964 MOZ_ASSERT(delta
<= 0);
4966 DebugOnly
<bool> ok
= UpdateUsage(delta
);
4972 int64_t result
= mUpdateBatchUsage
;
4973 mUpdateBatchUsage
= -1;
4975 if (IsPersistent()) {
4976 mConnection
->EndUpdateBatch();
4979 mInUpdateBatch
= false;
4984 int64_t Datastore::AttemptToUpdateUsage(int64_t aMinSize
, bool aInitial
) {
4985 AssertIsOnBackgroundThread();
4986 MOZ_ASSERT_IF(aInitial
, aMinSize
>= 0);
4987 MOZ_ASSERT_IF(!aInitial
, aMinSize
> 0);
4989 const int64_t size
= aMinSize
+ GetSnapshotPeakUsagePreincrement(aInitial
);
4991 if (size
&& UpdateUsage(size
)) {
4995 const int64_t reducedSize
=
4996 aMinSize
+ GetSnapshotPeakUsageReducedPreincrement(aInitial
);
4998 if (reducedSize
&& UpdateUsage(reducedSize
)) {
5002 if (aMinSize
> 0 && UpdateUsage(aMinSize
)) {
5009 bool Datastore::HasOtherProcessObservers(Database
* aDatabase
) {
5010 AssertIsOnBackgroundThread();
5011 MOZ_ASSERT(aDatabase
);
5017 nsTArray
<NotNull
<Observer
*>>* array
;
5018 if (!gObservers
->Get(mOriginMetadata
.mOrigin
, &array
)) {
5024 PBackgroundParent
* databaseBackgroundActor
= aDatabase
->Manager();
5026 for (Observer
* observer
: *array
) {
5027 if (observer
->Manager() != databaseBackgroundActor
) {
5035 void Datastore::NotifyOtherProcessObservers(Database
* aDatabase
,
5036 const nsString
& aDocumentURI
,
5037 const nsString
& aKey
,
5038 const LSValue
& aOldValue
,
5039 const LSValue
& aNewValue
) {
5040 AssertIsOnBackgroundThread();
5041 MOZ_ASSERT(aDatabase
);
5047 nsTArray
<NotNull
<Observer
*>>* array
;
5048 if (!gObservers
->Get(mOriginMetadata
.mOrigin
, &array
)) {
5054 // We do not want to send information about events back to the content process
5055 // that caused the change.
5056 PBackgroundParent
* databaseBackgroundActor
= aDatabase
->Manager();
5058 for (Observer
* observer
: *array
) {
5059 if (observer
->Manager() != databaseBackgroundActor
) {
5060 observer
->Observe(aDatabase
, aDocumentURI
, aKey
, aOldValue
, aNewValue
);
5065 void Datastore::NoteChangedObserverArray(
5066 const nsTArray
<NotNull
<Observer
*>>& aObservers
) {
5067 AssertIsOnBackgroundThread();
5069 for (Database
* database
: mActiveDatabases
) {
5070 Snapshot
* snapshot
= database
->GetSnapshot();
5071 MOZ_ASSERT(snapshot
);
5073 if (snapshot
->IsDirty()) {
5077 bool hasOtherProcessObservers
= false;
5079 PBackgroundParent
* databaseBackgroundActor
= database
->Manager();
5081 for (Observer
* observer
: aObservers
) {
5082 if (observer
->Manager() != databaseBackgroundActor
) {
5083 hasOtherProcessObservers
= true;
5088 if (snapshot
->HasOtherProcessObservers() != hasOtherProcessObservers
) {
5089 snapshot
->MarkDirty();
5094 void Datastore::Stringify(nsACString
& aResult
) const {
5095 AssertIsOnBackgroundThread();
5097 aResult
.AppendLiteral("DirectoryLock:");
5098 aResult
.AppendInt(!!mDirectoryLock
);
5099 aResult
.Append(kQuotaGenericDelimiter
);
5101 aResult
.AppendLiteral("Connection:");
5102 aResult
.AppendInt(!!mConnection
);
5103 aResult
.Append(kQuotaGenericDelimiter
);
5105 aResult
.AppendLiteral("QuotaObject:");
5106 aResult
.AppendInt(!!mQuotaObject
);
5107 aResult
.Append(kQuotaGenericDelimiter
);
5109 aResult
.AppendLiteral("PrepareDatastoreOps:");
5110 aResult
.AppendInt(mPrepareDatastoreOps
.Count());
5111 aResult
.Append(kQuotaGenericDelimiter
);
5113 aResult
.AppendLiteral("PreparedDatastores:");
5114 aResult
.AppendInt(mPreparedDatastores
.Count());
5115 aResult
.Append(kQuotaGenericDelimiter
);
5117 aResult
.AppendLiteral("Databases:");
5118 aResult
.AppendInt(mDatabases
.Count());
5119 aResult
.Append(kQuotaGenericDelimiter
);
5121 aResult
.AppendLiteral("ActiveDatabases:");
5122 aResult
.AppendInt(mActiveDatabases
.Count());
5123 aResult
.Append(kQuotaGenericDelimiter
);
5125 aResult
.AppendLiteral("Origin:");
5126 aResult
.Append(AnonymizedOriginString(mOriginMetadata
.mOrigin
));
5127 aResult
.Append(kQuotaGenericDelimiter
);
5129 aResult
.AppendLiteral("PrivateBrowsingId:");
5130 aResult
.AppendInt(mPrivateBrowsingId
);
5131 aResult
.Append(kQuotaGenericDelimiter
);
5133 aResult
.AppendLiteral("Closed:");
5134 aResult
.AppendInt(mClosed
);
5137 bool Datastore::UpdateUsage(int64_t aDelta
) {
5138 AssertIsOnBackgroundThread();
5140 // Check internal LocalStorage origin limit.
5141 int64_t newUsage
= mUsage
+ aDelta
;
5143 MOZ_ASSERT(newUsage
>= 0);
5145 if (newUsage
> StaticPrefs::dom_storage_default_quota() * 1024) {
5149 // Check QuotaManager limits (group and global limit).
5150 if (IsPersistent()) {
5151 MOZ_ASSERT(mQuotaObject
);
5153 if (!mQuotaObject
->MaybeUpdateSize(newUsage
, /* aTruncate */ true)) {
5158 // Quota checks passed, set new usage.
5164 void Datastore::MaybeClose() {
5165 AssertIsOnBackgroundThread();
5167 if (!mPrepareDatastoreOps
.Count() && !mHasLivePrivateDatastore
&&
5168 !mPreparedDatastores
.Count() && !mDatabases
.Count()) {
5173 void Datastore::ConnectionClosedCallback() {
5174 AssertIsOnBackgroundThread();
5175 MOZ_ASSERT(mDirectoryLock
);
5176 MOZ_ASSERT(mConnection
);
5177 MOZ_ASSERT(mQuotaObject
);
5178 MOZ_ASSERT(mClosed
);
5180 // Release the quota object first.
5181 mQuotaObject
= nullptr;
5183 bool databaseWasNotAvailable
;
5184 bool hasCreatedDatabase
;
5185 mConnection
->GetFinishInfo(databaseWasNotAvailable
, hasCreatedDatabase
);
5187 if (databaseWasNotAvailable
&& !hasCreatedDatabase
) {
5188 MOZ_ASSERT(mUsage
== 0);
5190 QuotaManager
* quotaManager
= QuotaManager::Get();
5191 MOZ_ASSERT(quotaManager
);
5193 quotaManager
->ResetUsageForClient(
5194 ClientMetadata
{mOriginMetadata
, mozilla::dom::quota::Client::LS
});
5197 mConnection
= nullptr;
5199 // Now it's safe to release the directory lock and unregister itself from
5202 mDirectoryLock
= nullptr;
5206 if (mCompleteCallback
) {
5207 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback
.forget()));
5211 void Datastore::CleanupMetadata() {
5212 AssertIsOnBackgroundThread();
5214 MOZ_ASSERT(gDatastores
);
5215 const DebugOnly
<bool> removed
= gDatastores
->Remove(mOriginMetadata
.mOrigin
);
5216 MOZ_ASSERT(removed
);
5218 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS
,
5219 "Datastore removed"_ns
);
5221 if (!gDatastores
->Count()) {
5222 gDatastores
= nullptr;
5226 void Datastore::NotifySnapshots(Database
* aDatabase
, const nsAString
& aKey
,
5227 const LSValue
& aOldValue
, bool aAffectsOrder
) {
5228 AssertIsOnBackgroundThread();
5230 for (Database
* database
: mDatabases
) {
5231 MOZ_ASSERT(database
);
5233 if (database
== aDatabase
) {
5237 Snapshot
* snapshot
= database
->GetSnapshot();
5239 snapshot
->SaveItem(aKey
, aOldValue
, aAffectsOrder
);
5244 void Datastore::NoteChangedDatabaseMap() {
5245 AssertIsOnBackgroundThread();
5247 for (Database
* database
: mActiveDatabases
) {
5248 Snapshot
* snapshot
= database
->GetSnapshot();
5249 MOZ_ASSERT(snapshot
);
5251 if (snapshot
->IsDirty()) {
5255 if (snapshot
->HasOtherProcessDatabases() !=
5256 HasOtherProcessDatabases(database
)) {
5257 snapshot
->MarkDirty();
5262 /*******************************************************************************
5264 ******************************************************************************/
5266 void PreparedDatastore::Destroy() {
5267 AssertIsOnBackgroundThread();
5268 MOZ_ASSERT(gPreparedDatastores
);
5269 DebugOnly
<bool> removed
= gPreparedDatastores
->Remove(mDatastoreId
);
5270 MOZ_ASSERT(removed
);
5274 void PreparedDatastore::TimerCallback(nsITimer
* aTimer
, void* aClosure
) {
5275 AssertIsOnBackgroundThread();
5277 auto* self
= static_cast<PreparedDatastore
*>(aClosure
);
5283 /*******************************************************************************
5285 ******************************************************************************/
5287 Database::Database(const PrincipalInfo
& aPrincipalInfo
,
5288 const Maybe
<ContentParentId
>& aContentParentId
,
5289 const nsACString
& aOrigin
, uint32_t aPrivateBrowsingId
)
5290 : mSnapshot(nullptr),
5291 mPrincipalInfo(aPrincipalInfo
),
5292 mContentParentId(aContentParentId
),
5294 mPrivateBrowsingId(aPrivateBrowsingId
),
5295 mAllowedToClose(false),
5296 mActorDestroyed(false),
5297 mRequestedAllowToClose(false)
5300 mActorWasAlive(false)
5303 AssertIsOnBackgroundThread();
5306 Database::~Database() {
5307 MOZ_ASSERT_IF(mActorWasAlive
, mAllowedToClose
);
5308 MOZ_ASSERT_IF(mActorWasAlive
, mActorDestroyed
);
5311 void Database::SetActorAlive(Datastore
* aDatastore
) {
5312 AssertIsOnBackgroundThread();
5313 MOZ_ASSERT(!mActorWasAlive
);
5314 MOZ_ASSERT(!mActorDestroyed
);
5317 mActorWasAlive
= true;
5320 mDatastore
= aDatastore
;
5322 mDatastore
->NoteLiveDatabase(this);
5324 if (!gLiveDatabases
) {
5325 gLiveDatabases
= new LiveDatabaseArray();
5328 gLiveDatabases
->AppendElement(WrapNotNullUnchecked(this));
5331 void Database::RegisterSnapshot(Snapshot
* aSnapshot
) {
5332 AssertIsOnBackgroundThread();
5333 MOZ_ASSERT(aSnapshot
);
5334 MOZ_ASSERT(!mSnapshot
);
5335 MOZ_ASSERT(!mAllowedToClose
);
5337 // Only one snapshot at a time is currently supported.
5338 mSnapshot
= aSnapshot
;
5340 mDatastore
->NoteActiveDatabase(this);
5343 void Database::UnregisterSnapshot(Snapshot
* aSnapshot
) {
5344 MOZ_ASSERT(aSnapshot
);
5345 MOZ_ASSERT(mSnapshot
== aSnapshot
);
5347 mSnapshot
= nullptr;
5349 mDatastore
->NoteInactiveDatabase(this);
5352 void Database::RequestAllowToClose() {
5353 AssertIsOnBackgroundThread();
5355 if (mRequestedAllowToClose
) {
5359 mRequestedAllowToClose
= true;
5361 // Send the RequestAllowToClose message to the child to avoid racing with the
5362 // child actor. Except the case when the actor was already destroyed.
5363 if (mActorDestroyed
) {
5364 MOZ_ASSERT(mAllowedToClose
);
5368 if (NS_WARN_IF(!SendRequestAllowToClose()) && !mSnapshot
) {
5369 // This is not necessary, because there should be a runnable scheduled that
5370 // will call ActorDestroy which calls AllowToClose. However we can speedup
5371 // the shutdown a bit if we do it here directly, but only if there's no
5372 // registered snapshot.
5377 void Database::ForceKill() {
5378 AssertIsOnBackgroundThread();
5380 if (mActorDestroyed
) {
5381 MOZ_ASSERT(mAllowedToClose
);
5385 Unused
<< PBackgroundLSDatabaseParent::Send__delete__(this);
5388 void Database::Stringify(nsACString
& aResult
) const {
5389 AssertIsOnBackgroundThread();
5391 aResult
.AppendLiteral("SnapshotRegistered:");
5392 aResult
.AppendInt(!!mSnapshot
);
5393 aResult
.Append(kQuotaGenericDelimiter
);
5395 aResult
.AppendLiteral("OtherProcessActor:");
5396 aResult
.AppendInt(BackgroundParent::IsOtherProcessActor(Manager()));
5397 aResult
.Append(kQuotaGenericDelimiter
);
5399 aResult
.AppendLiteral("Origin:");
5400 aResult
.Append(AnonymizedOriginString(mOrigin
));
5401 aResult
.Append(kQuotaGenericDelimiter
);
5403 aResult
.AppendLiteral("PrivateBrowsingId:");
5404 aResult
.AppendInt(mPrivateBrowsingId
);
5405 aResult
.Append(kQuotaGenericDelimiter
);
5407 aResult
.AppendLiteral("AllowedToClose:");
5408 aResult
.AppendInt(mAllowedToClose
);
5409 aResult
.Append(kQuotaGenericDelimiter
);
5411 aResult
.AppendLiteral("ActorDestroyed:");
5412 aResult
.AppendInt(mActorDestroyed
);
5413 aResult
.Append(kQuotaGenericDelimiter
);
5415 aResult
.AppendLiteral("RequestedAllowToClose:");
5416 aResult
.AppendInt(mRequestedAllowToClose
);
5419 void Database::AllowToClose() {
5420 AssertIsOnBackgroundThread();
5421 MOZ_ASSERT(!mAllowedToClose
);
5422 MOZ_ASSERT(mDatastore
);
5423 MOZ_ASSERT(!mSnapshot
);
5425 mAllowedToClose
= true;
5427 mDatastore
->NoteFinishedDatabase(this);
5429 mDatastore
= nullptr;
5431 MOZ_ASSERT(gLiveDatabases
);
5432 gLiveDatabases
->RemoveElement(this);
5434 QuotaManager::MaybeRecordQuotaClientShutdownStep(quota::Client::LS
,
5435 "Live database removed"_ns
);
5437 if (gLiveDatabases
->IsEmpty()) {
5438 gLiveDatabases
= nullptr;
5442 void Database::ActorDestroy(ActorDestroyReason aWhy
) {
5443 AssertIsOnBackgroundThread();
5444 MOZ_ASSERT(!mActorDestroyed
);
5446 mActorDestroyed
= true;
5448 if (!mAllowedToClose
) {
5453 mozilla::ipc::IPCResult
Database::RecvDeleteMe() {
5454 AssertIsOnBackgroundThread();
5455 MOZ_ASSERT(!mActorDestroyed
);
5457 IProtocol
* mgr
= Manager();
5458 if (!PBackgroundLSDatabaseParent::Send__delete__(this)) {
5459 return IPC_FAIL(mgr
, "Send__delete__ failed!");
5464 mozilla::ipc::IPCResult
Database::RecvAllowToClose() {
5465 AssertIsOnBackgroundThread();
5467 if (NS_WARN_IF(mAllowedToClose
)) {
5468 return IPC_FAIL(this, "mAllowedToClose already set!");
5476 PBackgroundLSSnapshotParent
* Database::AllocPBackgroundLSSnapshotParent(
5477 const nsAString
& aDocumentURI
, const nsAString
& aKey
,
5478 const bool& aIncreasePeakUsage
, const int64_t& aMinSize
,
5479 LSSnapshotInitInfo
* aInitInfo
) {
5480 AssertIsOnBackgroundThread();
5482 if (NS_WARN_IF(aIncreasePeakUsage
&& aMinSize
< 0)) {
5483 MOZ_ASSERT_UNLESS_FUZZING(false);
5487 if (NS_WARN_IF(mAllowedToClose
)) {
5488 MOZ_ASSERT_UNLESS_FUZZING(false);
5492 RefPtr
<Snapshot
> snapshot
= new Snapshot(this, aDocumentURI
);
5494 // Transfer ownership to IPDL.
5495 return snapshot
.forget().take();
5498 mozilla::ipc::IPCResult
Database::RecvPBackgroundLSSnapshotConstructor(
5499 PBackgroundLSSnapshotParent
* aActor
, const nsAString
& aDocumentURI
,
5500 const nsAString
& aKey
, const bool& aIncreasePeakUsage
,
5501 const int64_t& aMinSize
, LSSnapshotInitInfo
* aInitInfo
) {
5502 AssertIsOnBackgroundThread();
5503 MOZ_ASSERT_IF(aIncreasePeakUsage
, aMinSize
>= 0);
5504 MOZ_ASSERT(aInitInfo
);
5505 MOZ_ASSERT(!mAllowedToClose
);
5507 auto* snapshot
= static_cast<Snapshot
*>(aActor
);
5509 bool addKeyToUnknownItems
;
5510 nsTHashtable
<nsStringHashKey
> loadedItems
;
5511 nsTArray
<LSItemInfo
> itemInfos
;
5512 uint32_t nextLoadIndex
;
5513 LSSnapshot::LoadState loadState
;
5514 mDatastore
->GetSnapshotLoadInfo(aKey
, addKeyToUnknownItems
, loadedItems
,
5515 itemInfos
, nextLoadIndex
, loadState
);
5517 nsTHashSet
<nsString
> unknownItems
;
5518 if (addKeyToUnknownItems
) {
5519 unknownItems
.Insert(aKey
);
5522 uint32_t totalLength
= mDatastore
->GetLength();
5524 int64_t usage
= mDatastore
->GetUsage();
5526 int64_t peakUsage
= usage
;
5528 if (aIncreasePeakUsage
) {
5530 mDatastore
->AttemptToUpdateUsage(aMinSize
, /* aInitial */ true);
5535 bool hasOtherProcessDatabases
= mDatastore
->HasOtherProcessDatabases(this);
5536 bool hasOtherProcessObservers
= mDatastore
->HasOtherProcessObservers(this);
5538 snapshot
->Init(loadedItems
, std::move(unknownItems
), nextLoadIndex
,
5539 totalLength
, usage
, peakUsage
, loadState
,
5540 hasOtherProcessDatabases
, hasOtherProcessObservers
);
5542 RegisterSnapshot(snapshot
);
5544 aInitInfo
->addKeyToUnknownItems() = addKeyToUnknownItems
;
5545 aInitInfo
->itemInfos() = std::move(itemInfos
);
5546 aInitInfo
->totalLength() = totalLength
;
5547 aInitInfo
->usage() = usage
;
5548 aInitInfo
->peakUsage() = peakUsage
;
5549 aInitInfo
->loadState() = loadState
;
5550 aInitInfo
->hasOtherProcessDatabases() = hasOtherProcessDatabases
;
5551 aInitInfo
->hasOtherProcessObservers() = hasOtherProcessObservers
;
5556 bool Database::DeallocPBackgroundLSSnapshotParent(
5557 PBackgroundLSSnapshotParent
* aActor
) {
5558 AssertIsOnBackgroundThread();
5561 // Transfer ownership back from IPDL.
5562 RefPtr
<Snapshot
> actor
= dont_AddRef(static_cast<Snapshot
*>(aActor
));
5567 /*******************************************************************************
5569 ******************************************************************************/
5571 Snapshot::Snapshot(Database
* aDatabase
, const nsAString
& aDocumentURI
)
5572 : mDatabase(aDatabase
),
5573 mDatastore(aDatabase
->GetDatastore()),
5574 mDocumentURI(aDocumentURI
),
5579 mActorDestroyed(false),
5580 mFinishReceived(false),
5581 mLoadedReceived(false),
5582 mLoadedAllItems(false),
5583 mLoadKeysReceived(false),
5584 mSentMarkDirty(false) {
5585 AssertIsOnBackgroundThread();
5586 MOZ_ASSERT(aDatabase
);
5589 Snapshot::~Snapshot() {
5590 MOZ_ASSERT(mActorDestroyed
);
5591 MOZ_ASSERT(mFinishReceived
);
5594 void Snapshot::SaveItem(const nsAString
& aKey
, const LSValue
& aOldValue
,
5595 bool aAffectsOrder
) {
5596 AssertIsOnBackgroundThread();
5600 if (mLoadedAllItems
) {
5604 if (!mLoadedItems
.Contains(aKey
) && !mUnknownItems
.Contains(aKey
)) {
5605 mValues
.LookupOrInsert(aKey
, aOldValue
);
5608 if (aAffectsOrder
&& !mSavedKeys
) {
5609 mDatastore
->GetKeys(mKeys
);
5614 void Snapshot::MarkDirty() {
5615 AssertIsOnBackgroundThread();
5617 if (!mSentMarkDirty
) {
5618 Unused
<< SendMarkDirty();
5619 mSentMarkDirty
= true;
5623 void Snapshot::Finish() {
5624 AssertIsOnBackgroundThread();
5625 MOZ_ASSERT(mDatabase
);
5626 MOZ_ASSERT(mDatastore
);
5627 MOZ_ASSERT(!mFinishReceived
);
5629 mDatastore
->BeginUpdateBatch(mUsage
);
5631 mDatastore
->EndUpdateBatch(mPeakUsage
);
5633 mDatabase
->UnregisterSnapshot(this);
5635 mFinishReceived
= true;
5638 void Snapshot::ActorDestroy(ActorDestroyReason aWhy
) {
5639 AssertIsOnBackgroundThread();
5640 MOZ_ASSERT(!mActorDestroyed
);
5642 mActorDestroyed
= true;
5644 if (!mFinishReceived
) {
5649 mozilla::ipc::IPCResult
Snapshot::RecvDeleteMe() {
5650 AssertIsOnBackgroundThread();
5651 MOZ_ASSERT(!mActorDestroyed
);
5653 IProtocol
* mgr
= Manager();
5654 if (!PBackgroundLSSnapshotParent::Send__delete__(this)) {
5655 return IPC_FAIL(mgr
, "Send__delete__ failed!");
5660 mozilla::ipc::IPCResult
Snapshot::Checkpoint(
5661 nsTArray
<LSWriteInfo
>&& aWriteInfos
) {
5662 AssertIsOnBackgroundThread();
5663 // Don't assert `mUsage >= 0`, it can be negative when multiple snapshots are
5664 // operating in parallel.
5665 MOZ_ASSERT(mPeakUsage
>= mUsage
);
5667 if (NS_WARN_IF(aWriteInfos
.IsEmpty())) {
5668 return IPC_FAIL(this, "aWriteInfos is empty!");
5671 if (NS_WARN_IF(mHasOtherProcessObservers
)) {
5672 return IPC_FAIL(this, "mHasOtherProcessObservers already set!");
5675 mDatastore
->BeginUpdateBatch(mUsage
);
5677 for (uint32_t index
= 0; index
< aWriteInfos
.Length(); index
++) {
5678 const LSWriteInfo
& writeInfo
= aWriteInfos
[index
];
5680 switch (writeInfo
.type()) {
5681 case LSWriteInfo::TLSSetItemInfo
: {
5682 const LSSetItemInfo
& info
= writeInfo
.get_LSSetItemInfo();
5684 mDatastore
->SetItem(mDatabase
, info
.key(), info
.value());
5689 case LSWriteInfo::TLSRemoveItemInfo
: {
5690 const LSRemoveItemInfo
& info
= writeInfo
.get_LSRemoveItemInfo();
5692 mDatastore
->RemoveItem(mDatabase
, info
.key());
5697 case LSWriteInfo::TLSClearInfo
: {
5698 mDatastore
->Clear(mDatabase
);
5704 MOZ_CRASH("Should never get here!");
5708 mUsage
= mDatastore
->EndUpdateBatch(-1);
5713 mozilla::ipc::IPCResult
Snapshot::CheckpointAndNotify(
5714 nsTArray
<LSWriteAndNotifyInfo
>&& aWriteAndNotifyInfos
) {
5715 AssertIsOnBackgroundThread();
5716 // Don't assert `mUsage >= 0`, it can be negative when multiple snapshots are
5717 // operating in parallel.
5718 MOZ_ASSERT(mPeakUsage
>= mUsage
);
5720 if (NS_WARN_IF(aWriteAndNotifyInfos
.IsEmpty())) {
5721 return IPC_FAIL(this, "aWriteAndNotifyInfos is empty!");
5724 if (NS_WARN_IF(!mHasOtherProcessObservers
)) {
5725 return IPC_FAIL(this, "mHasOtherProcessObservers is not set!");
5728 mDatastore
->BeginUpdateBatch(mUsage
);
5730 for (uint32_t index
= 0; index
< aWriteAndNotifyInfos
.Length(); index
++) {
5731 const LSWriteAndNotifyInfo
& writeAndNotifyInfo
=
5732 aWriteAndNotifyInfos
[index
];
5734 switch (writeAndNotifyInfo
.type()) {
5735 case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo
: {
5736 const LSSetItemAndNotifyInfo
& info
=
5737 writeAndNotifyInfo
.get_LSSetItemAndNotifyInfo();
5739 mDatastore
->SetItem(mDatabase
, info
.key(), info
.value());
5741 mDatastore
->NotifyOtherProcessObservers(
5742 mDatabase
, mDocumentURI
, info
.key(), info
.oldValue(), info
.value());
5747 case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo
: {
5748 const LSRemoveItemAndNotifyInfo
& info
=
5749 writeAndNotifyInfo
.get_LSRemoveItemAndNotifyInfo();
5751 mDatastore
->RemoveItem(mDatabase
, info
.key());
5753 mDatastore
->NotifyOtherProcessObservers(mDatabase
, mDocumentURI
,
5754 info
.key(), info
.oldValue(),
5760 case LSWriteAndNotifyInfo::TLSClearInfo
: {
5761 mDatastore
->Clear(mDatabase
);
5763 mDatastore
->NotifyOtherProcessObservers(mDatabase
, mDocumentURI
,
5764 VoidString(), VoidLSValue(),
5771 MOZ_CRASH("Should never get here!");
5775 mUsage
= mDatastore
->EndUpdateBatch(-1);
5780 mozilla::ipc::IPCResult
Snapshot::RecvAsyncCheckpoint(
5781 nsTArray
<LSWriteInfo
>&& aWriteInfos
) {
5782 return Checkpoint(std::move(aWriteInfos
));
5785 mozilla::ipc::IPCResult
Snapshot::RecvAsyncCheckpointAndNotify(
5786 nsTArray
<LSWriteAndNotifyInfo
>&& aWriteAndNotifyInfos
) {
5787 return CheckpointAndNotify(std::move(aWriteAndNotifyInfos
));
5790 mozilla::ipc::IPCResult
Snapshot::RecvSyncCheckpoint(
5791 nsTArray
<LSWriteInfo
>&& aWriteInfos
) {
5792 return Checkpoint(std::move(aWriteInfos
));
5795 mozilla::ipc::IPCResult
Snapshot::RecvSyncCheckpointAndNotify(
5796 nsTArray
<LSWriteAndNotifyInfo
>&& aWriteAndNotifyInfos
) {
5797 return CheckpointAndNotify(std::move(aWriteAndNotifyInfos
));
5800 mozilla::ipc::IPCResult
Snapshot::RecvAsyncFinish() {
5801 AssertIsOnBackgroundThread();
5803 if (NS_WARN_IF(mFinishReceived
)) {
5804 MOZ_ASSERT_UNLESS_FUZZING(false);
5805 return IPC_FAIL(this, "Already finished");
5813 mozilla::ipc::IPCResult
Snapshot::RecvSyncFinish() {
5814 AssertIsOnBackgroundThread();
5816 if (NS_WARN_IF(mFinishReceived
)) {
5817 MOZ_ASSERT_UNLESS_FUZZING(false);
5818 return IPC_FAIL(this, "Already finished");
5826 mozilla::ipc::IPCResult
Snapshot::RecvLoaded() {
5827 AssertIsOnBackgroundThread();
5829 if (NS_WARN_IF(mFinishReceived
)) {
5830 return IPC_FAIL(this, "mFinishReceived already set!");
5833 if (NS_WARN_IF(mLoadedReceived
)) {
5834 return IPC_FAIL(this, "mLoadedReceived already set!");
5837 if (NS_WARN_IF(mLoadedAllItems
)) {
5838 return IPC_FAIL(this, "mLoadedAllItems already set!");
5841 if (NS_WARN_IF(mLoadKeysReceived
)) {
5842 return IPC_FAIL(this, "mLoadKeysReceived already set!");
5845 mLoadedReceived
= true;
5847 mLoadedItems
.Clear();
5848 mUnknownItems
.Clear();
5851 mLoadedAllItems
= true;
5852 mLoadKeysReceived
= true;
5857 mozilla::ipc::IPCResult
Snapshot::RecvLoadValueAndMoreItems(
5858 const nsAString
& aKey
, LSValue
* aValue
, nsTArray
<LSItemInfo
>* aItemInfos
) {
5859 AssertIsOnBackgroundThread();
5861 MOZ_ASSERT(aItemInfos
);
5862 MOZ_ASSERT(mDatastore
);
5864 if (NS_WARN_IF(mFinishReceived
)) {
5865 return IPC_FAIL(this, "mFinishReceived already set!");
5868 if (NS_WARN_IF(mLoadedReceived
)) {
5869 return IPC_FAIL(this, "mLoadedReceived already set!");
5872 if (NS_WARN_IF(mLoadedAllItems
)) {
5873 return IPC_FAIL(this, "mLoadedAllItems already set!");
5876 if (mLoadedItems
.Contains(aKey
)) {
5877 return IPC_FAIL(this, "mLoadedItems already contains aKey!");
5880 if (mUnknownItems
.Contains(aKey
)) {
5881 return IPC_FAIL(this, "mUnknownItems already contains aKey!");
5884 if (auto entry
= mValues
.Lookup(aKey
)) {
5885 *aValue
= entry
.Data();
5888 mDatastore
->GetItem(aKey
, *aValue
);
5891 if (aValue
->IsVoid()) {
5892 mUnknownItems
.Insert(aKey
);
5894 mLoadedItems
.PutEntry(aKey
);
5896 // mLoadedItems.Count()==mTotalLength is checked below.
5899 // Load some more key/value pairs (as many as the snapshot gradual prefill
5900 // byte budget allows).
5902 if (gSnapshotGradualPrefill
> 0) {
5903 const nsTArray
<LSItemInfo
>& orderedItems
= mDatastore
->GetOrderedItems();
5907 length
= mKeys
.Length();
5909 length
= orderedItems
.Length();
5913 while (mNextLoadIndex
< length
) {
5914 // If the datastore's ordering has changed, mSavedKeys will be true and
5915 // mKeys contains an ordered list of the keys. Otherwise we can use the
5916 // datastore's key ordering which is still the same as when the snapshot
5921 key
= mKeys
[mNextLoadIndex
];
5923 key
= orderedItems
[mNextLoadIndex
].key();
5926 // Normally we would do this:
5927 // if (!mLoadedItems.GetEntry(key)) {
5929 // mLoadedItems.PutEntry(key);
5931 // but that requires two hash lookups. We can reduce that to just one
5932 // hash lookup if we always call PutEntry and check the number of entries
5933 // before and after the put (which is very cheap). However, if we reach
5934 // the prefill limit, we need to call RemoveEntry, but that is also cheap
5935 // because we pass the entry (not the key).
5937 uint32_t countBeforePut
= mLoadedItems
.Count();
5938 auto loadedItemEntry
= mLoadedItems
.PutEntry(key
);
5939 if (countBeforePut
!= mLoadedItems
.Count()) {
5940 // Check mValues first since that contains values as they existed when
5941 // our snapshot was created, but have since been changed/removed in the
5942 // datastore. If it's not there, then the datastore has the
5943 // still-current value. However, if the datastore's key ordering has
5944 // changed, we need to do a hash lookup rather than being able to do an
5945 // optimized direct access to the index.
5948 auto valueEntry
= mValues
.Lookup(key
);
5950 value
= valueEntry
.Data();
5951 } else if (mSavedKeys
) {
5952 mDatastore
->GetItem(nsString(key
), value
);
5954 value
= orderedItems
[mNextLoadIndex
].value();
5957 // All not loaded keys must have a value.
5958 MOZ_ASSERT(!value
.IsVoid());
5960 size
+= static_cast<int64_t>(key
.Length()) +
5961 static_cast<int64_t>(value
.Length());
5963 if (size
> gSnapshotGradualPrefill
) {
5964 mLoadedItems
.RemoveEntry(loadedItemEntry
);
5966 // mNextLoadIndex is not incremented, so we will resume at the same
5967 // position next time.
5972 valueEntry
.Remove();
5975 LSItemInfo
* itemInfo
= aItemInfos
->AppendElement();
5976 itemInfo
->key() = key
;
5977 itemInfo
->value() = value
;
5984 if (mLoadedItems
.Count() == mTotalLength
) {
5985 mLoadedItems
.Clear();
5986 mUnknownItems
.Clear();
5988 const bool allValuesVoid
=
5989 std::all_of(mValues
.Values().cbegin(), mValues
.Values().cend(),
5990 [](const auto& entry
) { return entry
.IsVoid(); });
5991 MOZ_ASSERT(allValuesVoid
);
5994 mLoadedAllItems
= true;
6000 mozilla::ipc::IPCResult
Snapshot::RecvLoadKeys(nsTArray
<nsString
>* aKeys
) {
6001 AssertIsOnBackgroundThread();
6003 MOZ_ASSERT(mDatastore
);
6005 if (NS_WARN_IF(mFinishReceived
)) {
6006 return IPC_FAIL(this, "mFinishReceived already set!");
6009 if (NS_WARN_IF(mLoadedReceived
)) {
6010 return IPC_FAIL(this, "mLoadedReceived already set!");
6013 if (NS_WARN_IF(mLoadKeysReceived
)) {
6014 return IPC_FAIL(this, "mLoadKeysReceived already set!");
6017 mLoadKeysReceived
= true;
6020 aKeys
->AppendElements(std::move(mKeys
));
6022 mDatastore
->GetKeys(*aKeys
);
6028 mozilla::ipc::IPCResult
Snapshot::RecvIncreasePeakUsage(const int64_t& aMinSize
,
6030 AssertIsOnBackgroundThread();
6033 if (NS_WARN_IF(aMinSize
<= 0)) {
6034 return IPC_FAIL(this, "aMinSize not valid!");
6037 if (NS_WARN_IF(mFinishReceived
)) {
6038 return IPC_FAIL(this, "mFinishReceived already set!");
6042 mDatastore
->AttemptToUpdateUsage(aMinSize
, /* aInitial */ false);
6051 /*******************************************************************************
6053 ******************************************************************************/
6055 Observer::Observer(const nsACString
& aOrigin
)
6056 : mOrigin(aOrigin
), mActorDestroyed(false) {
6057 AssertIsOnBackgroundThread();
6060 Observer::~Observer() { MOZ_ASSERT(mActorDestroyed
); }
6062 void Observer::Observe(Database
* aDatabase
, const nsString
& aDocumentURI
,
6063 const nsString
& aKey
, const LSValue
& aOldValue
,
6064 const LSValue
& aNewValue
) {
6065 AssertIsOnBackgroundThread();
6066 MOZ_ASSERT(aDatabase
);
6068 Unused
<< SendObserve(aDatabase
->GetPrincipalInfo(),
6069 aDatabase
->PrivateBrowsingId(), aDocumentURI
, aKey
,
6070 aOldValue
, aNewValue
);
6073 void Observer::ActorDestroy(ActorDestroyReason aWhy
) {
6074 AssertIsOnBackgroundThread();
6075 MOZ_ASSERT(!mActorDestroyed
);
6077 mActorDestroyed
= true;
6079 MOZ_ASSERT(gObservers
);
6081 nsTArray
<NotNull
<Observer
*>>* array
;
6082 gObservers
->Get(mOrigin
, &array
);
6085 array
->RemoveElement(this);
6087 if (RefPtr
<Datastore
> datastore
= GetDatastore(mOrigin
)) {
6088 datastore
->NoteChangedObserverArray(*array
);
6091 if (array
->IsEmpty()) {
6092 gObservers
->Remove(mOrigin
);
6095 if (!gObservers
->Count()) {
6096 gObservers
= nullptr;
6100 mozilla::ipc::IPCResult
Observer::RecvDeleteMe() {
6101 AssertIsOnBackgroundThread();
6102 MOZ_ASSERT(!mActorDestroyed
);
6104 IProtocol
* mgr
= Manager();
6105 if (!PBackgroundLSObserverParent::Send__delete__(this)) {
6106 return IPC_FAIL(mgr
, "Send__delete__ failed!");
6111 /*******************************************************************************
6113 ******************************************************************************/
6115 LSRequestBase::LSRequestBase(const LSRequestParams
& aParams
,
6116 const Maybe
<ContentParentId
>& aContentParentId
)
6118 mContentParentId(aContentParentId
),
6119 mState(State::Initial
),
6120 mWaitingForFinish(false) {}
6122 LSRequestBase::~LSRequestBase() {
6123 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
6124 mState
== State::Initial
|| mState
== State::Completed
);
6127 void LSRequestBase::Dispatch() {
6128 AssertIsOnOwningThread();
6130 mState
= State::StartingRequest
;
6132 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
6135 void LSRequestBase::StringifyState(nsACString
& aResult
) const {
6136 AssertIsOnOwningThread();
6139 case State::Initial
:
6140 aResult
.AppendLiteral("Initial");
6143 case State::StartingRequest
:
6144 aResult
.AppendLiteral("StartingRequest");
6147 case State::Nesting
:
6148 aResult
.AppendLiteral("Nesting");
6151 case State::SendingReadyMessage
:
6152 aResult
.AppendLiteral("SendingReadyMessage");
6155 case State::WaitingForFinish
:
6156 aResult
.AppendLiteral("WaitingForFinish");
6159 case State::SendingResults
:
6160 aResult
.AppendLiteral("SendingResults");
6163 case State::Completed
:
6164 aResult
.AppendLiteral("Completed");
6168 MOZ_CRASH("Bad state!");
6172 void LSRequestBase::Stringify(nsACString
& aResult
) const {
6173 AssertIsOnOwningThread();
6175 aResult
.AppendLiteral("State:");
6176 StringifyState(aResult
);
6179 void LSRequestBase::Log() {
6180 AssertIsOnOwningThread();
6182 if (!LS_LOG_TEST()) {
6186 LS_LOG(("LSRequestBase [%p]", this));
6189 StringifyState(state
);
6191 LS_LOG((" mState: %s", state
.get()));
6194 nsresult
LSRequestBase::NestedRun() { return NS_OK
; }
6196 bool LSRequestBase::VerifyRequestParams() {
6197 AssertIsOnBackgroundThread();
6199 MOZ_ASSERT(mParams
.type() != LSRequestParams::T__None
);
6201 switch (mParams
.type()) {
6202 case LSRequestParams::TLSRequestPreloadDatastoreParams
: {
6203 const LSRequestCommonParams
& params
=
6204 mParams
.get_LSRequestPreloadDatastoreParams().commonParams();
6206 if (NS_WARN_IF(!VerifyPrincipalInfo(
6207 params
.principalInfo(), params
.storagePrincipalInfo(), false))) {
6212 !VerifyOriginKey(params
.originKey(), params
.principalInfo()))) {
6219 case LSRequestParams::TLSRequestPrepareDatastoreParams
: {
6220 const LSRequestPrepareDatastoreParams
& params
=
6221 mParams
.get_LSRequestPrepareDatastoreParams();
6223 const LSRequestCommonParams
& commonParams
= params
.commonParams();
6225 if (NS_WARN_IF(!VerifyPrincipalInfo(commonParams
.principalInfo(),
6226 commonParams
.storagePrincipalInfo(),
6231 if (params
.clientPrincipalInfo() &&
6232 NS_WARN_IF(!VerifyPrincipalInfo(commonParams
.principalInfo(),
6233 params
.clientPrincipalInfo().ref(),
6238 if (NS_WARN_IF(!VerifyClientId(mContentParentId
,
6239 params
.clientPrincipalInfo(),
6240 params
.clientId()))) {
6244 if (NS_WARN_IF(!VerifyOriginKey(commonParams
.originKey(),
6245 commonParams
.principalInfo()))) {
6252 case LSRequestParams::TLSRequestPrepareObserverParams
: {
6253 const LSRequestPrepareObserverParams
& params
=
6254 mParams
.get_LSRequestPrepareObserverParams();
6256 if (NS_WARN_IF(!VerifyPrincipalInfo(
6257 params
.principalInfo(), params
.storagePrincipalInfo(), false))) {
6261 if (params
.clientPrincipalInfo() &&
6262 NS_WARN_IF(!VerifyPrincipalInfo(params
.principalInfo(),
6263 params
.clientPrincipalInfo().ref(),
6268 if (NS_WARN_IF(!VerifyClientId(mContentParentId
,
6269 params
.clientPrincipalInfo(),
6270 params
.clientId()))) {
6278 MOZ_CRASH("Should never get here!");
6284 nsresult
LSRequestBase::StartRequest() {
6285 AssertIsOnOwningThread();
6286 MOZ_ASSERT(mState
== State::StartingRequest
);
6288 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6290 return NS_ERROR_ABORT
;
6294 // Always verify parameters in DEBUG builds!
6295 bool trustParams
= false;
6297 bool trustParams
= !BackgroundParent::IsOtherProcessActor(Manager());
6300 if (!trustParams
&& NS_WARN_IF(!VerifyRequestParams())) {
6301 return NS_ERROR_FAILURE
;
6304 QM_TRY(MOZ_TO_RESULT(Start()));
6309 void LSRequestBase::SendReadyMessage() {
6310 AssertIsOnOwningThread();
6311 MOZ_ASSERT(mState
== State::SendingReadyMessage
);
6313 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6315 MaybeSetFailureCode(NS_ERROR_ABORT
);
6318 nsresult rv
= SendReadyMessageInternal();
6319 if (NS_WARN_IF(NS_FAILED(rv
))) {
6320 MaybeSetFailureCode(rv
);
6326 nsresult
LSRequestBase::SendReadyMessageInternal() {
6327 AssertIsOnOwningThread();
6328 MOZ_ASSERT(mState
== State::SendingReadyMessage
);
6330 if (!MayProceed()) {
6331 return NS_ERROR_ABORT
;
6334 if (NS_WARN_IF(!SendReady())) {
6335 return NS_ERROR_FAILURE
;
6338 mState
= State::WaitingForFinish
;
6340 mWaitingForFinish
= true;
6345 void LSRequestBase::Finish() {
6346 AssertIsOnOwningThread();
6347 MOZ_ASSERT(mState
== State::WaitingForFinish
);
6349 mWaitingForFinish
= false;
6354 void LSRequestBase::FinishInternal() {
6355 AssertIsOnOwningThread();
6356 MOZ_ASSERT(mState
== State::SendingReadyMessage
||
6357 mState
== State::WaitingForFinish
);
6359 mState
= State::SendingResults
;
6361 // This LSRequestBase can only be held alive by the IPDL. Run() can end up
6362 // with clearing that last reference. So we need to add a self reference here.
6363 RefPtr
<LSRequestBase
> kungFuDeathGrip
= this;
6365 MOZ_ALWAYS_SUCCEEDS(this->Run());
6368 void LSRequestBase::SendResults() {
6369 AssertIsOnOwningThread();
6370 MOZ_ASSERT(mState
== State::SendingResults
);
6372 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6374 MaybeSetFailureCode(NS_ERROR_ABORT
);
6378 LSRequestResponse response
;
6380 if (NS_SUCCEEDED(ResultCode())) {
6381 GetResponse(response
);
6383 MOZ_ASSERT(response
.type() != LSRequestResponse::T__None
);
6385 if (response
.type() == LSRequestResponse::Tnsresult
) {
6386 MOZ_ASSERT(NS_FAILED(response
.get_nsresult()));
6388 SetFailureCode(response
.get_nsresult());
6391 response
= ResultCode();
6394 Unused
<< PBackgroundLSRequestParent::Send__delete__(this, response
);
6399 mState
= State::Completed
;
6403 LSRequestBase::Run() {
6407 case State::StartingRequest
:
6408 rv
= StartRequest();
6411 case State::Nesting
:
6415 case State::SendingReadyMessage
:
6419 case State::SendingResults
:
6424 MOZ_CRASH("Bad state!");
6427 if (NS_WARN_IF(NS_FAILED(rv
)) && mState
!= State::SendingReadyMessage
) {
6428 MaybeSetFailureCode(rv
);
6430 // Must set mState before dispatching otherwise we will race with the owning
6432 mState
= State::SendingReadyMessage
;
6434 if (IsOnOwningThread()) {
6437 MOZ_ALWAYS_SUCCEEDS(
6438 OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
));
6445 void LSRequestBase::ActorDestroy(ActorDestroyReason aWhy
) {
6446 AssertIsOnOwningThread();
6450 // Assume ActorDestroy can happen at any time, so we can't probe the current
6451 // state since mState can be modified on any thread (only one thread at a time
6452 // based on the state machine). However we can use mWaitingForFinish which is
6453 // only touched on the owning thread. If mWaitingForFinisg is true, we can
6454 // also modify mState since we are guaranteed that there are no pending
6455 // runnables which would probe mState to decide what code needs to run (there
6456 // shouldn't be any running runnables on other threads either).
6458 if (mWaitingForFinish
) {
6462 // We don't have to handle the case when mWaitingForFinish is not true since
6463 // it means that either nothing has been initialized yet, so nothing to
6464 // cleanup or there are pending runnables that will detect that the actor has
6465 // been destroyed and cleanup accordingly.
6468 mozilla::ipc::IPCResult
LSRequestBase::RecvCancel() {
6469 AssertIsOnOwningThread();
6473 const char* crashOnCancel
= PR_GetEnv("LSNG_CRASH_ON_CANCEL");
6474 if (crashOnCancel
) {
6475 MOZ_CRASH("LSNG: Crash on cancel.");
6478 IProtocol
* mgr
= Manager();
6479 if (!PBackgroundLSRequestParent::Send__delete__(this, NS_ERROR_ABORT
)) {
6480 return IPC_FAIL(mgr
, "Send__delete__ failed!");
6486 mozilla::ipc::IPCResult
LSRequestBase::RecvFinish() {
6487 AssertIsOnOwningThread();
6494 /*******************************************************************************
6495 * PrepareDatastoreOp
6496 ******************************************************************************/
6498 PrepareDatastoreOp::PrepareDatastoreOp(
6499 const LSRequestParams
& aParams
,
6500 const Maybe
<ContentParentId
>& aContentParentId
)
6501 : LSRequestBase(aParams
, aContentParentId
),
6502 mLoadDataOp(nullptr),
6503 mPrivateBrowsingId(0),
6508 mNestedState(NestedState::BeforeNesting
),
6509 mForPreload(aParams
.type() ==
6510 LSRequestParams::TLSRequestPreloadDatastoreParams
),
6511 mDatabaseNotAvailable(false),
6519 aParams
.type() == LSRequestParams::TLSRequestPreloadDatastoreParams
||
6520 aParams
.type() == LSRequestParams::TLSRequestPrepareDatastoreParams
);
6523 PrepareDatastoreOp::~PrepareDatastoreOp() {
6524 MOZ_ASSERT(!mDirectoryLock
);
6525 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
6526 mState
== State::Initial
|| mState
== State::Completed
);
6527 MOZ_ASSERT(!mLoadDataOp
);
6530 void PrepareDatastoreOp::StringifyNestedState(nsACString
& aResult
) const {
6531 AssertIsOnOwningThread();
6533 switch (mNestedState
) {
6534 case NestedState::BeforeNesting
:
6535 aResult
.AppendLiteral("BeforeNesting");
6538 case NestedState::CheckExistingOperations
:
6539 aResult
.AppendLiteral("CheckExistingOperations");
6542 case NestedState::CheckClosingDatastore
:
6543 aResult
.AppendLiteral("CheckClosingDatastore");
6546 case NestedState::PreparationPending
:
6547 aResult
.AppendLiteral("PreparationPending");
6550 case NestedState::DirectoryOpenPending
:
6551 aResult
.AppendLiteral("DirectoryOpenPending");
6554 case NestedState::DatabaseWorkOpen
:
6555 aResult
.AppendLiteral("DatabaseWorkOpen");
6558 case NestedState::BeginLoadData
:
6559 aResult
.AppendLiteral("BeginLoadData");
6562 case NestedState::DatabaseWorkLoadData
:
6563 aResult
.AppendLiteral("DatabaseWorkLoadData");
6566 case NestedState::AfterNesting
:
6567 aResult
.AppendLiteral("AfterNesting");
6571 MOZ_CRASH("Bad state!");
6575 void PrepareDatastoreOp::Stringify(nsACString
& aResult
) const {
6576 AssertIsOnOwningThread();
6578 LSRequestBase::Stringify(aResult
);
6579 aResult
.Append(kQuotaGenericDelimiter
);
6581 aResult
.AppendLiteral("Origin:");
6582 aResult
.Append(AnonymizedOriginString(Origin()));
6583 aResult
.Append(kQuotaGenericDelimiter
);
6585 aResult
.AppendLiteral("NestedState:");
6586 StringifyNestedState(aResult
);
6589 void PrepareDatastoreOp::Log() {
6590 AssertIsOnOwningThread();
6592 LSRequestBase::Log();
6594 if (!LS_LOG_TEST()) {
6598 nsCString nestedState
;
6599 StringifyNestedState(nestedState
);
6601 LS_LOG((" mNestedState: %s", nestedState
.get()));
6603 switch (mNestedState
) {
6604 case NestedState::CheckClosingDatastore
: {
6605 for (uint32_t index
= gPrepareDatastoreOps
->Length(); index
> 0;
6607 const auto& existingOp
= (*gPrepareDatastoreOps
)[index
- 1];
6609 if (existingOp
->mDelayedOp
== this) {
6610 LS_LOG((" mDelayedBy: [%p]",
6611 static_cast<PrepareDatastoreOp
*>(existingOp
.get())));
6622 case NestedState::DirectoryOpenPending
: {
6623 MOZ_ASSERT(mPendingDirectoryLock
);
6625 LS_LOG((" mPendingDirectoryLock: [%p]", mPendingDirectoryLock
.get()));
6627 mPendingDirectoryLock
->Log();
6636 nsresult
PrepareDatastoreOp::Start() {
6637 AssertIsOnOwningThread();
6638 MOZ_ASSERT(mState
== State::StartingRequest
);
6639 MOZ_ASSERT(mNestedState
== NestedState::BeforeNesting
);
6640 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6641 MOZ_ASSERT(MayProceed());
6643 QM_TRY(QuotaManager::EnsureCreated());
6645 const LSRequestCommonParams
& commonParams
=
6647 ? mParams
.get_LSRequestPreloadDatastoreParams().commonParams()
6648 : mParams
.get_LSRequestPrepareDatastoreParams().commonParams();
6650 const PrincipalInfo
& storagePrincipalInfo
=
6651 commonParams
.storagePrincipalInfo();
6653 if (storagePrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
) {
6654 mOriginMetadata
= {QuotaManager::GetInfoForChrome(),
6655 PERSISTENCE_TYPE_DEFAULT
};
6657 MOZ_ASSERT(storagePrincipalInfo
.type() ==
6658 PrincipalInfo::TContentPrincipalInfo
);
6660 QM_TRY_UNWRAP(auto principalMetadata
,
6661 QuotaManager::Get()->GetInfoFromValidatedPrincipalInfo(
6662 storagePrincipalInfo
));
6664 mOriginMetadata
.mSuffix
= std::move(principalMetadata
.mSuffix
);
6665 mOriginMetadata
.mGroup
= std::move(principalMetadata
.mGroup
);
6666 // XXX We can probably get rid of mMainThreadOrigin if we change
6667 // LSRequestBase::Dispatch to synchronously run LSRequestBase::StartRequest
6668 // through LSRequestBase::Run.
6669 mMainThreadOrigin
= std::move(principalMetadata
.mOrigin
);
6670 mOriginMetadata
.mStorageOrigin
=
6671 std::move(principalMetadata
.mStorageOrigin
);
6672 mOriginMetadata
.mIsPrivate
= principalMetadata
.mIsPrivate
;
6673 mOriginMetadata
.mPersistenceType
= principalMetadata
.mIsPrivate
6674 ? PERSISTENCE_TYPE_PRIVATE
6675 : PERSISTENCE_TYPE_DEFAULT
;
6678 mState
= State::Nesting
;
6679 mNestedState
= NestedState::CheckExistingOperations
;
6681 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
));
6686 nsresult
PrepareDatastoreOp::CheckExistingOperations() {
6687 AssertIsOnOwningThread();
6688 MOZ_ASSERT(mState
== State::Nesting
);
6689 MOZ_ASSERT(mNestedState
== NestedState::CheckExistingOperations
);
6690 MOZ_ASSERT(gPrepareDatastoreOps
);
6692 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6694 return NS_ERROR_ABORT
;
6697 const LSRequestCommonParams
& commonParams
=
6699 ? mParams
.get_LSRequestPreloadDatastoreParams().commonParams()
6700 : mParams
.get_LSRequestPrepareDatastoreParams().commonParams();
6702 const PrincipalInfo
& storagePrincipalInfo
=
6703 commonParams
.storagePrincipalInfo();
6705 nsCString originAttrSuffix
;
6706 uint32_t privateBrowsingId
;
6708 if (storagePrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
) {
6709 privateBrowsingId
= 0;
6711 MOZ_ASSERT(storagePrincipalInfo
.type() ==
6712 PrincipalInfo::TContentPrincipalInfo
);
6714 const ContentPrincipalInfo
& info
=
6715 storagePrincipalInfo
.get_ContentPrincipalInfo();
6716 const OriginAttributes
& attrs
= info
.attrs();
6717 attrs
.CreateSuffix(originAttrSuffix
);
6719 privateBrowsingId
= attrs
.mPrivateBrowsingId
;
6722 mArchivedOriginScope
= ArchivedOriginScope::CreateFromOrigin(
6723 originAttrSuffix
, commonParams
.originKey());
6724 MOZ_ASSERT(mArchivedOriginScope
);
6726 // Normally it's safe to access member variables without a mutex because even
6727 // though we hop between threads, the variables are never accessed by multiple
6728 // threads at the same time.
6729 // However, the methods OriginIsKnown and Origin can be called at any time.
6730 // So we have to make sure the member variable is set on the same thread as
6731 // those methods are called.
6732 mOriginMetadata
.mOrigin
= mMainThreadOrigin
;
6734 MOZ_ASSERT(OriginIsKnown());
6736 mPrivateBrowsingId
= privateBrowsingId
;
6738 mNestedState
= NestedState::CheckClosingDatastore
;
6740 // See if this PrepareDatastoreOp needs to wait.
6741 bool foundThis
= false;
6742 for (uint32_t index
= gPrepareDatastoreOps
->Length(); index
> 0; index
--) {
6743 const auto& existingOp
= (*gPrepareDatastoreOps
)[index
- 1];
6745 if (existingOp
== this) {
6750 if (foundThis
&& existingOp
->Origin() == Origin()) {
6751 // Only one op can be delayed.
6752 MOZ_ASSERT(!existingOp
->mDelayedOp
);
6753 existingOp
->mDelayedOp
= this;
6759 QM_TRY(MOZ_TO_RESULT(CheckClosingDatastoreInternal()));
6764 nsresult
PrepareDatastoreOp::CheckClosingDatastore() {
6765 AssertIsOnOwningThread();
6766 MOZ_ASSERT(mState
== State::Nesting
);
6767 MOZ_ASSERT(mNestedState
== NestedState::CheckClosingDatastore
);
6769 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6771 return NS_ERROR_ABORT
;
6774 QM_TRY(MOZ_TO_RESULT(CheckClosingDatastoreInternal()));
6779 nsresult
PrepareDatastoreOp::CheckClosingDatastoreInternal() {
6780 AssertIsOnOwningThread();
6781 MOZ_ASSERT(mState
== State::Nesting
);
6782 MOZ_ASSERT(mNestedState
== NestedState::CheckClosingDatastore
);
6783 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6784 MOZ_ASSERT(MayProceed());
6786 mNestedState
= NestedState::PreparationPending
;
6788 RefPtr
<Datastore
> datastore
;
6789 if ((datastore
= GetDatastore(Origin())) && datastore
->IsClosed()) {
6790 datastore
->WaitForConnectionToComplete(this);
6795 QM_TRY(MOZ_TO_RESULT(BeginDatastorePreparationInternal()));
6800 nsresult
PrepareDatastoreOp::BeginDatastorePreparation() {
6801 AssertIsOnOwningThread();
6802 MOZ_ASSERT(mState
== State::Nesting
);
6803 MOZ_ASSERT(mNestedState
== NestedState::PreparationPending
);
6805 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
6807 return NS_ERROR_ABORT
;
6810 QM_TRY(MOZ_TO_RESULT(BeginDatastorePreparationInternal()));
6815 nsresult
PrepareDatastoreOp::BeginDatastorePreparationInternal() {
6816 AssertIsOnOwningThread();
6817 MOZ_ASSERT(mState
== State::Nesting
);
6818 MOZ_ASSERT(mNestedState
== NestedState::PreparationPending
);
6819 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6820 MOZ_ASSERT(MayProceed());
6821 MOZ_ASSERT(OriginIsKnown());
6822 MOZ_ASSERT(!mDirectoryLock
);
6824 if ((mDatastore
= GetDatastore(Origin()))) {
6825 MOZ_ASSERT(!mDatastore
->IsClosed());
6827 mDatastore
->NoteLivePrepareDatastoreOp(this);
6834 QuotaManager
* quotaManager
= QuotaManager::Get();
6835 MOZ_ASSERT(quotaManager
);
6837 mNestedState
= NestedState::DirectoryOpenPending
;
6840 ->OpenClientDirectory({mOriginMetadata
, mozilla::dom::quota::Client::LS
},
6841 SomeRef(mPendingDirectoryLock
))
6843 GetCurrentSerialEventTarget(), __func__
,
6844 [self
= RefPtr(this)](
6845 const ClientDirectoryLockPromise::ResolveOrRejectValue
& aValue
) {
6846 self
->mPendingDirectoryLock
= nullptr;
6848 if (aValue
.IsResolve()) {
6849 self
->DirectoryLockAcquired(aValue
.ResolveValue());
6851 self
->DirectoryLockFailed();
6858 void PrepareDatastoreOp::SendToIOThread() {
6859 AssertIsOnOwningThread();
6860 MOZ_ASSERT(mState
== State::Nesting
);
6861 MOZ_ASSERT(mNestedState
== NestedState::DirectoryOpenPending
);
6862 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
6863 MOZ_ASSERT(MayProceed());
6865 // Skip all disk related stuff and transition to SendingReadyMessage if we
6866 // are preparing a datastore for private browsing.
6867 // Note that we do use a directory lock for private browsing even though we
6868 // don't do any stuff on disk. The thing is that without a directory lock,
6869 // quota manager wouldn't call AbortOperationsForLocks for our private
6870 // browsing origin when a clear origin operation is requested.
6871 // AbortOperationsForLocks requests all databases to close and the datastore
6872 // is destroyed in the end. Any following LocalStorage API call will trigger
6873 // preparation of a new (empty) datastore.
6874 if (mPrivateBrowsingId
) {
6880 QuotaManager
* quotaManager
= QuotaManager::Get();
6881 MOZ_ASSERT(quotaManager
);
6883 // Must set this before dispatching otherwise we will race with the IO thread.
6884 mNestedState
= NestedState::DatabaseWorkOpen
;
6886 MOZ_ALWAYS_SUCCEEDS(
6887 quotaManager
->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL
));
6890 nsresult
PrepareDatastoreOp::DatabaseWork() {
6891 AssertIsOnIOThread();
6892 MOZ_ASSERT(mArchivedOriginScope
);
6893 MOZ_ASSERT(mUsage
== 0);
6894 MOZ_ASSERT(mState
== State::Nesting
);
6895 MOZ_ASSERT(mNestedState
== NestedState::DatabaseWorkOpen
);
6897 const auto innerFunc
= [&](const auto&) -> nsresult
{
6898 // XXX This function is too long, refactor it into helper functions for
6901 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
6902 !MayProceedOnNonOwningThread()) {
6903 return NS_ERROR_ABORT
;
6906 QuotaManager
* quotaManager
= QuotaManager::Get();
6907 MOZ_ASSERT(quotaManager
);
6909 // This ensures that usages for existings origin directories are cached in
6911 QM_TRY(MOZ_TO_RESULT(
6912 quotaManager
->EnsureTemporaryStorageIsInitializedInternal()));
6914 const UsageInfo usageInfo
= quotaManager
->GetUsageForClient(
6915 PERSISTENCE_TYPE_DEFAULT
, mOriginMetadata
,
6916 mozilla::dom::quota::Client::LS
);
6918 const bool hasUsage
= usageInfo
.DatabaseUsage().isSome();
6919 MOZ_ASSERT(usageInfo
.FileUsage().isNothing());
6921 if (!gArchivedOrigins
) {
6922 QM_TRY(MOZ_TO_RESULT(LoadArchivedOrigins()));
6923 MOZ_ASSERT(gArchivedOrigins
);
6926 bool hasDataForMigration
=
6927 mArchivedOriginScope
->HasMatches(gArchivedOrigins
);
6929 // If there's nothing to preload (except the case when we want to migrate
6930 // data during preloading), then we can finish the operation without
6931 // creating a datastore in GetResponse (GetResponse won't create a datastore
6932 // if mDatatabaseNotAvailable and mForPreload are both true).
6933 if (mForPreload
&& !hasUsage
&& !hasDataForMigration
) {
6934 return DatabaseNotAvailable();
6937 // The origin directory doesn't need to be created when we don't have data
6938 // for migration. It will be created on the connection thread in
6939 // Connection::EnsureStorageConnection.
6940 // However, origin quota must be initialized, GetQuotaObject in GetResponse
6941 // would fail otherwise.
6943 const auto& directoryEntry
,
6944 ([hasDataForMigration
, "aManager
,
6945 this]() -> mozilla::Result
<nsCOMPtr
<nsIFile
>, nsresult
> {
6946 if (hasDataForMigration
) {
6947 QM_TRY_RETURN(quotaManager
6948 ->EnsureTemporaryOriginIsInitialized(
6949 PERSISTENCE_TYPE_DEFAULT
, mOriginMetadata
)
6950 .map([](const auto& res
) { return res
.first
; }));
6953 MOZ_ASSERT(mOriginMetadata
.mPersistenceType
==
6954 PERSISTENCE_TYPE_DEFAULT
);
6956 QM_TRY_UNWRAP(auto directoryEntry
,
6957 quotaManager
->GetOriginDirectory(mOriginMetadata
));
6959 quotaManager
->EnsureQuotaForOrigin(mOriginMetadata
);
6961 return directoryEntry
;
6964 QM_TRY(MOZ_TO_RESULT(directoryEntry
->Append(
6965 NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME
))));
6968 const auto& directoryPath
,
6969 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString
, directoryEntry
, GetPath
));
6971 // The ls directory doesn't need to be created when we don't have data for
6972 // migration. It will be created on the connection thread in
6973 // Connection::EnsureStorageConnection.
6974 QM_TRY(MOZ_TO_RESULT(
6975 EnsureDirectoryEntry(directoryEntry
,
6976 /* aCreateIfNotExists */ hasDataForMigration
,
6977 /* aIsDirectory */ true)));
6979 QM_TRY(MOZ_TO_RESULT(directoryEntry
->Append(kDataFileName
)));
6981 QM_TRY(MOZ_TO_RESULT(directoryEntry
->GetPath(mDatabaseFilePath
)));
6983 // The database doesn't need to be created when we don't have data for
6984 // migration. It will be created on the connection thread in
6985 // Connection::EnsureStorageConnection.
6986 bool alreadyExisted
;
6987 QM_TRY(MOZ_TO_RESULT(
6988 EnsureDirectoryEntry(directoryEntry
,
6989 /* aCreateIfNotExists */ hasDataForMigration
,
6990 /* aIsDirectory */ false, &alreadyExisted
)));
6992 if (alreadyExisted
) {
6993 // The database does exist.
6994 MOZ_ASSERT(hasUsage
);
6996 // XXX Change type of mUsage to UsageInfo or DatabaseUsageType.
6997 mUsage
= usageInfo
.DatabaseUsage().valueOr(0);
6999 // The database doesn't exist.
7000 MOZ_ASSERT(!hasUsage
);
7002 if (!hasDataForMigration
) {
7003 // The database doesn't exist and we don't have data for migration.
7004 // Finish the operation, but create an empty datastore in GetResponse
7005 // (GetResponse will create an empty datastore if mDatabaseNotAvailable
7006 // is true and mForPreload is false).
7007 return DatabaseNotAvailable();
7011 // We initialized mDatabaseFilePath and mUsage, GetQuotaObject can now be
7013 const RefPtr
<QuotaObject
> quotaObject
= GetQuotaObject();
7015 QM_TRY(OkIf(quotaObject
), Err(NS_ERROR_FAILURE
));
7017 QM_TRY_INSPECT(const auto& usageFile
, GetUsageFile(directoryPath
));
7019 QM_TRY_INSPECT(const auto& usageJournalFile
,
7020 GetUsageJournalFile(directoryPath
));
7023 const auto& connection
,
7024 (CreateStorageConnectionWithRecovery(
7025 *directoryEntry
, *usageFile
, Origin(), ["aObject
, this] {
7026 // This is called when the usage file was removed or we notice
7027 // that the usage file doesn't exist anymore. Adjust the usage
7031 quotaObject
->MaybeUpdateSize(0, /* aTruncate */ true));
7036 QM_TRY(MOZ_TO_RESULT(VerifyDatabaseInformation(connection
)));
7038 if (hasDataForMigration
) {
7039 MOZ_ASSERT(mUsage
== 0);
7042 QM_TRY_INSPECT(const auto& archiveFile
,
7043 GetArchiveFile(quotaManager
->GetStoragePath()));
7045 auto autoArchiveDatabaseAttacher
=
7046 AutoDatabaseAttacher(connection
, archiveFile
, "archive"_ns
);
7048 QM_TRY(MOZ_TO_RESULT(autoArchiveDatabaseAttacher
.Attach()));
7050 QM_TRY_INSPECT(const int64_t& newUsage
,
7051 GetUsage(*connection
, mArchivedOriginScope
.get()));
7054 OkIf(quotaObject
->MaybeUpdateSize(newUsage
, /* aTruncate */ true)),
7055 NS_ERROR_FILE_NO_DEVICE_SPACE
);
7057 auto autoUpdateSize
= MakeScopeExit(["aObject
] {
7059 quotaObject
->MaybeUpdateSize(0, /* aTruncate */ true));
7062 mozStorageTransaction
transaction(
7063 connection
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
7065 QM_TRY(MOZ_TO_RESULT(transaction
.Start()));
7068 nsCOMPtr
<mozIStorageFunction
> function
= new CompressFunction();
7070 QM_TRY(MOZ_TO_RESULT(
7071 connection
->CreateFunction("compress"_ns
, 1, function
)));
7073 function
= new CompressionTypeFunction();
7075 QM_TRY(MOZ_TO_RESULT(
7076 connection
->CreateFunction("compressionType"_ns
, 1, function
)));
7080 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7081 nsCOMPtr
<mozIStorageStatement
>, connection
, CreateStatement
,
7082 "INSERT INTO data (key, utf16_length, conversion_type, "
7083 "compression_type, value) "
7084 "SELECT key, utf16Length(value), :conversionType, "
7085 "compressionType(value), compress(value)"
7086 "FROM webappsstore2 "
7087 "WHERE originKey = :originKey "
7088 "AND originAttributes = :originAttributes;"_ns
));
7090 QM_TRY(MOZ_TO_RESULT(stmt
->BindInt32ByName(
7091 "conversionType"_ns
,
7092 static_cast<int32_t>(LSValue::ConversionType::UTF16_UTF8
))));
7094 QM_TRY(MOZ_TO_RESULT(mArchivedOriginScope
->BindToStatement(stmt
)));
7096 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()));
7098 QM_TRY(MOZ_TO_RESULT(connection
->RemoveFunction("compress"_ns
)));
7101 MOZ_TO_RESULT(connection
->RemoveFunction("compressionType"_ns
)));
7107 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7108 nsCOMPtr
<mozIStorageStatement
>, connection
, CreateStatement
,
7109 "UPDATE database SET usage = :usage;"_ns
));
7111 QM_TRY(MOZ_TO_RESULT(stmt
->BindInt64ByName("usage"_ns
, newUsage
)));
7113 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()));
7119 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7120 nsCOMPtr
<mozIStorageStatement
>, connection
, CreateStatement
,
7121 "DELETE FROM webappsstore2 "
7122 "WHERE originKey = :originKey "
7123 "AND originAttributes = :originAttributes;"_ns
));
7125 QM_TRY(MOZ_TO_RESULT(mArchivedOriginScope
->BindToStatement(stmt
)));
7126 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()));
7129 QM_TRY(MOZ_TO_RESULT(
7130 UpdateUsageFile(usageFile
, usageJournalFile
, newUsage
)));
7131 QM_TRY(MOZ_TO_RESULT(transaction
.Commit()));
7133 autoUpdateSize
.release();
7135 QM_TRY(MOZ_TO_RESULT(usageJournalFile
->Remove(false)));
7139 QM_TRY(MOZ_TO_RESULT(autoArchiveDatabaseAttacher
.Detach()));
7142 MOZ_ASSERT(gArchivedOrigins
);
7143 MOZ_ASSERT(mArchivedOriginScope
->HasMatches(gArchivedOrigins
));
7144 mArchivedOriginScope
->RemoveMatches(gArchivedOrigins
);
7147 nsCOMPtr
<mozIStorageConnection
> shadowConnection
;
7148 if (!gInitializedShadowStorage
) {
7149 QM_TRY_UNWRAP(shadowConnection
,
7150 CreateShadowStorageConnection(quotaManager
->GetBasePath()));
7152 gInitializedShadowStorage
= true;
7155 // Must close connections before dispatching otherwise we might race with
7156 // the connection thread which needs to open the same databases.
7157 MOZ_ALWAYS_SUCCEEDS(connection
->Close());
7159 if (shadowConnection
) {
7160 MOZ_ALWAYS_SUCCEEDS(shadowConnection
->Close());
7163 // Must set this before dispatching otherwise we will race with the owning
7165 mNestedState
= NestedState::BeginLoadData
;
7168 MOZ_TO_RESULT(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
)));
7173 return ExecuteOriginInitialization(
7174 mOriginMetadata
.mOrigin
, LSOriginInitialization::Datastore
,
7175 "dom::localstorage::FirstOriginInitializationAttempt::Datastore"_ns
,
7179 nsresult
PrepareDatastoreOp::DatabaseNotAvailable() {
7180 AssertIsOnIOThread();
7181 MOZ_ASSERT(mState
== State::Nesting
);
7182 MOZ_ASSERT(mNestedState
== NestedState::DatabaseWorkOpen
);
7184 mDatabaseNotAvailable
= true;
7186 nsresult rv
= FinishNestingOnNonOwningThread();
7187 if (NS_WARN_IF(NS_FAILED(rv
))) {
7194 nsresult
PrepareDatastoreOp::EnsureDirectoryEntry(nsIFile
* aEntry
,
7195 bool aCreateIfNotExists
,
7197 bool* aAlreadyExisted
) {
7198 AssertIsOnIOThread();
7201 QM_TRY_INSPECT(const bool& exists
,
7202 MOZ_TO_RESULT_INVOKE_MEMBER(aEntry
, Exists
));
7205 if (!aCreateIfNotExists
) {
7206 if (aAlreadyExisted
) {
7207 *aAlreadyExisted
= false;
7213 QM_TRY(MOZ_TO_RESULT(aEntry
->Create(nsIFile::DIRECTORY_TYPE
, 0755)));
7219 MOZ_ASSERT(NS_SUCCEEDED(aEntry
->IsDirectory(&isDirectory
)));
7220 MOZ_ASSERT(isDirectory
== aIsDirectory
);
7224 if (aAlreadyExisted
) {
7225 *aAlreadyExisted
= exists
;
7230 nsresult
PrepareDatastoreOp::VerifyDatabaseInformation(
7231 mozIStorageConnection
* aConnection
) {
7232 AssertIsOnIOThread();
7233 MOZ_ASSERT(aConnection
);
7235 QM_TRY_INSPECT(const auto& stmt
,
7236 CreateAndExecuteSingleStepStatement
<
7237 SingleStepResult::ReturnNullIfNoResult
>(
7238 *aConnection
, "SELECT origin FROM database"_ns
));
7240 QM_TRY(OkIf(stmt
), NS_ERROR_FILE_CORRUPTED
);
7242 QM_TRY_INSPECT(const auto& origin
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7243 nsCString
, stmt
, GetUTF8String
, 0));
7245 QM_TRY(OkIf(QuotaManager::AreOriginsEqualOnDisk(Origin(), origin
)),
7246 NS_ERROR_FILE_CORRUPTED
);
7251 already_AddRefed
<QuotaObject
> PrepareDatastoreOp::GetQuotaObject() {
7252 MOZ_ASSERT(IsOnOwningThread() || IsOnIOThread());
7253 MOZ_ASSERT(!mOriginMetadata
.mGroup
.IsEmpty());
7254 MOZ_ASSERT(OriginIsKnown());
7255 MOZ_ASSERT(!mDatabaseFilePath
.IsEmpty());
7257 QuotaManager
* quotaManager
= QuotaManager::Get();
7258 MOZ_ASSERT(quotaManager
);
7260 RefPtr
<QuotaObject
> quotaObject
= quotaManager
->GetQuotaObject(
7261 PERSISTENCE_TYPE_DEFAULT
, mOriginMetadata
,
7262 mozilla::dom::quota::Client::LS
, mDatabaseFilePath
, mUsage
);
7265 LS_WARNING("Failed to get quota object for group (%s) and origin (%s)!",
7266 mOriginMetadata
.mGroup
.get(), Origin().get());
7269 return quotaObject
.forget();
7272 nsresult
PrepareDatastoreOp::BeginLoadData() {
7273 AssertIsOnOwningThread();
7274 MOZ_ASSERT(mState
== State::Nesting
);
7275 MOZ_ASSERT(mNestedState
== NestedState::BeginLoadData
);
7276 MOZ_ASSERT(!mConnection
);
7278 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7280 return NS_ERROR_ABORT
;
7283 if (!gConnectionThread
) {
7284 gConnectionThread
= new ConnectionThread();
7287 mConnection
= gConnectionThread
->CreateConnection(
7288 mOriginMetadata
, std::move(mArchivedOriginScope
),
7289 /* aDatabaseWasNotAvailable */ false);
7290 MOZ_ASSERT(mConnection
);
7292 // Must set this before dispatching otherwise we will race with the
7293 // connection thread.
7294 mNestedState
= NestedState::DatabaseWorkLoadData
;
7296 // Can't assign to mLoadDataOp directly since that's a weak reference and
7297 // LoadDataOp is reference counted.
7298 RefPtr
<LoadDataOp
> loadDataOp
= new LoadDataOp(this);
7300 // This add refs loadDataOp.
7301 mConnection
->Dispatch(loadDataOp
);
7303 // This is cleared in LoadDataOp::Cleanup() before the load data op is
7305 mLoadDataOp
= loadDataOp
;
7310 void PrepareDatastoreOp::FinishNesting() {
7311 AssertIsOnOwningThread();
7312 MOZ_ASSERT(mState
== State::Nesting
);
7314 // The caller holds a strong reference to us, no need for a self reference
7315 // before calling Run().
7317 mState
= State::SendingReadyMessage
;
7318 mNestedState
= NestedState::AfterNesting
;
7320 MOZ_ALWAYS_SUCCEEDS(Run());
7323 nsresult
PrepareDatastoreOp::FinishNestingOnNonOwningThread() {
7324 MOZ_ASSERT(!IsOnOwningThread());
7325 MOZ_ASSERT(mState
== State::Nesting
);
7327 // Must set mState before dispatching otherwise we will race with the owning
7329 mState
= State::SendingReadyMessage
;
7330 mNestedState
= NestedState::AfterNesting
;
7333 MOZ_TO_RESULT(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
)));
7338 nsresult
PrepareDatastoreOp::NestedRun() {
7341 switch (mNestedState
) {
7342 case NestedState::CheckExistingOperations
:
7343 rv
= CheckExistingOperations();
7346 case NestedState::CheckClosingDatastore
:
7347 rv
= CheckClosingDatastore();
7350 case NestedState::PreparationPending
:
7351 rv
= BeginDatastorePreparation();
7354 case NestedState::DatabaseWorkOpen
:
7355 rv
= DatabaseWork();
7358 case NestedState::BeginLoadData
:
7359 rv
= BeginLoadData();
7363 MOZ_CRASH("Bad state!");
7366 if (NS_WARN_IF(NS_FAILED(rv
))) {
7367 mNestedState
= NestedState::AfterNesting
;
7375 void PrepareDatastoreOp::GetResponse(LSRequestResponse
& aResponse
) {
7376 AssertIsOnOwningThread();
7377 MOZ_ASSERT(mState
== State::SendingResults
);
7378 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
7379 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7380 MOZ_ASSERT(MayProceed());
7382 // A datastore is not created when we are just trying to preload data and
7383 // there's no database file.
7384 if (mDatabaseNotAvailable
&& mForPreload
) {
7385 LSRequestPreloadDatastoreResponse preloadDatastoreResponse
;
7387 aResponse
= preloadDatastoreResponse
;
7393 MOZ_ASSERT(mUsage
== mDEBUGUsage
);
7395 RefPtr
<QuotaObject
> quotaObject
;
7397 if (mPrivateBrowsingId
== 0) {
7399 // This can happen when there's no database file.
7400 MOZ_ASSERT(mDatabaseNotAvailable
);
7402 // Even though there's no database file, we need to create a connection
7403 // and pass it to datastore.
7404 if (!gConnectionThread
) {
7405 gConnectionThread
= new ConnectionThread();
7408 mConnection
= gConnectionThread
->CreateConnection(
7409 mOriginMetadata
, std::move(mArchivedOriginScope
),
7410 /* aDatabaseWasNotAvailable */ true);
7411 MOZ_ASSERT(mConnection
);
7414 quotaObject
= GetQuotaObject();
7416 aResponse
= NS_ERROR_FAILURE
;
7421 mDatastore
= new Datastore(
7422 mOriginMetadata
, mPrivateBrowsingId
, mUsage
, mSizeOfKeys
, mSizeOfItems
,
7423 std::move(mDirectoryLock
), std::move(mConnection
),
7424 std::move(quotaObject
), mValues
, std::move(mOrderedItems
));
7426 mDatastore
->NoteLivePrepareDatastoreOp(this);
7429 gDatastores
= new DatastoreHashtable();
7432 MOZ_ASSERT(!gDatastores
->Contains(Origin()));
7433 gDatastores
->InsertOrUpdate(Origin(),
7434 WrapMovingNotNullUnchecked(mDatastore
));
7437 if (mPrivateBrowsingId
&& !mInvalidated
) {
7438 if (!gPrivateDatastores
) {
7439 gPrivateDatastores
= MakeUnique
<PrivateDatastoreHashtable
>();
7442 gPrivateDatastores
->LookupOrInsertWith(Origin(), [&] {
7443 auto privateDatastore
=
7444 MakeUnique
<PrivateDatastore
>(WrapMovingNotNull(mDatastore
));
7446 mPrivateDatastoreRegistered
.Flip();
7448 return privateDatastore
;
7452 mDatastoreId
= ++gLastDatastoreId
;
7454 if (!gPreparedDatastores
) {
7455 gPreparedDatastores
= new PreparedDatastoreHashtable();
7457 const auto& preparedDatastore
= gPreparedDatastores
->InsertOrUpdate(
7458 mDatastoreId
, MakeUnique
<PreparedDatastore
>(
7459 mDatastore
, mContentParentId
, Origin(), mDatastoreId
,
7460 /* aForPreload */ mForPreload
));
7463 preparedDatastore
->Invalidate();
7466 mPreparedDatastoreRegistered
.Flip();
7469 LSRequestPreloadDatastoreResponse preloadDatastoreResponse
;
7471 aResponse
= preloadDatastoreResponse
;
7473 LSRequestPrepareDatastoreResponse prepareDatastoreResponse
;
7474 prepareDatastoreResponse
.datastoreId() = mDatastoreId
;
7476 aResponse
= prepareDatastoreResponse
;
7480 void PrepareDatastoreOp::Cleanup() {
7481 AssertIsOnOwningThread();
7484 MOZ_ASSERT(!mDirectoryLock
);
7485 MOZ_ASSERT(!mConnection
);
7487 if (NS_FAILED(ResultCode())) {
7488 if (mPrivateDatastoreRegistered
) {
7489 MOZ_ASSERT(gPrivateDatastores
);
7490 DebugOnly
<bool> removed
= gPrivateDatastores
->Remove(Origin());
7491 MOZ_ASSERT(removed
);
7493 if (!gPrivateDatastores
->Count()) {
7494 gPrivateDatastores
= nullptr;
7498 if (mPreparedDatastoreRegistered
) {
7499 // Just in case we failed to send datastoreId to the child, we need to
7500 // destroy prepared datastore, otherwise it won't be destroyed until
7501 // the timer fires (after 20 seconds).
7502 MOZ_ASSERT(gPreparedDatastores
);
7503 MOZ_ASSERT(mDatastoreId
> 0);
7504 DebugOnly
<bool> removed
= gPreparedDatastores
->Remove(mDatastoreId
);
7505 MOZ_ASSERT(removed
);
7507 if (!gPreparedDatastores
->Count()) {
7508 gPreparedDatastores
= nullptr;
7513 // Make sure to release the datastore on this thread.
7515 mDatastore
->NoteFinishedPrepareDatastoreOp(this);
7517 mDatastore
= nullptr;
7520 } else if (mConnection
) {
7521 // If we have a connection then the operation must have failed and there
7522 // must be a directory lock too.
7523 MOZ_ASSERT(NS_FAILED(ResultCode()));
7524 MOZ_ASSERT(mDirectoryLock
);
7526 // We must close the connection on the connection thread before releasing
7527 // it on this thread. The directory lock can't be released either.
7528 nsCOMPtr
<nsIRunnable
> callback
=
7529 NewRunnableMethod("dom::OpenDatabaseOp::ConnectionClosedCallback", this,
7530 &PrepareDatastoreOp::ConnectionClosedCallback
);
7532 mConnection
->Close(callback
);
7534 // If we don't have a connection, but we do have a directory lock then the
7535 // operation must have failed or we were preloading a datastore and there
7536 // was no physical database on disk.
7537 MOZ_ASSERT_IF(mDirectoryLock
,
7538 NS_FAILED(ResultCode()) || mDatabaseNotAvailable
);
7540 // There's no connection, so it's safe to release the directory lock and
7541 // unregister itself from the array.
7543 mDirectoryLock
= nullptr;
7549 void PrepareDatastoreOp::ConnectionClosedCallback() {
7550 AssertIsOnOwningThread();
7551 MOZ_ASSERT(NS_FAILED(ResultCode()));
7552 MOZ_ASSERT(mDirectoryLock
);
7553 MOZ_ASSERT(mConnection
);
7555 mConnection
= nullptr;
7556 mDirectoryLock
= nullptr;
7561 void PrepareDatastoreOp::CleanupMetadata() {
7562 AssertIsOnOwningThread();
7565 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp
.forget()));
7568 MOZ_ASSERT(gPrepareDatastoreOps
);
7569 gPrepareDatastoreOps
->RemoveElement(this);
7571 QuotaManager::MaybeRecordQuotaClientShutdownStep(
7572 quota::Client::LS
, "PrepareDatastoreOp completed"_ns
);
7574 if (gPrepareDatastoreOps
->IsEmpty()) {
7575 gPrepareDatastoreOps
= nullptr;
7579 void PrepareDatastoreOp::ActorDestroy(ActorDestroyReason aWhy
) {
7580 AssertIsOnOwningThread();
7582 LSRequestBase::ActorDestroy(aWhy
);
7585 mLoadDataOp
->NoteComplete();
7589 void PrepareDatastoreOp::DirectoryLockAcquired(DirectoryLock
* aLock
) {
7590 AssertIsOnOwningThread();
7591 MOZ_ASSERT(mState
== State::Nesting
);
7592 MOZ_ASSERT(mNestedState
== NestedState::DirectoryOpenPending
);
7593 MOZ_ASSERT(!mDirectoryLock
);
7595 mPendingDirectoryLock
= nullptr;
7597 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7599 MaybeSetFailureCode(NS_ERROR_ABORT
);
7606 mDirectoryLock
= aLock
;
7611 void PrepareDatastoreOp::DirectoryLockFailed() {
7612 AssertIsOnOwningThread();
7613 MOZ_ASSERT(mState
== State::Nesting
);
7614 MOZ_ASSERT(mNestedState
== NestedState::DirectoryOpenPending
);
7615 MOZ_ASSERT(!mDirectoryLock
);
7617 mPendingDirectoryLock
= nullptr;
7619 MaybeSetFailureCode(NS_ERROR_FAILURE
);
7624 nsresult
PrepareDatastoreOp::LoadDataOp::DoDatastoreWork() {
7625 AssertIsOnGlobalConnectionThread();
7626 MOZ_ASSERT(mConnection
);
7627 MOZ_ASSERT(mPrepareDatastoreOp
);
7628 MOZ_ASSERT(mPrepareDatastoreOp
->mState
== State::Nesting
);
7629 MOZ_ASSERT(mPrepareDatastoreOp
->mNestedState
==
7630 NestedState::DatabaseWorkLoadData
);
7632 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
7633 !MayProceedOnNonOwningThread()) {
7634 return NS_ERROR_ABORT
;
7639 mConnection
->BorrowCachedStatement(
7640 "SELECT key, utf16_length, conversion_type, compression_type, value "
7643 QM_TRY(quota::CollectWhileHasResult(
7644 *stmt
, [this](auto& stmt
) -> mozilla::Result
<Ok
, nsresult
> {
7645 QM_TRY_UNWRAP(auto key
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7646 nsString
, stmt
, GetString
, 0));
7649 QM_TRY(MOZ_TO_RESULT(value
.InitFromStatement(&stmt
, 1)));
7651 mPrepareDatastoreOp
->mValues
.InsertOrUpdate(key
, value
);
7652 mPrepareDatastoreOp
->mSizeOfKeys
+= key
.Length();
7653 mPrepareDatastoreOp
->mSizeOfItems
+= key
.Length() + value
.Length();
7655 mPrepareDatastoreOp
->mDEBUGUsage
+= key
.Length() + value
.UTF16Length();
7658 auto item
= mPrepareDatastoreOp
->mOrderedItems
.AppendElement();
7659 item
->key() = std::move(key
);
7660 item
->value() = std::move(value
);
7668 void PrepareDatastoreOp::LoadDataOp::OnSuccess() {
7669 AssertIsOnOwningThread();
7670 MOZ_ASSERT(mPrepareDatastoreOp
);
7671 MOZ_ASSERT(mPrepareDatastoreOp
->mState
== State::Nesting
);
7672 MOZ_ASSERT(mPrepareDatastoreOp
->mNestedState
==
7673 NestedState::DatabaseWorkLoadData
);
7674 MOZ_ASSERT(mPrepareDatastoreOp
->mLoadDataOp
== this);
7676 mPrepareDatastoreOp
->FinishNesting();
7679 void PrepareDatastoreOp::LoadDataOp::OnFailure(nsresult aResultCode
) {
7680 AssertIsOnOwningThread();
7681 MOZ_ASSERT(mPrepareDatastoreOp
);
7682 MOZ_ASSERT(mPrepareDatastoreOp
->mState
== State::Nesting
);
7683 MOZ_ASSERT(mPrepareDatastoreOp
->mNestedState
==
7684 NestedState::DatabaseWorkLoadData
);
7685 MOZ_ASSERT(mPrepareDatastoreOp
->mLoadDataOp
== this);
7687 mPrepareDatastoreOp
->SetFailureCode(aResultCode
);
7689 mPrepareDatastoreOp
->FinishNesting();
7692 void PrepareDatastoreOp::LoadDataOp::Cleanup() {
7693 AssertIsOnOwningThread();
7694 MOZ_ASSERT(mPrepareDatastoreOp
);
7695 MOZ_ASSERT(mPrepareDatastoreOp
->mLoadDataOp
== this);
7697 mPrepareDatastoreOp
->mLoadDataOp
= nullptr;
7698 mPrepareDatastoreOp
= nullptr;
7700 ConnectionDatastoreOperationBase::Cleanup();
7703 NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressFunction
, mozIStorageFunction
)
7706 PrepareDatastoreOp::CompressFunction::OnFunctionCall(
7707 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
) {
7708 AssertIsOnIOThread();
7709 MOZ_ASSERT(aFunctionArguments
);
7710 MOZ_ASSERT(aResult
);
7715 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments
->GetNumEntries(&argCount
));
7716 MOZ_ASSERT(argCount
== 1);
7719 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments
->GetTypeOfIndex(0, &type
));
7720 MOZ_ASSERT(type
== mozIStorageValueArray::VALUE_TYPE_TEXT
);
7724 QM_TRY_INSPECT(const auto& value
,
7725 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7726 nsCString
, aFunctionArguments
, GetUTF8String
, 0));
7728 nsCString compressed
;
7729 QM_TRY(OkIf(SnappyCompress(value
, compressed
)), NS_ERROR_OUT_OF_MEMORY
);
7731 const nsCString
& buffer
= compressed
.IsVoid() ? value
: compressed
;
7733 // mozStorage transforms empty blobs into null values, but our database
7734 // schema doesn't allow null values. We can workaround this by storing
7735 // empty buffers as UTF8 text (SQLite supports the type affinity, so the type
7736 // of the column is not fixed).
7737 nsCOMPtr
<nsIVariant
> result
;
7738 if (0u == buffer
.Length()) { // Otherwise empty string becomes null
7739 result
= new storage::UTF8TextVariant(buffer
);
7741 result
= new storage::BlobVariant(std::make_pair(
7742 static_cast<const void*>(buffer
.get()), int(buffer
.Length())));
7745 result
.forget(aResult
);
7749 NS_IMPL_ISUPPORTS(PrepareDatastoreOp::CompressionTypeFunction
,
7750 mozIStorageFunction
)
7753 PrepareDatastoreOp::CompressionTypeFunction::OnFunctionCall(
7754 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
) {
7755 AssertIsOnIOThread();
7756 MOZ_ASSERT(aFunctionArguments
);
7757 MOZ_ASSERT(aResult
);
7762 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments
->GetNumEntries(&argCount
));
7763 MOZ_ASSERT(argCount
== 1);
7766 MOZ_ALWAYS_SUCCEEDS(aFunctionArguments
->GetTypeOfIndex(0, &type
));
7767 MOZ_ASSERT(type
== mozIStorageValueArray::VALUE_TYPE_TEXT
);
7771 QM_TRY_INSPECT(const auto& value
,
7772 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7773 nsCString
, aFunctionArguments
, GetUTF8String
, 0));
7775 nsCString compressed
;
7776 QM_TRY(OkIf(SnappyCompress(value
, compressed
)), NS_ERROR_OUT_OF_MEMORY
);
7778 const int32_t compression
= static_cast<int32_t>(
7779 compressed
.IsVoid() ? LSValue::CompressionType::UNCOMPRESSED
7780 : LSValue::CompressionType::SNAPPY
);
7782 nsCOMPtr
<nsIVariant
> result
= new storage::IntegerVariant(compression
);
7784 result
.forget(aResult
);
7788 /*******************************************************************************
7790 ******************************************************************************/
7792 PrepareObserverOp::PrepareObserverOp(
7793 const LSRequestParams
& aParams
,
7794 const Maybe
<ContentParentId
>& aContentParentId
)
7795 : LSRequestBase(aParams
, aContentParentId
) {
7796 MOZ_ASSERT(aParams
.type() ==
7797 LSRequestParams::TLSRequestPrepareObserverParams
);
7800 nsresult
PrepareObserverOp::Start() {
7801 AssertIsOnOwningThread();
7802 MOZ_ASSERT(mState
== State::StartingRequest
);
7803 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7804 MOZ_ASSERT(MayProceed());
7806 const LSRequestPrepareObserverParams params
=
7807 mParams
.get_LSRequestPrepareObserverParams();
7809 const PrincipalInfo
& storagePrincipalInfo
= params
.storagePrincipalInfo();
7811 if (storagePrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
) {
7812 mOrigin
= QuotaManager::GetOriginForChrome();
7814 MOZ_ASSERT(storagePrincipalInfo
.type() ==
7815 PrincipalInfo::TContentPrincipalInfo
);
7818 QuotaManager::GetOriginFromValidatedPrincipalInfo(storagePrincipalInfo
);
7821 mState
= State::SendingReadyMessage
;
7822 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
));
7827 void PrepareObserverOp::GetResponse(LSRequestResponse
& aResponse
) {
7828 AssertIsOnOwningThread();
7829 MOZ_ASSERT(mState
== State::SendingResults
);
7830 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
7831 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
7832 MOZ_ASSERT(MayProceed());
7834 uint64_t observerId
= ++gLastObserverId
;
7836 RefPtr
<Observer
> observer
= new Observer(mOrigin
);
7838 if (!gPreparedObsevers
) {
7839 gPreparedObsevers
= new PreparedObserverHashtable();
7841 gPreparedObsevers
->InsertOrUpdate(observerId
, std::move(observer
));
7843 LSRequestPrepareObserverResponse prepareObserverResponse
;
7844 prepareObserverResponse
.observerId() = observerId
;
7846 aResponse
= prepareObserverResponse
;
7849 /*******************************************************************************
7850 + * LSSimpleRequestBase
7852 ******************************************************************************/
7854 LSSimpleRequestBase::LSSimpleRequestBase(
7855 const LSSimpleRequestParams
& aParams
,
7856 const Maybe
<ContentParentId
>& aContentParentId
)
7858 mContentParentId(aContentParentId
),
7859 mState(State::Initial
) {}
7861 LSSimpleRequestBase::~LSSimpleRequestBase() {
7862 MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
7863 mState
== State::Initial
|| mState
== State::Completed
);
7866 void LSSimpleRequestBase::Dispatch() {
7867 AssertIsOnOwningThread();
7869 mState
= State::StartingRequest
;
7871 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
7874 bool LSSimpleRequestBase::VerifyRequestParams() {
7875 AssertIsOnBackgroundThread();
7877 MOZ_ASSERT(mParams
.type() != LSSimpleRequestParams::T__None
);
7879 switch (mParams
.type()) {
7880 case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams
: {
7881 const LSSimpleRequestPreloadedParams
& params
=
7882 mParams
.get_LSSimpleRequestPreloadedParams();
7884 if (NS_WARN_IF(!VerifyPrincipalInfo(
7885 params
.principalInfo(), params
.storagePrincipalInfo(), false))) {
7892 case LSSimpleRequestParams::TLSSimpleRequestGetStateParams
: {
7893 const LSSimpleRequestGetStateParams
& params
=
7894 mParams
.get_LSSimpleRequestGetStateParams();
7896 if (NS_WARN_IF(!VerifyPrincipalInfo(
7897 params
.principalInfo(), params
.storagePrincipalInfo(), false))) {
7905 MOZ_CRASH("Should never get here!");
7911 nsresult
LSSimpleRequestBase::StartRequest() {
7912 AssertIsOnOwningThread();
7913 MOZ_ASSERT(mState
== State::StartingRequest
);
7915 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7917 return NS_ERROR_ABORT
;
7921 // Always verify parameters in DEBUG builds!
7922 bool trustParams
= false;
7924 bool trustParams
= !BackgroundParent::IsOtherProcessActor(Manager());
7927 if (!trustParams
&& NS_WARN_IF(!VerifyRequestParams())) {
7928 return NS_ERROR_FAILURE
;
7931 QM_TRY(MOZ_TO_RESULT(Start()));
7936 void LSSimpleRequestBase::SendResults() {
7937 AssertIsOnOwningThread();
7938 MOZ_ASSERT(mState
== State::SendingResults
);
7940 if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
7942 MaybeSetFailureCode(NS_ERROR_ABORT
);
7946 LSSimpleRequestResponse response
;
7948 if (NS_SUCCEEDED(ResultCode())) {
7949 GetResponse(response
);
7951 response
= ResultCode();
7954 Unused
<< PBackgroundLSSimpleRequestParent::Send__delete__(this, response
);
7957 mState
= State::Completed
;
7961 LSSimpleRequestBase::Run() {
7965 case State::StartingRequest
:
7966 rv
= StartRequest();
7969 case State::SendingResults
:
7974 MOZ_CRASH("Bad state!");
7977 if (NS_WARN_IF(NS_FAILED(rv
)) && mState
!= State::SendingResults
) {
7978 MaybeSetFailureCode(rv
);
7980 // Must set mState before dispatching otherwise we will race with the owning
7982 mState
= State::SendingResults
;
7984 if (IsOnOwningThread()) {
7987 MOZ_ALWAYS_SUCCEEDS(
7988 OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
));
7995 void LSSimpleRequestBase::ActorDestroy(ActorDestroyReason aWhy
) {
7996 AssertIsOnOwningThread();
8001 /*******************************************************************************
8003 ******************************************************************************/
8005 PreloadedOp::PreloadedOp(const LSSimpleRequestParams
& aParams
,
8006 const Maybe
<ContentParentId
>& aContentParentId
)
8007 : LSSimpleRequestBase(aParams
, aContentParentId
) {
8008 MOZ_ASSERT(aParams
.type() ==
8009 LSSimpleRequestParams::TLSSimpleRequestPreloadedParams
);
8012 nsresult
PreloadedOp::Start() {
8013 AssertIsOnOwningThread();
8014 MOZ_ASSERT(mState
== State::StartingRequest
);
8015 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8016 MOZ_ASSERT(MayProceed());
8018 const LSSimpleRequestPreloadedParams
& params
=
8019 mParams
.get_LSSimpleRequestPreloadedParams();
8021 const PrincipalInfo
& storagePrincipalInfo
= params
.storagePrincipalInfo();
8024 storagePrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
||
8025 storagePrincipalInfo
.type() == PrincipalInfo::TContentPrincipalInfo
);
8026 mOrigin
= storagePrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
8027 ? nsCString
{QuotaManager::GetOriginForChrome()}
8028 : QuotaManager::GetOriginFromValidatedPrincipalInfo(
8029 storagePrincipalInfo
);
8031 mState
= State::SendingResults
;
8032 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
));
8037 void PreloadedOp::GetResponse(LSSimpleRequestResponse
& aResponse
) {
8038 AssertIsOnOwningThread();
8039 MOZ_ASSERT(mState
== State::SendingResults
);
8040 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
8041 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8042 MOZ_ASSERT(MayProceed());
8045 RefPtr
<Datastore
> datastore
;
8046 if ((datastore
= GetDatastore(mOrigin
)) && !datastore
->IsClosed()) {
8052 LSSimpleRequestPreloadedResponse preloadedResponse
;
8053 preloadedResponse
.preloaded() = preloaded
;
8055 aResponse
= preloadedResponse
;
8058 /*******************************************************************************
8060 ******************************************************************************/
8062 GetStateOp::GetStateOp(const LSSimpleRequestParams
& aParams
,
8063 const Maybe
<ContentParentId
>& aContentParentId
)
8064 : LSSimpleRequestBase(aParams
, aContentParentId
) {
8065 MOZ_ASSERT(aParams
.type() ==
8066 LSSimpleRequestParams::TLSSimpleRequestGetStateParams
);
8069 nsresult
GetStateOp::Start() {
8070 AssertIsOnOwningThread();
8071 MOZ_ASSERT(mState
== State::StartingRequest
);
8072 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8073 MOZ_ASSERT(MayProceed());
8075 const LSSimpleRequestGetStateParams
& params
=
8076 mParams
.get_LSSimpleRequestGetStateParams();
8078 const PrincipalInfo
& storagePrincipalInfo
= params
.storagePrincipalInfo();
8081 storagePrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
||
8082 storagePrincipalInfo
.type() == PrincipalInfo::TContentPrincipalInfo
);
8083 mOrigin
= storagePrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
8084 ? nsCString
{QuotaManager::GetOriginForChrome()}
8085 : QuotaManager::GetOriginFromValidatedPrincipalInfo(
8086 storagePrincipalInfo
);
8088 mState
= State::SendingResults
;
8089 MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL
));
8094 void GetStateOp::GetResponse(LSSimpleRequestResponse
& aResponse
) {
8095 AssertIsOnOwningThread();
8096 MOZ_ASSERT(mState
== State::SendingResults
);
8097 MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
8098 MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
8099 MOZ_ASSERT(MayProceed());
8101 LSSimpleRequestGetStateResponse getStateResponse
;
8103 if (RefPtr
<Datastore
> datastore
= GetDatastore(mOrigin
)) {
8104 if (!datastore
->IsClosed()) {
8105 getStateResponse
.itemInfos() = datastore
->GetOrderedItems().Clone();
8109 aResponse
= getStateResponse
;
8112 /*******************************************************************************
8113 * ArchivedOriginScope
8114 ******************************************************************************/
8117 UniquePtr
<ArchivedOriginScope
> ArchivedOriginScope::CreateFromOrigin(
8118 const nsACString
& aOriginAttrSuffix
, const nsACString
& aOriginKey
) {
8120 new ArchivedOriginScope(Origin(aOriginAttrSuffix
, aOriginKey
)));
8124 UniquePtr
<ArchivedOriginScope
> ArchivedOriginScope::CreateFromPrefix(
8125 const nsACString
& aOriginKey
) {
8126 return WrapUnique(new ArchivedOriginScope(Prefix(aOriginKey
)));
8130 UniquePtr
<ArchivedOriginScope
> ArchivedOriginScope::CreateFromPattern(
8131 const OriginAttributesPattern
& aPattern
) {
8132 return WrapUnique(new ArchivedOriginScope(Pattern(aPattern
)));
8136 UniquePtr
<ArchivedOriginScope
> ArchivedOriginScope::CreateFromNull() {
8137 return WrapUnique(new ArchivedOriginScope(Null()));
8140 nsLiteralCString
ArchivedOriginScope::GetBindingClause() const {
8143 return " WHERE originKey = :originKey "
8144 "AND originAttributes = :originAttributes"_ns
;
8146 [](const Pattern
&) {
8147 return " WHERE originAttributes MATCH :originAttributesPattern"_ns
;
8149 [](const Prefix
&) { return " WHERE originKey = :originKey"_ns
; },
8150 [](const Null
&) { return ""_ns
; });
8153 nsresult
ArchivedOriginScope::BindToStatement(
8154 mozIStorageStatement
* aStmt
) const {
8155 MOZ_ASSERT(IsOnIOThread() || IsOnGlobalConnectionThread());
8159 mozIStorageStatement
* mStmt
;
8161 explicit Matcher(mozIStorageStatement
* aStmt
) : mStmt(aStmt
) {}
8163 nsresult
operator()(const Origin
& aOrigin
) {
8164 QM_TRY(MOZ_TO_RESULT(mStmt
->BindUTF8StringByName(
8165 "originKey"_ns
, aOrigin
.OriginNoSuffix())));
8167 QM_TRY(MOZ_TO_RESULT(mStmt
->BindUTF8StringByName(
8168 "originAttributes"_ns
, aOrigin
.OriginSuffix())));
8173 nsresult
operator()(const Prefix
& aPrefix
) {
8174 QM_TRY(MOZ_TO_RESULT(mStmt
->BindUTF8StringByName(
8175 "originKey"_ns
, aPrefix
.OriginNoSuffix())));
8180 nsresult
operator()(const Pattern
& aPattern
) {
8181 QM_TRY(MOZ_TO_RESULT(mStmt
->BindUTF8StringByName(
8182 "originAttributesPattern"_ns
, "pattern1"_ns
)));
8187 nsresult
operator()(const Null
& aNull
) { return NS_OK
; }
8190 QM_TRY(MOZ_TO_RESULT(mData
.match(Matcher(aStmt
))));
8195 bool ArchivedOriginScope::HasMatches(
8196 ArchivedOriginHashtable
* aHashtable
) const {
8197 AssertIsOnIOThread();
8198 MOZ_ASSERT(aHashtable
);
8201 [aHashtable
](const Origin
& aOrigin
) {
8202 const nsCString hashKey
= GetArchivedOriginHashKey(
8203 aOrigin
.OriginSuffix(), aOrigin
.OriginNoSuffix());
8205 return aHashtable
->Contains(hashKey
);
8207 [aHashtable
](const Pattern
& aPattern
) {
8209 aHashtable
->Values().cbegin(), aHashtable
->Values().cend(),
8210 [&aPattern
](const auto& entry
) {
8211 return aPattern
.GetPattern().Matches(entry
->mOriginAttributes
);
8214 [aHashtable
](const Prefix
& aPrefix
) {
8216 aHashtable
->Values().cbegin(), aHashtable
->Values().cend(),
8217 [&aPrefix
](const auto& entry
) {
8218 return entry
->mOriginNoSuffix
== aPrefix
.OriginNoSuffix();
8221 [aHashtable
](const Null
& aNull
) { return !aHashtable
->IsEmpty(); });
8224 void ArchivedOriginScope::RemoveMatches(
8225 ArchivedOriginHashtable
* aHashtable
) const {
8226 AssertIsOnIOThread();
8227 MOZ_ASSERT(aHashtable
);
8230 ArchivedOriginHashtable
* mHashtable
;
8232 explicit Matcher(ArchivedOriginHashtable
* aHashtable
)
8233 : mHashtable(aHashtable
) {}
8235 void operator()(const Origin
& aOrigin
) {
8236 nsCString hashKey
= GetArchivedOriginHashKey(aOrigin
.OriginSuffix(),
8237 aOrigin
.OriginNoSuffix());
8239 mHashtable
->Remove(hashKey
);
8242 void operator()(const Prefix
& aPrefix
) {
8243 for (auto iter
= mHashtable
->Iter(); !iter
.Done(); iter
.Next()) {
8244 const auto& archivedOriginInfo
= iter
.Data();
8246 if (archivedOriginInfo
->mOriginNoSuffix
== aPrefix
.OriginNoSuffix()) {
8252 void operator()(const Pattern
& aPattern
) {
8253 for (auto iter
= mHashtable
->Iter(); !iter
.Done(); iter
.Next()) {
8254 const auto& archivedOriginInfo
= iter
.Data();
8256 if (aPattern
.GetPattern().Matches(
8257 archivedOriginInfo
->mOriginAttributes
)) {
8263 void operator()(const Null
& aNull
) { mHashtable
->Clear(); }
8266 mData
.match(Matcher(aHashtable
));
8269 /*******************************************************************************
8271 ******************************************************************************/
8273 QuotaClient
* QuotaClient::sInstance
= nullptr;
8275 QuotaClient::QuotaClient()
8276 : mShadowDatabaseMutex("LocalStorage mShadowDatabaseMutex") {
8277 AssertIsOnBackgroundThread();
8278 MOZ_ASSERT(!sInstance
, "We expect this to be a singleton!");
8283 QuotaClient::~QuotaClient() {
8284 AssertIsOnBackgroundThread();
8285 MOZ_ASSERT(sInstance
== this, "We expect this to be a singleton!");
8287 sInstance
= nullptr;
8290 mozilla::dom::quota::Client::Type
QuotaClient::GetType() {
8291 return QuotaClient::LS
;
8294 Result
<UsageInfo
, nsresult
> QuotaClient::InitOrigin(
8295 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
8296 const AtomicBool
& aCanceled
) {
8297 AssertIsOnIOThread();
8298 MOZ_ASSERT(aPersistenceType
== PERSISTENCE_TYPE_DEFAULT
);
8299 MOZ_ASSERT(aOriginMetadata
.mPersistenceType
== aPersistenceType
);
8301 QuotaManager
* quotaManager
= QuotaManager::Get();
8302 MOZ_ASSERT(quotaManager
);
8304 QM_TRY_INSPECT(const auto& directory
,
8305 quotaManager
->GetOriginDirectory(aOriginMetadata
));
8307 MOZ_ASSERT(directory
);
8309 QM_TRY(MOZ_TO_RESULT(
8310 directory
->Append(NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME
))));
8314 QM_TRY_INSPECT(const bool& exists
,
8315 MOZ_TO_RESULT_INVOKE_MEMBER(directory
, Exists
));
8320 QM_TRY_INSPECT(const auto& directoryPath
, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8321 nsString
, directory
, GetPath
));
8323 QM_TRY_INSPECT(const auto& usageFile
, GetUsageFile(directoryPath
));
8325 // XXX Try to make usageFileExists const
8326 QM_TRY_UNWRAP(bool usageFileExists
, ExistsAsFile(*usageFile
));
8328 QM_TRY_INSPECT(const auto& usageJournalFile
,
8329 GetUsageJournalFile(directoryPath
));
8331 QM_TRY_INSPECT(const bool& usageJournalFileExists
,
8332 ExistsAsFile(*usageJournalFile
));
8334 if (usageJournalFileExists
) {
8335 if (usageFileExists
) {
8336 QM_TRY(MOZ_TO_RESULT(usageFile
->Remove(false)));
8338 usageFileExists
= false;
8341 QM_TRY(MOZ_TO_RESULT(usageJournalFile
->Remove(false)));
8344 QM_TRY_INSPECT(const auto& file
,
8345 CloneFileAndAppend(*directory
, kDataFileName
));
8347 QM_TRY_INSPECT(const bool& fileExists
, ExistsAsFile(*file
));
8350 const UsageInfo
& res
,
8351 ([fileExists
, usageFileExists
, &file
, &usageFile
, &usageJournalFile
,
8352 &aOriginMetadata
]() -> Result
<UsageInfo
, nsresult
> {
8354 QM_TRY_RETURN(QM_OR_ELSE_WARN(
8355 // Expression. To simplify control flow, we call LoadUsageFile
8356 // unconditionally here, even though it will necessarily fail if
8357 // usageFileExists is false.
8358 LoadUsageFile(*usageFile
),
8360 ([&file
, &usageFile
, &usageJournalFile
, &aOriginMetadata
](
8361 const nsresult
) -> Result
<UsageInfo
, nsresult
> {
8363 const auto& connection
,
8364 CreateStorageConnectionWithRecovery(
8365 *file
, *usageFile
, aOriginMetadata
.mOrigin
, [] {}));
8367 QM_TRY_INSPECT(const int64_t& usage
,
8368 GetUsage(*connection
,
8369 /* aArchivedOriginScope */ nullptr));
8371 QM_TRY(MOZ_TO_RESULT(
8372 UpdateUsageFile(usageFile
, usageJournalFile
, usage
)));
8374 QM_TRY(MOZ_TO_RESULT(usageJournalFile
->Remove(false)));
8376 MOZ_ASSERT(usage
>= 0);
8377 return UsageInfo
{DatabaseUsageType(Some(uint64_t(usage
)))};
8381 if (usageFileExists
) {
8382 QM_TRY(MOZ_TO_RESULT(usageFile
->Remove(false)));
8388 // Report unknown files in debug builds, but don't fail, just warn (we don't
8389 // report unknown files in release builds because that requires extra
8390 // scanning of the directory which would slow down entire initialization for
8394 QM_TRY(CollectEachFileAtomicCancelable(
8395 *directory
, aCanceled
,
8396 [](const nsCOMPtr
<nsIFile
>& file
) -> Result
<Ok
, nsresult
> {
8397 QM_TRY_INSPECT(const auto& dirEntryKind
, GetDirEntryKind(*file
));
8399 switch (dirEntryKind
) {
8400 case nsIFileKind::ExistsAsDirectory
:
8401 Unused
<< WARN_IF_FILE_IS_UNKNOWN(*file
);
8404 case nsIFileKind::ExistsAsFile
: {
8406 const auto& leafName
,
8407 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString
, file
, GetLeafName
));
8409 if (leafName
.Equals(kDataFileName
) ||
8410 leafName
.Equals(kJournalFileName
) ||
8411 leafName
.Equals(kUsageFileName
) ||
8412 leafName
.Equals(kUsageJournalFileName
)) {
8416 Unused
<< WARN_IF_FILE_IS_UNKNOWN(*file
);
8421 case nsIFileKind::DoesNotExist
:
8422 // Ignore files that got removed externally while iterating.
8432 nsresult
QuotaClient::InitOriginWithoutTracking(
8433 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
8434 const AtomicBool
& aCanceled
) {
8435 AssertIsOnIOThread();
8437 // This is called when a storage/permanent/${origin}/ls directory exists. Even
8438 // though this shouldn't happen with a "good" profile, we shouldn't return an
8439 // error here, since that would cause origin initialization to fail. We just
8440 // warn and otherwise ignore that.
8441 UNKNOWN_FILE_WARNING(NS_LITERAL_STRING_FROM_CSTRING(LS_DIRECTORY_NAME
));
8445 Result
<UsageInfo
, nsresult
> QuotaClient::GetUsageForOrigin(
8446 PersistenceType aPersistenceType
, const OriginMetadata
& aOriginMetadata
,
8447 const AtomicBool
& aCanceled
) {
8448 AssertIsOnIOThread();
8449 MOZ_ASSERT(aPersistenceType
== PERSISTENCE_TYPE_DEFAULT
);
8451 // We can't open the database at this point, since it can be already used
8452 // by the connection thread. Use the cached value instead.
8454 QuotaManager
* quotaManager
= QuotaManager::Get();
8455 MOZ_ASSERT(quotaManager
);
8457 return quotaManager
->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT
,
8458 aOriginMetadata
, Client::LS
);
8461 nsresult
QuotaClient::AboutToClearOrigins(
8462 const Nullable
<PersistenceType
>& aPersistenceType
,
8463 const OriginScope
& aOriginScope
) {
8464 AssertIsOnIOThread();
8466 // This method is not called when the clearing is triggered by the eviction
8467 // process. It's on purpose to avoid a problem with the origin access time
8468 // which can be described as follows:
8469 // When there's a storage pressure condition and quota manager starts
8470 // collecting origins for eviction, there can be an origin that hasn't been
8471 // touched for long time. However, the old implementation of local storage
8472 // could have touched the origin only recently and the new implementation
8473 // hasn't had a chance to create a new per origin database for it yet (the
8474 // data is still in the archive database), so the origin access time hasn't
8475 // been updated either. In the end, the origin would be evicted despite the
8476 // fact that there was recent local storage activity.
8477 // So this method clears the archived data and shadow database entries for
8478 // given origin scope, but only if it's a privacy-related origin clearing.
8480 if (!aPersistenceType
.IsNull() &&
8481 aPersistenceType
.Value() != PERSISTENCE_TYPE_DEFAULT
) {
8485 // There can be no data for the system principal in the archive or the shadow
8486 // database. This early return silences potential warnings caused by failed
8487 // `CreateAerchivedOriginScope` because it calls `GenerateOriginKey2` which
8488 // doesn't support the system principal.
8489 if (aOriginScope
.IsOrigin() &&
8490 aOriginScope
.GetOrigin() == QuotaManager::GetOriginForChrome()) {
8494 const bool shadowWrites
= gShadowWrites
;
8496 QM_TRY_INSPECT(const auto& archivedOriginScope
,
8497 CreateArchivedOriginScope(aOriginScope
));
8499 if (!gArchivedOrigins
) {
8500 QM_TRY(MOZ_TO_RESULT(LoadArchivedOrigins()));
8501 MOZ_ASSERT(gArchivedOrigins
);
8504 const bool hasDataForRemoval
=
8505 archivedOriginScope
->HasMatches(gArchivedOrigins
);
8507 QuotaManager
* quotaManager
= QuotaManager::Get();
8508 MOZ_ASSERT(quotaManager
);
8510 const nsString
& basePath
= quotaManager
->GetBasePath();
8513 MutexAutoLock
shadowDatabaseLock(mShadowDatabaseMutex
);
8516 const auto& connection
,
8517 ([&basePath
]() -> Result
<nsCOMPtr
<mozIStorageConnection
>, nsresult
> {
8518 if (gInitializedShadowStorage
) {
8519 QM_TRY_RETURN(GetShadowStorageConnection(basePath
));
8522 QM_TRY_UNWRAP(auto connection
,
8523 CreateShadowStorageConnection(basePath
));
8525 gInitializedShadowStorage
= true;
8531 Maybe
<AutoDatabaseAttacher
> maybeAutoArchiveDatabaseAttacher
;
8533 if (hasDataForRemoval
) {
8534 QM_TRY_INSPECT(const auto& archiveFile
,
8535 GetArchiveFile(quotaManager
->GetStoragePath()));
8537 maybeAutoArchiveDatabaseAttacher
.emplace(
8538 AutoDatabaseAttacher(connection
, archiveFile
, "archive"_ns
));
8540 QM_TRY(MOZ_TO_RESULT(maybeAutoArchiveDatabaseAttacher
->Attach()));
8543 if (archivedOriginScope
->IsPattern()) {
8544 nsCOMPtr
<mozIStorageFunction
> function(
8545 new MatchFunction(archivedOriginScope
->GetPattern()));
8548 MOZ_TO_RESULT(connection
->CreateFunction("match"_ns
, 2, function
)));
8552 QM_TRY_INSPECT(const auto& stmt
,
8553 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8554 nsCOMPtr
<mozIStorageStatement
>, connection
,
8555 CreateStatement
, "BEGIN IMMEDIATE;"_ns
));
8557 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()));
8561 QM_TRY(MOZ_TO_RESULT(
8562 PerformDelete(connection
, "main"_ns
, archivedOriginScope
.get())));
8565 if (hasDataForRemoval
) {
8566 QM_TRY(MOZ_TO_RESULT(PerformDelete(connection
, "archive"_ns
,
8567 archivedOriginScope
.get())));
8571 QM_TRY_INSPECT(const auto& stmt
,
8572 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8573 nsCOMPtr
<mozIStorageStatement
>, connection
,
8574 CreateStatement
, "COMMIT;"_ns
));
8576 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()));
8579 if (archivedOriginScope
->IsPattern()) {
8580 QM_TRY(MOZ_TO_RESULT(connection
->RemoveFunction("match"_ns
)));
8583 if (hasDataForRemoval
) {
8584 MOZ_ASSERT(maybeAutoArchiveDatabaseAttacher
.isSome());
8585 QM_TRY(MOZ_TO_RESULT(maybeAutoArchiveDatabaseAttacher
->Detach()));
8587 maybeAutoArchiveDatabaseAttacher
.reset();
8589 MOZ_ASSERT(gArchivedOrigins
);
8590 MOZ_ASSERT(archivedOriginScope
->HasMatches(gArchivedOrigins
));
8591 archivedOriginScope
->RemoveMatches(gArchivedOrigins
);
8594 QM_TRY(MOZ_TO_RESULT(connection
->Close()));
8597 if (aOriginScope
.IsNull()) {
8598 QM_TRY_INSPECT(const auto& shadowFile
, GetShadowFile(basePath
));
8600 QM_TRY(MOZ_TO_RESULT(shadowFile
->Remove(false)));
8602 gInitializedShadowStorage
= false;
8608 void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType
,
8609 const nsACString
& aOrigin
) {
8610 AssertIsOnIOThread();
8613 void QuotaClient::OnRepositoryClearCompleted(PersistenceType aPersistenceType
) {
8614 AssertIsOnIOThread();
8617 void QuotaClient::ReleaseIOThreadObjects() {
8618 AssertIsOnIOThread();
8620 gInitializationInfo
= nullptr;
8622 // Delete archived origins hashtable since QuotaManager clears the whole
8623 // storage directory including ls-archive.sqlite.
8625 gArchivedOrigins
= nullptr;
8628 void QuotaClient::AbortOperationsForLocks(
8629 const DirectoryLockIdTable
& aDirectoryLockIds
) {
8630 AssertIsOnBackgroundThread();
8632 // A PrepareDatastoreOp object could already acquire a directory lock for
8633 // the given origin. Its last step is creation of a Datastore object (which
8634 // will take ownership of the directory lock) and a PreparedDatastore object
8635 // which keeps the Datastore alive until a database actor is created.
8636 // We need to invalidate the PreparedDatastore object when it's created,
8637 // otherwise the Datastore object can block the origin clear operation for
8638 // long time. It's not a problem that we don't fail the PrepareDatastoreOp
8639 // immediatelly (avoiding the creation of the Datastore and PreparedDatastore
8640 // object). We will call RequestAllowToClose on the database actor once it's
8641 // created and the child actor will respond by sending AllowToClose which
8642 // will close the Datastore on the parent side (the closing releases the
8645 InvalidatePrepareDatastoreOpsMatching(
8646 [&aDirectoryLockIds
](const auto& prepareDatastoreOp
) {
8647 // Check if the PrepareDatastoreOp holds an acquired DirectoryLock.
8648 // Origin clearing can't be blocked by this PrepareDatastoreOp if there
8649 // is no acquired DirectoryLock. If there is an acquired DirectoryLock,
8650 // check if the table contains the lock for the PrepareDatastoreOp.
8651 return IsLockForObjectAcquiredAndContainedInLockTable(
8652 prepareDatastoreOp
, aDirectoryLockIds
);
8655 if (gPrivateDatastores
) {
8656 gPrivateDatastores
->RemoveIf([&aDirectoryLockIds
](const auto& iter
) {
8657 const auto& privateDatastore
= iter
.Data();
8659 // The PrivateDatastore::mDatastore member is not cleared until the
8660 // PrivateDatastore is destroyed.
8661 const auto& datastore
= privateDatastore
->DatastoreRef();
8663 // If the PrivateDatastore exists then it must be registered in
8664 // Datastore::mHasLivePrivateDatastore as well. The Datastore must have
8665 // a DirectoryLock if there is a registered PrivateDatastore.
8666 return IsLockForObjectContainedInLockTable(datastore
, aDirectoryLockIds
);
8669 if (!gPrivateDatastores
->Count()) {
8670 gPrivateDatastores
= nullptr;
8674 InvalidatePreparedDatastoresMatching([&aDirectoryLockIds
](
8675 const auto& preparedDatastore
) {
8676 // The PreparedDatastore::mDatastore member is not cleared until the
8677 // PreparedDatastore is destroyed.
8678 const auto& datastore
= preparedDatastore
.DatastoreRef();
8680 // If the PreparedDatastore exists then it must be registered in
8681 // Datastore::mPreparedDatastores as well. The Datastore must have a
8682 // DirectoryLock if there are registered PreparedDatastore objects.
8683 return IsLockForObjectContainedInLockTable(datastore
, aDirectoryLockIds
);
8686 RequestAllowToCloseDatabasesMatching(
8687 [&aDirectoryLockIds
](const auto& database
) {
8688 const auto& maybeDatastore
= database
.MaybeDatastoreRef();
8690 // If the Database is registered in gLiveDatabases then it must have a
8692 MOZ_ASSERT(maybeDatastore
.isSome());
8694 // If the Database is registered in gLiveDatabases then it must be
8695 // registered in Datastore::mDatabases as well. The Datastore must have
8696 // a DirectoryLock if there are registered Database objects.
8697 return IsLockForObjectContainedInLockTable(*maybeDatastore
,
8702 void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId
) {
8703 AssertIsOnBackgroundThread();
8705 RequestAllowToCloseDatabasesMatching(
8706 [&aContentParentId
](const auto& database
) {
8707 return database
.IsOwnedByProcess(aContentParentId
);
8711 void QuotaClient::AbortAllOperations() {
8712 AssertIsOnBackgroundThread();
8714 InvalidatePrepareDatastoreOpsMatching([](const auto& prepareDatastoreOp
) {
8715 return prepareDatastoreOp
.MaybeDirectoryLockRef();
8718 if (gPrivateDatastores
) {
8719 gPrivateDatastores
= nullptr;
8722 InvalidatePreparedDatastoresMatching([](const auto&) { return true; });
8724 RequestAllowToCloseDatabasesMatching([](const auto&) { return true; });
8727 void QuotaClient::StartIdleMaintenance() { AssertIsOnBackgroundThread(); }
8729 void QuotaClient::StopIdleMaintenance() { AssertIsOnBackgroundThread(); }
8731 void QuotaClient::InitiateShutdown() {
8732 // gPrepareDatastoreOps are short lived objects running a state machine.
8733 // The shutdown flag is checked between states, so we don't have to notify
8734 // all the objects here.
8735 // Allocation of a new PrepareDatastoreOp object is prevented once the
8736 // shutdown flag is set.
8737 // When the last PrepareDatastoreOp finishes, the gPrepareDatastoreOps array
8740 if (gPreparedDatastores
) {
8741 gPreparedDatastores
= nullptr;
8744 if (gPrivateDatastores
) {
8745 gPrivateDatastores
= nullptr;
8748 RequestAllowToCloseDatabasesMatching([](const auto&) { return true; });
8750 if (gPreparedObsevers
) {
8751 gPreparedObsevers
= nullptr;
8755 bool QuotaClient::IsShutdownCompleted() const {
8756 // Don't have to check gPrivateDatastores and gPreparedDatastores since we
8757 // nulled it out in InitiateShutdown.
8758 return !gPrepareDatastoreOps
&& !gDatastores
&& !gLiveDatabases
;
8761 void QuotaClient::ForceKillActors() { ForceKillAllDatabases(); }
8763 nsCString
QuotaClient::GetShutdownStatus() const {
8764 AssertIsOnBackgroundThread();
8768 if (gPrepareDatastoreOps
) {
8769 data
.Append("PrepareDatastoreOperations: ");
8770 data
.AppendInt(static_cast<uint32_t>(gPrepareDatastoreOps
->Length()));
8773 // XXX What's the purpose of adding these to a hashtable before joining them
8774 // to the string? (Maybe this used to be an ordered container before???)
8775 nsTHashSet
<nsCString
> ids
;
8776 std::transform(gPrepareDatastoreOps
->cbegin(), gPrepareDatastoreOps
->cend(),
8777 MakeInserter(ids
), [](const auto& prepareDatastoreOp
) {
8779 prepareDatastoreOp
->Stringify(id
);
8783 StringJoinAppend(data
, ", "_ns
, ids
);
8789 data
.Append("Datastores: ");
8790 data
.AppendInt(gDatastores
->Count());
8793 // XXX It might be confusing to remove duplicates here, as the actual list
8794 // won't match the count then.
8795 nsTHashSet
<nsCString
> ids
;
8796 std::transform(gDatastores
->Values().cbegin(), gDatastores
->Values().cend(),
8797 MakeInserter(ids
), [](const auto& entry
) {
8799 entry
->Stringify(id
);
8803 StringJoinAppend(data
, ", "_ns
, ids
);
8808 if (gLiveDatabases
) {
8809 data
.Append("LiveDatabases: ");
8810 data
.AppendInt(static_cast<uint32_t>(gLiveDatabases
->Length()));
8813 // XXX It might be confusing to remove duplicates here, as the actual list
8814 // won't match the count then.
8815 nsTHashSet
<nsCString
> ids
;
8816 std::transform(gLiveDatabases
->cbegin(), gLiveDatabases
->cend(),
8817 MakeInserter(ids
), [](const auto& database
) {
8819 database
->Stringify(id
);
8823 StringJoinAppend(data
, ", "_ns
, ids
);
8831 void QuotaClient::FinalizeShutdown() {
8832 // And finally, shutdown the connection thread.
8833 if (gConnectionThread
) {
8834 gConnectionThread
->Shutdown();
8836 gConnectionThread
= nullptr;
8840 Result
<UniquePtr
<ArchivedOriginScope
>, nsresult
>
8841 QuotaClient::CreateArchivedOriginScope(const OriginScope
& aOriginScope
) {
8842 AssertIsOnIOThread();
8844 if (aOriginScope
.IsOrigin()) {
8845 QM_TRY_INSPECT(const auto& principalInfo
,
8846 QuotaManager::ParseOrigin(aOriginScope
.GetOrigin()));
8848 QM_TRY_INSPECT((const auto& [originAttrSuffix
, originKey
]),
8849 GenerateOriginKey2(principalInfo
));
8851 return ArchivedOriginScope::CreateFromOrigin(originAttrSuffix
, originKey
);
8854 if (aOriginScope
.IsPrefix()) {
8855 QM_TRY_INSPECT(const auto& principalInfo
,
8856 QuotaManager::ParseOrigin(aOriginScope
.GetOriginNoSuffix()));
8858 QM_TRY_INSPECT((const auto& [originAttrSuffix
, originKey
]),
8859 GenerateOriginKey2(principalInfo
));
8861 Unused
<< originAttrSuffix
;
8863 return ArchivedOriginScope::CreateFromPrefix(originKey
);
8866 if (aOriginScope
.IsPattern()) {
8867 return ArchivedOriginScope::CreateFromPattern(aOriginScope
.GetPattern());
8870 MOZ_ASSERT(aOriginScope
.IsNull());
8872 return ArchivedOriginScope::CreateFromNull();
8875 nsresult
QuotaClient::PerformDelete(
8876 mozIStorageConnection
* aConnection
, const nsACString
& aSchemaName
,
8877 ArchivedOriginScope
* aArchivedOriginScope
) const {
8878 AssertIsOnIOThread();
8879 MOZ_ASSERT(aConnection
);
8880 MOZ_ASSERT(aArchivedOriginScope
);
8884 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8885 nsCOMPtr
<mozIStorageStatement
>, aConnection
, CreateStatement
,
8886 "DELETE FROM "_ns
+ aSchemaName
+ ".webappsstore2"_ns
+
8887 aArchivedOriginScope
->GetBindingClause() + ";"_ns
));
8889 QM_TRY(MOZ_TO_RESULT(aArchivedOriginScope
->BindToStatement(stmt
)));
8891 QM_TRY(MOZ_TO_RESULT(stmt
->Execute()));
8896 NS_IMPL_ISUPPORTS(QuotaClient::MatchFunction
, mozIStorageFunction
)
8899 QuotaClient::MatchFunction::OnFunctionCall(
8900 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
) {
8901 AssertIsOnIOThread();
8902 MOZ_ASSERT(aFunctionArguments
);
8903 MOZ_ASSERT(aResult
);
8905 QM_TRY_INSPECT(const auto& suffix
,
8906 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8907 nsAutoCString
, aFunctionArguments
, GetUTF8String
, 1));
8909 OriginAttributes oa
;
8910 QM_TRY(OkIf(oa
.PopulateFromSuffix(suffix
)), NS_ERROR_FAILURE
);
8912 const bool result
= mPattern
.Matches(oa
);
8914 RefPtr
<nsVariant
> outVar(new nsVariant());
8915 QM_TRY(MOZ_TO_RESULT(outVar
->SetAsBool(result
)));
8917 outVar
.forget(aResult
);
8921 /*******************************************************************************
8922 * AutoWriteTransaction
8923 ******************************************************************************/
8925 AutoWriteTransaction::AutoWriteTransaction(bool aShadowWrites
)
8926 : mConnection(nullptr), mShadowWrites(aShadowWrites
) {
8927 AssertIsOnGlobalConnectionThread();
8929 MOZ_COUNT_CTOR(mozilla::dom::AutoWriteTransaction
);
8932 AutoWriteTransaction::~AutoWriteTransaction() {
8933 AssertIsOnGlobalConnectionThread();
8935 MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction
);
8938 QM_WARNONLY_TRY(QM_TO_RESULT(mConnection
->RollbackWriteTransaction()));
8940 if (mShadowWrites
) {
8941 QM_WARNONLY_TRY(QM_TO_RESULT(DetachShadowDatabaseAndUnlock()));
8946 nsresult
AutoWriteTransaction::Start(Connection
* aConnection
) {
8947 AssertIsOnGlobalConnectionThread();
8948 MOZ_ASSERT(aConnection
);
8949 MOZ_ASSERT(!mConnection
);
8951 if (mShadowWrites
) {
8952 QM_TRY(MOZ_TO_RESULT(LockAndAttachShadowDatabase(aConnection
)));
8955 QM_TRY(MOZ_TO_RESULT(aConnection
->BeginWriteTransaction()));
8957 mConnection
= aConnection
;
8962 nsresult
AutoWriteTransaction::Commit() {
8963 AssertIsOnGlobalConnectionThread();
8964 MOZ_ASSERT(mConnection
);
8966 QM_TRY(MOZ_TO_RESULT(mConnection
->CommitWriteTransaction()));
8968 if (mShadowWrites
) {
8969 QM_TRY(MOZ_TO_RESULT(DetachShadowDatabaseAndUnlock()));
8972 mConnection
= nullptr;
8977 nsresult
AutoWriteTransaction::LockAndAttachShadowDatabase(
8978 Connection
* aConnection
) {
8979 AssertIsOnGlobalConnectionThread();
8980 MOZ_ASSERT(aConnection
);
8981 MOZ_ASSERT(!mConnection
);
8982 MOZ_ASSERT(mShadowDatabaseLock
.isNothing());
8983 MOZ_ASSERT(mShadowWrites
);
8985 QuotaManager
* quotaManager
= QuotaManager::Get();
8986 MOZ_ASSERT(quotaManager
);
8988 mShadowDatabaseLock
.emplace(
8989 aConnection
->GetQuotaClient()->ShadowDatabaseMutex());
8991 QM_TRY(MOZ_TO_RESULT(AttachShadowDatabase(
8992 quotaManager
->GetBasePath(), &aConnection
->MutableStorageConnection())));
8997 nsresult
AutoWriteTransaction::DetachShadowDatabaseAndUnlock() {
8998 AssertIsOnGlobalConnectionThread();
8999 MOZ_ASSERT(mConnection
);
9000 MOZ_ASSERT(mShadowDatabaseLock
.isSome());
9001 MOZ_ASSERT(mShadowWrites
);
9003 nsCOMPtr
<mozIStorageConnection
> storageConnection
=
9004 mConnection
->StorageConnection();
9005 MOZ_ASSERT(storageConnection
);
9007 QM_TRY(MOZ_TO_RESULT(DetachShadowDatabase(storageConnection
)));
9009 mShadowDatabaseLock
.reset();
9014 } // namespace mozilla::dom