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