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