Bug 1941046 - Part 4: Send a callback request for impression and clicks of MARS Top...
[gecko.git] / dom / quota / ActorsParent.cpp
blob4bc3c5781504f897c1a9d134c7517e18d0ec2182
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ActorsParent.h"
9 // Local includes
10 #include "CanonicalQuotaObject.h"
11 #include "ClientUsageArray.h"
12 #include "Flatten.h"
13 #include "FirstInitializationAttemptsImpl.h"
14 #include "InitializationUtils.h"
15 #include "GroupInfo.h"
16 #include "GroupInfoPair.h"
17 #include "NormalOriginOperationBase.h"
18 #include "OriginOperationBase.h"
19 #include "OriginOperations.h"
20 #include "OriginParser.h"
21 #include "OriginScope.h"
22 #include "OriginInfo.h"
23 #include "QuotaCommon.h"
24 #include "QuotaManager.h"
25 #include "QuotaPrefs.h"
26 #include "ResolvableNormalOriginOp.h"
27 #include "SanitizationUtils.h"
28 #include "ScopedLogExtraInfo.h"
29 #include "UsageInfo.h"
31 // Global includes
32 #include <cinttypes>
33 #include <cstdlib>
34 #include <cstring>
35 #include <algorithm>
36 #include <cstdint>
37 #include <functional>
38 #include <new>
39 #include <numeric>
40 #include <tuple>
41 #include <type_traits>
42 #include <utility>
43 #include "DirectoryLockImpl.h"
44 #include "ErrorList.h"
45 #include "MainThreadUtils.h"
46 #include "mozIStorageAsyncConnection.h"
47 #include "mozIStorageConnection.h"
48 #include "mozIStorageService.h"
49 #include "mozIStorageStatement.h"
50 #include "mozStorageCID.h"
51 #include "mozStorageHelper.h"
52 #include "mozilla/AlreadyAddRefed.h"
53 #include "mozilla/AppShutdown.h"
54 #include "mozilla/Assertions.h"
55 #include "mozilla/Atomics.h"
56 #include "mozilla/Attributes.h"
57 #include "mozilla/AutoRestore.h"
58 #include "mozilla/BasePrincipal.h"
59 #include "mozilla/CheckedInt.h"
60 #include "mozilla/CondVar.h"
61 #include "mozilla/InitializedOnce.h"
62 #include "mozilla/Logging.h"
63 #include "mozilla/MacroForEach.h"
64 #include "mozilla/Maybe.h"
65 #include "mozilla/Mutex.h"
66 #include "mozilla/NotNull.h"
67 #include "mozilla/OriginAttributes.h"
68 #include "mozilla/Preferences.h"
69 #include "mozilla/RefPtr.h"
70 #include "mozilla/Result.h"
71 #include "mozilla/ResultExtensions.h"
72 #include "mozilla/ScopeExit.h"
73 #include "mozilla/SpinEventLoopUntil.h"
74 #include "mozilla/StaticPrefs_dom.h"
75 #include "mozilla/StaticPtr.h"
76 #include "mozilla/SystemPrincipal.h"
77 #include "mozilla/Telemetry.h"
78 #include "mozilla/TelemetryHistogramEnums.h"
79 #include "mozilla/TextUtils.h"
80 #include "mozilla/TimeStamp.h"
81 #include "mozilla/UniquePtr.h"
82 #include "mozilla/Unused.h"
83 #include "mozilla/Variant.h"
84 #include "mozilla/dom/FileSystemQuotaClientFactory.h"
85 #include "mozilla/dom/FlippedOnce.h"
86 #include "mozilla/dom/IndexedDatabaseManager.h"
87 #include "mozilla/dom/LocalStorageCommon.h"
88 #include "mozilla/dom/StorageDBUpdater.h"
89 #include "mozilla/dom/cache/QuotaClient.h"
90 #include "mozilla/dom/indexedDB/ActorsParent.h"
91 #include "mozilla/dom/ipc/IdType.h"
92 #include "mozilla/dom/localstorage/ActorsParent.h"
93 #include "mozilla/dom/quota/ArtificialFailure.h"
94 #include "mozilla/dom/quota/AssertionsImpl.h"
95 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
96 #include "mozilla/dom/quota/Client.h"
97 #include "mozilla/dom/quota/ClientDirectoryLock.h"
98 #include "mozilla/dom/quota/Config.h"
99 #include "mozilla/dom/quota/Constants.h"
100 #include "mozilla/dom/quota/DirectoryLockInlines.h"
101 #include "mozilla/dom/quota/FileUtils.h"
102 #include "mozilla/dom/quota/MozPromiseUtils.h"
103 #include "mozilla/dom/quota/OriginDirectoryLock.h"
104 #include "mozilla/dom/quota/PersistenceType.h"
105 #include "mozilla/dom/quota/PrincipalUtils.h"
106 #include "mozilla/dom/quota/QuotaManagerImpl.h"
107 #include "mozilla/dom/quota/QuotaManagerService.h"
108 #include "mozilla/dom/quota/ResultExtensions.h"
109 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
110 #include "mozilla/dom/quota/StreamUtils.h"
111 #include "mozilla/dom/quota/UniversalDirectoryLock.h"
112 #include "mozilla/dom/quota/ThreadUtils.h"
113 #include "mozilla/dom/simpledb/ActorsParent.h"
114 #include "mozilla/fallible.h"
115 #include "mozilla/glean/DomQuotaMetrics.h"
116 #include "mozilla/ipc/BackgroundChild.h"
117 #include "mozilla/ipc/BackgroundParent.h"
118 #include "mozilla/ipc/PBackgroundChild.h"
119 #include "mozilla/ipc/ProtocolUtils.h"
120 #include "mozilla/net/ExtensionProtocolHandler.h"
121 #include "mozilla/StorageOriginAttributes.h"
122 #include "nsAppDirectoryServiceDefs.h"
123 #include "nsBaseHashtable.h"
124 #include "nsCOMPtr.h"
125 #include "nsCRTGlue.h"
126 #include "nsClassHashtable.h"
127 #include "nsComponentManagerUtils.h"
128 #include "nsContentUtils.h"
129 #include "nsDebug.h"
130 #include "nsDirectoryServiceUtils.h"
131 #include "nsError.h"
132 #include "nsIBinaryInputStream.h"
133 #include "nsIBinaryOutputStream.h"
134 #include "nsIConsoleService.h"
135 #include "nsIDirectoryEnumerator.h"
136 #include "nsIDUtils.h"
137 #include "nsIEventTarget.h"
138 #include "nsIFile.h"
139 #include "nsIFileStreams.h"
140 #include "nsIInputStream.h"
141 #include "nsIObjectInputStream.h"
142 #include "nsIObjectOutputStream.h"
143 #include "nsIObserver.h"
144 #include "nsIObserverService.h"
145 #include "nsIOutputStream.h"
146 #include "nsIQuotaManagerServiceInternal.h"
147 #include "nsIQuotaRequests.h"
148 #include "nsIQuotaUtilsService.h"
149 #include "nsIPlatformInfo.h"
150 #include "nsIPrincipal.h"
151 #include "nsIRunnable.h"
152 #include "nsISupports.h"
153 #include "nsIThread.h"
154 #include "nsITimer.h"
155 #include "nsIURI.h"
156 #include "nsIWidget.h"
157 #include "nsLiteralString.h"
158 #include "nsNetUtil.h"
159 #include "nsPrintfCString.h"
160 #include "nsStandardURL.h"
161 #include "nsServiceManagerUtils.h"
162 #include "nsString.h"
163 #include "nsStringFlags.h"
164 #include "nsStringFwd.h"
165 #include "nsTArray.h"
166 #include "nsTHashtable.h"
167 #include "nsTLiteralString.h"
168 #include "nsTPromiseFlatString.h"
169 #include "nsTStringRepr.h"
170 #include "nsThreadUtils.h"
171 #include "nsURLHelper.h"
172 #include "nsXPCOM.h"
173 #include "nsXPCOMCID.h"
174 #include "nsXULAppAPI.h"
175 #include "prinrval.h"
176 #include "prio.h"
177 #include "prtime.h"
179 // The amount of time, in milliseconds, that our IO thread will stay alive
180 // after the last event it processes.
181 #define DEFAULT_THREAD_TIMEOUT_MS 30000
184 * If shutdown takes this long, kill actors of a quota client, to avoid reaching
185 * the crash timeout.
187 #define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000
190 * Automatically crash the browser if shutdown of a quota client takes this
191 * long. We've chosen a value that is long enough that it is unlikely for the
192 * problem to be falsely triggered by slow system I/O. We've also chosen a
193 * value long enough so that automated tests should time out and fail if
194 * shutdown of a quota client takes too long. Also, this value is long enough
195 * so that testers can notice the timeout; we want to know about the timeouts,
196 * not hide them. On the other hand this value is less than 60 seconds which is
197 * used by nsTerminator to crash a hung main process.
199 #define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000
201 static_assert(
202 SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
203 "The kill actors timeout must be shorter than the crash browser one.");
205 // profile-before-change, when we need to shut down quota manager
206 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
208 #define KB *1024ULL
209 #define MB *1024ULL KB
210 #define GB *1024ULL MB
212 namespace mozilla::dom::quota {
214 using namespace mozilla::ipc;
216 // We want profiles to be platform-independent so we always need to replace
217 // the same characters on every platform. Windows has the most extensive set
218 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
219 // FILE_PATH_SEPARATOR.
220 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
221 const char16_t QuotaManager::kReplaceChars16[] =
222 u"" CONTROL_CHARACTERS "/:*?\"<>|\\";
224 namespace {
226 /*******************************************************************************
227 * Constants
228 ******************************************************************************/
230 const uint32_t kSQLitePageSizeOverride = 512;
232 // Important version history:
233 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
234 // which caused Firefox 57 release concerns because the major schema upgrade
235 // means anyone downgrading to Firefox 56 will experience a non-operational
236 // QuotaManager and all of its clients.
237 // - Bug 1404344 got very concerned about that and so we decided to effectively
238 // rename 3.0 to 2.1, effective in Firefox 57. This works because post
239 // storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
240 // increases. It also works because all the upgrade did was give the DOM
241 // Cache API QuotaClient an opportunity to create its newly added .padding
242 // files during initialization/upgrade, which isn't functionally necessary as
243 // that can be done on demand.
245 // Major storage version. Bump for backwards-incompatible changes.
246 // (The next major version should be 4 to distinguish from the Bug 1290481
247 // downgrade snafu.)
248 const uint32_t kMajorStorageVersion = 2;
250 // Minor storage version. Bump for backwards-compatible changes.
251 const uint32_t kMinorStorageVersion = 3;
253 // The storage version we store in the SQLite database is a (signed) 32-bit
254 // integer. The major version is left-shifted 16 bits so the max value is
255 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
256 static_assert(kMajorStorageVersion <= 0xFFFF,
257 "Major version needs to fit in 16 bits.");
258 static_assert(kMinorStorageVersion <= 0xFFFF,
259 "Minor version needs to fit in 16 bits.");
261 const int32_t kStorageVersion =
262 int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
264 // See comments above about why these are a thing.
265 const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
266 const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
268 const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
269 const char kIndexedDBOriginPrefix[] = "indexeddb://";
270 const char kResourceOriginPrefix[] = "resource://";
272 constexpr auto kStorageName = u"storage"_ns;
274 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
275 #define ARCHIVES_DIRECTORY_NAME u"archives"
276 #define PERSISTENT_DIRECTORY_NAME u"persistent"
277 #define PERMANENT_DIRECTORY_NAME u"permanent"
278 #define TEMPORARY_DIRECTORY_NAME u"temporary"
279 #define DEFAULT_DIRECTORY_NAME u"default"
280 #define PRIVATE_DIRECTORY_NAME u"private"
281 #define TOBEREMOVED_DIRECTORY_NAME u"to-be-removed"
283 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
284 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
285 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
287 const int32_t kLocalStorageArchiveVersion = 4;
289 const char kProfileDoChangeTopic[] = "profile-do-change";
290 const char kContextualIdentityServiceLoadFinishedTopic[] =
291 "contextual-identity-service-load-finished";
292 const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";
294 const int32_t kCacheVersion = 2;
296 /******************************************************************************
297 * SQLite functions
298 ******************************************************************************/
300 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
301 uint32_t aMinorStorageVersion) {
302 return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
305 uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
306 return uint32_t(aStorageVersion >> 16);
309 nsresult CreateTables(mozIStorageConnection* aConnection) {
310 AssertIsOnIOThread();
311 MOZ_ASSERT(aConnection);
313 // Table `database`
314 QM_TRY(MOZ_TO_RESULT(
315 aConnection->ExecuteSimpleSQL("CREATE TABLE database"
316 "( cache_version INTEGER NOT NULL DEFAULT 0"
317 ");"_ns)));
319 #ifdef DEBUG
321 QM_TRY_INSPECT(const int32_t& storageVersion,
322 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
324 MOZ_ASSERT(storageVersion == 0);
326 #endif
328 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kStorageVersion)));
330 return NS_OK;
333 Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) {
334 AssertIsOnIOThread();
336 QM_TRY_INSPECT(const auto& stmt,
337 CreateAndExecuteSingleStepStatement<
338 SingleStepResult::ReturnNullIfNoResult>(
339 aConnection, "SELECT cache_version FROM database"_ns));
341 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
343 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
346 nsresult SaveCacheVersion(mozIStorageConnection& aConnection,
347 int32_t aVersion) {
348 AssertIsOnIOThread();
350 QM_TRY_INSPECT(
351 const auto& stmt,
352 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
353 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
354 "UPDATE database SET cache_version = :version;"_ns));
356 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, aVersion)));
358 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
360 return NS_OK;
363 nsresult CreateCacheTables(mozIStorageConnection& aConnection) {
364 AssertIsOnIOThread();
366 // Table `cache`
367 QM_TRY(MOZ_TO_RESULT(
368 aConnection.ExecuteSimpleSQL("CREATE TABLE cache"
369 "( valid INTEGER NOT NULL DEFAULT 0"
370 ", build_id TEXT NOT NULL DEFAULT ''"
371 ");"_ns)));
373 // Table `repository`
374 QM_TRY(
375 MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("CREATE TABLE repository"
376 "( id INTEGER PRIMARY KEY"
377 ", name TEXT NOT NULL"
378 ");"_ns)));
380 // Table `origin`
381 QM_TRY(MOZ_TO_RESULT(
382 aConnection.ExecuteSimpleSQL("CREATE TABLE origin"
383 "( repository_id INTEGER NOT NULL"
384 ", suffix TEXT"
385 ", group_ TEXT NOT NULL"
386 ", origin TEXT NOT NULL"
387 ", client_usages TEXT NOT NULL"
388 ", usage INTEGER NOT NULL"
389 ", last_access_time INTEGER NOT NULL"
390 ", accessed INTEGER NOT NULL"
391 ", persisted INTEGER NOT NULL"
392 ", PRIMARY KEY (repository_id, origin)"
393 ", FOREIGN KEY (repository_id) "
394 "REFERENCES repository(id) "
395 ");"_ns)));
397 #ifdef DEBUG
399 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
400 MOZ_ASSERT(cacheVersion == 0);
402 #endif
404 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, kCacheVersion)));
406 return NS_OK;
409 OkOrErr InvalidateCache(mozIStorageConnection& aConnection) {
410 AssertIsOnIOThread();
412 static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns;
413 static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns;
415 QM_TRY(QM_OR_ELSE_WARN(
416 // Expression.
417 ([&]() -> OkOrErr {
418 mozStorageTransaction transaction(&aConnection,
419 /*aCommitOnComplete */ false);
421 QM_TRY(QM_TO_RESULT(transaction.Start()));
422 QM_TRY(QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery)));
423 QM_TRY(
424 QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
425 QM_TRY(QM_TO_RESULT(transaction.Commit()));
427 return Ok{};
428 }()),
429 // Fallback.
430 ([&](const QMResult& rv) -> OkOrErr {
431 QM_TRY(
432 QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
434 return Ok{};
435 })));
437 return Ok{};
440 nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) {
441 AssertIsOnIOThread();
443 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
444 "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns)));
446 QM_TRY(InvalidateCache(aConnection));
448 #ifdef DEBUG
450 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
452 MOZ_ASSERT(cacheVersion == 1);
454 #endif
456 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 2)));
458 return NS_OK;
461 Result<bool, nsresult> MaybeCreateOrUpgradeCache(
462 mozIStorageConnection& aConnection) {
463 bool cacheUsable = true;
465 QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection));
467 if (cacheVersion > kCacheVersion) {
468 cacheUsable = false;
469 } else if (cacheVersion != kCacheVersion) {
470 const bool newCache = !cacheVersion;
472 mozStorageTransaction transaction(
473 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
475 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
477 if (newCache) {
478 QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection)));
480 #ifdef DEBUG
482 QM_TRY_INSPECT(const int32_t& cacheVersion,
483 LoadCacheVersion(aConnection));
484 MOZ_ASSERT(cacheVersion == kCacheVersion);
486 #endif
488 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
489 nsLiteralCString("INSERT INTO cache (valid, build_id) "
490 "VALUES (0, '')"))));
492 nsCOMPtr<mozIStorageStatement> insertStmt;
494 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
495 if (insertStmt) {
496 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
497 } else {
498 QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
499 nsCOMPtr<mozIStorageStatement>,
500 aConnection, CreateStatement,
501 "INSERT INTO repository (id, name) "
502 "VALUES (:id, :name)"_ns));
505 QM_TRY(MOZ_TO_RESULT(
506 insertStmt->BindInt32ByName("id"_ns, persistenceType)));
508 QM_TRY(MOZ_TO_RESULT(insertStmt->BindUTF8StringByName(
509 "name"_ns, PersistenceTypeToString(persistenceType))));
511 QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()));
513 } else {
514 // This logic needs to change next time we change the cache!
515 static_assert(kCacheVersion == 2,
516 "Upgrade function needed due to cache version increase.");
518 while (cacheVersion != kCacheVersion) {
519 if (cacheVersion == 1) {
520 QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection)));
521 } else {
522 QM_FAIL(Err(NS_ERROR_FAILURE), []() {
523 QM_WARNING(
524 "Unable to initialize cache, no upgrade path is "
525 "available!");
529 QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection));
532 MOZ_ASSERT(cacheVersion == kCacheVersion);
535 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
538 return cacheUsable;
541 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection(
542 nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) {
543 AssertIsOnIOThread();
545 // Check if the old database exists at all.
546 QM_TRY_INSPECT(const bool& exists,
547 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, Exists));
549 if (!exists) {
550 // webappsstore.sqlite doesn't exist, return a null connection.
551 return nsCOMPtr<mozIStorageConnection>{};
554 QM_TRY_INSPECT(const bool& isDirectory,
555 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, IsDirectory));
557 if (isDirectory) {
558 QM_WARNING("webappsstore.sqlite is not a file!");
559 return nsCOMPtr<mozIStorageConnection>{};
562 QM_TRY_INSPECT(const auto& connection,
563 QM_OR_ELSE_WARN_IF(
564 // Expression.
565 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
566 nsCOMPtr<mozIStorageConnection>, aStorageService,
567 OpenUnsharedDatabase, &aWebAppsStoreFile,
568 mozIStorageService::CONNECTION_DEFAULT),
569 // Predicate.
570 IsDatabaseCorruptionError,
571 // Fallback. Don't throw an error, leave a corrupted
572 // webappsstore database as it is.
573 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
575 if (connection) {
576 // Don't propagate an error, leave a non-updateable webappsstore database as
577 // it is.
578 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
579 nsCOMPtr<mozIStorageConnection>{});
582 return connection;
585 Result<nsCOMPtr<nsIFile>, QMResult> GetLocalStorageArchiveFile(
586 const nsAString& aDirectoryPath) {
587 AssertIsOnIOThread();
588 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
590 QM_TRY_UNWRAP(auto lsArchiveFile,
591 QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aDirectoryPath)));
593 QM_TRY(QM_TO_RESULT(
594 lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));
596 return lsArchiveFile;
599 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveTmpFile(
600 const nsAString& aDirectoryPath) {
601 AssertIsOnIOThread();
602 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
604 QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath));
606 QM_TRY(MOZ_TO_RESULT(
607 lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
609 return lsArchiveTmpFile;
612 Result<bool, nsresult> IsLocalStorageArchiveInitialized(
613 mozIStorageConnection& aConnection) {
614 AssertIsOnIOThread();
616 QM_TRY_RETURN(
617 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, TableExists, "database"_ns));
620 nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection) {
621 AssertIsOnIOThread();
622 MOZ_ASSERT(aConnection);
624 #ifdef DEBUG
626 QM_TRY_INSPECT(const auto& initialized,
627 IsLocalStorageArchiveInitialized(*aConnection));
628 MOZ_ASSERT(!initialized);
630 #endif
632 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
633 "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns)));
635 QM_TRY_INSPECT(
636 const auto& stmt,
637 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
638 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
639 "INSERT INTO database (version) VALUES (:version)"_ns));
641 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, 0)));
642 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
644 return NS_OK;
647 Result<int32_t, nsresult> LoadLocalStorageArchiveVersion(
648 mozIStorageConnection& aConnection) {
649 AssertIsOnIOThread();
651 QM_TRY_INSPECT(const auto& stmt,
652 CreateAndExecuteSingleStepStatement<
653 SingleStepResult::ReturnNullIfNoResult>(
654 aConnection, "SELECT version FROM database"_ns));
656 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
658 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
661 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
662 int32_t aVersion) {
663 AssertIsOnIOThread();
664 MOZ_ASSERT(aConnection);
666 nsCOMPtr<mozIStorageStatement> stmt;
667 nsresult rv = aConnection->CreateStatement(
668 "UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt));
669 if (NS_WARN_IF(NS_FAILED(rv))) {
670 return rv;
673 rv = stmt->BindInt32ByName("version"_ns, aVersion);
674 if (NS_WARN_IF(NS_FAILED(rv))) {
675 return rv;
678 rv = stmt->Execute();
679 if (NS_WARN_IF(NS_FAILED(rv))) {
680 return rv;
683 return NS_OK;
686 template <typename FileFunc, typename DirectoryFunc>
687 Result<mozilla::Ok, nsresult> CollectEachFileEntry(
688 nsIFile& aDirectory, const FileFunc& aFileFunc,
689 const DirectoryFunc& aDirectoryFunc) {
690 AssertIsOnIOThread();
692 return CollectEachFile(
693 aDirectory,
694 [&aFileFunc, &aDirectoryFunc](
695 const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> {
696 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
698 switch (dirEntryKind) {
699 case nsIFileKind::ExistsAsDirectory:
700 return aDirectoryFunc(file);
702 case nsIFileKind::ExistsAsFile:
703 return aFileFunc(file);
705 case nsIFileKind::DoesNotExist:
706 // Ignore files that got removed externally while iterating.
707 break;
710 return Ok{};
714 /******************************************************************************
715 * Quota manager class declarations
716 ******************************************************************************/
718 } // namespace
720 class QuotaManager::Observer final : public nsIObserver {
721 static Observer* sInstance;
723 bool mPendingProfileChange;
724 bool mShutdownComplete;
726 public:
727 static nsresult Initialize();
729 static nsIObserver* GetInstance();
731 static void ShutdownCompleted();
733 private:
734 Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
735 MOZ_ASSERT(NS_IsMainThread());
738 ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
740 nsresult Init();
742 nsresult Shutdown();
744 NS_DECL_ISUPPORTS
745 NS_DECL_NSIOBSERVER
748 namespace {
750 /*******************************************************************************
751 * Local class declarations
752 ******************************************************************************/
754 } // namespace
756 namespace {
758 class CollectOriginsHelper final : public Runnable {
759 uint64_t mMinSizeToBeFreed;
761 Mutex& mMutex;
762 CondVar mCondVar;
764 // The members below are protected by mMutex.
765 nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
766 uint64_t mSizeToBeFreed;
767 bool mWaiting;
769 public:
770 CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
772 // Blocks the current thread until origins are collected on the main thread.
773 // The returned value contains an aggregate size of those origins.
774 int64_t BlockAndReturnOriginsForEviction(
775 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);
777 private:
778 ~CollectOriginsHelper() = default;
780 NS_IMETHOD
781 Run() override;
784 /*******************************************************************************
785 * Other class declarations
786 ******************************************************************************/
788 class RecordTimeDeltaHelper final : public Runnable {
789 const Telemetry::HistogramID mHistogram;
791 // TimeStamps that are set on the IO thread.
792 LazyInitializedOnceNotNull<const TimeStamp> mStartTime;
793 LazyInitializedOnceNotNull<const TimeStamp> mEndTime;
795 // A TimeStamp that is set on the main thread.
796 LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime;
798 public:
799 explicit RecordTimeDeltaHelper(const Telemetry::HistogramID aHistogram)
800 : Runnable("dom::quota::RecordTimeDeltaHelper"), mHistogram(aHistogram) {}
802 TimeStamp Start();
804 TimeStamp End();
806 private:
807 ~RecordTimeDeltaHelper() = default;
809 NS_DECL_NSIRUNNABLE
812 /*******************************************************************************
813 * Helper classes
814 ******************************************************************************/
816 /*******************************************************************************
817 * Helper Functions
818 ******************************************************************************/
820 // Return whether the group was actually updated.
821 Result<bool, nsresult> MaybeUpdateGroupForOrigin(
822 OriginMetadata& aOriginMetadata) {
823 MOZ_ASSERT(!NS_IsMainThread());
825 bool updated = false;
827 if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
828 if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) {
829 aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin);
830 updated = true;
832 } else {
833 nsCOMPtr<nsIPrincipal> principal =
834 BasePrincipal::CreateContentPrincipal(aOriginMetadata.mOrigin);
835 QM_TRY(MOZ_TO_RESULT(principal));
837 QM_TRY_INSPECT(const auto& baseDomain,
838 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
839 GetBaseDomain));
841 const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;
843 if (aOriginMetadata.mGroup != upToDateGroup) {
844 aOriginMetadata.mGroup = upToDateGroup;
845 updated = true;
849 return updated;
852 Result<bool, nsresult> MaybeUpdateLastAccessTimeForOrigin(
853 FullOriginMetadata& aFullOriginMetadata) {
854 MOZ_ASSERT(!NS_IsMainThread());
856 if (aFullOriginMetadata.mLastAccessTime == INT64_MIN) {
857 QuotaManager* quotaManager = QuotaManager::Get();
858 MOZ_ASSERT(quotaManager);
860 QM_TRY_INSPECT(const auto& metadataFile,
861 quotaManager->GetOriginDirectory(aFullOriginMetadata));
863 QM_TRY(MOZ_TO_RESULT(
864 metadataFile->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
866 QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
867 metadataFile, GetLastModifiedTime));
869 // Need to convert from milliseconds to microseconds.
870 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
871 timestamp *= int64_t(PR_USEC_PER_MSEC);
873 aFullOriginMetadata.mLastAccessTime = timestamp;
875 return true;
878 return false;
881 } // namespace
883 void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
884 // Get leaf of file path
885 for (const char* p = aFile; *p; ++p) {
886 if (*p == '/' && *(p + 1)) {
887 aFile = p + 1;
891 nsContentUtils::LogSimpleConsoleError(
892 NS_ConvertUTF8toUTF16(
893 nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
894 "quota"_ns,
895 false /* Quota Manager is not active in private browsing mode */,
896 true /* Quota Manager runs always in a chrome context */);
899 namespace {
901 bool gInvalidateQuotaCache = false;
902 StaticAutoPtr<nsString> gBasePath;
903 StaticAutoPtr<nsString> gStorageName;
904 StaticAutoPtr<nsCString> gBuildId;
906 #ifdef DEBUG
907 bool gQuotaManagerInitialized = false;
908 #endif
910 StaticRefPtr<QuotaManager> gInstance;
911 mozilla::Atomic<bool> gShutdown(false);
913 // A time stamp that can only be accessed on the main thread.
914 TimeStamp gLastOSWake;
916 // XXX Move to QuotaManager once NormalOriginOperationBase is declared in a
917 // separate and includable file.
918 using NormalOriginOpArray =
919 nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>;
920 StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;
922 class StorageOperationBase {
923 protected:
924 struct OriginProps {
925 enum Type { eChrome, eContent, eObsolete, eInvalid };
927 NotNull<nsCOMPtr<nsIFile>> mDirectory;
928 nsString mLeafName;
929 nsCString mSpec;
930 OriginAttributes mAttrs;
931 int64_t mTimestamp;
932 OriginMetadata mOriginMetadata;
933 nsCString mOriginalSuffix;
935 LazyInitializedOnceEarlyDestructible<const PersistenceType>
936 mPersistenceType;
937 Type mType;
938 bool mNeedsRestore;
939 bool mNeedsRestore2;
940 bool mIgnore;
942 public:
943 explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory)
944 : mDirectory(std::move(aDirectory)),
945 mTimestamp(0),
946 mType(eContent),
947 mNeedsRestore(false),
948 mNeedsRestore2(false),
949 mIgnore(false) {}
951 template <typename PersistenceTypeFunc>
952 nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc);
955 nsTArray<OriginProps> mOriginProps;
957 nsCOMPtr<nsIFile> mDirectory;
959 public:
960 explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) {
961 AssertIsOnIOThread();
964 NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)
966 protected:
967 virtual ~StorageOperationBase() = default;
969 nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
970 nsACString& aGroup, nsACString& aOrigin,
971 Nullable<bool>& aIsApp);
973 // Upgrade helper to load the contents of ".metadata-v2" files from previous
974 // schema versions. Although QuotaManager has a similar GetDirectoryMetadata2
975 // method, it is only intended to read current version ".metadata-v2" files.
976 // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
977 // because our "storage.sqlite" lets us track the overall version of the
978 // storage directory.
979 nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
980 nsACString& aSuffix, nsACString& aGroup,
981 nsACString& aOrigin, bool& aIsApp);
983 int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps);
985 nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
988 * Rename the origin if the origin string generation from nsIPrincipal
989 * changed. This consists of renaming the origin in the metadata files and
990 * renaming the origin directory itself. For simplicity, the origin in
991 * metadata files is not actually updated, but the metadata files are
992 * recreated instead.
994 * @param aOriginProps the properties of the origin to check.
996 * @return whether origin was renamed.
998 Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps);
1000 nsresult ProcessOriginDirectories();
1002 virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
1005 class RepositoryOperationBase : public StorageOperationBase {
1006 public:
1007 explicit RepositoryOperationBase(nsIFile* aDirectory)
1008 : StorageOperationBase(aDirectory) {}
1010 nsresult ProcessRepository();
1012 protected:
1013 virtual ~RepositoryOperationBase() = default;
1015 template <typename UpgradeMethod>
1016 nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
1017 UpgradeMethod aMethod);
1019 private:
1020 virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0;
1022 virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1023 bool* aRemoved) = 0;
1025 virtual nsresult PrepareClientDirectory(nsIFile* aFile,
1026 const nsAString& aLeafName,
1027 bool& aRemoved);
1030 class CreateOrUpgradeDirectoryMetadataHelper final
1031 : public RepositoryOperationBase {
1032 nsCOMPtr<nsIFile> mPermanentStorageDir;
1034 // The legacy PersistenceType, before the default repository introduction.
1035 enum class LegacyPersistenceType {
1036 Persistent = 0,
1037 Temporary
1038 // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
1039 // it here.
1042 LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType;
1044 public:
1045 explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory)
1046 : RepositoryOperationBase(aDirectory) {}
1048 nsresult Init();
1050 private:
1051 Maybe<LegacyPersistenceType> LegacyPersistenceTypeFromFile(nsIFile& aFile,
1052 const fallible_t&);
1054 PersistenceType PersistenceTypeFromLegacyPersistentSpec(
1055 const nsCString& aSpec);
1057 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
1059 nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory);
1061 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1062 bool* aRemoved) override;
1064 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1067 class UpgradeStorageHelperBase : public RepositoryOperationBase {
1068 LazyInitializedOnce<const PersistenceType> mPersistenceType;
1070 public:
1071 explicit UpgradeStorageHelperBase(nsIFile* aDirectory)
1072 : RepositoryOperationBase(aDirectory) {}
1074 nsresult Init();
1076 private:
1077 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
1080 class UpgradeStorageFrom0_0To1_0Helper final : public UpgradeStorageHelperBase {
1081 public:
1082 explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory)
1083 : UpgradeStorageHelperBase(aDirectory) {}
1085 private:
1086 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1087 bool* aRemoved) override;
1089 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1092 class UpgradeStorageFrom1_0To2_0Helper final : public UpgradeStorageHelperBase {
1093 public:
1094 explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory)
1095 : UpgradeStorageHelperBase(aDirectory) {}
1097 private:
1098 nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps);
1101 * Remove the origin directory if appId is present in origin attributes.
1103 * @param aOriginProps the properties of the origin to check.
1105 * @return whether the origin directory was removed.
1107 Result<bool, nsresult> MaybeRemoveAppsData(const OriginProps& aOriginProps);
1109 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1110 bool* aRemoved) override;
1112 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1115 class UpgradeStorageFrom2_0To2_1Helper final : public UpgradeStorageHelperBase {
1116 public:
1117 explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory)
1118 : UpgradeStorageHelperBase(aDirectory) {}
1120 private:
1121 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1122 bool* aRemoved) override;
1124 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1127 class UpgradeStorageFrom2_1To2_2Helper final : public UpgradeStorageHelperBase {
1128 public:
1129 explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile* aDirectory)
1130 : UpgradeStorageHelperBase(aDirectory) {}
1132 private:
1133 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1134 bool* aRemoved) override;
1136 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1138 nsresult PrepareClientDirectory(nsIFile* aFile, const nsAString& aLeafName,
1139 bool& aRemoved) override;
1142 class RestoreDirectoryMetadata2Helper final : public StorageOperationBase {
1143 LazyInitializedOnce<const PersistenceType> mPersistenceType;
1145 public:
1146 explicit RestoreDirectoryMetadata2Helper(nsIFile* aDirectory)
1147 : StorageOperationBase(aDirectory) {}
1149 nsresult Init();
1151 nsresult RestoreMetadata2File();
1153 private:
1154 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1157 Result<nsAutoString, nsresult> GetPathForStorage(
1158 nsIFile& aBaseDir, const nsAString& aStorageName) {
1159 QM_TRY_INSPECT(const auto& storageDir,
1160 CloneFileAndAppend(aBaseDir, aStorageName));
1162 QM_TRY_RETURN(
1163 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, storageDir, GetPath));
1166 int64_t GetLastModifiedTime(PersistenceType aPersistenceType, nsIFile& aFile) {
1167 AssertIsOnIOThread();
1169 class MOZ_STACK_CLASS Helper final {
1170 public:
1171 static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) {
1172 AssertIsOnIOThread();
1173 MOZ_ASSERT(aFile);
1174 MOZ_ASSERT(aTimestamp);
1176 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*aFile));
1178 switch (dirEntryKind) {
1179 case nsIFileKind::ExistsAsDirectory:
1180 QM_TRY(CollectEachFile(
1181 *aFile,
1182 [&aTimestamp](const nsCOMPtr<nsIFile>& file)
1183 -> Result<mozilla::Ok, nsresult> {
1184 QM_TRY(MOZ_TO_RESULT(GetLastModifiedTime(file, aTimestamp)));
1186 return Ok{};
1187 }));
1188 break;
1190 case nsIFileKind::ExistsAsFile: {
1191 QM_TRY_INSPECT(const auto& leafName,
1192 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aFile,
1193 GetLeafName));
1195 // Bug 1595445 will handle unknown files here.
1197 if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) ||
1198 IsDotFile(leafName)) {
1199 return NS_OK;
1202 QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
1203 aFile, GetLastModifiedTime));
1205 // Need to convert from milliseconds to microseconds.
1206 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
1207 timestamp *= int64_t(PR_USEC_PER_MSEC);
1209 if (timestamp > *aTimestamp) {
1210 *aTimestamp = timestamp;
1212 break;
1215 case nsIFileKind::DoesNotExist:
1216 // Ignore files that got removed externally while iterating.
1217 break;
1220 return NS_OK;
1224 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
1225 return PR_Now();
1228 int64_t timestamp = INT64_MIN;
1229 nsresult rv = Helper::GetLastModifiedTime(&aFile, &timestamp);
1230 if (NS_FAILED(rv)) {
1231 timestamp = PR_Now();
1234 // XXX if there were no suitable files for getting last modified time
1235 // (timestamp is still set to INT64_MIN), we should return the current time
1236 // instead of returning INT64_MIN.
1238 return timestamp;
1241 // Returns a bool indicating whether the directory was newly created.
1242 Result<bool, nsresult> EnsureDirectory(nsIFile& aDirectory) {
1243 AssertIsOnIOThread();
1245 // Callers call this function without checking if the directory already
1246 // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
1247 // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
1248 // reports.
1249 QM_TRY_INSPECT(const auto& exists,
1250 QM_OR_ELSE_LOG_VERBOSE_IF(
1251 // Expression.
1252 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Create,
1253 nsIFile::DIRECTORY_TYPE, 0755,
1254 /* aSkipAncestors = */ false)
1255 .map([](Ok) { return false; }),
1256 // Predicate.
1257 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
1258 // Fallback.
1259 ErrToOk<true>));
1261 if (exists) {
1262 QM_TRY_INSPECT(const bool& isDirectory,
1263 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
1264 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
1267 return !exists;
1270 void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) {
1271 aJarPrefix.Truncate();
1273 // Fallback.
1274 if (!aInIsolatedMozBrowser) {
1275 return;
1278 // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
1279 // 1320404).
1280 // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
1281 aJarPrefix.AppendInt(0); // TODO: this is the appId, to be removed.
1282 aJarPrefix.Append('+');
1283 aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
1284 aJarPrefix.Append('+');
1287 // This method computes and returns our best guess for the temporary storage
1288 // limit (in bytes), based on disk capacity.
1289 Result<uint64_t, nsresult> GetTemporaryStorageLimit(nsIFile& aStorageDir) {
1290 // The fixed limit pref can be used to override temporary storage limit
1291 // calculation.
1292 if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
1293 return static_cast<uint64_t>(
1294 StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
1295 1024;
1298 constexpr int64_t teraByte = (1024LL * 1024LL * 1024LL * 1024LL);
1299 constexpr int64_t maxAllowedCapacity = 8LL * teraByte;
1301 // Check for disk capacity of user's device on which storage directory lives.
1302 int64_t diskCapacity = maxAllowedCapacity;
1304 // Log error when default disk capacity is returned due to the error
1305 QM_WARNONLY_TRY(MOZ_TO_RESULT(aStorageDir.GetDiskCapacity(&diskCapacity)));
1307 MOZ_ASSERT(diskCapacity >= 0LL);
1309 // Allow temporary storage to consume up to 50% of disk capacity.
1310 int64_t capacityLimit = diskCapacity / 2LL;
1312 // If the disk capacity reported by the operating system is very
1313 // large and potentially incorrect due to hardware issues,
1314 // a hardcoded limit is supplied instead.
1315 QM_WARNONLY_TRY(
1316 OkIf(capacityLimit < maxAllowedCapacity),
1317 ([&capacityLimit](const auto&) { capacityLimit = maxAllowedCapacity; }));
1319 return capacityLimit;
1322 bool IsOriginUnaccessed(const FullOriginMetadata& aFullOriginMetadata,
1323 const int64_t aRecentTime) {
1324 if (aFullOriginMetadata.mLastAccessTime > aRecentTime) {
1325 return false;
1328 return (aRecentTime - aFullOriginMetadata.mLastAccessTime) / PR_USEC_PER_SEC >
1329 StaticPrefs::dom_quotaManager_unaccessedForLongTimeThresholdSec();
1332 bool IsDirectoryLockBlockedBy(
1333 const DirectoryLockImpl::PrepareInfo& aPrepareInfo,
1334 const EnumSet<DirectoryLockCategory>& aCategories) {
1335 const auto& locks = aPrepareInfo.BlockedOnRef();
1336 return std::any_of(locks.cbegin(), locks.cend(),
1337 [&aCategories](const auto& lock) {
1338 return aCategories.contains(lock->Category());
1342 bool IsDirectoryLockBlockedByUninitStorageOperation(
1343 const DirectoryLockImpl::PrepareInfo& aPrepareInfo) {
1344 return IsDirectoryLockBlockedBy(aPrepareInfo,
1345 DirectoryLockCategory::UninitStorage);
1348 bool IsDirectoryLockBlockedByUninitStorageOrUninitOriginsOperation(
1349 const DirectoryLockImpl::PrepareInfo& aPrepareInfo) {
1350 return IsDirectoryLockBlockedBy(aPrepareInfo,
1351 {DirectoryLockCategory::UninitStorage,
1352 DirectoryLockCategory::UninitOrigins});
1355 } // namespace
1357 /*******************************************************************************
1358 * Exported functions
1359 ******************************************************************************/
1361 void InitializeQuotaManager() {
1362 MOZ_ASSERT(XRE_IsParentProcess());
1363 MOZ_ASSERT(NS_IsMainThread());
1364 MOZ_ASSERT(!gQuotaManagerInitialized);
1366 if (!QuotaManager::IsRunningGTests()) {
1367 // These services have to be started on the main thread currently.
1368 const nsCOMPtr<mozIStorageService> ss =
1369 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
1370 QM_WARNONLY_TRY(OkIf(ss));
1372 RefPtr<net::ExtensionProtocolHandler> extensionProtocolHandler =
1373 net::ExtensionProtocolHandler::GetSingleton();
1374 QM_WARNONLY_TRY(MOZ_TO_RESULT(extensionProtocolHandler));
1376 IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
1377 QM_WARNONLY_TRY(MOZ_TO_RESULT(mgr));
1380 QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize()));
1382 #ifdef DEBUG
1383 gQuotaManagerInitialized = true;
1384 #endif
1387 void InitializeScopedLogExtraInfo() {
1388 #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
1389 ScopedLogExtraInfo::Initialize();
1390 #endif
1393 bool RecvShutdownQuotaManager() {
1394 AssertIsOnBackgroundThread();
1396 // If we are already in shutdown, don't call ShutdownInstance()
1397 // again and return true immediately. We shall see this incident
1398 // in Telemetry.
1399 // XXX todo: Make QM_TRY stacks thread-aware (Bug 1735124)
1400 // XXX todo: Active QM_TRY context for shutdown (Bug 1735170)
1401 QM_TRY(OkIf(!gShutdown), true);
1403 QuotaManager::ShutdownInstance();
1405 return true;
1408 QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr;
1410 // static
1411 nsresult QuotaManager::Observer::Initialize() {
1412 MOZ_ASSERT(NS_IsMainThread());
1414 RefPtr<Observer> observer = new Observer();
1416 nsresult rv = observer->Init();
1417 if (NS_WARN_IF(NS_FAILED(rv))) {
1418 return rv;
1421 sInstance = observer;
1423 return NS_OK;
1426 // static
1427 nsIObserver* QuotaManager::Observer::GetInstance() {
1428 MOZ_ASSERT(NS_IsMainThread());
1430 return sInstance;
1433 // static
1434 void QuotaManager::Observer::ShutdownCompleted() {
1435 MOZ_ASSERT(NS_IsMainThread());
1436 MOZ_ASSERT(sInstance);
1438 sInstance->mShutdownComplete = true;
1441 nsresult QuotaManager::Observer::Init() {
1442 MOZ_ASSERT(NS_IsMainThread());
1445 * A RAII utility class to manage the registration and automatic
1446 * unregistration of observers with `nsIObserverService`. This class is
1447 * designed to simplify observer management, particularly when registering
1448 * for multiple topics, by ensuring that already registered topics are
1449 * unregistered if a failure occurs during subsequent registrations.
1451 class MOZ_RAII Registrar {
1452 public:
1453 Registrar(nsIObserverService* aObserverService, nsIObserver* aObserver,
1454 const char* aTopic)
1455 : mObserverService(std::move(aObserverService)),
1456 mObserver(aObserver),
1457 mTopic(aTopic),
1458 mUnregisterOnDestruction(false) {
1459 MOZ_ASSERT(aObserverService);
1460 MOZ_ASSERT(aObserver);
1461 MOZ_ASSERT(aTopic);
1464 ~Registrar() {
1465 if (mUnregisterOnDestruction) {
1466 mObserverService->RemoveObserver(mObserver, mTopic);
1470 nsresult Register() {
1471 MOZ_ASSERT(!mUnregisterOnDestruction);
1473 nsresult rv = mObserverService->AddObserver(mObserver, mTopic, false);
1474 if (NS_WARN_IF(NS_FAILED(rv))) {
1475 return rv;
1478 mUnregisterOnDestruction = true;
1480 return NS_OK;
1483 void Commit() { mUnregisterOnDestruction = false; }
1485 private:
1486 nsIObserverService* mObserverService;
1487 nsIObserver* mObserver;
1488 const char* mTopic;
1489 bool mUnregisterOnDestruction;
1492 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1493 if (NS_WARN_IF(!obs)) {
1494 return NS_ERROR_FAILURE;
1497 Registrar xpcomShutdownRegistrar(obs, this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1498 QM_TRY(MOZ_TO_RESULT(xpcomShutdownRegistrar.Register()));
1500 Registrar profileDoChangeRegistrar(obs, this, kProfileDoChangeTopic);
1501 QM_TRY(MOZ_TO_RESULT(profileDoChangeRegistrar.Register()));
1503 Registrar contextualIdentityServiceLoadFinishedRegistrar(
1504 obs, this, kContextualIdentityServiceLoadFinishedTopic);
1505 QM_TRY(
1506 MOZ_TO_RESULT(contextualIdentityServiceLoadFinishedRegistrar.Register()));
1508 Registrar profileBeforeChangeQmRegistrar(
1509 obs, this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
1510 QM_TRY(MOZ_TO_RESULT(profileBeforeChangeQmRegistrar.Register()));
1512 Registrar wakeNotificationRegistrar(obs, this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
1513 QM_TRY(MOZ_TO_RESULT(wakeNotificationRegistrar.Register()));
1515 Registrar lastPbContextExitedRegistrar(obs, this,
1516 kPrivateBrowsingObserverTopic);
1517 QM_TRY(MOZ_TO_RESULT(lastPbContextExitedRegistrar.Register()));
1519 xpcomShutdownRegistrar.Commit();
1520 profileDoChangeRegistrar.Commit();
1521 contextualIdentityServiceLoadFinishedRegistrar.Commit();
1522 profileBeforeChangeQmRegistrar.Commit();
1523 wakeNotificationRegistrar.Commit();
1524 lastPbContextExitedRegistrar.Commit();
1526 return NS_OK;
1529 nsresult QuotaManager::Observer::Shutdown() {
1530 MOZ_ASSERT(NS_IsMainThread());
1532 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1533 if (NS_WARN_IF(!obs)) {
1534 return NS_ERROR_FAILURE;
1537 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kPrivateBrowsingObserverTopic));
1538 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC));
1539 MOZ_ALWAYS_SUCCEEDS(
1540 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
1541 MOZ_ALWAYS_SUCCEEDS(
1542 obs->RemoveObserver(this, kContextualIdentityServiceLoadFinishedTopic));
1543 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic));
1544 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
1546 sInstance = nullptr;
1548 // In general, the instance will have died after the latter removal call, so
1549 // it's not safe to do anything after that point.
1550 // However, Shutdown is currently called from Observe which is called by the
1551 // Observer Service which holds a strong reference to the observer while the
1552 // Observe method is being called.
1554 return NS_OK;
1557 NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver)
1559 NS_IMETHODIMP
1560 QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
1561 const char16_t* aData) {
1562 MOZ_ASSERT(NS_IsMainThread());
1564 nsresult rv;
1566 if (!strcmp(aTopic, kProfileDoChangeTopic)) {
1567 if (NS_WARN_IF(gBasePath)) {
1568 NS_WARNING(
1569 "profile-before-change-qm must precede repeated "
1570 "profile-do-change!");
1571 return NS_OK;
1574 gBasePath = new nsString();
1576 nsCOMPtr<nsIFile> baseDir;
1577 rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
1578 getter_AddRefs(baseDir));
1579 if (NS_FAILED(rv)) {
1580 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1581 getter_AddRefs(baseDir));
1583 if (NS_WARN_IF(NS_FAILED(rv))) {
1584 return rv;
1587 rv = baseDir->GetPath(*gBasePath);
1588 if (NS_WARN_IF(NS_FAILED(rv))) {
1589 return rv;
1592 #ifdef XP_WIN
1593 // Annotate if our profile lives on a network resource.
1594 bool isNetworkPath = PathIsNetworkPathW(gBasePath->get());
1595 CrashReporter::RecordAnnotationBool(
1596 CrashReporter::Annotation::QuotaManagerStorageIsNetworkResource,
1597 isNetworkPath);
1598 #endif
1600 QM_LOG(("Base path: %s", NS_ConvertUTF16toUTF8(*gBasePath).get()));
1602 gStorageName = new nsString();
1604 rv = Preferences::GetString("dom.quotaManager.storageName", *gStorageName);
1605 if (NS_FAILED(rv)) {
1606 *gStorageName = kStorageName;
1609 gBuildId = new nsCString();
1611 nsCOMPtr<nsIPlatformInfo> platformInfo =
1612 do_GetService("@mozilla.org/xre/app-info;1");
1613 if (NS_WARN_IF(!platformInfo)) {
1614 return NS_ERROR_FAILURE;
1617 rv = platformInfo->GetPlatformBuildID(*gBuildId);
1618 if (NS_WARN_IF(NS_FAILED(rv))) {
1619 return rv;
1622 return NS_OK;
1625 if (!strcmp(aTopic, kContextualIdentityServiceLoadFinishedTopic)) {
1626 if (NS_WARN_IF(!gBasePath)) {
1627 NS_WARNING(
1628 "profile-do-change must precede "
1629 "contextual-identity-service-load-finished!");
1630 return NS_OK;
1633 nsCOMPtr<nsIQuotaManagerServiceInternal> quotaManagerService =
1634 QuotaManagerService::GetOrCreate();
1635 if (NS_WARN_IF(!quotaManagerService)) {
1636 return NS_ERROR_FAILURE;
1639 nsCOMPtr<nsIQuotaUtilsService> quotaUtilsService =
1640 do_GetService("@mozilla.org/dom/quota-utils-service;1");
1641 if (NS_WARN_IF(!quotaUtilsService)) {
1642 return NS_ERROR_FAILURE;
1645 uint32_t thumbnailPrivateIdentityId;
1646 nsresult rv = quotaUtilsService->GetPrivateIdentityId(
1647 u"userContextIdInternal.thumbnail"_ns, &thumbnailPrivateIdentityId);
1648 if (NS_WARN_IF(NS_FAILED(rv))) {
1649 return rv;
1652 rv = quotaManagerService->SetThumbnailPrivateIdentityId(
1653 thumbnailPrivateIdentityId);
1654 if (NS_WARN_IF(NS_FAILED(rv))) {
1655 return rv;
1658 return NS_OK;
1661 if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
1662 if (NS_WARN_IF(!gBasePath)) {
1663 NS_WARNING("profile-do-change must precede profile-before-change-qm!");
1664 return NS_OK;
1667 // mPendingProfileChange is our re-entrancy guard (the nested event loop
1668 // below may cause re-entrancy).
1669 if (mPendingProfileChange) {
1670 return NS_OK;
1673 AutoRestore<bool> pending(mPendingProfileChange);
1674 mPendingProfileChange = true;
1676 mShutdownComplete = false;
1678 PBackgroundChild* backgroundActor =
1679 BackgroundChild::GetOrCreateForCurrentThread();
1680 if (NS_WARN_IF(!backgroundActor)) {
1681 return NS_ERROR_FAILURE;
1684 if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
1685 return NS_ERROR_FAILURE;
1688 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
1689 "QuotaManager::Observer::Observe profile-before-change-qm"_ns,
1690 [&]() { return mShutdownComplete; }));
1692 gBasePath = nullptr;
1694 gStorageName = nullptr;
1696 gBuildId = nullptr;
1698 return NS_OK;
1701 if (!strcmp(aTopic, kPrivateBrowsingObserverTopic)) {
1702 auto* const quotaManagerService = QuotaManagerService::GetOrCreate();
1703 if (NS_WARN_IF(!quotaManagerService)) {
1704 return NS_ERROR_FAILURE;
1707 nsCOMPtr<nsIQuotaRequest> request;
1708 rv = quotaManagerService->ClearStoragesForPrivateBrowsing(
1709 nsGetterAddRefs(request));
1710 if (NS_WARN_IF(NS_FAILED(rv))) {
1711 return rv;
1714 return NS_OK;
1717 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
1718 rv = Shutdown();
1719 if (NS_WARN_IF(NS_FAILED(rv))) {
1720 return rv;
1723 return NS_OK;
1726 if (!strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
1727 gLastOSWake = TimeStamp::Now();
1729 return NS_OK;
1732 NS_WARNING("Unknown observer topic!");
1733 return NS_OK;
1736 /*******************************************************************************
1737 * Quota manager
1738 ******************************************************************************/
1740 QuotaManager::QuotaManager(const nsAString& aBasePath,
1741 const nsAString& aStorageName)
1742 : mQuotaMutex("QuotaManager.mQuotaMutex"),
1743 mBasePath(aBasePath),
1744 mStorageName(aStorageName),
1745 mTemporaryStorageUsage(0),
1746 mNextDirectoryLockId(0),
1747 mStorageInitialized(false),
1748 mPersistentStorageInitialized(false),
1749 mPersistentStorageInitializedInternal(false),
1750 mTemporaryStorageInitialized(false),
1751 mTemporaryStorageInitializedInternal(false),
1752 mInitializingAllTemporaryOrigins(false),
1753 mAllTemporaryOriginsInitialized(false),
1754 mCacheUsable(false) {
1755 AssertIsOnOwningThread();
1756 MOZ_ASSERT(!gInstance);
1759 QuotaManager::~QuotaManager() {
1760 AssertIsOnOwningThread();
1761 MOZ_ASSERT(!gInstance || gInstance == this);
1764 // static
1765 nsresult QuotaManager::Initialize() {
1766 MOZ_ASSERT(NS_IsMainThread());
1768 nsresult rv = Observer::Initialize();
1769 if (NS_WARN_IF(NS_FAILED(rv))) {
1770 return rv;
1773 return NS_OK;
1776 // static
1777 Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult>
1778 QuotaManager::GetOrCreate() {
1779 AssertIsOnBackgroundThread();
1781 if (gInstance) {
1782 return WrapMovingNotNullUnchecked(RefPtr<QuotaManager>{gInstance});
1785 QM_TRY(OkIf(gBasePath), Err(NS_ERROR_FAILURE), [](const auto&) {
1786 NS_WARNING(
1787 "Trying to create QuotaManager before profile-do-change! "
1788 "Forgot to call do_get_profile()?");
1791 QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE), [](const auto&) {
1792 MOZ_ASSERT(false,
1793 "Trying to create QuotaManager after profile-before-change-qm!");
1796 auto instance = MakeRefPtr<QuotaManager>(*gBasePath, *gStorageName);
1798 QM_TRY(MOZ_TO_RESULT(instance->Init()));
1800 gInstance = instance;
1802 // Do this before clients have a chance to acquire a directory lock for the
1803 // private repository.
1804 gInstance->ClearPrivateRepository();
1806 return WrapMovingNotNullUnchecked(std::move(instance));
1809 Result<Ok, nsresult> QuotaManager::EnsureCreated() {
1810 AssertIsOnBackgroundThread();
1812 QM_TRY_RETURN(GetOrCreate().map([](const auto& res) { return Ok{}; }))
1815 // static
1816 QuotaManager* QuotaManager::Get() {
1817 // Does not return an owning reference.
1818 return gInstance;
1821 // static
1822 nsIObserver* QuotaManager::GetObserver() {
1823 MOZ_ASSERT(NS_IsMainThread());
1825 return Observer::GetInstance();
1828 // static
1829 bool QuotaManager::IsShuttingDown() { return gShutdown; }
1831 // static
1832 void QuotaManager::ShutdownInstance() {
1833 AssertIsOnBackgroundThread();
1835 if (gInstance) {
1836 auto recordTimeDeltaHelper =
1837 MakeRefPtr<RecordTimeDeltaHelper>(Telemetry::QM_SHUTDOWN_TIME_V0);
1839 recordTimeDeltaHelper->Start();
1841 gInstance->Shutdown();
1843 recordTimeDeltaHelper->End();
1845 gInstance = nullptr;
1846 } else {
1847 // If we were never initialized, just set the flag to avoid late creation.
1848 gShutdown = true;
1851 RefPtr<Runnable> runnable =
1852 NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
1853 []() { Observer::ShutdownCompleted(); });
1854 MOZ_ASSERT(runnable);
1856 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
1859 // static
1860 void QuotaManager::Reset() {
1861 AssertIsOnBackgroundThread();
1862 MOZ_ASSERT(!gInstance);
1863 MOZ_ASSERT(gShutdown);
1865 gShutdown = false;
1868 // static
1869 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
1870 return mozilla::dom::quota::IsOSMetadata(aFileName);
1873 // static
1874 bool QuotaManager::IsDotFile(const nsAString& aFileName) {
1875 return mozilla::dom::quota::IsDotFile(aFileName);
1878 void QuotaManager::RegisterNormalOriginOp(
1879 NormalOriginOperationBase& aNormalOriginOp) {
1880 AssertIsOnBackgroundThread();
1882 if (!gNormalOriginOps) {
1883 gNormalOriginOps = new NormalOriginOpArray();
1886 gNormalOriginOps->AppendElement(&aNormalOriginOp);
1889 void QuotaManager::UnregisterNormalOriginOp(
1890 NormalOriginOperationBase& aNormalOriginOp) {
1891 AssertIsOnBackgroundThread();
1892 MOZ_ASSERT(gNormalOriginOps);
1894 gNormalOriginOps->RemoveElement(&aNormalOriginOp);
1896 if (gNormalOriginOps->IsEmpty()) {
1897 gNormalOriginOps = nullptr;
1901 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl& aLock) {
1902 AssertIsOnOwningThread();
1904 mDirectoryLocks.AppendElement(WrapNotNullUnchecked(&aLock));
1906 if (aLock.ShouldUpdateLockIdTable()) {
1907 MutexAutoLock lock(mQuotaMutex);
1909 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable.Contains(aLock.Id()));
1910 mDirectoryLockIdTable.InsertOrUpdate(aLock.Id(),
1911 WrapNotNullUnchecked(&aLock));
1914 if (aLock.ShouldUpdateLockTable()) {
1915 DirectoryLockTable& directoryLockTable =
1916 GetDirectoryLockTable(aLock.GetPersistenceType());
1918 // XXX It seems that the contents of the array are never actually used, we
1919 // just use that like an inefficient use counter. Can't we just change
1920 // DirectoryLockTable to a nsTHashMap<nsCStringHashKey, uint32_t>?
1921 directoryLockTable
1922 .LookupOrInsertWith(
1923 aLock.Origin(),
1924 [this, &aLock] {
1925 if (!IsShuttingDown()) {
1926 UpdateOriginAccessTime(aLock.GetPersistenceType(),
1927 aLock.OriginMetadata());
1929 return MakeUnique<nsTArray<NotNull<DirectoryLockImpl*>>>();
1931 ->AppendElement(WrapNotNullUnchecked(&aLock));
1934 aLock.SetRegistered(true);
1937 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl& aLock) {
1938 AssertIsOnOwningThread();
1940 MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(&aLock));
1942 if (aLock.ShouldUpdateLockIdTable()) {
1943 MutexAutoLock lock(mQuotaMutex);
1945 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable.Contains(aLock.Id()));
1946 mDirectoryLockIdTable.Remove(aLock.Id());
1949 if (aLock.ShouldUpdateLockTable()) {
1950 DirectoryLockTable& directoryLockTable =
1951 GetDirectoryLockTable(aLock.GetPersistenceType());
1953 // ClearDirectoryLockTables may have been called, so the element or entire
1954 // array may not exist anymre.
1955 nsTArray<NotNull<DirectoryLockImpl*>>* array;
1956 if (directoryLockTable.Get(aLock.Origin(), &array) &&
1957 array->RemoveElement(&aLock) && array->IsEmpty()) {
1958 directoryLockTable.Remove(aLock.Origin());
1960 if (!IsShuttingDown()) {
1961 UpdateOriginAccessTime(aLock.GetPersistenceType(),
1962 aLock.OriginMetadata());
1967 aLock.SetRegistered(false);
1970 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl& aLock) {
1971 AssertIsOnOwningThread();
1973 mPendingDirectoryLocks.AppendElement(&aLock);
1976 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl& aLock) {
1977 AssertIsOnOwningThread();
1979 MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(&aLock));
1982 uint64_t QuotaManager::CollectOriginsForEviction(
1983 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
1984 AssertIsOnOwningThread();
1985 MOZ_ASSERT(aLocks.IsEmpty());
1987 // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
1988 // or maybe a generalization if that.
1990 struct MOZ_STACK_CLASS Helper final {
1991 static void GetInactiveOriginInfos(
1992 const nsTArray<NotNull<RefPtr<OriginInfo>>>& aOriginInfos,
1993 const nsTArray<NotNull<const DirectoryLockImpl*>>& aLocks,
1994 OriginInfosFlatTraversable& aInactiveOriginInfos) {
1995 for (const auto& originInfo : aOriginInfos) {
1996 MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType !=
1997 PERSISTENCE_TYPE_PERSISTENT);
1999 if (originInfo->LockedPersisted()) {
2000 continue;
2003 // Never evict PERSISTENCE_TYPE_DEFAULT data associated to a
2004 // moz-extension origin, unlike websites (which may more likely using
2005 // the local data as a cache but still able to retrieve the same data
2006 // from the server side) extensions do not have the same data stored
2007 // anywhere else and evicting the data would result into potential data
2008 // loss for the users.
2010 // Also, unlike a website the extensions are explicitly installed and
2011 // uninstalled by the user and all data associated to the extension
2012 // principal will be completely removed once the addon is uninstalled.
2013 if (originInfo->mGroupInfo->mPersistenceType !=
2014 PERSISTENCE_TYPE_TEMPORARY &&
2015 originInfo->IsExtensionOrigin()) {
2016 continue;
2019 const auto originScope =
2020 OriginScope::FromOrigin(originInfo->FlattenToOriginMetadata());
2022 const bool match =
2023 std::any_of(aLocks.begin(), aLocks.end(),
2024 [&originScope](const DirectoryLockImpl* const lock) {
2025 return originScope.Matches(lock->GetOriginScope());
2028 if (!match) {
2029 MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count(),
2030 "Inactive origin shouldn't have open files!");
2031 aInactiveOriginInfos.InsertElementSorted(
2032 originInfo, OriginInfoAccessTimeComparator());
2038 // Split locks into separate arrays and filter out locks for persistent
2039 // storage, they can't block us.
2040 auto [temporaryStorageLocks, defaultStorageLocks,
2041 privateStorageLocks] = [this] {
2042 nsTArray<NotNull<const DirectoryLockImpl*>> temporaryStorageLocks;
2043 nsTArray<NotNull<const DirectoryLockImpl*>> defaultStorageLocks;
2044 nsTArray<NotNull<const DirectoryLockImpl*>> privateStorageLocks;
2046 for (NotNull<const DirectoryLockImpl*> const lock : mDirectoryLocks) {
2047 const PersistenceScope& persistenceScope = lock->PersistenceScopeRef();
2049 if (persistenceScope.Matches(
2050 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_TEMPORARY))) {
2051 temporaryStorageLocks.AppendElement(lock);
2054 if (persistenceScope.Matches(
2055 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_DEFAULT))) {
2056 defaultStorageLocks.AppendElement(lock);
2059 if (persistenceScope.Matches(
2060 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PRIVATE))) {
2061 privateStorageLocks.AppendElement(lock);
2065 return std::make_tuple(std::move(temporaryStorageLocks),
2066 std::move(defaultStorageLocks),
2067 std::move(privateStorageLocks));
2068 }();
2070 // Enumerate and process inactive origins. This must be protected by the
2071 // mutex.
2072 MutexAutoLock lock(mQuotaMutex);
2074 const auto [inactiveOrigins, sizeToBeFreed] =
2075 [this, &temporaryStorageLocks = temporaryStorageLocks,
2076 &defaultStorageLocks = defaultStorageLocks,
2077 &privateStorageLocks = privateStorageLocks, aMinSizeToBeFreed] {
2078 nsTArray<NotNull<RefPtr<const OriginInfo>>> inactiveOrigins;
2079 for (const auto& entry : mGroupInfoPairs) {
2080 const auto& pair = entry.GetData();
2082 MOZ_ASSERT(!entry.GetKey().IsEmpty());
2083 MOZ_ASSERT(pair);
2085 RefPtr<GroupInfo> groupInfo =
2086 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2087 if (groupInfo) {
2088 Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
2089 temporaryStorageLocks,
2090 inactiveOrigins);
2093 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
2094 if (groupInfo) {
2095 Helper::GetInactiveOriginInfos(
2096 groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins);
2099 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
2100 if (groupInfo) {
2101 Helper::GetInactiveOriginInfos(
2102 groupInfo->mOriginInfos, privateStorageLocks, inactiveOrigins);
2106 #ifdef DEBUG
2107 // Make sure the array is sorted correctly.
2108 const bool inactiveOriginsSorted =
2109 std::is_sorted(inactiveOrigins.cbegin(), inactiveOrigins.cend(),
2110 [](const auto& lhs, const auto& rhs) {
2111 return lhs->mAccessTime < rhs->mAccessTime;
2113 MOZ_ASSERT(inactiveOriginsSorted);
2114 #endif
2116 // Create a list of inactive and the least recently used origins
2117 // whose aggregate size is greater or equals the minimal size to be
2118 // freed.
2119 uint64_t sizeToBeFreed = 0;
2120 for (uint32_t count = inactiveOrigins.Length(), index = 0;
2121 index < count; index++) {
2122 if (sizeToBeFreed >= aMinSizeToBeFreed) {
2123 inactiveOrigins.TruncateLength(index);
2124 break;
2127 sizeToBeFreed += inactiveOrigins[index]->LockedUsage();
2130 return std::pair(std::move(inactiveOrigins), sizeToBeFreed);
2131 }();
2133 if (sizeToBeFreed >= aMinSizeToBeFreed) {
2134 // Success, add directory locks for these origins, so any other
2135 // operations for them will be delayed (until origin eviction is finalized).
2137 for (const auto& originInfo : inactiveOrigins) {
2138 auto lock = OriginDirectoryLock::CreateForEviction(
2139 WrapNotNullUnchecked(this), originInfo->mGroupInfo->mPersistenceType,
2140 originInfo->FlattenToOriginMetadata());
2142 lock->AcquireImmediately();
2144 aLocks.AppendElement(lock.forget());
2147 return sizeToBeFreed;
2150 return 0;
2153 nsresult QuotaManager::Init() {
2154 AssertIsOnOwningThread();
2156 #ifdef XP_WIN
2157 CacheUseDOSDevicePathSyntaxPrefValue();
2158 #endif
2160 QM_TRY_INSPECT(const auto& baseDir, QM_NewLocalFile(mBasePath));
2162 QM_TRY_UNWRAP(
2163 do_Init(mIndexedDBPath),
2164 GetPathForStorage(*baseDir, nsLiteralString(INDEXEDDB_DIRECTORY_NAME)));
2166 QM_TRY(MOZ_TO_RESULT(baseDir->Append(mStorageName)));
2168 QM_TRY_UNWRAP(do_Init(mStoragePath),
2169 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, baseDir, GetPath));
2171 QM_TRY_UNWRAP(
2172 do_Init(mStorageArchivesPath),
2173 GetPathForStorage(*baseDir, nsLiteralString(ARCHIVES_DIRECTORY_NAME)));
2175 QM_TRY_UNWRAP(
2176 do_Init(mPermanentStoragePath),
2177 GetPathForStorage(*baseDir, nsLiteralString(PERMANENT_DIRECTORY_NAME)));
2179 QM_TRY_UNWRAP(
2180 do_Init(mTemporaryStoragePath),
2181 GetPathForStorage(*baseDir, nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
2183 QM_TRY_UNWRAP(
2184 do_Init(mDefaultStoragePath),
2185 GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
2187 QM_TRY_UNWRAP(
2188 do_Init(mPrivateStoragePath),
2189 GetPathForStorage(*baseDir, nsLiteralString(PRIVATE_DIRECTORY_NAME)));
2191 QM_TRY_UNWRAP(
2192 do_Init(mToBeRemovedStoragePath),
2193 GetPathForStorage(*baseDir, nsLiteralString(TOBEREMOVED_DIRECTORY_NAME)));
2195 QM_TRY_UNWRAP(do_Init(mIOThread),
2196 MOZ_TO_RESULT_INVOKE_TYPED(
2197 nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread),
2198 "QuotaManager IO"));
2200 // XXX This could be eventually moved to nsThreadUtils.h or nsIThread
2201 // could have an infallible method returning PRThread as return value.
2202 auto PRThreadFromThread = [](nsIThread* aThread) {
2203 MOZ_ASSERT(aThread);
2205 PRThread* result;
2206 MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
2207 MOZ_ASSERT(result);
2209 return result;
2212 mIOThreadAccessible.Transfer(PRThreadFromThread(*mIOThread));
2214 static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
2215 Client::FILESYSTEM == 3 && Client::LS == 4 &&
2216 Client::TYPE_MAX == 5,
2217 "Fix the registration!");
2219 // Register clients.
2220 auto clients = decltype(mClients)::ValueType{};
2221 clients.AppendElement(indexedDB::CreateQuotaClient());
2222 clients.AppendElement(cache::CreateQuotaClient());
2223 clients.AppendElement(simpledb::CreateQuotaClient());
2224 clients.AppendElement(fs::CreateQuotaClient());
2225 if (NextGenLocalStorageEnabled()) {
2226 clients.AppendElement(localstorage::CreateQuotaClient());
2227 } else {
2228 clients.SetLength(Client::TypeMax());
2231 mClients.init(std::move(clients));
2233 MOZ_ASSERT(mClients->Capacity() == Client::TYPE_MAX,
2234 "Should be using an auto array with correct capacity!");
2236 mAllClientTypes.init(ClientTypesArray{
2237 Client::Type::IDB, Client::Type::DOMCACHE, Client::Type::SDB,
2238 Client::Type::FILESYSTEM, Client::Type::LS});
2239 mAllClientTypesExceptLS.init(
2240 ClientTypesArray{Client::Type::IDB, Client::Type::DOMCACHE,
2241 Client::Type::SDB, Client::Type::FILESYSTEM});
2243 return NS_OK;
2246 // static
2247 void QuotaManager::MaybeRecordQuotaClientShutdownStep(
2248 const Client::Type aClientType, const nsACString& aStepDescription) {
2249 // Callable on any thread.
2251 auto* const quotaManager = QuotaManager::Get();
2252 MOZ_DIAGNOSTIC_ASSERT(quotaManager);
2254 if (quotaManager->IsShuttingDown()) {
2255 quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
2259 // static
2260 void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
2261 const Client::Type aClientType, const nsACString& aStepDescription) {
2262 // Callable on any thread.
2264 auto* const quotaManager = QuotaManager::Get();
2266 if (quotaManager && quotaManager->IsShuttingDown()) {
2267 quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
2271 void QuotaManager::RecordQuotaManagerShutdownStep(
2272 const nsACString& aStepDescription) {
2273 // Callable on any thread.
2274 MOZ_ASSERT(IsShuttingDown());
2276 RecordShutdownStep(Nothing{}, aStepDescription);
2279 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
2280 const nsACString& aStepDescription) {
2281 // Callable on any thread.
2283 if (IsShuttingDown()) {
2284 RecordQuotaManagerShutdownStep(aStepDescription);
2288 void QuotaManager::RecordShutdownStep(const Maybe<Client::Type> aClientType,
2289 const nsACString& aStepDescription) {
2290 MOZ_ASSERT(IsShuttingDown());
2292 const TimeDuration elapsedSinceShutdownStart =
2293 TimeStamp::NowLoRes() - *mShutdownStartedAt;
2295 const auto stepString =
2296 nsPrintfCString("%fs: %s", elapsedSinceShutdownStart.ToSeconds(),
2297 nsPromiseFlatCString(aStepDescription).get());
2299 if (aClientType) {
2300 AssertIsOnBackgroundThread();
2302 mShutdownSteps[*aClientType].Append(stepString + "\n"_ns);
2303 } else {
2304 // Callable on any thread.
2305 MutexAutoLock lock(mQuotaMutex);
2307 mQuotaManagerShutdownSteps.Append(stepString + "\n"_ns);
2310 #ifdef DEBUG
2311 // XXX Probably this isn't the mechanism that should be used here.
2313 NS_DebugBreak(
2314 NS_DEBUG_WARNING,
2315 nsAutoCString(aClientType ? Client::TypeToText(*aClientType)
2316 : "quota manager"_ns + " shutdown step"_ns)
2317 .get(),
2318 stepString.get(), __FILE__, __LINE__);
2319 #endif
2322 void QuotaManager::Shutdown() {
2323 AssertIsOnOwningThread();
2324 MOZ_DIAGNOSTIC_ASSERT(!gShutdown);
2326 // Define some local helper functions
2328 auto flagShutdownStarted = [this]() {
2329 mShutdownStartedAt.init(TimeStamp::NowLoRes());
2331 // Setting this flag prevents the service from being recreated and prevents
2332 // further storages from being created.
2333 gShutdown = true;
2336 nsCOMPtr<nsITimer> crashBrowserTimer;
2338 auto crashBrowserTimerCallback = [](nsITimer* aTimer, void* aClosure) {
2339 auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
2341 quotaManager->RecordQuotaManagerShutdownStep(
2342 "crashBrowserTimerCallback"_ns);
2344 nsCString annotation;
2346 for (Client::Type type : quotaManager->AllClientTypes()) {
2347 auto& quotaClient = *(*quotaManager->mClients)[type];
2349 if (!quotaClient.IsShutdownCompleted()) {
2350 annotation.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n",
2351 Client::TypeToText(type).get(),
2352 quotaClient.GetShutdownStatus().get(),
2353 quotaManager->mShutdownSteps[type].get());
2357 if (gNormalOriginOps) {
2358 annotation.AppendPrintf("QM: %zu normal origin ops pending\n",
2359 gNormalOriginOps->Length());
2361 for (const auto& op : *gNormalOriginOps) {
2362 #ifdef QM_COLLECTING_OPERATION_TELEMETRY
2363 annotation.AppendPrintf("Op: %s pending\n", op->Name());
2364 #endif
2366 nsCString data;
2367 op->Stringify(data);
2369 annotation.AppendPrintf("Op details:\n%s\n", data.get());
2373 MutexAutoLock lock(quotaManager->mQuotaMutex);
2375 annotation.AppendPrintf("Intermediate steps:\n%s\n",
2376 quotaManager->mQuotaManagerShutdownSteps.get());
2379 CrashReporter::RecordAnnotationNSCString(
2380 CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation);
2382 MOZ_CRASH("Quota manager shutdown timed out");
2385 auto startCrashBrowserTimer = [&]() {
2386 crashBrowserTimer = NS_NewTimer();
2387 MOZ_ASSERT(crashBrowserTimer);
2388 if (crashBrowserTimer) {
2389 RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns);
2390 MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer->InitWithNamedFuncCallback(
2391 crashBrowserTimerCallback, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS,
2392 nsITimer::TYPE_ONE_SHOT,
2393 "quota::QuotaManager::Shutdown::crashBrowserTimer"));
2397 auto stopCrashBrowserTimer = [&]() {
2398 if (crashBrowserTimer) {
2399 RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns);
2400 QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer->Cancel()));
2404 auto initiateShutdownWorkThreads = [this]() {
2405 RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns);
2406 bool needsToWait = false;
2407 for (Client::Type type : AllClientTypes()) {
2408 // Clients are supposed to also AbortAllOperations from this point on
2409 // to speed up shutdown, if possible. Thus pending operations
2410 // might not be executed anymore.
2411 needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
2414 return needsToWait;
2417 nsCOMPtr<nsITimer> killActorsTimer;
2419 auto killActorsTimerCallback = [](nsITimer* aTimer, void* aClosure) {
2420 auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
2422 quotaManager->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns);
2424 // XXX: This abort is a workaround to unblock shutdown, which
2425 // ought to be removed by bug 1682326. We probably need more
2426 // checks to immediately abort new operations during
2427 // shutdown.
2428 quotaManager->GetClient(Client::IDB)->AbortAllOperations();
2430 for (Client::Type type : quotaManager->AllClientTypes()) {
2431 quotaManager->GetClient(type)->ForceKillActors();
2435 auto startKillActorsTimer = [&]() {
2436 killActorsTimer = NS_NewTimer();
2437 MOZ_ASSERT(killActorsTimer);
2438 if (killActorsTimer) {
2439 RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns);
2440 MOZ_ALWAYS_SUCCEEDS(killActorsTimer->InitWithNamedFuncCallback(
2441 killActorsTimerCallback, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
2442 nsITimer::TYPE_ONE_SHOT,
2443 "quota::QuotaManager::Shutdown::killActorsTimer"));
2447 auto stopKillActorsTimer = [&]() {
2448 if (killActorsTimer) {
2449 RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns);
2450 QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer->Cancel()));
2454 auto isAllClientsShutdownComplete = [this] {
2455 return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(),
2456 [&self = *this](const auto type) {
2457 return (*self.mClients)[type]->IsShutdownCompleted();
2461 auto shutdownAndJoinWorkThreads = [this]() {
2462 RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns);
2463 for (Client::Type type : AllClientTypes()) {
2464 (*mClients)[type]->FinalizeShutdownWorkThreads();
2468 auto shutdownAndJoinIOThread = [this]() {
2469 RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns);
2471 // Make sure to join with our IO thread.
2472 QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
2475 auto invalidatePendingDirectoryLocks = [this]() {
2476 RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns);
2477 for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
2478 lock->Invalidate();
2482 // Body of the function
2484 ScopedLogExtraInfo scope{ScopedLogExtraInfo::kTagContextTainted,
2485 "dom::quota::QuotaManager::Shutdown"_ns};
2487 // We always need to ensure that firefox does not shutdown with a private
2488 // repository still on disk. They are ideally cleaned up on PBM session end
2489 // but, in some cases like PBM autostart (i.e.
2490 // browser.privatebrowsing.autostart), private repository could only be
2491 // cleaned up on shutdown. ClearPrivateRepository below runs a async op and is
2492 // better to do it before we run the ShutdownStorageOp since it expects all
2493 // cleanup operations to be done by that point. We don't need to use the
2494 // returned promise here because `ClearPrivateRepository` registers the
2495 // underlying `ClearPrivateRepositoryOp` in `gNormalOriginOps`.
2496 ClearPrivateRepository();
2498 // This must be called before `flagShutdownStarted`, it would fail otherwise.
2499 // `ShutdownStorageOp` needs to acquire an exclusive directory lock over
2500 // entire <profile>/storage which will abort any existing operations and wait
2501 // for all existing directory locks to be released. So the shutdown operation
2502 // will effectively run after all existing operations.
2503 // Similar, to ClearPrivateRepository operation above, ShutdownStorageOp also
2504 // registers it's operation in `gNormalOriginOps` so we don't need to assign
2505 // returned promise.
2506 ShutdownStorage();
2508 flagShutdownStarted();
2510 startCrashBrowserTimer();
2512 // XXX: StopIdleMaintenance now just notifies all clients to abort any
2513 // maintenance work.
2514 // This could be done as part of QuotaClient::AbortAllOperations.
2515 StopIdleMaintenance();
2517 // XXX In theory, we could simplify the code below (and also the `Client`
2518 // interface) by removing the `initiateShutdownWorkThreads` and
2519 // `isAllClientsShutdownComplete` calls because it should be sufficient
2520 // to rely on `ShutdownStorage` to abort all existing operations and to
2521 // wait for all existing directory locks to be released as well.
2523 const bool needsToWait =
2524 initiateShutdownWorkThreads() || static_cast<bool>(gNormalOriginOps);
2526 // If any clients cannot shutdown immediately, spin the event loop while we
2527 // wait on all the threads to close.
2528 if (needsToWait) {
2529 startKillActorsTimer();
2531 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
2532 "QuotaManager::Shutdown"_ns, [isAllClientsShutdownComplete]() {
2533 return !gNormalOriginOps && isAllClientsShutdownComplete();
2534 }));
2536 stopKillActorsTimer();
2539 shutdownAndJoinWorkThreads();
2541 shutdownAndJoinIOThread();
2543 invalidatePendingDirectoryLocks();
2545 stopCrashBrowserTimer();
2548 void QuotaManager::InitQuotaForOrigin(
2549 const FullOriginMetadata& aFullOriginMetadata,
2550 const ClientUsageArray& aClientUsages, uint64_t aUsageBytes,
2551 bool aDirectoryExists) {
2552 AssertIsOnIOThread();
2553 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
2555 MutexAutoLock lock(mQuotaMutex);
2557 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
2558 aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mSuffix,
2559 aFullOriginMetadata.mGroup);
2561 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
2562 groupInfo, aFullOriginMetadata.mOrigin,
2563 aFullOriginMetadata.mStorageOrigin, aFullOriginMetadata.mIsPrivate,
2564 aClientUsages, aUsageBytes, aFullOriginMetadata.mLastAccessTime,
2565 aFullOriginMetadata.mPersisted, aDirectoryExists));
2568 void QuotaManager::DecreaseUsageForClient(const ClientMetadata& aClientMetadata,
2569 int64_t aSize) {
2570 MOZ_ASSERT(!NS_IsMainThread());
2571 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
2573 MutexAutoLock lock(mQuotaMutex);
2575 GroupInfoPair* pair;
2576 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
2577 return;
2580 RefPtr<GroupInfo> groupInfo =
2581 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
2582 if (!groupInfo) {
2583 return;
2586 RefPtr<OriginInfo> originInfo =
2587 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
2588 if (originInfo) {
2589 originInfo->LockedDecreaseUsage(aClientMetadata.mClientType, aSize);
2593 void QuotaManager::ResetUsageForClient(const ClientMetadata& aClientMetadata) {
2594 MOZ_ASSERT(!NS_IsMainThread());
2595 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
2597 MutexAutoLock lock(mQuotaMutex);
2599 GroupInfoPair* pair;
2600 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
2601 return;
2604 RefPtr<GroupInfo> groupInfo =
2605 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
2606 if (!groupInfo) {
2607 return;
2610 RefPtr<OriginInfo> originInfo =
2611 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
2612 if (originInfo) {
2613 originInfo->LockedResetUsageForClient(aClientMetadata.mClientType);
2617 UsageInfo QuotaManager::GetUsageForClient(PersistenceType aPersistenceType,
2618 const OriginMetadata& aOriginMetadata,
2619 Client::Type aClientType) {
2620 MOZ_ASSERT(!NS_IsMainThread());
2621 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
2623 MutexAutoLock lock(mQuotaMutex);
2625 GroupInfoPair* pair;
2626 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
2627 return UsageInfo{};
2630 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
2631 if (!groupInfo) {
2632 return UsageInfo{};
2635 RefPtr<OriginInfo> originInfo =
2636 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2637 if (!originInfo) {
2638 return UsageInfo{};
2641 return originInfo->LockedGetUsageForClient(aClientType);
2644 void QuotaManager::UpdateOriginAccessTime(
2645 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
2646 AssertIsOnOwningThread();
2647 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
2648 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
2649 MOZ_ASSERT(!IsShuttingDown());
2651 MutexAutoLock lock(mQuotaMutex);
2653 GroupInfoPair* pair;
2654 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
2655 return;
2658 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
2659 if (!groupInfo) {
2660 return;
2663 RefPtr<OriginInfo> originInfo =
2664 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2665 if (originInfo) {
2666 int64_t timestamp = PR_Now();
2667 originInfo->LockedUpdateAccessTime(timestamp);
2669 MutexAutoUnlock autoUnlock(mQuotaMutex);
2671 auto op = CreateSaveOriginAccessTimeOp(WrapMovingNotNullUnchecked(this),
2672 aOriginMetadata, timestamp);
2674 RegisterNormalOriginOp(*op);
2676 op->RunImmediately();
2680 void QuotaManager::RemoveQuota() {
2681 AssertIsOnIOThread();
2683 MutexAutoLock lock(mQuotaMutex);
2685 for (const auto& entry : mGroupInfoPairs) {
2686 const auto& pair = entry.GetData();
2688 MOZ_ASSERT(!entry.GetKey().IsEmpty());
2689 MOZ_ASSERT(pair);
2691 RefPtr<GroupInfo> groupInfo =
2692 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2693 if (groupInfo) {
2694 groupInfo->LockedRemoveOriginInfos();
2697 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
2698 if (groupInfo) {
2699 groupInfo->LockedRemoveOriginInfos();
2702 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
2703 if (groupInfo) {
2704 groupInfo->LockedRemoveOriginInfos();
2708 mGroupInfoPairs.Clear();
2710 MOZ_ASSERT(mTemporaryStorageUsage == 0, "Should be zero!");
2713 // XXX Rename this method because the method doesn't load full quota
2714 // information if origin initialization is done lazily.
2715 nsresult QuotaManager::LoadQuota() {
2716 AssertIsOnIOThread();
2717 MOZ_ASSERT(mStorageConnection);
2718 MOZ_ASSERT(!mTemporaryStorageInitializedInternal);
2720 // A list of all unaccessed default or temporary origins.
2721 nsTArray<FullOriginMetadata> unaccessedOrigins;
2723 // XXX The list of all unaccessed default or temporary origins can be now
2724 // generated from mAllTemporaryOrigins.
2725 auto MaybeCollectUnaccessedOrigin =
2726 [loadQuotaInfoStartTime = PR_Now(),
2727 &unaccessedOrigins](const auto& fullOriginMetadata) {
2728 if (IsOriginUnaccessed(fullOriginMetadata, loadQuotaInfoStartTime)) {
2729 unaccessedOrigins.AppendElement(fullOriginMetadata);
2733 auto recordTimeDeltaHelper =
2734 MakeRefPtr<RecordTimeDeltaHelper>(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0);
2736 const auto startTime = recordTimeDeltaHelper->Start();
2738 auto LoadQuotaFromCache = [&]() -> nsresult {
2739 QM_TRY_INSPECT(
2740 const auto& stmt,
2741 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2742 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
2743 "SELECT repository_id, suffix, group_, "
2744 "origin, client_usages, usage, "
2745 "last_access_time, accessed, persisted "
2746 "FROM origin"_ns));
2748 auto autoRemoveQuota = MakeScopeExit([&] {
2749 RemoveQuota();
2750 RemoveTemporaryOrigins();
2751 unaccessedOrigins.Clear();
2754 QM_TRY(quota::CollectWhileHasResult(
2755 *stmt,
2756 [this,
2757 &MaybeCollectUnaccessedOrigin](auto& stmt) -> Result<Ok, nsresult> {
2758 QM_TRY_INSPECT(const int32_t& repositoryId,
2759 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
2761 const auto maybePersistenceType =
2762 PersistenceTypeFromInt32(repositoryId, fallible);
2763 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
2765 FullOriginMetadata fullOriginMetadata;
2767 fullOriginMetadata.mPersistenceType = maybePersistenceType.value();
2769 QM_TRY_UNWRAP(fullOriginMetadata.mSuffix,
2770 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2771 GetUTF8String, 1));
2773 QM_TRY_UNWRAP(fullOriginMetadata.mGroup,
2774 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2775 GetUTF8String, 2));
2777 QM_TRY_UNWRAP(fullOriginMetadata.mOrigin,
2778 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2779 GetUTF8String, 3));
2781 fullOriginMetadata.mStorageOrigin = fullOriginMetadata.mOrigin;
2783 const auto extraInfo =
2784 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
2785 fullOriginMetadata.mStorageOrigin};
2787 fullOriginMetadata.mIsPrivate = false;
2789 QM_TRY_INSPECT(const auto& clientUsagesText,
2790 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2791 GetUTF8String, 4));
2793 ClientUsageArray clientUsages;
2794 QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText)));
2796 QM_TRY_INSPECT(const int64_t& usage,
2797 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 5));
2798 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
2799 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 6));
2800 QM_TRY_INSPECT(const int64_t& accessed,
2801 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 7));
2802 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
2803 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 8));
2805 QM_TRY_INSPECT(const bool& groupUpdated,
2806 MaybeUpdateGroupForOrigin(fullOriginMetadata));
2808 Unused << groupUpdated;
2810 QM_TRY_INSPECT(
2811 const bool& lastAccessTimeUpdated,
2812 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
2814 Unused << lastAccessTimeUpdated;
2816 // We don't need to update the .metadata-v2 file on disk here,
2817 // EnsureTemporaryOriginIsInitializedInternal is responsible for
2818 // doing that. We just need to use correct group and last access time
2819 // before initializing quota for the given origin. (Note that calling
2820 // LoadFullOriginMetadataWithRestore below might update the group in
2821 // the metadata file, but only as a side-effect. The actual place we
2822 // ensure consistency is in
2823 // EnsureTemporaryOriginIsInitializedInternal.)
2825 if (accessed) {
2826 QM_TRY_INSPECT(const auto& directory,
2827 GetOriginDirectory(fullOriginMetadata));
2829 QM_TRY_INSPECT(const bool& exists,
2830 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
2832 QM_TRY(OkIf(exists), Err(NS_ERROR_FILE_NOT_FOUND));
2834 QM_TRY_INSPECT(const bool& isDirectory,
2835 MOZ_TO_RESULT_INVOKE_MEMBER(directory, IsDirectory));
2837 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FILE_DESTINATION_NOT_DIR));
2839 // Calling LoadFullOriginMetadataWithRestore might update the group
2840 // in the metadata file, but only as a side-effect. The actual place
2841 // we ensure consistency is in
2842 // EnsureTemporaryOriginIsInitializedInternal.
2844 QM_TRY_INSPECT(const auto& metadata,
2845 LoadFullOriginMetadataWithRestore(directory));
2847 QM_WARNONLY_TRY(OkIf(fullOriginMetadata.mLastAccessTime ==
2848 metadata.mLastAccessTime));
2850 QM_TRY(OkIf(fullOriginMetadata.mPersisted == metadata.mPersisted),
2851 Err(NS_ERROR_FAILURE));
2853 QM_TRY(OkIf(fullOriginMetadata.mPersistenceType ==
2854 metadata.mPersistenceType),
2855 Err(NS_ERROR_FAILURE));
2857 QM_TRY(OkIf(fullOriginMetadata.mSuffix == metadata.mSuffix),
2858 Err(NS_ERROR_FAILURE));
2860 QM_TRY(OkIf(fullOriginMetadata.mGroup == metadata.mGroup),
2861 Err(NS_ERROR_FAILURE));
2863 QM_TRY(OkIf(fullOriginMetadata.mOrigin == metadata.mOrigin),
2864 Err(NS_ERROR_FAILURE));
2866 QM_TRY(OkIf(fullOriginMetadata.mStorageOrigin ==
2867 metadata.mStorageOrigin),
2868 Err(NS_ERROR_FAILURE));
2870 QM_TRY(OkIf(fullOriginMetadata.mIsPrivate == metadata.mIsPrivate),
2871 Err(NS_ERROR_FAILURE));
2873 MaybeCollectUnaccessedOrigin(fullOriginMetadata);
2875 AddTemporaryOrigin(fullOriginMetadata);
2877 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
2878 fullOriginMetadata.mPersistenceType, fullOriginMetadata,
2879 fullOriginMetadata.mLastAccessTime,
2880 fullOriginMetadata.mPersisted, directory)));
2881 } else {
2882 MaybeCollectUnaccessedOrigin(fullOriginMetadata);
2884 AddTemporaryOrigin(fullOriginMetadata);
2886 InitQuotaForOrigin(fullOriginMetadata, clientUsages, usage);
2889 return Ok{};
2890 }));
2892 autoRemoveQuota.release();
2894 return NS_OK;
2897 QM_TRY_INSPECT(
2898 const bool& loadQuotaFromCache, ([this]() -> Result<bool, nsresult> {
2899 if (mCacheUsable) {
2900 QM_TRY_INSPECT(
2901 const auto& stmt,
2902 CreateAndExecuteSingleStepStatement<
2903 SingleStepResult::ReturnNullIfNoResult>(
2904 *mStorageConnection, "SELECT valid, build_id FROM cache"_ns));
2906 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
2908 QM_TRY_INSPECT(const int32_t& valid,
2909 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
2911 if (valid) {
2912 if (!StaticPrefs::dom_quotaManager_caching_checkBuildId()) {
2913 return true;
2916 QM_TRY_INSPECT(const auto& buildId,
2917 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2918 nsAutoCString, stmt, GetUTF8String, 1));
2920 return buildId == *gBuildId;
2924 return false;
2925 }()));
2927 auto autoRemoveQuota = MakeScopeExit([&] {
2928 RemoveQuota();
2929 RemoveTemporaryOrigins();
2932 if (!loadQuotaFromCache ||
2933 !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
2934 ![&LoadQuotaFromCache] {
2935 QM_WARNONLY_TRY_UNWRAP(auto res, MOZ_TO_RESULT(LoadQuotaFromCache()));
2936 return static_cast<bool>(res);
2937 }()) {
2938 // A keeper to defer the return only in Nightly, so that the telemetry data
2939 // for whole profile can be collected.
2940 #ifdef NIGHTLY_BUILD
2941 nsresult statusKeeper = NS_OK;
2942 #endif
2944 const auto statusKeeperFunc = [&](const nsresult rv) {
2945 RECORD_IN_NIGHTLY(statusKeeper, rv);
2948 for (const PersistenceType type :
2949 kInitializableBestEffortPersistenceTypes) {
2950 if (NS_WARN_IF(IsShuttingDown())) {
2951 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
2954 QM_TRY(([&]() -> Result<Ok, nsresult> {
2955 QM_TRY(MOZ_TO_RESULT(([this, type, &MaybeCollectUnaccessedOrigin] {
2956 const auto innerFunc = [&](const auto&) -> nsresult {
2957 return InitializeRepository(type,
2958 MaybeCollectUnaccessedOrigin);
2961 return ExecuteInitialization(
2962 type == PERSISTENCE_TYPE_DEFAULT
2963 ? Initialization::DefaultRepository
2964 : Initialization::TemporaryRepository,
2965 innerFunc);
2966 }())),
2967 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
2969 return Ok{};
2970 }()));
2973 #ifdef NIGHTLY_BUILD
2974 if (NS_FAILED(statusKeeper)) {
2975 return statusKeeper;
2977 #endif
2980 autoRemoveQuota.release();
2982 const auto endTime = recordTimeDeltaHelper->End();
2984 if (StaticPrefs::dom_quotaManager_checkQuotaInfoLoadTime() &&
2985 static_cast<uint32_t>((endTime - startTime).ToMilliseconds()) >=
2986 StaticPrefs::dom_quotaManager_longQuotaInfoLoadTimeThresholdMs() &&
2987 !unaccessedOrigins.IsEmpty()) {
2988 QM_WARNONLY_TRY(ArchiveOrigins(unaccessedOrigins));
2991 return NS_OK;
2994 void QuotaManager::UnloadQuota() {
2995 AssertIsOnIOThread();
2996 MOZ_ASSERT(mStorageConnection);
2997 MOZ_ASSERT(mTemporaryStorageInitializedInternal);
2998 MOZ_ASSERT(mCacheUsable);
3000 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
3002 mozStorageTransaction transaction(
3003 mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
3005 QM_TRY(MOZ_TO_RESULT(transaction.Start()), QM_VOID);
3007 QM_TRY(MOZ_TO_RESULT(
3008 mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns)),
3009 QM_VOID);
3011 nsCOMPtr<mozIStorageStatement> insertStmt;
3014 MutexAutoLock lock(mQuotaMutex);
3016 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
3017 MOZ_ASSERT(!iter.Key().IsEmpty());
3019 GroupInfoPair* const pair = iter.UserData();
3020 MOZ_ASSERT(pair);
3022 for (const PersistenceType type : kBestEffortPersistenceTypes) {
3023 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
3024 if (!groupInfo) {
3025 continue;
3028 for (const auto& originInfo : groupInfo->mOriginInfos) {
3029 MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count());
3031 if (!originInfo->LockedDirectoryExists()) {
3032 continue;
3035 if (originInfo->mIsPrivate) {
3036 continue;
3039 if (insertStmt) {
3040 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
3041 } else {
3042 QM_TRY_UNWRAP(
3043 insertStmt,
3044 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3045 nsCOMPtr<mozIStorageStatement>, mStorageConnection,
3046 CreateStatement,
3047 "INSERT INTO origin (repository_id, suffix, group_, "
3048 "origin, client_usages, usage, last_access_time, "
3049 "accessed, persisted) "
3050 "VALUES (:repository_id, :suffix, :group_, :origin, "
3051 ":client_usages, :usage, :last_access_time, :accessed, "
3052 ":persisted)"_ns),
3053 QM_VOID);
3056 QM_TRY(MOZ_TO_RESULT(originInfo->LockedBindToStatement(insertStmt)),
3057 QM_VOID);
3059 QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()), QM_VOID);
3062 groupInfo->LockedRemoveOriginInfos();
3065 iter.Remove();
3069 QM_TRY_INSPECT(
3070 const auto& stmt,
3071 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3072 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
3073 "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns),
3074 QM_VOID);
3076 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("valid"_ns, 1)), QM_VOID);
3077 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId)),
3078 QM_VOID);
3079 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
3080 QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
3083 void QuotaManager::RemoveOriginFromCache(
3084 const OriginMetadata& aOriginMetadata) {
3085 AssertIsOnIOThread();
3086 MOZ_ASSERT(mStorageConnection);
3087 MOZ_ASSERT(!mTemporaryStorageInitializedInternal);
3089 if (!mCacheUsable) {
3090 return;
3093 mozStorageTransaction transaction(
3094 mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
3096 QM_TRY_INSPECT(
3097 const auto& stmt,
3098 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3099 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
3100 "DELETE FROM origin WHERE repository_id = :repository_id AND suffix = :suffix AND group_ = :group AND origin = :origin;"_ns),
3101 QM_VOID);
3103 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("repository_id"_ns,
3104 aOriginMetadata.mPersistenceType)),
3105 QM_VOID);
3106 QM_TRY(MOZ_TO_RESULT(
3107 stmt->BindUTF8StringByName("suffix"_ns, aOriginMetadata.mSuffix)),
3108 QM_VOID);
3109 QM_TRY(MOZ_TO_RESULT(
3110 stmt->BindUTF8StringByName("group"_ns, aOriginMetadata.mGroup)),
3111 QM_VOID);
3112 QM_TRY(MOZ_TO_RESULT(
3113 stmt->BindUTF8StringByName("origin"_ns, aOriginMetadata.mOrigin)),
3114 QM_VOID);
3115 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
3117 QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
3120 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3121 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
3122 Client::Type aClientType, nsIFile* aFile, int64_t aFileSize,
3123 int64_t* aFileSizeOut /* = nullptr */) {
3124 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3125 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
3127 if (aFileSizeOut) {
3128 *aFileSizeOut = 0;
3131 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
3132 return nullptr;
3135 QM_TRY_INSPECT(const auto& path,
3136 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aFile, GetPath),
3137 nullptr);
3139 #ifdef DEBUG
3141 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
3142 nullptr);
3144 nsAutoString clientType;
3145 QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)),
3146 nullptr);
3148 QM_TRY(MOZ_TO_RESULT(directory->Append(clientType)), nullptr);
3150 QM_TRY_INSPECT(
3151 const auto& directoryPath,
3152 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directory, GetPath),
3153 nullptr);
3155 MOZ_ASSERT(StringBeginsWith(path, directoryPath));
3157 #endif
3159 QM_TRY_INSPECT(
3160 const int64_t fileSize,
3161 ([&aFile, aFileSize]() -> Result<int64_t, nsresult> {
3162 if (aFileSize == -1) {
3163 QM_TRY_INSPECT(const bool& exists,
3164 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists));
3166 if (exists) {
3167 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize));
3170 return 0;
3173 return aFileSize;
3174 }()),
3175 nullptr);
3177 RefPtr<QuotaObject> result;
3179 MutexAutoLock lock(mQuotaMutex);
3181 GroupInfoPair* pair;
3182 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
3183 return nullptr;
3186 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3188 if (!groupInfo) {
3189 return nullptr;
3192 RefPtr<OriginInfo> originInfo =
3193 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3195 if (!originInfo) {
3196 return nullptr;
3199 // We need this extra raw pointer because we can't assign to the smart
3200 // pointer directly since QuotaObject::AddRef would try to acquire the same
3201 // mutex.
3202 const NotNull<CanonicalQuotaObject*> canonicalQuotaObject =
3203 originInfo->mCanonicalQuotaObjects.LookupOrInsertWith(path, [&] {
3204 // Create a new QuotaObject. The hashtable is not responsible to
3205 // delete the QuotaObject.
3206 return WrapNotNullUnchecked(new CanonicalQuotaObject(
3207 originInfo, aClientType, path, fileSize));
3210 // Addref the QuotaObject and move the ownership to the result. This must
3211 // happen before we unlock!
3212 result = canonicalQuotaObject->LockedAddRef();
3215 if (aFileSizeOut) {
3216 *aFileSizeOut = fileSize;
3219 // The caller becomes the owner of the QuotaObject, that is, the caller is
3220 // is responsible to delete it when the last reference is removed.
3221 return result.forget();
3224 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3225 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
3226 Client::Type aClientType, const nsAString& aPath, int64_t aFileSize,
3227 int64_t* aFileSizeOut /* = nullptr */) {
3228 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3230 if (aFileSizeOut) {
3231 *aFileSizeOut = 0;
3234 QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath), nullptr);
3236 return GetQuotaObject(aPersistenceType, aOriginMetadata, aClientType, file,
3237 aFileSize, aFileSizeOut);
3240 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3241 const int64_t aDirectoryLockId, const nsAString& aPath) {
3242 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3244 Maybe<MutexAutoLock> lock;
3246 // See the comment for mDirectoryLockIdTable in QuotaManager.h
3247 if (!IsOnBackgroundThread()) {
3248 lock.emplace(mQuotaMutex);
3251 if (auto maybeDirectoryLock =
3252 mDirectoryLockIdTable.MaybeGet(aDirectoryLockId)) {
3253 const auto& directoryLock = *maybeDirectoryLock;
3254 MOZ_DIAGNOSTIC_ASSERT(directoryLock->ShouldUpdateLockIdTable());
3256 const PersistenceType persistenceType = directoryLock->GetPersistenceType();
3257 const OriginMetadata& originMetadata = directoryLock->OriginMetadata();
3258 const Client::Type clientType = directoryLock->ClientType();
3260 lock.reset();
3262 return GetQuotaObject(persistenceType, originMetadata, clientType, aPath);
3265 MOZ_ASSERT(aDirectoryLockId == -1);
3266 return nullptr;
3269 Nullable<bool> QuotaManager::OriginPersisted(
3270 const OriginMetadata& aOriginMetadata) {
3271 AssertIsOnIOThread();
3273 MutexAutoLock lock(mQuotaMutex);
3275 RefPtr<OriginInfo> originInfo =
3276 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
3277 if (originInfo) {
3278 return Nullable<bool>(originInfo->LockedPersisted());
3281 return Nullable<bool>();
3284 void QuotaManager::PersistOrigin(const OriginMetadata& aOriginMetadata) {
3285 AssertIsOnIOThread();
3287 MutexAutoLock lock(mQuotaMutex);
3289 RefPtr<OriginInfo> originInfo =
3290 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
3291 if (originInfo && !originInfo->LockedPersisted()) {
3292 originInfo->LockedPersist();
3296 void QuotaManager::AbortOperationsForLocks(
3297 const DirectoryLockIdTableArray& aLockIds) {
3298 for (Client::Type type : AllClientTypes()) {
3299 if (aLockIds[type].Filled()) {
3300 (*mClients)[type]->AbortOperationsForLocks(aLockIds[type]);
3305 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) {
3306 AssertIsOnOwningThread();
3308 for (const RefPtr<Client>& client : *mClients) {
3309 client->AbortOperationsForProcess(aContentParentId);
3313 Result<nsCOMPtr<nsIFile>, nsresult> QuotaManager::GetOriginDirectory(
3314 const OriginMetadata& aOriginMetadata) const {
3315 QM_TRY_UNWRAP(
3316 auto directory,
3317 QM_NewLocalFile(GetStoragePath(aOriginMetadata.mPersistenceType)));
3319 QM_TRY(MOZ_TO_RESULT(directory->Append(
3320 MakeSanitizedOriginString(aOriginMetadata.mStorageOrigin))));
3322 return directory;
3325 Result<bool, nsresult> QuotaManager::DoesOriginDirectoryExist(
3326 const OriginMetadata& aOriginMetadata) const {
3327 AssertIsOnIOThread();
3329 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata));
3331 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
3334 Result<nsCOMPtr<nsIFile>, nsresult>
3335 QuotaManager::GetOrCreateTemporaryOriginDirectory(
3336 const OriginMetadata& aOriginMetadata) {
3337 AssertIsOnIOThread();
3338 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
3339 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
3340 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal());
3341 MOZ_ASSERT(IsTemporaryGroupInitializedInternal(aOriginMetadata));
3342 MOZ_ASSERT(IsTemporaryOriginInitializedInternal(aOriginMetadata));
3344 ScopedLogExtraInfo scope{
3345 ScopedLogExtraInfo::kTagContextTainted,
3346 "dom::quota::QuotaManager::GetOrCreateTemporaryOriginDirectory"_ns};
3348 // XXX Temporary band-aid fix until the root cause of uninitialized origins
3349 // after obtaining a client directory lock via OpenClientDirectory is
3350 // identified.
3351 QM_TRY(
3352 // Expression.
3353 MOZ_TO_RESULT(IsTemporaryOriginInitializedInternal(aOriginMetadata))
3354 .mapErr([](const nsresult rv) { return NS_ERROR_NOT_INITIALIZED; }),
3355 // Custom return value.
3356 QM_PROPAGATE,
3357 // Cleanup function.
3358 ([this, aOriginMetadata](const nsresult) {
3359 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(
3360 NS_NewRunnableFunction(
3361 "QuotaManager::GetOrCreateTemporaryOriginDirectory",
3362 [aOriginMetadata]() {
3363 QuotaManager* quotaManager = QuotaManager::Get();
3364 MOZ_ASSERT(quotaManager);
3366 OriginMetadataArray originMetadataArray;
3367 originMetadataArray.AppendElement(aOriginMetadata);
3369 quotaManager->NoteUninitializedOrigins(originMetadataArray);
3371 NS_DISPATCH_NORMAL));
3372 }));
3374 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
3376 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
3378 if (created) {
3379 // A new origin directory has been created.
3381 // We have a temporary origin which has been initialized without ensuring
3382 // respective origin directory. So OriginInfo already exists and it needs
3383 // to be updated because the origin directory has been just created.
3385 auto [timestamp, persisted] =
3386 WithOriginInfo(aOriginMetadata, [](const auto& originInfo) {
3387 const int64_t timestamp = originInfo->LockedAccessTime();
3388 const bool persisted = originInfo->LockedPersisted();
3390 originInfo->LockedDirectoryCreated();
3392 return std::make_pair(timestamp, persisted);
3395 // Usually, infallible operations are placed after fallible ones. However,
3396 // since we lack atomic support for creating the origin directory along
3397 // with its metadata, we need to add the origin to cached origins right
3398 // after directory creation.
3399 AddTemporaryOrigin(
3400 FullOriginMetadata{aOriginMetadata, persisted, timestamp});
3402 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
3403 persisted, aOriginMetadata)));
3406 return std::move(directory);
3409 Result<Ok, nsresult> QuotaManager::EnsureTemporaryOriginDirectoryCreated(
3410 const OriginMetadata& aOriginMetadata) {
3411 QM_TRY_RETURN(GetOrCreateTemporaryOriginDirectory(aOriginMetadata)
3412 .map([](const auto& res) { return Ok{}; }));
3415 // static
3416 nsresult QuotaManager::CreateDirectoryMetadata(
3417 nsIFile& aDirectory, int64_t aTimestamp,
3418 const OriginMetadata& aOriginMetadata) {
3419 AssertIsOnIOThread();
3421 StorageOriginAttributes groupAttributes;
3423 nsCString groupNoSuffix;
3424 QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup,
3425 groupNoSuffix)),
3426 NS_ERROR_FAILURE);
3428 nsCString groupPrefix;
3429 GetJarPrefix(groupAttributes.InIsolatedMozBrowser(), groupPrefix);
3431 nsCString group = groupPrefix + groupNoSuffix;
3433 StorageOriginAttributes originAttributes;
3435 nsCString originNoSuffix;
3436 QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
3437 originNoSuffix)),
3438 NS_ERROR_FAILURE);
3440 nsCString originPrefix;
3441 GetJarPrefix(originAttributes.InIsolatedMozBrowser(), originPrefix);
3443 nsCString origin = originPrefix + originNoSuffix;
3445 MOZ_ASSERT(groupPrefix == originPrefix);
3447 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3448 nsCOMPtr<nsIFile>, aDirectory, Clone));
3450 QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_TMP_FILE_NAME))));
3452 QM_TRY_INSPECT(const auto& stream,
3453 GetBinaryOutputStream(*file, FileFlag::Truncate));
3454 MOZ_ASSERT(stream);
3456 QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
3458 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(group.get())));
3460 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(origin.get())));
3462 // Currently unused (used to be isApp).
3463 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
3465 QM_TRY(MOZ_TO_RESULT(stream->Flush()));
3467 QM_TRY(MOZ_TO_RESULT(stream->Close()));
3469 QM_TRY(MOZ_TO_RESULT(
3470 file->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME))));
3472 return NS_OK;
3475 // static
3476 nsresult QuotaManager::CreateDirectoryMetadata2(
3477 nsIFile& aDirectory, int64_t aTimestamp, bool aPersisted,
3478 const OriginMetadata& aOriginMetadata) {
3479 AssertIsOnIOThread();
3481 QM_TRY(ArtificialFailure(
3482 nsIQuotaArtificialFailure::CATEGORY_CREATE_DIRECTORY_METADATA2));
3484 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3485 nsCOMPtr<nsIFile>, aDirectory, Clone));
3487 QM_TRY(
3488 MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME))));
3490 QM_TRY_INSPECT(const auto& stream,
3491 GetBinaryOutputStream(*file, FileFlag::Truncate));
3492 MOZ_ASSERT(stream);
3494 QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
3496 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aPersisted)));
3498 // Reserved data 1
3499 QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
3501 // Reserved data 2
3502 QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
3504 // Currently unused (used to be suffix).
3505 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ("")));
3507 // Currently unused (used to be group).
3508 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ("")));
3510 QM_TRY(MOZ_TO_RESULT(
3511 stream->WriteStringZ(aOriginMetadata.mStorageOrigin.get())));
3513 // Currently used for isPrivate (used to be used for isApp).
3514 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aOriginMetadata.mIsPrivate)));
3516 QM_TRY(MOZ_TO_RESULT(stream->Flush()));
3518 QM_TRY(MOZ_TO_RESULT(stream->Close()));
3520 QM_TRY(MOZ_TO_RESULT(
3521 file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME))));
3523 return NS_OK;
3526 nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory) {
3527 AssertIsOnIOThread();
3528 MOZ_ASSERT(aDirectory);
3529 MOZ_ASSERT(mStorageConnection);
3531 RefPtr<RestoreDirectoryMetadata2Helper> helper =
3532 new RestoreDirectoryMetadata2Helper(aDirectory);
3534 QM_TRY(MOZ_TO_RESULT(helper->Init()));
3536 QM_TRY(MOZ_TO_RESULT(helper->RestoreMetadata2File()));
3538 return NS_OK;
3541 Result<FullOriginMetadata, nsresult> QuotaManager::LoadFullOriginMetadata(
3542 nsIFile* aDirectory, PersistenceType aPersistenceType) {
3543 MOZ_ASSERT(!NS_IsMainThread());
3544 MOZ_ASSERT(aDirectory);
3545 MOZ_ASSERT(mStorageConnection);
3547 QM_TRY_INSPECT(const auto& binaryStream,
3548 GetBinaryInputStream(*aDirectory,
3549 nsLiteralString(METADATA_V2_FILE_NAME)));
3551 FullOriginMetadata fullOriginMetadata;
3553 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
3554 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
3556 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
3557 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
3559 QM_TRY_INSPECT(const bool& reservedData1,
3560 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
3561 Unused << reservedData1;
3563 // XXX Use for the persistence type.
3564 QM_TRY_INSPECT(const bool& reservedData2,
3565 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
3566 Unused << reservedData2;
3568 fullOriginMetadata.mPersistenceType = aPersistenceType;
3570 QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3571 nsCString, binaryStream, ReadCString));
3572 Unused << suffix;
3574 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3575 nsCString, binaryStream, ReadCString));
3576 Unused << group;
3578 QM_TRY_UNWRAP(
3579 fullOriginMetadata.mStorageOrigin,
3580 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
3582 // Currently used for isPrivate (used to be used for isApp).
3583 QM_TRY_UNWRAP(fullOriginMetadata.mIsPrivate,
3584 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
3586 QM_TRY(MOZ_TO_RESULT(binaryStream->Close()));
3588 auto principal =
3589 [&storageOrigin =
3590 fullOriginMetadata.mStorageOrigin]() -> nsCOMPtr<nsIPrincipal> {
3591 if (storageOrigin.EqualsLiteral(kChromeOrigin)) {
3592 return SystemPrincipal::Get();
3594 return BasePrincipal::CreateContentPrincipal(storageOrigin);
3595 }();
3596 QM_TRY(MOZ_TO_RESULT(principal));
3598 PrincipalInfo principalInfo;
3599 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
3601 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
3602 Err(NS_ERROR_MALFORMED_URI));
3604 QM_TRY_UNWRAP(auto principalMetadata,
3605 GetInfoFromValidatedPrincipalInfo(*this, principalInfo));
3607 fullOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix);
3608 fullOriginMetadata.mGroup = std::move(principalMetadata.mGroup);
3609 fullOriginMetadata.mOrigin = std::move(principalMetadata.mOrigin);
3611 QM_TRY_INSPECT(const bool& groupUpdated,
3612 MaybeUpdateGroupForOrigin(fullOriginMetadata));
3614 // A workaround for a bug in GetLastModifiedTime implementation which should
3615 // have returned the current time instead of INT64_MIN when there were no
3616 // suitable files for getting last modified time.
3617 QM_TRY_INSPECT(const bool& lastAccessTimeUpdated,
3618 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
3620 if (groupUpdated || lastAccessTimeUpdated) {
3621 // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
3622 // I/O.
3623 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
3624 *aDirectory, fullOriginMetadata.mLastAccessTime,
3625 fullOriginMetadata.mPersisted, fullOriginMetadata)));
3628 return fullOriginMetadata;
3631 Result<FullOriginMetadata, nsresult>
3632 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile* aDirectory) {
3633 // XXX Once the persistence type is stored in the metadata file, this block
3634 // for getting the persistence type from the parent directory name can be
3635 // removed.
3636 nsCOMPtr<nsIFile> parentDir;
3637 QM_TRY(MOZ_TO_RESULT(aDirectory->GetParent(getter_AddRefs(parentDir))));
3639 const auto maybePersistenceType =
3640 PersistenceTypeFromFile(*parentDir, fallible);
3641 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
3643 const auto& persistenceType = maybePersistenceType.value();
3645 QM_TRY_RETURN(QM_OR_ELSE_WARN(
3646 // Expression.
3647 LoadFullOriginMetadata(aDirectory, persistenceType),
3648 // Fallback.
3649 ([&aDirectory, &persistenceType,
3650 this](const nsresult rv) -> Result<FullOriginMetadata, nsresult> {
3651 QM_TRY(MOZ_TO_RESULT(RestoreDirectoryMetadata2(aDirectory)));
3653 QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory, persistenceType));
3654 })));
3657 Result<OriginMetadata, nsresult> QuotaManager::GetOriginMetadata(
3658 nsIFile* aDirectory) {
3659 MOZ_ASSERT(aDirectory);
3660 MOZ_ASSERT(mStorageConnection);
3662 QM_TRY_INSPECT(
3663 const auto& leafName,
3664 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aDirectory, GetLeafName));
3666 // XXX Consider using QuotaManager::ParseOrigin here.
3667 nsCString spec;
3668 OriginAttributes attrs;
3669 nsCString originalSuffix;
3670 OriginParser::ResultType result = OriginParser::ParseOrigin(
3671 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
3672 QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
3674 QM_TRY_INSPECT(
3675 const auto& principal,
3676 ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
3677 if (spec.EqualsLiteral(kChromeOrigin)) {
3678 return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
3681 nsCOMPtr<nsIURI> uri;
3682 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
3684 return nsCOMPtr<nsIPrincipal>(
3685 BasePrincipal::CreateContentPrincipal(uri, attrs));
3686 }()));
3687 QM_TRY(MOZ_TO_RESULT(principal));
3689 PrincipalInfo principalInfo;
3690 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
3692 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
3693 Err(NS_ERROR_MALFORMED_URI));
3695 QM_TRY_UNWRAP(auto principalMetadata,
3696 GetInfoFromValidatedPrincipalInfo(*this, principalInfo));
3698 QM_TRY_INSPECT(const auto& parentDirectory,
3699 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<nsIFile>,
3700 aDirectory, GetParent));
3702 const auto maybePersistenceType =
3703 PersistenceTypeFromFile(*parentDirectory, fallible);
3704 QM_TRY(MOZ_TO_RESULT(maybePersistenceType.isSome()));
3706 return OriginMetadata{std::move(principalMetadata),
3707 maybePersistenceType.value()};
3710 Result<Ok, nsresult> QuotaManager::RemoveOriginDirectory(nsIFile& aDirectory) {
3711 AssertIsOnIOThread();
3713 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown)) {
3714 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.Remove(true)));
3717 QM_TRY_INSPECT(const auto& toBeRemovedStorageDir,
3718 QM_NewLocalFile(*mToBeRemovedStoragePath));
3720 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*toBeRemovedStorageDir));
3722 (void)created;
3724 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.MoveTo(
3725 toBeRemovedStorageDir, NSID_TrimBracketsUTF16(nsID::GenerateUUID()))));
3728 Result<bool, nsresult> QuotaManager::DoesClientDirectoryExist(
3729 const ClientMetadata& aClientMetadata) const {
3730 AssertIsOnIOThread();
3732 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aClientMetadata));
3734 QM_TRY(MOZ_TO_RESULT(
3735 directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
3737 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
3740 template <typename OriginFunc>
3741 nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType,
3742 OriginFunc&& aOriginFunc) {
3743 AssertIsOnIOThread();
3744 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_PERSISTENT ||
3745 aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
3746 aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
3748 QM_TRY_INSPECT(const auto& directory,
3749 QM_NewLocalFile(GetStoragePath(aPersistenceType)));
3751 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*directory));
3753 Unused << created;
3755 uint64_t iterations = 0;
3757 // A keeper to defer the return only in Nightly, so that the telemetry data
3758 // for whole profile can be collected
3759 #ifdef NIGHTLY_BUILD
3760 nsresult statusKeeper = NS_OK;
3761 #endif
3763 const auto statusKeeperFunc = [&](const nsresult rv) {
3764 RECORD_IN_NIGHTLY(statusKeeper, rv);
3767 struct RenameAndInitInfo {
3768 nsCOMPtr<nsIFile> mOriginDirectory;
3769 FullOriginMetadata mFullOriginMetadata;
3770 int64_t mTimestamp;
3771 bool mPersisted;
3773 nsTArray<RenameAndInitInfo> renameAndInitInfos;
3775 QM_TRY(([&]() -> Result<Ok, nsresult> {
3776 QM_TRY(
3777 CollectEachFile(
3778 *directory,
3779 [&](nsCOMPtr<nsIFile>&& childDirectory) -> Result<Ok, nsresult> {
3780 if (NS_WARN_IF(IsShuttingDown())) {
3781 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
3784 QM_TRY(
3785 ([this, &iterations, &childDirectory, &renameAndInitInfos,
3786 aPersistenceType, &aOriginFunc]() -> Result<Ok, nsresult> {
3787 QM_TRY_INSPECT(
3788 const auto& leafName,
3789 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3790 nsAutoString, childDirectory, GetLeafName));
3792 QM_TRY_INSPECT(const auto& dirEntryKind,
3793 GetDirEntryKind(*childDirectory));
3795 switch (dirEntryKind) {
3796 case nsIFileKind::ExistsAsDirectory: {
3797 QM_TRY_UNWRAP(
3798 auto maybeMetadata,
3799 QM_OR_ELSE_WARN_IF(
3800 // Expression
3801 LoadFullOriginMetadataWithRestore(
3802 childDirectory)
3803 .map([](auto metadata)
3804 -> Maybe<FullOriginMetadata> {
3805 return Some(std::move(metadata));
3807 // Predicate.
3808 IsSpecificError<NS_ERROR_MALFORMED_URI>,
3809 // Fallback.
3810 ErrToDefaultOk<Maybe<FullOriginMetadata>>));
3812 if (!maybeMetadata) {
3813 // Unknown directories during initialization are
3814 // allowed. Just warn if we find them.
3815 UNKNOWN_FILE_WARNING(leafName);
3816 break;
3819 auto metadata = maybeMetadata.extract();
3821 MOZ_ASSERT(metadata.mPersistenceType ==
3822 aPersistenceType);
3824 const auto extraInfo = ScopedLogExtraInfo{
3825 ScopedLogExtraInfo::kTagStorageOriginTainted,
3826 metadata.mStorageOrigin};
3828 // FIXME(tt): The check for origin name consistency can
3829 // be removed once we have an upgrade to traverse origin
3830 // directories and check through the directory metadata
3831 // files.
3832 const auto originSanitized =
3833 MakeSanitizedOriginCString(metadata.mOrigin);
3835 NS_ConvertUTF16toUTF8 utf8LeafName(leafName);
3836 if (!originSanitized.Equals(utf8LeafName)) {
3837 QM_WARNING(
3838 "The name of the origin directory (%s) doesn't "
3839 "match the sanitized origin string (%s) in the "
3840 "metadata file!",
3841 utf8LeafName.get(), originSanitized.get());
3843 // If it's the known case, we try to restore the
3844 // origin directory name if it's possible.
3845 if (originSanitized.Equals(utf8LeafName + "."_ns)) {
3846 const int64_t lastAccessTime =
3847 metadata.mLastAccessTime;
3848 const bool persisted = metadata.mPersisted;
3849 renameAndInitInfos.AppendElement(RenameAndInitInfo{
3850 std::move(childDirectory), std::move(metadata),
3851 lastAccessTime, persisted});
3852 break;
3855 // XXXtt: Try to restore the unknown cases base on the
3856 // content for their metadata files. Note that if the
3857 // restore fails, QM should maintain a list and ensure
3858 // they won't be accessed after initialization.
3861 if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
3862 std::forward<OriginFunc>(aOriginFunc)(metadata);
3864 AddTemporaryOrigin(metadata);
3867 QM_TRY(QM_OR_ELSE_WARN_IF(
3868 // Expression.
3869 MOZ_TO_RESULT(InitializeOrigin(
3870 aPersistenceType, metadata,
3871 metadata.mLastAccessTime, metadata.mPersisted,
3872 childDirectory)),
3873 // Predicate.
3874 IsDatabaseCorruptionError,
3875 // Fallback.
3876 ([&childDirectory, &metadata,
3877 this](const nsresult rv) -> Result<Ok, nsresult> {
3878 // If the origin can't be initialized due to
3879 // corruption, this is a permanent
3880 // condition, and we need to remove all data
3881 // for the origin on disk.
3883 QM_TRY(
3884 MOZ_TO_RESULT(childDirectory->Remove(true)));
3886 RemoveTemporaryOrigin(metadata);
3888 return Ok{};
3889 })));
3891 break;
3894 case nsIFileKind::ExistsAsFile:
3895 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
3896 break;
3899 // Unknown files during initialization are now allowed.
3900 // Just warn if we find them.
3901 UNKNOWN_FILE_WARNING(leafName);
3902 break;
3904 case nsIFileKind::DoesNotExist:
3905 // Ignore files that got removed externally while
3906 // iterating.
3907 break;
3910 iterations++;
3912 return Ok{};
3913 }()),
3914 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3916 return Ok{};
3918 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3920 return Ok{};
3921 }()));
3923 for (auto& info : renameAndInitInfos) {
3924 QM_TRY(([&]() -> Result<Ok, nsresult> {
3925 QM_TRY(
3926 ([&directory, &info, this, aPersistenceType,
3927 &aOriginFunc]() -> Result<Ok, nsresult> {
3928 const auto extraInfo =
3929 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
3930 info.mFullOriginMetadata.mStorageOrigin};
3932 const auto originDirName =
3933 MakeSanitizedOriginString(info.mFullOriginMetadata.mOrigin);
3935 // Check if targetDirectory exist.
3936 QM_TRY_INSPECT(const auto& targetDirectory,
3937 CloneFileAndAppend(*directory, originDirName));
3939 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(
3940 targetDirectory, Exists));
3942 if (exists) {
3943 QM_TRY(MOZ_TO_RESULT(info.mOriginDirectory->Remove(true)));
3945 return Ok{};
3948 QM_TRY(MOZ_TO_RESULT(
3949 info.mOriginDirectory->RenameTo(nullptr, originDirName)));
3951 if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
3952 std::forward<OriginFunc>(aOriginFunc)(info.mFullOriginMetadata);
3954 AddTemporaryOrigin(info.mFullOriginMetadata);
3957 // XXX We don't check corruption here ?
3958 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
3959 aPersistenceType, info.mFullOriginMetadata, info.mTimestamp,
3960 info.mPersisted, targetDirectory)));
3962 return Ok{};
3963 }()),
3964 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3966 return Ok{};
3967 }()));
3970 #ifdef NIGHTLY_BUILD
3971 if (NS_FAILED(statusKeeper)) {
3972 return statusKeeper;
3974 #endif
3976 glean::quotamanager_initialize_repository::number_of_iterations
3977 .Get(PersistenceTypeToString(aPersistenceType))
3978 .AccumulateSingleSample(iterations);
3980 return NS_OK;
3983 nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
3984 const OriginMetadata& aOriginMetadata,
3985 int64_t aAccessTime, bool aPersisted,
3986 nsIFile* aDirectory, bool aForGroup) {
3987 QM_LOG(("Starting origin initialization for: %s",
3988 aOriginMetadata.mOrigin.get()));
3990 AssertIsOnIOThread();
3992 QM_TRY(
3993 ArtificialFailure(nsIQuotaArtificialFailure::CATEGORY_INITIALIZE_ORIGIN));
3995 // The ScopedLogExtraInfo is not set here on purpose, so the callers can
3996 // decide if they want to set it. The extra info can be set sooner this way
3997 // as well.
3999 const bool trackQuota = aPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
4001 if (trackQuota && !aForGroup &&
4002 QuotaPrefs::LazyOriginInitializationEnabled()) {
4003 QM_LOG(("Skipping origin initialization for: %s (it will be done lazily)",
4004 aOriginMetadata.mOrigin.get()));
4006 return NS_OK;
4009 // We need to initialize directories of all clients if they exists and also
4010 // get the total usage to initialize the quota.
4012 ClientUsageArray clientUsages;
4014 // A keeper to defer the return only in Nightly, so that the telemetry data
4015 // for whole profile can be collected
4016 #ifdef NIGHTLY_BUILD
4017 nsresult statusKeeper = NS_OK;
4018 #endif
4020 QM_TRY(([&, statusKeeperFunc = [&](const nsresult rv) {
4021 RECORD_IN_NIGHTLY(statusKeeper, rv);
4022 }]() -> Result<Ok, nsresult> {
4023 QM_TRY(
4024 CollectEachFile(
4025 *aDirectory,
4026 [&](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
4027 if (NS_WARN_IF(IsShuttingDown())) {
4028 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
4031 QM_TRY(
4032 ([this, &file, trackQuota, aPersistenceType, &aOriginMetadata,
4033 &clientUsages]() -> Result<Ok, nsresult> {
4034 QM_TRY_INSPECT(const auto& leafName,
4035 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4036 nsAutoString, file, GetLeafName));
4038 QM_TRY_INSPECT(const auto& dirEntryKind,
4039 GetDirEntryKind(*file));
4041 switch (dirEntryKind) {
4042 case nsIFileKind::ExistsAsDirectory: {
4043 Client::Type clientType;
4044 const bool ok = Client::TypeFromText(
4045 leafName, clientType, fallible);
4046 if (!ok) {
4047 // Unknown directories during initialization are now
4048 // allowed. Just warn if we find them.
4049 UNKNOWN_FILE_WARNING(leafName);
4050 break;
4053 if (trackQuota) {
4054 QM_TRY_INSPECT(
4055 const auto& usageInfo,
4056 (*mClients)[clientType]->InitOrigin(
4057 aPersistenceType, aOriginMetadata,
4058 /* aCanceled */ Atomic<bool>(false)));
4060 MOZ_ASSERT(!clientUsages[clientType]);
4062 if (usageInfo.TotalUsage()) {
4063 // XXX(Bug 1683863) Until we identify the root cause
4064 // of seemingly converted-from-negative usage
4065 // values, we will just treat them as unset here,
4066 // but log a warning to the browser console.
4067 if (static_cast<int64_t>(*usageInfo.TotalUsage()) >=
4068 0) {
4069 clientUsages[clientType] = usageInfo.TotalUsage();
4070 } else {
4071 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
4072 const nsCOMPtr<nsIConsoleService> console =
4073 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
4074 if (console) {
4075 console->LogStringMessage(
4076 nsString(
4077 u"QuotaManager warning: client "_ns +
4078 leafName +
4079 u" reported negative usage for group "_ns +
4080 NS_ConvertUTF8toUTF16(
4081 aOriginMetadata.mGroup) +
4082 u", origin "_ns +
4083 NS_ConvertUTF8toUTF16(
4084 aOriginMetadata.mOrigin))
4085 .get());
4087 #endif
4090 } else {
4091 QM_TRY(MOZ_TO_RESULT(
4092 (*mClients)[clientType]
4093 ->InitOriginWithoutTracking(
4094 aPersistenceType, aOriginMetadata,
4095 /* aCanceled */ Atomic<bool>(false))));
4098 break;
4101 case nsIFileKind::ExistsAsFile:
4102 if (IsOriginMetadata(leafName)) {
4103 break;
4106 if (IsTempMetadata(leafName)) {
4107 QM_TRY(MOZ_TO_RESULT(
4108 file->Remove(/* recursive */ false)));
4110 break;
4113 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
4114 break;
4117 // Unknown files during initialization are now allowed.
4118 // Just warn if we find them.
4119 UNKNOWN_FILE_WARNING(leafName);
4120 // Bug 1595448 will handle the case for unknown files
4121 // like idb, cache, or ls.
4122 break;
4124 case nsIFileKind::DoesNotExist:
4125 // Ignore files that got removed externally while
4126 // iterating.
4127 break;
4130 return Ok{};
4131 }()),
4132 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4134 return Ok{};
4136 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4138 return Ok{};
4139 }()));
4141 #ifdef NIGHTLY_BUILD
4142 if (NS_FAILED(statusKeeper)) {
4143 return statusKeeper;
4145 #endif
4147 if (trackQuota) {
4148 const auto usage = std::accumulate(
4149 clientUsages.cbegin(), clientUsages.cend(), CheckedUint64(0),
4150 [](CheckedUint64 value, const Maybe<uint64_t>& clientUsage) {
4151 return value + clientUsage.valueOr(0);
4154 // XXX Should we log more information, i.e. the whole clientUsages array, in
4155 // case usage is not valid?
4157 QM_TRY(OkIf(usage.isValid()), NS_ERROR_FAILURE);
4159 InitQuotaForOrigin(
4160 FullOriginMetadata{aOriginMetadata, aPersisted, aAccessTime},
4161 clientUsages, usage.value());
4164 SleepIfEnabled(
4165 StaticPrefs::dom_quotaManager_originInitialization_pauseOnIOThreadMs());
4167 QM_LOG(
4168 ("Ending origin initialization for: %s", aOriginMetadata.mOrigin.get()));
4170 return NS_OK;
4173 nsresult
4174 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
4175 nsIFile* aIndexedDBDir) {
4176 AssertIsOnIOThread();
4177 MOZ_ASSERT(aIndexedDBDir);
4179 const auto innerFunc = [this, &aIndexedDBDir](const auto&) -> nsresult {
4180 bool isDirectory;
4181 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->IsDirectory(&isDirectory)));
4183 if (!isDirectory) {
4184 NS_WARNING("indexedDB entry is not a directory!");
4185 return NS_OK;
4188 auto persistentStorageDirOrErr = QM_NewLocalFile(*mStoragePath);
4189 if (NS_WARN_IF(persistentStorageDirOrErr.isErr())) {
4190 return persistentStorageDirOrErr.unwrapErr();
4193 nsCOMPtr<nsIFile> persistentStorageDir = persistentStorageDirOrErr.unwrap();
4195 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
4196 nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
4198 bool exists;
4199 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Exists(&exists)));
4201 if (exists) {
4202 QM_WARNING("Deleting old <profile>/indexedDB directory!");
4204 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->Remove(/* aRecursive */ true)));
4206 return NS_OK;
4209 nsCOMPtr<nsIFile> storageDir;
4210 QM_TRY(MOZ_TO_RESULT(
4211 persistentStorageDir->GetParent(getter_AddRefs(storageDir))));
4213 // MoveTo() is atomic if the move happens on the same volume which should
4214 // be our case, so even if we crash in the middle of the operation nothing
4215 // breaks next time we try to initialize.
4216 // However there's a theoretical possibility that the indexedDB directory
4217 // is on different volume, but it should be rare enough that we don't have
4218 // to worry about it.
4219 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->MoveTo(
4220 storageDir, nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
4222 return NS_OK;
4225 return ExecuteInitialization(Initialization::UpgradeFromIndexedDBDirectory,
4226 innerFunc);
4229 nsresult
4230 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
4231 nsIFile* aPersistentStorageDir) {
4232 AssertIsOnIOThread();
4233 MOZ_ASSERT(aPersistentStorageDir);
4235 const auto innerFunc = [this,
4236 &aPersistentStorageDir](const auto&) -> nsresult {
4237 QM_TRY_INSPECT(
4238 const bool& isDirectory,
4239 MOZ_TO_RESULT_INVOKE_MEMBER(aPersistentStorageDir, IsDirectory));
4241 if (!isDirectory) {
4242 NS_WARNING("persistent entry is not a directory!");
4243 return NS_OK;
4247 QM_TRY_INSPECT(const auto& defaultStorageDir,
4248 QM_NewLocalFile(*mDefaultStoragePath));
4250 QM_TRY_INSPECT(const bool& exists,
4251 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
4253 if (exists) {
4254 QM_WARNING("Deleting old <profile>/storage/persistent directory!");
4256 QM_TRY(MOZ_TO_RESULT(
4257 aPersistentStorageDir->Remove(/* aRecursive */ true)));
4259 return NS_OK;
4264 // Create real metadata files for origin directories in persistent
4265 // storage.
4266 auto helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
4267 aPersistentStorageDir);
4269 QM_TRY(MOZ_TO_RESULT(helper->Init()));
4271 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
4273 // Upgrade metadata files for origin directories in temporary storage.
4274 QM_TRY_INSPECT(const auto& temporaryStorageDir,
4275 QM_NewLocalFile(*mTemporaryStoragePath));
4277 QM_TRY_INSPECT(const bool& exists,
4278 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, Exists));
4280 if (exists) {
4281 QM_TRY_INSPECT(
4282 const bool& isDirectory,
4283 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, IsDirectory));
4285 if (!isDirectory) {
4286 NS_WARNING("temporary entry is not a directory!");
4287 return NS_OK;
4290 helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
4291 temporaryStorageDir);
4293 QM_TRY(MOZ_TO_RESULT(helper->Init()));
4295 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
4299 // And finally rename persistent to default.
4300 QM_TRY(MOZ_TO_RESULT(aPersistentStorageDir->RenameTo(
4301 nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME))));
4303 return NS_OK;
4306 return ExecuteInitialization(
4307 Initialization::UpgradeFromPersistentStorageDirectory, innerFunc);
4310 template <typename Helper>
4311 nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion,
4312 const int32_t aNewVersion,
4313 mozIStorageConnection* aConnection) {
4314 AssertIsOnIOThread();
4315 MOZ_ASSERT(aNewVersion > aOldVersion);
4316 MOZ_ASSERT(aNewVersion <= kStorageVersion);
4317 MOZ_ASSERT(aConnection);
4319 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
4320 QM_TRY_UNWRAP(auto directory,
4321 QM_NewLocalFile(GetStoragePath(persistenceType)));
4323 QM_TRY_INSPECT(const bool& exists,
4324 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
4326 if (!exists) {
4327 continue;
4330 RefPtr<UpgradeStorageHelperBase> helper = new Helper(directory);
4332 QM_TRY(MOZ_TO_RESULT(helper->Init()));
4334 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
4337 #ifdef DEBUG
4339 QM_TRY_INSPECT(const int32_t& storageVersion,
4340 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4342 MOZ_ASSERT(storageVersion == aOldVersion);
4344 #endif
4346 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(aNewVersion)));
4348 return NS_OK;
4351 nsresult QuotaManager::UpgradeStorageFrom0_0To1_0(
4352 mozIStorageConnection* aConnection) {
4353 AssertIsOnIOThread();
4354 MOZ_ASSERT(aConnection);
4356 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4357 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>(
4358 0, MakeStorageVersion(1, 0), aConnection)));
4360 return NS_OK;
4363 return ExecuteInitialization(Initialization::UpgradeStorageFrom0_0To1_0,
4364 innerFunc);
4367 nsresult QuotaManager::UpgradeStorageFrom1_0To2_0(
4368 mozIStorageConnection* aConnection) {
4369 AssertIsOnIOThread();
4370 MOZ_ASSERT(aConnection);
4372 // The upgrade consists of a number of logically distinct bugs that
4373 // intentionally got fixed at the same time to trigger just one major
4374 // version bump.
4377 // Morgue directory cleanup
4378 // [Feature/Bug]:
4379 // The original bug that added "on demand" morgue cleanup is 1165119.
4381 // [Mutations]:
4382 // Morgue directories are removed from all origin directories during the
4383 // upgrade process. Origin initialization and usage calculation doesn't try
4384 // to remove morgue directories anymore.
4386 // [Downgrade-incompatible changes]:
4387 // Morgue directories can reappear if user runs an already upgraded profile
4388 // in an older version of Firefox. Morgue directories then prevent current
4389 // Firefox from initializing and using the storage.
4392 // App data removal
4393 // [Feature/Bug]:
4394 // The bug that removes isApp flags is 1311057.
4396 // [Mutations]:
4397 // Origin directories with appIds are removed during the upgrade process.
4399 // [Downgrade-incompatible changes]:
4400 // Origin directories with appIds can reappear if user runs an already
4401 // upgraded profile in an older version of Firefox. Origin directories with
4402 // appIds don't prevent current Firefox from initializing and using the
4403 // storage, but they wouldn't ever be removed again, potentially causing
4404 // problems once appId is removed from origin attributes.
4407 // Strip obsolete origin attributes
4408 // [Feature/Bug]:
4409 // The bug that strips obsolete origin attributes is 1314361.
4411 // [Mutations]:
4412 // Origin directories with obsolete origin attributes are renamed and their
4413 // metadata files are updated during the upgrade process.
4415 // [Downgrade-incompatible changes]:
4416 // Origin directories with obsolete origin attributes can reappear if user
4417 // runs an already upgraded profile in an older version of Firefox. Origin
4418 // directories with obsolete origin attributes don't prevent current Firefox
4419 // from initializing and using the storage, but they wouldn't ever be upgraded
4420 // again, potentially causing problems in future.
4423 // File manager directory renaming (client specific)
4424 // [Feature/Bug]:
4425 // The original bug that added "on demand" file manager directory renaming is
4426 // 1056939.
4428 // [Mutations]:
4429 // All file manager directories are renamed to contain the ".files" suffix.
4431 // [Downgrade-incompatible changes]:
4432 // File manager directories with the ".files" suffix prevent older versions of
4433 // Firefox from initializing and using the storage.
4434 // File manager directories without the ".files" suffix can appear if user
4435 // runs an already upgraded profile in an older version of Firefox. File
4436 // manager directories without the ".files" suffix then prevent current
4437 // Firefox from initializing and using the storage.
4439 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4440 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>(
4441 MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection)));
4443 return NS_OK;
4446 return ExecuteInitialization(Initialization::UpgradeStorageFrom1_0To2_0,
4447 innerFunc);
4450 nsresult QuotaManager::UpgradeStorageFrom2_0To2_1(
4451 mozIStorageConnection* aConnection) {
4452 AssertIsOnIOThread();
4453 MOZ_ASSERT(aConnection);
4455 // The upgrade is mainly to create a directory padding file in DOM Cache
4456 // directory to record the overall padding size of an origin.
4458 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4459 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>(
4460 MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection)));
4462 return NS_OK;
4465 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_0To2_1,
4466 innerFunc);
4469 nsresult QuotaManager::UpgradeStorageFrom2_1To2_2(
4470 mozIStorageConnection* aConnection) {
4471 AssertIsOnIOThread();
4472 MOZ_ASSERT(aConnection);
4474 // The upgrade is mainly to clean obsolete origins in the repositoies, remove
4475 // asmjs client, and ".tmp" file in the idb folers.
4477 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4478 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_1To2_2Helper>(
4479 MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection)));
4481 return NS_OK;
4484 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_1To2_2,
4485 innerFunc);
4488 nsresult QuotaManager::UpgradeStorageFrom2_2To2_3(
4489 mozIStorageConnection* aConnection) {
4490 AssertIsOnIOThread();
4491 MOZ_ASSERT(aConnection);
4493 const auto innerFunc = [&aConnection](const auto&) -> nsresult {
4494 // Table `database`
4495 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
4496 nsLiteralCString("CREATE TABLE database"
4497 "( cache_version INTEGER NOT NULL DEFAULT 0"
4498 ");"))));
4500 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
4501 nsLiteralCString("INSERT INTO database (cache_version) "
4502 "VALUES (0)"))));
4504 #ifdef DEBUG
4506 QM_TRY_INSPECT(
4507 const int32_t& storageVersion,
4508 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4510 MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 2));
4512 #endif
4514 QM_TRY(
4515 MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeStorageVersion(2, 3))));
4517 return NS_OK;
4520 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_2To2_3,
4521 innerFunc);
4524 nsresult QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
4525 nsIFile& aLsArchiveFile) {
4526 AssertIsOnIOThread();
4527 MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
4529 QM_TRY_INSPECT(const bool& exists,
4530 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4532 if (!exists) {
4533 // If the ls archive doesn't exist then ls directories can't exist either.
4534 return NS_OK;
4537 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
4539 InvalidateQuotaCache();
4541 // Finally remove the ls archive, so we don't have to check all origin
4542 // directories next time this method is called.
4543 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
4545 return NS_OK;
4548 nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() {
4549 AssertIsOnIOThread();
4551 QM_TRY_INSPECT(const auto& defaultStorageDir,
4552 QM_NewLocalFile(*mDefaultStoragePath));
4554 QM_TRY_INSPECT(const bool& exists,
4555 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
4557 if (!exists) {
4558 return NS_OK;
4561 QM_TRY(CollectEachFile(
4562 *defaultStorageDir,
4563 [](const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
4564 #ifdef DEBUG
4566 QM_TRY_INSPECT(const bool& exists,
4567 MOZ_TO_RESULT_INVOKE_MEMBER(originDir, Exists));
4568 MOZ_ASSERT(exists);
4570 #endif
4572 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
4574 switch (dirEntryKind) {
4575 case nsIFileKind::ExistsAsDirectory: {
4576 QM_TRY_INSPECT(
4577 const auto& lsDir,
4578 CloneFileAndAppend(*originDir, NS_LITERAL_STRING_FROM_CSTRING(
4579 LS_DIRECTORY_NAME)));
4582 QM_TRY_INSPECT(const bool& exists,
4583 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, Exists));
4585 if (!exists) {
4586 return Ok{};
4591 QM_TRY_INSPECT(const bool& isDirectory,
4592 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, IsDirectory));
4594 if (!isDirectory) {
4595 QM_WARNING("ls entry is not a directory!");
4597 return Ok{};
4601 nsString path;
4602 QM_TRY(MOZ_TO_RESULT(lsDir->GetPath(path)));
4604 QM_WARNING("Deleting %s directory!",
4605 NS_ConvertUTF16toUTF8(path).get());
4607 QM_TRY(MOZ_TO_RESULT(lsDir->Remove(/* aRecursive */ true)));
4609 break;
4612 case nsIFileKind::ExistsAsFile: {
4613 QM_TRY_INSPECT(const auto& leafName,
4614 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4615 nsAutoString, originDir, GetLeafName));
4617 // Unknown files during upgrade are allowed. Just warn if we find
4618 // them.
4619 if (!IsOSMetadata(leafName)) {
4620 UNKNOWN_FILE_WARNING(leafName);
4623 break;
4626 case nsIFileKind::DoesNotExist:
4627 // Ignore files that got removed externally while iterating.
4628 break;
4630 return Ok{};
4631 }));
4633 return NS_OK;
4636 Result<Ok, nsresult> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
4637 nsIFile& aLsArchiveFile) const {
4638 AssertIsOnIOThread();
4639 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4641 #ifdef DEBUG
4643 QM_TRY_INSPECT(const bool& exists,
4644 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4645 MOZ_ASSERT(!exists);
4647 #endif
4649 // Get the storage service first, we will need it at multiple places.
4650 QM_TRY_INSPECT(const auto& ss,
4651 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4652 MOZ_SELECT_OVERLOAD(do_GetService),
4653 MOZ_STORAGE_SERVICE_CONTRACTID));
4655 // Get the web apps store file.
4656 QM_TRY_INSPECT(const auto& webAppsStoreFile, QM_NewLocalFile(mBasePath));
4658 QM_TRY(MOZ_TO_RESULT(
4659 webAppsStoreFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME))));
4661 // Now check if the web apps store is useable.
4662 QM_TRY_INSPECT(const auto& connection,
4663 CreateWebAppsStoreConnection(*webAppsStoreFile, *ss));
4665 if (connection) {
4666 // Find out the journal mode.
4667 QM_TRY_INSPECT(const auto& stmt,
4668 CreateAndExecuteSingleStepStatement(
4669 *connection, "PRAGMA journal_mode;"_ns));
4671 QM_TRY_INSPECT(const auto& journalMode,
4672 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt,
4673 GetUTF8String, 0));
4675 QM_TRY(MOZ_TO_RESULT(stmt->Finalize()));
4677 if (journalMode.EqualsLiteral("wal")) {
4678 // We don't copy the WAL file, so make sure the old database is fully
4679 // checkpointed.
4680 QM_TRY(MOZ_TO_RESULT(
4681 connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns)));
4684 // Explicitely close the connection before the old database is copied.
4685 QM_TRY(MOZ_TO_RESULT(connection->Close()));
4687 // Copy the old database. The database is copied from
4688 // <profile>/webappsstore.sqlite to
4689 // <profile>/storage/ls-archive-tmp.sqlite
4690 // We use a "-tmp" postfix since we are not done yet.
4691 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
4693 QM_TRY(MOZ_TO_RESULT(webAppsStoreFile->CopyTo(
4694 storageDir, nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
4696 QM_TRY_INSPECT(const auto& lsArchiveTmpFile,
4697 GetLocalStorageArchiveTmpFile(*mStoragePath));
4699 if (journalMode.EqualsLiteral("wal")) {
4700 QM_TRY_INSPECT(
4701 const auto& lsArchiveTmpConnection,
4702 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4703 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4704 lsArchiveTmpFile, mozIStorageService::CONNECTION_DEFAULT));
4706 // The archive will only be used for lazy data migration. There won't be
4707 // any concurrent readers and writers that could benefit from Write-Ahead
4708 // Logging. So switch to a standard rollback journal. The standard
4709 // rollback journal also provides atomicity across multiple attached
4710 // databases which is import for the lazy data migration to work safely.
4711 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->ExecuteSimpleSQL(
4712 "PRAGMA journal_mode = DELETE;"_ns)));
4714 // Close the connection explicitly. We are going to rename the file below.
4715 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->Close()));
4718 // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
4719 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpFile->MoveTo(
4720 nullptr, nsLiteralString(LS_ARCHIVE_FILE_NAME))));
4722 return Ok{};
4725 // If webappsstore database is not useable, just create an empty archive.
4726 // XXX The code below should be removed and the caller should call us only
4727 // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
4728 // reworked to propagate database corruption instead of returning null
4729 // connection.
4730 // So, if there's no webappsstore.sqlite
4731 // MaybeCreateOrUpgradeLocalStorageArchive will call
4732 // CreateEmptyLocalStorageArchive instead of
4733 // CopyLocalStorageArchiveFromWebAppsStore.
4734 // If there's any corruption detected during
4735 // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
4736 // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
4737 // EnsureStorageIsInitializedInternal will fallback to
4738 // CreateEmptyLocalStorageArchive.
4740 // Ensure the storage directory actually exists.
4741 QM_TRY_INSPECT(const auto& storageDirectory, QM_NewLocalFile(*mStoragePath));
4743 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDirectory));
4745 Unused << created;
4747 QM_TRY_UNWRAP(auto lsArchiveConnection,
4748 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4749 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4750 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
4752 QM_TRY(MOZ_TO_RESULT(
4753 StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection)));
4755 return Ok{};
4758 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4759 QuotaManager::CreateLocalStorageArchiveConnection(
4760 nsIFile& aLsArchiveFile) const {
4761 AssertIsOnIOThread();
4762 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4764 #ifdef DEBUG
4766 QM_TRY_INSPECT(const bool& exists,
4767 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4768 MOZ_ASSERT(exists);
4770 #endif
4772 QM_TRY_INSPECT(const bool& isDirectory,
4773 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, IsDirectory));
4775 // A directory with the name of the archive file is treated as corruption
4776 // (similarly as wrong content of the file).
4777 QM_TRY(OkIf(!isDirectory), Err(NS_ERROR_FILE_CORRUPTED));
4779 QM_TRY_INSPECT(const auto& ss,
4780 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4781 MOZ_SELECT_OVERLOAD(do_GetService),
4782 MOZ_STORAGE_SERVICE_CONTRACTID));
4784 // This may return NS_ERROR_FILE_CORRUPTED too.
4785 QM_TRY_UNWRAP(auto connection,
4786 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4787 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4788 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
4790 // The legacy LS implementation removes the database and creates an empty one
4791 // when the schema can't be updated. The same effect can be achieved here by
4792 // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
4793 // sub test case 3 of dom/localstorage/test/unit/test_archive.js
4794 QM_TRY(
4795 MOZ_TO_RESULT(StorageDBUpdater::Update(connection))
4796 .mapErr([](const nsresult rv) { return NS_ERROR_FILE_CORRUPTED; }));
4798 return connection;
4801 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4802 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
4803 nsIFile& aLsArchiveFile) {
4804 AssertIsOnIOThread();
4805 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4807 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
4809 #ifdef DEBUG
4811 QM_TRY_INSPECT(const bool& exists,
4812 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4814 MOZ_ASSERT(exists);
4816 #endif
4818 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
4820 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4822 QM_TRY_UNWRAP(auto connection,
4823 CreateLocalStorageArchiveConnection(aLsArchiveFile));
4825 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
4827 return connection;
4830 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4831 QuotaManager::DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile) {
4832 AssertIsOnIOThread();
4833 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4835 QM_TRY_UNWRAP(auto connection,
4836 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4838 QM_TRY(MOZ_TO_RESULT(
4839 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
4841 return connection;
4844 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4845 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
4846 nsIFile& aLsArchiveFile) {
4847 AssertIsOnIOThread();
4848 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4850 QM_TRY_UNWRAP(auto connection,
4851 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4853 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(connection, 4)));
4855 return connection;
4859 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
4860 nsCOMPtr<mozIStorageConnection>& aConnection) {
4861 AssertIsOnIOThread();
4862 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4864 nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
4865 if (NS_WARN_IF(NS_FAILED(rv))) {
4866 return rv;
4869 return NS_OK;
4873 #ifdef DEBUG
4875 void QuotaManager::AssertStorageIsInitializedInternal() const {
4876 AssertIsOnIOThread();
4877 MOZ_ASSERT(IsStorageInitializedInternal());
4880 #endif // DEBUG
4882 nsresult QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
4883 nsIFile& aStorageFile) {
4884 AssertIsOnIOThread();
4886 QM_TRY_INSPECT(const auto& storageFileExists,
4887 MOZ_TO_RESULT_INVOKE_MEMBER(aStorageFile, Exists));
4889 if (!storageFileExists) {
4890 QM_TRY_INSPECT(const auto& indexedDBDir, QM_NewLocalFile(*mIndexedDBPath));
4892 QM_TRY_INSPECT(const auto& indexedDBDirExists,
4893 MOZ_TO_RESULT_INVOKE_MEMBER(indexedDBDir, Exists));
4895 if (indexedDBDirExists) {
4896 QM_TRY(MOZ_TO_RESULT(
4897 UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
4898 indexedDBDir)));
4901 QM_TRY_INSPECT(const auto& persistentStorageDir,
4902 QM_NewLocalFile(*mStoragePath));
4904 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
4905 nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
4907 QM_TRY_INSPECT(const auto& persistentStorageDirExists,
4908 MOZ_TO_RESULT_INVOKE_MEMBER(persistentStorageDir, Exists));
4910 if (persistentStorageDirExists) {
4911 QM_TRY(MOZ_TO_RESULT(
4912 UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
4913 persistentStorageDir)));
4917 return NS_OK;
4920 nsresult QuotaManager::MaybeCreateOrUpgradeStorage(
4921 mozIStorageConnection& aConnection) {
4922 AssertIsOnIOThread();
4924 QM_TRY_UNWRAP(auto storageVersion,
4925 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4927 // Hacky downgrade logic!
4928 // If we see major.minor of 3.0, downgrade it to be 2.1.
4929 if (storageVersion == kHackyPreDowngradeStorageVersion) {
4930 storageVersion = kHackyPostDowngradeStorageVersion;
4931 QM_TRY(MOZ_TO_RESULT(aConnection.SetSchemaVersion(storageVersion)),
4932 QM_PROPAGATE,
4933 [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
4936 QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion),
4937 NS_ERROR_FAILURE, [](const auto&) {
4938 NS_WARNING("Unable to initialize storage, version is too high!");
4941 if (storageVersion < kStorageVersion) {
4942 const bool newDatabase = !storageVersion;
4944 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
4946 QM_TRY_INSPECT(const auto& storageDirExists,
4947 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists));
4949 const bool newDirectory = !storageDirExists;
4951 if (newDatabase) {
4952 // Set the page size first.
4953 if (kSQLitePageSizeOverride) {
4954 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsPrintfCString(
4955 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
4959 mozStorageTransaction transaction(
4960 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
4962 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
4964 // An upgrade method can upgrade the database, the storage or both.
4965 // The upgrade loop below can only be avoided when there's no database and
4966 // no storage yet (e.g. new profile).
4967 if (newDatabase && newDirectory) {
4968 QM_TRY(MOZ_TO_RESULT(CreateTables(&aConnection)));
4970 #ifdef DEBUG
4972 QM_TRY_INSPECT(
4973 const int32_t& storageVersion,
4974 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion),
4975 QM_ASSERT_UNREACHABLE);
4976 MOZ_ASSERT(storageVersion == kStorageVersion);
4978 #endif
4980 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
4981 nsLiteralCString("INSERT INTO database (cache_version) "
4982 "VALUES (0)"))));
4983 } else {
4984 // This logic needs to change next time we change the storage!
4985 static_assert(kStorageVersion == int32_t((2 << 16) + 3),
4986 "Upgrade function needed due to storage version increase.");
4988 while (storageVersion != kStorageVersion) {
4989 if (storageVersion == 0) {
4990 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom0_0To1_0(&aConnection)));
4991 } else if (storageVersion == MakeStorageVersion(1, 0)) {
4992 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom1_0To2_0(&aConnection)));
4993 } else if (storageVersion == MakeStorageVersion(2, 0)) {
4994 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_0To2_1(&aConnection)));
4995 } else if (storageVersion == MakeStorageVersion(2, 1)) {
4996 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_1To2_2(&aConnection)));
4997 } else if (storageVersion == MakeStorageVersion(2, 2)) {
4998 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_2To2_3(&aConnection)));
4999 } else {
5000 QM_FAIL(NS_ERROR_FAILURE, []() {
5001 NS_WARNING(
5002 "Unable to initialize storage, no upgrade path is "
5003 "available!");
5007 QM_TRY_UNWRAP(storageVersion, MOZ_TO_RESULT_INVOKE_MEMBER(
5008 aConnection, GetSchemaVersion));
5011 MOZ_ASSERT(storageVersion == kStorageVersion);
5014 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
5017 return NS_OK;
5020 OkOrErr QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
5021 AssertIsOnIOThread();
5023 QM_TRY_INSPECT(
5024 const auto& lsArchiveTmpFile,
5025 QM_TO_RESULT_TRANSFORM(GetLocalStorageArchiveTmpFile(*mStoragePath)));
5027 QM_TRY_INSPECT(const bool& exists,
5028 QM_TO_RESULT_INVOKE_MEMBER(lsArchiveTmpFile, Exists));
5030 if (exists) {
5031 QM_TRY(QM_TO_RESULT(lsArchiveTmpFile->Remove(false)));
5034 return Ok{};
5037 Result<Ok, nsresult> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
5038 nsIFile& aLsArchiveFile) {
5039 AssertIsOnIOThread();
5041 QM_TRY_INSPECT(
5042 const bool& lsArchiveFileExisted,
5043 ([this, &aLsArchiveFile]() -> Result<bool, nsresult> {
5044 QM_TRY_INSPECT(const bool& exists,
5045 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
5047 if (!exists) {
5048 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
5051 return exists;
5052 }()));
5054 QM_TRY_UNWRAP(auto connection,
5055 CreateLocalStorageArchiveConnection(aLsArchiveFile));
5057 QM_TRY_INSPECT(const auto& initialized,
5058 IsLocalStorageArchiveInitialized(*connection));
5060 if (!initialized) {
5061 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
5064 QM_TRY_UNWRAP(int32_t version, LoadLocalStorageArchiveVersion(*connection));
5066 if (version > kLocalStorageArchiveVersion) {
5067 // Close local storage archive connection. We are going to remove underlying
5068 // file.
5069 QM_TRY(MOZ_TO_RESULT(connection->Close()));
5071 // This will wipe the archive and any migrated data and recopy the archive
5072 // from webappsstore.sqlite.
5073 QM_TRY_UNWRAP(connection, DowngradeLocalStorageArchive(aLsArchiveFile));
5075 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
5077 MOZ_ASSERT(version == kLocalStorageArchiveVersion);
5078 } else if (version != kLocalStorageArchiveVersion) {
5079 // The version can be zero either when the archive didn't exist or it did
5080 // exist, but the archive was created without any version information.
5081 // We don't need to do any upgrades only if it didn't exist because existing
5082 // archives without version information must be recopied to really fix bug
5083 // 1542104. See also bug 1546305 which introduced archive versions.
5084 if (!lsArchiveFileExisted) {
5085 MOZ_ASSERT(version == 0);
5087 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(
5088 connection, kLocalStorageArchiveVersion)));
5089 } else {
5090 static_assert(kLocalStorageArchiveVersion == 4,
5091 "Upgrade function needed due to LocalStorage archive "
5092 "version increase.");
5094 while (version != kLocalStorageArchiveVersion) {
5095 if (version < 4) {
5096 // Close local storage archive connection. We are going to remove
5097 // underlying file.
5098 QM_TRY(MOZ_TO_RESULT(connection->Close()));
5100 // This won't do an "upgrade" in a normal sense. It will wipe the
5101 // archive and any migrated data and recopy the archive from
5102 // webappsstore.sqlite
5103 QM_TRY_UNWRAP(connection, UpgradeLocalStorageArchiveFromLessThan4To4(
5104 aLsArchiveFile));
5105 } /* else if (version == 4) {
5106 QM_TRY(MOZ_TO_RESULT(UpgradeLocalStorageArchiveFrom4To5(connection)));
5107 } */
5108 else {
5109 QM_FAIL(Err(NS_ERROR_FAILURE), []() {
5110 QM_WARNING(
5111 "Unable to initialize LocalStorage archive, no upgrade path "
5112 "is available!");
5116 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
5119 MOZ_ASSERT(version == kLocalStorageArchiveVersion);
5123 // At this point, we have finished initializing the local storage archive, and
5124 // can continue storage initialization. We don't know though if the actual
5125 // data in the archive file is readable. We can't do a PRAGMA integrity_check
5126 // here though, because that would be too heavyweight.
5128 return Ok{};
5131 Result<Ok, nsresult> QuotaManager::CreateEmptyLocalStorageArchive(
5132 nsIFile& aLsArchiveFile) const {
5133 AssertIsOnIOThread();
5135 QM_TRY_INSPECT(const bool& exists,
5136 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
5138 // If it exists, remove it. It might be a directory, so remove it recursively.
5139 if (exists) {
5140 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(true)));
5142 // XXX If we crash right here, the next session will copy the archive from
5143 // webappsstore.sqlite again!
5144 // XXX Create a marker file before removing the archive which can be
5145 // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
5146 // archive instead of recopying it from webapppstore.sqlite (in other
5147 // words, finishing what was started here).
5150 QM_TRY_INSPECT(const auto& ss,
5151 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
5152 MOZ_SELECT_OVERLOAD(do_GetService),
5153 MOZ_STORAGE_SERVICE_CONTRACTID));
5155 QM_TRY_UNWRAP(const auto connection,
5156 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5157 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
5158 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
5160 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection)));
5162 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
5164 QM_TRY(MOZ_TO_RESULT(
5165 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
5167 return Ok{};
5170 RefPtr<BoolPromise> QuotaManager::InitializeStorage() {
5171 AssertIsOnOwningThread();
5173 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
5174 PersistenceScope::CreateFromNull(), OriginScope::FromNull(),
5175 Nullable<Client::Type>(),
5176 /* aExclusive */ false);
5178 auto prepareInfo = directoryLock->Prepare();
5180 // If storage is initialized but there's a clear storage or shutdown storage
5181 // operation already scheduled, we can't immediately resolve the promise and
5182 // return from the function because the clear and shutdown storage operation
5183 // uninitializes storage.
5184 if (mStorageInitialized &&
5185 !IsDirectoryLockBlockedByUninitStorageOperation(prepareInfo)) {
5186 return BoolPromise::CreateAndResolve(true, __func__);
5189 return directoryLock->Acquire(std::move(prepareInfo))
5190 ->Then(GetCurrentSerialEventTarget(), __func__,
5191 [self = RefPtr(this), directoryLock](
5192 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5193 if (aValue.IsReject()) {
5194 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5195 __func__);
5198 return self->InitializeStorage(std::move(directoryLock));
5202 RefPtr<BoolPromise> QuotaManager::InitializeStorage(
5203 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
5204 AssertIsOnOwningThread();
5205 MOZ_ASSERT(aDirectoryLock);
5206 MOZ_ASSERT(aDirectoryLock->Acquired());
5208 // If storage is initialized and the directory lock for the initialize
5209 // storage operation is acquired, we can immediately resolve the promise and
5210 // return from the function because there can't be a clear storage or
5211 // shutdown storage operation which would uninitialize storage.
5212 if (mStorageInitialized) {
5213 DropDirectoryLock(aDirectoryLock);
5215 return BoolPromise::CreateAndResolve(true, __func__);
5218 auto initializeStorageOp =
5219 CreateInitOp(WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
5221 RegisterNormalOriginOp(*initializeStorageOp);
5223 initializeStorageOp->RunImmediately();
5225 return initializeStorageOp->OnResults()->Then(
5226 GetCurrentSerialEventTarget(), __func__,
5227 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
5228 if (aValue.IsReject()) {
5229 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5232 self->mStorageInitialized = true;
5234 return BoolPromise::CreateAndResolve(true, __func__);
5238 RefPtr<BoolPromise> QuotaManager::StorageInitialized() {
5239 AssertIsOnOwningThread();
5241 auto storageInitializedOp =
5242 CreateStorageInitializedOp(WrapMovingNotNullUnchecked(this));
5244 RegisterNormalOriginOp(*storageInitializedOp);
5246 storageInitializedOp->RunImmediately();
5248 return storageInitializedOp->OnResults();
5251 nsresult QuotaManager::EnsureStorageIsInitializedInternal() {
5252 DiagnosticAssertIsOnIOThread();
5254 const auto innerFunc =
5255 [&](const auto& firstInitializationAttempt) -> nsresult {
5256 if (mStorageConnection) {
5257 MOZ_ASSERT(firstInitializationAttempt.Recorded());
5258 return NS_OK;
5261 QM_TRY_INSPECT(const auto& storageFile, QM_NewLocalFile(mBasePath));
5262 QM_TRY(MOZ_TO_RESULT(storageFile->Append(mStorageName + kSQLiteSuffix)));
5264 QM_TRY(MOZ_TO_RESULT(MaybeUpgradeToDefaultStorageDirectory(*storageFile)));
5266 QM_TRY_INSPECT(const auto& ss,
5267 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
5268 MOZ_SELECT_OVERLOAD(do_GetService),
5269 MOZ_STORAGE_SERVICE_CONTRACTID));
5271 QM_TRY_UNWRAP(
5272 auto connection,
5273 QM_OR_ELSE_WARN_IF(
5274 // Expression.
5275 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5276 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
5277 storageFile, mozIStorageService::CONNECTION_DEFAULT),
5278 // Predicate.
5279 IsDatabaseCorruptionError,
5280 // Fallback.
5281 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
5283 if (!connection) {
5284 // Nuke the database file.
5285 QM_TRY(MOZ_TO_RESULT(storageFile->Remove(false)));
5287 QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5288 nsCOMPtr<mozIStorageConnection>, ss,
5289 OpenUnsharedDatabase, storageFile,
5290 mozIStorageService::CONNECTION_DEFAULT));
5293 // We want extra durability for this important file.
5294 QM_TRY(MOZ_TO_RESULT(
5295 connection->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns)));
5297 // Check to make sure that the storage version is correct.
5298 QM_TRY(MOZ_TO_RESULT(MaybeCreateOrUpgradeStorage(*connection)));
5300 QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
5302 QM_TRY_INSPECT(const auto& lsArchiveFile,
5303 GetLocalStorageArchiveFile(*mStoragePath));
5305 if (CachedNextGenLocalStorageEnabled()) {
5306 QM_TRY(QM_OR_ELSE_WARN_IF(
5307 // Expression.
5308 MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile),
5309 // Predicate.
5310 IsDatabaseCorruptionError,
5311 // Fallback.
5312 ([&](const nsresult rv) -> Result<Ok, nsresult> {
5313 QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile));
5314 })));
5315 } else {
5316 QM_TRY(
5317 MOZ_TO_RESULT(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile)));
5320 QM_TRY_UNWRAP(mCacheUsable, MaybeCreateOrUpgradeCache(*connection));
5322 if (mCacheUsable && gInvalidateQuotaCache) {
5323 QM_TRY(InvalidateCache(*connection));
5325 gInvalidateQuotaCache = false;
5328 uint32_t pauseOnIOThreadMs =
5329 StaticPrefs::dom_quotaManager_storageInitialization_pauseOnIOThreadMs();
5330 if (pauseOnIOThreadMs > 0) {
5331 PR_Sleep(PR_MillisecondsToInterval(pauseOnIOThreadMs));
5334 mStorageConnection = std::move(connection);
5336 return NS_OK;
5339 return ExecuteInitialization(
5340 Initialization::Storage,
5341 "dom::quota::FirstInitializationAttempt::Storage"_ns, innerFunc);
5344 RefPtr<BoolPromise> QuotaManager::TemporaryStorageInitialized() {
5345 AssertIsOnOwningThread();
5347 auto temporaryStorageInitializedOp =
5348 CreateTemporaryStorageInitializedOp(WrapMovingNotNullUnchecked(this));
5350 RegisterNormalOriginOp(*temporaryStorageInitializedOp);
5352 temporaryStorageInitializedOp->RunImmediately();
5354 return temporaryStorageInitializedOp->OnResults();
5357 RefPtr<UniversalDirectoryLockPromise> QuotaManager::OpenStorageDirectory(
5358 const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope,
5359 const Nullable<Client::Type>& aClientType, bool aExclusive,
5360 bool aInitializeOrigins, DirectoryLockCategory aCategory,
5361 Maybe<RefPtr<UniversalDirectoryLock>&> aPendingDirectoryLockOut) {
5362 AssertIsOnOwningThread();
5364 nsTArray<RefPtr<BoolPromise>> promises;
5366 // Directory locks for specific initializations can be null, indicating that
5367 // either the initialization should not occur or it has already been
5368 // completed.
5370 RefPtr<UniversalDirectoryLock> storageDirectoryLock =
5371 CreateDirectoryLockForInitialization(
5372 *this, PersistenceScope::CreateFromNull(), OriginScope::FromNull(),
5373 mStorageInitialized, IsDirectoryLockBlockedByUninitStorageOperation,
5374 MakeBackInserter(promises));
5376 RefPtr<UniversalDirectoryLock> persistentStorageDirectoryLock;
5378 RefPtr<UniversalDirectoryLock> temporaryStorageDirectoryLock;
5380 if (aInitializeOrigins) {
5381 if (MatchesPersistentPersistenceScope(aPersistenceScope)) {
5382 persistentStorageDirectoryLock = CreateDirectoryLockForInitialization(
5383 *this, PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT),
5384 OriginScope::FromNull(), mPersistentStorageInitialized,
5385 IsDirectoryLockBlockedByUninitStorageOperation,
5386 MakeBackInserter(promises));
5389 // We match all best effort persistence types, but the persistence scope is
5390 // created only for the temporary and default persistence type because the
5391 // repository for the private persistence type is never initialized as part
5392 // of temporary initialization. However, some other steps of the temporary
5393 // storage initialization need to be done even for the private persistence
5394 // type. For example, the initialization of mTemporaryStorageLimit.
5395 if (MatchesBestEffortPersistenceScope(aPersistenceScope)) {
5396 temporaryStorageDirectoryLock = CreateDirectoryLockForInitialization(
5397 *this,
5398 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
5399 PERSISTENCE_TYPE_DEFAULT),
5400 OriginScope::FromNull(), mTemporaryStorageInitialized,
5401 IsDirectoryLockBlockedByUninitStorageOperation,
5402 MakeBackInserter(promises));
5406 RefPtr<UniversalDirectoryLock> universalDirectoryLock =
5407 CreateDirectoryLockInternal(aPersistenceScope, aOriginScope, aClientType,
5408 aExclusive, aCategory);
5410 RefPtr<BoolPromise> universalDirectoryLockPromise =
5411 universalDirectoryLock->Acquire();
5413 if (aPendingDirectoryLockOut.isSome()) {
5414 aPendingDirectoryLockOut.ref() = universalDirectoryLock;
5417 return BoolPromise::All(GetCurrentSerialEventTarget(), promises)
5418 ->Then(
5419 GetCurrentSerialEventTarget(), __func__,
5420 [](const CopyableTArray<bool>& aResolveValues) {
5421 return BoolPromise::CreateAndResolve(true, __func__);
5423 [](nsresult aRejectValue) {
5424 return BoolPromise::CreateAndReject(aRejectValue, __func__);
5426 ->Then(GetCurrentSerialEventTarget(), __func__,
5427 MaybeInitialize(std::move(storageDirectoryLock), this,
5428 &QuotaManager::InitializeStorage))
5429 ->Then(GetCurrentSerialEventTarget(), __func__,
5430 MaybeInitialize(std::move(persistentStorageDirectoryLock), this,
5431 &QuotaManager::InitializePersistentStorage))
5432 ->Then(GetCurrentSerialEventTarget(), __func__,
5433 MaybeInitialize(std::move(temporaryStorageDirectoryLock), this,
5434 &QuotaManager::InitializeTemporaryStorage))
5435 ->Then(GetCurrentSerialEventTarget(), __func__,
5436 [universalDirectoryLockPromise =
5437 std::move(universalDirectoryLockPromise)](
5438 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5439 if (aValue.IsReject()) {
5440 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5441 __func__);
5444 return std::move(universalDirectoryLockPromise);
5446 ->Then(GetCurrentSerialEventTarget(), __func__,
5447 [universalDirectoryLock = std::move(universalDirectoryLock)](
5448 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5449 if (aValue.IsReject()) {
5450 DropDirectoryLockIfNotDropped(universalDirectoryLock);
5452 return UniversalDirectoryLockPromise::CreateAndReject(
5453 aValue.RejectValue(), __func__);
5456 return UniversalDirectoryLockPromise::CreateAndResolve(
5457 std::move(universalDirectoryLock), __func__);
5461 RefPtr<ClientDirectoryLockPromise> QuotaManager::OpenClientDirectory(
5462 const ClientMetadata& aClientMetadata, bool aInitializeOrigin,
5463 bool aCreateIfNonExistent,
5464 Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut) {
5465 AssertIsOnOwningThread();
5467 const auto persistenceType = aClientMetadata.mPersistenceType;
5469 nsTArray<RefPtr<BoolPromise>> promises;
5471 // Directory locks for specific initializations can be null, indicating that
5472 // either the initialization should not occur or it has already been
5473 // completed.
5475 RefPtr<UniversalDirectoryLock> storageDirectoryLock =
5476 CreateDirectoryLockForInitialization(
5477 *this, PersistenceScope::CreateFromNull(), OriginScope::FromNull(),
5478 mStorageInitialized, IsDirectoryLockBlockedByUninitStorageOperation,
5479 MakeBackInserter(promises));
5481 RefPtr<UniversalDirectoryLock> temporaryStorageDirectoryLock;
5483 RefPtr<UniversalDirectoryLock> groupDirectoryLock;
5485 if (IsBestEffortPersistenceType(persistenceType)) {
5486 temporaryStorageDirectoryLock = CreateDirectoryLockForInitialization(
5487 *this,
5488 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
5489 PERSISTENCE_TYPE_DEFAULT),
5490 OriginScope::FromNull(), mTemporaryStorageInitialized,
5491 IsDirectoryLockBlockedByUninitStorageOperation,
5492 MakeBackInserter(promises));
5494 const bool groupInitialized = IsTemporaryGroupInitialized(aClientMetadata);
5496 groupDirectoryLock = CreateDirectoryLockForInitialization(
5497 *this,
5498 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
5499 PERSISTENCE_TYPE_DEFAULT),
5500 OriginScope::FromGroup(aClientMetadata.mGroup), groupInitialized,
5501 IsDirectoryLockBlockedByUninitStorageOperation,
5502 MakeBackInserter(promises));
5505 RefPtr<UniversalDirectoryLock> originDirectoryLock;
5507 if (aInitializeOrigin) {
5508 const bool originInitialized =
5509 persistenceType == PERSISTENCE_TYPE_PERSISTENT
5510 ? IsPersistentOriginInitialized(aClientMetadata)
5511 : IsTemporaryOriginInitialized(aClientMetadata);
5513 originDirectoryLock = CreateDirectoryLockForInitialization(
5514 *this, PersistenceScope::CreateFromValue(persistenceType),
5515 OriginScope::FromOrigin(aClientMetadata), originInitialized,
5516 IsDirectoryLockBlockedByUninitStorageOrUninitOriginsOperation,
5517 MakeBackInserter(promises));
5520 RefPtr<ClientDirectoryLock> clientDirectoryLock =
5521 CreateDirectoryLock(aClientMetadata, /* aExclusive */ false);
5523 promises.AppendElement(clientDirectoryLock->Acquire());
5525 if (aPendingDirectoryLockOut.isSome()) {
5526 aPendingDirectoryLockOut.ref() = clientDirectoryLock;
5529 RefPtr<ClientDirectoryLockPromise> promise =
5530 BoolPromise::All(GetCurrentSerialEventTarget(), promises)
5531 ->Then(
5532 GetCurrentSerialEventTarget(), __func__,
5533 [](const CopyableTArray<bool>& aResolveValues) {
5534 return BoolPromise::CreateAndResolve(true, __func__);
5536 [](nsresult aRejectValue) {
5537 return BoolPromise::CreateAndReject(aRejectValue, __func__);
5539 ->Then(GetCurrentSerialEventTarget(), __func__,
5540 MaybeInitialize(std::move(storageDirectoryLock), this,
5541 &QuotaManager::InitializeStorage))
5542 ->Then(GetCurrentSerialEventTarget(), __func__,
5543 MaybeInitialize(std::move(temporaryStorageDirectoryLock), this,
5544 &QuotaManager::InitializeTemporaryStorage))
5545 ->Then(GetCurrentSerialEventTarget(), __func__,
5546 MaybeInitialize(std::move(groupDirectoryLock),
5547 [self = RefPtr(this), aClientMetadata](
5548 RefPtr<UniversalDirectoryLock>
5549 groupDirectoryLock) mutable {
5550 return self->InitializeTemporaryGroup(
5551 aClientMetadata,
5552 std::move(groupDirectoryLock));
5554 ->Then(GetCurrentSerialEventTarget(), __func__,
5555 MaybeInitialize(
5556 std::move(originDirectoryLock),
5557 [self = RefPtr(this), aClientMetadata,
5558 aCreateIfNonExistent](RefPtr<UniversalDirectoryLock>
5559 originDirectoryLock) mutable {
5560 if (aClientMetadata.mPersistenceType ==
5561 PERSISTENCE_TYPE_PERSISTENT) {
5562 return self->InitializePersistentOrigin(
5563 aClientMetadata, std::move(originDirectoryLock));
5566 return self->InitializeTemporaryOrigin(
5567 aClientMetadata, aCreateIfNonExistent,
5568 std::move(originDirectoryLock));
5570 ->Then(GetCurrentSerialEventTarget(), __func__,
5571 [clientDirectoryLock = std::move(clientDirectoryLock)](
5572 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5573 if (aValue.IsReject()) {
5574 DropDirectoryLockIfNotDropped(clientDirectoryLock);
5576 return ClientDirectoryLockPromise::CreateAndReject(
5577 aValue.RejectValue(), __func__);
5580 QM_TRY(ArtificialFailure(nsIQuotaArtificialFailure::
5581 CATEGORY_OPEN_CLIENT_DIRECTORY),
5582 [&clientDirectoryLock](nsresult rv) {
5583 DropDirectoryLockIfNotDropped(clientDirectoryLock);
5585 return ClientDirectoryLockPromise::CreateAndReject(
5586 rv, __func__);
5589 return ClientDirectoryLockPromise::CreateAndResolve(
5590 std::move(clientDirectoryLock), __func__);
5593 NotifyClientDirectoryOpeningStarted(*this);
5595 return promise;
5598 RefPtr<ClientDirectoryLock> QuotaManager::CreateDirectoryLock(
5599 const ClientMetadata& aClientMetadata, bool aExclusive) {
5600 AssertIsOnOwningThread();
5602 return ClientDirectoryLock::Create(
5603 WrapNotNullUnchecked(this), aClientMetadata.mPersistenceType,
5604 aClientMetadata, aClientMetadata.mClientType, aExclusive);
5607 RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal(
5608 const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope,
5609 const Nullable<Client::Type>& aClientType, bool aExclusive,
5610 DirectoryLockCategory aCategory) {
5611 AssertIsOnOwningThread();
5613 return UniversalDirectoryLock::CreateInternal(
5614 WrapNotNullUnchecked(this), aPersistenceScope, aOriginScope, aClientType,
5615 aExclusive, aCategory);
5618 bool QuotaManager::IsPendingOrigin(
5619 const OriginMetadata& aOriginMetadata) const {
5620 MutexAutoLock lock(mQuotaMutex);
5622 RefPtr<OriginInfo> originInfo =
5623 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
5625 return originInfo && !originInfo->LockedDirectoryExists();
5628 RefPtr<BoolPromise> QuotaManager::InitializePersistentStorage() {
5629 AssertIsOnOwningThread();
5631 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
5632 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT),
5633 OriginScope::FromNull(), Nullable<Client::Type>(),
5634 /* aExclusive */ false);
5636 auto prepareInfo = directoryLock->Prepare();
5638 // If persistent storage is initialized but there's a clear storage or
5639 // shutdown storage operation already scheduled, we can't immediately resolve
5640 // the promise and return from the function because the clear or shutdown
5641 // storage operation uninitializes storage.
5642 if (mPersistentStorageInitialized &&
5643 !IsDirectoryLockBlockedByUninitStorageOperation(prepareInfo)) {
5644 return BoolPromise::CreateAndResolve(true, __func__);
5647 return directoryLock->Acquire(std::move(prepareInfo))
5648 ->Then(
5649 GetCurrentSerialEventTarget(), __func__,
5650 [self = RefPtr(this), directoryLock](
5651 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5652 if (aValue.IsReject()) {
5653 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5654 __func__);
5657 return self->InitializePersistentStorage(std::move(directoryLock));
5661 RefPtr<BoolPromise> QuotaManager::InitializePersistentStorage(
5662 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
5663 AssertIsOnOwningThread();
5664 MOZ_ASSERT(aDirectoryLock);
5665 MOZ_ASSERT(aDirectoryLock->Acquired());
5667 // If persistent storage is initialized and the directory lock for the
5668 // initialize persistent storage operation is acquired, we can immediately
5669 // resolve the promise and return from the function because there can't be a
5670 // clear storage or shutdown storage operation which would uninitialize
5671 // persistent storage.
5672 if (mPersistentStorageInitialized) {
5673 DropDirectoryLock(aDirectoryLock);
5675 return BoolPromise::CreateAndResolve(true, __func__);
5678 auto initializePersistentStorageOp = CreateInitializePersistentStorageOp(
5679 WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
5681 RegisterNormalOriginOp(*initializePersistentStorageOp);
5683 initializePersistentStorageOp->RunImmediately();
5685 return initializePersistentStorageOp->OnResults()->Then(
5686 GetCurrentSerialEventTarget(), __func__,
5687 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
5688 if (aValue.IsReject()) {
5689 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5692 self->mPersistentStorageInitialized = true;
5694 return BoolPromise::CreateAndResolve(true, __func__);
5698 RefPtr<BoolPromise> QuotaManager::PersistentStorageInitialized() {
5699 AssertIsOnOwningThread();
5701 auto persistentStorageInitializedOp =
5702 CreatePersistentStorageInitializedOp(WrapMovingNotNullUnchecked(this));
5704 RegisterNormalOriginOp(*persistentStorageInitializedOp);
5706 persistentStorageInitializedOp->RunImmediately();
5708 return persistentStorageInitializedOp->OnResults();
5711 nsresult QuotaManager::EnsurePersistentStorageIsInitializedInternal() {
5712 AssertIsOnIOThread();
5713 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5715 const auto innerFunc =
5716 [&](const auto& firstInitializationAttempt) -> nsresult {
5717 if (mPersistentStorageInitializedInternal) {
5718 MOZ_ASSERT(firstInitializationAttempt.Recorded());
5719 return NS_OK;
5722 QM_TRY(MOZ_TO_RESULT(ExecuteInitialization(
5723 Initialization::PersistentRepository, [&](const auto&) -> nsresult {
5724 return InitializeRepository(PERSISTENCE_TYPE_PERSISTENT,
5725 [](auto&) {});
5726 })));
5728 mPersistentStorageInitializedInternal = true;
5730 return NS_OK;
5733 return ExecuteInitialization(
5734 Initialization::TemporaryStorage,
5735 "dom::quota::FirstInitializationAttempt::PersistentStorage"_ns,
5736 innerFunc);
5739 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryGroup(
5740 const PrincipalMetadata& aPrincipalMetadata) {
5741 AssertIsOnOwningThread();
5743 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
5744 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
5745 PERSISTENCE_TYPE_DEFAULT),
5746 OriginScope::FromGroup(aPrincipalMetadata.mGroup),
5747 Nullable<Client::Type>(),
5748 /* aExclusive */ false);
5750 auto prepareInfo = directoryLock->Prepare();
5752 // If temporary group is initialized but there's a clear storage or shutdown
5753 // storage operation already scheduled, we can't immediately resolve the
5754 // promise and return from the function because the clear and shutdown
5755 // storage operation uninitializes storage.
5756 if (IsTemporaryGroupInitialized(aPrincipalMetadata) &&
5757 !IsDirectoryLockBlockedByUninitStorageOperation(prepareInfo)) {
5758 return BoolPromise::CreateAndResolve(true, __func__);
5761 return directoryLock->Acquire(std::move(prepareInfo))
5762 ->Then(GetCurrentSerialEventTarget(), __func__,
5763 [self = RefPtr(this), aPrincipalMetadata, directoryLock](
5764 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5765 if (aValue.IsReject()) {
5766 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5767 __func__);
5770 return self->InitializeTemporaryGroup(aPrincipalMetadata,
5771 std::move(directoryLock));
5775 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryGroup(
5776 const PrincipalMetadata& aPrincipalMetadata,
5777 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
5778 AssertIsOnOwningThread();
5779 MOZ_ASSERT(aDirectoryLock);
5780 MOZ_ASSERT(aDirectoryLock->Acquired());
5782 // If temporary group is initialized and the directory lock for the
5783 // initialize temporary group operation is acquired, we can immediately
5784 // resolve the promise and return from the function because there can't be a
5785 // clear storage or shutdown storage operation which would uninitialize
5786 // temporary storage.
5787 if (IsTemporaryGroupInitialized(aPrincipalMetadata)) {
5788 DropDirectoryLock(aDirectoryLock);
5790 return BoolPromise::CreateAndResolve(true, __func__);
5793 auto initializeTemporaryGroupOp = CreateInitializeTemporaryGroupOp(
5794 WrapMovingNotNullUnchecked(this), aPrincipalMetadata,
5795 std::move(aDirectoryLock));
5797 RegisterNormalOriginOp(*initializeTemporaryGroupOp);
5799 initializeTemporaryGroupOp->RunImmediately();
5801 return Map<BoolPromise>(
5802 initializeTemporaryGroupOp->OnResults(),
5803 [self = RefPtr(this), group = aPrincipalMetadata.mGroup](
5804 const BoolPromise::ResolveOrRejectValue& aValue) {
5805 self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Insert(
5806 group);
5808 return aValue.ResolveValue();
5812 RefPtr<BoolPromise> QuotaManager::TemporaryGroupInitialized(
5813 const PrincipalMetadata& aPrincipalMetadata) {
5814 AssertIsOnOwningThread();
5816 auto temporaryGroupInitializedOp = CreateTemporaryGroupInitializedOp(
5817 WrapMovingNotNullUnchecked(this), aPrincipalMetadata);
5819 RegisterNormalOriginOp(*temporaryGroupInitializedOp);
5821 temporaryGroupInitializedOp->RunImmediately();
5823 return temporaryGroupInitializedOp->OnResults();
5826 bool QuotaManager::IsTemporaryGroupInitialized(
5827 const PrincipalMetadata& aPrincipalMetadata) {
5828 AssertIsOnOwningThread();
5830 return mBackgroundThreadAccessible.Access()->mInitializedGroups.Contains(
5831 aPrincipalMetadata.mGroup);
5834 bool QuotaManager::IsTemporaryGroupInitializedInternal(
5835 const PrincipalMetadata& aPrincipalMetadata) const {
5836 AssertIsOnIOThread();
5838 MutexAutoLock lock(mQuotaMutex);
5840 return LockedHasGroupInfoPair(aPrincipalMetadata.mGroup);
5843 Result<Ok, nsresult> QuotaManager::EnsureTemporaryGroupIsInitializedInternal(
5844 const PrincipalMetadata& aPrincipalMetadata) {
5845 AssertIsOnIOThread();
5846 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5847 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
5849 const auto innerFunc = [&aPrincipalMetadata,
5850 this](const auto&) -> mozilla::Result<Ok, nsresult> {
5851 const auto& array =
5852 mIOThreadAccessible.Access()->mAllTemporaryOrigins.Lookup(
5853 aPrincipalMetadata.mGroup);
5854 if (!array) {
5855 return Ok{};
5858 // XXX At the moment, the loop skips all elements in the array because
5859 // temporary storage initialization still initializes all temporary
5860 // origins. This is going to change soon with the planned asynchronous
5861 // temporary origin initialization done in the background.
5862 for (const auto& originMetadata : *array) {
5863 if (IsTemporaryOriginInitializedInternal(originMetadata)) {
5864 continue;
5867 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(originMetadata));
5869 QM_TRY_INSPECT(const auto& metadata,
5870 LoadFullOriginMetadataWithRestore(directory));
5872 // XXX Check corruption here!
5873 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(metadata.mPersistenceType, metadata,
5874 metadata.mLastAccessTime,
5875 metadata.mPersisted, directory,
5876 /* aForGroup */ true)));
5879 // XXX Evict origins that exceed their group limit here.
5881 return Ok{};
5884 return ExecuteGroupInitialization(
5885 aPrincipalMetadata.mGroup, GroupInitialization::TemporaryGroup,
5886 "dom::quota::FirstOriginInitializationAttempt::TemporaryGroup"_ns,
5887 innerFunc);
5890 RefPtr<BoolPromise> QuotaManager::InitializePersistentOrigin(
5891 const OriginMetadata& aOriginMetadata) {
5892 AssertIsOnOwningThread();
5893 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5895 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
5896 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT),
5897 OriginScope::FromOrigin(aOriginMetadata), Nullable<Client::Type>(),
5898 /* aExclusive */ false);
5900 auto prepareInfo = directoryLock->Prepare();
5902 // If persistent origin is initialized but there's a clear storage, shutdown
5903 // storage, clear origin, or shutdown origin operation already scheduled, we
5904 // can't immediately resolve the promise and return from the function because
5905 // the clear and shutdown storage operations uninitialize storage (which also
5906 // includes uninitialization of origins) and because clear and shutdown origin
5907 // operations uninitialize origins directly.
5908 if (IsPersistentOriginInitialized(aOriginMetadata) &&
5909 !IsDirectoryLockBlockedByUninitStorageOrUninitOriginsOperation(
5910 prepareInfo)) {
5911 return BoolPromise::CreateAndResolve(true, __func__);
5914 return directoryLock->Acquire(std::move(prepareInfo))
5915 ->Then(GetCurrentSerialEventTarget(), __func__,
5916 [self = RefPtr(this), aOriginMetadata, directoryLock](
5917 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5918 if (aValue.IsReject()) {
5919 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5920 __func__);
5923 return self->InitializePersistentOrigin(
5924 aOriginMetadata, std::move(directoryLock));
5928 RefPtr<BoolPromise> QuotaManager::InitializePersistentOrigin(
5929 const OriginMetadata& aOriginMetadata,
5930 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
5931 AssertIsOnOwningThread();
5932 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5933 MOZ_ASSERT(aDirectoryLock);
5934 MOZ_ASSERT(aDirectoryLock->Acquired());
5936 // If persistent origin is initialized and the directory lock for the
5937 // initialize persistent origin operation is acquired, we can immediately
5938 // resolve the promise and return from the function because there can't be a
5939 // clear storage, shutdown storage, clear origin, or shutdown origin
5940 // operation which would uninitialize storage (which also includes
5941 // uninitialization of origins), or which would uninitialize origins
5942 // directly.
5943 if (IsPersistentOriginInitialized(aOriginMetadata)) {
5944 DropDirectoryLock(aDirectoryLock);
5946 return BoolPromise::CreateAndResolve(true, __func__);
5949 auto initializePersistentOriginOp = CreateInitializePersistentOriginOp(
5950 WrapMovingNotNullUnchecked(this), aOriginMetadata,
5951 std::move(aDirectoryLock));
5953 RegisterNormalOriginOp(*initializePersistentOriginOp);
5955 initializePersistentOriginOp->RunImmediately();
5957 return Map<BoolPromise>(
5958 initializePersistentOriginOp->OnResults(),
5959 [self = RefPtr(this), origin = aOriginMetadata.mOrigin](
5960 const BoolPromise::ResolveOrRejectValue& aValue) {
5961 self->NoteInitializedOrigin(PERSISTENCE_TYPE_PERSISTENT, origin);
5963 return aValue.ResolveValue();
5967 RefPtr<BoolPromise> QuotaManager::PersistentOriginInitialized(
5968 const OriginMetadata& aOriginMetadata) {
5969 AssertIsOnOwningThread();
5970 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5972 auto persistentOriginInitializedOp = CreatePersistentOriginInitializedOp(
5973 WrapMovingNotNullUnchecked(this), aOriginMetadata);
5975 RegisterNormalOriginOp(*persistentOriginInitializedOp);
5977 persistentOriginInitializedOp->RunImmediately();
5979 return persistentOriginInitializedOp->OnResults();
5982 bool QuotaManager::IsPersistentOriginInitialized(
5983 const OriginMetadata& aOriginMetadata) {
5984 AssertIsOnOwningThread();
5985 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5987 return IsOriginInitialized(aOriginMetadata.mPersistenceType,
5988 aOriginMetadata.mOrigin);
5991 bool QuotaManager::IsPersistentOriginInitializedInternal(
5992 const OriginMetadata& aOriginMetadata) const {
5993 AssertIsOnIOThread();
5994 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5996 return mInitializedOriginsInternal.Contains(aOriginMetadata.mOrigin);
5999 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
6000 QuotaManager::EnsurePersistentOriginIsInitializedInternal(
6001 const OriginMetadata& aOriginMetadata) {
6002 AssertIsOnIOThread();
6003 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
6004 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6006 const auto innerFunc = [&aOriginMetadata,
6007 this](const auto& firstInitializationAttempt)
6008 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
6009 const auto extraInfo =
6010 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted,
6011 aOriginMetadata.mStorageOrigin};
6013 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
6015 if (mInitializedOriginsInternal.Contains(aOriginMetadata.mOrigin)) {
6016 MOZ_ASSERT(firstInitializationAttempt.Recorded());
6017 return std::pair(std::move(directory), false);
6020 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
6022 QM_TRY_INSPECT(
6023 const int64_t& timestamp,
6024 ([this, created, &directory,
6025 &aOriginMetadata]() -> Result<int64_t, nsresult> {
6026 if (created) {
6027 const int64_t timestamp = PR_Now();
6029 // Only creating .metadata-v2 to reduce IO.
6030 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
6031 /* aPersisted */ true,
6032 aOriginMetadata)));
6034 return timestamp;
6037 // Get the metadata. We only use the timestamp.
6038 QM_TRY_INSPECT(const auto& metadata,
6039 LoadFullOriginMetadataWithRestore(directory));
6041 MOZ_ASSERT(metadata.mLastAccessTime <= PR_Now());
6043 return metadata.mLastAccessTime;
6044 }()));
6046 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(PERSISTENCE_TYPE_PERSISTENT,
6047 aOriginMetadata, timestamp,
6048 /* aPersisted */ true, directory)));
6050 mInitializedOriginsInternal.AppendElement(aOriginMetadata.mOrigin);
6052 return std::pair(std::move(directory), created);
6055 return ExecuteOriginInitialization(
6056 aOriginMetadata.mOrigin, OriginInitialization::PersistentOrigin,
6057 "dom::quota::FirstOriginInitializationAttempt::PersistentOrigin"_ns,
6058 innerFunc);
6061 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryOrigin(
6062 const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent) {
6063 AssertIsOnOwningThread();
6064 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6066 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
6067 PersistenceScope::CreateFromValue(aOriginMetadata.mPersistenceType),
6068 OriginScope::FromOrigin(aOriginMetadata), Nullable<Client::Type>(),
6069 /* aExclusive */ false);
6071 auto prepareInfo = directoryLock->Prepare();
6073 // If temporary origin is initialized but there's a clear storage, shutdown
6074 // storage, clear origin, or shutdown origin operation already scheduled, we
6075 // can't immediately resolve the promise and return from the function because
6076 // the clear and shutdown storage operations uninitialize storage (which also
6077 // includes uninitialization of origins) and because clear and shutdown origin
6078 // operations uninitialize origins directly.
6079 if (IsTemporaryOriginInitialized(aOriginMetadata) &&
6080 !IsDirectoryLockBlockedByUninitStorageOrUninitOriginsOperation(
6081 prepareInfo)) {
6082 return BoolPromise::CreateAndResolve(true, __func__);
6085 return directoryLock->Acquire(std::move(prepareInfo))
6086 ->Then(GetCurrentSerialEventTarget(), __func__,
6087 [self = RefPtr(this), aOriginMetadata, aCreateIfNonExistent,
6088 directoryLock](
6089 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
6090 if (aValue.IsReject()) {
6091 return BoolPromise::CreateAndReject(aValue.RejectValue(),
6092 __func__);
6095 return self->InitializeTemporaryOrigin(aOriginMetadata,
6096 aCreateIfNonExistent,
6097 std::move(directoryLock));
6101 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryOrigin(
6102 const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent,
6103 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
6104 AssertIsOnOwningThread();
6105 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6106 MOZ_ASSERT(aDirectoryLock);
6107 MOZ_ASSERT(aDirectoryLock->Acquired());
6109 // If temporary origin is initialized and the directory lock for the
6110 // initialize temporary origin operation is acquired, we can immediately
6111 // resolve the promise and return from the function because there can't be a
6112 // clear storage, shutdown storage, clear origin, or shutdown origin
6113 // operation which would uninitialize storage (which also includes
6114 // uninitialization of origins), or which would uninitialize origins
6115 // directly.
6116 if (IsTemporaryOriginInitialized(aOriginMetadata)) {
6117 DropDirectoryLock(aDirectoryLock);
6119 return BoolPromise::CreateAndResolve(true, __func__);
6122 auto initializeTemporaryOriginOp = CreateInitializeTemporaryOriginOp(
6123 WrapMovingNotNullUnchecked(this), aOriginMetadata, aCreateIfNonExistent,
6124 std::move(aDirectoryLock));
6126 RegisterNormalOriginOp(*initializeTemporaryOriginOp);
6128 initializeTemporaryOriginOp->RunImmediately();
6130 return Map<BoolPromise>(
6131 initializeTemporaryOriginOp->OnResults(),
6132 [self = RefPtr(this), persistenceType = aOriginMetadata.mPersistenceType,
6133 origin = aOriginMetadata.mOrigin](
6134 const BoolPromise::ResolveOrRejectValue& aValue) {
6135 self->NoteInitializedOrigin(persistenceType, origin);
6137 return aValue.ResolveValue();
6141 RefPtr<BoolPromise> QuotaManager::TemporaryOriginInitialized(
6142 const OriginMetadata& aOriginMetadata) {
6143 AssertIsOnOwningThread();
6144 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6146 auto temporaryOriginInitializedOp = CreateTemporaryOriginInitializedOp(
6147 WrapMovingNotNullUnchecked(this), aOriginMetadata);
6149 RegisterNormalOriginOp(*temporaryOriginInitializedOp);
6151 temporaryOriginInitializedOp->RunImmediately();
6153 return temporaryOriginInitializedOp->OnResults();
6156 bool QuotaManager::IsTemporaryOriginInitialized(
6157 const OriginMetadata& aOriginMetadata) {
6158 AssertIsOnOwningThread();
6159 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6161 return IsOriginInitialized(aOriginMetadata.mPersistenceType,
6162 aOriginMetadata.mOrigin);
6165 bool QuotaManager::IsTemporaryOriginInitializedInternal(
6166 const OriginMetadata& aOriginMetadata) const {
6167 AssertIsOnIOThread();
6168 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6170 MutexAutoLock lock(mQuotaMutex);
6172 RefPtr<OriginInfo> originInfo =
6173 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
6175 return static_cast<bool>(originInfo);
6178 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
6179 QuotaManager::EnsureTemporaryOriginIsInitializedInternal(
6180 const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent) {
6181 AssertIsOnIOThread();
6182 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6183 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6184 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
6186 const auto innerFunc = [&aOriginMetadata, aCreateIfNonExistent,
6187 this](const auto&)
6188 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
6189 // Get directory for this origin and persistence type.
6190 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
6192 if (IsTemporaryOriginInitializedInternal(aOriginMetadata)) {
6193 return std::pair(std::move(directory), false);
6196 if (!aCreateIfNonExistent) {
6197 const int64_t timestamp = PR_Now();
6199 InitQuotaForOrigin(FullOriginMetadata{aOriginMetadata,
6200 /* aPersisted */ false, timestamp},
6201 ClientUsageArray(), /* aUsageBytes */ 0,
6202 /* aDirectoryExists */ false);
6204 return std::pair(std::move(directory), false);
6207 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
6209 if (created) {
6210 const int64_t timestamp = PR_Now();
6212 FullOriginMetadata fullOriginMetadata =
6213 FullOriginMetadata{aOriginMetadata,
6214 /* aPersisted */ false, timestamp};
6216 // Usually, infallible operations are placed after fallible ones.
6217 // However, since we lack atomic support for creating the origin
6218 // directory along with its metadata, we need to add the origin to cached
6219 // origins right after directory creation.
6220 AddTemporaryOrigin(fullOriginMetadata);
6222 // Only creating .metadata-v2 to reduce IO.
6223 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
6224 /* aPersisted */ false,
6225 aOriginMetadata)));
6227 // Don't need to traverse the directory, since it's empty.
6228 InitQuotaForOrigin(fullOriginMetadata, ClientUsageArray(),
6229 /* aUsageBytes */ 0);
6230 } else {
6231 QM_TRY_INSPECT(const auto& metadata,
6232 LoadFullOriginMetadataWithRestore(directory));
6234 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(metadata.mPersistenceType, metadata,
6235 metadata.mLastAccessTime,
6236 metadata.mPersisted, directory)));
6239 // TODO: If the metadata file exists and we didn't call
6240 // LoadFullOriginMetadataWithRestore for it (because the quota info
6241 // was loaded from the cache), then the group in the metadata file
6242 // may be wrong, so it should be checked and eventually updated.
6243 // It's not a big deal that we are not doing it here, because the
6244 // origin will be marked as "accessed", so
6245 // LoadFullOriginMetadataWithRestore will be called for the metadata
6246 // file in next session in LoadQuotaFromCache.
6247 // (If a previous origin initialization failed, we actually do call
6248 // LoadFullOriginMetadataWithRestore which updates the group)
6250 return std::pair(std::move(directory), created);
6253 return ExecuteOriginInitialization(
6254 aOriginMetadata.mOrigin, OriginInitialization::TemporaryOrigin,
6255 "dom::quota::FirstOriginInitializationAttempt::TemporaryOrigin"_ns,
6256 innerFunc);
6259 RefPtr<BoolPromise> QuotaManager::InitializePersistentClient(
6260 const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) {
6261 AssertIsOnOwningThread();
6263 auto initializePersistentClientOp = CreateInitializePersistentClientOp(
6264 WrapMovingNotNullUnchecked(this), aPrincipalInfo, aClientType);
6266 RegisterNormalOriginOp(*initializePersistentClientOp);
6268 initializePersistentClientOp->RunImmediately();
6270 return initializePersistentClientOp->OnResults();
6273 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
6274 QuotaManager::EnsurePersistentClientIsInitialized(
6275 const ClientMetadata& aClientMetadata) {
6276 AssertIsOnIOThread();
6277 MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
6278 MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
6279 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
6280 MOZ_DIAGNOSTIC_ASSERT(
6281 IsPersistentOriginInitializedInternal(aClientMetadata.mOrigin));
6283 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
6285 QM_TRY(MOZ_TO_RESULT(
6286 directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
6288 QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
6290 return std::pair(std::move(directory), created);
6293 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryClient(
6294 PersistenceType aPersistenceType, const PrincipalInfo& aPrincipalInfo,
6295 Client::Type aClientType) {
6296 AssertIsOnOwningThread();
6298 auto initializeTemporaryClientOp = CreateInitializeTemporaryClientOp(
6299 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo,
6300 aClientType);
6302 RegisterNormalOriginOp(*initializeTemporaryClientOp);
6304 initializeTemporaryClientOp->RunImmediately();
6306 return initializeTemporaryClientOp->OnResults();
6309 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
6310 QuotaManager::EnsureTemporaryClientIsInitialized(
6311 const ClientMetadata& aClientMetadata) {
6312 AssertIsOnIOThread();
6313 MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6314 MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
6315 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
6316 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal());
6317 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryOriginInitializedInternal(aClientMetadata));
6319 QM_TRY_UNWRAP(auto directory,
6320 GetOrCreateTemporaryOriginDirectory(aClientMetadata));
6322 QM_TRY(MOZ_TO_RESULT(
6323 directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
6325 QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
6327 return std::pair(std::move(directory), created);
6330 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage() {
6331 AssertIsOnOwningThread();
6333 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
6334 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY,
6335 PERSISTENCE_TYPE_DEFAULT),
6336 OriginScope::FromNull(), Nullable<Client::Type>(),
6337 /* aExclusive */ false);
6339 auto prepareInfo = directoryLock->Prepare();
6341 // If temporary storage is initialized but there's a clear storage or
6342 // shutdown storage operation already scheduled, we can't immediately resolve
6343 // the promise and return from the function because the clear and shutdown
6344 // storage operation uninitializes storage.
6345 if (mTemporaryStorageInitialized &&
6346 !IsDirectoryLockBlockedByUninitStorageOperation(prepareInfo)) {
6347 return BoolPromise::CreateAndResolve(true, __func__);
6350 return directoryLock->Acquire(std::move(prepareInfo))
6351 ->Then(
6352 GetCurrentSerialEventTarget(), __func__,
6353 [self = RefPtr(this), directoryLock](
6354 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
6355 if (aValue.IsReject()) {
6356 return BoolPromise::CreateAndReject(aValue.RejectValue(),
6357 __func__);
6360 return self->InitializeTemporaryStorage(std::move(directoryLock));
6364 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage(
6365 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
6366 AssertIsOnOwningThread();
6367 MOZ_ASSERT(aDirectoryLock);
6368 MOZ_ASSERT(aDirectoryLock->Acquired());
6370 // If temporary storage is initialized and the directory lock for the
6371 // initialize temporary storage operation is acquired, we can immediately
6372 // resolve the promise and return from the function because there can't be a
6373 // clear storage or shutdown storage operation which would uninitialize
6374 // temporary storage.
6375 if (mTemporaryStorageInitialized) {
6376 DropDirectoryLock(aDirectoryLock);
6378 return BoolPromise::CreateAndResolve(true, __func__);
6381 auto initializeTemporaryStorageOp = CreateInitTemporaryStorageOp(
6382 WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
6384 RegisterNormalOriginOp(*initializeTemporaryStorageOp);
6386 initializeTemporaryStorageOp->RunImmediately();
6388 return initializeTemporaryStorageOp->OnResults()->Then(
6389 GetCurrentSerialEventTarget(), __func__,
6390 [self = RefPtr(this)](
6391 MaybePrincipalMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
6392 if (aValue.IsReject()) {
6393 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
6396 self->mTemporaryStorageInitialized = true;
6398 if (aValue.ResolveValue() &&
6399 QuotaPrefs::LazyOriginInitializationEnabled()) {
6400 self->mBackgroundThreadAccessible.Access()->mUninitializedGroups =
6401 aValue.ResolveValue().extract();
6403 if (QuotaPrefs::TriggerOriginInitializationInBackgroundEnabled()) {
6404 self->InitializeAllTemporaryOrigins();
6408 return BoolPromise::CreateAndResolve(true, __func__);
6412 nsresult QuotaManager::EnsureTemporaryStorageIsInitializedInternal() {
6413 AssertIsOnIOThread();
6414 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6416 const auto innerFunc =
6417 [&](const auto& firstInitializationAttempt) -> nsresult {
6418 if (mTemporaryStorageInitializedInternal) {
6419 MOZ_ASSERT(firstInitializationAttempt.Recorded());
6420 return NS_OK;
6423 nsCOMPtr<nsIFile> storageDir;
6424 QM_TRY(MOZ_TO_RESULT(
6425 NS_NewLocalFile(GetStoragePath(), getter_AddRefs(storageDir))));
6427 // The storage directory must exist before calling GetTemporaryStorageLimit.
6428 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDir));
6430 Unused << created;
6432 QM_TRY_UNWRAP(mTemporaryStorageLimit,
6433 GetTemporaryStorageLimit(*storageDir));
6435 QM_TRY(MOZ_TO_RESULT(LoadQuota()));
6437 mTemporaryStorageInitializedInternal = true;
6439 // If origin initialization is done lazily, then there's either no quota
6440 // information at this point (if the cache couldn't be used) or only
6441 // partial quota information (origins accessed in a previous session
6442 // require full initialization). Given that, the cleanup can't be done
6443 // at this point yet.
6444 if (!QuotaPrefs::LazyOriginInitializationEnabled()) {
6445 CleanupTemporaryStorage();
6448 if (mCacheUsable) {
6449 QM_TRY(InvalidateCache(*mStorageConnection));
6452 return NS_OK;
6455 return ExecuteInitialization(
6456 Initialization::TemporaryStorage,
6457 "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns, innerFunc);
6460 RefPtr<BoolPromise> QuotaManager::InitializeAllTemporaryOrigins() {
6461 AssertIsOnOwningThread();
6463 if (mAllTemporaryOriginsInitialized) {
6464 return BoolPromise::CreateAndResolve(true, __func__);
6467 if (!mInitializingAllTemporaryOrigins) {
6468 // TODO: make a copy of mUninitializedGroups and use it as the queue.
6470 mInitializingAllTemporaryOrigins = true;
6472 auto processNextGroup = [self = RefPtr(this)](
6473 auto&& processNextGroupCallback) {
6474 // TODO: Add shutdown checks.
6476 auto backgroundThreadData = self->mBackgroundThreadAccessible.Access();
6478 if (backgroundThreadData->mUninitializedGroups.IsEmpty()) {
6479 self->mInitializingAllTemporaryOrigins = false;
6480 self->mAllTemporaryOriginsInitialized = true;
6482 self->mInitializeAllTemporaryOriginsPromiseHolder.ResolveIfExists(
6483 true, __func__);
6485 return;
6488 auto principalMetadata =
6489 backgroundThreadData->mUninitializedGroups.PopLastElement();
6491 self->InitializeTemporaryGroup(principalMetadata)
6492 ->Then(GetCurrentSerialEventTarget(), __func__,
6493 [processNextGroupCallback](
6494 const BoolPromise::ResolveOrRejectValue& aValue) {
6495 // TODO: Remove the group from mUninitializedGroups only
6496 // when the group initialization succeeded.
6498 processNextGroupCallback(processNextGroupCallback);
6502 processNextGroup(/* processNextGroupCallback */ processNextGroup);
6505 return mInitializeAllTemporaryOriginsPromiseHolder.Ensure(__func__);
6508 RefPtr<OriginUsageMetadataArrayPromise> QuotaManager::GetUsage(
6509 bool aGetAll, RefPtr<BoolPromise> aOnCancelPromise) {
6510 AssertIsOnOwningThread();
6512 auto getUsageOp = CreateGetUsageOp(WrapMovingNotNullUnchecked(this), aGetAll);
6514 RegisterNormalOriginOp(*getUsageOp);
6516 getUsageOp->RunImmediately();
6518 if (aOnCancelPromise) {
6519 RefPtr<BoolPromise> onCancelPromise = std::move(aOnCancelPromise);
6521 onCancelPromise->Then(
6522 GetCurrentSerialEventTarget(), __func__,
6523 [getUsageOp](const BoolPromise::ResolveOrRejectValue& aValue) {
6524 if (aValue.IsReject()) {
6525 return;
6528 if (getUsageOp->Cancel()) {
6529 NS_WARNING("Canceled more than once?!");
6534 return getUsageOp->OnResults();
6537 RefPtr<UsageInfoPromise> QuotaManager::GetOriginUsage(
6538 const PrincipalInfo& aPrincipalInfo, RefPtr<BoolPromise> aOnCancelPromise) {
6539 AssertIsOnOwningThread();
6541 auto getOriginUsageOp =
6542 CreateGetOriginUsageOp(WrapMovingNotNullUnchecked(this), aPrincipalInfo);
6544 RegisterNormalOriginOp(*getOriginUsageOp);
6546 getOriginUsageOp->RunImmediately();
6548 if (aOnCancelPromise) {
6549 RefPtr<BoolPromise> onCancelPromise = std::move(aOnCancelPromise);
6551 onCancelPromise->Then(
6552 GetCurrentSerialEventTarget(), __func__,
6553 [getOriginUsageOp](const BoolPromise::ResolveOrRejectValue& aValue) {
6554 if (aValue.IsReject()) {
6555 return;
6558 if (getOriginUsageOp->Cancel()) {
6559 NS_WARNING("Canceled more than once?!");
6564 return getOriginUsageOp->OnResults();
6567 RefPtr<UInt64Promise> QuotaManager::GetCachedOriginUsage(
6568 const PrincipalInfo& aPrincipalInfo) {
6569 AssertIsOnOwningThread();
6571 auto getCachedOriginUsageOp = CreateGetCachedOriginUsageOp(
6572 WrapMovingNotNullUnchecked(this), aPrincipalInfo);
6574 RegisterNormalOriginOp(*getCachedOriginUsageOp);
6576 getCachedOriginUsageOp->RunImmediately();
6578 return getCachedOriginUsageOp->OnResults();
6581 RefPtr<CStringArrayPromise> QuotaManager::ListOrigins() {
6582 AssertIsOnOwningThread();
6584 auto listOriginsOp = CreateListOriginsOp(WrapMovingNotNullUnchecked(this));
6586 RegisterNormalOriginOp(*listOriginsOp);
6588 listOriginsOp->RunImmediately();
6590 return listOriginsOp->OnResults();
6593 RefPtr<CStringArrayPromise> QuotaManager::ListCachedOrigins() {
6594 AssertIsOnOwningThread();
6596 auto listCachedOriginsOp =
6597 CreateListCachedOriginsOp(WrapMovingNotNullUnchecked(this));
6599 RegisterNormalOriginOp(*listCachedOriginsOp);
6601 listCachedOriginsOp->RunImmediately();
6603 return listCachedOriginsOp->OnResults();
6606 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOrigin(
6607 const Maybe<PersistenceType>& aPersistenceType,
6608 const PrincipalInfo& aPrincipalInfo) {
6609 AssertIsOnOwningThread();
6611 auto clearOriginOp = CreateClearOriginOp(WrapMovingNotNullUnchecked(this),
6612 aPersistenceType, aPrincipalInfo);
6614 RegisterNormalOriginOp(*clearOriginOp);
6616 clearOriginOp->RunImmediately();
6618 return Map<BoolPromise>(
6619 clearOriginOp->OnResults(),
6620 [self = RefPtr(this)](
6621 OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
6622 self->NoteUninitializedOrigins(aValue.ResolveValue());
6624 return true;
6628 RefPtr<BoolPromise> QuotaManager::ClearStoragesForClient(
6629 Maybe<PersistenceType> aPersistenceType,
6630 const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) {
6631 AssertIsOnOwningThread();
6633 auto clearClientOp =
6634 CreateClearClientOp(WrapMovingNotNullUnchecked(this), aPersistenceType,
6635 aPrincipalInfo, aClientType);
6637 RegisterNormalOriginOp(*clearClientOp);
6639 clearClientOp->RunImmediately();
6641 return clearClientOp->OnResults();
6644 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginPrefix(
6645 const Maybe<PersistenceType>& aPersistenceType,
6646 const PrincipalInfo& aPrincipalInfo) {
6647 AssertIsOnOwningThread();
6649 auto clearStoragesForOriginPrefixOp = CreateClearStoragesForOriginPrefixOp(
6650 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo);
6652 RegisterNormalOriginOp(*clearStoragesForOriginPrefixOp);
6654 clearStoragesForOriginPrefixOp->RunImmediately();
6656 return Map<BoolPromise>(
6657 clearStoragesForOriginPrefixOp->OnResults(),
6658 [self = RefPtr(this)](
6659 OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
6660 self->NoteUninitializedOrigins(aValue.ResolveValue());
6662 return true;
6666 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginAttributesPattern(
6667 const OriginAttributesPattern& aPattern) {
6668 AssertIsOnOwningThread();
6670 auto clearDataOp =
6671 CreateClearDataOp(WrapMovingNotNullUnchecked(this), aPattern);
6673 RegisterNormalOriginOp(*clearDataOp);
6675 clearDataOp->RunImmediately();
6677 return Map<BoolPromise>(
6678 clearDataOp->OnResults(),
6679 [self = RefPtr(this)](
6680 OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
6681 self->NoteUninitializedOrigins(aValue.ResolveValue());
6683 return true;
6687 RefPtr<BoolPromise> QuotaManager::ClearPrivateRepository() {
6688 AssertIsOnOwningThread();
6690 auto clearPrivateRepositoryOp =
6691 CreateClearPrivateRepositoryOp(WrapMovingNotNullUnchecked(this));
6693 RegisterNormalOriginOp(*clearPrivateRepositoryOp);
6695 clearPrivateRepositoryOp->RunImmediately();
6697 return Map<BoolPromise>(
6698 clearPrivateRepositoryOp->OnResults(),
6699 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
6700 self->NoteUninitializedRepository(PERSISTENCE_TYPE_PRIVATE);
6702 return aValue.ResolveValue();
6706 RefPtr<BoolPromise> QuotaManager::ClearStorage() {
6707 AssertIsOnOwningThread();
6709 auto clearStorageOp = CreateClearStorageOp(WrapMovingNotNullUnchecked(this));
6711 RegisterNormalOriginOp(*clearStorageOp);
6713 clearStorageOp->RunImmediately();
6715 return clearStorageOp->OnResults()->Then(
6716 GetCurrentSerialEventTarget(), __func__,
6717 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
6718 if (aValue.IsReject()) {
6719 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
6722 self->mInitializedOrigins.Clear();
6723 self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Clear();
6724 self->mAllTemporaryOriginsInitialized = false;
6725 self->mTemporaryStorageInitialized = false;
6726 self->mStorageInitialized = false;
6728 return BoolPromise::CreateAndResolve(true, __func__);
6732 RefPtr<BoolPromise> QuotaManager::ShutdownStoragesForOrigin(
6733 Maybe<PersistenceType> aPersistenceType,
6734 const PrincipalInfo& aPrincipalInfo) {
6735 AssertIsOnOwningThread();
6737 auto shutdownOriginOp = CreateShutdownOriginOp(
6738 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo);
6740 RegisterNormalOriginOp(*shutdownOriginOp);
6742 shutdownOriginOp->RunImmediately();
6744 return Map<BoolPromise>(
6745 shutdownOriginOp->OnResults(),
6746 [self = RefPtr(this)](
6747 OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) {
6748 self->NoteUninitializedOrigins(aValue.ResolveValue());
6750 return true;
6754 RefPtr<BoolPromise> QuotaManager::ShutdownStoragesForClient(
6755 Maybe<PersistenceType> aPersistenceType,
6756 const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) {
6757 AssertIsOnOwningThread();
6759 auto shutdownClientOp =
6760 CreateShutdownClientOp(WrapMovingNotNullUnchecked(this), aPersistenceType,
6761 aPrincipalInfo, aClientType);
6763 RegisterNormalOriginOp(*shutdownClientOp);
6765 shutdownClientOp->RunImmediately();
6767 return shutdownClientOp->OnResults();
6770 RefPtr<BoolPromise> QuotaManager::ShutdownStorage(
6771 Maybe<OriginOperationCallbackOptions> aCallbackOptions,
6772 Maybe<OriginOperationCallbacks&> aCallbacks) {
6773 AssertIsOnOwningThread();
6775 auto shutdownStorageOp =
6776 CreateShutdownStorageOp(WrapMovingNotNullUnchecked(this));
6778 RegisterNormalOriginOp(*shutdownStorageOp);
6780 shutdownStorageOp->RunImmediately();
6782 if (aCallbackOptions.isSome() && aCallbacks.isSome()) {
6783 aCallbacks.ref() = shutdownStorageOp->GetCallbacks(aCallbackOptions.ref());
6786 return shutdownStorageOp->OnResults()->Then(
6787 GetCurrentSerialEventTarget(), __func__,
6788 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
6789 if (aValue.IsReject()) {
6790 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
6793 self->mInitializedOrigins.Clear();
6794 self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Clear();
6795 self->mAllTemporaryOriginsInitialized = false;
6796 self->mTemporaryStorageInitialized = false;
6797 self->mStorageInitialized = false;
6799 return BoolPromise::CreateAndResolve(true, __func__);
6803 void QuotaManager::ShutdownStorageInternal() {
6804 AssertIsOnIOThread();
6806 if (mStorageConnection) {
6807 mInitializationInfo.ResetOriginInitializationInfos();
6808 mInitializedOriginsInternal.Clear();
6810 if (mTemporaryStorageInitializedInternal) {
6811 if (mCacheUsable) {
6812 UnloadQuota();
6813 } else {
6814 RemoveQuota();
6817 RemoveTemporaryOrigins();
6819 mTemporaryStorageInitializedInternal = false;
6822 ReleaseIOThreadObjects();
6824 mStorageConnection = nullptr;
6825 mCacheUsable = false;
6828 mInitializationInfo.ResetFirstInitializationAttempts();
6831 Result<bool, nsresult> QuotaManager::EnsureOriginDirectory(
6832 nsIFile& aDirectory) {
6833 AssertIsOnIOThread();
6835 QM_TRY_INSPECT(const bool& exists,
6836 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
6838 if (!exists) {
6839 QM_TRY_INSPECT(
6840 const auto& leafName,
6841 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aDirectory, GetLeafName)
6842 .map([](const auto& leafName) {
6843 return NS_ConvertUTF16toUTF8(leafName);
6844 }));
6846 QM_TRY(OkIf(IsSanitizedOriginValid(leafName)), Err(NS_ERROR_FAILURE),
6847 [](const auto&) {
6848 QM_WARNING(
6849 "Preventing creation of a new origin directory which is not "
6850 "supported by our origin parser or is obsolete!");
6854 QM_TRY_RETURN(EnsureDirectory(aDirectory));
6857 nsresult QuotaManager::AboutToClearOrigins(
6858 const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope,
6859 const Nullable<Client::Type>& aClientType) {
6860 AssertIsOnIOThread();
6862 if (aClientType.IsNull()) {
6863 for (Client::Type type : AllClientTypes()) {
6864 QM_TRY(MOZ_TO_RESULT((*mClients)[type]->AboutToClearOrigins(
6865 aPersistenceScope, aOriginScope)));
6867 } else {
6868 QM_TRY(MOZ_TO_RESULT((*mClients)[aClientType.Value()]->AboutToClearOrigins(
6869 aPersistenceScope, aOriginScope)));
6872 return NS_OK;
6875 void QuotaManager::OriginClearCompleted(
6876 const OriginMetadata& aOriginMetadata,
6877 const Nullable<Client::Type>& aClientType) {
6878 AssertIsOnIOThread();
6880 if (aClientType.IsNull()) {
6881 if (aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
6882 mInitializedOriginsInternal.RemoveElement(aOriginMetadata.mOrigin);
6883 } else {
6884 RemoveTemporaryOrigin(aOriginMetadata);
6887 for (Client::Type type : AllClientTypes()) {
6888 (*mClients)[type]->OnOriginClearCompleted(aOriginMetadata);
6890 } else {
6891 (*mClients)[aClientType.Value()]->OnOriginClearCompleted(aOriginMetadata);
6895 void QuotaManager::RepositoryClearCompleted(PersistenceType aPersistenceType) {
6896 AssertIsOnIOThread();
6898 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
6899 mInitializedOriginsInternal.Clear();
6900 } else {
6901 RemoveTemporaryOrigins(aPersistenceType);
6904 for (Client::Type type : AllClientTypes()) {
6905 (*mClients)[type]->OnRepositoryClearCompleted(aPersistenceType);
6909 Client* QuotaManager::GetClient(Client::Type aClientType) {
6910 MOZ_ASSERT(aClientType >= Client::IDB);
6911 MOZ_ASSERT(aClientType < Client::TypeMax());
6913 return (*mClients)[aClientType];
6916 const AutoTArray<Client::Type, Client::TYPE_MAX>&
6917 QuotaManager::AllClientTypes() {
6918 if (CachedNextGenLocalStorageEnabled()) {
6919 return *mAllClientTypes;
6921 return *mAllClientTypesExceptLS;
6924 bool QuotaManager::IsThumbnailPrivateIdentityIdKnown() const {
6925 AssertIsOnIOThread();
6927 return mIOThreadAccessible.Access()->mThumbnailPrivateIdentityId.isSome();
6930 uint32_t QuotaManager::GetThumbnailPrivateIdentityId() const {
6931 AssertIsOnIOThread();
6933 return mIOThreadAccessible.Access()->mThumbnailPrivateIdentityId.ref();
6936 void QuotaManager::SetThumbnailPrivateIdentityId(
6937 uint32_t aThumbnailPrivateIdentityId) {
6938 AssertIsOnIOThread();
6940 auto ioThreadData = mIOThreadAccessible.Access();
6942 ioThreadData->mThumbnailPrivateIdentityId = Some(aThumbnailPrivateIdentityId);
6943 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount = 0;
6945 for (auto iter = ioThreadData->mAllTemporaryOrigins.Iter(); !iter.Done();
6946 iter.Next()) {
6947 auto& array = iter.Data();
6949 for (const auto& originMetadata : array) {
6950 if (IsUserContextSuffix(originMetadata.mSuffix,
6951 GetThumbnailPrivateIdentityId())) {
6952 AssertNoOverflow(
6953 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1);
6954 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount++;
6960 uint64_t QuotaManager::GetGroupLimit() const {
6961 // To avoid one group evicting all the rest, limit the amount any one group
6962 // can use to 20% resp. a fifth. To prevent individual sites from using
6963 // exorbitant amounts of storage where there is a lot of free space, cap the
6964 // group limit to 10GB.
6965 const auto x = std::min<uint64_t>(mTemporaryStorageLimit / 5, 10 GB);
6967 // In low-storage situations, make an exception (while not exceeding the total
6968 // storage limit).
6969 return std::min<uint64_t>(mTemporaryStorageLimit,
6970 std::max<uint64_t>(x, 10 MB));
6973 std::pair<uint64_t, uint64_t> QuotaManager::GetUsageAndLimitForEstimate(
6974 const OriginMetadata& aOriginMetadata) {
6975 AssertIsOnIOThread();
6977 uint64_t totalGroupUsage = 0;
6980 MutexAutoLock lock(mQuotaMutex);
6982 GroupInfoPair* pair;
6983 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6984 for (const PersistenceType type : kBestEffortPersistenceTypes) {
6985 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
6986 if (groupInfo) {
6987 if (type == PERSISTENCE_TYPE_DEFAULT) {
6988 RefPtr<OriginInfo> originInfo =
6989 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
6991 if (originInfo && originInfo->LockedPersisted()) {
6992 return std::pair(mTemporaryStorageUsage, mTemporaryStorageLimit);
6996 AssertNoOverflow(totalGroupUsage, groupInfo->mUsage);
6997 totalGroupUsage += groupInfo->mUsage;
7003 return std::pair(totalGroupUsage, GetGroupLimit());
7006 uint64_t QuotaManager::GetOriginUsage(
7007 const PrincipalMetadata& aPrincipalMetadata) {
7008 AssertIsOnIOThread();
7010 uint64_t usage = 0;
7013 MutexAutoLock lock(mQuotaMutex);
7015 GroupInfoPair* pair;
7016 if (mGroupInfoPairs.Get(aPrincipalMetadata.mGroup, &pair)) {
7017 for (const PersistenceType type : kBestEffortPersistenceTypes) {
7018 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
7019 if (groupInfo) {
7020 RefPtr<OriginInfo> originInfo =
7021 groupInfo->LockedGetOriginInfo(aPrincipalMetadata.mOrigin);
7022 if (originInfo) {
7023 AssertNoOverflow(usage, originInfo->LockedUsage());
7024 usage += originInfo->LockedUsage();
7031 return usage;
7034 Maybe<FullOriginMetadata> QuotaManager::GetFullOriginMetadata(
7035 const OriginMetadata& aOriginMetadata) {
7036 AssertIsOnIOThread();
7037 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
7038 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
7040 MutexAutoLock lock(mQuotaMutex);
7042 RefPtr<OriginInfo> originInfo =
7043 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
7044 if (originInfo) {
7045 return Some(originInfo->LockedFlattenToFullOriginMetadata());
7048 return Nothing();
7051 uint64_t QuotaManager::TotalDirectoryIterations() const {
7052 AssertIsOnIOThread();
7054 return mIOThreadAccessible.Access()->mTotalDirectoryIterations;
7057 // static
7058 void QuotaManager::GetStorageId(PersistenceType aPersistenceType,
7059 const nsACString& aOrigin,
7060 Client::Type aClientType,
7061 nsACString& aDatabaseId) {
7062 nsAutoCString str;
7063 str.AppendInt(aPersistenceType);
7064 str.Append('*');
7065 str.Append(aOrigin);
7066 str.Append('*');
7067 str.AppendInt(aClientType);
7069 aDatabaseId = str;
7072 // static
7073 bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) {
7074 MOZ_ASSERT(!aOrigin.IsEmpty());
7076 // The first prompt is not required for these origins.
7077 if (aOrigin.EqualsLiteral(kChromeOrigin) ||
7078 StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
7079 StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
7080 StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
7081 return true;
7084 return false;
7087 // static
7088 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString& aOrigin1,
7089 const nsACString& aOrigin2) {
7090 return MakeSanitizedOriginCString(aOrigin1) ==
7091 MakeSanitizedOriginCString(aOrigin2);
7094 // static
7095 Result<PrincipalInfo, nsresult> QuotaManager::ParseOrigin(
7096 const nsACString& aOrigin) {
7097 // An origin string either corresponds to a SystemPrincipalInfo or a
7098 // ContentPrincipalInfo, see
7099 // QuotaManager::GetOriginFromValidatedPrincipalInfo.
7101 nsCString spec;
7102 OriginAttributes attrs;
7103 nsCString originalSuffix;
7104 const OriginParser::ResultType result = OriginParser::ParseOrigin(
7105 MakeSanitizedOriginCString(aOrigin), spec, &attrs, originalSuffix);
7106 QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
7108 QM_TRY_INSPECT(
7109 const auto& principal,
7110 ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
7111 if (spec.EqualsLiteral(kChromeOrigin)) {
7112 return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
7115 nsCOMPtr<nsIURI> uri;
7116 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
7118 return nsCOMPtr<nsIPrincipal>(
7119 BasePrincipal::CreateContentPrincipal(uri, attrs));
7120 }()));
7121 QM_TRY(MOZ_TO_RESULT(principal));
7123 PrincipalInfo principalInfo;
7124 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
7126 return std::move(principalInfo);
7129 // static
7130 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache = true; }
7132 uint64_t QuotaManager::LockedCollectOriginsForEviction(
7133 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks)
7134 MOZ_REQUIRES(mQuotaMutex) {
7135 mQuotaMutex.AssertCurrentThreadOwns();
7137 RefPtr<CollectOriginsHelper> helper =
7138 new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
7140 // Unlock while calling out to XPCOM (code behind the dispatch method needs
7141 // to acquire its own lock which can potentially lead to a deadlock and it
7142 // also calls an observer that can do various stuff like IO, so it's better
7143 // to not hold our mutex while that happens).
7145 MutexAutoUnlock autoUnlock(mQuotaMutex);
7147 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
7150 return helper->BlockAndReturnOriginsForEviction(aLocks);
7153 void QuotaManager::LockedRemoveQuotaForRepository(
7154 PersistenceType aPersistenceType) {
7155 mQuotaMutex.AssertCurrentThreadOwns();
7156 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
7158 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
7159 auto& pair = iter.Data();
7161 if (RefPtr<GroupInfo> groupInfo =
7162 pair->LockedGetGroupInfo(aPersistenceType)) {
7163 groupInfo->LockedRemoveOriginInfos();
7165 pair->LockedClearGroupInfo(aPersistenceType);
7167 if (!pair->LockedHasGroupInfos()) {
7168 iter.Remove();
7174 void QuotaManager::LockedRemoveQuotaForOrigin(
7175 const OriginMetadata& aOriginMetadata) {
7176 mQuotaMutex.AssertCurrentThreadOwns();
7177 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
7179 GroupInfoPair* pair;
7180 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
7181 return;
7184 MOZ_ASSERT(pair);
7186 if (RefPtr<GroupInfo> groupInfo =
7187 pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType)) {
7188 groupInfo->LockedRemoveOriginInfo(aOriginMetadata.mOrigin);
7190 if (!groupInfo->LockedHasOriginInfos()) {
7191 pair->LockedClearGroupInfo(aOriginMetadata.mPersistenceType);
7193 if (!pair->LockedHasGroupInfos()) {
7194 mGroupInfoPairs.Remove(aOriginMetadata.mGroup);
7200 bool QuotaManager::LockedHasGroupInfoPair(const nsACString& aGroup) const {
7201 mQuotaMutex.AssertCurrentThreadOwns();
7203 return mGroupInfoPairs.Get(aGroup, nullptr);
7206 already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo(
7207 PersistenceType aPersistenceType, const nsACString& aSuffix,
7208 const nsACString& aGroup) {
7209 mQuotaMutex.AssertCurrentThreadOwns();
7210 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
7212 GroupInfoPair* const pair =
7213 mGroupInfoPairs.GetOrInsertNew(aGroup, aSuffix, aGroup);
7215 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
7216 if (!groupInfo) {
7217 groupInfo = new GroupInfo(pair, aPersistenceType);
7218 pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
7221 return groupInfo.forget();
7224 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
7225 PersistenceType aPersistenceType,
7226 const OriginMetadata& aOriginMetadata) const {
7227 mQuotaMutex.AssertCurrentThreadOwns();
7228 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
7230 GroupInfoPair* pair;
7231 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
7232 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
7233 if (groupInfo) {
7234 return groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
7238 return nullptr;
7241 template <typename Iterator>
7242 void QuotaManager::MaybeInsertNonPersistedOriginInfos(
7243 Iterator aDest, const RefPtr<GroupInfo>& aTemporaryGroupInfo,
7244 const RefPtr<GroupInfo>& aDefaultGroupInfo,
7245 const RefPtr<GroupInfo>& aPrivateGroupInfo) {
7246 const auto copy = [&aDest](const GroupInfo& groupInfo) {
7247 std::copy_if(
7248 groupInfo.mOriginInfos.cbegin(), groupInfo.mOriginInfos.cend(), aDest,
7249 [](const auto& originInfo) { return !originInfo->LockedPersisted(); });
7252 if (aTemporaryGroupInfo) {
7253 MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY ==
7254 aTemporaryGroupInfo->GetPersistenceType());
7256 copy(*aTemporaryGroupInfo);
7258 if (aDefaultGroupInfo) {
7259 MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT ==
7260 aDefaultGroupInfo->GetPersistenceType());
7262 copy(*aDefaultGroupInfo);
7264 if (aPrivateGroupInfo) {
7265 MOZ_ASSERT(PERSISTENCE_TYPE_PRIVATE ==
7266 aPrivateGroupInfo->GetPersistenceType());
7267 copy(*aPrivateGroupInfo);
7271 template <typename Collect, typename Pred>
7272 QuotaManager::OriginInfosFlatTraversable
7273 QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) {
7274 OriginInfosFlatTraversable originInfos;
7276 std::forward<Collect>(aCollect)(MakeBackInserter(originInfos));
7278 originInfos.Sort(OriginInfoAccessTimeComparator());
7280 const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(),
7281 std::forward<Pred>(aPred));
7283 originInfos.TruncateLength(foundIt - originInfos.cbegin());
7285 return originInfos;
7288 QuotaManager::OriginInfosNestedTraversable
7289 QuotaManager::GetOriginInfosExceedingGroupLimit() const {
7290 MutexAutoLock lock(mQuotaMutex);
7292 OriginInfosNestedTraversable originInfos;
7294 for (const auto& entry : mGroupInfoPairs) {
7295 const auto& pair = entry.GetData();
7297 MOZ_ASSERT(!entry.GetKey().IsEmpty());
7298 MOZ_ASSERT(pair);
7300 uint64_t groupUsage = 0;
7302 const RefPtr<GroupInfo> temporaryGroupInfo =
7303 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
7304 if (temporaryGroupInfo) {
7305 groupUsage += temporaryGroupInfo->mUsage;
7308 const RefPtr<GroupInfo> defaultGroupInfo =
7309 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
7310 if (defaultGroupInfo) {
7311 groupUsage += defaultGroupInfo->mUsage;
7314 const RefPtr<GroupInfo> privateGroupInfo =
7315 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
7316 if (privateGroupInfo) {
7317 groupUsage += privateGroupInfo->mUsage;
7320 if (groupUsage > 0) {
7321 QuotaManager* quotaManager = QuotaManager::Get();
7322 MOZ_ASSERT(quotaManager, "Shouldn't be null!");
7324 if (groupUsage > quotaManager->GetGroupLimit()) {
7325 originInfos.AppendElement(CollectLRUOriginInfosUntil(
7326 [&temporaryGroupInfo, &defaultGroupInfo,
7327 &privateGroupInfo](auto inserter) {
7328 MaybeInsertNonPersistedOriginInfos(
7329 std::move(inserter), temporaryGroupInfo, defaultGroupInfo,
7330 privateGroupInfo);
7332 [&groupUsage, quotaManager](const auto& originInfo) {
7333 groupUsage -= originInfo->LockedUsage();
7335 return groupUsage <= quotaManager->GetGroupLimit();
7336 }));
7341 return originInfos;
7344 QuotaManager::OriginInfosNestedTraversable
7345 QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
7346 MutexAutoLock lock(mQuotaMutex);
7348 QuotaManager::OriginInfosNestedTraversable res;
7349 res.AppendElement(CollectLRUOriginInfosUntil(
7350 // XXX The lambda only needs to capture this, but due to Bug 1421435 it
7351 // can't.
7352 [&](auto inserter) {
7353 for (const auto& entry : mGroupInfoPairs) {
7354 const auto& pair = entry.GetData();
7356 MOZ_ASSERT(!entry.GetKey().IsEmpty());
7357 MOZ_ASSERT(pair);
7359 MaybeInsertNonPersistedOriginInfos(
7360 inserter, pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY),
7361 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT),
7362 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE));
7365 [temporaryStorageUsage = mTemporaryStorageUsage,
7366 temporaryStorageLimit = mTemporaryStorageLimit,
7367 doomedUsage = uint64_t{0}](const auto& originInfo) mutable {
7368 if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) {
7369 return true;
7372 doomedUsage += originInfo->LockedUsage();
7373 return false;
7374 }));
7376 return res;
7379 void QuotaManager::ClearOrigins(
7380 const OriginInfosNestedTraversable& aDoomedOriginInfos) {
7381 AssertIsOnIOThread();
7383 // If we are in shutdown, we could break off early from clearing origins.
7384 // In such cases, we would like to track the ones that were already cleared
7385 // up, such that other essential cleanup could be performed on clearedOrigins.
7386 // clearedOrigins is used in calls to LockedRemoveQuotaForOrigin and
7387 // OriginClearCompleted below. We could have used a collection of OriginInfos
7388 // rather than flattening them to OriginMetadata but groupInfo in OriginInfo
7389 // is just a raw ptr and LockedRemoveQuotaForOrigin might delete groupInfo and
7390 // as a result, we would not be able to get origin persistence type required
7391 // in OriginClearCompleted call after lockedRemoveQuotaForOrigin call.
7392 nsTArray<OriginMetadata> clearedOrigins;
7394 // XXX Does this need to be done a) in order and/or b) sequentially?
7395 for (const auto& doomedOriginInfo :
7396 Flatten<OriginInfosFlatTraversable::value_type>(aDoomedOriginInfos)) {
7397 #ifdef DEBUG
7399 MutexAutoLock lock(mQuotaMutex);
7400 MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
7402 #endif
7404 // TODO: We are currently only checking for this flag here which
7405 // means that we cannot break off once we start cleaning an origin. It
7406 // could be better if we could check for shutdown flag while cleaning an
7407 // origin such that we could break off early from the cleaning process if
7408 // we are stuck cleaning on one huge origin. Bug1797098 has been filed to
7409 // track this.
7410 if (QuotaManager::IsShuttingDown()) {
7411 break;
7414 auto originMetadata = doomedOriginInfo->FlattenToOriginMetadata();
7416 DeleteOriginDirectory(originMetadata);
7418 clearedOrigins.AppendElement(std::move(originMetadata));
7422 MutexAutoLock lock(mQuotaMutex);
7424 for (const auto& clearedOrigin : clearedOrigins) {
7425 LockedRemoveQuotaForOrigin(clearedOrigin);
7429 for (const auto& clearedOrigin : clearedOrigins) {
7430 OriginClearCompleted(clearedOrigin, Nullable<Client::Type>());
7434 void QuotaManager::CleanupTemporaryStorage() {
7435 AssertIsOnIOThread();
7437 // Evicting origins that exceed their group limit also affects the global
7438 // temporary storage usage, so these steps have to be taken sequentially.
7439 // Combining them doesn't seem worth the added complexity.
7440 ClearOrigins(GetOriginInfosExceedingGroupLimit());
7441 ClearOrigins(GetOriginInfosExceedingGlobalLimit());
7443 if (mTemporaryStorageUsage > mTemporaryStorageLimit) {
7444 // If disk space is still low after origin clear, notify storage pressure.
7445 NotifyStoragePressure(*this, mTemporaryStorageUsage);
7449 void QuotaManager::DeleteOriginDirectory(
7450 const OriginMetadata& aOriginMetadata) {
7451 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
7452 QM_VOID);
7454 nsresult rv = directory->Remove(true);
7455 if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
7456 // This should never fail if we've closed all storage connections
7457 // correctly...
7458 NS_ERROR("Failed to remove directory!");
7462 void QuotaManager::FinalizeOriginEviction(
7463 nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) {
7464 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
7466 auto finalizeOriginEviction = [locks = std::move(aLocks)]() mutable {
7467 QuotaManager* quotaManager = QuotaManager::Get();
7468 MOZ_ASSERT(quotaManager);
7470 RefPtr<OriginOperationBase> op = CreateFinalizeOriginEvictionOp(
7471 WrapMovingNotNull(quotaManager), std::move(locks));
7473 op->RunImmediately();
7476 if (IsOnBackgroundThread()) {
7477 finalizeOriginEviction();
7478 } else {
7479 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(
7480 NS_NewRunnableFunction(
7481 "dom::quota::QuotaManager::FinalizeOriginEviction",
7482 std::move(finalizeOriginEviction)),
7483 NS_DISPATCH_NORMAL));
7487 Result<Ok, nsresult> QuotaManager::ArchiveOrigins(
7488 const nsTArray<FullOriginMetadata>& aFullOriginMetadatas) {
7489 AssertIsOnIOThread();
7490 MOZ_ASSERT(!aFullOriginMetadatas.IsEmpty());
7492 QM_TRY_INSPECT(const auto& storageArchivesDir,
7493 QM_NewLocalFile(*mStorageArchivesPath));
7495 // Create another subdir, so once we decide to remove all temporary archives,
7496 // we can remove only the subdir and the parent directory can still be used
7497 // for something else or similar in future. Otherwise, we would have to
7498 // figure out a new name for it.
7499 QM_TRY(MOZ_TO_RESULT(storageArchivesDir->Append(u"0"_ns)));
7501 PRExplodedTime now;
7502 PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
7504 const auto dateStr =
7505 nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32, now.tm_year,
7506 now.tm_month + 1, now.tm_mday);
7508 QM_TRY_INSPECT(
7509 const auto& storageArchiveDir,
7510 CloneFileAndAppend(*storageArchivesDir, NS_ConvertASCIItoUTF16(dateStr)));
7512 QM_TRY(MOZ_TO_RESULT(
7513 storageArchiveDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700)));
7515 QM_TRY_INSPECT(const auto& defaultStorageArchiveDir,
7516 CloneFileAndAppend(*storageArchiveDir,
7517 nsLiteralString(DEFAULT_DIRECTORY_NAME)));
7519 QM_TRY_INSPECT(const auto& temporaryStorageArchiveDir,
7520 CloneFileAndAppend(*storageArchiveDir,
7521 nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
7523 for (const auto& fullOriginMetadata : aFullOriginMetadatas) {
7524 MOZ_ASSERT(
7525 IsBestEffortPersistenceType(fullOriginMetadata.mPersistenceType));
7527 QM_TRY_INSPECT(const auto& directory,
7528 GetOriginDirectory(fullOriginMetadata));
7530 // The origin could have been removed, for example due to corruption.
7531 QM_TRY_INSPECT(
7532 const auto& moved,
7533 QM_OR_ELSE_WARN_IF(
7534 // Expression.
7535 MOZ_TO_RESULT(
7536 directory->MoveTo(fullOriginMetadata.mPersistenceType ==
7537 PERSISTENCE_TYPE_DEFAULT
7538 ? defaultStorageArchiveDir
7539 : temporaryStorageArchiveDir,
7540 u""_ns))
7541 .map([](Ok) { return true; }),
7542 // Predicate.
7543 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
7544 // Fallback.
7545 ErrToOk<false>));
7547 if (moved) {
7548 RemoveQuotaForOrigin(fullOriginMetadata.mPersistenceType,
7549 fullOriginMetadata);
7551 RemoveTemporaryOrigin(fullOriginMetadata);
7555 return Ok{};
7558 auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
7559 -> DirectoryLockTable& {
7560 switch (aPersistenceType) {
7561 case PERSISTENCE_TYPE_TEMPORARY:
7562 return mTemporaryDirectoryLockTable;
7563 case PERSISTENCE_TYPE_DEFAULT:
7564 return mDefaultDirectoryLockTable;
7565 case PERSISTENCE_TYPE_PRIVATE:
7566 return mPrivateDirectoryLockTable;
7568 case PERSISTENCE_TYPE_PERSISTENT:
7569 case PERSISTENCE_TYPE_INVALID:
7570 default:
7571 MOZ_CRASH("Bad persistence type value!");
7575 void QuotaManager::ClearDirectoryLockTables() {
7576 AssertIsOnOwningThread();
7578 for (const PersistenceType type : kBestEffortPersistenceTypes) {
7579 DirectoryLockTable& directoryLockTable = GetDirectoryLockTable(type);
7581 if (!IsShuttingDown()) {
7582 for (const auto& entry : directoryLockTable) {
7583 const auto& array = entry.GetData();
7585 // It doesn't matter which lock is used, they all have the same
7586 // persistence type and origin metadata.
7587 MOZ_ASSERT(!array->IsEmpty());
7588 const auto& lock = array->ElementAt(0);
7590 UpdateOriginAccessTime(lock->GetPersistenceType(),
7591 lock->OriginMetadata());
7595 directoryLockTable.Clear();
7599 bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) {
7600 AssertIsOnIOThread();
7602 // Do not parse this sanitized origin string, if we already parsed it.
7603 return mValidOrigins.LookupOrInsertWith(
7604 aSanitizedOrigin, [&aSanitizedOrigin] {
7605 nsCString spec;
7606 OriginAttributes attrs;
7607 nsCString originalSuffix;
7608 const auto result = OriginParser::ParseOrigin(aSanitizedOrigin, spec,
7609 &attrs, originalSuffix);
7611 return result == OriginParser::ValidOrigin;
7615 void QuotaManager::AddTemporaryOrigin(
7616 const FullOriginMetadata& aFullOriginMetadata) {
7617 AssertIsOnIOThread();
7618 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
7620 auto ioThreadData = mIOThreadAccessible.Access();
7622 auto& array = ioThreadData->mAllTemporaryOrigins.LookupOrInsert(
7623 aFullOriginMetadata.mGroup);
7625 DebugOnly<bool> containsOrigin = array.Contains(
7626 aFullOriginMetadata, [](const auto& aLeft, const auto& aRight) {
7627 if (aLeft.mPersistenceType == aRight.mPersistenceType) {
7628 return Compare(aLeft.mOrigin, aRight.mOrigin);
7630 return aLeft.mPersistenceType > aRight.mPersistenceType ? 1 : -1;
7632 MOZ_ASSERT(!containsOrigin);
7634 array.AppendElement(aFullOriginMetadata);
7636 if (IsThumbnailPrivateIdentityIdKnown() &&
7637 IsUserContextSuffix(aFullOriginMetadata.mSuffix,
7638 GetThumbnailPrivateIdentityId())) {
7639 AssertNoOverflow(
7640 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1);
7641 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount++;
7645 void QuotaManager::RemoveTemporaryOrigin(
7646 const OriginMetadata& aOriginMetadata) {
7647 AssertIsOnIOThread();
7648 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
7650 auto ioThreadData = mIOThreadAccessible.Access();
7652 auto entry =
7653 ioThreadData->mAllTemporaryOrigins.Lookup(aOriginMetadata.mGroup);
7654 if (!entry) {
7655 return;
7658 auto& array = *entry;
7660 DebugOnly<size_t> count =
7661 array.RemoveElementsBy([&aOriginMetadata](const auto& originMetadata) {
7662 return originMetadata.mPersistenceType ==
7663 aOriginMetadata.mPersistenceType &&
7664 originMetadata.mOrigin == aOriginMetadata.mOrigin;
7666 MOZ_ASSERT(count <= 1);
7668 if (array.IsEmpty()) {
7669 entry.Remove();
7672 if (IsThumbnailPrivateIdentityIdKnown() &&
7673 IsUserContextSuffix(aOriginMetadata.mSuffix,
7674 GetThumbnailPrivateIdentityId())) {
7675 AssertNoUnderflow(
7676 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1);
7677 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount--;
7681 void QuotaManager::RemoveTemporaryOrigins(PersistenceType aPersistenceType) {
7682 AssertIsOnIOThread();
7683 MOZ_ASSERT(IsBestEffortPersistenceType(aPersistenceType));
7685 auto ioThreadData = mIOThreadAccessible.Access();
7687 for (auto iter = ioThreadData->mAllTemporaryOrigins.Iter(); !iter.Done();
7688 iter.Next()) {
7689 auto& array = iter.Data();
7691 uint32_t thumbnailPrivateIdentityTemporaryOriginCount = 0;
7693 array.RemoveElementsBy([this, aPersistenceType,
7694 &thumbnailPrivateIdentityTemporaryOriginCount](
7695 const auto& originMetadata) {
7696 const bool match = originMetadata.mPersistenceType == aPersistenceType;
7697 if (!match) {
7698 return false;
7701 if (IsThumbnailPrivateIdentityIdKnown() &&
7702 IsUserContextSuffix(originMetadata.mSuffix,
7703 GetThumbnailPrivateIdentityId())) {
7704 AssertNoOverflow(thumbnailPrivateIdentityTemporaryOriginCount, 1);
7705 thumbnailPrivateIdentityTemporaryOriginCount++;
7708 return true;
7711 if (array.IsEmpty()) {
7712 iter.Remove();
7715 AssertNoUnderflow(
7716 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount,
7717 thumbnailPrivateIdentityTemporaryOriginCount);
7718 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount -=
7719 thumbnailPrivateIdentityTemporaryOriginCount;
7723 void QuotaManager::RemoveTemporaryOrigins() {
7724 AssertIsOnIOThread();
7726 auto ioThreadData = mIOThreadAccessible.Access();
7728 ioThreadData->mAllTemporaryOrigins.Clear();
7729 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount = 0;
7732 PrincipalMetadataArray QuotaManager::GetAllTemporaryGroups() const {
7733 AssertIsOnIOThread();
7735 auto ioThreadData = mIOThreadAccessible.Access();
7737 PrincipalMetadataArray principalMetadataArray;
7739 std::transform(ioThreadData->mAllTemporaryOrigins.Values().cbegin(),
7740 ioThreadData->mAllTemporaryOrigins.Values().cend(),
7741 MakeBackInserter(principalMetadataArray),
7742 [](const auto& array) {
7743 MOZ_ASSERT(!array.IsEmpty());
7745 // All items in the array have the same PrincipalMetadata,
7746 // so we can use any item to get it.
7747 return array[0];
7750 return principalMetadataArray;
7753 OriginMetadataArray QuotaManager::GetAllTemporaryOrigins() const {
7754 AssertIsOnIOThread();
7756 auto ioThreadData = mIOThreadAccessible.Access();
7758 OriginMetadataArray originMetadataArray;
7760 for (auto iter = ioThreadData->mAllTemporaryOrigins.ConstIter(); !iter.Done();
7761 iter.Next()) {
7762 const auto& array = iter.Data();
7764 for (const auto& originMetadata : array) {
7765 originMetadataArray.AppendElement(originMetadata);
7769 return originMetadataArray;
7772 uint32_t QuotaManager::ThumbnailPrivateIdentityTemporaryOriginCount() const {
7773 AssertIsOnIOThread();
7774 MOZ_ASSERT(IsThumbnailPrivateIdentityIdKnown());
7776 return mIOThreadAccessible.Access()
7777 ->mThumbnailPrivateIdentityTemporaryOriginCount;
7780 void QuotaManager::NoteInitializedOrigin(PersistenceType aPersistenceType,
7781 const nsACString& aOrigin) {
7782 AssertIsOnOwningThread();
7784 auto& boolArray = mInitializedOrigins.LookupOrInsertWith(aOrigin, []() {
7785 BoolArray boolArray;
7786 boolArray.AppendElements(PERSISTENCE_TYPE_INVALID);
7787 std::fill(boolArray.begin(), boolArray.end(), false);
7788 return boolArray;
7791 boolArray[aPersistenceType] = true;
7794 void QuotaManager::NoteUninitializedOrigins(
7795 const OriginMetadataArray& aOriginMetadataArray) {
7796 AssertIsOnOwningThread();
7798 for (const auto& originMetadata : aOriginMetadataArray) {
7799 auto entry = mInitializedOrigins.Lookup(originMetadata.mOrigin);
7800 if (!entry) {
7801 return;
7804 auto& boolArray = *entry;
7806 if (boolArray[originMetadata.mPersistenceType]) {
7807 boolArray[originMetadata.mPersistenceType] = false;
7809 if (std::all_of(boolArray.begin(), boolArray.end(),
7810 [](bool entry) { return !entry; })) {
7811 entry.Remove();
7817 void QuotaManager::NoteUninitializedRepository(
7818 PersistenceType aPersistenceType) {
7819 AssertIsOnOwningThread();
7821 for (auto iter = mInitializedOrigins.Iter(); !iter.Done(); iter.Next()) {
7822 auto& boolArray = iter.Data();
7824 if (boolArray[aPersistenceType]) {
7825 boolArray[aPersistenceType] = false;
7827 if (std::all_of(boolArray.begin(), boolArray.end(),
7828 [](bool entry) { return !entry; })) {
7829 iter.Remove();
7835 bool QuotaManager::IsOriginInitialized(PersistenceType aPersistenceType,
7836 const nsACString& aOrigin) const {
7837 AssertIsOnOwningThread();
7839 const auto entry = mInitializedOrigins.Lookup(aOrigin);
7841 return entry && (*entry)[aPersistenceType];
7844 Result<nsCString, nsresult> QuotaManager::EnsureStorageOriginFromOrigin(
7845 const nsACString& aOrigin) {
7846 MutexAutoLock lock(mQuotaMutex);
7848 QM_TRY_UNWRAP(
7849 auto storageOrigin,
7850 mOriginToStorageOriginMap.TryLookupOrInsertWith(
7851 aOrigin, [this, &aOrigin]() -> Result<nsCString, nsresult> {
7852 OriginAttributes originAttributes;
7854 nsCString originNoSuffix;
7855 QM_TRY(MOZ_TO_RESULT(
7856 originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix)));
7858 nsCOMPtr<nsIURI> uri;
7859 QM_TRY(MOZ_TO_RESULT(
7860 NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
7861 .SetSpec(originNoSuffix)
7862 .SetScheme(kUUIDOriginScheme)
7863 .SetHost(NSID_TrimBracketsASCII(nsID::GenerateUUID()))
7864 .SetPort(-1)
7865 .Finalize(uri)));
7867 nsCOMPtr<nsIPrincipal> principal =
7868 BasePrincipal::CreateContentPrincipal(uri, OriginAttributes{});
7869 QM_TRY(MOZ_TO_RESULT(principal));
7871 QM_TRY_UNWRAP(auto origin,
7872 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7873 nsAutoCString, principal, GetOrigin));
7875 mStorageOriginToOriginMap.WithEntryHandle(
7876 origin,
7877 [&aOrigin](auto entryHandle) { entryHandle.Insert(aOrigin); });
7879 return nsCString(std::move(origin));
7880 }));
7882 return nsCString(std::move(storageOrigin));
7885 Result<nsCString, nsresult> QuotaManager::GetOriginFromStorageOrigin(
7886 const nsACString& aStorageOrigin) {
7887 MutexAutoLock lock(mQuotaMutex);
7889 auto maybeOrigin = mStorageOriginToOriginMap.MaybeGet(aStorageOrigin);
7890 if (maybeOrigin.isNothing()) {
7891 return Err(NS_ERROR_FAILURE);
7894 return maybeOrigin.ref();
7897 int64_t QuotaManager::GenerateDirectoryLockId() {
7898 const int64_t directorylockId = mNextDirectoryLockId;
7900 if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1;
7901 result.isValid()) {
7902 mNextDirectoryLockId = result.value();
7903 } else {
7904 NS_WARNING("Quota manager has run out of ids for directory locks!");
7906 // There's very little chance for this to happen given the max size of
7907 // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
7908 // to zero since such old directory locks shouldn't exist anymore.
7909 mNextDirectoryLockId = 0;
7912 // TODO: Maybe add an assertion here to check that there is no existing
7913 // directory lock with given id.
7915 return directorylockId;
7918 template <typename Func>
7919 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
7920 Func&& aFunc)
7921 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
7922 Initialization, StringGenerator>&> {
7923 return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
7924 std::forward<Func>(aFunc));
7927 template <typename Func>
7928 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
7929 const nsACString& aContext,
7930 Func&& aFunc)
7931 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
7932 Initialization, StringGenerator>&> {
7933 return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
7934 aContext, std::forward<Func>(aFunc));
7937 template <typename Func>
7938 auto QuotaManager::ExecuteGroupInitialization(
7939 const nsACString& aGroup, const GroupInitialization aInitialization,
7940 const nsACString& aContext, Func&& aFunc)
7941 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
7942 Initialization, StringGenerator>&> {
7943 return quota::ExecuteInitialization(
7944 mInitializationInfo.MutableGroupInitializationInfoRef(
7945 aGroup, CreateIfNonExistent{}),
7946 aInitialization, aContext, std::forward<Func>(aFunc));
7949 template <typename Func>
7950 auto QuotaManager::ExecuteOriginInitialization(
7951 const nsACString& aOrigin, const OriginInitialization aInitialization,
7952 const nsACString& aContext, Func&& aFunc)
7953 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
7954 Initialization, StringGenerator>&> {
7955 return quota::ExecuteInitialization(
7956 mInitializationInfo.MutableOriginInitializationInfoRef(
7957 aOrigin, CreateIfNonExistent{}),
7958 aInitialization, aContext, std::forward<Func>(aFunc));
7961 void QuotaManager::IncreaseTotalDirectoryIterations() {
7962 AssertIsOnIOThread();
7964 auto ioThreadData = mIOThreadAccessible.Access();
7966 AssertNoOverflow(ioThreadData->mTotalDirectoryIterations, 1);
7967 ioThreadData->mTotalDirectoryIterations++;
7970 /*******************************************************************************
7971 * Local class implementations
7972 ******************************************************************************/
7974 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
7975 uint64_t aMinSizeToBeFreed)
7976 : Runnable("dom::quota::CollectOriginsHelper"),
7977 mMinSizeToBeFreed(aMinSizeToBeFreed),
7978 mMutex(aMutex),
7979 mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
7980 mSizeToBeFreed(0),
7981 mWaiting(true) {
7982 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
7983 mMutex.AssertCurrentThreadOwns();
7986 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
7987 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
7988 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
7989 mMutex.AssertCurrentThreadOwns();
7991 while (mWaiting) {
7992 mCondVar.Wait();
7995 mLocks.SwapElements(aLocks);
7996 return mSizeToBeFreed;
7999 NS_IMETHODIMP
8000 CollectOriginsHelper::Run() {
8001 AssertIsOnBackgroundThread();
8003 QuotaManager* quotaManager = QuotaManager::Get();
8004 NS_ASSERTION(quotaManager, "Shouldn't be null!");
8006 // We use extra stack vars here to avoid race detector warnings (the same
8007 // memory accessed with and without the lock held).
8008 nsTArray<RefPtr<OriginDirectoryLock>> locks;
8009 uint64_t sizeToBeFreed =
8010 quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
8012 MutexAutoLock lock(mMutex);
8014 NS_ASSERTION(mWaiting, "Huh?!");
8016 mLocks.SwapElements(locks);
8017 mSizeToBeFreed = sizeToBeFreed;
8018 mWaiting = false;
8019 mCondVar.Notify();
8021 return NS_OK;
8024 TimeStamp RecordTimeDeltaHelper::Start() {
8025 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
8027 // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
8028 // gLastOSWake is set, then this time duration would still be recorded with
8029 // key "Normal". We are assumming this is rather rare to happen.
8030 mStartTime.init(TimeStamp::Now());
8031 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
8033 return *mStartTime;
8036 TimeStamp RecordTimeDeltaHelper::End() {
8037 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
8039 mEndTime.init(TimeStamp::Now());
8040 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
8042 return *mEndTime;
8045 NS_IMETHODIMP
8046 RecordTimeDeltaHelper::Run() {
8047 MOZ_ASSERT(NS_IsMainThread());
8049 if (mInitializedTime.isSome()) {
8050 // Keys for QM_QUOTA_INFO_LOAD_TIME_V0 and QM_SHUTDOWN_TIME_V0:
8051 // Normal: Normal conditions.
8052 // WasSuspended: There was a OS sleep so that it was suspended.
8053 // TimeStampErr1: The recorded start time is unexpectedly greater than the
8054 // end time.
8055 // TimeStampErr2: The initialized time for the recording class is unexpectly
8056 // greater than the last OS wake time.
8057 const auto key = [this, wasSuspended = gLastOSWake > *mInitializedTime]() {
8058 if (wasSuspended) {
8059 return "WasSuspended"_ns;
8062 // XXX File a bug if we have data for this key.
8063 // We found negative values in our query in STMO for
8064 // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
8065 // because the documentation for TimeStamp::Now() says it returns a
8066 // monotonically increasing number.
8067 if (*mStartTime > *mEndTime) {
8068 return "TimeStampErr1"_ns;
8071 if (*mInitializedTime > gLastOSWake) {
8072 return "TimeStampErr2"_ns;
8075 return "Normal"_ns;
8076 }();
8078 Telemetry::AccumulateTimeDelta(mHistogram, key, *mStartTime, *mEndTime);
8080 return NS_OK;
8083 gLastOSWake = TimeStamp::Now();
8084 mInitializedTime.init(gLastOSWake);
8086 return NS_OK;
8089 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
8090 int64_t& aTimestamp,
8091 nsACString& aGroup,
8092 nsACString& aOrigin,
8093 Nullable<bool>& aIsApp) {
8094 AssertIsOnIOThread();
8095 MOZ_ASSERT(aDirectory);
8097 QM_TRY_INSPECT(
8098 const auto& binaryStream,
8099 GetBinaryInputStream(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
8101 QM_TRY_INSPECT(const uint64_t& timestamp,
8102 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
8104 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8105 nsCString, binaryStream, ReadCString));
8107 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8108 nsCString, binaryStream, ReadCString));
8110 Nullable<bool> isApp;
8111 bool value;
8112 if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) {
8113 isApp.SetValue(value);
8116 aTimestamp = timestamp;
8117 aGroup = group;
8118 aOrigin = origin;
8119 aIsApp = std::move(isApp);
8120 return NS_OK;
8123 nsresult StorageOperationBase::GetDirectoryMetadata2(
8124 nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix,
8125 nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) {
8126 AssertIsOnIOThread();
8127 MOZ_ASSERT(aDirectory);
8129 QM_TRY_INSPECT(const auto& binaryStream,
8130 GetBinaryInputStream(*aDirectory,
8131 nsLiteralString(METADATA_V2_FILE_NAME)));
8133 QM_TRY_INSPECT(const uint64_t& timestamp,
8134 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
8136 QM_TRY_INSPECT(const bool& persisted,
8137 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
8138 Unused << persisted;
8140 QM_TRY_INSPECT(const bool& reservedData1,
8141 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
8142 Unused << reservedData1;
8144 QM_TRY_INSPECT(const bool& reservedData2,
8145 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
8146 Unused << reservedData2;
8148 QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8149 nsCString, binaryStream, ReadCString));
8151 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8152 nsCString, binaryStream, ReadCString));
8154 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8155 nsCString, binaryStream, ReadCString));
8157 QM_TRY_INSPECT(const bool& isApp,
8158 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
8160 aTimestamp = timestamp;
8161 aSuffix = suffix;
8162 aGroup = group;
8163 aOrigin = origin;
8164 aIsApp = isApp;
8165 return NS_OK;
8168 int64_t StorageOperationBase::GetOriginLastModifiedTime(
8169 const OriginProps& aOriginProps) {
8170 return GetLastModifiedTime(*aOriginProps.mPersistenceType,
8171 *aOriginProps.mDirectory);
8174 nsresult StorageOperationBase::RemoveObsoleteOrigin(
8175 const OriginProps& aOriginProps) {
8176 AssertIsOnIOThread();
8178 QM_WARNING(
8179 "Deleting obsolete %s directory that is no longer a legal "
8180 "origin!",
8181 NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get());
8183 QM_TRY(MOZ_TO_RESULT(aOriginProps.mDirectory->Remove(/* recursive */ true)));
8185 return NS_OK;
8188 Result<bool, nsresult> StorageOperationBase::MaybeRenameOrigin(
8189 const OriginProps& aOriginProps) {
8190 AssertIsOnIOThread();
8192 const nsAString& oldLeafName = aOriginProps.mLeafName;
8194 const auto newLeafName =
8195 MakeSanitizedOriginString(aOriginProps.mOriginMetadata.mOrigin);
8197 if (oldLeafName == newLeafName) {
8198 return false;
8201 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
8202 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8203 aOriginProps.mOriginMetadata)));
8205 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
8206 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8207 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
8209 QM_TRY_INSPECT(const auto& newFile,
8210 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8211 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, GetParent));
8213 QM_TRY(MOZ_TO_RESULT(newFile->Append(newLeafName)));
8215 QM_TRY_INSPECT(const bool& exists,
8216 MOZ_TO_RESULT_INVOKE_MEMBER(newFile, Exists));
8218 if (exists) {
8219 QM_WARNING(
8220 "Can't rename %s directory to %s, the target already exists, removing "
8221 "instead of renaming!",
8222 NS_ConvertUTF16toUTF8(oldLeafName).get(),
8223 NS_ConvertUTF16toUTF8(newLeafName).get());
8226 QM_TRY(CallWithDelayedRetriesIfAccessDenied(
8227 [&exists, &aOriginProps, &newLeafName] {
8228 if (exists) {
8229 QM_TRY_RETURN(MOZ_TO_RESULT(
8230 aOriginProps.mDirectory->Remove(/* recursive */ true)));
8232 QM_TRY_RETURN(MOZ_TO_RESULT(
8233 aOriginProps.mDirectory->RenameTo(nullptr, newLeafName)));
8235 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
8236 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
8238 return true;
8241 nsresult StorageOperationBase::ProcessOriginDirectories() {
8242 AssertIsOnIOThread();
8243 MOZ_ASSERT(!mOriginProps.IsEmpty());
8245 QuotaManager* quotaManager = QuotaManager::Get();
8246 MOZ_ASSERT(quotaManager);
8248 for (auto& originProps : mOriginProps) {
8249 switch (originProps.mType) {
8250 case OriginProps::eChrome: {
8251 originProps.mOriginMetadata = {GetInfoForChrome(),
8252 *originProps.mPersistenceType};
8253 break;
8256 case OriginProps::eContent: {
8257 nsCOMPtr<nsIURI> uri;
8258 QM_TRY(
8259 MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), originProps.mSpec)));
8261 nsCOMPtr<nsIPrincipal> principal =
8262 BasePrincipal::CreateContentPrincipal(uri, originProps.mAttrs);
8263 QM_TRY(MOZ_TO_RESULT(principal));
8265 PrincipalInfo principalInfo;
8266 QM_TRY(
8267 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
8269 QM_WARNONLY_TRY_UNWRAP(
8270 auto valid, MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)));
8272 if (!valid) {
8273 // Unknown directories during upgrade are allowed. Just warn if we
8274 // find them.
8275 UNKNOWN_FILE_WARNING(originProps.mLeafName);
8276 originProps.mIgnore = true;
8277 break;
8280 QM_TRY_UNWRAP(
8281 auto principalMetadata,
8282 GetInfoFromValidatedPrincipalInfo(*quotaManager, principalInfo));
8284 originProps.mOriginMetadata = {std::move(principalMetadata),
8285 *originProps.mPersistenceType};
8287 break;
8290 case OriginProps::eObsolete: {
8291 // There's no way to get info for obsolete origins.
8292 break;
8295 default:
8296 MOZ_CRASH("Bad type!");
8300 // Don't try to upgrade obsolete origins, remove them right after we detect
8301 // them.
8302 for (const auto& originProps : mOriginProps) {
8303 if (originProps.mType == OriginProps::eObsolete) {
8304 MOZ_ASSERT(originProps.mOriginMetadata.mSuffix.IsEmpty());
8305 MOZ_ASSERT(originProps.mOriginMetadata.mGroup.IsEmpty());
8306 MOZ_ASSERT(originProps.mOriginMetadata.mOrigin.IsEmpty());
8308 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(originProps)));
8309 } else if (!originProps.mIgnore) {
8310 MOZ_ASSERT(!originProps.mOriginMetadata.mGroup.IsEmpty());
8311 MOZ_ASSERT(!originProps.mOriginMetadata.mOrigin.IsEmpty());
8313 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectory(originProps)));
8317 return NS_OK;
8320 // XXX Do the fallible initialization in a separate non-static member function
8321 // of StorageOperationBase and eventually get rid of this method and use a
8322 // normal constructor instead.
8323 template <typename PersistenceTypeFunc>
8324 nsresult StorageOperationBase::OriginProps::Init(
8325 PersistenceTypeFunc&& aPersistenceTypeFunc) {
8326 AssertIsOnIOThread();
8328 QM_TRY_INSPECT(const auto& leafName,
8329 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, *mDirectory,
8330 GetLeafName));
8332 // XXX Consider using QuotaManager::ParseOrigin here.
8333 nsCString spec;
8334 OriginAttributes attrs;
8335 nsCString originalSuffix;
8336 OriginParser::ResultType result = OriginParser::ParseOrigin(
8337 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
8338 if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) {
8339 mType = OriginProps::eInvalid;
8340 return NS_OK;
8343 const auto persistenceType = [&]() -> PersistenceType {
8344 // XXX We shouldn't continue with initialization if OriginParser returned
8345 // anything else but ValidOrigin. Otherwise, we have to deal with empty
8346 // spec when the origin is obsolete, like here. The caller should handle
8347 // the errors. Until it's fixed, we have to treat obsolete origins as
8348 // origins with unknown/invalid persistence type.
8349 if (result != OriginParser::ValidOrigin) {
8350 return PERSISTENCE_TYPE_INVALID;
8352 return std::forward<PersistenceTypeFunc>(aPersistenceTypeFunc)(spec);
8353 }();
8355 mLeafName = leafName;
8356 mSpec = spec;
8357 mAttrs = attrs;
8358 mOriginalSuffix = originalSuffix;
8359 mPersistenceType.init(persistenceType);
8360 if (result == OriginParser::ObsoleteOrigin) {
8361 mType = eObsolete;
8362 } else if (mSpec.EqualsLiteral(kChromeOrigin)) {
8363 mType = eChrome;
8364 } else {
8365 mType = eContent;
8368 return NS_OK;
8371 nsresult RepositoryOperationBase::ProcessRepository() {
8372 AssertIsOnIOThread();
8374 #ifdef DEBUG
8376 QM_TRY_INSPECT(const bool& exists,
8377 MOZ_TO_RESULT_INVOKE_MEMBER(mDirectory, Exists),
8378 QM_ASSERT_UNREACHABLE);
8379 MOZ_ASSERT(exists);
8381 #endif
8383 QM_TRY(CollectEachFileEntry(
8384 *mDirectory,
8385 [](const auto& originFile) -> Result<mozilla::Ok, nsresult> {
8386 QM_TRY_INSPECT(const auto& leafName,
8387 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8388 nsAutoString, originFile, GetLeafName));
8390 // Unknown files during upgrade are allowed. Just warn if we find
8391 // them.
8392 if (!IsOSMetadata(leafName)) {
8393 UNKNOWN_FILE_WARNING(leafName);
8396 return mozilla::Ok{};
8398 [&self = *this](const auto& originDir) -> Result<mozilla::Ok, nsresult> {
8399 OriginProps originProps(WrapMovingNotNullUnchecked(originDir));
8400 QM_TRY(MOZ_TO_RESULT(originProps.Init([&self](const auto& aSpec) {
8401 return self.PersistenceTypeFromSpec(aSpec);
8402 })));
8403 // Bypass invalid origins while upgrading
8404 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), mozilla::Ok{});
8406 if (originProps.mType != OriginProps::eObsolete) {
8407 QM_TRY_INSPECT(const bool& removed,
8408 MOZ_TO_RESULT_INVOKE_MEMBER(
8409 self, PrepareOriginDirectory, originProps));
8410 if (removed) {
8411 return mozilla::Ok{};
8415 self.mOriginProps.AppendElement(std::move(originProps));
8417 return mozilla::Ok{};
8418 }));
8420 if (mOriginProps.IsEmpty()) {
8421 return NS_OK;
8424 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
8426 return NS_OK;
8429 template <typename UpgradeMethod>
8430 nsresult RepositoryOperationBase::MaybeUpgradeClients(
8431 const OriginProps& aOriginProps, UpgradeMethod aMethod) {
8432 AssertIsOnIOThread();
8433 MOZ_ASSERT(aMethod);
8435 QuotaManager* quotaManager = QuotaManager::Get();
8436 MOZ_ASSERT(quotaManager);
8438 QM_TRY(CollectEachFileEntry(
8439 *aOriginProps.mDirectory,
8440 [](const auto& file) -> Result<mozilla::Ok, nsresult> {
8441 QM_TRY_INSPECT(
8442 const auto& leafName,
8443 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, GetLeafName));
8445 if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) {
8446 UNKNOWN_FILE_WARNING(leafName);
8449 return mozilla::Ok{};
8451 [quotaManager, &aMethod,
8452 &self = *this](const auto& dir) -> Result<mozilla::Ok, nsresult> {
8453 QM_TRY_INSPECT(
8454 const auto& leafName,
8455 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, dir, GetLeafName));
8457 QM_TRY_INSPECT(const bool& removed,
8458 MOZ_TO_RESULT_INVOKE_MEMBER(self, PrepareClientDirectory,
8459 dir, leafName));
8460 if (removed) {
8461 return mozilla::Ok{};
8464 Client::Type clientType;
8465 bool ok = Client::TypeFromText(leafName, clientType, fallible);
8466 if (!ok) {
8467 UNKNOWN_FILE_WARNING(leafName);
8468 return mozilla::Ok{};
8471 Client* client = quotaManager->GetClient(clientType);
8472 MOZ_ASSERT(client);
8474 QM_TRY(MOZ_TO_RESULT((client->*aMethod)(dir)));
8476 return mozilla::Ok{};
8477 }));
8479 return NS_OK;
8482 nsresult RepositoryOperationBase::PrepareClientDirectory(
8483 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
8484 AssertIsOnIOThread();
8486 aRemoved = false;
8487 return NS_OK;
8490 nsresult CreateOrUpgradeDirectoryMetadataHelper::Init() {
8491 AssertIsOnIOThread();
8492 MOZ_ASSERT(mDirectory);
8494 const auto maybeLegacyPersistenceType =
8495 LegacyPersistenceTypeFromFile(*mDirectory, fallible);
8496 QM_TRY(OkIf(maybeLegacyPersistenceType.isSome()), Err(NS_ERROR_FAILURE));
8498 mLegacyPersistenceType.init(maybeLegacyPersistenceType.value());
8500 return NS_OK;
8503 Maybe<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType>
8504 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
8505 nsIFile& aFile, const fallible_t&) {
8506 nsAutoString leafName;
8507 MOZ_ALWAYS_SUCCEEDS(aFile.GetLeafName(leafName));
8509 if (leafName.Equals(u"persistent"_ns)) {
8510 return Some(LegacyPersistenceType::Persistent);
8513 if (leafName.Equals(u"temporary"_ns)) {
8514 return Some(LegacyPersistenceType::Temporary);
8517 return Nothing();
8520 PersistenceType
8521 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
8522 const nsCString& aSpec) {
8523 if (QuotaManager::IsOriginInternal(aSpec)) {
8524 return PERSISTENCE_TYPE_PERSISTENT;
8527 return PERSISTENCE_TYPE_DEFAULT;
8530 PersistenceType CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
8531 const nsCString& aSpec) {
8532 switch (*mLegacyPersistenceType) {
8533 case LegacyPersistenceType::Persistent:
8534 return PersistenceTypeFromLegacyPersistentSpec(aSpec);
8535 case LegacyPersistenceType::Temporary:
8536 return PERSISTENCE_TYPE_TEMPORARY;
8538 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
8541 nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
8542 nsIFile* aDirectory) {
8543 AssertIsOnIOThread();
8544 MOZ_ASSERT(aDirectory);
8546 QM_TRY_INSPECT(
8547 const auto& metadataFile,
8548 CloneFileAndAppend(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
8550 QM_TRY_INSPECT(const bool& exists,
8551 MOZ_TO_RESULT_INVOKE_MEMBER(metadataFile, Exists));
8553 if (!exists) {
8554 // Directory structure upgrade needed.
8555 // Move all files to IDB specific directory.
8557 nsString idbDirectoryName;
8558 QM_TRY(OkIf(Client::TypeToText(Client::IDB, idbDirectoryName, fallible)),
8559 NS_ERROR_FAILURE);
8561 QM_TRY_INSPECT(const auto& idbDirectory,
8562 CloneFileAndAppend(*aDirectory, idbDirectoryName));
8564 // Usually we only use QM_OR_ELSE_LOG_VERBOSE/QM_OR_ELSE_LOG_VERBOSE_IF
8565 // with Create and NS_ERROR_FILE_ALREADY_EXISTS check, but typically the
8566 // idb directory shouldn't exist during the upgrade and the upgrade runs
8567 // only once in most of the cases, so the use of QM_OR_ELSE_WARN_IF is ok
8568 // here.
8569 QM_TRY(QM_OR_ELSE_WARN_IF(
8570 // Expression.
8571 MOZ_TO_RESULT(idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
8572 // Predicate.
8573 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
8574 // Fallback.
8575 ([&idbDirectory](const nsresult rv) -> Result<Ok, nsresult> {
8576 QM_TRY_INSPECT(
8577 const bool& isDirectory,
8578 MOZ_TO_RESULT_INVOKE_MEMBER(idbDirectory, IsDirectory));
8580 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
8582 return Ok{};
8583 })));
8585 QM_TRY(CollectEachFile(
8586 *aDirectory,
8587 [&idbDirectory, &idbDirectoryName](
8588 const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
8589 QM_TRY_INSPECT(const auto& leafName,
8590 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
8591 GetLeafName));
8593 if (!leafName.Equals(idbDirectoryName)) {
8594 QM_TRY(MOZ_TO_RESULT(file->MoveTo(idbDirectory, u""_ns)));
8597 return Ok{};
8598 }));
8600 QM_TRY(
8601 MOZ_TO_RESULT(metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
8604 return NS_OK;
8607 nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
8608 OriginProps& aOriginProps, bool* aRemoved) {
8609 AssertIsOnIOThread();
8610 MOZ_ASSERT(aRemoved);
8612 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
8613 QM_TRY(MOZ_TO_RESULT(
8614 MaybeUpgradeOriginDirectory(aOriginProps.mDirectory.get())));
8616 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
8617 } else {
8618 int64_t timestamp;
8619 nsCString group;
8620 nsCString origin;
8621 Nullable<bool> isApp;
8623 QM_WARNONLY_TRY_UNWRAP(
8624 const auto maybeDirectoryMetadata,
8625 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
8626 timestamp, group, origin, isApp)));
8627 if (!maybeDirectoryMetadata) {
8628 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
8629 aOriginProps.mNeedsRestore = true;
8630 } else if (!isApp.IsNull()) {
8631 aOriginProps.mIgnore = true;
8635 *aRemoved = false;
8636 return NS_OK;
8639 nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
8640 const OriginProps& aOriginProps) {
8641 AssertIsOnIOThread();
8643 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
8644 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
8645 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8646 aOriginProps.mOriginMetadata)));
8648 // Move internal origins to new persistent storage.
8649 if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps.mSpec) ==
8650 PERSISTENCE_TYPE_PERSISTENT) {
8651 if (!mPermanentStorageDir) {
8652 QuotaManager* quotaManager = QuotaManager::Get();
8653 MOZ_ASSERT(quotaManager);
8655 const nsString& permanentStoragePath =
8656 quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
8658 QM_TRY_UNWRAP(mPermanentStorageDir,
8659 QM_NewLocalFile(permanentStoragePath));
8662 const nsAString& leafName = aOriginProps.mLeafName;
8664 QM_TRY_INSPECT(const auto& newDirectory,
8665 CloneFileAndAppend(*mPermanentStorageDir, leafName));
8667 QM_TRY_INSPECT(const bool& exists,
8668 MOZ_TO_RESULT_INVOKE_MEMBER(newDirectory, Exists));
8670 if (exists) {
8671 QM_WARNING("Found %s in storage/persistent and storage/permanent !",
8672 NS_ConvertUTF16toUTF8(leafName).get());
8674 QM_TRY(MOZ_TO_RESULT(
8675 aOriginProps.mDirectory->Remove(/* recursive */ true)));
8676 } else {
8677 QM_TRY(MOZ_TO_RESULT(
8678 aOriginProps.mDirectory->MoveTo(mPermanentStorageDir, u""_ns)));
8681 } else if (aOriginProps.mNeedsRestore) {
8682 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
8683 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8684 aOriginProps.mOriginMetadata)));
8685 } else if (!aOriginProps.mIgnore) {
8686 QM_TRY_INSPECT(const auto& file,
8687 CloneFileAndAppend(*aOriginProps.mDirectory,
8688 nsLiteralString(METADATA_FILE_NAME)));
8690 QM_TRY_INSPECT(const auto& stream,
8691 GetBinaryOutputStream(*file, FileFlag::Append));
8693 MOZ_ASSERT(stream);
8695 // Currently unused (used to be isApp).
8696 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
8699 return NS_OK;
8702 nsresult UpgradeStorageHelperBase::Init() {
8703 AssertIsOnIOThread();
8704 MOZ_ASSERT(mDirectory);
8706 const auto maybePersistenceType =
8707 PersistenceTypeFromFile(*mDirectory, fallible);
8708 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
8710 mPersistenceType.init(maybePersistenceType.value());
8712 return NS_OK;
8715 PersistenceType UpgradeStorageHelperBase::PersistenceTypeFromSpec(
8716 const nsCString& aSpec) {
8717 // There's no moving of origin directories between repositories like in the
8718 // CreateOrUpgradeDirectoryMetadataHelper
8719 return *mPersistenceType;
8722 nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
8723 OriginProps& aOriginProps, bool* aRemoved) {
8724 AssertIsOnIOThread();
8725 MOZ_ASSERT(aRemoved);
8727 int64_t timestamp;
8728 nsCString group;
8729 nsCString origin;
8730 Nullable<bool> isApp;
8732 QM_WARNONLY_TRY_UNWRAP(
8733 const auto maybeDirectoryMetadata,
8734 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
8735 timestamp, group, origin, isApp)));
8736 if (!maybeDirectoryMetadata || isApp.IsNull()) {
8737 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
8738 aOriginProps.mNeedsRestore = true;
8739 } else {
8740 aOriginProps.mTimestamp = timestamp;
8743 *aRemoved = false;
8744 return NS_OK;
8747 nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
8748 const OriginProps& aOriginProps) {
8749 AssertIsOnIOThread();
8751 // This handles changes in origin string generation from nsIPrincipal,
8752 // especially the change from: appId+inMozBrowser+originNoSuffix
8753 // to: origin (with origin suffix).
8754 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
8755 if (renamed) {
8756 return NS_OK;
8759 if (aOriginProps.mNeedsRestore) {
8760 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
8761 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8762 aOriginProps.mOriginMetadata)));
8765 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
8766 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8767 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
8769 return NS_OK;
8772 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
8773 const OriginProps& aOriginProps) {
8774 AssertIsOnIOThread();
8776 // The Cache API was creating top level morgue directories by accident for
8777 // a short time in nightly. This unfortunately prevents all storage from
8778 // working. So recover these profiles permanently by removing these corrupt
8779 // directories as part of this upgrade.
8781 QM_TRY_INSPECT(const auto& morgueDir,
8782 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8783 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, Clone));
8785 QM_TRY(MOZ_TO_RESULT(morgueDir->Append(u"morgue"_ns)));
8787 QM_TRY_INSPECT(const bool& exists,
8788 MOZ_TO_RESULT_INVOKE_MEMBER(morgueDir, Exists));
8790 if (exists) {
8791 QM_WARNING("Deleting accidental morgue directory!");
8793 QM_TRY(MOZ_TO_RESULT(morgueDir->Remove(/* recursive */ true)));
8796 return NS_OK;
8799 Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
8800 const OriginProps& aOriginProps) {
8801 AssertIsOnIOThread();
8803 // TODO: This method was empty for some time due to accidental changes done
8804 // in bug 1320404. This led to renaming of origin directories like:
8805 // https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
8806 // to:
8807 // https+++developer.cdn.mozilla.net^inBrowser=1
8808 // instead of just removing them.
8810 const nsCString& originalSuffix = aOriginProps.mOriginalSuffix;
8811 if (!originalSuffix.IsEmpty()) {
8812 MOZ_ASSERT(originalSuffix[0] == '^');
8814 if (!URLParams::Parse(
8815 Substring(originalSuffix, 1, originalSuffix.Length() - 1), true,
8816 [](const nsACString& aName, const nsACString& aValue) {
8817 if (aName.EqualsLiteral("appId")) {
8818 return false;
8820 return true;
8821 })) {
8822 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(aOriginProps)));
8824 return true;
8828 return false;
8831 nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
8832 OriginProps& aOriginProps, bool* aRemoved) {
8833 AssertIsOnIOThread();
8834 MOZ_ASSERT(aRemoved);
8836 QM_TRY(MOZ_TO_RESULT(MaybeRemoveMorgueDirectory(aOriginProps)));
8838 QM_TRY(MOZ_TO_RESULT(
8839 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0)));
8841 QM_TRY_INSPECT(const bool& removed, MaybeRemoveAppsData(aOriginProps));
8842 if (removed) {
8843 *aRemoved = true;
8844 return NS_OK;
8847 int64_t timestamp;
8848 nsCString group;
8849 nsCString origin;
8850 Nullable<bool> isApp;
8851 QM_WARNONLY_TRY_UNWRAP(
8852 const auto maybeDirectoryMetadata,
8853 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
8854 timestamp, group, origin, isApp)));
8855 if (!maybeDirectoryMetadata || isApp.IsNull()) {
8856 aOriginProps.mNeedsRestore = true;
8859 nsCString suffix;
8860 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
8861 MOZ_TO_RESULT(GetDirectoryMetadata2(
8862 aOriginProps.mDirectory.get(), timestamp, suffix,
8863 group, origin, isApp.SetValue())));
8864 if (!maybeDirectoryMetadata2) {
8865 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
8866 aOriginProps.mNeedsRestore2 = true;
8867 } else {
8868 aOriginProps.mTimestamp = timestamp;
8871 *aRemoved = false;
8872 return NS_OK;
8875 nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
8876 const OriginProps& aOriginProps) {
8877 AssertIsOnIOThread();
8879 // This handles changes in origin string generation from nsIPrincipal,
8880 // especially the stripping of obsolete origin attributes like addonId.
8881 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
8882 if (renamed) {
8883 return NS_OK;
8886 if (aOriginProps.mNeedsRestore) {
8887 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
8888 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8889 aOriginProps.mOriginMetadata)));
8892 if (aOriginProps.mNeedsRestore2) {
8893 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
8894 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8895 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
8898 return NS_OK;
8901 nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
8902 OriginProps& aOriginProps, bool* aRemoved) {
8903 AssertIsOnIOThread();
8904 MOZ_ASSERT(aRemoved);
8906 QM_TRY(MOZ_TO_RESULT(
8907 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1)));
8909 int64_t timestamp;
8910 nsCString group;
8911 nsCString origin;
8912 Nullable<bool> isApp;
8913 QM_WARNONLY_TRY_UNWRAP(
8914 const auto maybeDirectoryMetadata,
8915 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
8916 timestamp, group, origin, isApp)));
8917 if (!maybeDirectoryMetadata || isApp.IsNull()) {
8918 aOriginProps.mNeedsRestore = true;
8921 nsCString suffix;
8922 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
8923 MOZ_TO_RESULT(GetDirectoryMetadata2(
8924 aOriginProps.mDirectory.get(), timestamp, suffix,
8925 group, origin, isApp.SetValue())));
8926 if (!maybeDirectoryMetadata2) {
8927 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
8928 aOriginProps.mNeedsRestore2 = true;
8929 } else {
8930 aOriginProps.mTimestamp = timestamp;
8933 *aRemoved = false;
8934 return NS_OK;
8937 nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
8938 const OriginProps& aOriginProps) {
8939 AssertIsOnIOThread();
8941 if (aOriginProps.mNeedsRestore) {
8942 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
8943 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8944 aOriginProps.mOriginMetadata)));
8947 if (aOriginProps.mNeedsRestore2) {
8948 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
8949 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8950 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
8953 return NS_OK;
8956 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
8957 OriginProps& aOriginProps, bool* aRemoved) {
8958 AssertIsOnIOThread();
8959 MOZ_ASSERT(aRemoved);
8961 QM_TRY(MOZ_TO_RESULT(
8962 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_1To2_2)));
8964 int64_t timestamp;
8965 nsCString group;
8966 nsCString origin;
8967 Nullable<bool> isApp;
8968 QM_WARNONLY_TRY_UNWRAP(
8969 const auto maybeDirectoryMetadata,
8970 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
8971 timestamp, group, origin, isApp)));
8972 if (!maybeDirectoryMetadata || isApp.IsNull()) {
8973 aOriginProps.mNeedsRestore = true;
8976 nsCString suffix;
8977 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
8978 MOZ_TO_RESULT(GetDirectoryMetadata2(
8979 aOriginProps.mDirectory.get(), timestamp, suffix,
8980 group, origin, isApp.SetValue())));
8981 if (!maybeDirectoryMetadata2) {
8982 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
8983 aOriginProps.mNeedsRestore2 = true;
8984 } else {
8985 aOriginProps.mTimestamp = timestamp;
8988 *aRemoved = false;
8989 return NS_OK;
8992 nsresult UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
8993 const OriginProps& aOriginProps) {
8994 AssertIsOnIOThread();
8996 if (aOriginProps.mNeedsRestore) {
8997 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
8998 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
8999 aOriginProps.mOriginMetadata)));
9002 if (aOriginProps.mNeedsRestore2) {
9003 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
9004 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
9005 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
9008 return NS_OK;
9011 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
9012 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
9013 AssertIsOnIOThread();
9015 if (Client::IsDeprecatedClient(aLeafName)) {
9016 QM_WARNING("Deleting deprecated %s client!",
9017 NS_ConvertUTF16toUTF8(aLeafName).get());
9019 QM_TRY(MOZ_TO_RESULT(aFile->Remove(true)));
9021 aRemoved = true;
9022 } else {
9023 aRemoved = false;
9026 return NS_OK;
9029 nsresult RestoreDirectoryMetadata2Helper::Init() {
9030 AssertIsOnIOThread();
9031 MOZ_ASSERT(mDirectory);
9033 nsCOMPtr<nsIFile> parentDir;
9034 QM_TRY(MOZ_TO_RESULT(mDirectory->GetParent(getter_AddRefs(parentDir))));
9036 const auto maybePersistenceType =
9037 PersistenceTypeFromFile(*parentDir, fallible);
9038 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
9040 mPersistenceType.init(maybePersistenceType.value());
9042 return NS_OK;
9045 nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
9046 OriginProps originProps(WrapMovingNotNull(mDirectory));
9047 QM_TRY(MOZ_TO_RESULT(originProps.Init(
9048 [&self = *this](const auto& aSpec) { return *self.mPersistenceType; })));
9050 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), NS_ERROR_FAILURE);
9052 originProps.mTimestamp = GetOriginLastModifiedTime(originProps);
9054 mOriginProps.AppendElement(std::move(originProps));
9056 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
9058 return NS_OK;
9061 nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
9062 const OriginProps& aOriginProps) {
9063 AssertIsOnIOThread();
9065 // We don't have any approach to restore aPersisted, so reset it to false.
9066 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
9067 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
9068 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
9070 return NS_OK;
9073 } // namespace mozilla::dom::quota